evolcore 0.0.19 → 0.0.21
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 +43 -1
- package/README.md +60 -9
- package/bin/install-codex-managed-hooks.mjs +61 -0
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +383 -111
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +405 -137
- package/dist/agents/ecagent-runner.js +174 -63
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +66 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +86 -9
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +26 -11
- package/dist/aun/outbox.js +295 -68
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +1004 -273
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +81 -121
- package/dist/cli/index.js +1 -0
- package/dist/cli/init.js +76 -24
- package/dist/cli/restart-monitor.js +3 -3
- package/dist/cli/task-context.js +46 -0
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +16 -5
- package/dist/config/config-manager.js +226 -24
- package/dist/config/config-operation-service.js +1 -2
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +29 -16
- package/dist/config/lifecycle.js +16 -5
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +161 -12
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +155 -4
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +2 -2
- package/dist/core/bootstrap-service.js +27 -38
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/channel-loader.js +0 -2
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +290 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +279 -80
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +350 -32
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +9 -4
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +10 -0
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +83 -84
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +124 -10
- package/dist/core/message/message-log.js +14 -7
- package/dist/core/message/message-queue.js +206 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +495 -72
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +465 -56
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +205 -13
- package/dist/core/permission/tool-policy.js +673 -62
- package/dist/core/relation/peer-identity.js +18 -0
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +299 -30
- package/dist/core/session/session-renew.js +19 -12
- package/dist/core/session/session-turn-coordinator.js +11 -4
- package/dist/eck/kit-renderer.js +18 -9
- package/dist/index.js +283 -65
- package/dist/ipc.js +374 -24
- package/dist/paths.js +64 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +56 -5
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +3 -2
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +21 -2
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/stats.js +52 -18
- package/dist/utils/welcome.js +2 -2
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/rules/01-overview.md +1 -1
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/schemas/_meta.json +10 -5
- package/kits/schemas/agent-config.schema.10.json +2 -1
- package/kits/schemas/agent-config.schema.11.json +421 -0
- package/kits/schemas/daemon.schema.5.json +135 -0
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +119 -0
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +7 -3
- package/skills/eclink/SKILL.md +2 -0
package/dist/aun/msg/history.js
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { isValidAid } from '../aid/identity.js';
|
|
3
3
|
import { isTransientProtocolMessage, messageLogPath } from '../../core/message/message-log.js';
|
|
4
|
-
import { chatDirPath, readAllJsonlLines, readJsonFile, readLastJsonlLine } from '../../core/session/session-fs-store.js';
|
|
4
|
+
import { chatDirPath, readAllJsonlLines, readJsonFile, readLastJsonlLine, scanChatDirs, scanMetaFiles } from '../../core/session/session-fs-store.js';
|
|
5
|
+
import { tryParseChannelKey } from '../../core/channel-loader.js';
|
|
5
6
|
import { resolvePaths } from '../../paths.js';
|
|
6
7
|
const DEFAULT_LIMIT = 50;
|
|
7
8
|
export const MAX_MSG_HISTORY_LIMIT = 500;
|
|
8
9
|
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
|
|
9
|
-
const CHANNELS = new Set(['aun', 'feishu', 'wechat', 'dingtalk', 'qqbot', 'wecom']);
|
|
10
10
|
export function msgHistory(args, options = {}) {
|
|
11
11
|
if (!isValidAid(args.from)) {
|
|
12
12
|
return error('INVALID_SELF_AID', `无效 self AID: ${args.from}`);
|
|
13
13
|
}
|
|
14
|
-
const channel = args.channel ?? 'aun';
|
|
15
|
-
if (!CHANNELS.has(channel)) {
|
|
16
|
-
return error('INVALID_CHANNEL', `不支持的 channel: ${String(args.channel)}`);
|
|
17
|
-
}
|
|
18
|
-
if (channel === 'aun' && !isValidAid(args.target)) {
|
|
19
|
-
return error('INVALID_TARGET_AID', `无效 target AID: ${args.target}`);
|
|
20
|
-
}
|
|
21
|
-
if (channel !== 'aun' && !isSafeChannelPeerId(args.target)) {
|
|
22
|
-
return error('INVALID_TARGET_ID', `无效 ${channel} 对端 ID`);
|
|
23
|
-
}
|
|
24
|
-
if (args.channelName !== undefined && !isSafeChannelName(args.channelName)) {
|
|
25
|
-
return error('INVALID_CHANNEL_NAME', '无效 channel 实例名');
|
|
26
|
-
}
|
|
27
14
|
const limit = args.limit ?? DEFAULT_LIMIT;
|
|
28
15
|
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_MSG_HISTORY_LIMIT) {
|
|
29
16
|
return error('INVALID_LIMIT', `limit 必须是 1-${MAX_MSG_HISTORY_LIMIT} 的整数`);
|
|
@@ -42,36 +29,167 @@ export function msgHistory(args, options = {}) {
|
|
|
42
29
|
return error('INVALID_TIME_RANGE', 'after 必须早于 before');
|
|
43
30
|
}
|
|
44
31
|
const sessionsDir = options.sessionsDir ?? resolvePaths().sessionsDir;
|
|
45
|
-
|
|
46
|
-
|
|
32
|
+
if (args.channelKey !== undefined || args.peerId !== undefined) {
|
|
33
|
+
return msgHistoryByChannelPeer(args, { sessionsDir, limit, direction });
|
|
34
|
+
}
|
|
35
|
+
return msgHistoryByLegacyRoute(args, { sessionsDir, limit, direction });
|
|
36
|
+
}
|
|
37
|
+
function msgHistoryByLegacyRoute(args, options) {
|
|
38
|
+
const channel = args.channel ?? 'aun';
|
|
39
|
+
const target = args.target;
|
|
40
|
+
if (!isSafeChannelType(channel)) {
|
|
41
|
+
return error('INVALID_CHANNEL', `无效 channel: ${String(args.channel)}`);
|
|
42
|
+
}
|
|
43
|
+
if (!target)
|
|
44
|
+
return error('INVALID_TARGET_ID', '缺少 target ID');
|
|
45
|
+
if (channel === 'aun' && !isValidAid(target)) {
|
|
46
|
+
return error('INVALID_TARGET_AID', `无效 target AID: ${target}`);
|
|
47
|
+
}
|
|
48
|
+
if (channel !== 'aun' && !isSafeChannelPeerId(target)) {
|
|
49
|
+
return error('INVALID_TARGET_ID', `无效 ${channel} 对端 ID`);
|
|
50
|
+
}
|
|
51
|
+
if (args.channelName !== undefined && !isSafeChannelName(args.channelName)) {
|
|
52
|
+
return error('INVALID_CHANNEL_NAME', '无效 channel 实例名');
|
|
53
|
+
}
|
|
54
|
+
const chatDir = chatDirPath(options.sessionsDir, channel, target, args.from, channel === 'aun' && !args.channelName ? undefined : `${channel}#${args.from}#${args.channelName ?? 'main'}`);
|
|
55
|
+
const resolvedSession = resolveLegacySessionFilter(chatDir, args.from, target, channel, args.session ?? 'latest');
|
|
47
56
|
if (!resolvedSession.ok)
|
|
48
57
|
return resolvedSession;
|
|
49
|
-
|
|
58
|
+
return readHistoryScopes({
|
|
59
|
+
args,
|
|
60
|
+
scopes: [{
|
|
61
|
+
chatDir,
|
|
62
|
+
...(resolvedSession.sessionId ? { sessionIds: [resolvedSession.sessionId] } : {}),
|
|
63
|
+
}],
|
|
64
|
+
target,
|
|
65
|
+
limit: options.limit,
|
|
66
|
+
direction: options.direction,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function msgHistoryByChannelPeer(args, options) {
|
|
70
|
+
if (!args.channelKey || !args.peerId) {
|
|
71
|
+
return error('INVALID_CHANNEL_SELECTOR', '--channel-key 与 --peer-id 必须同时提供');
|
|
72
|
+
}
|
|
73
|
+
if (args.target !== undefined || args.channel !== undefined || args.channelName !== undefined) {
|
|
74
|
+
return error('INVALID_CHANNEL_SELECTOR', '使用 --channel-key/--peer-id 时不能同时指定 target、--channel 或 --channel-name');
|
|
75
|
+
}
|
|
76
|
+
if (!isSafeChannelPeerId(args.peerId)) {
|
|
77
|
+
return error('INVALID_PEER_ID', '无效 peer ID');
|
|
78
|
+
}
|
|
79
|
+
const channel = tryParseChannelKey(args.channelKey);
|
|
80
|
+
if (!channel || !isSafeChannelType(channel.type) || !isSafeChannelName(channel.name)) {
|
|
81
|
+
return error('INVALID_CHANNEL_KEY', 'channelKey 必须是完整且安全的 <type>#<selfAID>#<instance>');
|
|
82
|
+
}
|
|
83
|
+
if (channel.type === 'aun' || channel.type === 'daemon') {
|
|
84
|
+
return error('INVALID_CHANNEL_SELECTOR', `${channel.type} history 不支持 --channel-key/--peer-id 定位`);
|
|
85
|
+
}
|
|
86
|
+
if (channel.selfAID !== args.from) {
|
|
87
|
+
return error('SESSION_SCOPE_MISMATCH', 'channelKey 中的 self AID 必须与 from 一致');
|
|
88
|
+
}
|
|
89
|
+
const requested = args.session ?? 'latest';
|
|
90
|
+
if (requested !== 'latest' && requested !== 'all' && !SESSION_ID_RE.test(requested)) {
|
|
91
|
+
return error('INVALID_SESSION_ID', `无效 session ID: ${requested}`);
|
|
92
|
+
}
|
|
93
|
+
const scopesByChatDir = new Map();
|
|
94
|
+
let latest;
|
|
95
|
+
let requestedSessionWasOutOfScope = false;
|
|
96
|
+
for (const entry of scanChatDirs(options.sessionsDir)) {
|
|
97
|
+
if (entry.selfAID !== args.from
|
|
98
|
+
|| entry.channelType !== channel.type
|
|
99
|
+
|| entry.channelKey !== args.channelKey) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const candidates = readPersistedSessions(entry.dirPath);
|
|
103
|
+
for (const session of candidates) {
|
|
104
|
+
if (!session.id || !SESSION_ID_RE.test(session.id))
|
|
105
|
+
continue;
|
|
106
|
+
const belongsToPeer = nonAunSessionBelongsToPeer(session, args.from, args.channelKey, channel.type, args.peerId);
|
|
107
|
+
if (requested === 'all') {
|
|
108
|
+
if (!belongsToPeer)
|
|
109
|
+
continue;
|
|
110
|
+
addScopeSession(scopesByChatDir, entry.dirPath, session.id);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (requested === 'latest') {
|
|
114
|
+
if (!belongsToPeer)
|
|
115
|
+
continue;
|
|
116
|
+
if (!latest || compareSessionRecency(session, entry.dirPath, latest.session, latest.chatDir) > 0) {
|
|
117
|
+
latest = { chatDir: entry.dirPath, session };
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (session.id !== requested)
|
|
122
|
+
continue;
|
|
123
|
+
if (!belongsToPeer) {
|
|
124
|
+
requestedSessionWasOutOfScope = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
addScopeSession(scopesByChatDir, entry.dirPath, session.id);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (requested === 'latest') {
|
|
131
|
+
if (latest)
|
|
132
|
+
addScopeSession(scopesByChatDir, latest.chatDir, latest.session.id);
|
|
133
|
+
else
|
|
134
|
+
return error('LATEST_SESSION_NOT_FOUND', '该 channelKey/peerId 对暂无最新会话');
|
|
135
|
+
}
|
|
136
|
+
const scopes = [...scopesByChatDir.entries()]
|
|
137
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
138
|
+
.map(([chatDir, sessionIds]) => ({ chatDir, sessionIds: [...sessionIds].sort() }));
|
|
139
|
+
if (requested !== 'latest' && requested !== 'all' && scopes.length === 0) {
|
|
140
|
+
if (requestedSessionWasOutOfScope) {
|
|
141
|
+
return error('SESSION_SCOPE_MISMATCH', '指定会话不属于该 channelKey/peerId 对');
|
|
142
|
+
}
|
|
143
|
+
return error('SESSION_NOT_FOUND', `在该 channelKey/peerId 对中找不到会话: ${requested}`);
|
|
144
|
+
}
|
|
145
|
+
return readHistoryScopes({
|
|
146
|
+
args,
|
|
147
|
+
scopes,
|
|
148
|
+
target: args.peerId,
|
|
149
|
+
channelKey: args.channelKey,
|
|
150
|
+
peerId: args.peerId,
|
|
151
|
+
limit: options.limit,
|
|
152
|
+
direction: options.direction,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function readHistoryScopes(input) {
|
|
156
|
+
const entries = [];
|
|
50
157
|
try {
|
|
51
|
-
|
|
158
|
+
input.scopes.forEach((scope, scopeIndex) => {
|
|
159
|
+
const sessionIds = scope.sessionIds ? new Set(scope.sessionIds) : undefined;
|
|
160
|
+
for (const entry of readAllJsonlLines(messageLogPath(scope.chatDir))) {
|
|
161
|
+
entries.push({ entry, sessionIds, scope: scopeIndex });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
52
164
|
}
|
|
53
165
|
catch (cause) {
|
|
54
166
|
return error('HISTORY_READ_FAILED', cause instanceof Error ? cause.message : String(cause));
|
|
55
167
|
}
|
|
56
168
|
const messages = entries
|
|
57
|
-
.filter(entry => isMessageLogEntry(entry))
|
|
58
|
-
.filter(entry => !isTransientProtocolMessage(entry))
|
|
59
|
-
.filter(entry =>
|
|
60
|
-
|
|
61
|
-
.filter(entry =>
|
|
62
|
-
.filter(entry => args.
|
|
63
|
-
.
|
|
64
|
-
.
|
|
169
|
+
.filter(({ entry }) => isMessageLogEntry(entry))
|
|
170
|
+
.filter(({ entry }) => !isTransientProtocolMessage(entry))
|
|
171
|
+
.filter(({ entry, sessionIds }) => sessionIds === undefined
|
|
172
|
+
|| (typeof entry.sessionId === 'string' && sessionIds.has(entry.sessionId)))
|
|
173
|
+
.filter(({ entry }) => input.direction === 'all' || entry.dir === input.direction)
|
|
174
|
+
.filter(({ entry }) => input.args.after === undefined || entry.ts > input.args.after)
|
|
175
|
+
.filter(({ entry }) => input.args.before === undefined || entry.ts < input.args.before)
|
|
176
|
+
.sort((left, right) => left.entry.ts - right.entry.ts || left.scope - right.scope)
|
|
177
|
+
.slice(-input.limit)
|
|
178
|
+
.map(({ entry }) => entry);
|
|
179
|
+
const sessionIds = [...new Set(input.scopes.flatMap(scope => scope.sessionIds ?? []))];
|
|
65
180
|
return {
|
|
66
181
|
ok: true,
|
|
67
|
-
self_aid: args.from,
|
|
68
|
-
target_aid:
|
|
69
|
-
|
|
182
|
+
self_aid: input.args.from,
|
|
183
|
+
target_aid: input.target,
|
|
184
|
+
...(input.channelKey ? { channel_key: input.channelKey } : {}),
|
|
185
|
+
...(input.peerId ? { peer_id: input.peerId } : {}),
|
|
186
|
+
...(sessionIds.length === 1 ? { session_id: sessionIds[0] } : {}),
|
|
187
|
+
...(sessionIds.length > 1 ? { session_ids: sessionIds } : {}),
|
|
70
188
|
count: messages.length,
|
|
71
189
|
messages,
|
|
72
190
|
};
|
|
73
191
|
}
|
|
74
|
-
function
|
|
192
|
+
function resolveLegacySessionFilter(chatDir, selfAid, targetId, channel, requested) {
|
|
75
193
|
if (requested === 'all')
|
|
76
194
|
return { ok: true };
|
|
77
195
|
if (requested === 'latest') {
|
|
@@ -79,7 +197,7 @@ function resolveSessionFilter(chatDir, selfAid, targetAid, channel, requested) {
|
|
|
79
197
|
if (!active?.id) {
|
|
80
198
|
return error('LATEST_SESSION_NOT_FOUND', '该 AID 对暂无最新会话');
|
|
81
199
|
}
|
|
82
|
-
if (!
|
|
200
|
+
if (!legacySessionBelongsToRoute(active, selfAid, targetId, channel)) {
|
|
83
201
|
return error('SESSION_SCOPE_MISMATCH', 'active.json 不属于指定 AID 对');
|
|
84
202
|
}
|
|
85
203
|
return { ok: true, sessionId: active.id };
|
|
@@ -89,7 +207,7 @@ function resolveSessionFilter(chatDir, selfAid, targetAid, channel, requested) {
|
|
|
89
207
|
}
|
|
90
208
|
const active = readJsonFile(path.join(chatDir, 'active.json'));
|
|
91
209
|
if (active?.id === requested) {
|
|
92
|
-
if (!
|
|
210
|
+
if (!legacySessionBelongsToRoute(active, selfAid, targetId, channel)) {
|
|
93
211
|
return error('SESSION_SCOPE_MISMATCH', '指定会话不属于该 AID 对');
|
|
94
212
|
}
|
|
95
213
|
return { ok: true, sessionId: requested };
|
|
@@ -101,23 +219,82 @@ function resolveSessionFilter(chatDir, selfAid, targetAid, channel, requested) {
|
|
|
101
219
|
const session = readLastJsonlLine(sessionPath);
|
|
102
220
|
if (!session || session.id !== requested)
|
|
103
221
|
continue;
|
|
104
|
-
if (!
|
|
222
|
+
if (!legacySessionBelongsToRoute(session, selfAid, targetId, channel)) {
|
|
105
223
|
return error('SESSION_SCOPE_MISMATCH', '指定会话不属于该 AID 对');
|
|
106
224
|
}
|
|
107
225
|
return { ok: true, sessionId: requested };
|
|
108
226
|
}
|
|
109
227
|
return error('SESSION_NOT_FOUND', `在该 AID 对中找不到会话: ${requested}`);
|
|
110
228
|
}
|
|
111
|
-
function
|
|
229
|
+
function legacySessionBelongsToRoute(session, selfAid, targetId, channel) {
|
|
112
230
|
if (session.channelType && session.channelType !== channel)
|
|
113
231
|
return false;
|
|
114
|
-
if (session.channelId && session.channelId !==
|
|
232
|
+
if (session.channelId && session.channelId !== targetId)
|
|
115
233
|
return false;
|
|
116
234
|
const storedSelfAid = session.selfAID ?? session.selfId;
|
|
117
235
|
if (storedSelfAid && storedSelfAid !== selfAid)
|
|
118
236
|
return false;
|
|
119
237
|
return true;
|
|
120
238
|
}
|
|
239
|
+
function nonAunSessionBelongsToPeer(session, selfAid, channelKey, channelType, peerId) {
|
|
240
|
+
if (session.channelType !== channelType)
|
|
241
|
+
return false;
|
|
242
|
+
const storedSelfAid = session.selfAID ?? session.selfId;
|
|
243
|
+
if (storedSelfAid !== selfAid)
|
|
244
|
+
return false;
|
|
245
|
+
// The canonical chat directory is the authoritative channel-instance
|
|
246
|
+
// binding. Older metadata may retain a pre-migration `channel` key (for
|
|
247
|
+
// example `...#feishu` after the directory moved to `...#main`); only an
|
|
248
|
+
// explicitly persisted metadata.channelKey can contradict the directory.
|
|
249
|
+
if (session.metadata?.channelKey !== undefined && session.metadata.channelKey !== channelKey)
|
|
250
|
+
return false;
|
|
251
|
+
return session.metadata?.peerId === peerId;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* `active.json` is only a cache. Session JSONL files are the authoritative
|
|
255
|
+
* history index, including archived and topic sessions, so selector reads
|
|
256
|
+
* inspect all three locations before deciding which message IDs are in scope.
|
|
257
|
+
*/
|
|
258
|
+
function readPersistedSessions(chatDir) {
|
|
259
|
+
const byId = new Map();
|
|
260
|
+
const add = (session) => {
|
|
261
|
+
if (!session?.id || !SESSION_ID_RE.test(session.id))
|
|
262
|
+
return;
|
|
263
|
+
const current = byId.get(session.id);
|
|
264
|
+
if (!current || compareSessionRecency(session, chatDir, current, chatDir) > 0) {
|
|
265
|
+
byId.set(session.id, session);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
add(readJsonFile(path.join(chatDir, 'active.json')));
|
|
269
|
+
for (const metadataDir of [chatDir, path.join(chatDir, '_threads')]) {
|
|
270
|
+
for (const fileName of scanMetaFiles(metadataDir)) {
|
|
271
|
+
add(readLastJsonlLine(path.join(metadataDir, fileName)));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return [...byId.values()];
|
|
275
|
+
}
|
|
276
|
+
function addScopeSession(scopes, chatDir, sessionId) {
|
|
277
|
+
const sessionIds = scopes.get(chatDir) ?? new Set();
|
|
278
|
+
sessionIds.add(sessionId);
|
|
279
|
+
scopes.set(chatDir, sessionIds);
|
|
280
|
+
}
|
|
281
|
+
function compareSessionRecency(left, leftChatDir, right, rightChatDir) {
|
|
282
|
+
const leftTime = Number.isFinite(left.updatedAt) ? left.updatedAt : left.createdAt ?? 0;
|
|
283
|
+
const rightTime = Number.isFinite(right.updatedAt) ? right.updatedAt : right.createdAt ?? 0;
|
|
284
|
+
if (leftTime !== rightTime)
|
|
285
|
+
return leftTime - rightTime;
|
|
286
|
+
const leftId = left.id ?? '';
|
|
287
|
+
const rightId = right.id ?? '';
|
|
288
|
+
if (leftId !== rightId)
|
|
289
|
+
return leftId.localeCompare(rightId);
|
|
290
|
+
return leftChatDir.localeCompare(rightChatDir);
|
|
291
|
+
}
|
|
292
|
+
function isSafeChannelType(value) {
|
|
293
|
+
return typeof value === 'string'
|
|
294
|
+
&& value.trim().length > 0
|
|
295
|
+
&& value.length <= 128
|
|
296
|
+
&& !/[\u0000-\u001f\u007f#\/\\]/.test(value);
|
|
297
|
+
}
|
|
121
298
|
function isSafeChannelPeerId(value) {
|
|
122
299
|
return typeof value === 'string'
|
|
123
300
|
&& value.trim().length > 0
|
|
@@ -5,6 +5,7 @@ import { msgOnline } from './p2p.js';
|
|
|
5
5
|
import { resolveRoot } from '../../paths.js';
|
|
6
6
|
import { isExplicitGroupId } from '../group-identity.js';
|
|
7
7
|
import { isValidAid } from '../aid/validation.js';
|
|
8
|
+
import { tryParseChannelKey } from '../../core/channel-loader.js';
|
|
8
9
|
import { downloadGroupFsBytes } from '../group-fs-download.js';
|
|
9
10
|
import { groupBan, groupBanlist, groupCreate, groupDissolve, groupInfo, groupInvite, groupJoin, groupKick, groupLeave, groupList, groupMembers, groupOnline, groupPull, groupAck, groupResume, groupRules, groupRulesFileGet, groupRulesFilePublish, groupRulesFileSet, groupSetRole, groupSuspend, groupTransferOwner, groupUnban, groupUpdate, groupUpdateRules, } from './group.js';
|
|
10
11
|
/** Rules uploaded by a managed task are carried as bounded UTF-8 content. */
|
|
@@ -415,7 +416,7 @@ export class ManagedMsgOperationError extends Error {
|
|
|
415
416
|
}
|
|
416
417
|
}
|
|
417
418
|
const HISTORY_VALUE_FLAGS = new Set([
|
|
418
|
-
'--session', '--limit', '--before', '--after', '--direction', '--format',
|
|
419
|
+
'--session', '--limit', '--before', '--after', '--direction', '--format', '--channel-key', '--peer-id',
|
|
419
420
|
]);
|
|
420
421
|
function failMsg(code, reason) {
|
|
421
422
|
return { ok: false, code, reason };
|
|
@@ -431,7 +432,8 @@ function parseTime(value) {
|
|
|
431
432
|
const timestamp = Date.parse(trimmed);
|
|
432
433
|
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
433
434
|
}
|
|
434
|
-
/** Parse the daemon-owned
|
|
435
|
+
/** Parse the daemon-owned msg read grammar. AUN keeps positional peer AIDs;
|
|
436
|
+
* non-AUN history is selected by its canonical channel key and native peer ID. */
|
|
435
437
|
export function resolveManagedMsgOperation(argv) {
|
|
436
438
|
const raw = argv[0] === 'msg' ? argv.slice(1) : argv;
|
|
437
439
|
const kind = raw[0];
|
|
@@ -439,12 +441,12 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
439
441
|
return failMsg('NOT_ALLOWED', `unsupported managed msg command: ${kind || '<missing>'}`);
|
|
440
442
|
}
|
|
441
443
|
const from = String(raw[1] || '').replace(/^@/, '');
|
|
442
|
-
const target = String(raw[2] || '').replace(/^@/, '');
|
|
443
444
|
if (!isValidAid(from))
|
|
444
445
|
return failMsg('INVALID_AID', `invalid from AID: ${from || '<missing>'}`);
|
|
445
|
-
if (!isValidAid(target))
|
|
446
|
-
return failMsg('INVALID_AID', `invalid target AID: ${target || '<missing>'}`);
|
|
447
446
|
if (kind === 'online') {
|
|
447
|
+
const target = String(raw[2] || '').replace(/^@/, '');
|
|
448
|
+
if (!isValidAid(target))
|
|
449
|
+
return failMsg('INVALID_AID', `invalid target AID: ${target || '<missing>'}`);
|
|
448
450
|
const allowed = new Set(['--format']);
|
|
449
451
|
let format;
|
|
450
452
|
for (let index = 3; index < raw.length; index++) {
|
|
@@ -470,9 +472,14 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
470
472
|
},
|
|
471
473
|
};
|
|
472
474
|
}
|
|
475
|
+
const positionals = [];
|
|
473
476
|
const values = new Map();
|
|
474
|
-
for (let index =
|
|
477
|
+
for (let index = 2; index < raw.length; index++) {
|
|
475
478
|
const flag = raw[index];
|
|
479
|
+
if (!flag.startsWith('--')) {
|
|
480
|
+
positionals.push(flag);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
476
483
|
if (flag === '--aun-path' || flag === '--app' || flag === '--channel' || flag === '--channel-name') {
|
|
477
484
|
return failMsg('NOT_ALLOWED', `${flag} is not allowed for managed msg history`);
|
|
478
485
|
}
|
|
@@ -485,6 +492,29 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
485
492
|
return failMsg('INVALID_ARGUMENT', `${flag} requires a value`);
|
|
486
493
|
values.set(flag, value);
|
|
487
494
|
}
|
|
495
|
+
if (positionals.length > 1)
|
|
496
|
+
return failMsg('INVALID_ARGUMENT', 'managed msg history accepts at most one positional target');
|
|
497
|
+
const channelKey = values.get('--channel-key');
|
|
498
|
+
const peerId = values.get('--peer-id');
|
|
499
|
+
if ((channelKey === undefined) !== (peerId === undefined)) {
|
|
500
|
+
return failMsg('INVALID_ARGUMENT', '--channel-key and --peer-id must be provided together');
|
|
501
|
+
}
|
|
502
|
+
if (channelKey !== undefined) {
|
|
503
|
+
if (positionals.length !== 0) {
|
|
504
|
+
return failMsg('INVALID_ARGUMENT', '--channel-key/--peer-id cannot be combined with a positional target');
|
|
505
|
+
}
|
|
506
|
+
const parsedChannelKey = tryParseChannelKey(channelKey);
|
|
507
|
+
if (!parsedChannelKey || parsedChannelKey.selfAID !== from || parsedChannelKey.type === 'aun' || parsedChannelKey.type === 'daemon') {
|
|
508
|
+
return failMsg('INVALID_ARGUMENT', '--channel-key must be a non-AUN channel key belonging to from AID');
|
|
509
|
+
}
|
|
510
|
+
if (!peerId?.trim())
|
|
511
|
+
return failMsg('INVALID_ARGUMENT', '--peer-id must be non-empty');
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
const target = String(positionals[0] || '').replace(/^@/, '');
|
|
515
|
+
if (!isValidAid(target))
|
|
516
|
+
return failMsg('INVALID_AID', `invalid target AID: ${target || '<missing>'}`);
|
|
517
|
+
}
|
|
488
518
|
const format = values.get('--format');
|
|
489
519
|
if (format !== undefined && format !== 'text' && format !== 'json') {
|
|
490
520
|
return failMsg('INVALID_ARGUMENT', '--format only supports text|json');
|
|
@@ -509,14 +539,31 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
509
539
|
if (before !== undefined && after !== undefined && after >= before) {
|
|
510
540
|
return failMsg('INVALID_ARGUMENT', '--after must be earlier than --before');
|
|
511
541
|
}
|
|
542
|
+
const target = channelKey === undefined ? String(positionals[0] || '').replace(/^@/, '') : undefined;
|
|
543
|
+
const canonicalArgv = ['msg', kind, from];
|
|
544
|
+
if (target !== undefined)
|
|
545
|
+
canonicalArgv.push(target);
|
|
546
|
+
else
|
|
547
|
+
canonicalArgv.push('--channel-key', channelKey, '--peer-id', peerId);
|
|
548
|
+
if (values.has('--session'))
|
|
549
|
+
canonicalArgv.push('--session', values.get('--session'));
|
|
550
|
+
if (values.has('--limit'))
|
|
551
|
+
canonicalArgv.push('--limit', values.get('--limit'));
|
|
552
|
+
if (values.has('--before'))
|
|
553
|
+
canonicalArgv.push('--before', values.get('--before'));
|
|
554
|
+
if (values.has('--after'))
|
|
555
|
+
canonicalArgv.push('--after', values.get('--after'));
|
|
556
|
+
if (values.has('--direction'))
|
|
557
|
+
canonicalArgv.push('--direction', values.get('--direction'));
|
|
512
558
|
return {
|
|
513
559
|
ok: true,
|
|
514
560
|
command: {
|
|
515
561
|
kind,
|
|
516
562
|
operationId: 'ec.msg.history',
|
|
517
|
-
canonicalArgv
|
|
563
|
+
canonicalArgv,
|
|
518
564
|
from,
|
|
519
|
-
target,
|
|
565
|
+
...(target !== undefined ? { target } : {}),
|
|
566
|
+
...(channelKey !== undefined ? { channelKey, peerId } : {}),
|
|
520
567
|
session: values.get('--session'),
|
|
521
568
|
limit,
|
|
522
569
|
before,
|
|
@@ -531,7 +578,9 @@ export async function executeManagedMsgOperation(command) {
|
|
|
531
578
|
? msgHistory({
|
|
532
579
|
from: command.from,
|
|
533
580
|
target: command.target,
|
|
534
|
-
|
|
581
|
+
...(command.channelKey
|
|
582
|
+
? { channelKey: command.channelKey, peerId: command.peerId }
|
|
583
|
+
: { target: command.target, channel: 'aun' }),
|
|
535
584
|
session: command.session,
|
|
536
585
|
limit: command.limit,
|
|
537
586
|
before: command.before,
|
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -214,6 +214,11 @@ async function appendMsgSendOutboundLog(args, result, opts) {
|
|
|
214
214
|
}
|
|
215
215
|
export async function msgSend(args) {
|
|
216
216
|
const runtimeContext = await readBestTaskRuntimeContext();
|
|
217
|
+
// Contact admission is a daemon-owned policy for managed Agent tasks. A
|
|
218
|
+
// non-managed CLI caller may use any valid AUN sender identity and must not
|
|
219
|
+
// require an unrelated local Agent config (for example codex.agentid.cn).
|
|
220
|
+
// Managed sends are forced through tryDaemonMsgSend(), where the daemon
|
|
221
|
+
// checks the authoritative Contact Book before opening the AUN route.
|
|
217
222
|
let conn;
|
|
218
223
|
try {
|
|
219
224
|
let payload;
|
|
@@ -289,24 +294,34 @@ export async function msgSend(args) {
|
|
|
289
294
|
const encrypt = resolveAunMessageEncrypt(args.encrypt);
|
|
290
295
|
const sendParams = { to: args.to, payload, encrypt };
|
|
291
296
|
const result = await conn.call('message.send', sendParams);
|
|
297
|
+
const messageId = typeof result?.message_id === 'string' && result.message_id.trim()
|
|
298
|
+
? result.message_id
|
|
299
|
+
: typeof result?.message?.message_id === 'string' && result.message.message_id.trim()
|
|
300
|
+
? result.message.message_id
|
|
301
|
+
: undefined;
|
|
302
|
+
if (!messageId) {
|
|
303
|
+
return {
|
|
304
|
+
ok: false,
|
|
305
|
+
error: 'message.send returned no message_id',
|
|
306
|
+
code: 'MISSING_MESSAGE_ID',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
292
309
|
// 5. 写出方向 jsonl(与 daemon 一致格式,标记 source)
|
|
293
310
|
// source 标记:
|
|
294
311
|
// - 'cli': 用户手动调用 ec msg send
|
|
295
312
|
// - 'msg': agent 在会话中调用 ec msg send
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
catch { }
|
|
313
|
+
try {
|
|
314
|
+
await appendMsgSendOutboundLog(args, { ok: true, message_id: messageId }, {
|
|
315
|
+
payload,
|
|
316
|
+
runtimeContext,
|
|
317
|
+
chatmode,
|
|
318
|
+
encrypt,
|
|
319
|
+
});
|
|
306
320
|
}
|
|
321
|
+
catch { }
|
|
307
322
|
return {
|
|
308
323
|
ok: true,
|
|
309
|
-
message_id:
|
|
324
|
+
message_id: messageId,
|
|
310
325
|
seq: result?.seq,
|
|
311
326
|
timestamp: result?.timestamp,
|
|
312
327
|
status: result?.status,
|