evolcore 0.0.32 → 0.0.34
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 +61 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +135 -37
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +111 -15
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +112 -54
- package/dist/agents/codex-runner.js +241 -63
- package/dist/agents/ecagent-runner.js +78 -33
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/channels/aun.js +44 -8
- package/dist/channels/daemon.js +261 -3
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +368 -31
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +197 -27
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +98 -67
- package/dist/cli/init.js +2 -2
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +166 -146
- package/dist/cli/queue-command.js +9 -4
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +78 -2
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +622 -0
- package/dist/cli/trigger-command.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +6 -3
- package/dist/config/config-manager.js +26 -2
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +45 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +133 -36
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +25 -2
- package/dist/core/auth/operation-catalog.js +49 -0
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +197 -27
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +51 -1
- package/dist/core/command/menu-handler.js +114 -23
- package/dist/core/command/menu-protocol.js +25 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +195 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +175 -3
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +83 -29
- package/dist/core/memory/consolidation-ops-records.js +49 -16
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +264 -205
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +169 -10
- package/dist/core/message/message-bridge.js +15 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +929 -110
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +217 -9
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +129 -14
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +21 -4
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +770 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +19 -7
- package/dist/ipc.js +140 -50
- package/dist/paths.js +93 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +13 -2
- package/dist/trigger/parser.js +70 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +143 -1
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +40 -28
- package/kits/docs/evolcore/agent.md +21 -6
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +5 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +4 -6
- package/kits/docs/evolcore/group.md +11 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -15
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +8 -11
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -1
- package/kits/eck_manifest.json +55 -40
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +199 -0
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +7 -2
- package/kits/templates/memory-consolidation/generalize.md +9 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +8 -3
- package/kits/templates/memory-consolidation/promote.md +8 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +67 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +7 -6
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +6 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
|
@@ -3,25 +3,29 @@ import path from 'path';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import crypto from 'crypto';
|
|
6
|
+
import { MAIN_CLI_BIN } from '../../product.js';
|
|
6
7
|
import { BaseagentRunnerUnavailableError, hasCompact, autoCompactWindowForModel, isClaudeContextUsageModel, numericToken, formatAsyncQuestionAnswer, parseAsyncUserQuestions, } from '../../agents/runner-types.js';
|
|
7
8
|
import { loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
8
9
|
import { isDiagnosticSessionActive } from '../command/diag-service.js';
|
|
9
10
|
import { evaluateInboundAdmissionPreflight, evaluateOrdinaryInboundAdmission } from './message-admission.js';
|
|
10
11
|
import { authorizeAccess, authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
11
12
|
import { buildTaskRuntimeEnv, ensureRuntimeLockDir, ensureSessionRuntimeDir } from '../../cli/task-context.js';
|
|
12
|
-
import {
|
|
13
|
+
import { BUILTIN_SESSION_PATH_GRANTS, mergeRuntimeGrantsForSession, resolveGrantedReadRoots, } from '../permission/session-path-grants.js';
|
|
14
|
+
import { buildSystemSessionContext, getSystemSessionModule, isSystemSessionType, readSystemSessionMarker } from '../system-session/registry.js';
|
|
13
15
|
import { IMRenderer } from './im-renderer.js';
|
|
16
|
+
import { projectSubtaskStarted, projectSubtaskTerminal } from './subtask-tool-projection.js';
|
|
14
17
|
import { TaskActivityStore } from '../task/task-activity-store.js';
|
|
15
18
|
import { createSendFileMarkerPattern } from './file-markers.js';
|
|
16
19
|
import { createDecisionInferenceProvider, createTextInferenceProvider } from '../inference/text-inference.js';
|
|
17
20
|
import { StreamIdleMonitor } from './stream-idle-monitor.js';
|
|
18
21
|
import { logger } from '../../utils/logger.js';
|
|
19
22
|
import { isHostChinese } from '../../utils/locale.js';
|
|
20
|
-
import { ExecutionTimeoutError, OrphanedTargetSessionError, isExecutionFailure, getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
|
|
23
|
+
import { ExecutionTimeoutError, OrphanedTargetSessionError, isExecutionFailure, getErrorMessage, getErrorRawText, getToolResultErrorText, classifyError, normalizeCoreErrorReason, normalizeCoreErrorSubtype, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
|
|
21
24
|
import { readEvolcoreRuntimeFlavor, tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
22
|
-
import { describeProactiveSendCommandFailure, isExactEvolcoreSendCommandForSession } from '../permission/ec-command-parser.js';
|
|
25
|
+
import { describeProactiveSendCommandFailure, isExactEvolcoreSendCommandForSession, isThreadRoutedEvolcoreSendCommand, parseCodexToolCommand, resolveCodexShellCarrierArgv, resolveCodexShellCarrierString, } from '../permission/ec-command-parser.js';
|
|
26
|
+
import { resolveThreadRouteTarget } from '../thread/thread-route.js';
|
|
23
27
|
import { summarizeToolInputForDisplay } from '../../utils/tool-summary.js';
|
|
24
|
-
import { agentDataDir, agentDir as resolveAgentDir, agentMdPath, getPackageRoot, resolveRoot, resolvePaths } from '../../paths.js';
|
|
28
|
+
import { agentDataDir, agentDir as resolveAgentDir, agentMdPath, getPackageRoot, resolveRoot, resolvePaths, debugSessionsDir } from '../../paths.js';
|
|
25
29
|
import { renderKitSections, resolveSessionManifest, DEFAULT_MANIFEST_FILE } from '../../eck/kit-renderer.js';
|
|
26
30
|
import { renderMessageBody } from '../../eck/message-renderer.js';
|
|
27
31
|
import { syncGroupRulesContext } from '../../eck/group-rules-sync.js';
|
|
@@ -67,6 +71,10 @@ import { auditFullAccessEvent } from '../auth/authorization-audit.js';
|
|
|
67
71
|
import { TaskRecoveryService, isTaskRecoveryCurrent } from '../task/task-recovery-service.js';
|
|
68
72
|
const QUESTION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
69
73
|
/** Pending async questions outlive the originating turn, but never change its backend binding. */
|
|
74
|
+
/** 带 --thread 的跨话题发送:话题存在于本端索引且目标一致时视为合法发送。 */
|
|
75
|
+
function isThreadRoutedSendForSelf(toolName, toolInput, selfAid) {
|
|
76
|
+
return isThreadRoutedEvolcoreSendCommand(toolName, toolInput, selfAid, (scope, threadId, target) => !!selfAid && !!resolveThreadRouteTarget(selfAid, threadId, scope, target));
|
|
77
|
+
}
|
|
70
78
|
export class AsyncQuestionService {
|
|
71
79
|
deps;
|
|
72
80
|
registered = new Map();
|
|
@@ -408,6 +416,381 @@ export class PauseController {
|
|
|
408
416
|
function isShowActivitiesMode(value) {
|
|
409
417
|
return value === 'all' || value === 'text' || value === 'none';
|
|
410
418
|
}
|
|
419
|
+
const TOOL_INPUT_DIAGNOSTIC_MAX_LENGTH = 96_000;
|
|
420
|
+
const TOOL_INPUT_DIAGNOSTIC_MAX_FINDINGS = 3;
|
|
421
|
+
const TOOL_INPUT_DIAGNOSTIC_TIMEOUT_MS = 1_000;
|
|
422
|
+
function isToolResultFailure(event) {
|
|
423
|
+
return event.isError === true
|
|
424
|
+
|| event.executionState === 'failed'
|
|
425
|
+
|| event.executionState === 'blocked'
|
|
426
|
+
|| event.decision === 'deny'
|
|
427
|
+
|| event.decision === 'error'
|
|
428
|
+
|| (typeof event.exitCode === 'number' && Number.isFinite(event.exitCode) && event.exitCode !== 0);
|
|
429
|
+
}
|
|
430
|
+
function isStringArgv(value) {
|
|
431
|
+
return Array.isArray(value) && value.every(item => typeof item === 'string');
|
|
432
|
+
}
|
|
433
|
+
function boundedDiagnosticInput(value) {
|
|
434
|
+
return value.length <= TOOL_INPUT_DIAGNOSTIC_MAX_LENGTH;
|
|
435
|
+
}
|
|
436
|
+
function findApplyPatchArgumentsOffset(value) {
|
|
437
|
+
const match = value.trimStart().match(/^(?:"([^"\r\n]+)"|'([^'\r\n]+)'|([^\s;&|<>"']+))(?=\s|[;&|<>]|$)/u);
|
|
438
|
+
const executable = match?.[1] ?? match?.[2] ?? match?.[3];
|
|
439
|
+
if (!executable)
|
|
440
|
+
return -1;
|
|
441
|
+
const basename = executable.replaceAll('\\', '/').split('/').at(-1)?.toLowerCase();
|
|
442
|
+
if (basename !== 'apply_patch' && basename !== 'apply_patch.exe')
|
|
443
|
+
return -1;
|
|
444
|
+
return value.length - value.trimStart().length + match[0].length;
|
|
445
|
+
}
|
|
446
|
+
function findUnquotedDiagnosticText(value, needle, startOffset) {
|
|
447
|
+
let quote = null;
|
|
448
|
+
let escaped = false;
|
|
449
|
+
for (let index = startOffset; index < value.length; index++) {
|
|
450
|
+
const char = value[index];
|
|
451
|
+
if (escaped) {
|
|
452
|
+
escaped = false;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (quote === 'single') {
|
|
456
|
+
if (char === "'")
|
|
457
|
+
quote = null;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (quote === 'double') {
|
|
461
|
+
if (char === '\\')
|
|
462
|
+
escaped = true;
|
|
463
|
+
else if (char === '"')
|
|
464
|
+
quote = null;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (char === '\\') {
|
|
468
|
+
escaped = true;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (char === "'") {
|
|
472
|
+
quote = 'single';
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (char === '"') {
|
|
476
|
+
quote = 'double';
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (value.startsWith(needle, index))
|
|
480
|
+
return index;
|
|
481
|
+
}
|
|
482
|
+
return -1;
|
|
483
|
+
}
|
|
484
|
+
function diagnosticLineEnd(value, startOffset) {
|
|
485
|
+
const newline = value.indexOf('\n', startOffset);
|
|
486
|
+
return newline < 0 ? value.length : newline;
|
|
487
|
+
}
|
|
488
|
+
function parseDiagnosticHeredoc(value, argumentsOffset) {
|
|
489
|
+
const openOffset = findUnquotedDiagnosticText(value, '<<', argumentsOffset);
|
|
490
|
+
if (openOffset < 0 || openOffset >= diagnosticLineEnd(value, argumentsOffset)
|
|
491
|
+
|| value[openOffset + 2] === '<')
|
|
492
|
+
return undefined;
|
|
493
|
+
let cursor = openOffset + 2;
|
|
494
|
+
const stripTabs = value[cursor] === '-';
|
|
495
|
+
if (stripTabs)
|
|
496
|
+
cursor++;
|
|
497
|
+
while (value[cursor] === ' ' || value[cursor] === '\t')
|
|
498
|
+
cursor++;
|
|
499
|
+
const quote = value[cursor] === "'" || value[cursor] === '"' ? value[cursor] : undefined;
|
|
500
|
+
if (quote)
|
|
501
|
+
cursor++;
|
|
502
|
+
const delimiterStart = cursor;
|
|
503
|
+
while (cursor < value.length) {
|
|
504
|
+
const char = value[cursor];
|
|
505
|
+
if (quote ? char === quote : char === ' ' || char === '\t' || char === '\r' || char === '\n')
|
|
506
|
+
break;
|
|
507
|
+
cursor++;
|
|
508
|
+
}
|
|
509
|
+
const delimiter = value.slice(delimiterStart, cursor);
|
|
510
|
+
if (!delimiter)
|
|
511
|
+
return undefined;
|
|
512
|
+
const firstNewline = value.indexOf('\n', cursor);
|
|
513
|
+
if (firstNewline < 0)
|
|
514
|
+
return {};
|
|
515
|
+
let lineStart = firstNewline + 1;
|
|
516
|
+
let trailing;
|
|
517
|
+
while (lineStart <= value.length) {
|
|
518
|
+
const end = diagnosticLineEnd(value, lineStart);
|
|
519
|
+
const rawLine = value.slice(lineStart, end).replace(/\r$/u, '');
|
|
520
|
+
const line = stripTabs ? rawLine.replace(/^\t+/u, '') : rawLine;
|
|
521
|
+
if (line === delimiter) {
|
|
522
|
+
let extraOffset;
|
|
523
|
+
let next = end < value.length ? end + 1 : value.length;
|
|
524
|
+
while (next < value.length) {
|
|
525
|
+
const nextEnd = diagnosticLineEnd(value, next);
|
|
526
|
+
const nextLine = value.slice(next, nextEnd).trim();
|
|
527
|
+
if (nextLine !== '' && !nextLine.startsWith('#')) {
|
|
528
|
+
extraOffset = next;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
next = nextEnd < value.length ? nextEnd + 1 : value.length;
|
|
532
|
+
}
|
|
533
|
+
return { delimiterOffset: lineStart + rawLine.length - line.length, extraOffset };
|
|
534
|
+
}
|
|
535
|
+
if (line.startsWith(delimiter) && line.length > delimiter.length) {
|
|
536
|
+
trailing ??= { trailingOffset: lineStart + rawLine.length - line.length + delimiter.length, trailingEndOffset: end };
|
|
537
|
+
}
|
|
538
|
+
if (end >= value.length)
|
|
539
|
+
break;
|
|
540
|
+
lineStart = end + 1;
|
|
541
|
+
}
|
|
542
|
+
return trailing ?? {};
|
|
543
|
+
}
|
|
544
|
+
function findDiagnosticShellSeparator(value, startOffset, endOffset) {
|
|
545
|
+
let quote = null;
|
|
546
|
+
let escaped = false;
|
|
547
|
+
for (let index = startOffset; index < endOffset; index++) {
|
|
548
|
+
const char = value[index];
|
|
549
|
+
if (escaped) {
|
|
550
|
+
escaped = false;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (quote === 'single') {
|
|
554
|
+
if (char === "'")
|
|
555
|
+
quote = null;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (quote === 'double') {
|
|
559
|
+
if (char === '\\')
|
|
560
|
+
escaped = true;
|
|
561
|
+
else if (char === '"')
|
|
562
|
+
quote = null;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (char === '\\') {
|
|
566
|
+
escaped = true;
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (char === "'") {
|
|
570
|
+
quote = 'single';
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (char === '"') {
|
|
574
|
+
quote = 'double';
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (char === ';' || char === '|' || char === '&' || char === '\n' || char === '\r')
|
|
578
|
+
return index;
|
|
579
|
+
}
|
|
580
|
+
return -1;
|
|
581
|
+
}
|
|
582
|
+
function diagnosticLocation(source, startOffset, endOffset = startOffset) {
|
|
583
|
+
const start = Math.max(0, Math.min(startOffset, source.value.length));
|
|
584
|
+
const end = Math.max(start, Math.min(endOffset, source.value.length));
|
|
585
|
+
const lineStart = Math.max(source.value.lastIndexOf('\n', Math.max(0, start - 1)) + 1, 0);
|
|
586
|
+
return {
|
|
587
|
+
field: source.field,
|
|
588
|
+
...(source.argumentIndex !== undefined ? { argumentIndex: source.argumentIndex } : {}),
|
|
589
|
+
line: source.value.slice(0, start).split('\n').length,
|
|
590
|
+
column: [...source.value.slice(lineStart, start)].length + 1,
|
|
591
|
+
startOffset: start,
|
|
592
|
+
endOffset: end,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function diagnosticFinding(ruleId, title, advice, confidence, source, startOffset, endOffset) {
|
|
596
|
+
return { ruleId, title, advice, confidence, location: diagnosticLocation(source, startOffset, endOffset) };
|
|
597
|
+
}
|
|
598
|
+
function diagnosticCandidates(input) {
|
|
599
|
+
const candidates = [];
|
|
600
|
+
if (typeof input.command === 'string' && boundedDiagnosticInput(input.command)) {
|
|
601
|
+
const source = { field: 'command', value: input.command };
|
|
602
|
+
const carrier = resolveCodexShellCarrierString(input.command);
|
|
603
|
+
if (carrier) {
|
|
604
|
+
const commandOffset = input.command.indexOf(carrier.command);
|
|
605
|
+
candidates.push({
|
|
606
|
+
source,
|
|
607
|
+
command: carrier.command,
|
|
608
|
+
commandOffset: commandOffset >= 0 ? commandOffset : 0,
|
|
609
|
+
carrier: true,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
candidates.push({ source, command: input.command, commandOffset: 0, carrier: false });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (isStringArgv(input.commandArgv) && input.commandArgv.every(boundedDiagnosticInput)) {
|
|
617
|
+
const carrier = resolveCodexShellCarrierArgv(input.commandArgv);
|
|
618
|
+
if (carrier) {
|
|
619
|
+
const argumentIndex = input.commandArgv.length - 1;
|
|
620
|
+
candidates.push({
|
|
621
|
+
source: { field: 'commandArgv', value: input.commandArgv[argumentIndex], argumentIndex },
|
|
622
|
+
command: carrier.command,
|
|
623
|
+
commandOffset: 0,
|
|
624
|
+
carrier: true,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
const argumentIndex = 0;
|
|
629
|
+
if (findApplyPatchArgumentsOffset(input.commandArgv[argumentIndex] ?? '') >= 0) {
|
|
630
|
+
candidates.push({
|
|
631
|
+
source: { field: 'commandArgv', value: input.commandArgv[argumentIndex], argumentIndex },
|
|
632
|
+
command: input.commandArgv[argumentIndex],
|
|
633
|
+
commandOffset: 0,
|
|
634
|
+
carrier: false,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return candidates;
|
|
640
|
+
}
|
|
641
|
+
function addHeredocDiagnosticFindings(findings, candidate) {
|
|
642
|
+
const argumentsOffset = findApplyPatchArgumentsOffset(candidate.command);
|
|
643
|
+
if (argumentsOffset < 0)
|
|
644
|
+
return;
|
|
645
|
+
const heredoc = parseDiagnosticHeredoc(candidate.command, argumentsOffset);
|
|
646
|
+
if (!heredoc)
|
|
647
|
+
return;
|
|
648
|
+
const map = (offset) => candidate.commandOffset + offset;
|
|
649
|
+
if (heredoc.trailingOffset !== undefined) {
|
|
650
|
+
findings.push(diagnosticFinding('heredoc-terminator-trailing-characters', 'heredoc 结束标记行包含尾随字符', '让结束标记单独占一行,不要在标记后追加引号、命令或其它字符。', '高', candidate.source, map(heredoc.trailingOffset), map(heredoc.trailingEndOffset ?? heredoc.trailingOffset)));
|
|
651
|
+
}
|
|
652
|
+
else if (heredoc.delimiterOffset === undefined) {
|
|
653
|
+
findings.push(diagnosticFinding('heredoc-terminator-missing', '未找到匹配的 heredoc 结束标记', '补上与起始标记完全一致、且单独占一行的结束标记。', '高', candidate.source, map(candidate.command.length), map(candidate.command.length)));
|
|
654
|
+
}
|
|
655
|
+
else if (heredoc.extraOffset !== undefined) {
|
|
656
|
+
findings.push(diagnosticFinding('heredoc-extra-command', 'heredoc 结束标记后仍有额外命令或文本', '将 apply_patch 与测试或其它命令拆成两次工具调用。', '高', candidate.source, map(heredoc.extraOffset), map(heredoc.extraOffset)));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function addApplyPatchCompositionFinding(findings, candidate) {
|
|
660
|
+
const argumentsOffset = findApplyPatchArgumentsOffset(candidate.command);
|
|
661
|
+
if (argumentsOffset < 0 || candidate.carrier)
|
|
662
|
+
return;
|
|
663
|
+
const executableMatch = candidate.command.trimStart().match(/^(?:"([^"]+)"|'([^']+)'|([^\s]+))/u);
|
|
664
|
+
const executable = executableMatch?.[1] ?? executableMatch?.[2] ?? executableMatch?.[3] ?? '';
|
|
665
|
+
const basename = executable.replaceAll('\\', '/').split('/').at(-1)?.toLowerCase();
|
|
666
|
+
if (basename !== 'apply_patch' && basename !== 'apply_patch.exe')
|
|
667
|
+
return;
|
|
668
|
+
const heredocOffset = findUnquotedDiagnosticText(candidate.command, '<<', argumentsOffset);
|
|
669
|
+
const endOffset = heredocOffset >= 0 ? heredocOffset : candidate.command.length;
|
|
670
|
+
const separator = findDiagnosticShellSeparator(candidate.command, argumentsOffset, endOffset);
|
|
671
|
+
if (separator < 0)
|
|
672
|
+
return;
|
|
673
|
+
findings.push(diagnosticFinding('apply-patch-composed-command', 'apply_patch 与其它 Shell 命令组合提交', '一次工具调用只提交完整的 apply_patch;测试、检查或其它命令另行调用。', '高', candidate.source, candidate.commandOffset + separator, candidate.commandOffset + separator + 1));
|
|
674
|
+
}
|
|
675
|
+
function addNestedShellDiagnosticFinding(findings, candidate) {
|
|
676
|
+
if (!candidate.carrier || findApplyPatchArgumentsOffset(candidate.command) < 0)
|
|
677
|
+
return;
|
|
678
|
+
findings.push(diagnosticFinding('nested-shell-escaping-risk', '命令包含嵌套 Shell 包装层', '尽量直接提交单层 Shell 命令;需要 heredoc 或 JSON 时,减少外层转义和包装。', '中', candidate.source, candidate.commandOffset, candidate.commandOffset + candidate.command.length));
|
|
679
|
+
}
|
|
680
|
+
function addJsonArrayDiagnosticFinding(findings, input) {
|
|
681
|
+
const rawArgv = isStringArgv(input.commandArgv) ? input.commandArgv : undefined;
|
|
682
|
+
const parsedArgv = rawArgv ?? (typeof input.command === 'string'
|
|
683
|
+
? (() => {
|
|
684
|
+
const parsed = parseCodexToolCommand({ command: input.command });
|
|
685
|
+
return parsed.ok ? parsed.argv : undefined;
|
|
686
|
+
})()
|
|
687
|
+
: undefined);
|
|
688
|
+
if (!parsedArgv || parsedArgv.length < 6)
|
|
689
|
+
return;
|
|
690
|
+
if (parsedArgv[0].toLowerCase() !== MAIN_CLI_BIN || parsedArgv[1].toLowerCase() !== 'aid'
|
|
691
|
+
|| parsedArgv[2].toLowerCase() !== 'agentmd' || parsedArgv[3].toLowerCase() !== 'set'
|
|
692
|
+
|| parsedArgv[5].toLowerCase() !== 'tags')
|
|
693
|
+
return;
|
|
694
|
+
const value = parsedArgv[6];
|
|
695
|
+
const source = rawArgv
|
|
696
|
+
? { field: 'commandArgv', value: rawArgv[6] ?? '', argumentIndex: 6 }
|
|
697
|
+
: { field: 'command', value: typeof input.command === 'string' ? input.command : '' };
|
|
698
|
+
if (value === undefined) {
|
|
699
|
+
findings.push(diagnosticFinding('json-array-argument-missing', 'tags 参数缺少完整 JSON 数组值', '将 tags 作为一个完整参数传入,例如 PowerShell 使用单引号包住 ["DSH","AUN"]。', '高', source, source.value.length));
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
let valid = false;
|
|
703
|
+
try {
|
|
704
|
+
const decoded = JSON.parse(value);
|
|
705
|
+
valid = Array.isArray(decoded) && decoded.every(item => typeof item === 'string');
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
valid = false;
|
|
709
|
+
}
|
|
710
|
+
if (valid)
|
|
711
|
+
return;
|
|
712
|
+
const split = rawArgv !== undefined && rawArgv.length > 7 && !rawArgv[7].startsWith('--')
|
|
713
|
+
&& (value.startsWith('[') || value.endsWith(','));
|
|
714
|
+
findings.push(diagnosticFinding(split ? 'json-array-argument-splitting' : 'json-array-argument-invalid', split ? 'tags 的 JSON 数组疑似被拆成多个参数' : 'tags 参数不是可解析的 JSON 字符串数组', '保证整个 JSON 数组位于同一个参数中;PowerShell 普通单引号字符串内不要再添加反斜杠转义。', split ? '中' : '高', source, split ? source.value.length : 0, source.value.length));
|
|
715
|
+
}
|
|
716
|
+
function addCommandArgvMismatchFinding(findings, input) {
|
|
717
|
+
if (typeof input.command !== 'string' || !isStringArgv(input.commandArgv))
|
|
718
|
+
return;
|
|
719
|
+
const commandArgv = input.commandArgv;
|
|
720
|
+
if (!boundedDiagnosticInput(input.command) || commandArgv.some(value => !boundedDiagnosticInput(value)))
|
|
721
|
+
return;
|
|
722
|
+
if (resolveCodexShellCarrierArgv(commandArgv) || resolveCodexShellCarrierString(input.command))
|
|
723
|
+
return;
|
|
724
|
+
const parsed = parseCodexToolCommand({ command: input.command });
|
|
725
|
+
if (!parsed.ok || parsed.argv.length === commandArgv.length
|
|
726
|
+
&& parsed.argv.every((value, index) => value === commandArgv[index]))
|
|
727
|
+
return;
|
|
728
|
+
findings.push(diagnosticFinding('command-argv-mismatch', 'command 与 commandArgv 的解析结果不一致', '以执行器提供的结构化 commandArgv 为准,避免在外层重新拼接或改写命令文本。', '中', { field: 'command', value: input.command }, 0, input.command.length));
|
|
729
|
+
}
|
|
730
|
+
function deduplicateDiagnosticFindings(findings) {
|
|
731
|
+
const seen = new Set();
|
|
732
|
+
return findings.filter(item => {
|
|
733
|
+
const key = `${item.ruleId}:${item.location.field}:${item.location.argumentIndex ?? ''}:${item.location.startOffset}`;
|
|
734
|
+
if (seen.has(key))
|
|
735
|
+
return false;
|
|
736
|
+
seen.add(key);
|
|
737
|
+
return true;
|
|
738
|
+
}).slice(0, TOOL_INPUT_DIAGNOSTIC_MAX_FINDINGS);
|
|
739
|
+
}
|
|
740
|
+
function diagnoseToolInput(original, event, deadline) {
|
|
741
|
+
if (!isToolResultFailure(event) || !original.input || typeof original.input !== 'object')
|
|
742
|
+
return undefined;
|
|
743
|
+
const input = original.input;
|
|
744
|
+
if (typeof input.command === 'string' && !boundedDiagnosticInput(input.command))
|
|
745
|
+
return undefined;
|
|
746
|
+
if (Array.isArray(input.commandArgv)) {
|
|
747
|
+
if (input.commandArgv.length > 1_024 || !isStringArgv(input.commandArgv)
|
|
748
|
+
|| input.commandArgv.reduce((length, value) => length + value.length, 0) > TOOL_INPUT_DIAGNOSTIC_MAX_LENGTH) {
|
|
749
|
+
return undefined;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const candidates = diagnosticCandidates(input);
|
|
753
|
+
if (Date.now() > deadline)
|
|
754
|
+
return undefined;
|
|
755
|
+
const findings = [];
|
|
756
|
+
for (const candidate of candidates) {
|
|
757
|
+
if (Date.now() > deadline)
|
|
758
|
+
return undefined;
|
|
759
|
+
addHeredocDiagnosticFindings(findings, candidate);
|
|
760
|
+
addApplyPatchCompositionFinding(findings, candidate);
|
|
761
|
+
addNestedShellDiagnosticFinding(findings, candidate);
|
|
762
|
+
}
|
|
763
|
+
if (Date.now() <= deadline)
|
|
764
|
+
addJsonArrayDiagnosticFinding(findings, input);
|
|
765
|
+
if (Date.now() <= deadline)
|
|
766
|
+
addCommandArgvMismatchFinding(findings, input);
|
|
767
|
+
if (Date.now() > deadline)
|
|
768
|
+
return undefined;
|
|
769
|
+
const deduplicated = deduplicateDiagnosticFindings(findings);
|
|
770
|
+
return deduplicated.length > 0 ? { findings: deduplicated } : undefined;
|
|
771
|
+
}
|
|
772
|
+
export function diagnoseToolInputAfterFailure(original, event) {
|
|
773
|
+
if (!original)
|
|
774
|
+
return undefined;
|
|
775
|
+
try {
|
|
776
|
+
return diagnoseToolInput(original, event, Date.now() + TOOL_INPUT_DIAGNOSTIC_TIMEOUT_MS);
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function formatToolInputDiagnosticLocation(location) {
|
|
783
|
+
const field = location.argumentIndex === undefined
|
|
784
|
+
? location.field
|
|
785
|
+
: `${location.field}[${location.argumentIndex}]`;
|
|
786
|
+
return `${field},第 ${location.line} 行第 ${location.column} 列,UTF-16 偏移 ${location.startOffset}-${location.endOffset}`;
|
|
787
|
+
}
|
|
788
|
+
export function formatToolInputDiagnostics(report) {
|
|
789
|
+
if (!report || report.findings.length === 0)
|
|
790
|
+
return '';
|
|
791
|
+
const lines = report.findings.map(item => `- [${item.confidence}] ${item.title}(${formatToolInputDiagnosticLocation(item.location)})\n 建议:${item.advice}`);
|
|
792
|
+
return `\n\n输入核验(辅助):\n${lines.join('\n')}`;
|
|
793
|
+
}
|
|
411
794
|
function approvalActorRevision(actor, owners = [], admins = [], selfAid) {
|
|
412
795
|
if (!actor)
|
|
413
796
|
return undefined;
|
|
@@ -526,8 +909,10 @@ function markRetryHealthRecorded(error) {
|
|
|
526
909
|
function wasRetryHealthRecorded(error) {
|
|
527
910
|
return !!(error && typeof error === 'object' && error[RETRY_HEALTH_RECORDED_MARK] === true);
|
|
528
911
|
}
|
|
529
|
-
function markModelFallbackExhausted(error, message) {
|
|
912
|
+
function markModelFallbackExhausted(error, message, cause) {
|
|
530
913
|
error[MODEL_FALLBACK_EXHAUSTED_MARK] = message;
|
|
914
|
+
if (cause !== undefined)
|
|
915
|
+
error.cause = cause;
|
|
531
916
|
return error;
|
|
532
917
|
}
|
|
533
918
|
function getModelFallbackExhaustedMessage(error) {
|
|
@@ -548,7 +933,7 @@ function wasRetryInputAlreadySubmitted(error) {
|
|
|
548
933
|
return !!(error && typeof error === 'object' && error[RETRY_INPUT_ALREADY_SUBMITTED_MARK] === true);
|
|
549
934
|
}
|
|
550
935
|
function formatRetryInputAlreadySubmittedMessage(error) {
|
|
551
|
-
const reason =
|
|
936
|
+
const reason = getErrorRawText(error) || 'Codex 暂时不可用';
|
|
552
937
|
return `⚠️ Codex 已接收本次消息,但本轮未能确认完成。为避免在会话中重复写入同一消息,系统未自动重试。请检查会话状态后重新发送。\n原因:${reason}`;
|
|
553
938
|
}
|
|
554
939
|
function isNonErrorInterruptReason(reason) {
|
|
@@ -561,7 +946,7 @@ function isExpectedTerminalInterrupt(reason) {
|
|
|
561
946
|
return isAbortTerminalReason(reason);
|
|
562
947
|
}
|
|
563
948
|
function formatRetryableErrorFinalMessage(error, retries) {
|
|
564
|
-
const reason =
|
|
949
|
+
const reason = getErrorRawText(error) || 'API 暂时不可用';
|
|
565
950
|
return `❌ API 暂时不可用,已自动重试 ${retries} 次仍失败,任务已停止。\n原因:${reason}`;
|
|
566
951
|
}
|
|
567
952
|
/**
|
|
@@ -672,17 +1057,23 @@ function getStreamErrorText(result) {
|
|
|
672
1057
|
}
|
|
673
1058
|
return parts.join('\n');
|
|
674
1059
|
}
|
|
1060
|
+
function getStreamRawErrorText(result, fallback) {
|
|
1061
|
+
const fullText = result.fullText.trim();
|
|
1062
|
+
const fullTextLooksLikeDiagnostic = fullText.length > 0 && (isRetryableError(new Error(fullText))
|
|
1063
|
+
|| isContextTooLongText(fullText));
|
|
1064
|
+
const fallbackLooksLikeDiagnostic = typeof fallback === 'string' && fallback.trim().length > 0 && (isRetryableError(new Error(fallback))
|
|
1065
|
+
|| isContextTooLongText(fallback));
|
|
1066
|
+
const candidates = [
|
|
1067
|
+
result.syntheticDiagnosticText,
|
|
1068
|
+
result.errors?.join('\n'),
|
|
1069
|
+
fullTextLooksLikeDiagnostic ? fullText : undefined,
|
|
1070
|
+
fallbackLooksLikeDiagnostic ? fallback : undefined,
|
|
1071
|
+
];
|
|
1072
|
+
return candidates.find((part) => typeof part === 'string' && part.trim().length > 0)?.trim();
|
|
1073
|
+
}
|
|
675
1074
|
function getStreamErrorMessage(result, includeEmoji = false) {
|
|
676
|
-
const raw = getStreamErrorText(result)
|
|
677
|
-
|
|
678
|
-
const generic = includeEmoji ? '❌ 处理消息时出错,请稍后重试' : '处理消息时出错,请稍后重试';
|
|
679
|
-
// Unknown runner-specific terminal reasons do not have a friendly mapping.
|
|
680
|
-
// Preserve their diagnostic text instead of collapsing it to the generic
|
|
681
|
-
// "处理消息时出错" message.
|
|
682
|
-
if (raw && mapped === generic) {
|
|
683
|
-
return includeEmoji ? `❌ ${raw}` : raw;
|
|
684
|
-
}
|
|
685
|
-
return mapped;
|
|
1075
|
+
const raw = getStreamErrorText(result);
|
|
1076
|
+
return getErrorMessage(new Error(raw), result.terminalReason, includeEmoji);
|
|
686
1077
|
}
|
|
687
1078
|
function streamHitContextLimit(result) {
|
|
688
1079
|
return result.terminalReason === 'prompt_too_long' ||
|
|
@@ -826,6 +1217,8 @@ export class ResponseEngine {
|
|
|
826
1217
|
activeMonitors = new Map();
|
|
827
1218
|
/** sessionId → 当前正在处理任务的运行时上下文,供 in-task CLI 通过 IPC 查询。 */
|
|
828
1219
|
activeTaskRuntimeContexts = new Map();
|
|
1220
|
+
/** sessionId → 本轮最近一次 runQuery 请求的模型;仅在任务活跃时对外可见(见 getOutboundDebug)。 */
|
|
1221
|
+
activeRunnerModels = new Map();
|
|
829
1222
|
/** task.run started 后立即登记,覆盖运行时上下文尚未建立的 shutdown 窗口。 */
|
|
830
1223
|
activeTaskSpans = new Map();
|
|
831
1224
|
/** sessionId → 尚未收到 runner:task-notification 的子任务。 */
|
|
@@ -993,7 +1386,7 @@ export class ResponseEngine {
|
|
|
993
1386
|
// revocation for legacy events without a taskId.
|
|
994
1387
|
const interruptedTaskId = eventTaskId;
|
|
995
1388
|
if (interruptedTaskId && activeTask?.taskId === interruptedTaskId) {
|
|
996
|
-
this.agentDelegationRegistry?.revokeTask(event.sessionId, interruptedTaskId);
|
|
1389
|
+
this.agentDelegationRegistry?.revokeTask(event.sessionId, interruptedTaskId, 'interrupted');
|
|
997
1390
|
}
|
|
998
1391
|
}
|
|
999
1392
|
});
|
|
@@ -1337,7 +1730,8 @@ export class ResponseEngine {
|
|
|
1337
1730
|
|| this.isPauseRequested(sessionId) || this.retryDelayWaiters.has(sessionId))
|
|
1338
1731
|
return { state: 'live', ...identity };
|
|
1339
1732
|
const runner = this.getAgent(session.metadata?.channelKey || session.channel, session.baseagent, selfAid);
|
|
1340
|
-
|
|
1733
|
+
const owned = runner.hasActiveOperation?.(sessionId, active.taskId) ?? runner.hasActiveStream(sessionId);
|
|
1734
|
+
return { state: owned ? 'live' : 'missing', ...identity,
|
|
1341
1735
|
hasTerminalOwner: this.activeTimeoutHandlers.has(sessionId) };
|
|
1342
1736
|
}
|
|
1343
1737
|
// Never adopt or interrupt a newer task to clean up an older attempt.
|
|
@@ -1380,6 +1774,14 @@ export class ResponseEngine {
|
|
|
1380
1774
|
const interruptedTaskId = interruptedTurn?.active?.taskId;
|
|
1381
1775
|
this.interruptedSessions.set(sessionId, effectiveReason);
|
|
1382
1776
|
this.messageQueue?.markActiveTerminal?.(sessionId, effectiveReason);
|
|
1777
|
+
const channelKey = session.metadata?.channelKey || session.channel;
|
|
1778
|
+
const agent = this.getAgent(channelKey, session.baseagent, session.selfAID);
|
|
1779
|
+
const cancelledQueuedTurn = interruptedTurn?.active
|
|
1780
|
+
? agent.cancelQueuedTurn?.(sessionId, {
|
|
1781
|
+
taskId: interruptedTurn.active.taskId,
|
|
1782
|
+
generation: interruptedTurn.active.generation,
|
|
1783
|
+
}) ?? false
|
|
1784
|
+
: false;
|
|
1383
1785
|
// 终态必须先于渠道发送和 runner I/O 落盘。否则任一外部调用卡住时,
|
|
1384
1786
|
// shutdown 超时退出会再次留下 started/interrupting 的悬空任务。
|
|
1385
1787
|
const runtimeContext = this.activeTaskRuntimeContexts.get(sessionId);
|
|
@@ -1404,13 +1806,12 @@ export class ResponseEngine {
|
|
|
1404
1806
|
this.sessionManager.clearProcessingIfTask(sessionId, taskId);
|
|
1405
1807
|
if (this.activeTaskRuntimeContexts.get(sessionId)?.taskId === taskId) {
|
|
1406
1808
|
this.activeTaskRuntimeContexts.delete(sessionId);
|
|
1809
|
+
this.activeRunnerModels.delete(sessionId);
|
|
1407
1810
|
}
|
|
1408
|
-
this.agentDelegationRegistry?.revokeTask(sessionId, taskId);
|
|
1811
|
+
this.agentDelegationRegistry?.revokeTask(sessionId, taskId, 'interrupted');
|
|
1409
1812
|
}
|
|
1410
1813
|
this.closeActiveRunnerTasks(sessionId, effectiveReason, taskSpan?.causation ?? runtimeContext?.causation);
|
|
1411
1814
|
await this.interactionRouter?.cancelAll(sessionId, effectiveReason);
|
|
1412
|
-
const channelKey = session.metadata?.channelKey || session.channel;
|
|
1413
|
-
const agent = this.getAgent(channelKey, session.baseagent, session.selfAID);
|
|
1414
1815
|
const terminalStatus = (() => {
|
|
1415
1816
|
// TimeoutController owns timeout delivery (including user-facing timeout
|
|
1416
1817
|
// details) and already guarantees a single terminal status. Sending it
|
|
@@ -1441,7 +1842,9 @@ export class ResponseEngine {
|
|
|
1441
1842
|
logger.debug(`[ResponseEngine] interrupt terminal status failure: session=${sessionId} task=${interruptedTaskId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1442
1843
|
});
|
|
1443
1844
|
})();
|
|
1444
|
-
const runnerInterrupt =
|
|
1845
|
+
const runnerInterrupt = (cancelledQueuedTurn
|
|
1846
|
+
? Promise.resolve()
|
|
1847
|
+
: agent.interrupt(sessionId)).catch(error => {
|
|
1445
1848
|
logger.debug(`[ResponseEngine] interrupt barrier runner failure: session=${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1446
1849
|
if (strict)
|
|
1447
1850
|
throw error;
|
|
@@ -1482,6 +1885,64 @@ export class ResponseEngine {
|
|
|
1482
1885
|
getTaskRuntimeContext(sessionId) {
|
|
1483
1886
|
return this.activeTaskRuntimeContexts.get(sessionId) ?? null;
|
|
1484
1887
|
}
|
|
1888
|
+
/** debug 模式下写/删 debug-sessions 临时文件 */
|
|
1889
|
+
debugWriteSessionSnapshot(sessionId, ctx) {
|
|
1890
|
+
if (process.env.EVOLCORE_DEBUG_MODE !== '1')
|
|
1891
|
+
return;
|
|
1892
|
+
try {
|
|
1893
|
+
const dir = debugSessionsDir();
|
|
1894
|
+
if (!fs.existsSync(dir))
|
|
1895
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1896
|
+
const snapshot = {
|
|
1897
|
+
schemaVersion: 1,
|
|
1898
|
+
sessionId,
|
|
1899
|
+
selfAid: ctx.selfAid ?? '',
|
|
1900
|
+
channel: ctx.channel ?? '',
|
|
1901
|
+
channelKey: ctx.channelKey ?? '',
|
|
1902
|
+
channelId: ctx.channelId ?? '',
|
|
1903
|
+
peerKey: ctx.peerKey ?? '',
|
|
1904
|
+
chatType: ctx.chatType ?? 'private',
|
|
1905
|
+
threadId: ctx.threadId ?? '',
|
|
1906
|
+
sessionType: ctx.sessionType ?? 'main',
|
|
1907
|
+
baseagent: ctx.baseagent ?? '',
|
|
1908
|
+
writtenAt: Date.now(),
|
|
1909
|
+
};
|
|
1910
|
+
fs.writeFileSync(`${dir}/${sessionId}.json`, JSON.stringify(snapshot), 'utf-8');
|
|
1911
|
+
}
|
|
1912
|
+
catch (err) {
|
|
1913
|
+
logger.debug(`[Debug] failed to write debug session snapshot for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
debugDeleteSessionSnapshot(sessionId) {
|
|
1917
|
+
if (process.env.EVOLCORE_DEBUG_MODE !== '1')
|
|
1918
|
+
return;
|
|
1919
|
+
try {
|
|
1920
|
+
const snapshotPath = `${debugSessionsDir()}/${sessionId}.json`;
|
|
1921
|
+
if (fs.existsSync(snapshotPath))
|
|
1922
|
+
fs.rmSync(snapshotPath, { force: true });
|
|
1923
|
+
}
|
|
1924
|
+
catch (err) {
|
|
1925
|
+
logger.debug(`[Debug] failed to delete debug session snapshot for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* 出站 payload.debug 的会话侧字段(不含 sentAt,由发送方盖戳)。
|
|
1930
|
+
* 供 agent 经 CLI(ec msg send / ec group send)委托发送的路径使用,
|
|
1931
|
+
* 与 IMRenderer 回复路径写入的 debug 同构。会话不存在返回 undefined。
|
|
1932
|
+
*/
|
|
1933
|
+
async getOutboundDebug(sessionId) {
|
|
1934
|
+
const session = await this.sessionManager.getSessionById(sessionId);
|
|
1935
|
+
if (!session)
|
|
1936
|
+
return undefined;
|
|
1937
|
+
const model = this.activeTaskRuntimeContexts.has(sessionId)
|
|
1938
|
+
? this.activeRunnerModels.get(sessionId)
|
|
1939
|
+
: undefined;
|
|
1940
|
+
return {
|
|
1941
|
+
sessionId: session.id,
|
|
1942
|
+
...(session.agentSessionId ? { agentSessionId: session.agentSessionId } : {}),
|
|
1943
|
+
...(model ? { model } : {}),
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1485
1946
|
setAgentDelegationRegistry(registry) {
|
|
1486
1947
|
this.agentDelegationRegistry = registry;
|
|
1487
1948
|
}
|
|
@@ -1700,9 +2161,15 @@ export class ResponseEngine {
|
|
|
1700
2161
|
if (this.turnCoordinator.isCurrent(turnLease))
|
|
1701
2162
|
return false;
|
|
1702
2163
|
logger.info(`[ResponseEngine] Stale turn detected after agent.runQuery start; cancelling unconsumed stream: session=${sessionId} task=${taskId}`);
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
2164
|
+
const cancelledQueuedTurn = agent.cancelQueuedTurn?.(sessionId, {
|
|
2165
|
+
taskId: turnLease.taskId,
|
|
2166
|
+
generation: turnLease.generation,
|
|
2167
|
+
}) ?? false;
|
|
2168
|
+
if (!cancelledQueuedTurn) {
|
|
2169
|
+
await agent.interrupt(sessionId).catch(interruptError => {
|
|
2170
|
+
logger.warn(`[ResponseEngine] Failed to cancel stale runner start: session=${sessionId} task=${taskId} error=${interruptError instanceof Error ? interruptError.message : String(interruptError)}`);
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
1706
2173
|
agent.cleanupStream(streamKey);
|
|
1707
2174
|
return true;
|
|
1708
2175
|
}
|
|
@@ -1796,6 +2263,71 @@ export class ResponseEngine {
|
|
|
1796
2263
|
&& message.source === 'trigger'
|
|
1797
2264
|
&& !!message.triggerMeta?.triggerId;
|
|
1798
2265
|
}
|
|
2266
|
+
isTrustedBgSession(message) {
|
|
2267
|
+
return message.channel === 'daemon'
|
|
2268
|
+
&& message.source === 'bg-session'
|
|
2269
|
+
&& !!message.bgSessionMeta?.runId;
|
|
2270
|
+
}
|
|
2271
|
+
bgSessionTerminalMessages = new WeakSet();
|
|
2272
|
+
claimBgSessionTerminal(message) {
|
|
2273
|
+
const meta = message.bgSessionMeta;
|
|
2274
|
+
if (message.source !== 'bg-session' || !meta?.runId || this.bgSessionTerminalMessages.has(message)) {
|
|
2275
|
+
return undefined;
|
|
2276
|
+
}
|
|
2277
|
+
this.bgSessionTerminalMessages.add(message);
|
|
2278
|
+
return { meta, runId: meta.runId };
|
|
2279
|
+
}
|
|
2280
|
+
publishBgSessionError(message, error, opts = {}) {
|
|
2281
|
+
const terminal = this.claimBgSessionTerminal(message);
|
|
2282
|
+
if (!terminal)
|
|
2283
|
+
return;
|
|
2284
|
+
const { meta, runId } = terminal;
|
|
2285
|
+
this.eventBus.publish({
|
|
2286
|
+
type: 'bg-session:error',
|
|
2287
|
+
kind: meta.kind,
|
|
2288
|
+
runId,
|
|
2289
|
+
sessionId: meta.boundSessionId ?? '',
|
|
2290
|
+
messageId: opts.messageId ?? message.messageId ?? runId,
|
|
2291
|
+
error,
|
|
2292
|
+
targetChannel: message.channel,
|
|
2293
|
+
targetChannelId: message.channelId,
|
|
2294
|
+
causation: opts.causation ?? message.causation,
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
publishBgSessionInterrupted(message, reason, causation) {
|
|
2298
|
+
const terminal = this.claimBgSessionTerminal(message);
|
|
2299
|
+
if (!terminal)
|
|
2300
|
+
return;
|
|
2301
|
+
const { meta, runId } = terminal;
|
|
2302
|
+
this.eventBus.publish({
|
|
2303
|
+
type: 'bg-session:interrupted',
|
|
2304
|
+
kind: meta.kind,
|
|
2305
|
+
runId,
|
|
2306
|
+
sessionId: meta.boundSessionId ?? '',
|
|
2307
|
+
messageId: message.messageId ?? runId,
|
|
2308
|
+
reason,
|
|
2309
|
+
targetChannel: message.channel,
|
|
2310
|
+
targetChannelId: message.channelId,
|
|
2311
|
+
causation: causation ?? message.causation,
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
publishBgSessionCompleted(message, messageId, durationMs, causation) {
|
|
2315
|
+
const terminal = this.claimBgSessionTerminal(message);
|
|
2316
|
+
if (!terminal)
|
|
2317
|
+
return;
|
|
2318
|
+
const { meta, runId } = terminal;
|
|
2319
|
+
this.eventBus.publish({
|
|
2320
|
+
type: 'bg-session:completed',
|
|
2321
|
+
kind: meta.kind,
|
|
2322
|
+
runId,
|
|
2323
|
+
sessionId: meta.boundSessionId ?? '',
|
|
2324
|
+
messageId,
|
|
2325
|
+
durationMs,
|
|
2326
|
+
targetChannel: message.channel,
|
|
2327
|
+
targetChannelId: message.channelId,
|
|
2328
|
+
causation: causation ?? message.causation,
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
1799
2331
|
/**
|
|
1800
2332
|
* A fullaccess Trigger may run with no current relation access for its
|
|
1801
2333
|
* origin actor, but only after its persisted owner provenance still
|
|
@@ -1859,6 +2391,8 @@ export class ResponseEngine {
|
|
|
1859
2391
|
if (!selfAid || !actorId || !channelType || !originChannelKey)
|
|
1860
2392
|
return undefined;
|
|
1861
2393
|
const chatType = message.triggerMeta.originChatType ?? 'private';
|
|
2394
|
+
if (chatType === 'group' && !message.triggerMeta.originConversationId)
|
|
2395
|
+
return undefined;
|
|
1862
2396
|
const conversationId = message.triggerMeta.originConversationId ?? actorId;
|
|
1863
2397
|
const control = message.triggerMeta.originControl === true;
|
|
1864
2398
|
let actor;
|
|
@@ -1889,6 +2423,7 @@ export class ResponseEngine {
|
|
|
1889
2423
|
actorId,
|
|
1890
2424
|
channelType,
|
|
1891
2425
|
originChannelKey,
|
|
2426
|
+
conversationId: chatType === 'group' ? conversationId : actorId,
|
|
1892
2427
|
control,
|
|
1893
2428
|
peerKey: formatPeerKey(channelType, chatType === 'group' ? conversationId : actorId),
|
|
1894
2429
|
};
|
|
@@ -1958,8 +2493,11 @@ export class ResponseEngine {
|
|
|
1958
2493
|
/** Publish a terminal outcome before an early return from Trigger execution. */
|
|
1959
2494
|
publishTriggerExecutionFailure(message, error, opts = {}) {
|
|
1960
2495
|
const terminal = this.claimTriggerTerminal(message);
|
|
1961
|
-
if (!terminal)
|
|
2496
|
+
if (!terminal) {
|
|
2497
|
+
// bg-session 路径:trigger terminal 不匹配时尝试 bg-session
|
|
2498
|
+
this.publishBgSessionError(message, error, { messageId: opts.messageId, causation: opts.causation });
|
|
1962
2499
|
return;
|
|
2500
|
+
}
|
|
1963
2501
|
const { trigger, runId } = terminal;
|
|
1964
2502
|
const attemptId = trigger.attemptId;
|
|
1965
2503
|
this.eventBus.publish({
|
|
@@ -1980,8 +2518,11 @@ export class ResponseEngine {
|
|
|
1980
2518
|
}
|
|
1981
2519
|
publishTriggerExecutionSkipped(message, reason, causation, interruption) {
|
|
1982
2520
|
const terminal = this.claimTriggerTerminal(message);
|
|
1983
|
-
if (!terminal)
|
|
2521
|
+
if (!terminal) {
|
|
2522
|
+
// bg-session 路径
|
|
2523
|
+
this.publishBgSessionInterrupted(message, reason, causation);
|
|
1984
2524
|
return;
|
|
2525
|
+
}
|
|
1985
2526
|
const { trigger, runId } = terminal;
|
|
1986
2527
|
this.eventBus.publish({
|
|
1987
2528
|
type: 'trigger:skipped',
|
|
@@ -2017,6 +2558,14 @@ export class ResponseEngine {
|
|
|
2017
2558
|
logger.warn(`[ResponseEngine] Failed to close interrupted Trigger run=${message.triggerMeta?.runId ?? '<unknown>'}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2018
2559
|
});
|
|
2019
2560
|
}
|
|
2561
|
+
else if (this.isTrustedBgSession(message)) {
|
|
2562
|
+
await adapter.send(envelope, {
|
|
2563
|
+
kind: 'status.interrupted',
|
|
2564
|
+
metadata: { reason },
|
|
2565
|
+
}).catch(error => {
|
|
2566
|
+
logger.warn(`[ResponseEngine] Failed to close interrupted bg-session run=${message.bgSessionMeta?.runId ?? '<unknown>'}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2020
2569
|
this.publishTriggerExecutionSkipped(message, reason, causation, interruption);
|
|
2021
2570
|
}
|
|
2022
2571
|
publishTriggerExecutionCompleted(message, messageId, durationMs, causation) {
|
|
@@ -2888,8 +3437,12 @@ export class ResponseEngine {
|
|
|
2888
3437
|
? { kind: 'restartResume', groupId: message.groupId, threadId: message.threadId }
|
|
2889
3438
|
: undefined,
|
|
2890
3439
|
previousTurnState: this.turnCoordinator.current(session),
|
|
2891
|
-
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, chatType, selfAid),
|
|
2892
|
-
describeSendCommandFailure: describeProactiveSendCommandFailure,
|
|
3440
|
+
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, chatType, selfAid) || isThreadRoutedSendForSelf(toolName, toolInput, selfAid),
|
|
3441
|
+
describeSendCommandFailure: (toolName, toolInput) => describeProactiveSendCommandFailure(toolName, toolInput, {
|
|
3442
|
+
selfAid,
|
|
3443
|
+
channelId: session.channelId,
|
|
3444
|
+
chatType: chatType,
|
|
3445
|
+
}),
|
|
2893
3446
|
logger,
|
|
2894
3447
|
} : null;
|
|
2895
3448
|
if (resolvedMode?.mode.beforeProcess && modeProcessCtx) {
|
|
@@ -2952,8 +3505,9 @@ export class ResponseEngine {
|
|
|
2952
3505
|
this.sessionManager.clearProcessingIfTask(session.id, taskId);
|
|
2953
3506
|
if (this.activeTaskRuntimeContexts.get(session.id)?.taskId === taskId) {
|
|
2954
3507
|
this.activeTaskRuntimeContexts.delete(session.id);
|
|
3508
|
+
this.activeRunnerModels.delete(session.id); // F1修复
|
|
2955
3509
|
}
|
|
2956
|
-
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
3510
|
+
this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'interrupted');
|
|
2957
3511
|
const daemonTrigger = this.isTrustedDaemonTrigger(message);
|
|
2958
3512
|
if (!daemonTrigger) {
|
|
2959
3513
|
await adapter.send(envelope, statusPayload).catch(() => { });
|
|
@@ -2961,7 +3515,11 @@ export class ResponseEngine {
|
|
|
2961
3515
|
await adapter.send(envelope, {
|
|
2962
3516
|
kind: 'result.error',
|
|
2963
3517
|
text: userMessage,
|
|
2964
|
-
|
|
3518
|
+
...(getErrorRawText(timeoutError) ? { raw: getErrorRawText(timeoutError) } : {}),
|
|
3519
|
+
subtype: normalizeCoreErrorSubtype(timeoutError, undefined, isTotalExecutionTimeout ? 'timeout' : 'timeout'),
|
|
3520
|
+
reason: isExecutionFailure(timeoutError)
|
|
3521
|
+
? normalizeCoreErrorReason(timeoutError.reason)
|
|
3522
|
+
: 'timeout',
|
|
2965
3523
|
}).catch(() => { });
|
|
2966
3524
|
if (daemonTrigger) {
|
|
2967
3525
|
await adapter.send(envelope, statusPayload).catch(() => { });
|
|
@@ -3301,9 +3859,9 @@ export class ResponseEngine {
|
|
|
3301
3859
|
await adapter.send(withEnvelopeReplyContext(envelope, capturedReplyContext), { kind: 'result.text', text, isFinal: false });
|
|
3302
3860
|
};
|
|
3303
3861
|
const authChatType = configChatType;
|
|
3304
|
-
const authConversationId = authChatType === 'group'
|
|
3862
|
+
const authConversationId = triggerExecutionIdentity?.conversationId ?? (authChatType === 'group'
|
|
3305
3863
|
? (session.metadata?.groupId || session.channelId || capturedChannelId)
|
|
3306
|
-
: configActorId;
|
|
3864
|
+
: configActorId);
|
|
3307
3865
|
const authPeerKey = configChannelType && authConversationId
|
|
3308
3866
|
? formatPeerKey(configChannelType, authConversationId)
|
|
3309
3867
|
: undefined;
|
|
@@ -3429,11 +3987,53 @@ export class ResponseEngine {
|
|
|
3429
3987
|
? resolvedMode.mode.configureRun(modeProcessCtx)
|
|
3430
3988
|
: undefined;
|
|
3431
3989
|
const modePolicyHook = runModeConfig?.policyHook;
|
|
3432
|
-
//
|
|
3433
|
-
|
|
3434
|
-
const
|
|
3435
|
-
|
|
3436
|
-
|
|
3990
|
+
// 系统会话:路径 / 工具由 system-session 模块判定,与角色体系无关。
|
|
3991
|
+
// 有标记就只走模块,不挂模式策略、不算授权表、不并 runtime grant。
|
|
3992
|
+
const sessionMetadata = session.metadata;
|
|
3993
|
+
const declaredSessionType = session.sessionType ?? (typeof sessionMetadata?.sessionType === 'string' ? sessionMetadata.sessionType : undefined);
|
|
3994
|
+
const systemSessionMarker = readSystemSessionMarker(sessionMetadata);
|
|
3995
|
+
if (isSystemSessionType(declaredSessionType) && !systemSessionMarker) {
|
|
3996
|
+
throw new Error(`系统会话 ${declaredSessionType} 缺少有效 systemSession 标记`);
|
|
3997
|
+
}
|
|
3998
|
+
if (systemSessionMarker && declaredSessionType && declaredSessionType !== systemSessionMarker.type) {
|
|
3999
|
+
throw new Error(`系统会话标记类型 ${systemSessionMarker.type} 与 sessionType ${declaredSessionType} 不一致`);
|
|
4000
|
+
}
|
|
4001
|
+
const systemSessionModule = systemSessionMarker
|
|
4002
|
+
? getSystemSessionModule(systemSessionMarker.type)
|
|
4003
|
+
: undefined;
|
|
4004
|
+
const systemSessionContext = systemSessionModule && systemSessionMarker
|
|
4005
|
+
? buildSystemSessionContext({
|
|
4006
|
+
marker: systemSessionMarker,
|
|
4007
|
+
sessionId: session.id,
|
|
4008
|
+
selfAid: selfAid || session.selfAID || '',
|
|
4009
|
+
projectPath: absoluteProjectPath,
|
|
4010
|
+
managedTempDir: sessionRuntimeDir,
|
|
4011
|
+
metadata: sessionMetadata,
|
|
4012
|
+
})
|
|
4013
|
+
: undefined;
|
|
4014
|
+
const systemSessionCheck = systemSessionModule && systemSessionContext
|
|
4015
|
+
? (toolName, toolInput) => systemSessionModule.checkTool(toolName, toolInput, systemSessionContext)
|
|
4016
|
+
: undefined;
|
|
4017
|
+
// 沙箱声明与 runner 有关,但当前所有 runner 共用同一个结论;仍按 runner 问一次,
|
|
4018
|
+
// 以便将来某个 runner 需要不同取舍时只改模块。
|
|
4019
|
+
const systemSessionSandbox = systemSessionModule && systemSessionContext
|
|
4020
|
+
? systemSessionModule.runnerLaunch(normalizeBaseagent(agent.name).canonical, systemSessionContext).sandbox
|
|
4021
|
+
: undefined;
|
|
4022
|
+
const pureSessionPolicyHook = systemSessionMarker ? undefined : modePolicyHook;
|
|
4023
|
+
// 路径授权(仅用户会话):身份就是角色名;运行时授予(ec ctl path grant)按 sessionId 再并一层。
|
|
4024
|
+
const pathGrantIdentity = systemSessionMarker ? undefined : (peerRole || undefined);
|
|
4025
|
+
const grantedReadRoots = (() => {
|
|
4026
|
+
if (!pathGrantIdentity)
|
|
4027
|
+
return [];
|
|
4028
|
+
const table = mergeRuntimeGrantsForSession(BUILTIN_SESSION_PATH_GRANTS, session.id, pathGrantIdentity);
|
|
4029
|
+
return resolveGrantedReadRoots(table, {
|
|
4030
|
+
selfAid,
|
|
4031
|
+
peerKey: authPeerKey,
|
|
4032
|
+
projectPath: absoluteProjectPath,
|
|
4033
|
+
identity: pathGrantIdentity,
|
|
4034
|
+
permissionMode: effectivePermissionMode,
|
|
4035
|
+
});
|
|
4036
|
+
})();
|
|
3437
4037
|
// 设置权限审批的交互上下文(支持交互卡片)
|
|
3438
4038
|
const permissionContext = {
|
|
3439
4039
|
sendPrompt: permissionPromptForOrigin,
|
|
@@ -3447,10 +4047,12 @@ export class ResponseEngine {
|
|
|
3447
4047
|
agentName: agentNameForStats,
|
|
3448
4048
|
taskId,
|
|
3449
4049
|
chatmode: isProactive ? 'proactive' : 'interactive',
|
|
3450
|
-
|
|
4050
|
+
// 系统会话没有角色
|
|
4051
|
+
role: systemSessionMarker ? undefined : peerRole,
|
|
3451
4052
|
chatType: authChatType,
|
|
3452
4053
|
selfAid,
|
|
3453
4054
|
managedTempDir: sessionRuntimeDir,
|
|
4055
|
+
grantedReadRoots,
|
|
3454
4056
|
allowReadonlySourceDiagnostics: effectiveAgentConfig?.readonlySourceDiagnostics === true,
|
|
3455
4057
|
peerKey: authPeerKey,
|
|
3456
4058
|
causation: taskCausation,
|
|
@@ -3460,6 +4062,13 @@ export class ResponseEngine {
|
|
|
3460
4062
|
recordExecutionAnomaly,
|
|
3461
4063
|
pauseController: taskPauseController,
|
|
3462
4064
|
preToolUsePolicyHook: pureSessionPolicyHook,
|
|
4065
|
+
systemSessionCheck,
|
|
4066
|
+
...(systemSessionModule && systemSessionContext
|
|
4067
|
+
? {
|
|
4068
|
+
systemSessionGrants: systemSessionModule.grants(systemSessionContext),
|
|
4069
|
+
...(systemSessionSandbox ? { systemSessionSandbox } : {}),
|
|
4070
|
+
}
|
|
4071
|
+
: {}),
|
|
3463
4072
|
armApprovedDelegationCommand: this.agentDelegationRegistry
|
|
3464
4073
|
? (sessionToken, commandHash) => this.agentDelegationRegistry.armApprovedCommand({
|
|
3465
4074
|
sessionToken,
|
|
@@ -3468,6 +4077,11 @@ export class ResponseEngine {
|
|
|
3468
4077
|
commandHash,
|
|
3469
4078
|
})
|
|
3470
4079
|
: undefined,
|
|
4080
|
+
bindSessionCredential: this.agentDelegationRegistry
|
|
4081
|
+
? (sessionToken, threadId) => this.agentDelegationRegistry.registerSessionCredential({
|
|
4082
|
+
sessionToken, threadId, sessionId: session.id, taskId,
|
|
4083
|
+
})
|
|
4084
|
+
: undefined,
|
|
3471
4085
|
turn: {
|
|
3472
4086
|
sessionId: turnLease.sessionId,
|
|
3473
4087
|
taskId,
|
|
@@ -3552,16 +4166,31 @@ export class ResponseEngine {
|
|
|
3552
4166
|
const pluginHook = pureSessionPolicyHook;
|
|
3553
4167
|
if (!pluginHook)
|
|
3554
4168
|
return undefined;
|
|
3555
|
-
return (toolName, toolInput) => {
|
|
4169
|
+
return (toolName, toolInput, meta) => {
|
|
3556
4170
|
const stBefore = modeState.get('proactive');
|
|
3557
4171
|
const wasFirstPending = stBefore && !stBefore.firstToolDone;
|
|
3558
|
-
const result = pluginHook(toolName, toolInput);
|
|
4172
|
+
const result = pluginHook(toolName, toolInput, meta);
|
|
3559
4173
|
// 整理会话的拦截原因已经写清楚怎么改,不套 proactive 违规提醒
|
|
3560
4174
|
if (result?.block && result.policyCode?.startsWith('memory_session_'))
|
|
3561
4175
|
return result;
|
|
3562
4176
|
if (result?.block) {
|
|
3563
4177
|
// 拦截事件(首工具非表态 / 工具进展汇报未完成)
|
|
3564
4178
|
snapshot.set(session.id, taskId, { policyHook: { triggered: true, blocked: true, toolName } });
|
|
4179
|
+
// 问题1/2修复:熔断码表示连续违规已超阈值,停止注入提醒,直接中断任务。
|
|
4180
|
+
// F5修复:向渠道发一条人类可读的错误说明,避免静默中断。
|
|
4181
|
+
if (result.policyCode === 'proactive_circuit_break') {
|
|
4182
|
+
void adapter.send(withEnvelopeReplyContext(envelope, capturedReplyContext), {
|
|
4183
|
+
kind: 'result.text',
|
|
4184
|
+
text: '⚠️ 任务执行遇到循环障碍,已自动中断。请重新描述你的需求。',
|
|
4185
|
+
isFinal: true,
|
|
4186
|
+
}).catch(err => {
|
|
4187
|
+
logger.warn(`[ResponseEngine] circuit break channel send failed: session=${session.id} error=${err instanceof Error ? err.message : String(err)}`);
|
|
4188
|
+
});
|
|
4189
|
+
void agent.interrupt(session.id).catch(error => {
|
|
4190
|
+
logger.debug(`[ResponseEngine] circuit break interrupt failed: session=${session.id} error=${error instanceof Error ? error.message : String(error)}`);
|
|
4191
|
+
});
|
|
4192
|
+
return result;
|
|
4193
|
+
}
|
|
3565
4194
|
const errorMsg = `⚠️ proactive 模式违规:${result.reason ?? '请先发送必要说明'}。请重新执行正确的命令。`;
|
|
3566
4195
|
agent.injectUserMessage?.(session.id, errorMsg);
|
|
3567
4196
|
return result;
|
|
@@ -3605,6 +4234,8 @@ export class ResponseEngine {
|
|
|
3605
4234
|
let previousFallbackState;
|
|
3606
4235
|
let agentModel;
|
|
3607
4236
|
let v2HandoffIds = [];
|
|
4237
|
+
/** T3: 本次注入的 Thread Context 条目(渲染后据此清空 inbox)。 */
|
|
4238
|
+
let threadContextItems = [];
|
|
3608
4239
|
let v2HandoffResultRefs = [];
|
|
3609
4240
|
let v2HandoffDirection;
|
|
3610
4241
|
let handoffPromptRendered = false;
|
|
@@ -3634,7 +4265,15 @@ export class ResponseEngine {
|
|
|
3634
4265
|
const triggerPermissionModeOverride = message.triggerMeta?.permissionModeOverride;
|
|
3635
4266
|
const commandFullAccess = message.executionPermissionOverride;
|
|
3636
4267
|
const triggerFullAccess = triggerPermissionModeOverride === 'fullaccess';
|
|
3637
|
-
if (
|
|
4268
|
+
if (systemSessionMarker) {
|
|
4269
|
+
// 系统会话不走角色 / trigger override / fullaccess:runner 模式由模块固定
|
|
4270
|
+
if (commandFullAccess || triggerFullAccess) {
|
|
4271
|
+
throw new Error('系统会话不支持 fullaccess');
|
|
4272
|
+
}
|
|
4273
|
+
effectivePermissionMode = systemSessionModule
|
|
4274
|
+
.runnerLaunch(normalizedBaseagent.canonical, systemSessionContext).runnerMode;
|
|
4275
|
+
}
|
|
4276
|
+
else if (commandFullAccess || triggerFullAccess) {
|
|
3638
4277
|
if (commandFullAccess) {
|
|
3639
4278
|
if (commandFullAccess.permissionMode !== 'fullaccess'
|
|
3640
4279
|
|| commandFullAccess.processRole !== 'fullaccess-run'
|
|
@@ -3848,10 +4487,13 @@ export class ResponseEngine {
|
|
|
3848
4487
|
bootstrapAgentMdPath: agentMdPath(selfAid),
|
|
3849
4488
|
};
|
|
3850
4489
|
}
|
|
4490
|
+
// persona.md 是唯一的人格/交接文件(personal.md 已废弃并入)。
|
|
4491
|
+
// 该白名单让 owner 会话可直接用文件工具分段更新 `## Bootstrap 交接`,
|
|
4492
|
+
// 避免把长 Markdown 交接摘要塞进 `ec agent persona --text` 的 shell 转义。
|
|
3851
4493
|
if (selfAid && peerRole === 'owner' && effectivePermissionMode !== 'readonly') {
|
|
3852
4494
|
modelOverride = {
|
|
3853
4495
|
...(modelOverride || {}),
|
|
3854
|
-
personalMdPath: path.join(resolveRoot(), 'agents', selfAid, 'personal', '
|
|
4496
|
+
personalMdPath: path.join(resolveRoot(), 'agents', selfAid, 'personal', 'persona.md'),
|
|
3855
4497
|
};
|
|
3856
4498
|
}
|
|
3857
4499
|
if (normalizedBaseagent.canonical === 'claude') {
|
|
@@ -4095,9 +4737,12 @@ export class ResponseEngine {
|
|
|
4095
4737
|
// Bootstrap always uses the lifecycle-gated main manifest. A custom
|
|
4096
4738
|
// session manifest (for example the auxiliary manifest with `always`
|
|
4097
4739
|
// sections) must not reintroduce normal rules or command context.
|
|
4740
|
+
// 系统会话:manifest 由模块按 type/subtype 决定,不读 config
|
|
4098
4741
|
const manifestFile = isBootstrapping
|
|
4099
4742
|
? DEFAULT_MANIFEST_FILE
|
|
4100
|
-
:
|
|
4743
|
+
: systemSessionMarker
|
|
4744
|
+
? getSystemSessionModule(systemSessionMarker.type).manifest(systemSessionMarker.subtype)
|
|
4745
|
+
: resolveSessionManifest(sessionType, sessionManifests);
|
|
4101
4746
|
const kitContext = renderKitSections(kitCtx, manifestFile);
|
|
4102
4747
|
effectiveSystemPrompt = [options?.systemPromptAppend, kitContext].filter(Boolean).join('\n') || undefined;
|
|
4103
4748
|
// ── Stats: context_breakdown 旁路采集(各段估算 token 数,字符数/4 近似) ──
|
|
@@ -4137,6 +4782,16 @@ export class ResponseEngine {
|
|
|
4137
4782
|
const rawPeerItems = message.source === 'owner-inject' ? [] : message.items && message.items.length > 0
|
|
4138
4783
|
? message.items
|
|
4139
4784
|
: [{
|
|
4785
|
+
// T9.5/T10: trigger 消息填充专属 kind + triggerInfo(含 labels),走 msg-trigger-session/thread-check 渲染
|
|
4786
|
+
...(message.source === 'trigger' && message.triggerMeta?.triggerId ? {
|
|
4787
|
+
kind: 'trigger',
|
|
4788
|
+
triggerInfo: {
|
|
4789
|
+
triggerId: message.triggerMeta.triggerId,
|
|
4790
|
+
triggerName: message.triggerMeta.triggerName,
|
|
4791
|
+
fireTime: message.triggerMeta.fireTime,
|
|
4792
|
+
labels: message.triggerMeta.labels,
|
|
4793
|
+
},
|
|
4794
|
+
} : {}),
|
|
4140
4795
|
peerId: message.peerId, peerName: peerName || undefined,
|
|
4141
4796
|
peerType: message.peerType,
|
|
4142
4797
|
peerRole: message.batchRole || session.identity?.role || 'none',
|
|
@@ -4147,6 +4802,34 @@ export class ResponseEngine {
|
|
|
4147
4802
|
mentions: message.mentions,
|
|
4148
4803
|
mentionAids: message.mentionAids,
|
|
4149
4804
|
}];
|
|
4805
|
+
// T3.2: 读取 Thread Context inbox —— 跨话题发送留在本端的注入提醒。
|
|
4806
|
+
// 注入一次后立即删除整个文件(T3.3),不依赖任何消费标记。
|
|
4807
|
+
threadContextItems = [];
|
|
4808
|
+
if (selfAid && message.source !== 'owner-inject') {
|
|
4809
|
+
try {
|
|
4810
|
+
const { ThreadService } = await import('../thread/index.js');
|
|
4811
|
+
const inbox = ThreadService.readInbox(selfAid, session.id);
|
|
4812
|
+
threadContextItems = inbox.map(entry => ({
|
|
4813
|
+
kind: 'thread-context',
|
|
4814
|
+
peerId: entry.originPeer,
|
|
4815
|
+
peerType: 'agent',
|
|
4816
|
+
peerRole: 'none',
|
|
4817
|
+
content: entry.content,
|
|
4818
|
+
timestamp: entry.timestamp,
|
|
4819
|
+
threadContext: {
|
|
4820
|
+
messageId: entry.messageId,
|
|
4821
|
+
originThreadId: entry.originThreadId,
|
|
4822
|
+
originPeer: entry.originPeer,
|
|
4823
|
+
originSessionId: entry.originSessionId,
|
|
4824
|
+
...(entry.triggerId ? { triggerId: entry.triggerId } : {}),
|
|
4825
|
+
gapDetection: entry.gapDetection,
|
|
4826
|
+
},
|
|
4827
|
+
}));
|
|
4828
|
+
}
|
|
4829
|
+
catch (e) {
|
|
4830
|
+
logger.warn(`[ResponseEngine] ThreadContext inbox 读取失败: ${e?.message ?? e}`);
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4150
4833
|
const peerItems = (() => {
|
|
4151
4834
|
if (message.handoffDelivery && this.handoffRuntime) {
|
|
4152
4835
|
const items = this.handoffRuntime.buildPromptItems(message, peerRole);
|
|
@@ -4156,10 +4839,10 @@ export class ResponseEngine {
|
|
|
4156
4839
|
: (item.handoff?.handoffId ? [item.handoff.handoffId] : [])))));
|
|
4157
4840
|
v2HandoffResultRefs = items.flatMap(item => item.handoff?.resultRefs ?? []);
|
|
4158
4841
|
v2HandoffDirection = message.handoffDelivery.direction;
|
|
4159
|
-
return items;
|
|
4842
|
+
return [...threadContextItems, ...items];
|
|
4160
4843
|
}
|
|
4161
4844
|
}
|
|
4162
|
-
return rawPeerItems;
|
|
4845
|
+
return [...threadContextItems, ...rawPeerItems];
|
|
4163
4846
|
})();
|
|
4164
4847
|
// 将匹配的 pending hint 暂时 claim,排在真实消息之前;渲染或启动
|
|
4165
4848
|
// 失败时释放 claim,只有 runner 接受 prompt 后才持久化 consumed。
|
|
@@ -4263,6 +4946,16 @@ export class ResponseEngine {
|
|
|
4263
4946
|
}
|
|
4264
4947
|
}
|
|
4265
4948
|
}
|
|
4949
|
+
// T3.3: 渲染成功、prompt 非空 —— 注入完成,删除 inbox(注入一次语义)。
|
|
4950
|
+
if (threadContextItems.length > 0 && selfAid) {
|
|
4951
|
+
try {
|
|
4952
|
+
const { ThreadService } = await import('../thread/index.js');
|
|
4953
|
+
ThreadService.clearInbox(selfAid, session.id);
|
|
4954
|
+
}
|
|
4955
|
+
catch (e) {
|
|
4956
|
+
logger.warn(`[ResponseEngine] ThreadContext inbox 清理失败: ${e?.message ?? e}`);
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4266
4959
|
// Pass the stable session directory explicitly so sandboxed helper
|
|
4267
4960
|
// commands never fall back to a process, agent, or project directory
|
|
4268
4961
|
// for transient state.
|
|
@@ -4273,6 +4966,10 @@ export class ResponseEngine {
|
|
|
4273
4966
|
messageId: message.messageId,
|
|
4274
4967
|
baseagent: normalizedBaseagent.canonical,
|
|
4275
4968
|
channel: configChannelType,
|
|
4969
|
+
// 完整 channelKey(= adapter.channelName / inst.name,如 `aun#<selfAid>#main`)。
|
|
4970
|
+
// 必须与下面 issue() 的 `channel:` 用同一表达式,否则 debug 路径拿到的
|
|
4971
|
+
// origin.channelKey 会与真实任务路径不一致(见 D5)。
|
|
4972
|
+
channelKey: authorizationIdentity?.originChannelKey ?? message.channel,
|
|
4276
4973
|
channelId: message.channelId,
|
|
4277
4974
|
chatType: configChatType,
|
|
4278
4975
|
selfAid,
|
|
@@ -4280,7 +4977,8 @@ export class ResponseEngine {
|
|
|
4280
4977
|
peerKey,
|
|
4281
4978
|
peerName: peerName || undefined,
|
|
4282
4979
|
peerType: message.peerType || session.metadata?.peerType || undefined,
|
|
4283
|
-
|
|
4980
|
+
// 系统会话没有角色
|
|
4981
|
+
...(systemSessionMarker ? {} : { peerRole }),
|
|
4284
4982
|
// 会话原型与整理模式:ipc 侧靠 sessionType 拦非整理会话的写命令,
|
|
4285
4983
|
// 靠 memoryMode 校验 --scope 是否与本模式匹配。
|
|
4286
4984
|
sessionType: renderedSessionType,
|
|
@@ -4304,6 +5002,7 @@ export class ResponseEngine {
|
|
|
4304
5002
|
causation: taskCausation,
|
|
4305
5003
|
};
|
|
4306
5004
|
this.activeTaskRuntimeContexts.set(session.id, taskRuntimeContext);
|
|
5005
|
+
this.debugWriteSessionSnapshot(session.id, taskRuntimeContext);
|
|
4307
5006
|
runtimeEnv = buildTaskRuntimeEnv(taskRuntimeContext);
|
|
4308
5007
|
if (this.agentDelegationRegistry && configActorId && selfAid && peerKey) {
|
|
4309
5008
|
const delegationToken = this.agentDelegationRegistry.issue({
|
|
@@ -4316,7 +5015,8 @@ export class ResponseEngine {
|
|
|
4316
5015
|
chatType: configChatType,
|
|
4317
5016
|
selfAid,
|
|
4318
5017
|
peerKey,
|
|
4319
|
-
|
|
5018
|
+
// 系统会话与角色体系无关:带标记、不签角色
|
|
5019
|
+
...(systemSessionMarker ? { systemSession: systemSessionMarker } : { issuedRole: peerRole }),
|
|
4320
5020
|
executionSource: fullAccessAuthorization?.source ?? (message.source === 'trigger' ? 'trigger' : 'user'),
|
|
4321
5021
|
permissionMode: effectivePermissionMode,
|
|
4322
5022
|
approvalInteractionPolicy,
|
|
@@ -4409,6 +5109,10 @@ export class ResponseEngine {
|
|
|
4409
5109
|
mimeType: image.mimeType,
|
|
4410
5110
|
}));
|
|
4411
5111
|
debugRunnerModel = modelOverride?.model || agentModel;
|
|
5112
|
+
if (debugRunnerModel)
|
|
5113
|
+
this.activeRunnerModels.set(session.id, debugRunnerModel);
|
|
5114
|
+
else
|
|
5115
|
+
this.activeRunnerModels.delete(session.id);
|
|
4412
5116
|
const stream = await agent.runQuery(session.id, effectivePrompt, absoluteProjectPath, runAgentSessionId, runnerImages, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
|
|
4413
5117
|
// The turn may be superseded while the runner is still creating its
|
|
4414
5118
|
// backend query. Replay the interrupt after runQuery resolves, when
|
|
@@ -4578,7 +5282,7 @@ export class ResponseEngine {
|
|
|
4578
5282
|
const candidateHint = fallbackCandidates.length === 0
|
|
4579
5283
|
? '未配置可用备用模型'
|
|
4580
5284
|
: '备用模型均不可用';
|
|
4581
|
-
throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.fallbackModels
|
|
5285
|
+
throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.fallbackModels 配置同类备用模型。`, retryError);
|
|
4582
5286
|
}
|
|
4583
5287
|
if (retryAttemptTimedOut || isRetryableError(retryError)) {
|
|
4584
5288
|
await recordRetryHealthError(retryError);
|
|
@@ -4704,8 +5408,9 @@ export class ResponseEngine {
|
|
|
4704
5408
|
this.sessionManager.clearProcessingIfTask(session.id, taskId);
|
|
4705
5409
|
if (this.activeTaskRuntimeContexts.get(session.id)?.taskId === taskId) {
|
|
4706
5410
|
this.activeTaskRuntimeContexts.delete(session.id);
|
|
5411
|
+
this.activeRunnerModels.delete(session.id); // F1修复
|
|
4707
5412
|
}
|
|
4708
|
-
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
5413
|
+
this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
|
|
4709
5414
|
logger.info(`[ResponseEngine] Stale turn stopped before publish: session=${session.id} task=${taskId}`);
|
|
4710
5415
|
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
|
|
4711
5416
|
expected: turnLease?.generation,
|
|
@@ -4719,8 +5424,9 @@ export class ResponseEngine {
|
|
|
4719
5424
|
this.sessionManager.clearProcessingIfTask(session.id, taskId);
|
|
4720
5425
|
if (this.activeTaskRuntimeContexts.get(session.id)?.taskId === taskId) {
|
|
4721
5426
|
this.activeTaskRuntimeContexts.delete(session.id);
|
|
5427
|
+
this.activeRunnerModels.delete(session.id); // F1修复
|
|
4722
5428
|
}
|
|
4723
|
-
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
5429
|
+
this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
|
|
4724
5430
|
logger.info(`[ResponseEngine] Stale turn stopped before protocol rejection: session=${session.id} task=${taskId}`);
|
|
4725
5431
|
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
|
|
4726
5432
|
expected: turnLease?.generation,
|
|
@@ -4870,9 +5576,11 @@ export class ResponseEngine {
|
|
|
4870
5576
|
});
|
|
4871
5577
|
}
|
|
4872
5578
|
// 最终回复文本:suppressed 模式或无 text 事件时需要兜底添加
|
|
5579
|
+
// 错误诊断串已通过 system.error 单独发出时置 true,下游跳过 result.error 避免重复
|
|
5580
|
+
let sentErrorAsSystemError = false;
|
|
4873
5581
|
const finalReplyText = streamResult.lastReplyText || streamResult.fullText;
|
|
4874
5582
|
const visibleFinalReplyText = stripContextLimitErrorLines(finalReplyText);
|
|
4875
|
-
const
|
|
5583
|
+
const streamRawErrorText = getStreamRawErrorText(streamResult, visibleFinalReplyText || undefined);
|
|
4876
5584
|
const isTriggerNoop = message.source === 'trigger'
|
|
4877
5585
|
&& typeof message.triggerMeta?.noopSentinel === 'string'
|
|
4878
5586
|
&& message.triggerMeta.noopSentinel.length > 0
|
|
@@ -4882,16 +5590,42 @@ export class ResponseEngine {
|
|
|
4882
5590
|
this.sessionManager.clearProcessingIfTask(session.id, taskId);
|
|
4883
5591
|
if (this.activeTaskRuntimeContexts.get(session.id)?.taskId === taskId) {
|
|
4884
5592
|
this.activeTaskRuntimeContexts.delete(session.id);
|
|
5593
|
+
this.activeRunnerModels.delete(session.id); // F1修复
|
|
4885
5594
|
}
|
|
4886
|
-
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
5595
|
+
this.agentDelegationRegistry?.revokeTask(session.id, taskId, 'replaced');
|
|
4887
5596
|
logger.info(`[ResponseEngine] Stale turn stopped before final flush: session=${session.id} task=${taskId}`);
|
|
4888
5597
|
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_turn', taskCausation);
|
|
4889
5598
|
return;
|
|
4890
5599
|
}
|
|
4891
|
-
if (visibleFinalReplyText
|
|
5600
|
+
if ((visibleFinalReplyText || streamResult.syntheticDiagnosticText) && !isTriggerNoop) {
|
|
4892
5601
|
// `hasReceivedText` 是任务级计数。工具调用前的文字已经显示,并不表示最后一轮
|
|
4893
5602
|
// (通常来自 complete.result)的文字也已显示;用本轮投影标记避免终态丢失。
|
|
4894
|
-
|
|
5603
|
+
//
|
|
5604
|
+
// 终态路由按"本轮是否以错误收场"(isError)判定,不再按"有没有收到过文本"判定。
|
|
5605
|
+
// 原因:上游会把错误原文当正文送进来——Claude Code 用 `<synthetic>` assistant 表达
|
|
5606
|
+
// 传输/API 故障(runner 已标 synthetic,见上方 text 分支),网关也会把上下文超限错误
|
|
5607
|
+
// 伪装成 text event。这两种情况下 hasReceivedText / lastReplyText 都非空,旧判据
|
|
5608
|
+
// (isError && !lastReplyText)拦不住,错误串会被当成模型的最终回复发出去
|
|
5609
|
+
// (2026-10-11 403「无法连接到积分服务器」案例)。
|
|
5610
|
+
// 以错误收场的轮次一律走 system.error:前端收到的单条消息即可判断来源,
|
|
5611
|
+
// 不需要等后续的 result.error。
|
|
5612
|
+
const errorText = visibleFinalReplyText || streamResult.syntheticDiagnosticText || getStreamErrorMessage(streamResult, false);
|
|
5613
|
+
if (streamResult.isError) {
|
|
5614
|
+
// 用原始诊断文本(含错误码)判断可重试性,不用已过 stripContextLimitErrorLines 的展示文本
|
|
5615
|
+
const recoverable = isRetryableError(new Error(getStreamErrorText(streamResult)));
|
|
5616
|
+
const subtype = normalizeCoreErrorSubtype(new Error(streamRawErrorText || getStreamErrorText(streamResult)), streamResult.subtype, streamResult.terminalReason);
|
|
5617
|
+
const reason = normalizeCoreErrorReason(streamResult.terminalReason);
|
|
5618
|
+
await adapter.send(envelope, {
|
|
5619
|
+
kind: 'system.error',
|
|
5620
|
+
text: errorText,
|
|
5621
|
+
...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
|
|
5622
|
+
subtype,
|
|
5623
|
+
...(reason ? { reason } : {}),
|
|
5624
|
+
recoverable,
|
|
5625
|
+
}).catch(() => { });
|
|
5626
|
+
sentErrorAsSystemError = true;
|
|
5627
|
+
}
|
|
5628
|
+
else if (visibleFinalReplyText && (shouldSuppress() || !streamResult.hasProjectedCurrentReplyText)) {
|
|
4895
5629
|
await renderer.addText(visibleFinalReplyText, undefined, undefined, streamResult.assistantUuid);
|
|
4896
5630
|
}
|
|
4897
5631
|
}
|
|
@@ -4901,8 +5635,9 @@ export class ResponseEngine {
|
|
|
4901
5635
|
this.sessionManager.clearProcessingIfTask(session.id, taskId);
|
|
4902
5636
|
if (this.activeTaskRuntimeContexts.get(session.id)?.taskId === taskId) {
|
|
4903
5637
|
this.activeTaskRuntimeContexts.delete(session.id);
|
|
5638
|
+
this.activeRunnerModels.delete(session.id); // F1修复
|
|
4904
5639
|
}
|
|
4905
|
-
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
5640
|
+
this.agentDelegationRegistry?.revokeTask(session.id, taskId, streamResult.isError ? 'failed' : 'completed');
|
|
4906
5641
|
logger.info(`[ResponseEngine] session ${session.id} processing cleared task=${taskId}`);
|
|
4907
5642
|
import('../../utils/msg-diag.js').then(m => m.diagLog('task_done', { aid: session.selfAID ?? '', msgId: message.messageId ?? '', taskId, text: message.content ?? '' })).catch(() => { });
|
|
4908
5643
|
// 降级模型回复末尾追加标记(代码层硬注入,不依赖模型输出)
|
|
@@ -4971,6 +5706,10 @@ export class ResponseEngine {
|
|
|
4971
5706
|
: getStreamErrorMessage(streamResult, false);
|
|
4972
5707
|
const rawSubtype = streamResult.subtype || 'agent_error';
|
|
4973
5708
|
const errorType = prefixErrorType(ERROR_PREFIX.AGENT, rawSubtype);
|
|
5709
|
+
const coreSubtype = normalizeCoreErrorSubtype(new Error(streamRawErrorText || errorSummary), rawSubtype, streamResult.terminalReason);
|
|
5710
|
+
const coreReason = permissionTerminalFailure
|
|
5711
|
+
? normalizeCoreErrorReason(permissionTerminalFailure.reason)
|
|
5712
|
+
: normalizeCoreErrorReason(streamResult.terminalReason);
|
|
4974
5713
|
// 用户主动打断(新消息//stop/撤回)会让 SDK 流在工具调用中途被掐断,
|
|
4975
5714
|
// 末尾 result message 形状异常并被标记为 error(含 SDK 内部 ede_diagnostic 串)。
|
|
4976
5715
|
// 这不是真正的失败,不应把诊断串暴露给用户,也不计入错误统计。
|
|
@@ -4991,12 +5730,29 @@ export class ResponseEngine {
|
|
|
4991
5730
|
// DaemonChannel settles a trigger when it receives a terminal
|
|
4992
5731
|
// payload. Send the metadata-bearing status first so failed runs do
|
|
4993
5732
|
// not lose their tool counts and recovery diagnostics.
|
|
5733
|
+
// system.error 已在 finalReplyText 分支把错误文本发给了前端,不再重复发 result.error
|
|
4994
5734
|
if (this.isTrustedDaemonTrigger(message)) {
|
|
4995
5735
|
await adapter.send(envelope, { kind: 'status.error', metadata: terminalMetadata }).catch(() => { });
|
|
4996
|
-
|
|
5736
|
+
if (!sentErrorAsSystemError) {
|
|
5737
|
+
await adapter.send(envelope, {
|
|
5738
|
+
kind: 'result.error',
|
|
5739
|
+
text: userErrorSummary,
|
|
5740
|
+
...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
|
|
5741
|
+
subtype: coreSubtype,
|
|
5742
|
+
...(coreReason ? { reason: coreReason } : {}),
|
|
5743
|
+
}).catch(() => { });
|
|
5744
|
+
}
|
|
4997
5745
|
}
|
|
4998
5746
|
else {
|
|
4999
|
-
|
|
5747
|
+
if (!sentErrorAsSystemError) {
|
|
5748
|
+
await adapter.send(envelope, {
|
|
5749
|
+
kind: 'result.error',
|
|
5750
|
+
text: userErrorSummary,
|
|
5751
|
+
...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
|
|
5752
|
+
subtype: coreSubtype,
|
|
5753
|
+
...(coreReason ? { reason: coreReason } : {}),
|
|
5754
|
+
}).catch(() => { });
|
|
5755
|
+
}
|
|
5000
5756
|
await adapter.send(envelope, { kind: 'status.error', metadata: terminalMetadata }).catch(() => { });
|
|
5001
5757
|
}
|
|
5002
5758
|
this.touchAgentActivity(channelKey);
|
|
@@ -5249,6 +6005,7 @@ export class ResponseEngine {
|
|
|
5249
6005
|
}
|
|
5250
6006
|
if (!interruptReason) {
|
|
5251
6007
|
this.publishTriggerExecutionCompleted(message, messageId, durationMs, taskCausation);
|
|
6008
|
+
this.publishBgSessionCompleted(message, messageId, durationMs, taskCausation);
|
|
5252
6009
|
}
|
|
5253
6010
|
if (!this.turnCoordinator.canPublish(turnLease))
|
|
5254
6011
|
return;
|
|
@@ -5338,8 +6095,9 @@ export class ResponseEngine {
|
|
|
5338
6095
|
this.sessionManager.clearProcessingIfTask(session.id, taskId);
|
|
5339
6096
|
if (this.activeTaskRuntimeContexts.get(session.id)?.taskId === taskId) {
|
|
5340
6097
|
this.activeTaskRuntimeContexts.delete(session.id);
|
|
6098
|
+
this.activeRunnerModels.delete(session.id); // F1修复
|
|
5341
6099
|
}
|
|
5342
|
-
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
6100
|
+
this.agentDelegationRegistry?.revokeTask(session.id, taskId, this.interruptedSessions.has(session.id) ? 'interrupted' : 'failed');
|
|
5343
6101
|
logger.info(`[ResponseEngine] session ${session.id} processing cleared (on error) task=${taskId}`);
|
|
5344
6102
|
}
|
|
5345
6103
|
catch { }
|
|
@@ -5486,10 +6244,29 @@ export class ResponseEngine {
|
|
|
5486
6244
|
sessionId: session.id,
|
|
5487
6245
|
metadata: withTaskMetadata(sendOpts?.metadata),
|
|
5488
6246
|
};
|
|
6247
|
+
const errorReason = isExecutionFailure(error)
|
|
6248
|
+
? normalizeCoreErrorReason(error.reason)
|
|
6249
|
+
: retryExhaustedCount
|
|
6250
|
+
? 'retry_exhausted'
|
|
6251
|
+
: retryInputAlreadySubmitted
|
|
6252
|
+
? 'input_already_submitted'
|
|
6253
|
+
: modelFallbackExhaustedMessage
|
|
6254
|
+
? 'fallback_exhausted'
|
|
6255
|
+
: isTotalExecutionTimeout || isTimeout
|
|
6256
|
+
? 'timeout'
|
|
6257
|
+
: undefined;
|
|
6258
|
+
const errorSubtype = error instanceof OrphanedTargetSessionError
|
|
6259
|
+
? ErrorType.SESSION_UNAVAILABLE
|
|
6260
|
+
: modelFallbackExhaustedMessage
|
|
6261
|
+
? ErrorType.MODEL_UNAVAILABLE
|
|
6262
|
+
: normalizeCoreErrorSubtype(error, undefined, isTotalExecutionTimeout || isTimeout ? 'timeout' : undefined);
|
|
6263
|
+
const rawErrorText = getErrorRawText(error);
|
|
5489
6264
|
const errorPayload = {
|
|
5490
6265
|
kind: 'result.error',
|
|
5491
6266
|
text: userMessage,
|
|
5492
|
-
|
|
6267
|
+
subtype: errorSubtype,
|
|
6268
|
+
...(rawErrorText ? { raw: rawErrorText } : {}),
|
|
6269
|
+
...(errorReason ? { reason: errorReason } : {}),
|
|
5493
6270
|
};
|
|
5494
6271
|
if (!isUserInterrupt && daemonTrigger && procStatus === 'error') {
|
|
5495
6272
|
// DaemonChannel settles a trigger on the first terminal payload. Put
|
|
@@ -5889,6 +6666,9 @@ export class ResponseEngine {
|
|
|
5889
6666
|
});
|
|
5890
6667
|
// 追踪最后一轮 assistant 回复文本(tool_use 之后的纯文本)
|
|
5891
6668
|
let lastReplyText = '';
|
|
6669
|
+
// 上游合成错误帧的原文(runner 标记 synthetic 的 text)。与正文通道分开存放:
|
|
6670
|
+
// 它只能作为终态错误文案的来源,不得进入 lastReplyText / 投影 / hasReceivedText。
|
|
6671
|
+
let syntheticDiagnosticText = '';
|
|
5892
6672
|
// callId → description 映射,用于 tool_result 回显描述
|
|
5893
6673
|
const toolDescByCallId = new Map();
|
|
5894
6674
|
const toolInputByCallId = new Map();
|
|
@@ -5922,10 +6702,11 @@ export class ResponseEngine {
|
|
|
5922
6702
|
if (event.type === 'complete') {
|
|
5923
6703
|
event = normalizeCompleteAgentEvent(event);
|
|
5924
6704
|
}
|
|
5925
|
-
if (event.type === 'tool_result' && event
|
|
6705
|
+
if (event.type === 'tool_result' && isToolResultFailure(event)) {
|
|
5926
6706
|
const classification = normalizeToolErrorClassification(event);
|
|
5927
6707
|
event = {
|
|
5928
6708
|
...event,
|
|
6709
|
+
isError: true,
|
|
5929
6710
|
errorCode: classification.errorCode,
|
|
5930
6711
|
errorDetailCode: classification.errorDetailCode,
|
|
5931
6712
|
classificationMissing: classification.classificationMissing || event.classificationMissing,
|
|
@@ -6071,7 +6852,20 @@ export class ResponseEngine {
|
|
|
6071
6852
|
logger.info(`[ResponseEngine] Event: type=${event.type}${eventDetail}`);
|
|
6072
6853
|
}
|
|
6073
6854
|
// IMRenderer 旁路:proactive 模式逐事件投影为 thought(fire-and-forget)
|
|
6074
|
-
|
|
6855
|
+
let diagnosticSuffix = '';
|
|
6856
|
+
let projectedEvent = event;
|
|
6857
|
+
if (event.type === 'tool_result' && isToolResultFailure(event)) {
|
|
6858
|
+
const byCallId = event.callId ? toolInputByCallId.get(event.callId) : undefined;
|
|
6859
|
+
const byCorrelationId = event.correlationId
|
|
6860
|
+
? toolInputByCorrelationId.get(event.correlationId)
|
|
6861
|
+
: undefined;
|
|
6862
|
+
const original = event.callId && event.correlationId
|
|
6863
|
+
? byCallId === byCorrelationId ? byCallId : undefined
|
|
6864
|
+
: byCallId ?? byCorrelationId;
|
|
6865
|
+
diagnosticSuffix = formatToolInputDiagnostics(diagnoseToolInputAfterFailure(original, event));
|
|
6866
|
+
projectedEvent = { ...event, error: getToolResultErrorText(event) + diagnosticSuffix };
|
|
6867
|
+
}
|
|
6868
|
+
renderer.emit(projectedEvent);
|
|
6075
6869
|
if (event.type === 'text_start') {
|
|
6076
6870
|
await renderer.sendTextStart(event.contentIndex, event.turn);
|
|
6077
6871
|
continue;
|
|
@@ -6113,6 +6907,7 @@ export class ResponseEngine {
|
|
|
6113
6907
|
toolUseId: event.toolUseId,
|
|
6114
6908
|
description: event.description,
|
|
6115
6909
|
subagentType: event.subagentType,
|
|
6910
|
+
taskType: event.taskType,
|
|
6116
6911
|
skipTranscript: event.skipTranscript,
|
|
6117
6912
|
});
|
|
6118
6913
|
this.eventBus.publish({
|
|
@@ -6131,15 +6926,21 @@ export class ResponseEngine {
|
|
|
6131
6926
|
causation,
|
|
6132
6927
|
});
|
|
6133
6928
|
if (!event.skipTranscript) {
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6929
|
+
// 子任务就是来源工具调用的执行过程:来源卡片在本轮可见时归拢进它的 tool_result,
|
|
6930
|
+
// 不另出卡片;否则(如 Codex 子智能体无来源工具)退回合成卡片。见 subtask-tool-projection.ts。
|
|
6931
|
+
const folded = renderer.beginParentSubtask(event.toolUseId);
|
|
6932
|
+
tasks.get(event.taskId).projection = folded ? 'parent' : 'synthetic';
|
|
6933
|
+
if (!folded) {
|
|
6934
|
+
renderer.addSubtaskActivity(projectSubtaskStarted({
|
|
6935
|
+
taskId: event.taskId,
|
|
6936
|
+
taskKind: event.taskKind,
|
|
6937
|
+
description: event.description,
|
|
6938
|
+
taskType: event.taskType,
|
|
6939
|
+
subagentType: event.subagentType,
|
|
6940
|
+
toolUseId: event.toolUseId,
|
|
6941
|
+
workflowName: event.workflowName,
|
|
6942
|
+
}));
|
|
6943
|
+
}
|
|
6143
6944
|
}
|
|
6144
6945
|
continue;
|
|
6145
6946
|
}
|
|
@@ -6155,6 +6956,7 @@ export class ResponseEngine {
|
|
|
6155
6956
|
}
|
|
6156
6957
|
if (event.type === 'task_notification') {
|
|
6157
6958
|
const tasks = this.activeRunnerTasks.get(session.id);
|
|
6959
|
+
const started = tasks?.get(event.taskId);
|
|
6158
6960
|
tasks?.delete(event.taskId);
|
|
6159
6961
|
if (tasks?.size === 0)
|
|
6160
6962
|
this.activeRunnerTasks.delete(session.id);
|
|
@@ -6175,19 +6977,26 @@ export class ResponseEngine {
|
|
|
6175
6977
|
causation,
|
|
6176
6978
|
});
|
|
6177
6979
|
if (!event.skipTranscript) {
|
|
6178
|
-
const
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
subagentType
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6980
|
+
const terminal = { status: event.status, summary: event.summary, durationMs: event.usage?.durationMs };
|
|
6981
|
+
const toolUseId = event.toolUseId ?? started?.toolUseId;
|
|
6982
|
+
// 归拢的子任务:终态合并进来源工具的 tool_result(前台等真实结果、后台放出扣住的占位结果)。
|
|
6983
|
+
const merged = started?.projection === 'parent' && renderer.endParentSubtask(toolUseId, terminal);
|
|
6984
|
+
if (!merged) {
|
|
6985
|
+
// 没归拢上:开始时就是合成卡片,则终态配同一张合成卡片;
|
|
6986
|
+
// 否则来源卡片已在本轮之外,补发一条同 call_id 的终态结果更新那张卡片。
|
|
6987
|
+
// taskType/subagentType 只在 task_started 事件上,从已登记的任务元数据补齐。
|
|
6988
|
+
const toParent = started?.projection !== 'synthetic' && !!toolUseId;
|
|
6989
|
+
renderer.addSubtaskActivity(projectSubtaskTerminal({
|
|
6990
|
+
taskId: event.taskId,
|
|
6991
|
+
taskKind: event.taskKind,
|
|
6992
|
+
description: event.description ?? started?.description,
|
|
6993
|
+
taskType: started?.taskType,
|
|
6994
|
+
subagentType: event.subagentType ?? started?.subagentType,
|
|
6995
|
+
toolUseId,
|
|
6996
|
+
...terminal,
|
|
6997
|
+
...(toParent ? { callId: toolUseId, toolName: renderer.toolNameForCall(toolUseId) } : {}),
|
|
6998
|
+
}));
|
|
6999
|
+
}
|
|
6191
7000
|
}
|
|
6192
7001
|
continue;
|
|
6193
7002
|
}
|
|
@@ -6224,21 +7033,30 @@ export class ResponseEngine {
|
|
|
6224
7033
|
if (!isCurrentlyBackground) {
|
|
6225
7034
|
// 流式文本
|
|
6226
7035
|
if (event.type === 'text') {
|
|
6227
|
-
//
|
|
6228
|
-
//
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
madeProgress = true;
|
|
6234
|
-
}
|
|
6235
|
-
lastReplyText += event.text;
|
|
6236
|
-
if (visibleText) {
|
|
6237
|
-
this.eventBus.publish({ type: 'message:text', sessionId: session.id, text: visibleText, isFinal: false });
|
|
7036
|
+
// 上游合成错误帧(Claude Code 的 `<synthetic>` assistant)会把 API/传输故障的
|
|
7037
|
+
// 错误原文当成正文送进来。这段字是诊断回显,不是模型正文——出身由 runner 标注,
|
|
7038
|
+
// 不靠字符串或计数猜。不进入正文通道(lastReplyText / hasReceivedText / 投影),
|
|
7039
|
+
// 只单独留存,供终态错误分支取用。
|
|
7040
|
+
if (event.synthetic) {
|
|
7041
|
+
syntheticDiagnosticText = event.text;
|
|
6238
7042
|
}
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
7043
|
+
else {
|
|
7044
|
+
// 网关偶尔会把上下文超限错误伪装成普通 text event。不要向事件总线或
|
|
7045
|
+
// 渲染器投影错误行;lastReplyText 仍保留原文本,以便流结束后触发恢复。
|
|
7046
|
+
const visibleText = stripContextLimitErrorLines(event.text);
|
|
7047
|
+
if (visibleText) {
|
|
7048
|
+
hasReceivedText = true;
|
|
7049
|
+
if (visibleText.trim())
|
|
7050
|
+
madeProgress = true;
|
|
7051
|
+
}
|
|
7052
|
+
lastReplyText += event.text;
|
|
7053
|
+
if (visibleText) {
|
|
7054
|
+
this.eventBus.publish({ type: 'message:text', sessionId: session.id, text: visibleText, isFinal: false });
|
|
7055
|
+
}
|
|
7056
|
+
if (visibleText && !shouldSuppress()) {
|
|
7057
|
+
await renderer.addText(visibleText, event.outputTokens, event.turn, event.assistantUuid, event.phase);
|
|
7058
|
+
hasProjectedCurrentReplyText = true;
|
|
7059
|
+
}
|
|
6242
7060
|
}
|
|
6243
7061
|
}
|
|
6244
7062
|
// compact 完成
|
|
@@ -6330,6 +7148,7 @@ export class ResponseEngine {
|
|
|
6330
7148
|
state: modeHooks.state,
|
|
6331
7149
|
sessionState: modeHooks.sessionState,
|
|
6332
7150
|
turn: turnLease,
|
|
7151
|
+
modelTurn: typeof event.turn === 'number' ? event.turn : undefined,
|
|
6333
7152
|
toolName: event.name,
|
|
6334
7153
|
toolInput: event.input || {},
|
|
6335
7154
|
injectToModel: (text) => { agent.injectUserMessage?.(session.id, text); },
|
|
@@ -6344,7 +7163,7 @@ export class ResponseEngine {
|
|
|
6344
7163
|
});
|
|
6345
7164
|
},
|
|
6346
7165
|
getQueueLength: () => this.messageQueue?.getQueueLength(session.id) ?? 0,
|
|
6347
|
-
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', proactiveSelfAid || session.selfAID),
|
|
7166
|
+
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', proactiveSelfAid || session.selfAID) || isThreadRoutedSendForSelf(toolName, toolInput, proactiveSelfAid || session.selfAID),
|
|
6348
7167
|
logger,
|
|
6349
7168
|
});
|
|
6350
7169
|
}
|
|
@@ -6464,21 +7283,20 @@ export class ResponseEngine {
|
|
|
6464
7283
|
});
|
|
6465
7284
|
},
|
|
6466
7285
|
getQueueLength: () => this.messageQueue?.getQueueLength(session.id) ?? 0,
|
|
6467
|
-
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', proactiveSelfAid || session.selfAID),
|
|
7286
|
+
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', proactiveSelfAid || session.selfAID) || isThreadRoutedSendForSelf(toolName, toolInput, proactiveSelfAid || session.selfAID),
|
|
6468
7287
|
logger,
|
|
6469
7288
|
});
|
|
6470
7289
|
}
|
|
6471
7290
|
// 从 tool_use 阶段缓存的描述中回溯
|
|
6472
7291
|
const cachedDesc = event.callId ? toolDescByCallId.get(event.callId) : undefined;
|
|
6473
|
-
|
|
7292
|
+
const toolResultFailed = isToolResultFailure(event);
|
|
7293
|
+
if (toolResultFailed && !shouldSuppress()) {
|
|
6474
7294
|
hasErrorResult = true;
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
errorMsg = errorMsg.replace(/<tool_use_error>(.*?)<\/tool_use_error>/gs, '$1');
|
|
6478
|
-
renderer.addToolResult(event.name || '\u5de5\u5177', false, undefined, errorMsg, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
|
|
7295
|
+
const errorMsg = getToolResultErrorText(event) + diagnosticSuffix;
|
|
7296
|
+
renderer.addToolResult(event.name || '\u5de5\u5177', false, event.result, errorMsg, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
|
|
6479
7297
|
}
|
|
6480
|
-
else if (!
|
|
6481
|
-
renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, event.durationMs, cachedDesc,
|
|
7298
|
+
else if (!toolResultFailed && !shouldSuppress()) {
|
|
7299
|
+
renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
|
|
6482
7300
|
}
|
|
6483
7301
|
}
|
|
6484
7302
|
// 非终态运行时告警走 status notify,不进入 activity/thought 历史。
|
|
@@ -6502,6 +7320,7 @@ export class ResponseEngine {
|
|
|
6502
7320
|
errors: [event.error],
|
|
6503
7321
|
lastReplyText,
|
|
6504
7322
|
fullText: '',
|
|
7323
|
+
syntheticDiagnosticText,
|
|
6505
7324
|
};
|
|
6506
7325
|
}
|
|
6507
7326
|
// 完成事件
|
|
@@ -6516,7 +7335,7 @@ export class ResponseEngine {
|
|
|
6516
7335
|
logger.info(`[ResponseEngine] Auto-filled session name: ${event.sessionTitle}`);
|
|
6517
7336
|
}
|
|
6518
7337
|
// 记录完成状态 + 最后一轮回复文本(后续 complete 覆盖前序)
|
|
6519
|
-
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText, numTurns: event.numTurns, ttftMs: event.ttftMs, model: event.model, tokenUsage: event.tokenUsage, contextUsage: event.contextUsage, lastModelCall: event.lastModelCall, modelCalls: event.modelCalls };
|
|
7338
|
+
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText, numTurns: event.numTurns, ttftMs: event.ttftMs, model: event.model, tokenUsage: event.tokenUsage, contextUsage: event.contextUsage, lastModelCall: event.lastModelCall, modelCalls: event.modelCalls, syntheticDiagnosticText };
|
|
6520
7339
|
if (!event.isError)
|
|
6521
7340
|
madeProgress = true;
|
|
6522
7341
|
// thought jsonl 写入已下沉到 aun.ts:sendThought 成功后,
|
|
@@ -6567,7 +7386,7 @@ export class ResponseEngine {
|
|
|
6567
7386
|
logger.info(`[ResponseEngine] Auto-filled session name: ${event.sessionTitle}`);
|
|
6568
7387
|
}
|
|
6569
7388
|
// 记录完成状态
|
|
6570
|
-
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText, numTurns: event.numTurns, ttftMs: event.ttftMs, model: event.model, tokenUsage: event.tokenUsage, contextUsage: event.contextUsage, lastModelCall: event.lastModelCall };
|
|
7389
|
+
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText, syntheticDiagnosticText, numTurns: event.numTurns, ttftMs: event.ttftMs, model: event.model, tokenUsage: event.tokenUsage, contextUsage: event.contextUsage, lastModelCall: event.lastModelCall };
|
|
6571
7390
|
if (event.subtype === 'success')
|
|
6572
7391
|
madeProgress = true;
|
|
6573
7392
|
}
|