evolcore 0.0.27 → 0.0.29-test.0
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 +38 -15
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +29 -14
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +13 -2
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +444 -106
- package/dist/agents/gemini-runner.js +35 -17
- package/dist/aun/group/history.js +130 -90
- package/dist/aun/group/operations.js +1 -3
- package/dist/aun/menu/client.js +1 -0
- package/dist/aun/menu/transport.js +5 -3
- package/dist/aun/msg/managed-operation.js +70 -0
- package/dist/aun/msg/p2p.js +1 -3
- package/dist/aun/msg/upload.js +2 -2
- package/dist/aun/outbox.js +1 -37
- package/dist/channels/aun.js +262 -86
- package/dist/channels/daemon.js +18 -1
- package/dist/channels/feishu.js +1 -2
- package/dist/cli/agent-command.js +23 -3
- package/dist/cli/agent-create.js +68 -2
- package/dist/cli/agent.js +12 -2
- package/dist/cli/aun-commands.js +87 -13
- package/dist/cli/cli-argv.js +26 -0
- package/dist/cli/daemon-commands.js +116 -149
- package/dist/cli/data-command.js +4 -4
- package/dist/cli/diag.js +1 -1
- package/dist/cli/handoff-command.js +2 -0
- package/dist/cli/index.js +18 -1
- package/dist/cli/managed-command-guard.js +10 -0
- package/dist/cli/memory-command.js +441 -0
- package/dist/cli/menu-client.js +4 -0
- package/dist/cli/restart-monitor.js +127 -23
- package/dist/cli/skill-command.js +41 -0
- package/dist/cli/task-context.js +2 -23
- package/dist/cli/trigger-command.js +9 -1
- package/dist/cli/watch-msg-diag.js +209 -0
- package/dist/config/config-field-policy.js +52 -3
- package/dist/config/config-manager.js +17 -4
- package/dist/config/contact-book-store.js +2 -2
- package/dist/config/mention-mode.js +7 -2
- package/dist/config/merge.js +28 -9
- package/dist/config/resolved-config-op.js +1 -1
- package/dist/config/runtime-env.js +137 -0
- package/dist/config/schema-registry.js +11 -1
- package/dist/config-store.js +10 -10
- package/dist/core/audit/event-key.js +3 -1
- package/dist/core/audit/log-integrity.js +4 -0
- package/dist/core/auth/agent-delegation.js +2 -1
- package/dist/core/auth/auth-gateway.js +13 -6
- package/dist/core/auth/authorization-audit.js +54 -19
- package/dist/core/auth/operation-authorizer.js +17 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/causation/context.js +5 -1
- package/dist/core/command/agent-control.js +1 -2
- package/dist/core/command/command-handler.js +80 -24
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/memory-slash.js +270 -0
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +331 -79
- package/dist/core/command/menu-protocol.js +45 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +230 -79
- package/dist/core/data-migration.js +282 -505
- package/dist/core/event-catalog.js +26 -0
- package/dist/core/evolagent-registry.js +6 -0
- package/dist/core/evolagent.js +0 -10
- package/dist/core/handoff/store.js +1 -20
- package/dist/core/inference/text-inference.js +57 -1
- package/dist/core/memory/consolidation-context.js +505 -0
- package/dist/core/memory/consolidation-ops-records.js +124 -0
- package/dist/core/memory/consolidation-trigger.js +394 -0
- package/dist/core/memory/index-manager.js +308 -0
- package/dist/core/memory/launch-consolidation.js +395 -0
- package/dist/core/memory/memory-admin.js +234 -0
- package/dist/core/memory/memory-document.js +100 -0
- package/dist/core/memory/memory-session-policy.js +41 -0
- package/dist/core/memory/memory-signature.js +257 -0
- package/dist/core/memory/memory-writer.js +432 -0
- package/dist/core/memory/staleness.js +117 -0
- package/dist/core/message/auto-mention-gate.js +454 -0
- package/dist/core/message/im-renderer.js +90 -20
- package/dist/core/message/message-bridge.js +333 -23
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +114 -27
- package/dist/core/message/pending-hints.js +329 -161
- package/dist/core/message/response-engine.js +596 -90
- package/dist/core/permission/ec-command-parser.js +1044 -87
- package/dist/core/permission/protected-paths.js +376 -126
- package/dist/core/permission/sandbox-runtime.js +40 -12
- package/dist/core/permission/tool-error-code.js +96 -13
- package/dist/core/permission/tool-policy.js +277 -24
- package/dist/core/role/runtime-policy.js +3 -7
- package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
- package/dist/core/session/session-fs-store.js +0 -76
- package/dist/core/session/session-idle-monitor.js +355 -0
- package/dist/core/session/session-manager.js +482 -17
- package/dist/core/session/session-mapper.js +33 -16
- package/dist/core/session/session-renew.js +217 -155
- package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
- package/dist/core/skill/global-watcher.js +282 -0
- package/dist/core/skill/index-generator.js +84 -0
- package/dist/core/skill/startup.js +73 -0
- package/dist/core/skill/switch-resolver.js +115 -0
- package/dist/core/task/task-activity-store.js +219 -0
- package/dist/eck/kit-renderer.js +30 -3
- package/dist/eck/manifest-engine.js +26 -4
- package/dist/index.js +722 -171
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +116 -15
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +1 -0
- package/dist/trigger/manager.js +0 -10
- package/dist/trigger/scheduler.js +11 -0
- package/dist/trigger/validation.js +22 -0
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/avatar-upload.js +59 -18
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/error-dict.json +6 -3
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/logger.js +32 -10
- package/dist/utils/media-cache.js +84 -0
- package/dist/utils/msg-diag.js +97 -0
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/welcome.js +37 -8
- package/dist/utils/windows-shell-trust.js +37 -0
- package/kits/docs/AUTHORING.md +314 -0
- package/kits/docs/GUIDE.md +2 -0
- package/kits/docs/INDEX.md +2 -0
- package/kits/docs/aun/CHEATSHEET.md +4 -0
- package/kits/docs/aun/SDK.md +60 -0
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/context-assembly.md +10 -6
- package/kits/docs/evolcore/INDEX.md +17 -3
- package/kits/docs/evolcore/agent-create.md +24 -0
- package/kits/docs/evolcore/agent.md +5 -2
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/ec-capabilities.md +364 -0
- package/kits/docs/evolcore/group.md +34 -18
- package/kits/docs/evolcore/memory-architecture.md +78 -0
- package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
- package/kits/docs/evolcore/memory-consolidation.md +263 -0
- package/kits/docs/evolcore/memory-tags.md +63 -0
- package/kits/docs/evolcore/memory.md +110 -0
- package/kits/docs/evolcore/menu-client.md +27 -11
- package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
- package/kits/docs/evolcore/menu-protocol.md +213 -0
- package/kits/docs/evolcore/msg.md +2 -0
- package/kits/docs/evolcore/trigger.md +7 -5
- package/kits/docs/path-registry.md +6 -8
- package/kits/docs/prompt-loading-architecture.md +5 -3
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +294 -88
- package/kits/eck_manifest.memory.json +257 -0
- package/kits/rules/03-identity.md +8 -8
- package/kits/rules/05-venue.md +4 -2
- package/kits/rules/06-channel.md +1 -1
- package/kits/schemas/_meta.json +276 -47
- package/kits/schemas/agent-config.schema.12.json +38 -2
- package/kits/schemas/agent-config.schema.13.json +591 -0
- package/kits/schemas/daemon.schema.6.json +9 -9
- package/kits/schemas/defaults.schema.5.json +29 -0
- package/kits/schemas/relation-config.schema.8.json +14 -1
- package/kits/schemas/relation-config.schema.9.json +129 -0
- package/kits/schemas/role-config.schema.1.json +3 -3
- package/kits/skills/.switches.json +3 -0
- package/kits/skills/ec-skill/SKILL.md +217 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
- package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
- package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
- package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
- package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
- package/kits/templates/decision/auto-mention.md +25 -0
- package/kits/templates/decision/session-renew.md +22 -0
- package/kits/templates/memory-consolidation/forget.md +104 -0
- package/kits/templates/memory-consolidation/generalize.md +76 -0
- package/kits/templates/memory-consolidation/notes.md +133 -0
- package/kits/templates/memory-consolidation/profile.md +58 -0
- package/kits/templates/memory-consolidation/promote.md +72 -0
- package/kits/templates/memory-consolidation/workflow.md +75 -0
- package/kits/templates/system-fragments/bootstrap.md +14 -5
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/group-history.md +6 -0
- package/kits/templates/system-fragments/identity.md +0 -3
- package/kits/templates/system-fragments/memory.md +79 -0
- package/kits/templates/system-fragments/reply.md +1 -0
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -6
- package/dist/trigger/legacy-session-history.js +0 -19
- package/kits/templates/system-fragments/group-unread-history.md +0 -7
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { readGroupHistoryContext } from '../../aun/group/history.js';
|
|
3
|
+
import { getPackageRoot } from '../../paths.js';
|
|
4
|
+
import { createErrorCapturingReader, fileCache } from '../daemon-file-cache.js';
|
|
5
|
+
import { logger } from '../../utils/logger.js';
|
|
6
|
+
const MAX_HISTORY_MESSAGES = 20;
|
|
7
|
+
const MAX_HISTORY_MESSAGE_CHARS = 800;
|
|
8
|
+
const MAX_STATE_CONTENT_CHARS = 16_000;
|
|
9
|
+
const HISTORY_HEAD_COUNT = 5;
|
|
10
|
+
const HISTORY_TAIL_COUNT = 15;
|
|
11
|
+
const ADMISSION_GRACE_MS = 5_000;
|
|
12
|
+
const DEFAULT_QUIET_WINDOW_MS = 3_000;
|
|
13
|
+
function parseExecutionPolicyTemplate(source) {
|
|
14
|
+
const sections = new Map();
|
|
15
|
+
const matches = [...source.matchAll(/^##[ \t]+([^\r\n]+)\r?$/gmu)];
|
|
16
|
+
for (const [index, match] of matches.entries()) {
|
|
17
|
+
const name = match[1].trim();
|
|
18
|
+
if (sections.has(name))
|
|
19
|
+
throw new Error(`Duplicate attention prompt section: ${name}`);
|
|
20
|
+
const start = (match.index ?? 0) + match[0].length;
|
|
21
|
+
const end = matches[index + 1]?.index ?? source.length;
|
|
22
|
+
sections.set(name, source.slice(start, end).trim());
|
|
23
|
+
}
|
|
24
|
+
const requiredSection = (name) => {
|
|
25
|
+
const content = sections.get(name);
|
|
26
|
+
if (!content)
|
|
27
|
+
throw new Error(`Missing or empty attention prompt section: ${name}`);
|
|
28
|
+
return content;
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
execution_policy: {
|
|
32
|
+
type: 'choice',
|
|
33
|
+
instructions: requiredSection('Instructions'),
|
|
34
|
+
criteria: {
|
|
35
|
+
interrupt: requiredSection('Interrupt Criteria'),
|
|
36
|
+
defer: requiredSection('Defer Criteria'),
|
|
37
|
+
ignore: requiredSection('Ignore Criteria'),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function decisionFromHistory(entry) {
|
|
43
|
+
return entry.decision;
|
|
44
|
+
}
|
|
45
|
+
function lifecycleForHistory(entry, item, resolveMessageState) {
|
|
46
|
+
const decision = decisionFromHistory(entry);
|
|
47
|
+
if (decision === 'ignore')
|
|
48
|
+
return 'consumed';
|
|
49
|
+
if (!entry.message_id || !resolveMessageState)
|
|
50
|
+
return entry.lifecycle;
|
|
51
|
+
const state = resolveMessageState({
|
|
52
|
+
value: undefined,
|
|
53
|
+
selfAid: item.selfAid,
|
|
54
|
+
groupId: item.groupId,
|
|
55
|
+
threadId: item.threadId,
|
|
56
|
+
channelKey: item.channelKey,
|
|
57
|
+
sessionId: entry.session_id ?? item.sessionId,
|
|
58
|
+
projectPath: item.projectPath,
|
|
59
|
+
messageId: entry.message_id,
|
|
60
|
+
sender: entry.sender,
|
|
61
|
+
role: entry.role,
|
|
62
|
+
mentions: [],
|
|
63
|
+
content: entry.content,
|
|
64
|
+
receivedAt: Date.parse(entry.timestamp),
|
|
65
|
+
});
|
|
66
|
+
return state === 'queued' ? 'pending' : state === 'active' ? 'processing' : 'consumed';
|
|
67
|
+
}
|
|
68
|
+
function withLifecycle(entry, lifecycle) {
|
|
69
|
+
return { ...entry, lifecycle };
|
|
70
|
+
}
|
|
71
|
+
function dedupeMessages(messages) {
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
return messages.filter(message => {
|
|
74
|
+
const key = message.message_id || `${message.timestamp}\n${message.sender}\n${message.content}`;
|
|
75
|
+
if (seen.has(key))
|
|
76
|
+
return false;
|
|
77
|
+
seen.add(key);
|
|
78
|
+
return true;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function chooseRecent(messages) {
|
|
82
|
+
const ordered = dedupeMessages(messages).sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
83
|
+
if (ordered.length <= MAX_HISTORY_MESSAGES)
|
|
84
|
+
return ordered;
|
|
85
|
+
return [...ordered.slice(0, HISTORY_HEAD_COUNT), ...ordered.slice(-HISTORY_TAIL_COUNT)];
|
|
86
|
+
}
|
|
87
|
+
function compactHistoryContent(content, maxChars = MAX_HISTORY_MESSAGE_CHARS) {
|
|
88
|
+
if (content.length <= maxChars)
|
|
89
|
+
return content;
|
|
90
|
+
const marker = '[中间内容已省略]';
|
|
91
|
+
const paragraphs = content.split(/\n\s*\n/).map(value => value.trim()).filter(Boolean);
|
|
92
|
+
const headBudget = 600;
|
|
93
|
+
const tailBudget = Math.max(0, maxChars - headBudget - marker.length - 2);
|
|
94
|
+
if (paragraphs.length > 1) {
|
|
95
|
+
const head = [];
|
|
96
|
+
let headLength = 0;
|
|
97
|
+
for (const paragraph of paragraphs) {
|
|
98
|
+
if (headLength + paragraph.length + (head.length ? 2 : 0) > headBudget)
|
|
99
|
+
break;
|
|
100
|
+
head.push(paragraph);
|
|
101
|
+
headLength += paragraph.length + (head.length > 1 ? 2 : 0);
|
|
102
|
+
}
|
|
103
|
+
const tail = [];
|
|
104
|
+
let tailLength = 0;
|
|
105
|
+
for (let index = paragraphs.length - 1; index >= 0; index--) {
|
|
106
|
+
const paragraph = paragraphs[index];
|
|
107
|
+
if (tailLength + paragraph.length + (tail.length ? 2 : 0) > tailBudget)
|
|
108
|
+
break;
|
|
109
|
+
tail.unshift(paragraph);
|
|
110
|
+
tailLength += paragraph.length + (tail.length > 1 ? 2 : 0);
|
|
111
|
+
}
|
|
112
|
+
if (head.length > 0 && tail.length > 0 && head.join('\n\n') !== tail.join('\n\n')) {
|
|
113
|
+
const compacted = `${head.join('\n\n')}\n${marker}\n${tail.join('\n\n')}`;
|
|
114
|
+
if (compacted.length <= maxChars)
|
|
115
|
+
return compacted;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const head = Math.max(1, Math.floor((maxChars - marker.length) * 0.75));
|
|
119
|
+
const tail = Math.max(0, maxChars - marker.length - head);
|
|
120
|
+
return `${content.slice(0, head)}${marker}${tail ? content.slice(-tail) : ''}`.slice(0, maxChars);
|
|
121
|
+
}
|
|
122
|
+
function formatMessage(message, lifecycle, decision, compact = true) {
|
|
123
|
+
const sender = message.sender || '<unknown>';
|
|
124
|
+
const role = message.role || 'unknown';
|
|
125
|
+
const timestamp = 'timestamp' in message ? message.timestamp : '';
|
|
126
|
+
const body = compact ? compactHistoryContent(message.content) : message.content;
|
|
127
|
+
return `[lifecycle=${lifecycle} decision=${decision ?? 'none'} sender=${sender} role=${role} time=${timestamp}]\n${body}`;
|
|
128
|
+
}
|
|
129
|
+
function section(title, body) {
|
|
130
|
+
return `${title}\n${body || '(无)'}`;
|
|
131
|
+
}
|
|
132
|
+
function formatResources(resources) {
|
|
133
|
+
return [
|
|
134
|
+
section('当前群规则', resources.groupRules ?? ''),
|
|
135
|
+
section('当前 Agent 的个人资料', resources.personal ?? ''),
|
|
136
|
+
section('当前项目适用的项目指令', resources.projectInstructions ?? ''),
|
|
137
|
+
].join('\n\n');
|
|
138
|
+
}
|
|
139
|
+
function buildStateContent(processingNow, recentMessages, pendingMessages, newMessages, resources) {
|
|
140
|
+
const fixed = [
|
|
141
|
+
`当前处理状态:${processingNow ? '当前 Agent 正在处理其他任务。' : '当前 Agent 当前没有正在处理的任务。'}`,
|
|
142
|
+
section('尚未完成处理的消息', pendingMessages.map(message => formatMessage(message, message.lifecycle, message.decision, false)).join('\n\n')),
|
|
143
|
+
section('本次新消息', newMessages.map(message => formatMessage(message, 'new', null, false)).join('\n\n')),
|
|
144
|
+
formatResources(resources),
|
|
145
|
+
].join('\n\n');
|
|
146
|
+
const historyPrefix = '当前群会话历史对话\n';
|
|
147
|
+
const candidates = chooseRecent(recentMessages);
|
|
148
|
+
let kept = [...candidates];
|
|
149
|
+
const renderHistory = () => section(historyPrefix.trim(), kept.map(message => formatMessage(message, message.lifecycle, message.decision)).join('\n\n'));
|
|
150
|
+
while (`${fixed}\n\n${renderHistory()}`.length > MAX_STATE_CONTENT_CHARS && kept.length > 0) {
|
|
151
|
+
const removeIndex = kept.length > HISTORY_HEAD_COUNT ? HISTORY_HEAD_COUNT : 0;
|
|
152
|
+
kept.splice(removeIndex, 1);
|
|
153
|
+
}
|
|
154
|
+
const content = `${fixed}\n\n${renderHistory()}`;
|
|
155
|
+
if (content.length > MAX_STATE_CONTENT_CHARS) {
|
|
156
|
+
logger.warn(`[AutoMentionGate] required attention state exceeds ${MAX_STATE_CONTENT_CHARS} characters; preserving pending and new messages`);
|
|
157
|
+
}
|
|
158
|
+
return content;
|
|
159
|
+
}
|
|
160
|
+
export function parseAttentionDecision(text) {
|
|
161
|
+
return parseAttentionDecisionDetails(text).decision;
|
|
162
|
+
}
|
|
163
|
+
export function parseAttentionDecisionDetails(text) {
|
|
164
|
+
const parsed = JSON.parse(text.trim());
|
|
165
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
166
|
+
throw new Error('Invalid attention result');
|
|
167
|
+
const payload = parsed;
|
|
168
|
+
const answers = payload.answers;
|
|
169
|
+
const answer = answers && typeof answers === 'object' && !Array.isArray(answers)
|
|
170
|
+
? answers.execution_policy
|
|
171
|
+
: undefined;
|
|
172
|
+
const rawAnswer = answer ?? payload.execution_policy ?? payload.decision;
|
|
173
|
+
const choice = typeof rawAnswer === 'string'
|
|
174
|
+
? { choice: rawAnswer }
|
|
175
|
+
: rawAnswer && typeof rawAnswer === 'object' && !Array.isArray(rawAnswer)
|
|
176
|
+
? rawAnswer
|
|
177
|
+
: undefined;
|
|
178
|
+
if (!choice)
|
|
179
|
+
throw new Error('Missing execution policy');
|
|
180
|
+
if (choice.type !== undefined && choice.type !== 'choice')
|
|
181
|
+
throw new Error('Invalid execution policy type');
|
|
182
|
+
const decision = choice.choice ?? choice.decision ?? rawAnswer;
|
|
183
|
+
if (decision !== 'interrupt' && decision !== 'defer' && decision !== 'ignore') {
|
|
184
|
+
throw new Error('Invalid attention decision');
|
|
185
|
+
}
|
|
186
|
+
const confidence = choice.confidence;
|
|
187
|
+
const probabilities = choice.probabilities;
|
|
188
|
+
const requestId = payload.request_id;
|
|
189
|
+
const sanitized = {};
|
|
190
|
+
if (probabilities !== undefined) {
|
|
191
|
+
if (!probabilities || typeof probabilities !== 'object' || Array.isArray(probabilities)) {
|
|
192
|
+
throw new Error('Invalid attention probabilities');
|
|
193
|
+
}
|
|
194
|
+
for (const [policy, value] of Object.entries(probabilities)) {
|
|
195
|
+
if (policy !== 'interrupt' && policy !== 'defer' && policy !== 'ignore') {
|
|
196
|
+
throw new Error('Unexpected attention probability');
|
|
197
|
+
}
|
|
198
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
199
|
+
throw new Error('Invalid attention probability');
|
|
200
|
+
}
|
|
201
|
+
sanitized[policy] = value;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (confidence !== undefined && (typeof confidence !== 'number' || !Number.isFinite(confidence)
|
|
205
|
+
|| confidence < 0 || confidence > 1))
|
|
206
|
+
throw new Error('Invalid attention confidence');
|
|
207
|
+
return {
|
|
208
|
+
decision,
|
|
209
|
+
...(typeof confidence === 'number' ? { confidence } : {}),
|
|
210
|
+
...(probabilities !== undefined ? { probabilities: sanitized } : {}),
|
|
211
|
+
...(typeof requestId === 'string' && requestId ? { requestId: requestId.slice(0, 128) } : {}),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export class AutoMentionGate {
|
|
215
|
+
options;
|
|
216
|
+
states = new Map();
|
|
217
|
+
lastValidQuestions;
|
|
218
|
+
constructor(options) {
|
|
219
|
+
this.options = options;
|
|
220
|
+
}
|
|
221
|
+
loadExecutionPolicyQuestions() {
|
|
222
|
+
const templatePath = path.join(getPackageRoot(), 'kits', 'templates', 'decision', 'auto-mention.md');
|
|
223
|
+
const reader = createErrorCapturingReader();
|
|
224
|
+
try {
|
|
225
|
+
const questions = fileCache.get(templatePath, raw => {
|
|
226
|
+
if (raw === null) {
|
|
227
|
+
throw new Error(`Failed to read attention prompt ${templatePath}: ${reader.lastError()}`, {
|
|
228
|
+
cause: reader.lastErrorCause(),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return parseExecutionPolicyTemplate(raw);
|
|
232
|
+
}, { policy: 'mtime', group: 'decision-prompts', read: reader.read });
|
|
233
|
+
this.lastValidQuestions = questions;
|
|
234
|
+
return questions;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
if (!this.lastValidQuestions)
|
|
238
|
+
throw error;
|
|
239
|
+
logger.warn(`[AutoMentionGate] prompt template unavailable, using last valid prompt: ${String(error)}`);
|
|
240
|
+
return this.lastValidQuestions;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
background(item, excludeMessageIds = []) {
|
|
244
|
+
try {
|
|
245
|
+
return (this.options.readContext ?? readGroupHistoryContext)({
|
|
246
|
+
from: item.selfAid,
|
|
247
|
+
groupId: item.groupId,
|
|
248
|
+
thread: item.threadId || 'main',
|
|
249
|
+
channelKey: item.channelKey,
|
|
250
|
+
excludeMessageIds,
|
|
251
|
+
maxMessages: MAX_HISTORY_MESSAGES,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
logger.warn(`[AutoMentionGate] history unavailable, continuing without background: ${String(error)}`);
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async offer(item) {
|
|
260
|
+
const key = JSON.stringify([item.selfAid, item.groupId, item.threadId || 'main']);
|
|
261
|
+
let state = this.states.get(key);
|
|
262
|
+
if (!state) {
|
|
263
|
+
state = { pending: [], admitted: [], processing: false };
|
|
264
|
+
this.states.set(key, state);
|
|
265
|
+
}
|
|
266
|
+
if (state.maintenanceTimer) {
|
|
267
|
+
clearTimeout(state.maintenanceTimer);
|
|
268
|
+
state.maintenanceTimer = undefined;
|
|
269
|
+
}
|
|
270
|
+
this.pruneAdmitted(state);
|
|
271
|
+
const currentState = state;
|
|
272
|
+
const result = new Promise(resolve => {
|
|
273
|
+
currentState.pending.push({ item, queuedAt: Date.now(), resolve });
|
|
274
|
+
});
|
|
275
|
+
this.schedule(key, currentState);
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
pruneAdmitted(state) {
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
state.admitted = state.admitted.filter(entry => {
|
|
281
|
+
if (!entry.item.messageId || !this.options.resolveMessageState) {
|
|
282
|
+
return now - entry.admittedAt < ADMISSION_GRACE_MS;
|
|
283
|
+
}
|
|
284
|
+
const messageState = this.options.resolveMessageState(entry.item);
|
|
285
|
+
if (messageState === 'queued')
|
|
286
|
+
return true;
|
|
287
|
+
if (messageState === 'active')
|
|
288
|
+
return false;
|
|
289
|
+
// A decision is resolved before the bridge persists and enqueues the
|
|
290
|
+
// message. Keep the in-memory copy briefly so the next classification
|
|
291
|
+
// cannot lose it during that admission gap.
|
|
292
|
+
return now - entry.admittedAt < ADMISSION_GRACE_MS;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
scheduleMaintenance(key, state) {
|
|
296
|
+
if (state.maintenanceTimer)
|
|
297
|
+
clearTimeout(state.maintenanceTimer);
|
|
298
|
+
state.maintenanceTimer = setTimeout(() => {
|
|
299
|
+
state.maintenanceTimer = undefined;
|
|
300
|
+
this.pruneAdmitted(state);
|
|
301
|
+
if (state.pending.length > 0 || state.processing)
|
|
302
|
+
return;
|
|
303
|
+
if (state.admitted.length > 0) {
|
|
304
|
+
this.scheduleMaintenance(key, state);
|
|
305
|
+
}
|
|
306
|
+
else if (this.states.get(key) === state) {
|
|
307
|
+
this.states.delete(key);
|
|
308
|
+
}
|
|
309
|
+
}, ADMISSION_GRACE_MS);
|
|
310
|
+
}
|
|
311
|
+
schedule(key, state) {
|
|
312
|
+
if (state.timer)
|
|
313
|
+
clearTimeout(state.timer);
|
|
314
|
+
state.timer = undefined;
|
|
315
|
+
if (state.processing || state.pending.length === 0)
|
|
316
|
+
return;
|
|
317
|
+
const latestPending = state.pending.at(-1);
|
|
318
|
+
const latestItem = latestPending.item;
|
|
319
|
+
const configuredQuietWindowMs = this.options.resolveQuietWindowMs?.(latestItem) ?? this.options.quietWindowMs;
|
|
320
|
+
const quietWindowMs = typeof configuredQuietWindowMs === 'number'
|
|
321
|
+
&& Number.isFinite(configuredQuietWindowMs)
|
|
322
|
+
&& configuredQuietWindowMs >= 0
|
|
323
|
+
? configuredQuietWindowMs
|
|
324
|
+
: DEFAULT_QUIET_WINDOW_MS;
|
|
325
|
+
state.timer = setTimeout(() => {
|
|
326
|
+
state.timer = undefined;
|
|
327
|
+
void this.flush(key, state);
|
|
328
|
+
}, Math.max(0, latestPending.queuedAt + quietWindowMs - Date.now()));
|
|
329
|
+
}
|
|
330
|
+
async flush(key, state) {
|
|
331
|
+
if (state.processing || state.pending.length === 0)
|
|
332
|
+
return;
|
|
333
|
+
state.processing = true;
|
|
334
|
+
const batch = state.pending.splice(0, state.pending.length);
|
|
335
|
+
const items = batch.map(pending => pending.item);
|
|
336
|
+
const first = items[0];
|
|
337
|
+
const current = items.map(item => ({
|
|
338
|
+
source: 'current',
|
|
339
|
+
message_id: item.messageId ?? null,
|
|
340
|
+
sender: item.sender,
|
|
341
|
+
role: item.role,
|
|
342
|
+
mentions: item.mentions,
|
|
343
|
+
timestamp: new Date(item.receivedAt).toISOString(),
|
|
344
|
+
content: item.content,
|
|
345
|
+
}));
|
|
346
|
+
const history = this.readAllHistory(first, items.flatMap(item => item.messageId ? [item.messageId] : []));
|
|
347
|
+
const recent = history.map(entry => withLifecycle(entry, lifecycleForHistory(entry, first, this.options.resolveMessageState)));
|
|
348
|
+
const pendingHistory = recent.filter(entry => entry.lifecycle === 'pending');
|
|
349
|
+
const pendingItems = state.admitted.map(entry => this.asPendingMessage(entry.item, entry.decision));
|
|
350
|
+
const pendingMessages = dedupeMessages([...pendingHistory, ...pendingItems]);
|
|
351
|
+
let stateSnapshot;
|
|
352
|
+
let details = { decision: 'ignore' };
|
|
353
|
+
let source = 'fallback';
|
|
354
|
+
const processingNow = this.options.resolveProcessing?.(first) ?? !!first.mainSessionBusy;
|
|
355
|
+
try {
|
|
356
|
+
const inference = this.options.resolveInference(first);
|
|
357
|
+
if (!inference?.model)
|
|
358
|
+
throw new Error('Decision Model is not configured');
|
|
359
|
+
const resources = await this.options.resolveResources?.(first) ?? {};
|
|
360
|
+
const recentMessages = recent.filter(entry => entry.lifecycle !== 'pending');
|
|
361
|
+
const content = buildStateContent(processingNow, recentMessages, pendingMessages, current, resources);
|
|
362
|
+
stateSnapshot = {
|
|
363
|
+
processingNow,
|
|
364
|
+
recentMessages: chooseRecent(recentMessages),
|
|
365
|
+
pendingMessages,
|
|
366
|
+
newMessages: current,
|
|
367
|
+
resources,
|
|
368
|
+
content,
|
|
369
|
+
};
|
|
370
|
+
const questions = this.loadExecutionPolicyQuestions();
|
|
371
|
+
const controller = new AbortController();
|
|
372
|
+
const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs ?? 3000);
|
|
373
|
+
try {
|
|
374
|
+
const response = await Promise.race([
|
|
375
|
+
inference.provider.completeDecision({
|
|
376
|
+
model: inference.model,
|
|
377
|
+
state: { content },
|
|
378
|
+
questions,
|
|
379
|
+
signal: controller.signal,
|
|
380
|
+
}),
|
|
381
|
+
new Promise((_, reject) => controller.signal.addEventListener('abort', () => reject(new Error('Attention inference timeout')), { once: true })),
|
|
382
|
+
]);
|
|
383
|
+
details = parseAttentionDecisionDetails(response);
|
|
384
|
+
source = 'model';
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
clearTimeout(timeout);
|
|
388
|
+
controller.abort();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
details = { decision: 'ignore' };
|
|
393
|
+
source = 'fallback';
|
|
394
|
+
logger.warn(`[AutoMentionGate] decision model unavailable, falling back to mention-only: ${String(error)}`);
|
|
395
|
+
}
|
|
396
|
+
if (details.decision !== 'ignore') {
|
|
397
|
+
for (const item of items) {
|
|
398
|
+
if (!state.admitted.some(existing => existing.item.messageId && item.messageId
|
|
399
|
+
? existing.item.messageId === item.messageId
|
|
400
|
+
: existing.item === item)) {
|
|
401
|
+
state.admitted.push({ item, decision: details.decision, admittedAt: Date.now() });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
batch.forEach((pending, index) => pending.resolve({
|
|
406
|
+
decision: details.decision,
|
|
407
|
+
source,
|
|
408
|
+
leader: index === batch.length - 1,
|
|
409
|
+
batch: items,
|
|
410
|
+
background: stateSnapshot?.recentMessages ?? chooseRecent(recent),
|
|
411
|
+
current,
|
|
412
|
+
state: stateSnapshot,
|
|
413
|
+
details,
|
|
414
|
+
}));
|
|
415
|
+
state.processing = false;
|
|
416
|
+
if (state.pending.length) {
|
|
417
|
+
this.schedule(key, state);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
this.scheduleMaintenance(key, state);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
readAllHistory(item, excludeMessageIds) {
|
|
424
|
+
try {
|
|
425
|
+
return (this.options.readContext ?? readGroupHistoryContext)({
|
|
426
|
+
from: item.selfAid,
|
|
427
|
+
groupId: item.groupId,
|
|
428
|
+
thread: item.threadId || 'main',
|
|
429
|
+
channelKey: item.channelKey,
|
|
430
|
+
excludeMessageIds,
|
|
431
|
+
maxMessages: Number.MAX_SAFE_INTEGER,
|
|
432
|
+
maxChars: Number.MAX_SAFE_INTEGER,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
logger.warn(`[AutoMentionGate] history unavailable, continuing without background: ${String(error)}`);
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
asPendingMessage(item, decision) {
|
|
441
|
+
return {
|
|
442
|
+
source: 'background',
|
|
443
|
+
message_id: item.messageId ?? null,
|
|
444
|
+
session_id: item.sessionId,
|
|
445
|
+
sender: item.sender,
|
|
446
|
+
role: item.role,
|
|
447
|
+
timestamp: new Date(item.receivedAt).toISOString(),
|
|
448
|
+
content: item.content,
|
|
449
|
+
lifecycle: 'pending',
|
|
450
|
+
decision,
|
|
451
|
+
msg_type: 'text',
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
}
|
|
@@ -3,7 +3,7 @@ import { summarizeToolInput, summarizeToolInputForDisplay, toolInputForDisplay }
|
|
|
3
3
|
import { getErrorMessage, isContextTooLongText } from '../../utils/error-utils.js';
|
|
4
4
|
import { tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
5
5
|
import { DEFAULT_FLUSH_DELAY_MS } from '../../types.js';
|
|
6
|
-
import { classifyAunToolErrorCode } from '../permission/tool-error-code.js';
|
|
6
|
+
import { classifyAunToolErrorCode, normalizeToolErrorClassification } from '../permission/tool-error-code.js';
|
|
7
7
|
export class IMRenderer {
|
|
8
8
|
opts;
|
|
9
9
|
itemsQueue = [];
|
|
@@ -21,6 +21,7 @@ export class IMRenderer {
|
|
|
21
21
|
/** 自增 callId 兜底(runner 没提供时用) */
|
|
22
22
|
syntheticCallSeq = 0;
|
|
23
23
|
currentAssistantUuid;
|
|
24
|
+
currentTextPhase;
|
|
24
25
|
constructor(opts) {
|
|
25
26
|
this.opts = opts;
|
|
26
27
|
}
|
|
@@ -175,14 +176,24 @@ export class IMRenderer {
|
|
|
175
176
|
}
|
|
176
177
|
// ── 文本/活动注入(替代 StreamFlusher.addText/addActivity)──
|
|
177
178
|
/** 添加文本片段(流式 text) */
|
|
178
|
-
async addText(text, outputTokens, turn, assistantUuid) {
|
|
179
|
+
async addText(text, outputTokens, turn, assistantUuid, phase) {
|
|
179
180
|
if (this.opts.envelope.chatmode === 'proactive')
|
|
180
181
|
return;
|
|
181
|
-
|
|
182
|
+
const sameAssistant = this.currentAssistantUuid === assistantUuid;
|
|
183
|
+
// In deferred AUN private delivery, only explicit text_start/tool_use
|
|
184
|
+
// handlers are semantic boundaries. assistantUuid is correlation data and
|
|
185
|
+
// may be absent or change without proving that the prior text is final.
|
|
186
|
+
const hadPendingText = this.textBuffer.length > 0;
|
|
187
|
+
if (hadPendingText && !this.opts.deferTextUntilBoundary && !sameAssistant
|
|
188
|
+
&& !this.suppressIntermediateText()) {
|
|
182
189
|
await this.flushText();
|
|
183
190
|
}
|
|
184
191
|
// Do not carry the previous item's anchor onto unanchored output.
|
|
185
192
|
this.currentAssistantUuid = assistantUuid;
|
|
193
|
+
// A mixed buffer has no trustworthy phase, even when its latest chunk is commentary.
|
|
194
|
+
this.currentTextPhase = hadPendingText && this.opts.deferTextUntilBoundary
|
|
195
|
+
? this.currentTextPhase === phase ? phase : undefined
|
|
196
|
+
: phase;
|
|
186
197
|
this.emitProgress('text', outputTokens, turn);
|
|
187
198
|
if (!text)
|
|
188
199
|
return;
|
|
@@ -201,41 +212,67 @@ export class IMRenderer {
|
|
|
201
212
|
this.scheduleFlush();
|
|
202
213
|
}
|
|
203
214
|
/** 添加工具调用 */
|
|
204
|
-
addToolCall(name, input, callId, descText, turn, outputTokens) {
|
|
215
|
+
addToolCall(name, input, callId, descText, turn, outputTokens, assistantUuid) {
|
|
205
216
|
if (this.opts.envelope.chatmode === 'proactive')
|
|
206
217
|
return;
|
|
207
218
|
this.emitProgress('tool_call', outputTokens, turn, { toolName: name, callId });
|
|
208
|
-
if (this.suppressActivityItems())
|
|
209
|
-
return;
|
|
210
219
|
const displayInput = toolInputForDisplay(name, input);
|
|
211
|
-
this
|
|
220
|
+
// If this is a shell tool executing an EC command, extract the subcommand
|
|
221
|
+
// for the ext.evolCommand hint so the frontend can render it distinctly.
|
|
222
|
+
let evolCommand;
|
|
223
|
+
if ((name === 'Bash' || name === 'Shell') && typeof input?.command === 'string') {
|
|
224
|
+
const m = input.command.trim().match(/^(?:ec|evolcore)\s+([a-z][a-z0-9-]*)/i);
|
|
225
|
+
if (m)
|
|
226
|
+
evolCommand = m[1].toLowerCase();
|
|
227
|
+
}
|
|
228
|
+
const item = {
|
|
212
229
|
kind: 'tool_call',
|
|
213
230
|
call_id: callId || this.synthCallId(),
|
|
214
231
|
name,
|
|
215
232
|
arguments: displayInput,
|
|
216
233
|
text: displayInput !== input ? summarizeToolInput(name, displayInput || {}) : descText,
|
|
217
|
-
|
|
234
|
+
...(evolCommand ? { evolCommand } : {}),
|
|
235
|
+
...(assistantUuid ? { forkAnchor: { assistantMessageId: assistantUuid } } : {}),
|
|
236
|
+
};
|
|
237
|
+
this.recordActivity(item);
|
|
238
|
+
if (this.suppressActivityItems())
|
|
239
|
+
return;
|
|
240
|
+
this.itemsQueue.push(item);
|
|
218
241
|
this.messageTimestamps.push(Date.now());
|
|
219
242
|
this.scheduleFlush();
|
|
220
243
|
}
|
|
221
244
|
/** 添加工具结果 */
|
|
222
|
-
addToolResult(name, ok, result, error, callId, durationMs, descText, classification) {
|
|
245
|
+
addToolResult(name, ok, result, error, callId, durationMs, descText, classification, assistantUuid) {
|
|
223
246
|
if (this.opts.envelope.chatmode === 'proactive')
|
|
224
247
|
return;
|
|
225
248
|
this.emitProgress('tool_result', undefined, undefined, { toolName: name, callId, ok, durationMs });
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
249
|
+
const normalizedClassification = !ok
|
|
250
|
+
? normalizeToolErrorClassification(classification ?? {})
|
|
251
|
+
: undefined;
|
|
252
|
+
const item = {
|
|
229
253
|
kind: 'tool_result',
|
|
230
254
|
call_id: callId || this.synthCallId(),
|
|
231
255
|
name,
|
|
232
256
|
ok,
|
|
233
257
|
...(result !== undefined && { result }),
|
|
234
258
|
...(error !== undefined && { error }),
|
|
235
|
-
...(!ok && {
|
|
259
|
+
...(!ok && {
|
|
260
|
+
error_code: classifyAunToolErrorCode({
|
|
261
|
+
...(classification ?? {}),
|
|
262
|
+
...(normalizedClassification ?? {}),
|
|
263
|
+
}),
|
|
264
|
+
...(normalizedClassification?.errorDetailCode
|
|
265
|
+
? { detail_code: normalizedClassification.errorDetailCode }
|
|
266
|
+
: {}),
|
|
267
|
+
}),
|
|
236
268
|
...(durationMs !== undefined && { duration_ms: durationMs }),
|
|
237
269
|
...(descText !== undefined && { text: descText }),
|
|
238
|
-
|
|
270
|
+
...(assistantUuid ? { forkAnchor: { assistantMessageId: assistantUuid } } : {}),
|
|
271
|
+
};
|
|
272
|
+
this.recordActivity(item);
|
|
273
|
+
if (this.suppressActivityItems())
|
|
274
|
+
return;
|
|
275
|
+
this.itemsQueue.push(item);
|
|
239
276
|
this.messageTimestamps.push(Date.now());
|
|
240
277
|
this.scheduleFlush();
|
|
241
278
|
}
|
|
@@ -276,6 +313,8 @@ export class IMRenderer {
|
|
|
276
313
|
}
|
|
277
314
|
/** Notify the client that a new model text block has started. */
|
|
278
315
|
async sendTextStart(contentIndex, turn) {
|
|
316
|
+
if (this.opts.deferTextUntilBoundary)
|
|
317
|
+
await this.flushText();
|
|
279
318
|
const payload = {
|
|
280
319
|
kind: 'status.progress',
|
|
281
320
|
metadata: { activityType: 'text', kind: 'text_start', contentIndex, turn },
|
|
@@ -305,6 +344,7 @@ export class IMRenderer {
|
|
|
305
344
|
if (this.suppressActivityItems())
|
|
306
345
|
return;
|
|
307
346
|
this.itemsQueue.push(item);
|
|
347
|
+
this.recordActivity(item);
|
|
308
348
|
this.messageTimestamps.push(Date.now());
|
|
309
349
|
this.scheduleFlush();
|
|
310
350
|
}
|
|
@@ -332,7 +372,9 @@ export class IMRenderer {
|
|
|
332
372
|
}
|
|
333
373
|
if (this.suppressActivityItems() && !force)
|
|
334
374
|
return;
|
|
335
|
-
|
|
375
|
+
const item = { kind: 'notice', text, severity, subtype };
|
|
376
|
+
this.itemsQueue.push(item);
|
|
377
|
+
this.recordActivity(item);
|
|
336
378
|
this.messageTimestamps.push(Date.now());
|
|
337
379
|
this.scheduleFlush();
|
|
338
380
|
}
|
|
@@ -453,7 +495,7 @@ export class IMRenderer {
|
|
|
453
495
|
const elapsed = Date.now() - this.lastFlush;
|
|
454
496
|
const delay = Math.max(0, targetDelay - elapsed);
|
|
455
497
|
this.timer = setTimeout(() => {
|
|
456
|
-
void this.flushInternal().catch(error => {
|
|
498
|
+
void this.flushInternal(undefined, undefined, true).catch(error => {
|
|
457
499
|
logger.warn(`[IMRenderer] scheduled flush failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
458
500
|
});
|
|
459
501
|
}, delay);
|
|
@@ -485,7 +527,12 @@ export class IMRenderer {
|
|
|
485
527
|
this.itemsQueue = this.itemsQueue.filter(it => it.kind === 'text');
|
|
486
528
|
if (this.suppressActivityItems())
|
|
487
529
|
return;
|
|
488
|
-
|
|
530
|
+
// If any tool_call item carries an EC subcommand hint, surface it on the batch payload ext.
|
|
531
|
+
const evolCommandItem = nonThinking.find(it => it.kind === 'tool_call' && !!it.evolCommand);
|
|
532
|
+
const evolCommand = evolCommandItem ? evolCommandItem.evolCommand : undefined;
|
|
533
|
+
const payload = evolCommand
|
|
534
|
+
? { kind: 'activity.batch', items: nonThinking, ext: { evolCommand } }
|
|
535
|
+
: { kind: 'activity.batch', items: nonThinking };
|
|
489
536
|
this.sentContent = true;
|
|
490
537
|
try {
|
|
491
538
|
await this.sendPayload(payload);
|
|
@@ -501,7 +548,7 @@ export class IMRenderer {
|
|
|
501
548
|
* 完整 flush:把 itemsQueue 里所有 items 打包成 activity.batch 发送。
|
|
502
549
|
* 如果 isFinal=true,还会在 batch 之后单独发一条 result.text 作为最终回复。
|
|
503
550
|
*/
|
|
504
|
-
async flushInternal(isFinal, attention) {
|
|
551
|
+
async flushInternal(isFinal, attention, scheduled = false) {
|
|
505
552
|
if (this.timer) {
|
|
506
553
|
clearTimeout(this.timer);
|
|
507
554
|
this.timer = undefined;
|
|
@@ -562,6 +609,10 @@ export class IMRenderer {
|
|
|
562
609
|
if (!isFinal && this.textBuffer.length > 0) {
|
|
563
610
|
if (this.suppressIntermediateText())
|
|
564
611
|
return;
|
|
612
|
+
// A timer cannot decide whether this is the final answer. Keep it for
|
|
613
|
+
// an explicit boundary; known commentary can still be sent immediately.
|
|
614
|
+
if (scheduled && this.opts.deferTextUntilBoundary && this.currentTextPhase !== 'commentary')
|
|
615
|
+
return;
|
|
565
616
|
const text = this.textBuffer;
|
|
566
617
|
this.textBuffer = '';
|
|
567
618
|
const payload = {
|
|
@@ -669,8 +720,25 @@ export class IMRenderer {
|
|
|
669
720
|
this.emitProactiveItem(item);
|
|
670
721
|
}
|
|
671
722
|
/** proactive 模式逐条投影:单个 ThoughtItem 包成 activity.batch[1] 发出(fire-and-forget)。 */
|
|
723
|
+
recordActivity(item) {
|
|
724
|
+
try {
|
|
725
|
+
const result = this.opts.recordActivity?.(item);
|
|
726
|
+
if (result && typeof result.catch === 'function') {
|
|
727
|
+
result.catch(error => {
|
|
728
|
+
logger.warn(`[IMRenderer] task activity record failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
logger.warn(`[IMRenderer] task activity record failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
672
736
|
emitProactiveItem(item) {
|
|
673
|
-
|
|
737
|
+
this.recordActivity(item);
|
|
738
|
+
const evolCommand = item.kind === 'tool_call' ? item.evolCommand : undefined;
|
|
739
|
+
const payload = evolCommand
|
|
740
|
+
? { kind: 'activity.batch', items: [item], ext: { evolCommand } }
|
|
741
|
+
: { kind: 'activity.batch', items: [item] };
|
|
674
742
|
this.opts.send(payload).catch(err => {
|
|
675
743
|
logger.debug(`[IMRenderer] proactive send failed: ${err.message}`);
|
|
676
744
|
});
|
|
@@ -695,12 +763,14 @@ export class IMRenderer {
|
|
|
695
763
|
}
|
|
696
764
|
case 'tool_result':
|
|
697
765
|
if (event.isError) {
|
|
766
|
+
const classification = normalizeToolErrorClassification(event);
|
|
698
767
|
return {
|
|
699
768
|
kind: 'tool_result',
|
|
700
769
|
call_id: event.callId || this.synthCallId(),
|
|
701
770
|
name: event.name,
|
|
702
771
|
ok: false,
|
|
703
|
-
error_code: classifyAunToolErrorCode(event),
|
|
772
|
+
error_code: classifyAunToolErrorCode({ ...event, ...classification }),
|
|
773
|
+
...(classification.errorDetailCode ? { detail_code: classification.errorDetailCode } : {}),
|
|
704
774
|
error: event.error || (typeof event.result === 'string' ? event.result : '执行失败'),
|
|
705
775
|
...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
|
|
706
776
|
};
|