evolcore 0.0.21 → 0.0.23
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/AGENTS.md +62 -0
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/bin/codex-managed-acl.ps1 +155 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +376 -96
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +1 -1
- package/dist/agents/claude-runner.js +44 -226
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +154 -0
- package/dist/agents/codex-runner.js +1292 -219
- package/dist/agents/ecagent-runner.js +10 -31
- package/dist/agents/gemini-runner.js +2 -6
- package/dist/aun/aid/store.js +2 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +51 -4
- package/dist/aun/outbox.js +7 -0
- package/dist/aun/rpc/client.js +8 -1
- package/dist/channels/aun.js +240 -36
- package/dist/channels/wechat.js +81 -10
- package/dist/cli/aun-commands.js +48 -4
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/daemon-commands.js +169 -51
- package/dist/cli/index.js +20 -0
- package/dist/cli/init.js +2 -10
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/task-context.js +16 -2
- package/dist/cli/trigger-command.js +22 -5
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +15 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +70 -6
- package/dist/core/auth/operation-catalog.js +4 -0
- package/dist/core/auth/remote-access-store.js +135 -0
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +62 -5
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +150 -33
- package/dist/core/command/menu-protocol.js +120 -1
- package/dist/core/command/slash-gate.js +3 -3
- package/dist/core/command/slash-handler.js +83 -40
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +41 -15
- package/dist/core/message/response-engine.js +246 -114
- package/dist/core/model/catalog-service.js +308 -0
- package/dist/core/model/model-catalog.js +124 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +1 -1
- package/dist/core/permission/approval-gateway.js +16 -1
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +74 -14
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +290 -30
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +104 -34
- package/dist/ipc.js +218 -96
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +86 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +9 -4
- package/dist/utils/npm-ops.js +47 -15
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +159 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +256 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +10 -2
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +11 -3
|
@@ -37,7 +37,7 @@ import { resolveAgentLifecycle } from '../../config/lifecycle.js';
|
|
|
37
37
|
import { authorizationConfigRevision, checkRoleAccess, getFirstStaticAgentOwner, listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, roleToSessionIdentity, } from '../../config/peer-role-resolver.js';
|
|
38
38
|
import { insertUsageEvent, insertContextBreakdown, insertModelCalls } from '../../stats/writer.js';
|
|
39
39
|
import { normalizeUsage } from '../../stats/normalizer.js';
|
|
40
|
-
import { resolvePrices } from '../../stats/price-resolver.js';
|
|
40
|
+
import { resolvePrices, roundCostForOutput } from '../../stats/price-resolver.js';
|
|
41
41
|
import { getBudgetStatus } from '../../stats/budget.js';
|
|
42
42
|
import { formatUsageSubjectKey, getRoleBudgetStatus } from '../../stats/role-budget.js';
|
|
43
43
|
import { snapshot } from './response-snapshot.js';
|
|
@@ -47,7 +47,7 @@ import { registerBuiltinModes } from '../../response-system/modes/index.js';
|
|
|
47
47
|
import { deriveSessionTitle, shouldAutoFillSessionTitle } from '../session/session-title.js';
|
|
48
48
|
import { SessionTurnCoordinator } from '../session/session-turn-coordinator.js';
|
|
49
49
|
import { recordTriggerExecutionAnomaly } from '../../trigger/anomaly-store.js';
|
|
50
|
-
import { classifyToolErrorCode } from '../permission/tool-error-code.js';
|
|
50
|
+
import { classifyToolErrorCode, normalizeToolErrorCode } from '../permission/tool-error-code.js';
|
|
51
51
|
import { normalizeExecutionPermissionMode } from '../permission/mode.js';
|
|
52
52
|
import { buildToolLifecycleEventKey } from '../audit/event-key.js';
|
|
53
53
|
import { isFullAccessEnabled, loadDaemonConfig } from '../../config-store.js';
|
|
@@ -325,44 +325,71 @@ function withFallbackModel(override, candidate) {
|
|
|
325
325
|
...(candidate.effort ? { effort: candidate.effort } : { effortMode: 'model_default' }),
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Keep the per-task fullaccess fact in the user-message prompt rather than in
|
|
330
|
+
* the long-lived system/developer prompt. This avoids changing a runner's
|
|
331
|
+
* system-prompt cache while still giving the model an explicit, auditable
|
|
332
|
+
* execution-mode signal for the privileged task.
|
|
333
|
+
*/
|
|
334
|
+
const FULL_ACCESS_MESSAGE_PROMPT_CONTEXT = '【当前权限模式提升为fullaccess,可不受限制执行命令,仅当前轮 task 有效】';
|
|
335
|
+
/**
|
|
336
|
+
* Put the one-task permission notice between the rendered message envelope and
|
|
337
|
+
* the user's literal input. The normal message fragment ends its envelope in
|
|
338
|
+
* `›`; matching the dated normal-message form avoids placing the notice inside
|
|
339
|
+
* an owner-hint envelope when one is present before the real inbound message.
|
|
340
|
+
*/
|
|
341
|
+
function insertMessagePromptContext(prompt, context, userContent) {
|
|
342
|
+
if (!context)
|
|
343
|
+
return prompt;
|
|
344
|
+
const headers = [...prompt.matchAll(/‹\d{4}-\d{2}-\d{2} [^\n]*· from:[^\n]*›/gu)];
|
|
345
|
+
const renderedHeader = headers[0];
|
|
346
|
+
const headerEnd = renderedHeader
|
|
347
|
+
? (renderedHeader.index ?? 0) + renderedHeader[0].length
|
|
348
|
+
: -1;
|
|
349
|
+
if (headerEnd >= 0) {
|
|
350
|
+
const remainder = prompt.slice(headerEnd).replace(/^\n+/u, '');
|
|
351
|
+
return `${prompt.slice(0, headerEnd)}\n${context}${remainder ? `\n${remainder}` : ''}`;
|
|
352
|
+
}
|
|
353
|
+
if (userContent?.trim()) {
|
|
354
|
+
const contentStart = prompt.indexOf(userContent);
|
|
355
|
+
if (contentStart >= 0) {
|
|
356
|
+
return `${prompt.slice(0, contentStart)}${context}\n${prompt.slice(contentStart)}`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Internal recovery prompts have no message envelope; keep their context at
|
|
360
|
+
// the front so the runner still sees it for the same task.
|
|
361
|
+
return `${context}\n\n${prompt}`;
|
|
362
|
+
}
|
|
328
363
|
function getStreamErrorText(result) {
|
|
329
|
-
|
|
364
|
+
const parts = [
|
|
330
365
|
result.errors?.join('\n'),
|
|
331
|
-
result.lastReplyText,
|
|
332
|
-
result.fullText,
|
|
333
366
|
result.subtype,
|
|
334
367
|
result.terminalReason,
|
|
335
368
|
]
|
|
336
|
-
.filter((part) => typeof part === 'string' && part.trim().length > 0)
|
|
337
|
-
|
|
369
|
+
.filter((part) => typeof part === 'string' && part.trim().length > 0);
|
|
370
|
+
// Some older runners put the terminal diagnostic in `result`, but never
|
|
371
|
+
// treat streamed assistant text as an error merely because the turn failed.
|
|
372
|
+
const fullText = result.fullText.trim();
|
|
373
|
+
const fullTextLooksLikeDiagnostic = fullText.length > 0 && (isRetryableError(new Error(fullText))
|
|
374
|
+
|| isContextTooLongText(fullText));
|
|
375
|
+
if (result.isError && !result.hasReceivedText && fullText
|
|
376
|
+
&& (parts.length === 0 || fullTextLooksLikeDiagnostic)) {
|
|
377
|
+
parts.unshift(fullText);
|
|
378
|
+
}
|
|
379
|
+
return parts.join('\n');
|
|
338
380
|
}
|
|
339
381
|
function getStreamErrorMessage(result, includeEmoji = false) {
|
|
340
382
|
const raw = getStreamErrorText(result) || '任务执行失败';
|
|
341
383
|
const mapped = getErrorMessage(new Error(raw), result.terminalReason, includeEmoji);
|
|
342
384
|
const generic = includeEmoji ? '❌ 处理消息时出错,请稍后重试' : '处理消息时出错,请稍后重试';
|
|
343
|
-
|
|
385
|
+
// Unknown runner-specific terminal reasons do not have a friendly mapping.
|
|
386
|
+
// Preserve their diagnostic text instead of collapsing it to the generic
|
|
387
|
+
// "处理消息时出错" message.
|
|
388
|
+
if (raw && mapped === generic) {
|
|
344
389
|
return includeEmoji ? `❌ ${raw}` : raw;
|
|
345
390
|
}
|
|
346
391
|
return mapped;
|
|
347
392
|
}
|
|
348
|
-
function getRuntimeErrorMessage(raw, includeEmoji = false) {
|
|
349
|
-
const fallback = raw || '任务执行失败';
|
|
350
|
-
const mapped = getErrorMessage(new Error(fallback), undefined, includeEmoji);
|
|
351
|
-
const generic = includeEmoji ? '❌ 处理消息时出错,请稍后重试' : '处理消息时出错,请稍后重试';
|
|
352
|
-
if (mapped === generic)
|
|
353
|
-
return includeEmoji ? `❌ ${fallback}` : fallback;
|
|
354
|
-
return mapped;
|
|
355
|
-
}
|
|
356
|
-
function normalizeComparableText(text) {
|
|
357
|
-
return text.replace(/\s+/g, ' ').trim();
|
|
358
|
-
}
|
|
359
|
-
function isPendingTextSameAsStreamError(pendingText, errorText) {
|
|
360
|
-
const pending = normalizeComparableText(pendingText);
|
|
361
|
-
const error = normalizeComparableText(errorText);
|
|
362
|
-
if (!pending || !error || pending.length < 20)
|
|
363
|
-
return false;
|
|
364
|
-
return pending === error || pending.includes(error) || error.includes(pending);
|
|
365
|
-
}
|
|
366
393
|
function streamHitContextLimit(result) {
|
|
367
394
|
return result.terminalReason === 'prompt_too_long' ||
|
|
368
395
|
isContextTooLongText(result.lastReplyText) ||
|
|
@@ -446,12 +473,6 @@ function getContextTooLongHint(agent) {
|
|
|
446
473
|
}
|
|
447
474
|
return '上下文过长,请精简提问,或使用 /new 新建会话后继续';
|
|
448
475
|
}
|
|
449
|
-
function getContextCompactFailedHint(agent) {
|
|
450
|
-
if (canCompactAgent(agent)) {
|
|
451
|
-
return '上下文过长,自动压缩失败,请手动输入 /compact 重试';
|
|
452
|
-
}
|
|
453
|
-
return '上下文过长,请精简提问,或使用 /new 新建会话后继续';
|
|
454
|
-
}
|
|
455
476
|
function canCompactAgent(agent) {
|
|
456
477
|
return hasCompact(agent) && agent.capabilities?.compact !== false;
|
|
457
478
|
}
|
|
@@ -637,20 +658,44 @@ export class ResponseEngine {
|
|
|
637
658
|
// 监听中断事件,标记被中断的 session
|
|
638
659
|
this.eventBus.subscribe('task:interrupted', (event) => {
|
|
639
660
|
if ('sessionId' in event && event.sessionId) {
|
|
661
|
+
const eventIdentity = event;
|
|
662
|
+
const eventTaskId = eventIdentity.taskId;
|
|
663
|
+
const activeTask = this.activeTaskSpans.get(event.sessionId);
|
|
664
|
+
// A delayed interruption from an older task must never invalidate a
|
|
665
|
+
// newer task that has already claimed the same session.
|
|
666
|
+
if (eventTaskId && activeTask && activeTask.taskId !== eventTaskId)
|
|
667
|
+
return;
|
|
640
668
|
const reason = (event.reason || 'new_message');
|
|
641
|
-
this.turnCoordinator.invalidate(event.sessionId, reason);
|
|
669
|
+
const currentTurn = this.turnCoordinator.invalidate(event.sessionId, reason, { taskId: eventTaskId, generation: eventIdentity.generation });
|
|
670
|
+
if ((eventTaskId || eventIdentity.generation !== undefined) && !currentTurn)
|
|
671
|
+
return;
|
|
642
672
|
this.interruptedSessions.set(event.sessionId, reason);
|
|
643
673
|
this.cancelRetryDelays(event.sessionId);
|
|
644
|
-
|
|
674
|
+
// The event can be delayed relative to a newer task on the same
|
|
675
|
+
// session. Revoke only when the producer supplied the matching task;
|
|
676
|
+
// the interrupt path below performs the authoritative task-scoped
|
|
677
|
+
// revocation for legacy events without a taskId.
|
|
678
|
+
const interruptedTaskId = eventTaskId;
|
|
679
|
+
if (interruptedTaskId && activeTask?.taskId === interruptedTaskId) {
|
|
680
|
+
this.agentDelegationRegistry?.revokeTask(event.sessionId, interruptedTaskId);
|
|
681
|
+
}
|
|
645
682
|
}
|
|
646
683
|
});
|
|
647
684
|
this.eventBus.subscribe('task:completed', event => {
|
|
648
|
-
if ('sessionId' in event && event.sessionId)
|
|
649
|
-
|
|
685
|
+
if ('sessionId' in event && event.sessionId) {
|
|
686
|
+
const taskId = event.taskId;
|
|
687
|
+
const active = this.activeTaskSpans.get(event.sessionId);
|
|
688
|
+
if (!taskId || active?.taskId === taskId)
|
|
689
|
+
this.activeTaskSpans.delete(event.sessionId);
|
|
690
|
+
}
|
|
650
691
|
});
|
|
651
692
|
this.eventBus.subscribe('task:error', event => {
|
|
652
|
-
if ('sessionId' in event && event.sessionId)
|
|
653
|
-
|
|
693
|
+
if ('sessionId' in event && event.sessionId) {
|
|
694
|
+
const taskId = event.taskId;
|
|
695
|
+
const active = this.activeTaskSpans.get(event.sessionId);
|
|
696
|
+
if (!taskId || active?.taskId === taskId)
|
|
697
|
+
this.activeTaskSpans.delete(event.sessionId);
|
|
698
|
+
}
|
|
654
699
|
});
|
|
655
700
|
// 初始化响应模式协调器,注册内置模式(interactive/proactive)
|
|
656
701
|
const registry = new ResponseModeRegistry();
|
|
@@ -1018,7 +1063,7 @@ export class ResponseEngine {
|
|
|
1018
1063
|
return true;
|
|
1019
1064
|
}
|
|
1020
1065
|
async retryAfterContextRecovery(prompt, opts) {
|
|
1021
|
-
const { streamKey, renderer, agent, session, absoluteProjectPath, effectiveSystemPrompt, modelOverride, runtimeEnv, resetTimer, shouldSuppress, proactive, turnLease, beforeRunQuery, } = opts;
|
|
1066
|
+
const { streamKey, renderer, agent, session, absoluteProjectPath, effectiveSystemPrompt, modelOverride, runtimeEnv, resetTimer, shouldSuppress, proactive, turnLease, beforeRunQuery, messagePromptContext, } = opts;
|
|
1022
1067
|
if (!session.agentSessionId || !canCompactAgent(agent)) {
|
|
1023
1068
|
throw new Error('CONTEXT_COMPACT_FAILED');
|
|
1024
1069
|
}
|
|
@@ -1057,7 +1102,7 @@ export class ResponseEngine {
|
|
|
1057
1102
|
await this.emitOperationalNotice(renderer, '✅ 压缩完成,继续处理...', 'info', 'compact-retry');
|
|
1058
1103
|
await renderer.flush();
|
|
1059
1104
|
beforeRunQuery?.();
|
|
1060
|
-
const retryStream = await agent.runQuery(session.id, prompt, absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager, recoveryModelOverride, runtimeEnv);
|
|
1105
|
+
const retryStream = await agent.runQuery(session.id, insertMessagePromptContext(prompt, messagePromptContext), absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager, recoveryModelOverride, runtimeEnv);
|
|
1061
1106
|
if (await this.cancelStaleRunnerStart(agent, session.id, streamKey, turnLease, turnLease.taskId)) {
|
|
1062
1107
|
throw new Error('RUNNER_START_SUPERSEDED');
|
|
1063
1108
|
}
|
|
@@ -1274,7 +1319,7 @@ export class ResponseEngine {
|
|
|
1274
1319
|
causation: opts.causation ?? message.causation,
|
|
1275
1320
|
});
|
|
1276
1321
|
}
|
|
1277
|
-
publishTriggerExecutionSkipped(message, reason, causation) {
|
|
1322
|
+
publishTriggerExecutionSkipped(message, reason, causation, interruption) {
|
|
1278
1323
|
const terminal = this.claimTriggerTerminal(message);
|
|
1279
1324
|
if (!terminal)
|
|
1280
1325
|
return;
|
|
@@ -1287,6 +1332,7 @@ export class ResponseEngine {
|
|
|
1287
1332
|
attemptId: trigger.attemptId,
|
|
1288
1333
|
originTriggerId: trigger.triggerId,
|
|
1289
1334
|
reason,
|
|
1335
|
+
...(interruption ?? {}),
|
|
1290
1336
|
targetChannel: message.channel,
|
|
1291
1337
|
targetChannelId: message.channelId,
|
|
1292
1338
|
fireTime: trigger.fireTime,
|
|
@@ -1294,16 +1340,25 @@ export class ResponseEngine {
|
|
|
1294
1340
|
});
|
|
1295
1341
|
}
|
|
1296
1342
|
/** Close the internal daemon conversation before returning from an interrupted Trigger turn. */
|
|
1297
|
-
async publishTriggerExecutionInterrupted(message, adapter, envelope, reason, causation) {
|
|
1343
|
+
async publishTriggerExecutionInterrupted(message, adapter, envelope, reason, causation, generation) {
|
|
1344
|
+
const interruption = reason === 'stale_generation'
|
|
1345
|
+
? {
|
|
1346
|
+
reasonCode: 'stale_generation',
|
|
1347
|
+
decisionSource: 'infrastructure',
|
|
1348
|
+
executionState: 'interrupted',
|
|
1349
|
+
...(generation?.expected !== undefined ? { generation: generation.expected } : {}),
|
|
1350
|
+
...(generation?.current !== undefined ? { currentGeneration: generation.current } : {}),
|
|
1351
|
+
}
|
|
1352
|
+
: undefined;
|
|
1298
1353
|
if (this.isTrustedDaemonTrigger(message)) {
|
|
1299
1354
|
await adapter.send(envelope, {
|
|
1300
1355
|
kind: 'status.interrupted',
|
|
1301
|
-
metadata: { reason },
|
|
1356
|
+
metadata: { reason, ...(interruption ?? {}) },
|
|
1302
1357
|
}).catch(error => {
|
|
1303
1358
|
logger.warn(`[ResponseEngine] Failed to close interrupted Trigger run=${message.triggerMeta?.runId ?? '<unknown>'}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1304
1359
|
});
|
|
1305
1360
|
}
|
|
1306
|
-
this.publishTriggerExecutionSkipped(message, reason, causation);
|
|
1361
|
+
this.publishTriggerExecutionSkipped(message, reason, causation, interruption);
|
|
1307
1362
|
}
|
|
1308
1363
|
publishTriggerExecutionCompleted(message, messageId, durationMs, causation) {
|
|
1309
1364
|
const terminal = this.claimTriggerTerminal(message);
|
|
@@ -1331,11 +1386,11 @@ export class ResponseEngine {
|
|
|
1331
1386
|
'/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork',
|
|
1332
1387
|
'/stop', '/pause', '/resume', '/clear', '/compact', '/renew', '/del', '/perm', '/file', '/check',
|
|
1333
1388
|
'/s ', '/name ', '/rewind', '/rw', '/rw ', '/activity', '/observable', '/chatmode',
|
|
1334
|
-
'/aid', '/upgrade', '/evolagent',
|
|
1389
|
+
'/aid', '/upgrade', '/evolagent', '/pair ',
|
|
1335
1390
|
];
|
|
1336
1391
|
/** 判断消息内容是否为已知命令 */
|
|
1337
1392
|
isKnownCommand(content) {
|
|
1338
|
-
return content === '/s' ||
|
|
1393
|
+
return content === '/s' || content === '/pair' ||
|
|
1339
1394
|
ResponseEngine.COMMAND_PREFIXES.some(cmd => content.startsWith(cmd));
|
|
1340
1395
|
}
|
|
1341
1396
|
/**
|
|
@@ -2130,6 +2185,8 @@ export class ResponseEngine {
|
|
|
2130
2185
|
this.eventBus.publish({
|
|
2131
2186
|
type: 'task:error',
|
|
2132
2187
|
sessionId: session.id,
|
|
2188
|
+
taskId,
|
|
2189
|
+
generation: turnLease?.generation,
|
|
2133
2190
|
error: timeoutError.message,
|
|
2134
2191
|
errorType,
|
|
2135
2192
|
agentName: agentNameForStats,
|
|
@@ -2255,7 +2312,10 @@ export class ResponseEngine {
|
|
|
2255
2312
|
const contentPreview = formatInboundMessageLogText(message.content);
|
|
2256
2313
|
logger.info(`[${message.channel}] ${message.channelId}: ${contentPreview}${imageInfo}${modeInfo}${e2eeInfo}`);
|
|
2257
2314
|
// 构建 peer 标识(优先 peerName,退化到 peerId / channelId)
|
|
2258
|
-
|
|
2315
|
+
// Group sessions are shared by multiple senders. The current inbound
|
|
2316
|
+
// message is authoritative; session metadata is only a fallback for
|
|
2317
|
+
// legacy paths that do not carry a per-message display name.
|
|
2318
|
+
const peerName = message.peerName ?? session.metadata?.peerName;
|
|
2259
2319
|
const peerId = session.metadata?.peerId ?? message.peerId ?? message.channelId;
|
|
2260
2320
|
const peerShort = peerId ? peerId.split('.')[0].split(':')[0] : '?';
|
|
2261
2321
|
const peerLabel = peerName && peerName !== peerShort ? `${peerShort}(${peerName})` : peerShort;
|
|
@@ -2266,7 +2326,16 @@ export class ResponseEngine {
|
|
|
2266
2326
|
turnLease = await this.turnCoordinator.begin(session, taskId);
|
|
2267
2327
|
// 记录开始处理
|
|
2268
2328
|
const taskEncrypt = message.replyContext?.metadata?.encrypted != null ? !!(message.replyContext.metadata.encrypted) : undefined;
|
|
2269
|
-
this.eventBus.publish({
|
|
2329
|
+
this.eventBus.publish({
|
|
2330
|
+
type: 'task:started',
|
|
2331
|
+
sessionId: session.id,
|
|
2332
|
+
taskId,
|
|
2333
|
+
generation: turnLease.generation,
|
|
2334
|
+
agentName: agentNameForStats,
|
|
2335
|
+
encrypt: taskEncrypt,
|
|
2336
|
+
chatmode,
|
|
2337
|
+
causation: taskCausation,
|
|
2338
|
+
});
|
|
2270
2339
|
this.touchAgentActivity(channelKey);
|
|
2271
2340
|
// Upgrade the channel acknowledgement at task start, before compaction and
|
|
2272
2341
|
// runner setup, so processing feedback remains visible for the whole run.
|
|
@@ -2292,6 +2361,7 @@ export class ResponseEngine {
|
|
|
2292
2361
|
adapter,
|
|
2293
2362
|
envelope,
|
|
2294
2363
|
agentAid: session.selfAID,
|
|
2364
|
+
permissionMode: () => effectivePermissionMode,
|
|
2295
2365
|
flushDelay: (options?.flushDelay ?? this.agentRegistry?.resolveByChannel(channelKey)?.config?.flush_delay ?? 3) * 1000,
|
|
2296
2366
|
suppressActivityItems: isProactive ? false : middleOutputMode !== 'all',
|
|
2297
2367
|
suppressIntermediateText: isProactive ? false : middleOutputMode === 'none',
|
|
@@ -2640,6 +2710,7 @@ export class ResponseEngine {
|
|
|
2640
2710
|
: body;
|
|
2641
2711
|
// 先用裸文本兜底;vars 构造完成后用消息渲染层重算(见下方 effectivePrompt 重赋值)。
|
|
2642
2712
|
let effectivePrompt = wrapPrompt(message.content);
|
|
2713
|
+
let fullAccessMessagePromptContext;
|
|
2643
2714
|
let renderResult;
|
|
2644
2715
|
let effectiveSystemPrompt;
|
|
2645
2716
|
let modelOverride;
|
|
@@ -2735,6 +2806,9 @@ export class ResponseEngine {
|
|
|
2735
2806
|
// records all observe the same normalized value.
|
|
2736
2807
|
permissionContext.permissionMode = effectivePermissionMode;
|
|
2737
2808
|
permissionContext.executionPermission = fullAccessAuthorization;
|
|
2809
|
+
fullAccessMessagePromptContext = fullAccessAuthorization
|
|
2810
|
+
? FULL_ACCESS_MESSAGE_PROMPT_CONTEXT
|
|
2811
|
+
: undefined;
|
|
2738
2812
|
// 按 关系级 > agent级 > 全局 解析本次调用的模型/强度,作为 per-call 入参传入 runQuery。
|
|
2739
2813
|
// 不缓存、不绑会话——改关系级/agent级后该范围所有会话的下条消息即时生效;
|
|
2740
2814
|
// 多对端并发各自独立解析、各自传参,无共享状态可被污染。
|
|
@@ -2829,7 +2903,9 @@ export class ResponseEngine {
|
|
|
2829
2903
|
agentModel = (typeof agent.getModel === 'function') ? agent.getModel() : undefined;
|
|
2830
2904
|
primaryModel = modelOverride?.model || agentModel;
|
|
2831
2905
|
if (!hasExplicitTriggerModel && primaryModel) {
|
|
2832
|
-
const fallbackResolution = eligibleFallbackModels({
|
|
2906
|
+
const fallbackResolution = await eligibleFallbackModels({
|
|
2907
|
+
selfAid: selfAid || undefined,
|
|
2908
|
+
catalog: agent.getModelCatalog?.bind(agent),
|
|
2833
2909
|
baseagent: normalizedBaseagent.canonical,
|
|
2834
2910
|
currentModel: primaryModel,
|
|
2835
2911
|
resolveModelId: typeof agent.resolveModelId === 'function'
|
|
@@ -3108,7 +3184,7 @@ export class ResponseEngine {
|
|
|
3108
3184
|
}];
|
|
3109
3185
|
const peerItems = (() => {
|
|
3110
3186
|
if (message.handoffDelivery && this.handoffRuntime) {
|
|
3111
|
-
const items = this.handoffRuntime.buildPromptItems(message);
|
|
3187
|
+
const items = this.handoffRuntime.buildPromptItems(message, peerRole);
|
|
3112
3188
|
if (items.length > 0) {
|
|
3113
3189
|
v2HandoffIds = Array.from(new Set(items.flatMap(item => (item.handoff?.handoffIds?.length
|
|
3114
3190
|
? item.handoff.handoffIds
|
|
@@ -3169,6 +3245,12 @@ export class ResponseEngine {
|
|
|
3169
3245
|
if (v2HandoffIds.length > 0 && renderResult?.body.trim())
|
|
3170
3246
|
handoffPromptRendered = true;
|
|
3171
3247
|
}
|
|
3248
|
+
// Keep this notice in the per-turn message prompt. Injecting it into
|
|
3249
|
+
// the system prompt would alter long-lived runner prompt caches and
|
|
3250
|
+
// could make a one-turn grant look persistent.
|
|
3251
|
+
if (fullAccessMessagePromptContext && effectivePrompt.trim()) {
|
|
3252
|
+
effectivePrompt = insertMessagePromptContext(effectivePrompt, fullAccessMessagePromptContext, message.content);
|
|
3253
|
+
}
|
|
3172
3254
|
// 空消息防护:在 agent 调用之前检查 prompt 是否为空
|
|
3173
3255
|
// 防止空消息(或纯空格消息)浪费 API 调用
|
|
3174
3256
|
if (!effectivePrompt.trim()) {
|
|
@@ -3205,12 +3287,14 @@ export class ResponseEngine {
|
|
|
3205
3287
|
peerName: peerName || undefined,
|
|
3206
3288
|
peerType: message.peerType || session.metadata?.peerType || undefined,
|
|
3207
3289
|
peerRole,
|
|
3290
|
+
permissionMode: effectivePermissionMode,
|
|
3208
3291
|
...(fullAccessAuthorization ? {
|
|
3209
3292
|
processRole: fullAccessAuthorization.processRole,
|
|
3210
3293
|
dataScope: fullAccessAuthorization.dataScope,
|
|
3211
3294
|
authorizedBy: fullAccessAuthorization.authorizedBy,
|
|
3212
3295
|
executionSource: fullAccessAuthorization.source,
|
|
3213
3296
|
} : {}),
|
|
3297
|
+
daemonRuntimeEpoch: this.agentDelegationRegistry?.getRuntimeEpoch(),
|
|
3214
3298
|
threadId: session.threadId || undefined,
|
|
3215
3299
|
sessionRuntimeDir,
|
|
3216
3300
|
runtimeLockDir: ensureRuntimeLockDir(sessionRuntimeDir),
|
|
@@ -3476,6 +3560,7 @@ export class ResponseEngine {
|
|
|
3476
3560
|
proactiveSelfAid: selfAid,
|
|
3477
3561
|
permissionMode: effectivePermissionMode,
|
|
3478
3562
|
turnLease,
|
|
3563
|
+
messagePromptContext: fullAccessMessagePromptContext,
|
|
3479
3564
|
beforeRunQuery: fullAccessAuthorization
|
|
3480
3565
|
? () => this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent)
|
|
3481
3566
|
: undefined,
|
|
@@ -3522,6 +3607,7 @@ export class ResponseEngine {
|
|
|
3522
3607
|
proactiveSelfAid: selfAid,
|
|
3523
3608
|
permissionMode: effectivePermissionMode,
|
|
3524
3609
|
turnLease,
|
|
3610
|
+
messagePromptContext: fullAccessMessagePromptContext,
|
|
3525
3611
|
beforeRunQuery: fullAccessAuthorization
|
|
3526
3612
|
? () => this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent)
|
|
3527
3613
|
: undefined,
|
|
@@ -3547,7 +3633,10 @@ export class ResponseEngine {
|
|
|
3547
3633
|
}
|
|
3548
3634
|
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
3549
3635
|
logger.info(`[ResponseEngine] Stale turn stopped before publish: session=${session.id} task=${taskId}`);
|
|
3550
|
-
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation
|
|
3636
|
+
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
|
|
3637
|
+
expected: turnLease?.generation,
|
|
3638
|
+
current: this.turnCoordinator.current(session).generation,
|
|
3639
|
+
});
|
|
3551
3640
|
return;
|
|
3552
3641
|
}
|
|
3553
3642
|
if (!commitDecision.ok) {
|
|
@@ -3559,7 +3648,10 @@ export class ResponseEngine {
|
|
|
3559
3648
|
}
|
|
3560
3649
|
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
3561
3650
|
logger.info(`[ResponseEngine] Stale turn stopped before protocol rejection: session=${session.id} task=${taskId}`);
|
|
3562
|
-
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation
|
|
3651
|
+
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_generation', taskCausation, {
|
|
3652
|
+
expected: turnLease?.generation,
|
|
3653
|
+
current: this.turnCoordinator.current(session).generation,
|
|
3654
|
+
});
|
|
3563
3655
|
return;
|
|
3564
3656
|
}
|
|
3565
3657
|
const reason = streamResult.protocolIncompleteReason || commitDecision.reason;
|
|
@@ -3761,7 +3853,10 @@ export class ResponseEngine {
|
|
|
3761
3853
|
if (!commitDecision.ok) {
|
|
3762
3854
|
logger.info(`[ResponseEngine] Turn commit rejected after flush: session=${session.id} task=${taskId} reason=${commitDecision.reason}`);
|
|
3763
3855
|
if (commitDecision.reason === 'stale_generation') {
|
|
3764
|
-
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, commitDecision.reason, taskCausation
|
|
3856
|
+
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, commitDecision.reason, taskCausation, {
|
|
3857
|
+
expected: turnLease?.generation,
|
|
3858
|
+
current: this.turnCoordinator.current(session).generation,
|
|
3859
|
+
});
|
|
3765
3860
|
}
|
|
3766
3861
|
else {
|
|
3767
3862
|
this.publishTriggerExecutionFailure(message, `turn_commit_rejected:${commitDecision.reason}`, {
|
|
@@ -3848,6 +3943,8 @@ export class ResponseEngine {
|
|
|
3848
3943
|
this.eventBus.publish({
|
|
3849
3944
|
type: 'task:error',
|
|
3850
3945
|
sessionId: session.id,
|
|
3946
|
+
taskId,
|
|
3947
|
+
generation: turnLease?.generation,
|
|
3851
3948
|
error: errorSummary,
|
|
3852
3949
|
errorType,
|
|
3853
3950
|
agentName: agentNameForStats,
|
|
@@ -3965,12 +4062,18 @@ export class ResponseEngine {
|
|
|
3965
4062
|
cache_read_tokens: sum.cache_read_tokens,
|
|
3966
4063
|
cache_creation_tokens: sum.cache_creation_tokens,
|
|
3967
4064
|
// 顶层 cost_usd/cost_cny 保持向后兼容 = 网关实际价
|
|
3968
|
-
cost_usd: sum.cost_gateway_usd,
|
|
3969
|
-
cost_cny: sum.cost_gateway_cny,
|
|
4065
|
+
cost_usd: roundCostForOutput(sum.cost_gateway_usd),
|
|
4066
|
+
cost_cny: roundCostForOutput(sum.cost_gateway_cny),
|
|
3970
4067
|
call_count: sum.calls,
|
|
3971
4068
|
cost: {
|
|
3972
|
-
official: {
|
|
3973
|
-
|
|
4069
|
+
official: {
|
|
4070
|
+
usd: roundCostForOutput(sum.cost_official_usd),
|
|
4071
|
+
cny: roundCostForOutput(sum.cost_official_cny),
|
|
4072
|
+
},
|
|
4073
|
+
gateway: {
|
|
4074
|
+
usd: roundCostForOutput(sum.cost_gateway_usd),
|
|
4075
|
+
cny: roundCostForOutput(sum.cost_gateway_cny),
|
|
4076
|
+
},
|
|
3974
4077
|
},
|
|
3975
4078
|
};
|
|
3976
4079
|
}
|
|
@@ -3988,10 +4091,13 @@ export class ResponseEngine {
|
|
|
3988
4091
|
}
|
|
3989
4092
|
else {
|
|
3990
4093
|
// cost 同时给原价(official)与网关实际价(gateway);顶层 cost_usd/cost_cny 保持向后兼容 = 网关价。
|
|
3991
|
-
const gatewayUsd = turnCost.gateway?.usd ?? turnCost.official?.usd ?? 0;
|
|
3992
|
-
const gatewayCny = turnCost.gateway?.cny ?? turnCost.official?.cny ?? 0;
|
|
4094
|
+
const gatewayUsd = roundCostForOutput(turnCost.gateway?.usd ?? turnCost.official?.usd ?? 0);
|
|
4095
|
+
const gatewayCny = roundCostForOutput(turnCost.gateway?.cny ?? turnCost.official?.cny ?? 0);
|
|
3993
4096
|
const turnCostBlock = {
|
|
3994
|
-
official: {
|
|
4097
|
+
official: {
|
|
4098
|
+
usd: roundCostForOutput(turnCost.official?.usd ?? 0),
|
|
4099
|
+
cny: roundCostForOutput(turnCost.official?.cny ?? 0),
|
|
4100
|
+
},
|
|
3995
4101
|
gateway: { usd: gatewayUsd, cny: gatewayCny },
|
|
3996
4102
|
};
|
|
3997
4103
|
// 最后一次访问:本轮可能有多次大模型调用(numTurns>1),整轮的 turnCostBlock 不等于
|
|
@@ -4006,10 +4112,13 @@ export class ResponseEngine {
|
|
|
4006
4112
|
model: lastModel, turns: 1,
|
|
4007
4113
|
});
|
|
4008
4114
|
const lp = resolvePrices(resolveRoot(), lastEvent, agent.getGatewayPricing?.());
|
|
4009
|
-
const lpGwUsd = lp.gateway?.usd ?? lp.official?.usd ?? 0;
|
|
4010
|
-
const lpGwCny = lp.gateway?.cny ?? lp.official?.cny ?? 0;
|
|
4115
|
+
const lpGwUsd = roundCostForOutput(lp.gateway?.usd ?? lp.official?.usd ?? 0);
|
|
4116
|
+
const lpGwCny = roundCostForOutput(lp.gateway?.cny ?? lp.official?.cny ?? 0);
|
|
4011
4117
|
lastModelCall = { ...lastModelCall, cost: {
|
|
4012
|
-
official: {
|
|
4118
|
+
official: {
|
|
4119
|
+
usd: roundCostForOutput(lp.official?.usd ?? 0),
|
|
4120
|
+
cny: roundCostForOutput(lp.official?.cny ?? 0),
|
|
4121
|
+
},
|
|
4013
4122
|
gateway: { usd: lpGwUsd, cny: lpGwCny },
|
|
4014
4123
|
} };
|
|
4015
4124
|
}
|
|
@@ -4057,6 +4166,8 @@ export class ResponseEngine {
|
|
|
4057
4166
|
this.eventBus.publish({
|
|
4058
4167
|
type: 'task:completed',
|
|
4059
4168
|
sessionId: session.id,
|
|
4169
|
+
taskId,
|
|
4170
|
+
generation: turnLease.generation,
|
|
4060
4171
|
channel: message.channel,
|
|
4061
4172
|
channelId: message.channelId,
|
|
4062
4173
|
terminalReason: streamResult.terminalReason,
|
|
@@ -4188,6 +4299,8 @@ export class ResponseEngine {
|
|
|
4188
4299
|
this.eventBus.publish({
|
|
4189
4300
|
type: 'task:error',
|
|
4190
4301
|
sessionId: session.id,
|
|
4302
|
+
taskId,
|
|
4303
|
+
generation: turnLease?.generation,
|
|
4191
4304
|
error: errorMsg,
|
|
4192
4305
|
errorType,
|
|
4193
4306
|
agentName: agentNameForStats,
|
|
@@ -4208,14 +4321,15 @@ export class ResponseEngine {
|
|
|
4208
4321
|
}
|
|
4209
4322
|
// 发送用户友好的错误消息
|
|
4210
4323
|
// 用户主动中断(新消息打断 或 /stop 命令)时静默,不发送错误提示
|
|
4211
|
-
//
|
|
4324
|
+
// 普通渠道可跳过 renderer 已发送的重复错误;daemon trigger 仍必须
|
|
4325
|
+
// 发送结构化终态,否则 DaemonChannel 会一直等到 watchdog。
|
|
4212
4326
|
const retryExhaustedCount = getRetryExhaustedCount(error);
|
|
4213
4327
|
const modelFallbackExhaustedMessage = getModelFallbackExhaustedMessage(error);
|
|
4214
4328
|
const retryInputAlreadySubmitted = wasRetryInputAlreadySubmitted(error);
|
|
4215
4329
|
if (isUserInterrupt) {
|
|
4216
4330
|
logger.info(`[ResponseEngine] User interrupt by new_message, skip sending error message`);
|
|
4217
4331
|
}
|
|
4218
|
-
else if (error?._errorAlreadySent && !retryExhaustedCount && !isTimeout && !isTotalExecutionTimeout) {
|
|
4332
|
+
else if (error?._errorAlreadySent && !daemonTrigger && !retryExhaustedCount && !isTimeout && !isTotalExecutionTimeout) {
|
|
4219
4333
|
logger.info(`[ResponseEngine] Error already sent via renderer, skip sending duplicate message`);
|
|
4220
4334
|
}
|
|
4221
4335
|
else {
|
|
@@ -4633,11 +4747,17 @@ export class ResponseEngine {
|
|
|
4633
4747
|
if (event.type === 'complete') {
|
|
4634
4748
|
event = normalizeCompleteAgentEvent(event);
|
|
4635
4749
|
}
|
|
4636
|
-
if (event.type === 'tool_result' && event.isError
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4750
|
+
if (event.type === 'tool_result' && event.isError) {
|
|
4751
|
+
const classificationMissing = !normalizeToolErrorCode(event.errorCode)
|
|
4752
|
+
&& event.decisionSource !== 'policy'
|
|
4753
|
+
&& event.decisionSource !== 'approval';
|
|
4754
|
+
const errorCode = classifyToolErrorCode({
|
|
4755
|
+
errorCode: event.errorCode,
|
|
4756
|
+
decisionSource: event.decisionSource,
|
|
4757
|
+
});
|
|
4758
|
+
if (event.errorCode !== errorCode) {
|
|
4759
|
+
event = { ...event, errorCode, ...(classificationMissing ? { classificationMissing: true } : {}) };
|
|
4760
|
+
}
|
|
4641
4761
|
}
|
|
4642
4762
|
// 每收到事件重置空闲超时
|
|
4643
4763
|
const toolName = event.type === 'tool_use' ? event.name : undefined;
|
|
@@ -4740,7 +4860,13 @@ export class ResponseEngine {
|
|
|
4740
4860
|
eventDetail = ` tool=${event.name}${desc ? ` desc="${desc}"` : ''}`;
|
|
4741
4861
|
}
|
|
4742
4862
|
else if (event.type === 'tool_result') {
|
|
4743
|
-
|
|
4863
|
+
const decision = event.decision ?? (event.isError ? 'error' : 'allow');
|
|
4864
|
+
const executed = event.executed ?? !event.isError;
|
|
4865
|
+
const executionState = event.executionState
|
|
4866
|
+
?? (decision === 'deny' ? 'blocked' : event.isError ? 'failed' : 'completed');
|
|
4867
|
+
eventDetail = ` tool=${event.name} ok=${decision === 'allow' && executed}`
|
|
4868
|
+
+ ` decision=${decision} executed=${executed} executionState=${executionState}`
|
|
4869
|
+
+ (event.policyCode ? ` policy=${event.policyCode}` : '');
|
|
4744
4870
|
}
|
|
4745
4871
|
const frameworkEvents = new Set(['session_id', 'state_changed', 'status']);
|
|
4746
4872
|
if (frameworkEvents.has(event.type)) {
|
|
@@ -5006,6 +5132,16 @@ export class ResponseEngine {
|
|
|
5006
5132
|
toolName: event.name,
|
|
5007
5133
|
toolInput: event.input || {},
|
|
5008
5134
|
injectToModel: (text) => { agent.injectUserMessage?.(session.id, text); },
|
|
5135
|
+
recordReminder: reminder => {
|
|
5136
|
+
this.eventBus.publish({
|
|
5137
|
+
type: 'runner:proactive-reminder',
|
|
5138
|
+
sessionId: session.id,
|
|
5139
|
+
...reminder,
|
|
5140
|
+
injection: 'requested',
|
|
5141
|
+
timestamp: Date.now(),
|
|
5142
|
+
causation,
|
|
5143
|
+
});
|
|
5144
|
+
},
|
|
5009
5145
|
getQueueLength: () => this.messageQueue?.getQueueLength(session.id) ?? 0,
|
|
5010
5146
|
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', proactiveSelfAid || session.selfAID),
|
|
5011
5147
|
logger,
|
|
@@ -5031,6 +5167,7 @@ export class ResponseEngine {
|
|
|
5031
5167
|
sessionId: session.id,
|
|
5032
5168
|
callId: event.callId,
|
|
5033
5169
|
correlationId: event.correlationId,
|
|
5170
|
+
requestId: event.requestId,
|
|
5034
5171
|
});
|
|
5035
5172
|
this.eventBus.publish({
|
|
5036
5173
|
type: 'tool:result',
|
|
@@ -5039,16 +5176,29 @@ export class ResponseEngine {
|
|
|
5039
5176
|
sessionId: session.id,
|
|
5040
5177
|
toolName: event.name,
|
|
5041
5178
|
isError: event.isError,
|
|
5042
|
-
...(event.isError ? {
|
|
5179
|
+
...(event.isError ? {
|
|
5180
|
+
errorCode: classifyToolErrorCode({
|
|
5181
|
+
errorCode: event.errorCode,
|
|
5182
|
+
decisionSource: event.decisionSource,
|
|
5183
|
+
}),
|
|
5184
|
+
...((event.classificationMissing || (!normalizeToolErrorCode(event.errorCode)
|
|
5185
|
+
&& event.decisionSource !== 'policy'
|
|
5186
|
+
&& event.decisionSource !== 'approval')) ? { classificationMissing: true } : {}),
|
|
5187
|
+
} : {}),
|
|
5043
5188
|
...(event.callId ? { callId: event.callId } : {}),
|
|
5044
5189
|
...(event.correlationId || event.callId ? { correlationId: event.correlationId ?? event.callId } : {}),
|
|
5190
|
+
...(event.requestId ? { requestId: event.requestId } : {}),
|
|
5045
5191
|
agentName: lifecycleAgentName,
|
|
5046
5192
|
agentAid: lifecycleAgentAid ?? 'unknown',
|
|
5047
5193
|
permissionMode: lifecyclePermissionMode,
|
|
5048
|
-
decision: event.isError ? 'error' : 'allow',
|
|
5049
|
-
decisionSource: 'runner',
|
|
5050
|
-
|
|
5051
|
-
|
|
5194
|
+
decision: event.decision ?? (event.isError ? 'error' : 'allow'),
|
|
5195
|
+
decisionSource: event.decisionSource ?? 'runner',
|
|
5196
|
+
...(event.policyCode ? { policyCode: event.policyCode } : {}),
|
|
5197
|
+
...(event.reason ? { reason: event.reason } : {}),
|
|
5198
|
+
executed: event.executed ?? (event.decision === 'deny' ? false : true),
|
|
5199
|
+
executionState: event.executionState ?? (event.decision === 'deny'
|
|
5200
|
+
? 'blocked'
|
|
5201
|
+
: event.isError ? 'failed' : 'completed'),
|
|
5052
5202
|
timestamp: Date.now(),
|
|
5053
5203
|
causation,
|
|
5054
5204
|
});
|
|
@@ -5095,7 +5245,23 @@ export class ResponseEngine {
|
|
|
5095
5245
|
result: event.result,
|
|
5096
5246
|
isError: event.isError,
|
|
5097
5247
|
error: event.error,
|
|
5248
|
+
decision: event.decision,
|
|
5249
|
+
decisionSource: event.decisionSource,
|
|
5250
|
+
executed: event.executed,
|
|
5251
|
+
executionState: event.executionState,
|
|
5252
|
+
policyCode: event.policyCode,
|
|
5253
|
+
requestId: event.requestId,
|
|
5098
5254
|
injectToModel: (text) => { agent.injectUserMessage?.(session.id, text); },
|
|
5255
|
+
recordReminder: reminder => {
|
|
5256
|
+
this.eventBus.publish({
|
|
5257
|
+
type: 'runner:proactive-reminder',
|
|
5258
|
+
sessionId: session.id,
|
|
5259
|
+
...reminder,
|
|
5260
|
+
injection: 'requested',
|
|
5261
|
+
timestamp: Date.now(),
|
|
5262
|
+
causation,
|
|
5263
|
+
});
|
|
5264
|
+
},
|
|
5099
5265
|
getQueueLength: () => this.messageQueue?.getQueueLength(session.id) ?? 0,
|
|
5100
5266
|
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', proactiveSelfAid || session.selfAID),
|
|
5101
5267
|
logger,
|
|
@@ -5123,8 +5289,10 @@ export class ResponseEngine {
|
|
|
5123
5289
|
// 运行时错误(Codex: turn.failed / item error)
|
|
5124
5290
|
if (event.type === 'error') {
|
|
5125
5291
|
logger.warn(`[ResponseEngine] error event: ${event.errorType}: ${event.error}`);
|
|
5126
|
-
//
|
|
5127
|
-
|
|
5292
|
+
// Keep runtime diagnostics separate from assistant prose. Appending
|
|
5293
|
+
// an error to lastReplyText makes the terminal flush treat the error
|
|
5294
|
+
// as user-visible model output and can also make it look like a
|
|
5295
|
+
// duplicate of a pending text event.
|
|
5128
5296
|
completeResult = {
|
|
5129
5297
|
...completeResult,
|
|
5130
5298
|
isError: true,
|
|
@@ -5134,14 +5302,6 @@ export class ResponseEngine {
|
|
|
5134
5302
|
lastReplyText,
|
|
5135
5303
|
fullText: '',
|
|
5136
5304
|
};
|
|
5137
|
-
// 上下文过长的错误不在此处输出 notice,留给外层 isPromptTooLong 触发 auto-compact
|
|
5138
|
-
const isContextError = isContextTooLongText(event.error || '');
|
|
5139
|
-
const isRetryableRuntimeError = isRetryableError(new Error(event.error || ''));
|
|
5140
|
-
const isRecoverableModelError = classifyError(new Error(event.error || '')) === ErrorType.MODEL_UNAVAILABLE;
|
|
5141
|
-
if (!isContextError && !isRetryableRuntimeError && !isRecoverableModelError && !hasErrorResult && !shouldSuppress()) {
|
|
5142
|
-
hasErrorResult = true;
|
|
5143
|
-
renderer.addNotice(getRuntimeErrorMessage(event.error || '任务执行失败', false), 'warn', 'runtime-error', true);
|
|
5144
|
-
}
|
|
5145
5305
|
}
|
|
5146
5306
|
// 完成事件
|
|
5147
5307
|
// SDK 可能产生多个 complete 事件(如 subagent 或 auto-compact 二次查询),
|
|
@@ -5160,34 +5320,6 @@ export class ResponseEngine {
|
|
|
5160
5320
|
madeProgress = true;
|
|
5161
5321
|
// thought jsonl 写入已下沉到 aun.ts:sendThought 成功后,
|
|
5162
5322
|
// 由那里按 LLM 输出的每个 text item 单独写一条,此处不再写。
|
|
5163
|
-
// 失败且无前置错误输出:显示 errors 摘要
|
|
5164
|
-
// 但用户主动中断(新消息打断 或 /stop 命令)时不显示错误提示
|
|
5165
|
-
// 上下文过长的错误留给外层 isPromptTooLong 触发 auto-compact,不在此处输出
|
|
5166
|
-
const interruptReason = this.interruptedSessions.get(session.id);
|
|
5167
|
-
const isUserInterrupt = isNonErrorInterruptReason(interruptReason)
|
|
5168
|
-
|| isExpectedTerminalInterrupt(event.terminalReason);
|
|
5169
|
-
const isContextTooLong = event.terminalReason === 'prompt_too_long'
|
|
5170
|
-
|| isContextTooLongText(event.errors?.join(' ') || '')
|
|
5171
|
-
|| isContextTooLongText(lastReplyText);
|
|
5172
|
-
const completeErrorText = getStreamErrorText(completeResult);
|
|
5173
|
-
const isRetryableCompleteError = event.isError && !isContextTooLong && completeErrorText
|
|
5174
|
-
? isRetryableError(new Error(completeErrorText))
|
|
5175
|
-
: false;
|
|
5176
|
-
const isRecoverableModelError = event.isError && !isContextTooLong && completeErrorText
|
|
5177
|
-
? classifyError(new Error(completeErrorText)) === ErrorType.MODEL_UNAVAILABLE
|
|
5178
|
-
: false;
|
|
5179
|
-
if (event.isError && completeErrorText && isPendingTextSameAsStreamError(renderer.getRemainingText(), completeErrorText)) {
|
|
5180
|
-
renderer.discardPendingText();
|
|
5181
|
-
}
|
|
5182
|
-
if (event.isError && !hasErrorResult && !shouldSuppress() && !isUserInterrupt && !isContextTooLong && !isRetryableCompleteError && !isRecoverableModelError) {
|
|
5183
|
-
// 使用 terminalReason 提供更友好的错误提示(不带 emoji,由 formatter 统一加)
|
|
5184
|
-
const userFriendlyMessage = event.terminalReason === 'prompt_too_long'
|
|
5185
|
-
? getContextTooLongHint(agent)
|
|
5186
|
-
: event.terminalReason === 'context_compact_failed'
|
|
5187
|
-
? getContextCompactFailedHint(agent)
|
|
5188
|
-
: getStreamErrorMessage(completeResult, false);
|
|
5189
|
-
renderer.addNotice(userFriendlyMessage, 'warn', 'task-error', true);
|
|
5190
|
-
}
|
|
5191
5323
|
// 中间 complete:flush 掉已有 activities(不带 isFinal),让中间结果及时显示
|
|
5192
5324
|
// 最终文本留给流结束后的统一 flush(true)
|
|
5193
5325
|
if (renderer.hasContent()) {
|