evolcore 0.0.39 → 0.0.40
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 +49 -0
- package/README.md +31 -20
- package/bin/ec-safe-output.js +14 -2
- package/dist/agents/baseagent-availability.js +1 -0
- package/dist/agents/claude-runner.js +32 -46
- package/dist/agents/codex-runner.js +51 -27
- package/dist/agents/ecagent-runner.js +18 -7
- package/dist/agents/gemini-runner.js +1 -2
- package/dist/agents/runner-types.js +28 -6
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +23 -98
- package/dist/aun/aid/ownership-operation.js +1 -3
- package/dist/aun/group/invite-service.js +6 -0
- package/dist/aun/msg/p2p.js +8 -12
- package/dist/channels/aun.js +66 -48
- package/dist/channels/dingtalk.js +8 -2
- package/dist/channels/feishu.js +6 -1
- package/dist/channels/wecom.js +7 -3
- package/dist/cli/agent-create.js +23 -4
- package/dist/cli/aid-bind-command.js +4 -1
- package/dist/cli/daemon-commands.js +242 -17
- package/dist/cli/index.js +25 -13
- package/dist/cli/memory-command.js +33 -14
- package/dist/cli/restart-monitor.js +35 -13
- package/dist/cli/task-context.js +1 -0
- package/dist/config/config-field-policy.js +1 -0
- package/dist/contracts/ecweb-service-protocol.js +44 -0
- package/dist/core/agent-runtime.js +6 -0
- package/dist/core/auth/agent-delegation.js +7 -4
- package/dist/core/auth/auth-gateway.js +3 -1
- package/dist/core/auth/authorization-audit.js +5 -1
- package/dist/core/auth/debug-context.js +5 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/command-handler.js +7 -1
- package/dist/core/command/menu-catalog.js +48 -12
- package/dist/core/command/menu-handler.js +72 -4
- package/dist/core/command/menu-protocol.js +20 -1
- package/dist/core/command/slash-gate.js +17 -3
- package/dist/core/command/slash-handler.js +90 -7
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/interaction-router.js +2 -1
- package/dist/core/ipc-registration.js +26 -7
- package/dist/core/memory/batch-meta.js +2 -0
- package/dist/core/memory/consolidation-context.js +42 -51
- package/dist/core/memory/consolidation-ops-records.js +39 -7
- package/dist/core/memory/consolidation-trigger.js +71 -64
- package/dist/core/memory/consolidation.js +226 -20
- package/dist/core/memory/memory-writer.js +160 -34
- package/dist/core/memory/relation-idle-monitor.js +287 -0
- package/dist/core/memory/relation-sweep.js +206 -0
- package/dist/core/message/aun-ipc-sender.js +5 -0
- package/dist/core/message/message-bridge.js +64 -55
- package/dist/core/message/message-log.js +17 -0
- package/dist/core/message/message-queue.js +6 -0
- package/dist/core/message/outbound.js +70 -0
- package/dist/core/message/peer-mode.js +7 -2
- package/dist/core/message/response-engine.js +341 -73
- package/dist/core/ownership/owner-resolver.js +384 -178
- package/dist/core/permission/ec-command-parser.js +19 -3
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/session/session-manager.js +25 -5
- package/dist/core/system-session/memory.js +3 -18
- package/dist/ecweb/basic-settings-service.js +709 -0
- package/dist/ecweb/providers-service.js +172 -0
- package/dist/index.js +92 -4
- package/dist/ipc.js +78 -11
- package/dist/stats/db.js +33 -0
- package/dist/stats/index.js +2 -2
- package/dist/stats/query.js +246 -0
- package/dist/stats/writer.js +41 -1
- package/dist/trigger/application-service.js +12 -0
- package/dist/trigger/authorization.js +2 -2
- package/dist/trigger/history.js +36 -0
- package/dist/trigger/scheduler.js +3 -0
- package/dist/utils/ecweb-supervisor.js +31 -10
- package/dist/utils/error-utils.js +8 -4
- package/dist/utils/evolcore-version.js +3 -2
- package/dist/utils/host-info.js +52 -0
- package/ecagent/dist/harness/agent-harness.js +4 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/agent.md +1 -1
- package/kits/docs/evolcore/aid.md +29 -12
- package/kits/docs/evolcore/config.md +9 -7
- package/kits/docs/evolcore/memory-architecture.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +28 -7
- package/kits/docs/evolcore/memory-tags.md +1 -1
- package/kits/docs/evolcore/memory.md +1 -1
- package/kits/docs/evolcore/menu-client.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +6 -0
- package/kits/docs/evolcore/msg.md +1 -1
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/04-relation.md +3 -2
- package/kits/schemas/agent-config.schema.13.json +5 -1
- package/kits/templates/memory-consolidation/generalize.md +32 -4
- package/kits/templates/memory-consolidation/notes.md +2 -2
- package/kits/templates/memory-consolidation/promote.md +10 -2
- package/kits/templates/system-fragments/baseagent.md +5 -0
- package/kits/templates/system-fragments/memory.md +12 -3
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
- package/package.json +3 -3
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* relation-sweep.ts — 关系整理链
|
|
3
|
+
*
|
|
4
|
+
* 一个关系空闲 / 兜底到期后,对它依次跑:
|
|
5
|
+
*
|
|
6
|
+
* notes → generalize → promote(仅 owner 关系)→ profile → forget(关系层)
|
|
7
|
+
*
|
|
8
|
+
* 每一步在上一步收尾之后才判定要不要做(notes 新产出的 to-generalize 要被第 2 步看到),
|
|
9
|
+
* 没有候选的步骤跳过,一个候选都没有就一个会话都不起。所有步骤走同一个整理队列,同 agent 串行。
|
|
10
|
+
* 触发来源记为 `relation-idle`,静默不反馈。
|
|
11
|
+
*
|
|
12
|
+
* 链中途收到新入站不中断:兜底本就发生在聊天进行中,主会话只写 pending,链写 consolidated。
|
|
13
|
+
*
|
|
14
|
+
* 群聊 notes 会按发言人用 `--peer` 把记忆写进发言人自己的关系。那些关系不一定有入站
|
|
15
|
+
* (发言人从不私聊),所以链结束时把它们交给 onSweepFinished 的 touchedPeerKeys,
|
|
16
|
+
* 由调用方各自补一轮整理(不再继续传播)。
|
|
17
|
+
*/
|
|
18
|
+
import { logger } from '../../utils/logger.js';
|
|
19
|
+
import { RELATION_SWEEP_ORDER, isRelationModeDue } from './consolidation-trigger.js';
|
|
20
|
+
/** 单步整理会话迟迟不收尾时,链最多等这么久就放弃(整理会话自身 15 分钟超时,留余量)。 */
|
|
21
|
+
const DEFAULT_STEP_WATCHDOG_MS = 30 * 60_000;
|
|
22
|
+
/**
|
|
23
|
+
* 写进会话 metadata 的 relationSweepId 带上步骤序号:`<链 id>#<步骤序号>`。
|
|
24
|
+
* 看门狗放弃的那一步,会话之后才收尾时,回调里的序号对不上当前步骤,直接忽略,
|
|
25
|
+
* 不会被误认成下一步的收尾而让链提前推进。
|
|
26
|
+
*/
|
|
27
|
+
function stepTag(sweepId, step) {
|
|
28
|
+
return `${sweepId}#${step}`;
|
|
29
|
+
}
|
|
30
|
+
function parseStepTag(tag) {
|
|
31
|
+
const idx = tag.lastIndexOf('#');
|
|
32
|
+
if (idx <= 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
const step = Number(tag.slice(idx + 1));
|
|
35
|
+
if (!Number.isInteger(step))
|
|
36
|
+
return undefined;
|
|
37
|
+
return { sweepId: tag.slice(0, idx), step };
|
|
38
|
+
}
|
|
39
|
+
let sweepSeq = 0;
|
|
40
|
+
export class RelationSweepRunner {
|
|
41
|
+
deps;
|
|
42
|
+
sweeps = new Map();
|
|
43
|
+
isModeDue;
|
|
44
|
+
stepWatchdogMs;
|
|
45
|
+
constructor(deps) {
|
|
46
|
+
this.deps = deps;
|
|
47
|
+
this.isModeDue = deps.isModeDue ?? isRelationModeDue;
|
|
48
|
+
this.stepWatchdogMs = deps.stepWatchdogMs ?? DEFAULT_STEP_WATCHDOG_MS;
|
|
49
|
+
}
|
|
50
|
+
/** 开始一条整理链。保证之后恰好调用一次 onSweepFinished。 */
|
|
51
|
+
start(request) {
|
|
52
|
+
const id = `sweep-${Date.now().toString(36)}-${(++sweepSeq).toString(36)}`;
|
|
53
|
+
const state = {
|
|
54
|
+
id,
|
|
55
|
+
request,
|
|
56
|
+
remaining: [...RELATION_SWEEP_ORDER],
|
|
57
|
+
ran: [],
|
|
58
|
+
touched: new Set(),
|
|
59
|
+
step: 0,
|
|
60
|
+
};
|
|
61
|
+
this.sweeps.set(id, state);
|
|
62
|
+
void this.deps.resolveOwner(request.selfAid)
|
|
63
|
+
.catch(() => undefined)
|
|
64
|
+
.then(ownerAid => {
|
|
65
|
+
state.ownerAid = ownerAid;
|
|
66
|
+
return this.advance(state);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** 整理会话收尾回调(来自 finalizer 的 onSessionFinalized)。 */
|
|
70
|
+
onSessionFinalized(info) {
|
|
71
|
+
const state = this.currentStep(info.relationSweepId, info.sessionId);
|
|
72
|
+
if (!state)
|
|
73
|
+
return;
|
|
74
|
+
if (state.request.propagate) {
|
|
75
|
+
for (const peerKey of info.touchedPeerKeys) {
|
|
76
|
+
if (peerKey !== state.request.peerKey)
|
|
77
|
+
state.touched.add(peerKey);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.stepDone(state);
|
|
81
|
+
}
|
|
82
|
+
/** 排队后拉起失败的通知(来自 ConsolidationQueue.setLaunchFailedListener)。 */
|
|
83
|
+
onLaunchFailed(params) {
|
|
84
|
+
const state = this.currentStep(params.relationSweepId);
|
|
85
|
+
if (!state)
|
|
86
|
+
return;
|
|
87
|
+
this.stepDone(state);
|
|
88
|
+
}
|
|
89
|
+
/** 回调对应的是某条链**正在等的那一步**时返回该链;过期步骤、无关会话返回 undefined。 */
|
|
90
|
+
currentStep(tag, sessionId) {
|
|
91
|
+
if (tag) {
|
|
92
|
+
const parsed = parseStepTag(tag);
|
|
93
|
+
if (!parsed)
|
|
94
|
+
return undefined;
|
|
95
|
+
const state = this.sweeps.get(parsed.sweepId);
|
|
96
|
+
if (!state || !state.waiting || state.step !== parsed.step)
|
|
97
|
+
return undefined;
|
|
98
|
+
return state;
|
|
99
|
+
}
|
|
100
|
+
// 收尾时读不到会话 metadata(会话记录已丢失):按会话 ID 认领
|
|
101
|
+
if (!sessionId)
|
|
102
|
+
return undefined;
|
|
103
|
+
for (const state of this.sweeps.values()) {
|
|
104
|
+
if (state.waiting && state.waitingSessionId === sessionId)
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
/** 测试与诊断用:在跑的链数量。 */
|
|
110
|
+
get activeCount() {
|
|
111
|
+
return this.sweeps.size;
|
|
112
|
+
}
|
|
113
|
+
stepDone(state) {
|
|
114
|
+
if (state.watchdog)
|
|
115
|
+
clearTimeout(state.watchdog);
|
|
116
|
+
state.watchdog = undefined;
|
|
117
|
+
state.waiting = undefined;
|
|
118
|
+
state.waitingSessionId = undefined;
|
|
119
|
+
void this.advance(state);
|
|
120
|
+
}
|
|
121
|
+
async advance(state) {
|
|
122
|
+
const { selfAid, peerKey, route } = state.request;
|
|
123
|
+
while (state.remaining.length > 0) {
|
|
124
|
+
const mode = state.remaining.shift();
|
|
125
|
+
const agent = this.deps.getAgent(selfAid);
|
|
126
|
+
const memoryConfig = agent?.config?.memory;
|
|
127
|
+
let due = false;
|
|
128
|
+
try {
|
|
129
|
+
due = this.isModeDue({ mode, selfAid, peerKey, ownerAid: state.ownerAid, config: memoryConfig });
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
logger.warn(`[Memory] 关系整理链判定失败(peerKey=${peerKey},mode=${mode}): ${error?.message}`);
|
|
133
|
+
}
|
|
134
|
+
if (!due)
|
|
135
|
+
continue;
|
|
136
|
+
if (!agent?.baseagent)
|
|
137
|
+
break;
|
|
138
|
+
const taskCtx = {
|
|
139
|
+
selfAid,
|
|
140
|
+
peerKey,
|
|
141
|
+
baseagent: agent.baseagent,
|
|
142
|
+
...(route ? {
|
|
143
|
+
channel: route.channel,
|
|
144
|
+
channelId: route.channelId,
|
|
145
|
+
peerId: route.peerId,
|
|
146
|
+
chatType: route.chatType,
|
|
147
|
+
...(route.channelKey ? { channelKey: route.channelKey } : {}),
|
|
148
|
+
...(route.threadId ? { threadId: route.threadId } : {}),
|
|
149
|
+
} : {}),
|
|
150
|
+
};
|
|
151
|
+
// 先挂上等待标记再入队:会话可能在 enqueue 返回前就收尾,收尾回调要能认出这一步
|
|
152
|
+
const token = ++state.step;
|
|
153
|
+
state.waiting = mode;
|
|
154
|
+
let launched = null;
|
|
155
|
+
try {
|
|
156
|
+
launched = await this.deps.enqueue({
|
|
157
|
+
taskCtx,
|
|
158
|
+
mode,
|
|
159
|
+
triggeredBy: 'relation-idle',
|
|
160
|
+
ownerAid: state.ownerAid,
|
|
161
|
+
forgetAfterDays: memoryConfig?.forgetAfterDays,
|
|
162
|
+
...(mode === 'forget' ? { scopeOverride: 'relation' } : {}),
|
|
163
|
+
relationSweepId: stepTag(state.id, token),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
logger.warn(`[Memory] 关系整理链入队失败(peerKey=${peerKey},mode=${mode}): ${error?.message}`);
|
|
168
|
+
}
|
|
169
|
+
if (state.step !== token || state.waiting !== mode) {
|
|
170
|
+
// enqueue 返回前这一步已经收尾,后续由那次 stepDone 推进
|
|
171
|
+
if (launched)
|
|
172
|
+
state.ran.push(mode);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!launched) {
|
|
176
|
+
// 立即拉起失败:这一步作废,继续下一步
|
|
177
|
+
state.waiting = undefined;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
state.ran.push(mode);
|
|
181
|
+
if (launched.executionSessionId)
|
|
182
|
+
state.waitingSessionId = launched.executionSessionId;
|
|
183
|
+
state.watchdog = setTimeout(() => {
|
|
184
|
+
logger.warn(`[Memory] 关系整理链等待收尾超时,跳过该步(peerKey=${peerKey},mode=${mode})`);
|
|
185
|
+
this.stepDone(state);
|
|
186
|
+
}, this.stepWatchdogMs);
|
|
187
|
+
state.watchdog.unref?.();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this.finish(state);
|
|
191
|
+
}
|
|
192
|
+
finish(state) {
|
|
193
|
+
if (!this.sweeps.delete(state.id))
|
|
194
|
+
return;
|
|
195
|
+
const { selfAid, peerKey } = state.request;
|
|
196
|
+
if (state.ran.length > 0) {
|
|
197
|
+
logger.info(`[Memory] 关系整理链结束(aid=${selfAid},peerKey=${peerKey}):${state.ran.join(' → ')}`);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
this.deps.onSweepFinished({ selfAid, peerKey, ran: state.ran, touchedPeerKeys: [...state.touched] });
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
logger.warn(`[Memory] 关系整理链结束回调失败(peerKey=${peerKey}): ${error?.message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -15,6 +15,7 @@ import { resolveThreadRouteTarget } from '../thread/thread-route.js';
|
|
|
15
15
|
import { isTopicThreadId, parseMainTopicId, safeFormatMainTopicId, sessionLocalTopicId } from '../thread/topic-id.js';
|
|
16
16
|
import { ThreadService } from '../thread/thread-service.js';
|
|
17
17
|
import { getAunRequestRegistry, requestTtlSeconds } from './aun-request-registry.js';
|
|
18
|
+
const TASK_ID_MESSAGE_PAYLOAD_TYPES = new Set(['text', 'link', 'image', 'video', 'voice', 'file']);
|
|
18
19
|
/** payload.debug 只是诊断字段:取不到时照常发送,不带 debug。 */
|
|
19
20
|
async function resolveOutboundDebug(resolver, sessionId) {
|
|
20
21
|
if (!resolver || !sessionId)
|
|
@@ -408,6 +409,10 @@ export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegat
|
|
|
408
409
|
if (!payload) {
|
|
409
410
|
return { ok: false, error: 'message payload is required', code: 'INVALID_PAYLOAD' };
|
|
410
411
|
}
|
|
412
|
+
if (params.rawPayload !== true && runtime?.taskId
|
|
413
|
+
&& typeof payload.type === 'string' && TASK_ID_MESSAGE_PAYLOAD_TYPES.has(payload.type)) {
|
|
414
|
+
payload = { ...payload, task_id: runtime.taskId };
|
|
415
|
+
}
|
|
411
416
|
// 托管 argv 通道(runManagedSend)先前不带 log 元数据:群聊 sendDaemonGroupMsg
|
|
412
417
|
// 自身会写日志,私聊 sendDaemonMsg 只在 args.log 存在时才写,于是同一份
|
|
413
418
|
// `ec msg send` 在私聊下不落 messages.jsonl,话题历史里查不到已发出的消息。
|
|
@@ -19,7 +19,7 @@ import { findContactRequestNote, expirePendingContactRequests, reviewContactRequ
|
|
|
19
19
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
20
20
|
import { PeerIdentityCache, formatPeerKey } from '../relation/peer-identity.js';
|
|
21
21
|
import { ownerResolver } from '../ownership/owner-resolver.js';
|
|
22
|
-
import { isTopicSession, sessionLocalTopicId } from '../thread/topic-id.js';
|
|
22
|
+
import { isMainSession, isTopicSession, sessionLocalTopicId } from '../thread/topic-id.js';
|
|
23
23
|
import { resolveEffective } from '../../config/config-manager.js';
|
|
24
24
|
import { createDecisionInferenceProvider } from '../inference/text-inference.js';
|
|
25
25
|
import { AutoMentionGate } from './auto-mention-gate.js';
|
|
@@ -39,8 +39,7 @@ import { recordCausationSpan } from '../causation/audit.js';
|
|
|
39
39
|
import { renderActionAsText } from '../interaction-router.js';
|
|
40
40
|
import { planApprovalRoute } from '../permission/approval-gateway.js';
|
|
41
41
|
import { guardGroupSlashCommand, parseFullAccessCommand } from '../command/slash-gate.js';
|
|
42
|
-
import { createAgentPolicyAuthorization } from '../role/runtime-policy.js';
|
|
43
|
-
import { auditFullAccessEvent } from '../auth/authorization-audit.js';
|
|
42
|
+
import { createAgentPolicyAuthorization, createSessionFullAccessAuthorization, hasSessionFullAccessOverride } from '../role/runtime-policy.js';
|
|
44
43
|
import { DIAGNOSTIC_AGENT_AID, DiagnosticSessionRouter, parseDiagnosticEvent, isDiagnosticSessionActive } from '../command/diag-service.js';
|
|
45
44
|
import { consumeAdmissionReplyBudget, clearAdmissionReplyBudgets, evaluateInboundAdmissionPreflight, evaluateOrdinaryInboundAdmission, } from './message-admission.js';
|
|
46
45
|
const CONTACT_REJECTION_NOTICE = '[rejection] Not accepting messages from you.';
|
|
@@ -279,6 +278,43 @@ export class MessageBridge {
|
|
|
279
278
|
return { handled: true, status: 'not-first-owner', reply: '仅 Agent 配置中的首个 Owner 可在 AUN 私聊发送 /bind 发起 ownership 绑定。' };
|
|
280
279
|
}
|
|
281
280
|
try {
|
|
281
|
+
const current = await ownerResolver.refresh(selfAid, { force: true });
|
|
282
|
+
if (current.consensusStatus === 'consensus') {
|
|
283
|
+
return current.ownerAid === actorId
|
|
284
|
+
? { handled: true, status: 'already-bound', reply: '协议 Owner 已绑定,无需重复操作。' }
|
|
285
|
+
: {
|
|
286
|
+
handled: true,
|
|
287
|
+
status: 'owner-already-bound',
|
|
288
|
+
reply: `该 Agent 已绑定协议 Owner ${current.ownerAid ?? '(身份未知)'},暂不支持重新绑定。`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const taskStatus = current.task?.status;
|
|
292
|
+
const hasActiveSubmission = taskStatus === 'pending'
|
|
293
|
+
|| (['requesting', 'submitted'].includes(current.requestStatus ?? '')
|
|
294
|
+
&& (!taskStatus || taskStatus === 'completed'));
|
|
295
|
+
const interruptedSubmissionNeedsCheck = current.requestStatus === 'interrupted'
|
|
296
|
+
&& current.consensusStatus === 'single_sided';
|
|
297
|
+
if (hasActiveSubmission || interruptedSubmissionNeedsCheck) {
|
|
298
|
+
const requestedOwner = current.requestedOwnerAid ?? current.task?.owner_aid;
|
|
299
|
+
if (interruptedSubmissionNeedsCheck) {
|
|
300
|
+
return {
|
|
301
|
+
handled: true,
|
|
302
|
+
status: 'ownership-unverified',
|
|
303
|
+
reply: '上一笔协议 Owner 绑定请求状态尚未确认,请先核验状态,暂不重复发起。',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return requestedOwner && requestedOwner !== actorId
|
|
307
|
+
? {
|
|
308
|
+
handled: true,
|
|
309
|
+
status: 'ownership-pending-conflict',
|
|
310
|
+
reply: `已有发往 ${requestedOwner} 的协议 Owner 绑定请求正在处理中,请先完成或核验该请求。`,
|
|
311
|
+
}
|
|
312
|
+
: {
|
|
313
|
+
handled: true,
|
|
314
|
+
status: 'ownership-pending',
|
|
315
|
+
reply: '协议 Owner 绑定请求正在处理中,请在 Evol 中确认;无需重复发送 /bind。',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
282
318
|
const state = await ownerResolver.bind(selfAid, actorId);
|
|
283
319
|
const alreadyBound = state.consensusStatus === 'consensus' && state.ownerAid === actorId;
|
|
284
320
|
if (!alreadyBound && state.task && ['rejected', 'stopped', 'error'].includes(state.task.status)) {
|
|
@@ -1150,9 +1186,9 @@ export class MessageBridge {
|
|
|
1150
1186
|
return;
|
|
1151
1187
|
}
|
|
1152
1188
|
}
|
|
1153
|
-
// /fa is
|
|
1154
|
-
//
|
|
1155
|
-
//
|
|
1189
|
+
// /fa is a persistent current-session control. The slash handler owns
|
|
1190
|
+
// its storage and authorization; the bridge only enforces source and
|
|
1191
|
+
// group boundaries before normal command routing.
|
|
1156
1192
|
const fullAccessCommand = parseFullAccessCommand(content);
|
|
1157
1193
|
if (fullAccessCommand.matched) {
|
|
1158
1194
|
const groupGuard = guardGroupSlashCommand(content, chatType);
|
|
@@ -1160,51 +1196,14 @@ export class MessageBridge {
|
|
|
1160
1196
|
await this.sendSlashControlReply(channelName, msg, sendReply, groupGuard, adapter);
|
|
1161
1197
|
return;
|
|
1162
1198
|
}
|
|
1163
|
-
const reject = async (text) => {
|
|
1164
|
-
logger.warn(`[MessageBridge] fullaccess command rejected: channel=${channelName} `
|
|
1165
|
-
+ `actor=${authSubject.principalId ?? authSubject.actorId ?? '<none>'} reason=${text}`);
|
|
1166
|
-
auditFullAccessEvent({
|
|
1167
|
-
event: 'fullaccess.command.rejected',
|
|
1168
|
-
source: 'fullaccess-command',
|
|
1169
|
-
decision: 'deny',
|
|
1170
|
-
actorId: authSubject.principalId ?? authSubject.actorId,
|
|
1171
|
-
processRole: authSubject.processRole,
|
|
1172
|
-
messageId: msg.messageId,
|
|
1173
|
-
reason: text,
|
|
1174
|
-
});
|
|
1175
|
-
await sendReply(msg.channelId, `❌ ${text}`, msg.replyContext);
|
|
1176
|
-
};
|
|
1177
1199
|
if (msg.source !== undefined && msg.source !== 'user') {
|
|
1178
|
-
await
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
if (!fullAccessCommand.prompt) {
|
|
1182
|
-
await reject('用法:/fa <提示词> 或 /fullaccess <提示词>');
|
|
1200
|
+
await sendReply(msg.channelId, '❌ /fa 只能由用户直接发送', msg.replyContext);
|
|
1183
1201
|
return;
|
|
1184
1202
|
}
|
|
1185
|
-
if (
|
|
1186
|
-
await
|
|
1203
|
+
if (fullAccessCommand.action === 'invalid') {
|
|
1204
|
+
await sendReply(msg.channelId, '❌ 用法:/fa、/fa on 或 /fa off', msg.replyContext);
|
|
1187
1205
|
return;
|
|
1188
1206
|
}
|
|
1189
|
-
if (!msg.messageLogContent)
|
|
1190
|
-
msg.messageLogContent = content;
|
|
1191
|
-
content = fullAccessCommand.prompt;
|
|
1192
|
-
executionPermissionOverride = {
|
|
1193
|
-
permissionMode: 'fullaccess',
|
|
1194
|
-
processRole: 'fullaccess-run',
|
|
1195
|
-
dataScope: 'daemon',
|
|
1196
|
-
source: 'fullaccess-command',
|
|
1197
|
-
authorizedBy: authSubject.principalId,
|
|
1198
|
-
};
|
|
1199
|
-
logger.warn(`[MessageBridge] fullaccess command accepted: channel=${channelName} `
|
|
1200
|
-
+ `actor=${authSubject.principalId} message=${msg.messageId ?? '<none>'}`);
|
|
1201
|
-
auditFullAccessEvent({
|
|
1202
|
-
event: 'fullaccess.command.accepted',
|
|
1203
|
-
source: 'fullaccess-command',
|
|
1204
|
-
actorId: authSubject.principalId,
|
|
1205
|
-
processRole: authSubject.processRole,
|
|
1206
|
-
messageId: msg.messageId,
|
|
1207
|
-
});
|
|
1208
1207
|
}
|
|
1209
1208
|
// 0. 仅阻止协议遥测进入 session / model / messages.jsonl;协议本身已在
|
|
1210
1209
|
// channel/main/events/AUN/handoff 专用链路中处理和记录。
|
|
@@ -1248,6 +1247,7 @@ export class MessageBridge {
|
|
|
1248
1247
|
// 2. 命令快速路径(去除引用前缀后检查,兼容话题中引用上文的情况)
|
|
1249
1248
|
const contentForCmd = content.replace(/^(>[^\n]*\n)+\n?/, '').trim();
|
|
1250
1249
|
const cmdContent = contentForCmd || content;
|
|
1250
|
+
const parsedFullAccessControl = parseFullAccessCommand(cmdContent);
|
|
1251
1251
|
let autoMode = false;
|
|
1252
1252
|
if (resolvedChannelType === 'aun' && chatType === 'group' && selfAid && msg.source !== 'handoff') {
|
|
1253
1253
|
try {
|
|
@@ -1281,7 +1281,10 @@ export class MessageBridge {
|
|
|
1281
1281
|
// A prompt such as `/fa /status` must not be re-routed as the
|
|
1282
1282
|
// ordinary `/status` quick command after the prefix is removed.
|
|
1283
1283
|
&& !executionPermissionOverride
|
|
1284
|
-
&& !
|
|
1284
|
+
&& (!parsedFullAccessControl.matched
|
|
1285
|
+
|| parsedFullAccessControl.action === 'status'
|
|
1286
|
+
|| parsedFullAccessControl.action === 'on'
|
|
1287
|
+
|| parsedFullAccessControl.action === 'off')
|
|
1285
1288
|
&& this.cmdHandler.isCommand(cmdContent);
|
|
1286
1289
|
if (isCmd && /^\/new(?:\s|$)/i.test(cmdContent)) {
|
|
1287
1290
|
// /new changes the active main-session boundary. Serialize it with
|
|
@@ -1333,14 +1336,6 @@ export class MessageBridge {
|
|
|
1333
1336
|
return sendReply(msg.channelId, text, msg.replyContext);
|
|
1334
1337
|
}, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext, msg.messageId, msg.selfAID, authSubject))
|
|
1335
1338
|
return;
|
|
1336
|
-
// Commands retain their normal route. Only authenticated direct model
|
|
1337
|
-
// input can obtain the Agent policy's per-call authorization.
|
|
1338
|
-
if (!executionPermissionOverride && selfAid && owningAgent?.aid === selfAid) {
|
|
1339
|
-
executionPermissionOverride = createAgentPolicyAuthorization({
|
|
1340
|
-
agentAid: selfAid, actor: roleDetail.actor, chatType,
|
|
1341
|
-
source: msg.source, peerType: msg.peerType,
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
1339
|
// 3. session 解析(使用 Channel 层填充的 chatType)
|
|
1345
1340
|
if (!(await this.canCreateThreadSession(channelName, msg, chatType))) {
|
|
1346
1341
|
// 静默丢弃:绝不向群里注入回复。
|
|
@@ -1481,6 +1476,20 @@ export class MessageBridge {
|
|
|
1481
1476
|
causation: inboundCausation,
|
|
1482
1477
|
});
|
|
1483
1478
|
session = renewResult.session;
|
|
1479
|
+
if (!executionPermissionOverride && selfAid && owningAgent?.aid === selfAid) {
|
|
1480
|
+
executionPermissionOverride = createSessionFullAccessAuthorization({
|
|
1481
|
+
session,
|
|
1482
|
+
actor: roleDetail.actor,
|
|
1483
|
+
source: msg.source,
|
|
1484
|
+
peerType: msg.peerType,
|
|
1485
|
+
});
|
|
1486
|
+
if (!executionPermissionOverride && isMainSession(session) && !hasSessionFullAccessOverride(session)) {
|
|
1487
|
+
executionPermissionOverride = createAgentPolicyAuthorization({
|
|
1488
|
+
agentAid: selfAid, actor: roleDetail.actor, chatType,
|
|
1489
|
+
source: msg.source, peerType: msg.peerType,
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1484
1493
|
if ('renewed' in renewResult && renewResult.renewed) {
|
|
1485
1494
|
const notice = '已自动切换到新会话';
|
|
1486
1495
|
try {
|
|
@@ -295,6 +295,20 @@ export function findMessageLogOperationMessageId(chatDir, operationId) {
|
|
|
295
295
|
export function resolveChatDir(sessionsDir, channelType, channelId, selfAID) {
|
|
296
296
|
return chatDirPath(sessionsDir, channelType, channelId, selfAID);
|
|
297
297
|
}
|
|
298
|
+
let inboundLogListener;
|
|
299
|
+
export function setInboundLogListener(listener) {
|
|
300
|
+
inboundLogListener = listener;
|
|
301
|
+
}
|
|
302
|
+
function notifyInbound(chatDir, entry) {
|
|
303
|
+
if (entry.dir !== 'in' || !inboundLogListener)
|
|
304
|
+
return;
|
|
305
|
+
try {
|
|
306
|
+
inboundLogListener(chatDir, entry);
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
logger.warn(`[MessageLog] Inbound listener failed: ${e}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
298
312
|
/** Append conversation history only. Protocol telemetry stays in its dedicated logs/stores. */
|
|
299
313
|
export function appendMessageLog(chatDir, entry) {
|
|
300
314
|
if (isTransientProtocolMessage(entry))
|
|
@@ -311,7 +325,9 @@ export function appendMessageLog(chatDir, entry) {
|
|
|
311
325
|
}
|
|
312
326
|
catch (e) {
|
|
313
327
|
logger.warn(`[MessageLog] Failed to write message log: ${e}`);
|
|
328
|
+
return;
|
|
314
329
|
}
|
|
330
|
+
notifyInbound(chatDir, entry);
|
|
315
331
|
}
|
|
316
332
|
/** Strict conversation-history append used by handoff reply binding. */
|
|
317
333
|
export function appendMessageLogStrict(chatDir, entry, durable = false) {
|
|
@@ -323,6 +339,7 @@ export function appendMessageLogStrict(chatDir, entry, durable = false) {
|
|
|
323
339
|
(durable ? appendJsonlDurable : appendJsonl)(messageLogPath(chatDir), entry);
|
|
324
340
|
if (entry.dir === 'in')
|
|
325
341
|
rememberMessageId(chatDir, entry.msgId);
|
|
342
|
+
notifyInbound(chatDir, entry);
|
|
326
343
|
}
|
|
327
344
|
export function buildInboundEntry(opts) {
|
|
328
345
|
const ts = opts.timestamp || Date.now();
|
|
@@ -719,6 +719,7 @@ export class MessageQueue {
|
|
|
719
719
|
sessionId: sessionKey,
|
|
720
720
|
reason: 'daemon_restart',
|
|
721
721
|
agentName,
|
|
722
|
+
agentAid: this.activeStates.get(queueKey)?.selfAID,
|
|
722
723
|
taskId: this.activeStates.get(queueKey)?.taskId,
|
|
723
724
|
generation: this.activeStates.get(queueKey)?.generation,
|
|
724
725
|
causation: item.message.causation,
|
|
@@ -1154,6 +1155,7 @@ export class MessageQueue {
|
|
|
1154
1155
|
sessionId: sessionKey,
|
|
1155
1156
|
reason: 'new_message',
|
|
1156
1157
|
agentName: this.processingAgent.get(queueKey),
|
|
1158
|
+
agentAid: this.activeStates.get(queueKey)?.selfAID,
|
|
1157
1159
|
taskId: this.activeStates.get(queueKey)?.taskId,
|
|
1158
1160
|
generation: this.activeStates.get(queueKey)?.generation,
|
|
1159
1161
|
causation: this.activeBatches.get(queueKey)?.message.causation,
|
|
@@ -1755,6 +1757,7 @@ export class MessageQueue {
|
|
|
1755
1757
|
sessionId: sessionKey,
|
|
1756
1758
|
reason,
|
|
1757
1759
|
agentName,
|
|
1760
|
+
agentAid: activeState?.selfAID,
|
|
1758
1761
|
taskId: activeState?.taskId,
|
|
1759
1762
|
generation: activeState?.generation,
|
|
1760
1763
|
causation: active?.message.causation,
|
|
@@ -2043,6 +2046,7 @@ export class MessageQueue {
|
|
|
2043
2046
|
sessionId: sessionKey,
|
|
2044
2047
|
reason: 'stop',
|
|
2045
2048
|
agentName: name,
|
|
2049
|
+
agentAid: activeState?.selfAID,
|
|
2046
2050
|
taskId: this.activeStates.get(queueKey)?.taskId,
|
|
2047
2051
|
generation: this.activeStates.get(queueKey)?.generation,
|
|
2048
2052
|
causation: this.activeBatches.get(queueKey)?.message.causation,
|
|
@@ -2317,6 +2321,7 @@ export class MessageQueue {
|
|
|
2317
2321
|
sessionId: sessionKey,
|
|
2318
2322
|
reason: 'new_message',
|
|
2319
2323
|
agentName: targetAgentName,
|
|
2324
|
+
agentAid: this.activeStates.get(targetQueueKey)?.selfAID,
|
|
2320
2325
|
taskId: this.activeStates.get(targetQueueKey)?.taskId,
|
|
2321
2326
|
generation: this.activeStates.get(targetQueueKey)?.generation,
|
|
2322
2327
|
causation: this.activeBatches.get(targetQueueKey)?.message.causation,
|
|
@@ -2336,6 +2341,7 @@ export class MessageQueue {
|
|
|
2336
2341
|
this.eventBus?.publish({
|
|
2337
2342
|
type: 'task:interrupted', sessionId, reason: 'new_message',
|
|
2338
2343
|
agentName: this.processingAgent.get(queueKey),
|
|
2344
|
+
agentAid: this.activeStates.get(queueKey)?.selfAID,
|
|
2339
2345
|
taskId: this.activeStates.get(queueKey)?.taskId,
|
|
2340
2346
|
generation: this.activeStates.get(queueKey)?.generation,
|
|
2341
2347
|
causation: active.message.causation,
|
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
import { isDeliveryTargetForChannel } from './message-utils.js';
|
|
2
2
|
import { readEvolcoreRuntimeFlavor, readEvolcoreStartedAtIso, readInstalledEvolcoreVersion, tagErrorTextWithEvolcoreVersion, } from '../../utils/evolcore-version.js';
|
|
3
|
+
import { getErrorRawText } from '../../utils/error-utils.js';
|
|
4
|
+
function appendRawErrorText(text, raw) {
|
|
5
|
+
if (!raw || text === raw || text.includes(`原因:${raw}`))
|
|
6
|
+
return text;
|
|
7
|
+
return `${text}\n原因:${raw}`;
|
|
8
|
+
}
|
|
9
|
+
function resolveRawErrorText(raw, fallback) {
|
|
10
|
+
return getErrorRawText(raw) || getErrorRawText(fallback);
|
|
11
|
+
}
|
|
12
|
+
/** Ensure every outbound error carries the best available diagnostic text. */
|
|
13
|
+
export function normalizeOutboundErrorPayload(payload) {
|
|
14
|
+
switch (payload.kind) {
|
|
15
|
+
case 'result.error':
|
|
16
|
+
case 'system.error':
|
|
17
|
+
case 'command.error': {
|
|
18
|
+
const raw = resolveRawErrorText(payload.raw, payload.text);
|
|
19
|
+
if (!raw)
|
|
20
|
+
return payload;
|
|
21
|
+
return {
|
|
22
|
+
...payload,
|
|
23
|
+
text: appendRawErrorText(payload.text, raw),
|
|
24
|
+
raw,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
case 'status.error': {
|
|
28
|
+
const metadata = payload.metadata;
|
|
29
|
+
if (!metadata)
|
|
30
|
+
return payload;
|
|
31
|
+
const raw = resolveRawErrorText(metadata.raw, metadata.message);
|
|
32
|
+
if (!raw)
|
|
33
|
+
return payload;
|
|
34
|
+
return {
|
|
35
|
+
...payload,
|
|
36
|
+
metadata: {
|
|
37
|
+
...metadata,
|
|
38
|
+
message: typeof metadata.message === 'string'
|
|
39
|
+
? appendRawErrorText(metadata.message, raw)
|
|
40
|
+
: metadata.message,
|
|
41
|
+
raw,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
case 'status.timeout': {
|
|
46
|
+
const metadata = payload.metadata;
|
|
47
|
+
if (!metadata)
|
|
48
|
+
return payload;
|
|
49
|
+
const message = metadata.message ?? (typeof metadata.totalExecutionMs === 'number'
|
|
50
|
+
? `任务超过总执行时限(${Math.round(metadata.totalExecutionMs / 1000)}秒),已自动中断`
|
|
51
|
+
: typeof metadata.idleSec === 'number' && metadata.idleSec > 0
|
|
52
|
+
? `任务超时(${metadata.idleSec}秒无响应),已自动中断`
|
|
53
|
+
: '任务超时,已自动中断');
|
|
54
|
+
const raw = resolveRawErrorText(metadata.raw, metadata.reason) || message;
|
|
55
|
+
return {
|
|
56
|
+
...payload,
|
|
57
|
+
metadata: {
|
|
58
|
+
...metadata,
|
|
59
|
+
message: appendRawErrorText(message, raw),
|
|
60
|
+
raw,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
return payload;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
3
68
|
/** 出站 payload 摘要(用于 channel-out.log)。 */
|
|
4
69
|
export function summarizeOutboundPayload(payload) {
|
|
5
70
|
if (!payload)
|
|
@@ -18,6 +83,8 @@ export function summarizeOutboundPayload(payload) {
|
|
|
18
83
|
case 'command.error':
|
|
19
84
|
case 'result.error':
|
|
20
85
|
summary.text = payload.text;
|
|
86
|
+
if (payload.raw !== undefined)
|
|
87
|
+
summary.raw = payload.raw;
|
|
21
88
|
break;
|
|
22
89
|
case 'result.file':
|
|
23
90
|
summary.filePath = payload.filePath;
|
|
@@ -26,6 +93,8 @@ export function summarizeOutboundPayload(payload) {
|
|
|
26
93
|
case 'system.error':
|
|
27
94
|
summary.subtype = payload.subtype;
|
|
28
95
|
summary.text = payload.text;
|
|
96
|
+
if (payload.raw !== undefined)
|
|
97
|
+
summary.raw = payload.raw;
|
|
29
98
|
break;
|
|
30
99
|
case 'interaction':
|
|
31
100
|
summary.interactionId = payload.interaction?.id;
|
|
@@ -47,6 +116,7 @@ export function summarizeOutboundPayload(payload) {
|
|
|
47
116
|
}
|
|
48
117
|
/** Add the current EvolCore version marker to user-visible error payloads. */
|
|
49
118
|
export function tagErrorPayloadWithEvolcoreVersion(payload) {
|
|
119
|
+
payload = normalizeOutboundErrorPayload(payload);
|
|
50
120
|
switch (payload.kind) {
|
|
51
121
|
case 'result.error':
|
|
52
122
|
case 'system.error':
|
|
@@ -51,10 +51,15 @@ export function isPrivateChatmodeRoleLocked(field, role) {
|
|
|
51
51
|
* - 人类且非 owner/admin(含角色未知)→ proactive
|
|
52
52
|
* - 人类且 owner/admin → 分层配置(未配置时取出厂默认)
|
|
53
53
|
*/
|
|
54
|
-
export function chatmodeForPeerIdentity(identity, role) {
|
|
54
|
+
export function chatmodeForPeerIdentity(identity, role, configContext = {}) {
|
|
55
55
|
if (identity.isMachine)
|
|
56
56
|
return 'proactive';
|
|
57
|
-
return resolveChatMode({
|
|
57
|
+
return resolveChatMode({
|
|
58
|
+
...configContext,
|
|
59
|
+
role: role ?? undefined,
|
|
60
|
+
chatType: 'private',
|
|
61
|
+
peerType: identity.type ?? 'human',
|
|
62
|
+
});
|
|
58
63
|
}
|
|
59
64
|
export function resolveChatModeForField(params) {
|
|
60
65
|
return resolveChatMode({
|