evolcore 0.0.4 → 0.0.6
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 +65 -3
- package/README.md +4 -12
- package/dist/agents/baseagent.js +39 -0
- package/dist/agents/claude-runner.js +10 -2
- package/dist/agents/codex-runner.js +37 -8
- package/dist/agents/ecagent-runner.js +1159 -0
- package/dist/agents/gemini-runner.js +4 -1
- package/dist/agents/runner-types.js +20 -4
- package/dist/aun/msg/history.js +32 -9
- package/dist/aun/outbox.js +4 -9
- package/dist/channels/aun.js +32 -6
- package/dist/channels/daemon.js +7 -4
- package/dist/channels/dingtalk.js +3 -5
- package/dist/channels/feishu.js +16 -108
- package/dist/channels/wechat.js +4 -4
- package/dist/channels/wecom.js +4 -6
- package/dist/cli/agent-command.js +1 -1
- package/dist/cli/agent.js +42 -11
- package/dist/cli/aun-commands.js +50 -3
- package/dist/cli/daemon-commands.js +127 -21
- package/dist/cli/data-command.js +132 -0
- package/dist/cli/index.js +11 -3
- package/dist/cli/init.js +116 -37
- package/dist/cli/net-check.js +9 -20
- package/dist/cli/restart-monitor.js +11 -2
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-msg.js +12 -25
- package/dist/config/access-policy-domain.js +0 -20
- package/dist/config/access-policy.js +5 -29
- package/dist/config/builtin-roles.js +3 -1
- package/dist/config/config-field-policy.js +1 -1
- package/dist/config/gateway-config.js +21 -4
- package/dist/config/resolved-config-op.js +10 -0
- package/dist/core/auth/operation-catalog.js +1 -1
- package/dist/core/command/command-handler.js +2 -2
- package/dist/core/command/connect-menu.js +20 -42
- package/dist/core/command/menu-handler.js +11 -9
- package/dist/core/command/role-menu.js +27 -0
- package/dist/core/command/slash-handler.js +18 -14
- package/dist/core/data-migration.js +1434 -0
- package/dist/core/handoff/store.js +14 -4
- package/dist/core/inference/text-inference.js +42 -1
- package/dist/core/message/inbound-admission.js +23 -13
- package/dist/core/message/pending-hints.js +1 -1
- package/dist/core/message/response-engine.js +406 -167
- package/dist/core/message/retry-scheduler.js +71 -0
- package/dist/core/model/model-catalog.js +7 -2
- package/dist/core/model/model-fallback.js +81 -0
- package/dist/core/protected-paths.js +41 -8
- package/dist/core/session/adapters/ecagent-session-file-adapter.js +112 -0
- package/dist/core/session/session-fs-store.js +300 -61
- package/dist/core/session/session-manager.js +225 -21
- package/dist/core/session/session-title.js +1 -0
- package/dist/core/session/session-turn-coordinator.js +8 -1
- package/dist/core/system-channels.js +1 -0
- package/dist/index.js +10 -9
- package/dist/paths.js +35 -27
- package/dist/trigger/feedback.js +16 -0
- package/dist/trigger/legacy-session-history.js +19 -0
- package/dist/trigger/manager.js +12 -1
- package/dist/trigger/parser.js +4 -2
- package/dist/trigger/scheduler.js +185 -15
- package/dist/trigger/session-lock.js +62 -0
- package/dist/trigger/state.js +64 -0
- package/dist/trigger/validation.js +32 -2
- package/dist/utils/aid-bind.js +1 -1
- package/dist/utils/cross-platform.js +8 -2
- package/dist/utils/error-utils.js +9 -0
- package/dist/utils/process-introspect.js +19 -3
- package/dist/utils/welcome.js +2 -3
- package/ecagent/LICENSE +21 -0
- package/ecagent/NOTICE +11 -0
- package/ecagent/README.md +61 -0
- package/ecagent/dist/agent-loop.d.ts +24 -0
- package/ecagent/dist/agent-loop.d.ts.map +1 -0
- package/ecagent/dist/agent-loop.js +547 -0
- package/ecagent/dist/agent-loop.js.map +1 -0
- package/ecagent/dist/agent.d.ts +127 -0
- package/ecagent/dist/agent.d.ts.map +1 -0
- package/ecagent/dist/agent.js +386 -0
- package/ecagent/dist/agent.js.map +1 -0
- package/ecagent/dist/harness/agent-harness.d.ts +95 -0
- package/ecagent/dist/harness/agent-harness.d.ts.map +1 -0
- package/ecagent/dist/harness/agent-harness.js +926 -0
- package/ecagent/dist/harness/agent-harness.js.map +1 -0
- package/ecagent/dist/harness/compaction/branch-summarization.d.ts +51 -0
- package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
- package/ecagent/dist/harness/compaction/branch-summarization.js +174 -0
- package/ecagent/dist/harness/compaction/branch-summarization.js.map +1 -0
- package/ecagent/dist/harness/compaction/compaction.d.ts +95 -0
- package/ecagent/dist/harness/compaction/compaction.d.ts.map +1 -0
- package/ecagent/dist/harness/compaction/compaction.js +528 -0
- package/ecagent/dist/harness/compaction/compaction.js.map +1 -0
- package/ecagent/dist/harness/compaction/utils.d.ts +25 -0
- package/ecagent/dist/harness/compaction/utils.d.ts.map +1 -0
- package/ecagent/dist/harness/compaction/utils.js +131 -0
- package/ecagent/dist/harness/compaction/utils.js.map +1 -0
- package/ecagent/dist/harness/env/nodejs.d.ts +51 -0
- package/ecagent/dist/harness/env/nodejs.d.ts.map +1 -0
- package/ecagent/dist/harness/env/nodejs.js +513 -0
- package/ecagent/dist/harness/env/nodejs.js.map +1 -0
- package/ecagent/dist/harness/messages.d.ts +51 -0
- package/ecagent/dist/harness/messages.d.ts.map +1 -0
- package/ecagent/dist/harness/messages.js +102 -0
- package/ecagent/dist/harness/messages.js.map +1 -0
- package/ecagent/dist/harness/prompt-templates.d.ts +48 -0
- package/ecagent/dist/harness/prompt-templates.d.ts.map +1 -0
- package/ecagent/dist/harness/prompt-templates.js +230 -0
- package/ecagent/dist/harness/prompt-templates.js.map +1 -0
- package/ecagent/dist/harness/session/jsonl-repo.d.ts +26 -0
- package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +1 -0
- package/ecagent/dist/harness/session/jsonl-repo.js +100 -0
- package/ecagent/dist/harness/session/jsonl-repo.js.map +1 -0
- package/ecagent/dist/harness/session/jsonl-storage.d.ts +34 -0
- package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +1 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +234 -0
- package/ecagent/dist/harness/session/jsonl-storage.js.map +1 -0
- package/ecagent/dist/harness/session/memory-repo.d.ts +18 -0
- package/ecagent/dist/harness/session/memory-repo.d.ts.map +1 -0
- package/ecagent/dist/harness/session/memory-repo.js +44 -0
- package/ecagent/dist/harness/session/memory-repo.js.map +1 -0
- package/ecagent/dist/harness/session/memory-storage.d.ts +25 -0
- package/ecagent/dist/harness/session/memory-storage.d.ts.map +1 -0
- package/ecagent/dist/harness/session/memory-storage.js +111 -0
- package/ecagent/dist/harness/session/memory-storage.js.map +1 -0
- package/ecagent/dist/harness/session/repo-utils.d.ts +11 -0
- package/ecagent/dist/harness/session/repo-utils.d.ts.map +1 -0
- package/ecagent/dist/harness/session/repo-utils.js +39 -0
- package/ecagent/dist/harness/session/repo-utils.js.map +1 -0
- package/ecagent/dist/harness/session/session.d.ts +47 -0
- package/ecagent/dist/harness/session/session.d.ts.map +1 -0
- package/ecagent/dist/harness/session/session.js +245 -0
- package/ecagent/dist/harness/session/session.js.map +1 -0
- package/ecagent/dist/harness/session/uuid.d.ts +2 -0
- package/ecagent/dist/harness/session/uuid.d.ts.map +1 -0
- package/ecagent/dist/harness/session/uuid.js +50 -0
- package/ecagent/dist/harness/session/uuid.js.map +1 -0
- package/ecagent/dist/harness/skills.d.ts +44 -0
- package/ecagent/dist/harness/skills.d.ts.map +1 -0
- package/ecagent/dist/harness/skills.js +311 -0
- package/ecagent/dist/harness/skills.js.map +1 -0
- package/ecagent/dist/harness/system-prompt.d.ts +3 -0
- package/ecagent/dist/harness/system-prompt.d.ts.map +1 -0
- package/ecagent/dist/harness/system-prompt.js +30 -0
- package/ecagent/dist/harness/system-prompt.js.map +1 -0
- package/ecagent/dist/harness/types.d.ts +619 -0
- package/ecagent/dist/harness/types.d.ts.map +1 -0
- package/ecagent/dist/harness/types.js +81 -0
- package/ecagent/dist/harness/types.js.map +1 -0
- package/ecagent/dist/harness/utils/shell-output.d.ts +14 -0
- package/ecagent/dist/harness/utils/shell-output.d.ts.map +1 -0
- package/ecagent/dist/harness/utils/shell-output.js +126 -0
- package/ecagent/dist/harness/utils/shell-output.js.map +1 -0
- package/ecagent/dist/harness/utils/truncate.d.ts +70 -0
- package/ecagent/dist/harness/utils/truncate.d.ts.map +1 -0
- package/ecagent/dist/harness/utils/truncate.js +290 -0
- package/ecagent/dist/harness/utils/truncate.js.map +1 -0
- package/ecagent/dist/index.d.ts +22 -0
- package/ecagent/dist/index.d.ts.map +1 -0
- package/ecagent/dist/index.js +27 -0
- package/ecagent/dist/index.js.map +1 -0
- package/ecagent/dist/node.d.ts +3 -0
- package/ecagent/dist/node.d.ts.map +1 -0
- package/ecagent/dist/node.js +3 -0
- package/ecagent/dist/node.js.map +1 -0
- package/ecagent/dist/proxy.d.ts +69 -0
- package/ecagent/dist/proxy.d.ts.map +1 -0
- package/ecagent/dist/proxy.js +278 -0
- package/ecagent/dist/proxy.js.map +1 -0
- package/ecagent/dist/runtime/api/lazy.d.ts +15 -0
- package/ecagent/dist/runtime/api/lazy.d.ts.map +1 -0
- package/ecagent/dist/runtime/api/lazy.js +59 -0
- package/ecagent/dist/runtime/api/lazy.js.map +1 -0
- package/ecagent/dist/runtime/api/pi-messages.d.ts +97 -0
- package/ecagent/dist/runtime/api/pi-messages.d.ts.map +1 -0
- package/ecagent/dist/runtime/api/pi-messages.js +307 -0
- package/ecagent/dist/runtime/api/pi-messages.js.map +1 -0
- package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts +3 -0
- package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +1 -0
- package/ecagent/dist/runtime/api/pi-messages.lazy.js +3 -0
- package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +1 -0
- package/ecagent/dist/runtime/auth/context.d.ts +7 -0
- package/ecagent/dist/runtime/auth/context.d.ts.map +1 -0
- package/ecagent/dist/runtime/auth/context.js +42 -0
- package/ecagent/dist/runtime/auth/context.js.map +1 -0
- package/ecagent/dist/runtime/auth/credential-store.d.ts +16 -0
- package/ecagent/dist/runtime/auth/credential-store.d.ts.map +1 -0
- package/ecagent/dist/runtime/auth/credential-store.js +39 -0
- package/ecagent/dist/runtime/auth/credential-store.js.map +1 -0
- package/ecagent/dist/runtime/auth/helpers.d.ts +20 -0
- package/ecagent/dist/runtime/auth/helpers.d.ts.map +1 -0
- package/ecagent/dist/runtime/auth/helpers.js +46 -0
- package/ecagent/dist/runtime/auth/helpers.js.map +1 -0
- package/ecagent/dist/runtime/auth/resolve.d.ts +26 -0
- package/ecagent/dist/runtime/auth/resolve.d.ts.map +1 -0
- package/ecagent/dist/runtime/auth/resolve.js +100 -0
- package/ecagent/dist/runtime/auth/resolve.js.map +1 -0
- package/ecagent/dist/runtime/auth/types.d.ts +180 -0
- package/ecagent/dist/runtime/auth/types.d.ts.map +1 -0
- package/ecagent/dist/runtime/auth/types.js +2 -0
- package/ecagent/dist/runtime/auth/types.js.map +1 -0
- package/ecagent/dist/runtime/compat.d.ts +28 -0
- package/ecagent/dist/runtime/compat.d.ts.map +1 -0
- package/ecagent/dist/runtime/compat.js +86 -0
- package/ecagent/dist/runtime/compat.js.map +1 -0
- package/ecagent/dist/runtime/index.d.ts +20 -0
- package/ecagent/dist/runtime/index.d.ts.map +1 -0
- package/ecagent/dist/runtime/index.js +17 -0
- package/ecagent/dist/runtime/index.js.map +1 -0
- package/ecagent/dist/runtime/models.d.ts +142 -0
- package/ecagent/dist/runtime/models.d.ts.map +1 -0
- package/ecagent/dist/runtime/models.js +245 -0
- package/ecagent/dist/runtime/models.js.map +1 -0
- package/ecagent/dist/runtime/oauth.d.ts +2 -0
- package/ecagent/dist/runtime/oauth.d.ts.map +1 -0
- package/ecagent/dist/runtime/oauth.js +2 -0
- package/ecagent/dist/runtime/oauth.js.map +1 -0
- package/ecagent/dist/runtime/providers/faux.d.ts +97 -0
- package/ecagent/dist/runtime/providers/faux.d.ts.map +1 -0
- package/ecagent/dist/runtime/providers/faux.js +395 -0
- package/ecagent/dist/runtime/providers/faux.js.map +1 -0
- package/ecagent/dist/runtime/types.d.ts +607 -0
- package/ecagent/dist/runtime/types.d.ts.map +1 -0
- package/ecagent/dist/runtime/types.js +2 -0
- package/ecagent/dist/runtime/types.js.map +1 -0
- package/ecagent/dist/runtime/utils/diagnostics.d.ts +19 -0
- package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/diagnostics.js +25 -0
- package/ecagent/dist/runtime/utils/diagnostics.js.map +1 -0
- package/ecagent/dist/runtime/utils/event-stream.d.ts +21 -0
- package/ecagent/dist/runtime/utils/event-stream.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/event-stream.js +77 -0
- package/ecagent/dist/runtime/utils/event-stream.js.map +1 -0
- package/ecagent/dist/runtime/utils/headers.d.ts +4 -0
- package/ecagent/dist/runtime/utils/headers.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/headers.js +18 -0
- package/ecagent/dist/runtime/utils/headers.js.map +1 -0
- package/ecagent/dist/runtime/utils/json-parse.d.ts +16 -0
- package/ecagent/dist/runtime/utils/json-parse.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/json-parse.js +113 -0
- package/ecagent/dist/runtime/utils/json-parse.js.map +1 -0
- package/ecagent/dist/runtime/utils/oauth/types.d.ts +64 -0
- package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/oauth/types.js +2 -0
- package/ecagent/dist/runtime/utils/oauth/types.js.map +1 -0
- package/ecagent/dist/runtime/utils/overflow.d.ts +58 -0
- package/ecagent/dist/runtime/utils/overflow.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/overflow.js +158 -0
- package/ecagent/dist/runtime/utils/overflow.js.map +1 -0
- package/ecagent/dist/runtime/utils/provider-env.d.ts +7 -0
- package/ecagent/dist/runtime/utils/provider-env.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/provider-env.js +44 -0
- package/ecagent/dist/runtime/utils/provider-env.js.map +1 -0
- package/ecagent/dist/runtime/utils/retry.d.ts +12 -0
- package/ecagent/dist/runtime/utils/retry.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/retry.js +90 -0
- package/ecagent/dist/runtime/utils/retry.js.map +1 -0
- package/ecagent/dist/runtime/utils/typebox-helpers.d.ts +17 -0
- package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/typebox-helpers.js +21 -0
- package/ecagent/dist/runtime/utils/typebox-helpers.js.map +1 -0
- package/ecagent/dist/runtime/utils/validation.d.ts +18 -0
- package/ecagent/dist/runtime/utils/validation.d.ts.map +1 -0
- package/ecagent/dist/runtime/utils/validation.js +269 -0
- package/ecagent/dist/runtime/utils/validation.js.map +1 -0
- package/ecagent/dist/types.d.ts +401 -0
- package/ecagent/dist/types.d.ts.map +1 -0
- package/ecagent/dist/types.js +2 -0
- package/ecagent/dist/types.js.map +1 -0
- package/ecagent/package.json +93 -0
- package/ecagent/src/agent-loop.ts +792 -0
- package/ecagent/src/agent.ts +575 -0
- package/ecagent/src/harness/agent-harness.ts +1029 -0
- package/ecagent/src/harness/compaction/branch-summarization.ts +261 -0
- package/ecagent/src/harness/compaction/compaction.ts +753 -0
- package/ecagent/src/harness/compaction/utils.ts +144 -0
- package/ecagent/src/harness/env/nodejs.ts +569 -0
- package/ecagent/src/harness/messages.ts +164 -0
- package/ecagent/src/harness/prompt-templates.ts +267 -0
- package/ecagent/src/harness/session/jsonl-repo.ts +179 -0
- package/ecagent/src/harness/session/jsonl-storage.ts +314 -0
- package/ecagent/src/harness/session/memory-repo.ts +50 -0
- package/ecagent/src/harness/session/memory-storage.ts +133 -0
- package/ecagent/src/harness/session/repo-utils.ts +51 -0
- package/ecagent/src/harness/session/session.ts +338 -0
- package/ecagent/src/harness/session/uuid.ts +54 -0
- package/ecagent/src/harness/skills.ts +375 -0
- package/ecagent/src/harness/system-prompt.ts +34 -0
- package/ecagent/src/harness/types.ts +838 -0
- package/ecagent/src/harness/utils/shell-output.ts +135 -0
- package/ecagent/src/harness/utils/truncate.ts +344 -0
- package/ecagent/src/index.ts +46 -0
- package/ecagent/src/node.ts +2 -0
- package/ecagent/src/proxy.ts +367 -0
- package/ecagent/src/runtime/api/lazy.ts +70 -0
- package/ecagent/src/runtime/api/pi-messages.lazy.ts +4 -0
- package/ecagent/src/runtime/api/pi-messages.ts +436 -0
- package/ecagent/src/runtime/auth/context.ts +45 -0
- package/ecagent/src/runtime/auth/credential-store.ts +47 -0
- package/ecagent/src/runtime/auth/helpers.ts +46 -0
- package/ecagent/src/runtime/auth/resolve.ts +141 -0
- package/ecagent/src/runtime/auth/types.ts +182 -0
- package/ecagent/src/runtime/compat.ts +158 -0
- package/ecagent/src/runtime/index.ts +31 -0
- package/ecagent/src/runtime/models.ts +452 -0
- package/ecagent/src/runtime/oauth.ts +1 -0
- package/ecagent/src/runtime/providers/faux.ts +538 -0
- package/ecagent/src/runtime/types.ts +718 -0
- package/ecagent/src/runtime/utils/diagnostics.ts +45 -0
- package/ecagent/src/runtime/utils/event-stream.ts +88 -0
- package/ecagent/src/runtime/utils/headers.ts +18 -0
- package/ecagent/src/runtime/utils/json-parse.ts +124 -0
- package/ecagent/src/runtime/utils/oauth/types.ts +79 -0
- package/ecagent/src/runtime/utils/overflow.ts +165 -0
- package/ecagent/src/runtime/utils/provider-env.ts +52 -0
- package/ecagent/src/runtime/utils/retry.ts +101 -0
- package/ecagent/src/runtime/utils/typebox-helpers.ts +24 -0
- package/ecagent/src/runtime/utils/validation.ts +310 -0
- package/ecagent/src/types.ts +430 -0
- package/ecagent/tsconfig.build.json +32 -0
- package/kits/docs/evolcore/config.md +4 -4
- package/kits/docs/evolcore/contact.md +2 -1
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.10.json +370 -0
- package/kits/schemas/agent-config.schema.9.json +2 -12
- package/kits/schemas/defaults.schema.4.json +93 -0
- package/kits/schemas/relation-config.schema.8.json +69 -0
- package/kits/templates/roles/admin.json +1 -1
- package/package.json +22 -6
- package/skills/eclink/SKILL.md +70 -0
- package/skills/eclink/agents/openai.yaml +4 -0
- package/assets/.env.template +0 -4
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
|
-
import { BaseagentRunnerUnavailableError, hasCompact,
|
|
5
|
+
import { BaseagentRunnerUnavailableError, hasCompact, autoCompactWindowForContextWindow, autoCompactWindowForModel, isClaudeContextUsageModel, numericToken } from '../../agents/runner-types.js';
|
|
6
6
|
import { buildTaskRuntimeEnv } from '../../cli/task-context.js';
|
|
7
7
|
import { IMRenderer } from './im-renderer.js';
|
|
8
8
|
import { createSendFileMarkerPattern } from './file-markers.js';
|
|
@@ -10,7 +10,7 @@ import { createTextInferenceProvider } from '../inference/text-inference.js';
|
|
|
10
10
|
import { StreamIdleMonitor } from './stream-idle-monitor.js';
|
|
11
11
|
import { logger } from '../../utils/logger.js';
|
|
12
12
|
import { isHostChinese } from '../../utils/locale.js';
|
|
13
|
-
import { getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
|
|
13
|
+
import { getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
|
|
14
14
|
import { isEvolcoreSendCommandForSession } from '../permission/ec-command-parser.js';
|
|
15
15
|
import { summarizeToolInput } from '../../utils/tool-summary.js';
|
|
16
16
|
import { getPackageRoot, resolveRoot, resolvePaths } from '../../paths.js';
|
|
@@ -28,7 +28,9 @@ import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
|
28
28
|
import { hasTrustedPrincipal } from '../auth/authenticated-actor.js';
|
|
29
29
|
// Re-export 工具函数(向后兼容,让其他模块可以从 response-engine 导入)
|
|
30
30
|
export { buildEnvelope, sendInteractionPayload } from './message-utils.js';
|
|
31
|
-
import { constrainResolvedModelForRole } from '../model/model-permission.js';
|
|
31
|
+
import { constrainResolvedModelForRole, isModelAllowedForRoleBaseagent } from '../model/model-permission.js';
|
|
32
|
+
import { eligibleFallbackModels } from '../model/model-fallback.js';
|
|
33
|
+
import { RetryScheduler } from './retry-scheduler.js';
|
|
32
34
|
import { constrainRuntimePermissionMode, resolveRuntimeStringField } from '../role/runtime-policy.js';
|
|
33
35
|
import { resolveEffective, resolveEffectiveFieldWithSource } from '../../config/config-manager.js';
|
|
34
36
|
import { dispatchToMentionMode } from '../../config/mention-mode.js';
|
|
@@ -130,9 +132,11 @@ function createRetryAttemptTimeout(timeoutMs) {
|
|
|
130
132
|
},
|
|
131
133
|
};
|
|
132
134
|
}
|
|
135
|
+
const MAX_CONTEXT_RECOVERY_ATTEMPTS = 2;
|
|
133
136
|
const RETRY_EXHAUSTED_MARK = Symbol('evolcore.retryExhausted');
|
|
134
137
|
const RETRY_MADE_PROGRESS_MARK = Symbol('evolcore.retryMadeProgress');
|
|
135
138
|
const RETRY_HEALTH_RECORDED_MARK = Symbol('evolcore.retryHealthRecorded');
|
|
139
|
+
const MODEL_FALLBACK_EXHAUSTED_MARK = Symbol('evolcore.modelFallbackExhausted');
|
|
136
140
|
function markRetryExhausted(error, retries) {
|
|
137
141
|
if (error && typeof error === 'object') {
|
|
138
142
|
error[RETRY_EXHAUSTED_MARK] = retries;
|
|
@@ -163,16 +167,84 @@ function markRetryHealthRecorded(error) {
|
|
|
163
167
|
function wasRetryHealthRecorded(error) {
|
|
164
168
|
return !!(error && typeof error === 'object' && error[RETRY_HEALTH_RECORDED_MARK] === true);
|
|
165
169
|
}
|
|
170
|
+
function markModelFallbackExhausted(error, message) {
|
|
171
|
+
error[MODEL_FALLBACK_EXHAUSTED_MARK] = message;
|
|
172
|
+
return error;
|
|
173
|
+
}
|
|
174
|
+
function getModelFallbackExhaustedMessage(error) {
|
|
175
|
+
const message = error && typeof error === 'object'
|
|
176
|
+
? error[MODEL_FALLBACK_EXHAUSTED_MARK]
|
|
177
|
+
: undefined;
|
|
178
|
+
return typeof message === 'string' ? message : undefined;
|
|
179
|
+
}
|
|
166
180
|
function isNonErrorInterruptReason(reason) {
|
|
167
181
|
return reason === 'new_message'
|
|
168
182
|
|| reason === 'stop'
|
|
169
183
|
|| reason === 'recalled'
|
|
170
184
|
|| reason === 'daemon_restart';
|
|
171
185
|
}
|
|
186
|
+
function isExpectedTerminalInterrupt(reason) {
|
|
187
|
+
return isAbortTerminalReason(reason);
|
|
188
|
+
}
|
|
172
189
|
function formatRetryableErrorFinalMessage(error, retries) {
|
|
173
190
|
const reason = getErrorMessage(error, undefined, false) || 'API 暂时不可用';
|
|
174
191
|
return `❌ API 暂时不可用,已自动重试 ${retries} 次仍失败,任务已停止。\n原因:${reason}`;
|
|
175
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* A protocol failure may be replayed only when the stream provides no evidence
|
|
195
|
+
* that model work or a side effect occurred. This deliberately excludes normal
|
|
196
|
+
* silent completions, which can be input-accepted and still return an empty
|
|
197
|
+
* result after consuming tokens.
|
|
198
|
+
*/
|
|
199
|
+
function isSafeProtocolReplayCandidate(result, reason) {
|
|
200
|
+
if (reason !== 'input_not_consumed' && reason !== 'empty_success')
|
|
201
|
+
return false;
|
|
202
|
+
if (result.isError
|
|
203
|
+
|| result.hasReceivedText
|
|
204
|
+
|| result.assistantUuid
|
|
205
|
+
|| (result.textCount ?? 0) > 0
|
|
206
|
+
|| (result.toolUseCount ?? 0) > 0
|
|
207
|
+
|| (result.openToolUseIds?.length ?? 0) > 0
|
|
208
|
+
|| result.lastReplyText.trim()
|
|
209
|
+
|| result.fullText.trim()
|
|
210
|
+
|| hasRecordedTokenUsage(result)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
function usageHasTokens(usage) {
|
|
216
|
+
if (!usage)
|
|
217
|
+
return false;
|
|
218
|
+
if (numericToken(usage.input_tokens) > 0
|
|
219
|
+
|| numericToken(usage.output_tokens) > 0
|
|
220
|
+
|| numericToken(usage.cache_read_input_tokens) > 0
|
|
221
|
+
|| numericToken(usage.cache_creation_input_tokens) > 0) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
return Array.isArray(usage.iterations) && usage.iterations.some(usageHasTokens);
|
|
225
|
+
}
|
|
226
|
+
function hasRecordedTokenUsage(result) {
|
|
227
|
+
return usageHasTokens(result.tokenUsage)
|
|
228
|
+
|| usageHasTokens(result.lastModelCall?.tokenUsage)
|
|
229
|
+
|| result.modelCalls?.some(call => usageHasTokens(call.tokenUsage)) === true;
|
|
230
|
+
}
|
|
231
|
+
function formatProtocolReplayExhaustedMessage(reason, replayAttempts) {
|
|
232
|
+
if (replayAttempts === 0 || (reason !== 'input_not_consumed' && reason !== 'empty_success')) {
|
|
233
|
+
return `Agent 协议未闭合(${reason}),任务未提交`;
|
|
234
|
+
}
|
|
235
|
+
const detail = reason === 'input_not_consumed'
|
|
236
|
+
? '模型服务未确认接收本次输入'
|
|
237
|
+
: '模型服务未返回可提交的结果';
|
|
238
|
+
return `${detail}(${reason}),已立即重试 ${replayAttempts} 次仍失败,任务未提交。\n请稍后重试;若持续出现,请切换模型或向上游服务商提供错误代码和发生时间。`;
|
|
239
|
+
}
|
|
240
|
+
function withFallbackModel(override, candidate) {
|
|
241
|
+
const { model: _model, effort: _effort, effortMode: _effortMode, ...preserved } = override || {};
|
|
242
|
+
return {
|
|
243
|
+
...preserved,
|
|
244
|
+
model: candidate.model,
|
|
245
|
+
...(candidate.effort ? { effort: candidate.effort } : { effortMode: 'model_default' }),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
176
248
|
function getStreamErrorText(result) {
|
|
177
249
|
return [
|
|
178
250
|
result.errors?.join('\n'),
|
|
@@ -217,6 +289,17 @@ function streamHitContextLimit(result) {
|
|
|
217
289
|
isContextTooLongText(result.errors?.join(' ') || '') ||
|
|
218
290
|
isContextTooLongText(result.fullText);
|
|
219
291
|
}
|
|
292
|
+
/** Preserve task-level audit facts when a context recovery starts a new SDK stream. */
|
|
293
|
+
function mergeContextRecoveryResult(previous, next) {
|
|
294
|
+
return {
|
|
295
|
+
...next,
|
|
296
|
+
inputAccepted: previous.inputAccepted || next.inputAccepted,
|
|
297
|
+
textCount: (previous.textCount ?? 0) + (next.textCount ?? 0),
|
|
298
|
+
toolUseCount: (previous.toolUseCount ?? 0) + (next.toolUseCount ?? 0),
|
|
299
|
+
numTurns: (previous.numTurns ?? 0) + (next.numTurns ?? 0),
|
|
300
|
+
contextRecoveryAttempts: (previous.contextRecoveryAttempts ?? 0) + 1,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
220
303
|
/**
|
|
221
304
|
* `isContextTooLongText` 是给错误分类用的宽松匹配,不能拿来删除模型正文:正常回答
|
|
222
305
|
* 可能会说明或引用 `Prompt is too long`。这里只识别网关会单独输出的短错误行。
|
|
@@ -292,9 +375,6 @@ function getContextCompactFailedHint(agent) {
|
|
|
292
375
|
function canCompactAgent(agent) {
|
|
293
376
|
return hasCompact(agent) && agent.capabilities?.compact !== false;
|
|
294
377
|
}
|
|
295
|
-
function canClearAgent(agent) {
|
|
296
|
-
return hasClearSession(agent) && agent.capabilities?.clear !== false;
|
|
297
|
-
}
|
|
298
378
|
export async function resolveGroupRulesForTurn(opts, sync = syncGroupRulesContext) {
|
|
299
379
|
if (!opts.isAunGroup)
|
|
300
380
|
return {};
|
|
@@ -305,9 +385,7 @@ export async function resolveGroupRulesForTurn(opts, sync = syncGroupRulesContex
|
|
|
305
385
|
return sync({ selfAid: opts.selfAid, groupId: opts.groupId, channel: opts.channel });
|
|
306
386
|
}
|
|
307
387
|
function autoCompactTokensFromMaxTokens(maxTokens) {
|
|
308
|
-
|
|
309
|
-
return undefined;
|
|
310
|
-
return maxTokens >= 1000000 ? maxTokens - 100000 : maxTokens;
|
|
388
|
+
return autoCompactWindowForContextWindow(maxTokens);
|
|
311
389
|
}
|
|
312
390
|
/**
|
|
313
391
|
* 构造 OutboundEnvelope —— 出站三件套的信封部分。
|
|
@@ -341,8 +419,10 @@ export class ResponseEngine {
|
|
|
341
419
|
primaryRunnerKey;
|
|
342
420
|
interruptedSessions = new Map(); // sessionId → reason ('new_message' | 'stop' | ...)
|
|
343
421
|
timeoutErrors = new Map();
|
|
344
|
-
/** sessionId →
|
|
422
|
+
/** sessionId → 已成功的模型降级状态(进程重启清零) */
|
|
345
423
|
modelFallbackMap = new Map();
|
|
424
|
+
/** sessionId → interruptible task-level API retry delays. */
|
|
425
|
+
retryDelayWaiters = new Map();
|
|
346
426
|
interactionRouter;
|
|
347
427
|
messageQueue;
|
|
348
428
|
handoffRuntime;
|
|
@@ -479,6 +559,7 @@ export class ResponseEngine {
|
|
|
479
559
|
const reason = (event.reason || 'new_message');
|
|
480
560
|
this.turnCoordinator.invalidate(event.sessionId, reason);
|
|
481
561
|
this.interruptedSessions.set(event.sessionId, reason);
|
|
562
|
+
this.cancelRetryDelays(event.sessionId);
|
|
482
563
|
this.agentDelegationRegistry?.revokeSession(event.sessionId);
|
|
483
564
|
}
|
|
484
565
|
});
|
|
@@ -512,6 +593,9 @@ export class ResponseEngine {
|
|
|
512
593
|
this.messageQueue = queue;
|
|
513
594
|
}
|
|
514
595
|
async interruptSession(sessionId, reason) {
|
|
596
|
+
// A retry delay has no active runner stream to abort. Wake it before any
|
|
597
|
+
// session-store I/O so /stop and a newer message take effect immediately.
|
|
598
|
+
this.cancelRetryDelays(sessionId);
|
|
515
599
|
const session = await this.sessionManager.getSessionById(sessionId);
|
|
516
600
|
if (!session)
|
|
517
601
|
return;
|
|
@@ -831,30 +915,22 @@ export class ResponseEngine {
|
|
|
831
915
|
return await this.processEventStream(retryStream, session, agent, renderer, resetTimer, shouldSuppress, proactive, undefined, // 重试分支不调插件钩子
|
|
832
916
|
undefined, turnLease);
|
|
833
917
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
if
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
let
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
throw new Error('CONTEXT_COMPACT_FAILED');
|
|
918
|
+
// Dropping the whole session hides the root cause and can replay side
|
|
919
|
+
// effects in unattended trigger tasks. Surface an actionable failure
|
|
920
|
+
// instead; callers may explicitly start a fresh task if that is desired.
|
|
921
|
+
throw new Error('CONTEXT_COMPACT_FAILED');
|
|
922
|
+
}
|
|
923
|
+
async recoverFromContextLimit(initialResult, opts) {
|
|
924
|
+
let result = initialResult;
|
|
925
|
+
let attempts = initialResult.contextRecoveryAttempts ?? 0;
|
|
926
|
+
while (attempts < MAX_CONTEXT_RECOVERY_ATTEMPTS) {
|
|
927
|
+
const recovered = await this.retryAfterContextRecovery('上下文已自动压缩,请继续之前未完成的任务。', opts);
|
|
928
|
+
result = mergeContextRecoveryResult(result, recovered);
|
|
929
|
+
attempts = result.contextRecoveryAttempts ?? attempts + 1;
|
|
930
|
+
if (!result.isError || !streamHitContextLimit(result))
|
|
931
|
+
return result;
|
|
849
932
|
}
|
|
850
|
-
|
|
851
|
-
await this.sessionManager.updateSession(session.id, { agentSessionId: null });
|
|
852
|
-
await this.emitOperationalNotice(renderer, '✅ 会话已清空,继续处理...', 'info', 'clear-retry');
|
|
853
|
-
await renderer.flush();
|
|
854
|
-
const retryStream = await agent.runQuery(session.id, '会话历史已清空,请继续之前未完成的任务。', absoluteProjectPath, undefined, undefined, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
|
|
855
|
-
agent.registerStream(streamKey, retryStream);
|
|
856
|
-
return await this.processEventStream(retryStream, session, agent, renderer, resetTimer, shouldSuppress, proactive, undefined, // 重试分支不调插件钩子
|
|
857
|
-
undefined, turnLease);
|
|
933
|
+
return result;
|
|
858
934
|
}
|
|
859
935
|
/**
|
|
860
936
|
* 根据 channel 标识查找渠道信息
|
|
@@ -1357,6 +1433,36 @@ export class ResponseEngine {
|
|
|
1357
1433
|
? configuredSeconds * 1000
|
|
1358
1434
|
: undefined;
|
|
1359
1435
|
}
|
|
1436
|
+
waitForRetryDelay(sessionId, delayMs) {
|
|
1437
|
+
if (this.interruptedSessions.has(sessionId))
|
|
1438
|
+
return Promise.resolve(false);
|
|
1439
|
+
return new Promise(resolve => {
|
|
1440
|
+
let settled = false;
|
|
1441
|
+
const waiters = this.retryDelayWaiters.get(sessionId) ?? new Set();
|
|
1442
|
+
this.retryDelayWaiters.set(sessionId, waiters);
|
|
1443
|
+
const finish = (elapsed) => {
|
|
1444
|
+
if (settled)
|
|
1445
|
+
return;
|
|
1446
|
+
settled = true;
|
|
1447
|
+
clearTimeout(timer);
|
|
1448
|
+
waiters.delete(cancel);
|
|
1449
|
+
if (waiters.size === 0)
|
|
1450
|
+
this.retryDelayWaiters.delete(sessionId);
|
|
1451
|
+
resolve(elapsed);
|
|
1452
|
+
};
|
|
1453
|
+
const cancel = () => finish(false);
|
|
1454
|
+
const timer = setTimeout(() => finish(true), delayMs);
|
|
1455
|
+
timer.unref?.();
|
|
1456
|
+
waiters.add(cancel);
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
cancelRetryDelays(sessionId) {
|
|
1460
|
+
const waiters = this.retryDelayWaiters.get(sessionId);
|
|
1461
|
+
if (!waiters)
|
|
1462
|
+
return;
|
|
1463
|
+
for (const cancel of [...waiters])
|
|
1464
|
+
cancel();
|
|
1465
|
+
}
|
|
1360
1466
|
/** 自动安全模式已禁用:仅保留错误计数,不再自动切换状态 */
|
|
1361
1467
|
async _processMessageInternal(message, session, absoluteProjectPath, resetTimer, shouldSuppress, getLastIdleSec, outputState = { middleOutputMode: 'all' }, timeoutControl) {
|
|
1362
1468
|
const messageId = `${message.channel}_${message.channelId}_${message.timestamp || Date.now()}`;
|
|
@@ -1703,6 +1809,11 @@ export class ResponseEngine {
|
|
|
1703
1809
|
});
|
|
1704
1810
|
};
|
|
1705
1811
|
}
|
|
1812
|
+
// Kept outside the task body so a thrown terminal error can preserve the
|
|
1813
|
+
// work already performed in its trigger audit metadata.
|
|
1814
|
+
let streamResult = { isError: false, lastReplyText: '', fullText: '', hasReceivedText: false };
|
|
1815
|
+
let startTime = Date.now();
|
|
1816
|
+
let protocolReplayAttempts = 0;
|
|
1706
1817
|
try {
|
|
1707
1818
|
const isBackground = this.isBackgroundSession(session, message.channel, message.channelId);
|
|
1708
1819
|
// 记录收到消息
|
|
@@ -1879,7 +1990,7 @@ export class ResponseEngine {
|
|
|
1879
1990
|
// 均以普通非最终文本显示运行提示;none 不显示。其它 activity 在 all 下仍保持结构化。
|
|
1880
1991
|
// 它仍发生在真正的 prompt 执行之前。
|
|
1881
1992
|
await this.runPendingAutoCompactAtTaskStart(session, agent, absoluteProjectPath, renderer);
|
|
1882
|
-
|
|
1993
|
+
startTime = Date.now();
|
|
1883
1994
|
if (isProactive) {
|
|
1884
1995
|
logger.info(`[ResponseEngine] proactive mode: outputs via thought.put task=${taskId}`);
|
|
1885
1996
|
}
|
|
@@ -2151,12 +2262,15 @@ export class ResponseEngine {
|
|
|
2151
2262
|
: body;
|
|
2152
2263
|
// 先用裸文本兜底;vars 构造完成后用消息渲染层重算(见下方 effectivePrompt 重赋值)。
|
|
2153
2264
|
let effectivePrompt = wrapPrompt(message.content);
|
|
2154
|
-
let streamResult = { isError: false, lastReplyText: '', fullText: '', hasReceivedText: false };
|
|
2155
2265
|
let renderResult;
|
|
2156
2266
|
let effectiveSystemPrompt;
|
|
2157
2267
|
let modelOverride;
|
|
2158
|
-
let usedFallback
|
|
2268
|
+
let usedFallback;
|
|
2159
2269
|
let skipEvolcoreModel = false;
|
|
2270
|
+
let probingPrimaryModel = false;
|
|
2271
|
+
let primaryModel;
|
|
2272
|
+
let fallbackCandidates = [];
|
|
2273
|
+
let previousFallbackState;
|
|
2160
2274
|
let agentModel;
|
|
2161
2275
|
let v2HandoffIds = [];
|
|
2162
2276
|
let v2HandoffDirection;
|
|
@@ -2211,64 +2325,50 @@ export class ResponseEngine {
|
|
|
2211
2325
|
let effectiveEffortSource;
|
|
2212
2326
|
const triggerModelOverride = message.triggerMeta?.modelOverride;
|
|
2213
2327
|
const triggerEffortOverride = message.triggerMeta?.effortOverride;
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
// 取降级状态,按退避策略决定是否跳过 evolcore 作用域模型
|
|
2217
|
-
const fbState = this.modelFallbackMap.get(session.id) ?? {
|
|
2218
|
-
failCount: 0, fallbackActive: false,
|
|
2219
|
-
messagesSinceFallback: 0, nextProbeAt: 2, hintShown: false,
|
|
2220
|
-
};
|
|
2221
|
-
// 退避期内递增消息计数,判断是否到探测点
|
|
2222
|
-
if (fbState.fallbackActive) {
|
|
2223
|
-
fbState.messagesSinceFallback++;
|
|
2224
|
-
skipEvolcoreModel = fbState.messagesSinceFallback < fbState.nextProbeAt;
|
|
2225
|
-
this.modelFallbackMap.set(session.id, fbState);
|
|
2226
|
-
}
|
|
2227
|
-
// 非跳过时:尝试解析 evolcore 作用域模型
|
|
2328
|
+
const hasExplicitTriggerModel = typeof triggerModelOverride === 'string'
|
|
2329
|
+
&& triggerModelOverride.trim().length > 0;
|
|
2228
2330
|
let evolcoreModelOverride;
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
effectiveModel = model;
|
|
2264
|
-
}
|
|
2265
|
-
effectiveEffort = effectiveEffortValue;
|
|
2266
|
-
}
|
|
2267
|
-
catch (e) {
|
|
2268
|
-
logger.warn(`[ResponseEngine] effective model config resolution failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2331
|
+
try {
|
|
2332
|
+
const selector = { self: selfAid || undefined, peerKey };
|
|
2333
|
+
const modelResolution = resolveEffectiveFieldWithSource(`baseagents.${normalizedBaseagent.canonical}.model`, selector, { cache: true });
|
|
2334
|
+
const effortResolution = resolveEffectiveFieldWithSource(`baseagents.${normalizedBaseagent.canonical}.${normalizedBaseagent.canonical === 'codex' ? 'reasoning' : 'effort'}`, selector, { cache: true });
|
|
2335
|
+
const modelDecision = constrainResolvedModelForRole({
|
|
2336
|
+
role: peerRole,
|
|
2337
|
+
baseagent: normalizedBaseagent.canonical,
|
|
2338
|
+
model: modelResolution.value,
|
|
2339
|
+
resolveModelId: typeof agent.resolveModelId === 'function'
|
|
2340
|
+
? agent.resolveModelId.bind(agent)
|
|
2341
|
+
: undefined,
|
|
2342
|
+
selfAid: selfAid || undefined,
|
|
2343
|
+
});
|
|
2344
|
+
const model = modelDecision.model;
|
|
2345
|
+
const effortField = `baseagents.${normalizedBaseagent.canonical}.${normalizedBaseagent.canonical === 'codex' ? 'reasoning' : 'effort'}`;
|
|
2346
|
+
const effortDecision = resolveRuntimeStringField({
|
|
2347
|
+
selfAid: selfAid || undefined,
|
|
2348
|
+
role: peerRole,
|
|
2349
|
+
field: effortField,
|
|
2350
|
+
configuredValue: effortResolution.value,
|
|
2351
|
+
});
|
|
2352
|
+
const effectiveEffortValue = effortDecision.effectiveValue;
|
|
2353
|
+
const effort = effectiveEffortValue === 'auto' ? undefined : effectiveEffortValue;
|
|
2354
|
+
effectiveModelSource = modelDecision.constrained
|
|
2355
|
+
? 'role'
|
|
2356
|
+
: modelResolution.source ? `${modelResolution.source.target}:${modelResolution.source.file}` : undefined;
|
|
2357
|
+
effectiveEffortSource = effortDecision.decidedBy === 'role'
|
|
2358
|
+
? 'role'
|
|
2359
|
+
: effortResolution.source
|
|
2360
|
+
? `${effortResolution.source.target}:${effortResolution.source.file}`
|
|
2361
|
+
: undefined;
|
|
2362
|
+
if (model || effort) {
|
|
2363
|
+
evolcoreModelOverride = { model, effort };
|
|
2364
|
+
effectiveModel = model;
|
|
2269
2365
|
}
|
|
2270
|
-
|
|
2366
|
+
effectiveEffort = effectiveEffortValue;
|
|
2271
2367
|
}
|
|
2368
|
+
catch (e) {
|
|
2369
|
+
logger.warn(`[ResponseEngine] effective model config resolution failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2370
|
+
}
|
|
2371
|
+
modelOverride = evolcoreModelOverride;
|
|
2272
2372
|
if (triggerModelOverride || triggerEffortOverride) {
|
|
2273
2373
|
const constrainedOverride = { ...(modelOverride || {}) };
|
|
2274
2374
|
if (triggerModelOverride) {
|
|
@@ -2287,7 +2387,6 @@ export class ResponseEngine {
|
|
|
2287
2387
|
delete constrainedOverride.model;
|
|
2288
2388
|
effectiveModel = triggerModelDecision.model;
|
|
2289
2389
|
effectiveModelSource = triggerModelDecision.constrained ? 'role' : 'trigger';
|
|
2290
|
-
appliedTriggerModelOverride = !triggerModelDecision.constrained;
|
|
2291
2390
|
}
|
|
2292
2391
|
if (triggerEffortOverride) {
|
|
2293
2392
|
const effortField = `baseagents.${normalizedBaseagent.canonical}.${normalizedBaseagent.canonical === 'codex' ? 'reasoning' : 'effort'}`;
|
|
@@ -2298,9 +2397,9 @@ export class ResponseEngine {
|
|
|
2298
2397
|
configuredValue: triggerEffortOverride,
|
|
2299
2398
|
});
|
|
2300
2399
|
const triggerEffort = triggerEffortDecision.effectiveValue;
|
|
2301
|
-
|
|
2302
|
-
if (
|
|
2303
|
-
constrainedOverride.effort =
|
|
2400
|
+
const effectiveTriggerEffort = triggerEffort === 'auto' ? undefined : triggerEffort;
|
|
2401
|
+
if (effectiveTriggerEffort)
|
|
2402
|
+
constrainedOverride.effort = effectiveTriggerEffort;
|
|
2304
2403
|
else
|
|
2305
2404
|
delete constrainedOverride.effort;
|
|
2306
2405
|
effectiveEffort = triggerEffort;
|
|
@@ -2308,6 +2407,46 @@ export class ResponseEngine {
|
|
|
2308
2407
|
}
|
|
2309
2408
|
modelOverride = constrainedOverride;
|
|
2310
2409
|
}
|
|
2410
|
+
agentModel = (typeof agent.getModel === 'function') ? agent.getModel() : undefined;
|
|
2411
|
+
primaryModel = modelOverride?.model || agentModel;
|
|
2412
|
+
if (!hasExplicitTriggerModel && primaryModel) {
|
|
2413
|
+
const fallbackResolution = eligibleFallbackModels({
|
|
2414
|
+
baseagent: normalizedBaseagent.canonical,
|
|
2415
|
+
currentModel: primaryModel,
|
|
2416
|
+
resolveModelId: typeof agent.resolveModelId === 'function'
|
|
2417
|
+
? agent.resolveModelId.bind(agent)
|
|
2418
|
+
: undefined,
|
|
2419
|
+
isAllowed: model => isModelAllowedForRoleBaseagent(peerRole, normalizedBaseagent.canonical, model, typeof agent.resolveModelId === 'function'
|
|
2420
|
+
? agent.resolveModelId.bind(agent)
|
|
2421
|
+
: undefined, selfAid || undefined),
|
|
2422
|
+
});
|
|
2423
|
+
fallbackCandidates = fallbackResolution.candidates;
|
|
2424
|
+
if (fallbackResolution.rejected.length > 0) {
|
|
2425
|
+
logger.warn(`[ResponseEngine] Ignored ${fallbackResolution.rejected.length} fallback model candidate(s): session=${session.id}`);
|
|
2426
|
+
}
|
|
2427
|
+
const existingFallback = this.modelFallbackMap.get(session.id);
|
|
2428
|
+
if (existingFallback
|
|
2429
|
+
&& (existingFallback.baseagent !== normalizedBaseagent.canonical
|
|
2430
|
+
|| existingFallback.primaryModel !== primaryModel
|
|
2431
|
+
|| !fallbackCandidates.some(candidate => candidate.model === existingFallback.activeFallback.model))) {
|
|
2432
|
+
this.modelFallbackMap.delete(session.id);
|
|
2433
|
+
}
|
|
2434
|
+
else if (existingFallback) {
|
|
2435
|
+
previousFallbackState = existingFallback;
|
|
2436
|
+
existingFallback.messagesSinceFallback++;
|
|
2437
|
+
probingPrimaryModel = existingFallback.messagesSinceFallback >= existingFallback.nextProbeAt;
|
|
2438
|
+
if (!probingPrimaryModel) {
|
|
2439
|
+
skipEvolcoreModel = true;
|
|
2440
|
+
usedFallback = existingFallback.activeFallback;
|
|
2441
|
+
modelOverride = withFallbackModel(modelOverride, usedFallback);
|
|
2442
|
+
effectiveModel = usedFallback.model;
|
|
2443
|
+
effectiveModelSource = 'fallback';
|
|
2444
|
+
effectiveEffort = usedFallback.effort;
|
|
2445
|
+
effectiveEffortSource = usedFallback.effort ? 'fallback' : 'model_default';
|
|
2446
|
+
}
|
|
2447
|
+
this.modelFallbackMap.set(session.id, existingFallback);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2311
2450
|
// permissionMode 随角色策略或 trigger override 传入;单 runner
|
|
2312
2451
|
// 嵌入/测试路径没有 self/peer 作用域时,避免制造无配置来源的 override。
|
|
2313
2452
|
const shouldPassPermissionMode = !!message.triggerMeta?.permissionModeOverride || !!selfAid;
|
|
@@ -2320,7 +2459,6 @@ export class ResponseEngine {
|
|
|
2320
2459
|
sessionTitle: deriveSessionTitle(session.name, message.content, session.threadId),
|
|
2321
2460
|
};
|
|
2322
2461
|
}
|
|
2323
|
-
agentModel = (typeof agent.getModel === 'function') ? agent.getModel() : undefined;
|
|
2324
2462
|
const causationPath = inputCausation.trigger?.path ?? [];
|
|
2325
2463
|
const originNode = causationPath[0];
|
|
2326
2464
|
logger.info(`[ResponseEngine] execution context session=${session.id}`
|
|
@@ -2418,8 +2556,8 @@ export class ResponseEngine {
|
|
|
2418
2556
|
baseAgentName: normalizedBaseagent.displayName,
|
|
2419
2557
|
baseAgentModel: agentModel || undefined,
|
|
2420
2558
|
effectiveModel: effectiveModel || agentModel || undefined,
|
|
2421
|
-
modelFallbackActive:
|
|
2422
|
-
modelFallbackModel:
|
|
2559
|
+
modelFallbackActive: usedFallback ? true : undefined,
|
|
2560
|
+
modelFallbackModel: usedFallback?.model,
|
|
2423
2561
|
agentSessionId: session.agentSessionId || undefined,
|
|
2424
2562
|
// 渲染模式:各类型当前激活的 modeName(从内存 config 读,渲染层据此选 manifest section)。
|
|
2425
2563
|
renderModes: this.agentRegistry?.resolveByChannel(channelKey)?.config?.render ?? undefined,
|
|
@@ -2603,12 +2741,11 @@ export class ResponseEngine {
|
|
|
2603
2741
|
});
|
|
2604
2742
|
runtimeEnv[AGENT_DELEGATION_TOKEN_ENV] = delegationToken;
|
|
2605
2743
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2744
|
+
const retryScheduler = new RetryScheduler(fallbackCandidates, modelOverride?.model || agentModel, model => (typeof agent.resolveModelId === 'function'
|
|
2745
|
+
? (agent.resolveModelId(model) ?? model)
|
|
2746
|
+
: model));
|
|
2609
2747
|
const retryAttemptTimeoutMs = this.retryAttemptTimeoutMs();
|
|
2610
2748
|
let runAttempt = 1;
|
|
2611
|
-
let consecutiveRetryFailures = 0;
|
|
2612
2749
|
const recordRetryHealthError = async (retryError) => {
|
|
2613
2750
|
if (!policy.accumulateErrors(chatType, identityRole))
|
|
2614
2751
|
return;
|
|
@@ -2624,10 +2761,10 @@ export class ResponseEngine {
|
|
|
2624
2761
|
};
|
|
2625
2762
|
while (true) {
|
|
2626
2763
|
let streamRegistered = false;
|
|
2627
|
-
const shouldTimeoutRetryAttempt =
|
|
2764
|
+
const shouldTimeoutRetryAttempt = retryScheduler.shouldTimeoutAttempt() && retryAttemptTimeoutMs !== undefined;
|
|
2628
2765
|
let attemptTimeout;
|
|
2629
2766
|
try {
|
|
2630
|
-
logger.info(`[ResponseEngine] agent.runQuery start: agent=${agent.name} session=${session.id} task=${taskId} attempt=${runAttempt}
|
|
2767
|
+
logger.info(`[ResponseEngine] agent.runQuery start: agent=${agent.name} session=${session.id} task=${taskId} attempt=${runAttempt} apiRetries=${retryScheduler.apiRetryAttemptsMade} agentSessionId=${session.agentSessionId ?? 'none'}`);
|
|
2631
2768
|
const stream = await agent.runQuery(session.id, effectivePrompt, absoluteProjectPath, session.agentSessionId, renderResult?.images.length ? renderResult.images : message.images, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
|
|
2632
2769
|
agent.registerStream(streamKey, stream);
|
|
2633
2770
|
streamRegistered = true;
|
|
@@ -2642,15 +2779,32 @@ export class ResponseEngine {
|
|
|
2642
2779
|
streamResult = attemptTimeout
|
|
2643
2780
|
? await Promise.race([processAttempt, attemptTimeout.promise])
|
|
2644
2781
|
: await processAttempt;
|
|
2782
|
+
if (!streamResult.isError) {
|
|
2783
|
+
const protocolDecision = this.turnCoordinator.evaluateCommit(turnLease, this.buildTurnCommitEvidence(session, streamResult));
|
|
2784
|
+
const protocolReason = protocolDecision.ok ? undefined : protocolDecision.reason;
|
|
2785
|
+
const safeProtocolReplay = isSafeProtocolReplayCandidate(streamResult, protocolReason);
|
|
2786
|
+
const protocolRetry = retryScheduler.afterProtocolFailure(safeProtocolReplay ? protocolReason : undefined, safeProtocolReplay);
|
|
2787
|
+
if (protocolRetry.kind === 'protocol_replay') {
|
|
2788
|
+
protocolReplayAttempts = protocolRetry.attempt;
|
|
2789
|
+
agent.cleanupStream(streamKey);
|
|
2790
|
+
logger.warn(`[ResponseEngine] Replaying zero-work protocol failure: session=${session.id} `
|
|
2791
|
+
+ `task=${taskId} reason=${protocolReason} attempt=${protocolRetry.attempt}/${protocolRetry.maxAttempts}`);
|
|
2792
|
+
await this.emitOperationalNotice(renderer, `模型未确认完成,正在立即重试 ${protocolRetry.attempt}/${protocolRetry.maxAttempts}`, 'warn', 'protocol-retry');
|
|
2793
|
+
await renderer.flush();
|
|
2794
|
+
runAttempt++;
|
|
2795
|
+
continue;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2645
2798
|
if (streamResult.isError && !streamHitContextLimit(streamResult)) {
|
|
2646
2799
|
const streamErrorText = getStreamErrorText(streamResult);
|
|
2647
|
-
|
|
2800
|
+
const streamError = streamErrorText ? new Error(streamErrorText) : undefined;
|
|
2801
|
+
if (streamError && (isRetryableError(streamError) || classifyError(streamError) === ErrorType.MODEL_UNAVAILABLE)) {
|
|
2648
2802
|
renderer.discardPending();
|
|
2649
|
-
throw
|
|
2803
|
+
throw streamError;
|
|
2650
2804
|
}
|
|
2651
2805
|
}
|
|
2652
2806
|
// 探测成功(退避期内到达探测点且用的是 evolcore 模型)→ 清零降级状态
|
|
2653
|
-
if (
|
|
2807
|
+
if (probingPrimaryModel && !usedFallback) {
|
|
2654
2808
|
this.modelFallbackMap.delete(session.id);
|
|
2655
2809
|
logger.info(`[ResponseEngine] Model probe succeeded, cleared fallback state for session=${session.id}`);
|
|
2656
2810
|
}
|
|
@@ -2673,47 +2827,67 @@ export class ResponseEngine {
|
|
|
2673
2827
|
permissionError._errorAlreadySent = true;
|
|
2674
2828
|
throw permissionError;
|
|
2675
2829
|
}
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2830
|
+
if (classifyError(retryError) === ErrorType.MODEL_UNAVAILABLE && !hasExplicitTriggerModel) {
|
|
2831
|
+
if (probingPrimaryModel && previousFallbackState) {
|
|
2832
|
+
const resumedFallback = previousFallbackState.activeFallback;
|
|
2833
|
+
retryScheduler.markModelAttempted(resumedFallback.model);
|
|
2834
|
+
modelOverride = withFallbackModel(modelOverride, resumedFallback);
|
|
2835
|
+
usedFallback = resumedFallback;
|
|
2836
|
+
skipEvolcoreModel = true;
|
|
2837
|
+
probingPrimaryModel = false;
|
|
2838
|
+
previousFallbackState.messagesSinceFallback = 0;
|
|
2839
|
+
previousFallbackState.nextProbeAt = Math.min(previousFallbackState.nextProbeAt * 2, 8);
|
|
2840
|
+
this.modelFallbackMap.set(session.id, previousFallbackState);
|
|
2841
|
+
await this.emitOperationalNotice(renderer, `原模型 ${primaryModel ?? '默认模型'} 仍不可用,继续使用备用模型 ${resumedFallback.model}`, 'warn', 'model-probe-fallback');
|
|
2842
|
+
await renderer.flush();
|
|
2843
|
+
runAttempt++;
|
|
2844
|
+
continue;
|
|
2683
2845
|
}
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2846
|
+
const modelFallback = retryScheduler.afterModelUnavailable();
|
|
2847
|
+
if (modelFallback.kind === 'model_fallback') {
|
|
2848
|
+
modelOverride = withFallbackModel(modelOverride, modelFallback.candidate);
|
|
2849
|
+
usedFallback = modelFallback.candidate;
|
|
2850
|
+
skipEvolcoreModel = true;
|
|
2851
|
+
if (primaryModel) {
|
|
2852
|
+
this.modelFallbackMap.set(session.id, {
|
|
2853
|
+
baseagent: normalizedBaseagent.canonical,
|
|
2854
|
+
primaryModel,
|
|
2855
|
+
activeFallback: modelFallback.candidate,
|
|
2856
|
+
messagesSinceFallback: 0,
|
|
2857
|
+
nextProbeAt: 2,
|
|
2858
|
+
hintShown: false,
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
logger.warn(`[ResponseEngine] Model unavailable: switching ${primaryModel ?? '<runner-default>'} to ${modelFallback.candidate.model} (${modelFallback.attempt}/${modelFallback.candidateCount})`);
|
|
2862
|
+
await this.emitOperationalNotice(renderer, `模型 ${primaryModel ?? '默认模型'} 不可用,正在切换备用模型 ${modelFallback.candidate.model}(${modelFallback.attempt}/${modelFallback.candidateCount})`, 'warn', 'model-fallback');
|
|
2863
|
+
await renderer.flush();
|
|
2864
|
+
runAttempt++;
|
|
2865
|
+
continue;
|
|
2866
|
+
}
|
|
2867
|
+
this.modelFallbackMap.delete(session.id);
|
|
2868
|
+
const candidateHint = fallbackCandidates.length === 0
|
|
2869
|
+
? '未配置可用备用模型'
|
|
2870
|
+
: '备用模型均不可用';
|
|
2871
|
+
throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.fallbackModels 配置同类备用模型。`);
|
|
2697
2872
|
}
|
|
2698
2873
|
if (retryAttemptTimedOut || isRetryableError(retryError)) {
|
|
2699
|
-
if (didRetryMakeProgress(retryError)) {
|
|
2700
|
-
consecutiveRetryFailures = 0;
|
|
2701
|
-
}
|
|
2702
2874
|
await recordRetryHealthError(retryError);
|
|
2703
|
-
|
|
2875
|
+
const apiRetry = retryScheduler.afterTransientFailure(didRetryMakeProgress(retryError));
|
|
2876
|
+
if (apiRetry.kind === 'api_retry') {
|
|
2704
2877
|
// 检查中断状态:如果任务已被中断(/stop 或新消息),立即退出重试循环
|
|
2705
2878
|
const interruptReason = this.interruptedSessions.get(session.id);
|
|
2706
2879
|
if (interruptReason) {
|
|
2707
2880
|
logger.info(`[ResponseEngine] Task interrupted during retry wait (reason=${interruptReason}), aborting retry loop session=${session.id}`);
|
|
2708
2881
|
throw new Error('TASK_INTERRUPTED');
|
|
2709
2882
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
consecutiveRetryFailures++;
|
|
2713
|
-
logger.warn(`[ResponseEngine] Retryable error (attempt ${runAttempt}, consecutive ${consecutiveRetryFailures}/${MAX_RETRIES}), retrying in ${delay}ms:`, retryError);
|
|
2714
|
-
await this.emitOperationalNotice(renderer, `API 不可用,${delay / 1000}秒后重试 ${nextRetryNumber}/${MAX_RETRIES}`, 'warn', 'retry');
|
|
2883
|
+
logger.warn(`[ResponseEngine] Retryable error (attempt ${runAttempt}, api retry ${apiRetry.attempt}/${apiRetry.maxAttempts}), retrying in ${apiRetry.delayMs}ms:`, retryError);
|
|
2884
|
+
await this.emitOperationalNotice(renderer, `API 不可用,${apiRetry.delayMs / 1000}秒后重试 ${apiRetry.attempt}/${apiRetry.maxAttempts}`, 'warn', 'retry');
|
|
2715
2885
|
await renderer.flush();
|
|
2716
|
-
|
|
2886
|
+
const retryDelayElapsed = await this.waitForRetryDelay(session.id, apiRetry.delayMs);
|
|
2887
|
+
if (!retryDelayElapsed) {
|
|
2888
|
+
logger.info(`[ResponseEngine] Task interrupted during retry delay session=${session.id}`);
|
|
2889
|
+
throw new Error('TASK_INTERRUPTED');
|
|
2890
|
+
}
|
|
2717
2891
|
// 延迟后再次检查中断状态(延迟期间可能收到中断信号)
|
|
2718
2892
|
const postDelayInterrupt = this.interruptedSessions.get(session.id);
|
|
2719
2893
|
if (postDelayInterrupt) {
|
|
@@ -2723,7 +2897,7 @@ export class ResponseEngine {
|
|
|
2723
2897
|
runAttempt++;
|
|
2724
2898
|
continue;
|
|
2725
2899
|
}
|
|
2726
|
-
markRetryExhausted(retryError,
|
|
2900
|
+
markRetryExhausted(retryError, retryScheduler.apiRetryAttemptsMade);
|
|
2727
2901
|
}
|
|
2728
2902
|
throw retryError; // 不可重试或已耗尽重试次数
|
|
2729
2903
|
}
|
|
@@ -2734,7 +2908,7 @@ export class ResponseEngine {
|
|
|
2734
2908
|
}
|
|
2735
2909
|
catch (error) {
|
|
2736
2910
|
if (classifyError(error) === ErrorType.CONTEXT_TOO_LONG && session.agentSessionId && canCompactAgent(agent)) {
|
|
2737
|
-
streamResult = await this.
|
|
2911
|
+
streamResult = await this.recoverFromContextLimit(streamResult, {
|
|
2738
2912
|
streamKey,
|
|
2739
2913
|
renderer,
|
|
2740
2914
|
agent,
|
|
@@ -2775,7 +2949,7 @@ export class ResponseEngine {
|
|
|
2775
2949
|
const isPromptTooLong = streamResult.isError && !!session.agentSessionId && !!compactAgent
|
|
2776
2950
|
&& streamHitContextLimit(streamResult);
|
|
2777
2951
|
if (isPromptTooLong) {
|
|
2778
|
-
streamResult = await this.
|
|
2952
|
+
streamResult = await this.recoverFromContextLimit(streamResult, {
|
|
2779
2953
|
streamKey,
|
|
2780
2954
|
renderer,
|
|
2781
2955
|
agent,
|
|
@@ -2833,7 +3007,7 @@ export class ResponseEngine {
|
|
|
2833
3007
|
subtype: 'protocol_incomplete',
|
|
2834
3008
|
terminalReason: 'protocol_incomplete',
|
|
2835
3009
|
protocolIncompleteReason: reason,
|
|
2836
|
-
errors: [
|
|
3010
|
+
errors: [formatProtocolReplayExhaustedMessage(reason, protocolReplayAttempts)],
|
|
2837
3011
|
lastReplyText: '',
|
|
2838
3012
|
fullText: '',
|
|
2839
3013
|
hasReceivedText: false,
|
|
@@ -2994,13 +3168,12 @@ export class ResponseEngine {
|
|
|
2994
3168
|
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
2995
3169
|
logger.info(`[ResponseEngine] session ${session.id} processing cleared task=${taskId}`);
|
|
2996
3170
|
// 降级模型回复末尾追加标记(代码层硬注入,不依赖模型输出)
|
|
2997
|
-
|
|
2998
|
-
if (usingFallback && agentModel) {
|
|
3171
|
+
if (usedFallback) {
|
|
2999
3172
|
const curFbState = this.modelFallbackMap.get(session.id);
|
|
3000
3173
|
const showHint = curFbState && curFbState.nextProbeAt >= 8 && !curFbState.hintShown;
|
|
3001
3174
|
const suffix = showHint
|
|
3002
|
-
? `\n\n---\n⚠️ [降级模型: ${
|
|
3003
|
-
: `\n\n---\n⚠️ [降级模型: ${
|
|
3175
|
+
? `\n\n---\n⚠️ [降级模型: ${usedFallback.model} | 可告诉我"帮我检查可用模型"来诊断]`
|
|
3176
|
+
: `\n\n---\n⚠️ [降级模型: ${usedFallback.model}]`;
|
|
3004
3177
|
renderer.addText(suffix);
|
|
3005
3178
|
if (showHint && curFbState) {
|
|
3006
3179
|
curFbState.hintShown = true;
|
|
@@ -3016,7 +3189,8 @@ export class ResponseEngine {
|
|
|
3016
3189
|
// Flush 剩余内容(文件标记已在 flush 时自动移除)
|
|
3017
3190
|
await renderer.flush(true);
|
|
3018
3191
|
}
|
|
3019
|
-
|
|
3192
|
+
const terminalInterrupt = isExpectedTerminalInterrupt(streamResult.terminalReason);
|
|
3193
|
+
if (!streamResult.isError && !terminalInterrupt) {
|
|
3020
3194
|
const commitDecision = await this.turnCoordinator.commit(session, turnLease, this.buildTurnCommitEvidence(session, streamResult));
|
|
3021
3195
|
if (!commitDecision.ok) {
|
|
3022
3196
|
logger.info(`[ResponseEngine] Turn commit rejected after flush: session=${session.id} task=${taskId} reason=${commitDecision.reason}`);
|
|
@@ -3033,13 +3207,14 @@ export class ResponseEngine {
|
|
|
3033
3207
|
}
|
|
3034
3208
|
}
|
|
3035
3209
|
else {
|
|
3036
|
-
await this.turnCoordinator.finish(session, turnLease, 'failed', streamResult.protocolIncompleteReason || streamResult.terminalReason || streamResult.subtype);
|
|
3210
|
+
await this.turnCoordinator.finish(session, turnLease, terminalInterrupt ? 'cancelled' : 'failed', streamResult.protocolIncompleteReason || streamResult.terminalReason || streamResult.subtype);
|
|
3037
3211
|
}
|
|
3038
3212
|
// 注意:不在此处清除 interruptedSessions,由下一条消息的 prompt 包装逻辑消费
|
|
3039
3213
|
const interruptReason = this.interruptedSessions.get(session.id);
|
|
3040
|
-
if (streamResult.isError) {
|
|
3214
|
+
if (streamResult.isError || terminalInterrupt) {
|
|
3041
3215
|
// Agent 流正常结束但任务结果失败(权限被拒、max turns、工具链失败等)
|
|
3042
|
-
const errorSummary = streamResult.errors?.join('; ')
|
|
3216
|
+
const errorSummary = streamResult.errors?.join('; ')
|
|
3217
|
+
|| (terminalInterrupt ? '用户交互未确认,任务已暂停' : '任务执行失败');
|
|
3043
3218
|
const userErrorSummary = streamResult.subtype === 'protocol_incomplete'
|
|
3044
3219
|
? errorSummary
|
|
3045
3220
|
: streamHitContextLimit(streamResult)
|
|
@@ -3050,10 +3225,30 @@ export class ResponseEngine {
|
|
|
3050
3225
|
// 用户主动打断(新消息//stop/撤回)会让 SDK 流在工具调用中途被掐断,
|
|
3051
3226
|
// 末尾 result message 形状异常并被标记为 error(含 SDK 内部 ede_diagnostic 串)。
|
|
3052
3227
|
// 这不是真正的失败,不应把诊断串暴露给用户,也不计入错误统计。
|
|
3053
|
-
const isExpectedInterrupt = isNonErrorInterruptReason(interruptReason)
|
|
3228
|
+
const isExpectedInterrupt = isNonErrorInterruptReason(interruptReason)
|
|
3229
|
+
|| isExpectedTerminalInterrupt(streamResult.terminalReason);
|
|
3054
3230
|
if (!isExpectedInterrupt && !permissionTerminalFailure) {
|
|
3055
|
-
|
|
3056
|
-
|
|
3231
|
+
const terminalMetadata = {
|
|
3232
|
+
errorType: rawSubtype,
|
|
3233
|
+
message: userErrorSummary,
|
|
3234
|
+
durationMs: Date.now() - startTime,
|
|
3235
|
+
numTurns: streamResult.numTurns,
|
|
3236
|
+
toolCallCount: streamResult.toolUseCount ?? 0,
|
|
3237
|
+
tokenUsage: streamResult.tokenUsage,
|
|
3238
|
+
contextUsage: streamResult.contextUsage,
|
|
3239
|
+
contextRecoveryAttempts: streamResult.contextRecoveryAttempts ?? 0,
|
|
3240
|
+
};
|
|
3241
|
+
// DaemonChannel settles a trigger when it receives a terminal
|
|
3242
|
+
// payload. Send the metadata-bearing status first so failed runs do
|
|
3243
|
+
// not lose their tool counts and recovery diagnostics.
|
|
3244
|
+
if (message.source === 'trigger') {
|
|
3245
|
+
await adapter.send(envelope, { kind: 'status.error', metadata: terminalMetadata }).catch(() => { });
|
|
3246
|
+
await adapter.send(envelope, { kind: 'result.error', text: userErrorSummary, reason: rawSubtype }).catch(() => { });
|
|
3247
|
+
}
|
|
3248
|
+
else {
|
|
3249
|
+
await adapter.send(envelope, { kind: 'result.error', text: userErrorSummary, reason: rawSubtype }).catch(() => { });
|
|
3250
|
+
await adapter.send(envelope, { kind: 'status.error', metadata: terminalMetadata }).catch(() => { });
|
|
3251
|
+
}
|
|
3057
3252
|
this.touchAgentActivity(channelKey);
|
|
3058
3253
|
}
|
|
3059
3254
|
if (isExpectedInterrupt) {
|
|
@@ -3061,7 +3256,15 @@ export class ResponseEngine {
|
|
|
3061
3256
|
// 这里不再补发 task:error(否则同一次打断被记两遍且错误归类为 error)。
|
|
3062
3257
|
// 仅记 info 日志收尾。注意:task:interrupted 已填充 interruptedSessions,
|
|
3063
3258
|
// stats 侧已据此收尾任务生命周期,无需在此重复发事件。
|
|
3064
|
-
|
|
3259
|
+
const interactionCancelled = isExpectedTerminalInterrupt(streamResult.terminalReason)
|
|
3260
|
+
&& !isNonErrorInterruptReason(interruptReason);
|
|
3261
|
+
if (interactionCancelled) {
|
|
3262
|
+
await adapter.send(envelope, {
|
|
3263
|
+
kind: 'status.interrupted',
|
|
3264
|
+
metadata: { reason: 'interaction_cancelled' },
|
|
3265
|
+
}).catch(() => { });
|
|
3266
|
+
}
|
|
3267
|
+
logger.info(`[${message.channel}] Stream result error suppressed (${interactionCancelled ? 'interaction cancellation' : `user interrupt: ${interruptReason}`}): ${errorSummary}`);
|
|
3065
3268
|
logger.message({
|
|
3066
3269
|
msgId: messageId,
|
|
3067
3270
|
sessionId: session.id,
|
|
@@ -3070,7 +3273,7 @@ export class ResponseEngine {
|
|
|
3070
3273
|
error: errorSummary,
|
|
3071
3274
|
terminalReason: streamResult.terminalReason
|
|
3072
3275
|
});
|
|
3073
|
-
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, interruptReason ?? 'interrupted', taskCausation);
|
|
3276
|
+
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, interactionCancelled ? 'interaction_cancelled' : (interruptReason ?? 'interrupted'), taskCausation);
|
|
3074
3277
|
}
|
|
3075
3278
|
else {
|
|
3076
3279
|
logger.error(`[ResponseEngine] Task failed: session=${session.id} task=${taskId} ` +
|
|
@@ -3439,6 +3642,7 @@ export class ResponseEngine {
|
|
|
3439
3642
|
// 用户主动中断(新消息打断 或 /stop 命令)时静默,不发送错误提示
|
|
3440
3643
|
// processEventStream 已通过 renderer 发过错误时也跳过
|
|
3441
3644
|
const retryExhaustedCount = getRetryExhaustedCount(error);
|
|
3645
|
+
const modelFallbackExhaustedMessage = getModelFallbackExhaustedMessage(error);
|
|
3442
3646
|
if (isUserInterrupt) {
|
|
3443
3647
|
logger.info(`[ResponseEngine] User interrupt by new_message, skip sending error message`);
|
|
3444
3648
|
}
|
|
@@ -3450,11 +3654,13 @@ export class ResponseEngine {
|
|
|
3450
3654
|
const idleSec = getLastIdleSec?.() || 0;
|
|
3451
3655
|
const userMessage = retryExhaustedCount
|
|
3452
3656
|
? formatRetryableErrorFinalMessage(error, retryExhaustedCount)
|
|
3453
|
-
:
|
|
3454
|
-
?
|
|
3455
|
-
:
|
|
3456
|
-
?
|
|
3457
|
-
:
|
|
3657
|
+
: modelFallbackExhaustedMessage
|
|
3658
|
+
? modelFallbackExhaustedMessage
|
|
3659
|
+
: isTotalExecutionTimeout
|
|
3660
|
+
? `⚠️ 任务超过总执行时限(${Math.round(totalExecutionMs / 1000)}秒),已自动中断`
|
|
3661
|
+
: isTimeout
|
|
3662
|
+
? (idleSec > 0 ? `⚠️ 任务超时(${idleSec}秒无响应),已自动中断` : '⚠️ 任务超时,已自动中断')
|
|
3663
|
+
: getErrorMessage(error, undefined);
|
|
3458
3664
|
// 获取 session 用于话题回复(如果 resolveSession 已执行)
|
|
3459
3665
|
let sendOpts;
|
|
3460
3666
|
try {
|
|
@@ -3471,10 +3677,32 @@ export class ResponseEngine {
|
|
|
3471
3677
|
const errorPayload = {
|
|
3472
3678
|
kind: 'result.error',
|
|
3473
3679
|
text: userMessage,
|
|
3474
|
-
reason: retryExhaustedCount ? 'retry_exhausted' : isTotalExecutionTimeout ? 'total_execution_timeout' : isTimeout ? 'timeout' : errType,
|
|
3680
|
+
reason: retryExhaustedCount ? 'retry_exhausted' : modelFallbackExhaustedMessage ? 'model_unavailable' : isTotalExecutionTimeout ? 'total_execution_timeout' : isTimeout ? 'timeout' : errType,
|
|
3475
3681
|
};
|
|
3476
|
-
|
|
3477
|
-
|
|
3682
|
+
if (!isUserInterrupt && daemonTrigger && procStatus === 'error') {
|
|
3683
|
+
// DaemonChannel settles a trigger on the first terminal payload. Put
|
|
3684
|
+
// the complete failure metadata first so the audit retains tool
|
|
3685
|
+
// counts and context-recovery diagnostics even for thrown errors
|
|
3686
|
+
// (for example when compact itself fails).
|
|
3687
|
+
await adapter.send({ ...envelope, replyContext: sendOpts }, {
|
|
3688
|
+
kind: 'status.error',
|
|
3689
|
+
metadata: {
|
|
3690
|
+
errorType: errType,
|
|
3691
|
+
message: userMessage,
|
|
3692
|
+
durationMs: Date.now() - startTime,
|
|
3693
|
+
numTurns: streamResult.numTurns,
|
|
3694
|
+
toolCallCount: streamResult.toolUseCount ?? 0,
|
|
3695
|
+
tokenUsage: streamResult.tokenUsage,
|
|
3696
|
+
contextUsage: streamResult.contextUsage,
|
|
3697
|
+
contextRecoveryAttempts: streamResult.contextRecoveryAttempts ?? 0,
|
|
3698
|
+
},
|
|
3699
|
+
}).catch(() => { });
|
|
3700
|
+
await adapter.send({ ...envelope, replyContext: sendOpts }, errorPayload);
|
|
3701
|
+
}
|
|
3702
|
+
else {
|
|
3703
|
+
await adapter.send({ ...envelope, replyContext: sendOpts }, errorPayload);
|
|
3704
|
+
}
|
|
3705
|
+
if (!isUserInterrupt && daemonTrigger && procStatus !== 'error') {
|
|
3478
3706
|
await adapter.send({ ...envelope, replyContext: sendOpts }, statusPayload).catch(() => { });
|
|
3479
3707
|
}
|
|
3480
3708
|
// Proactive 可观测:catch 块的基础设施错误也透传为 thought,保证按 task_id 聚合完整
|
|
@@ -3574,9 +3802,14 @@ export class ResponseEngine {
|
|
|
3574
3802
|
return undefined;
|
|
3575
3803
|
const recordedAutoCompactTokens = row.auto_compact_tokens ?? undefined;
|
|
3576
3804
|
const inferredAutoCompactTokens = autoCompactTokensFromMaxTokens(row.max_tokens ?? undefined);
|
|
3577
|
-
const
|
|
3805
|
+
const candidateAutoCompactTokens = recordedAutoCompactTokens && recordedAutoCompactTokens > 0
|
|
3578
3806
|
? recordedAutoCompactTokens
|
|
3579
3807
|
: inferredAutoCompactTokens ?? autoCompactWindowForModel(model);
|
|
3808
|
+
// Historic rows may contain the old fixed 350k threshold. Never let a
|
|
3809
|
+
// persisted value exceed the context window that accompanied it.
|
|
3810
|
+
const autoCompactTokens = inferredAutoCompactTokens
|
|
3811
|
+
? Math.min(candidateAutoCompactTokens, inferredAutoCompactTokens)
|
|
3812
|
+
: candidateAutoCompactTokens;
|
|
3580
3813
|
return { totalTokens, autoCompactTokens };
|
|
3581
3814
|
}
|
|
3582
3815
|
finally {
|
|
@@ -3693,6 +3926,7 @@ export class ResponseEngine {
|
|
|
3693
3926
|
resultText: result.lastReplyText || result.fullText,
|
|
3694
3927
|
inputTokens: result.tokenUsage?.input_tokens ?? 0,
|
|
3695
3928
|
outputTokens: result.tokenUsage?.output_tokens ?? 0,
|
|
3929
|
+
hasTokenUsage: hasRecordedTokenUsage(result),
|
|
3696
3930
|
};
|
|
3697
3931
|
}
|
|
3698
3932
|
/**
|
|
@@ -4106,7 +4340,8 @@ export class ResponseEngine {
|
|
|
4106
4340
|
// 上下文过长的错误不在此处输出 notice,留给外层 isPromptTooLong 触发 auto-compact
|
|
4107
4341
|
const isContextError = isContextTooLongText(event.error || '');
|
|
4108
4342
|
const isRetryableRuntimeError = isRetryableError(new Error(event.error || ''));
|
|
4109
|
-
|
|
4343
|
+
const isRecoverableModelError = classifyError(new Error(event.error || '')) === ErrorType.MODEL_UNAVAILABLE;
|
|
4344
|
+
if (!isContextError && !isRetryableRuntimeError && !isRecoverableModelError && !hasErrorResult && !shouldSuppress()) {
|
|
4110
4345
|
hasErrorResult = true;
|
|
4111
4346
|
renderer.addNotice(getRuntimeErrorMessage(event.error || '任务执行失败', false), 'warn', 'runtime-error', true);
|
|
4112
4347
|
}
|
|
@@ -4115,7 +4350,7 @@ export class ResponseEngine {
|
|
|
4115
4350
|
// SDK 可能产生多个 complete 事件(如 subagent 或 auto-compact 二次查询),
|
|
4116
4351
|
// 仅记录状态,最终 flush(true) 在流结束后统一执行
|
|
4117
4352
|
if (event.type === 'complete') {
|
|
4118
|
-
const isAbort = event.terminalReason
|
|
4353
|
+
const isAbort = isExpectedTerminalInterrupt(event.terminalReason);
|
|
4119
4354
|
logger.info(`[ResponseEngine] ${isAbort ? 'task interrupted' : 'complete event'}: queryFinal=${event.queryFinal !== false} isError=${event.isError} terminalReason=${event.terminalReason ?? 'none'} subtype=${event.subtype ?? 'none'} hasReceivedText=${hasReceivedText}`);
|
|
4120
4355
|
// 自动回填会话名称
|
|
4121
4356
|
if (event.sessionTitle && shouldAutoFillSessionTitle(session.name, session.threadId)) {
|
|
@@ -4132,7 +4367,8 @@ export class ResponseEngine {
|
|
|
4132
4367
|
// 但用户主动中断(新消息打断 或 /stop 命令)时不显示错误提示
|
|
4133
4368
|
// 上下文过长的错误留给外层 isPromptTooLong 触发 auto-compact,不在此处输出
|
|
4134
4369
|
const interruptReason = this.interruptedSessions.get(session.id);
|
|
4135
|
-
const isUserInterrupt = isNonErrorInterruptReason(interruptReason)
|
|
4370
|
+
const isUserInterrupt = isNonErrorInterruptReason(interruptReason)
|
|
4371
|
+
|| isExpectedTerminalInterrupt(event.terminalReason);
|
|
4136
4372
|
const isContextTooLong = event.terminalReason === 'prompt_too_long'
|
|
4137
4373
|
|| isContextTooLongText(event.errors?.join(' ') || '')
|
|
4138
4374
|
|| isContextTooLongText(lastReplyText);
|
|
@@ -4140,10 +4376,13 @@ export class ResponseEngine {
|
|
|
4140
4376
|
const isRetryableCompleteError = event.isError && !isContextTooLong && completeErrorText
|
|
4141
4377
|
? isRetryableError(new Error(completeErrorText))
|
|
4142
4378
|
: false;
|
|
4379
|
+
const isRecoverableModelError = event.isError && !isContextTooLong && completeErrorText
|
|
4380
|
+
? classifyError(new Error(completeErrorText)) === ErrorType.MODEL_UNAVAILABLE
|
|
4381
|
+
: false;
|
|
4143
4382
|
if (event.isError && completeErrorText && isPendingTextSameAsStreamError(renderer.getRemainingText(), completeErrorText)) {
|
|
4144
4383
|
renderer.discardPendingText();
|
|
4145
4384
|
}
|
|
4146
|
-
if (event.isError && !hasErrorResult && !shouldSuppress() && !isUserInterrupt && !isContextTooLong && !isRetryableCompleteError) {
|
|
4385
|
+
if (event.isError && !hasErrorResult && !shouldSuppress() && !isUserInterrupt && !isContextTooLong && !isRetryableCompleteError && !isRecoverableModelError) {
|
|
4147
4386
|
// 使用 terminalReason 提供更友好的错误提示(不带 emoji,由 formatter 统一加)
|
|
4148
4387
|
const userFriendlyMessage = event.terminalReason === 'prompt_too_long'
|
|
4149
4388
|
? getContextTooLongHint(agent)
|