evolcore 0.0.28 → 0.0.29
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 +12 -0
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +19 -9
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +6 -2
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +396 -95
- 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/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 +249 -89
- 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 +40 -10
- package/dist/cli/data-command.js +4 -4
- 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/restart-monitor.js +62 -6
- 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 +51 -23
- 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 +315 -60
- package/dist/core/command/menu-protocol.js +42 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +206 -64
- 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 +69 -16
- package/dist/core/message/message-bridge.js +287 -21
- 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 +567 -80
- package/dist/core/permission/ec-command-parser.js +1029 -80
- 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 +276 -23
- 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 +478 -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 +708 -18
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- 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/avatar-upload.js +59 -18
- 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/welcome.js +37 -8
- 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/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 = [];
|
|
@@ -212,41 +212,67 @@ export class IMRenderer {
|
|
|
212
212
|
this.scheduleFlush();
|
|
213
213
|
}
|
|
214
214
|
/** 添加工具调用 */
|
|
215
|
-
addToolCall(name, input, callId, descText, turn, outputTokens) {
|
|
215
|
+
addToolCall(name, input, callId, descText, turn, outputTokens, assistantUuid) {
|
|
216
216
|
if (this.opts.envelope.chatmode === 'proactive')
|
|
217
217
|
return;
|
|
218
218
|
this.emitProgress('tool_call', outputTokens, turn, { toolName: name, callId });
|
|
219
|
-
if (this.suppressActivityItems())
|
|
220
|
-
return;
|
|
221
219
|
const displayInput = toolInputForDisplay(name, input);
|
|
222
|
-
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 = {
|
|
223
229
|
kind: 'tool_call',
|
|
224
230
|
call_id: callId || this.synthCallId(),
|
|
225
231
|
name,
|
|
226
232
|
arguments: displayInput,
|
|
227
233
|
text: displayInput !== input ? summarizeToolInput(name, displayInput || {}) : descText,
|
|
228
|
-
|
|
234
|
+
...(evolCommand ? { evolCommand } : {}),
|
|
235
|
+
...(assistantUuid ? { forkAnchor: { assistantMessageId: assistantUuid } } : {}),
|
|
236
|
+
};
|
|
237
|
+
this.recordActivity(item);
|
|
238
|
+
if (this.suppressActivityItems())
|
|
239
|
+
return;
|
|
240
|
+
this.itemsQueue.push(item);
|
|
229
241
|
this.messageTimestamps.push(Date.now());
|
|
230
242
|
this.scheduleFlush();
|
|
231
243
|
}
|
|
232
244
|
/** 添加工具结果 */
|
|
233
|
-
addToolResult(name, ok, result, error, callId, durationMs, descText, classification) {
|
|
245
|
+
addToolResult(name, ok, result, error, callId, durationMs, descText, classification, assistantUuid) {
|
|
234
246
|
if (this.opts.envelope.chatmode === 'proactive')
|
|
235
247
|
return;
|
|
236
248
|
this.emitProgress('tool_result', undefined, undefined, { toolName: name, callId, ok, durationMs });
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
249
|
+
const normalizedClassification = !ok
|
|
250
|
+
? normalizeToolErrorClassification(classification ?? {})
|
|
251
|
+
: undefined;
|
|
252
|
+
const item = {
|
|
240
253
|
kind: 'tool_result',
|
|
241
254
|
call_id: callId || this.synthCallId(),
|
|
242
255
|
name,
|
|
243
256
|
ok,
|
|
244
257
|
...(result !== undefined && { result }),
|
|
245
258
|
...(error !== undefined && { error }),
|
|
246
|
-
...(!ok && {
|
|
259
|
+
...(!ok && {
|
|
260
|
+
error_code: classifyAunToolErrorCode({
|
|
261
|
+
...(classification ?? {}),
|
|
262
|
+
...(normalizedClassification ?? {}),
|
|
263
|
+
}),
|
|
264
|
+
...(normalizedClassification?.errorDetailCode
|
|
265
|
+
? { detail_code: normalizedClassification.errorDetailCode }
|
|
266
|
+
: {}),
|
|
267
|
+
}),
|
|
247
268
|
...(durationMs !== undefined && { duration_ms: durationMs }),
|
|
248
269
|
...(descText !== undefined && { text: descText }),
|
|
249
|
-
|
|
270
|
+
...(assistantUuid ? { forkAnchor: { assistantMessageId: assistantUuid } } : {}),
|
|
271
|
+
};
|
|
272
|
+
this.recordActivity(item);
|
|
273
|
+
if (this.suppressActivityItems())
|
|
274
|
+
return;
|
|
275
|
+
this.itemsQueue.push(item);
|
|
250
276
|
this.messageTimestamps.push(Date.now());
|
|
251
277
|
this.scheduleFlush();
|
|
252
278
|
}
|
|
@@ -318,6 +344,7 @@ export class IMRenderer {
|
|
|
318
344
|
if (this.suppressActivityItems())
|
|
319
345
|
return;
|
|
320
346
|
this.itemsQueue.push(item);
|
|
347
|
+
this.recordActivity(item);
|
|
321
348
|
this.messageTimestamps.push(Date.now());
|
|
322
349
|
this.scheduleFlush();
|
|
323
350
|
}
|
|
@@ -345,7 +372,9 @@ export class IMRenderer {
|
|
|
345
372
|
}
|
|
346
373
|
if (this.suppressActivityItems() && !force)
|
|
347
374
|
return;
|
|
348
|
-
|
|
375
|
+
const item = { kind: 'notice', text, severity, subtype };
|
|
376
|
+
this.itemsQueue.push(item);
|
|
377
|
+
this.recordActivity(item);
|
|
349
378
|
this.messageTimestamps.push(Date.now());
|
|
350
379
|
this.scheduleFlush();
|
|
351
380
|
}
|
|
@@ -498,7 +527,12 @@ export class IMRenderer {
|
|
|
498
527
|
this.itemsQueue = this.itemsQueue.filter(it => it.kind === 'text');
|
|
499
528
|
if (this.suppressActivityItems())
|
|
500
529
|
return;
|
|
501
|
-
|
|
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 };
|
|
502
536
|
this.sentContent = true;
|
|
503
537
|
try {
|
|
504
538
|
await this.sendPayload(payload);
|
|
@@ -686,8 +720,25 @@ export class IMRenderer {
|
|
|
686
720
|
this.emitProactiveItem(item);
|
|
687
721
|
}
|
|
688
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
|
+
}
|
|
689
736
|
emitProactiveItem(item) {
|
|
690
|
-
|
|
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] };
|
|
691
742
|
this.opts.send(payload).catch(err => {
|
|
692
743
|
logger.debug(`[IMRenderer] proactive send failed: ${err.message}`);
|
|
693
744
|
});
|
|
@@ -712,12 +763,14 @@ export class IMRenderer {
|
|
|
712
763
|
}
|
|
713
764
|
case 'tool_result':
|
|
714
765
|
if (event.isError) {
|
|
766
|
+
const classification = normalizeToolErrorClassification(event);
|
|
715
767
|
return {
|
|
716
768
|
kind: 'tool_result',
|
|
717
769
|
call_id: event.callId || this.synthCallId(),
|
|
718
770
|
name: event.name,
|
|
719
771
|
ok: false,
|
|
720
|
-
error_code: classifyAunToolErrorCode(event),
|
|
772
|
+
error_code: classifyAunToolErrorCode({ ...event, ...classification }),
|
|
773
|
+
...(classification.errorDetailCode ? { detail_code: classification.errorDetailCode } : {}),
|
|
721
774
|
error: event.error || (typeof event.result === 'string' ? event.result : '执行失败'),
|
|
722
775
|
...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
|
|
723
776
|
};
|