evolcore 0.0.34 → 0.0.35
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 +22 -0
- package/bin/install-codex-managed-hooks.mjs +26 -3
- package/dist/agents/claude-runner.js +11 -1
- package/dist/agents/codex/diagnostics.js +42 -11
- package/dist/agents/codex-runner.js +27 -3
- package/dist/agents/ecagent-runner.js +17 -7
- package/dist/aun/aid/agentmd.js +13 -2
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/msg/managed-operation.js +16 -0
- package/dist/aun/msg/p2p.js +18 -2
- package/dist/channels/aun.js +113 -7
- package/dist/cli/aun-commands.js +37 -13
- package/dist/cli/daemon-commands.js +10 -4
- package/dist/cli/fs-command.js +1 -1
- package/dist/cli/getmeta-command.js +211 -0
- package/dist/cli/index.js +6 -0
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/memory-command.js +46 -15
- package/dist/cli/thread-command.js +12 -4
- package/dist/config/resolved-config-op.js +25 -8
- package/dist/core/auth/operation-authorizer.js +21 -2
- package/dist/core/channel-runtime.js +1 -1
- package/dist/core/command/command-handler.js +7 -1
- package/dist/core/command/menu-handler.js +2 -0
- package/dist/core/command/topic-slash.js +4 -7
- package/dist/core/event-catalog.js +14 -0
- package/dist/core/ipc-registration.js +11 -1
- package/dist/core/memory/batch-meta.js +1 -0
- package/dist/core/memory/consolidation-context.js +97 -14
- package/dist/core/memory/consolidation-trigger.js +15 -7
- package/dist/core/memory/consolidation.js +55 -0
- package/dist/core/memory/memory-writer.js +20 -4
- package/dist/core/message/aun-ipc-sender.js +19 -1
- package/dist/core/message/message-bridge.js +3 -3
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +17 -0
- package/dist/core/message/response-engine.js +15 -4
- package/dist/core/permission/ec-command-parser.js +76 -40
- package/dist/core/permission/tool-policy.js +32 -2
- package/dist/core/relation/peer-identity.js +87 -12
- package/dist/core/session/session-manager.js +56 -1
- package/dist/core/session/session-title.js +26 -1
- package/dist/core/system-session/memory.js +87 -20
- package/dist/core/thread/command-parser.js +6 -1
- package/dist/core/thread/ipc-operation.js +33 -16
- package/dist/core/thread/thread-operation.js +62 -24
- package/dist/core/thread/thread-service.js +246 -32
- package/dist/utils/cross-platform.js +6 -3
- package/kits/docs/evolcore/fs-architecture.md +5 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +5 -3
- package/kits/docs/evolcore/memory-consolidation.md +1 -2
- package/kits/docs/evolcore/memory-tags.md +1 -1
- package/kits/docs/evolcore/memory.md +2 -2
- package/kits/docs/evolcore/stats.md +1 -1
- package/kits/docs/evolcore/thread-chain.md +25 -9
- package/kits/eck_manifest.auxiliary.json +9 -0
- package/kits/eck_manifest.json +20 -0
- package/kits/eck_manifest.memory.json +20 -0
- package/kits/rules/01-overview.md +3 -3
- package/kits/rules/07-memory-and-thread.md +1 -1
- package/kits/skills/ec-group/SKILL.md +6 -3
- package/kits/skills/thread-ops/SKILL.md +18 -18
- package/kits/templates/memory-consolidation/notes.md +1 -1
- package/kits/templates/memory-consolidation/workflow.md +36 -1
- package/kits/templates/system-fragments/bash-description.md +9 -0
- package/kits/templates/system-fragments/memory.md +2 -2
- package/package.json +1 -1
package/dist/cli/aun-commands.js
CHANGED
|
@@ -259,14 +259,18 @@ async function runManagedMsg(args, formatJson) {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
else {
|
|
262
|
-
|
|
262
|
+
// `--thread` 是话题作用域的读取:目标对端由话题推断,数据是本 agent 自己的
|
|
263
|
+
// 消息日志,因此既不受"当前必须是私聊"限制(群会话里也要能查 A↔C 的话题),
|
|
264
|
+
// 也不受"只能查当前对端"限制。不带 --thread 时保持原有边界。
|
|
265
|
+
const threadScoped = parsed.command.kind === 'history' && !!parsed.command.threadId;
|
|
266
|
+
if (runtime && (runtime.channel !== 'aun' || runtime.chatType !== 'private') && !threadScoped) {
|
|
263
267
|
failManagedGroup(formatJson, 'SCOPE_MISMATCH', 'ec msg history/online 只能读取当前 AUN 私聊关系');
|
|
264
268
|
}
|
|
265
269
|
// owner/admin 可以跨对端读历史(每次查询仍只针对一个对端)。这里只是快速
|
|
266
270
|
// 预检:角色未知时放行交给 daemon 判定,daemon 侧 handleMsgOperation 才是
|
|
267
271
|
// 权威关卡,member 由 currentRelationOnly 约束、visitor 直接拒绝。
|
|
268
272
|
const mayReadCrossPeer = parsed.command.kind === 'history'
|
|
269
|
-
&& (runtime?.peerRole === undefined || isManagementRole(runtime.peerRole));
|
|
273
|
+
&& (threadScoped || runtime?.peerRole === undefined || isManagementRole(runtime.peerRole));
|
|
270
274
|
if (!mayReadCrossPeer && runtime?.channelId && parsed.command.target !== runtime.channelId.replace(/^@/, '')) {
|
|
271
275
|
failManagedGroup(formatJson, 'SCOPE_MISMATCH', `只能读取当前 AUN 私聊对端:${runtime.channelId}`);
|
|
272
276
|
}
|
|
@@ -2065,10 +2069,15 @@ Options:
|
|
|
2065
2069
|
// ==================== Group ====================
|
|
2066
2070
|
const GROUP_FROM_AID_FORMAT_EXAMPLE = 'sender.example.invalid';
|
|
2067
2071
|
const GROUP_AID_FORMAT_EXAMPLE = '12345.example.invalid';
|
|
2068
|
-
function
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
+
function printGroupArgsHint(sub) {
|
|
2073
|
+
if (sub === 'send') {
|
|
2074
|
+
console.error('正确格式: ec group send <发送方AID> <真实群AID> <正文或文件参数>');
|
|
2075
|
+
}
|
|
2076
|
+
else {
|
|
2077
|
+
console.error(`正确格式: ec group ${sub} <发送方AID> <真实群AID> [选项]`);
|
|
2078
|
+
}
|
|
2079
|
+
console.error(`仅格式示例(.example.invalid 是保留的虚构域,不是真实 AID): ec group ${sub} ${GROUP_FROM_AID_FORMAT_EXAMPLE} ${GROUP_AID_FORMAT_EXAMPLE}`);
|
|
2080
|
+
console.error('顺序固定为「先发送方 AID,后群 AID」;查询真实群 AID: ec group list <发送方AID>');
|
|
2072
2081
|
}
|
|
2073
2082
|
function printGroupRulesUsage() {
|
|
2074
2083
|
console.log(`用法:
|
|
@@ -2173,7 +2182,7 @@ Options:
|
|
|
2173
2182
|
无值时默认 5m;Trigger 名:check-<目标threadId>-from-<来源threadId>-<唯一后缀>
|
|
2174
2183
|
取消/调整用返回的 trigger_id(trig_…),show/update/cancel 只认 id
|
|
2175
2184
|
--trigger-prompt <text> 自定义复查 Trigger prompt 中的用户段(仅 --check-every)
|
|
2176
|
-
--mention <...> 发送时 @
|
|
2185
|
+
--mention <...> 发送时 @ 某个成员。单引号包住、逗号分隔多个 aid
|
|
2177
2186
|
(如 --mention 'alice.agentid.pub,bob.agentid.pub'),可多次传入
|
|
2178
2187
|
--mention-all 发送时 @ 所有人
|
|
2179
2188
|
--content-type <mime> 显式覆盖 MIME(仅 --file 模式)
|
|
@@ -2208,13 +2217,28 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
2208
2217
|
console.error('❌ 缺少 <from-aid> 参数');
|
|
2209
2218
|
process.exit(1);
|
|
2210
2219
|
}
|
|
2220
|
+
// 第 1 个位置参数必须是「发送方 AID」。群 ID 与私聊 AID 语法相同,isValidAid
|
|
2221
|
+
// 会放行,随后在加载证书时抛 CERT_NOT_FOUND——这条路径既不说明原因也不给出
|
|
2222
|
+
// 正确写法。因此先用明确的群 ID 形态判定,把"参数写反了"当场指出来。
|
|
2223
|
+
// 例外:invites 是多值标志/邀请列表,第 1 个位置参数不是 from;create/list 的
|
|
2224
|
+
// 形态不同,也在下面各自的用法里处理,这里只拦截"以群 ID 打头"这一种错位。
|
|
2211
2225
|
const { isValidAid } = await import('../aun/aid/index.js');
|
|
2226
|
+
if (isExplicitGroupId(from)) {
|
|
2227
|
+
const second = args[2];
|
|
2228
|
+
console.error(`❌ 第 1 个位置参数 <from-aid> 无效:收到了群 ID: ${from}`);
|
|
2229
|
+
if (second && isValidAid(second) && !isExplicitGroupId(second)) {
|
|
2230
|
+
console.error(`检测到参数顺序写反了:第 2 个参数 ${second} 才是发送方 AID。`);
|
|
2231
|
+
console.error(`请改成: ec group ${sub} ${second} ${from}` + (sub === 'send' ? ' <正文>' : ''));
|
|
2232
|
+
}
|
|
2233
|
+
else {
|
|
2234
|
+
console.error('当前命令缺少发送方 AID,后续参数发生了错位。');
|
|
2235
|
+
}
|
|
2236
|
+
printGroupArgsHint(sub);
|
|
2237
|
+
process.exit(1);
|
|
2238
|
+
}
|
|
2212
2239
|
if (!isValidAid(from)) {
|
|
2213
2240
|
console.error(`❌ 第 1 个位置参数 <from-aid> 无效: ${from}`);
|
|
2214
|
-
|
|
2215
|
-
console.error('检测到这里填写的是群 ID;当前命令缺少发送方 AID,后续参数发生了错位。');
|
|
2216
|
-
}
|
|
2217
|
-
printGroupSendAidHint();
|
|
2241
|
+
printGroupArgsHint(sub);
|
|
2218
2242
|
process.exit(1);
|
|
2219
2243
|
}
|
|
2220
2244
|
if ((process.env.EVOLCORE_SESSION_ID || (sub === 'history' && readDelegationCredential())) && sub !== 'send') {
|
|
@@ -2286,7 +2310,7 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
2286
2310
|
const gid = args[2];
|
|
2287
2311
|
if (!gid) {
|
|
2288
2312
|
console.error('❌ 缺少第 2 个位置参数 <group-aid>');
|
|
2289
|
-
|
|
2313
|
+
printGroupArgsHint(sub);
|
|
2290
2314
|
process.exit(1);
|
|
2291
2315
|
}
|
|
2292
2316
|
try {
|
|
@@ -2294,7 +2318,7 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
2294
2318
|
}
|
|
2295
2319
|
catch (error) {
|
|
2296
2320
|
console.error(`❌ 第 2 个位置参数 <group-aid> 不是有效群 AID: ${gid}: ${error.message}`);
|
|
2297
|
-
|
|
2321
|
+
printGroupArgsHint(sub);
|
|
2298
2322
|
process.exit(1);
|
|
2299
2323
|
}
|
|
2300
2324
|
};
|
|
@@ -25,7 +25,7 @@ import { isNonAgentSessionChannel } from '../core/system-channels.js';
|
|
|
25
25
|
import { printCodeStats } from './code-stats.js';
|
|
26
26
|
import { getProcessStartTime, startTimeMatches } from '../utils/process-introspect.js';
|
|
27
27
|
import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
|
|
28
|
-
import { MAIN_CLI_BIN, RUNTIME_HOME_ENV, WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js';
|
|
28
|
+
import { MAIN_CLI_BIN, RUNTIME_HOME_ENV, TARGET_PACKAGE_ROOT_ENV, WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME, } from '../product.js';
|
|
29
29
|
import { rotateStdoutLog } from '../utils/log-writer.js';
|
|
30
30
|
import { inspectDataMigrationRequirement } from '../core/data-migration.js';
|
|
31
31
|
import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
|
|
@@ -79,7 +79,7 @@ export async function ensureCodexHookBeforeLifecycle(onRepaired) {
|
|
|
79
79
|
});
|
|
80
80
|
if (!usesCodex)
|
|
81
81
|
return true;
|
|
82
|
-
const packageRoot = getPackageRoot();
|
|
82
|
+
const packageRoot = process.env[TARGET_PACKAGE_ROOT_ENV] || getPackageRoot();
|
|
83
83
|
const result = await ensureCodexManagedHook({
|
|
84
84
|
packageRoot,
|
|
85
85
|
repairDecision: state => confirmCodexHookRebind(state, packageRoot),
|
|
@@ -328,6 +328,11 @@ async function launchPackageLifecycle(source, log, options = {}) {
|
|
|
328
328
|
const result = await platform.launchMonitorAndConfirm(args, env, pid => scanInstances().restartMonitors.some(item => item.alive && item.record.pid === pid), pid => scanInstances().restartMonitors.some(item => item.alive && item.record.pid !== pid));
|
|
329
329
|
if (!result.ok && launch.stageDir)
|
|
330
330
|
platform.cleanupMonitorStage(launch.stageDir);
|
|
331
|
+
if (!result.ok) {
|
|
332
|
+
for (const line of (result.stderrTail ?? '').split(/\r?\n/).filter(Boolean).slice(-8)) {
|
|
333
|
+
console.error(` | ${line}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
331
336
|
return { ok: result.ok, ...(!result.ok ? { reason: result.reason } : {}) };
|
|
332
337
|
}
|
|
333
338
|
function requireCompletedDataMigration(root) {
|
|
@@ -3304,12 +3309,13 @@ export async function cmdDiagnose(args = []) {
|
|
|
3304
3309
|
console.log('[diagnose] - Codex managed hook: 跳过(没有 Agent 使用 Codex)');
|
|
3305
3310
|
}
|
|
3306
3311
|
else {
|
|
3307
|
-
const
|
|
3312
|
+
const packageRoot = process.env[TARGET_PACKAGE_ROOT_ENV] || getPackageRoot();
|
|
3313
|
+
const hook = inspectCodexManagedHook({ packageRoot });
|
|
3308
3314
|
if (hook.status === 'installed') {
|
|
3309
3315
|
console.log(`[diagnose] ✓ Codex managed hook 已安装: ${hook.hookPath}`);
|
|
3310
3316
|
if (runHookLaunchProbe) {
|
|
3311
3317
|
try {
|
|
3312
|
-
const launch = runCodexHookLaunchDiagnostic({ packageRoot
|
|
3318
|
+
const launch = runCodexHookLaunchDiagnostic({ packageRoot });
|
|
3313
3319
|
if (launch.status === 'ok') {
|
|
3314
3320
|
console.log('[diagnose] ✓ Codex managed hook 启动探针报告: ' + launch.output);
|
|
3315
3321
|
for (const result of launch.results) {
|
package/dist/cli/fs-command.js
CHANGED
|
@@ -56,7 +56,7 @@ Options:
|
|
|
56
56
|
--content-type <mime> 上传时指定内容类型
|
|
57
57
|
--public 上传后公开可读(personal storage)
|
|
58
58
|
--visibility <public|private> chmod 可见性
|
|
59
|
-
--allow-roles <roles> chmod
|
|
59
|
+
--allow-roles <roles> chmod 角色约束,单引号包住、逗号分隔,
|
|
60
60
|
如 --allow-roles 'owner,admin'
|
|
61
61
|
-m aid:<aid>:<perms> setfacl 新增/更新 ACL
|
|
62
62
|
-x aid:<aid> setfacl 移除 ACL
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { resolvePaths, agentThreadIndexPath } from '../paths.js';
|
|
4
|
+
import { scanChatDirs, readLastJsonlLine, } from '../core/session/session-fs-store.js';
|
|
5
|
+
const HELP_TEXT = `
|
|
6
|
+
ec getmeta <id> [选项]
|
|
7
|
+
|
|
8
|
+
按任意一种会话标识查询本地会话的完整元数据(不经过 daemon,直接读存档)。
|
|
9
|
+
|
|
10
|
+
<id> 接受三种形态:
|
|
11
|
+
meta_YYYYMMDD_<ts> ec 会话 id(meta 文件名)
|
|
12
|
+
thread-<...> 话题 id(threadId)
|
|
13
|
+
<uuid> provider 侧会话 id(agentSessionId)
|
|
14
|
+
|
|
15
|
+
输出字段:
|
|
16
|
+
sessionId / threadId / agentSessionId / peer / selfAID / baseagent
|
|
17
|
+
name / chatType / projectPath / updatedAt / metaFile
|
|
18
|
+
source 命中位置:meta(主会话)/ thread(话题)/ active / trash
|
|
19
|
+
indexed 该会话是否登记在话题索引里
|
|
20
|
+
|
|
21
|
+
选项:
|
|
22
|
+
--self <aid> 只扫描指定 agent,减少全量扫描开销
|
|
23
|
+
--format json 以 JSON 格式输出
|
|
24
|
+
`.trim();
|
|
25
|
+
function argValue(args, flag) {
|
|
26
|
+
const i = args.indexOf(flag);
|
|
27
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
28
|
+
}
|
|
29
|
+
function hasFlag(args, flag) {
|
|
30
|
+
return args.includes(flag);
|
|
31
|
+
}
|
|
32
|
+
function readJsonFile(filePath) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** 该 agent 的话题索引里是否登记了 (peer, threadId, sessionId)。 */
|
|
41
|
+
function isIndexed(selfAID, peer, threadId, sessionId) {
|
|
42
|
+
if (!selfAID)
|
|
43
|
+
return false;
|
|
44
|
+
const raw = readJsonFile(agentThreadIndexPath(selfAID));
|
|
45
|
+
if (!raw?.threads)
|
|
46
|
+
return false;
|
|
47
|
+
return raw.threads.some(t => (t.sessionId === sessionId
|
|
48
|
+
&& (t.threadId ?? '') === threadId
|
|
49
|
+
&& (peer == null || t.peer === peer)));
|
|
50
|
+
}
|
|
51
|
+
function makeHit(meta, metaFile, source, selfAID, chatPeer) {
|
|
52
|
+
const threadId = meta.threadId ?? '';
|
|
53
|
+
return {
|
|
54
|
+
sessionId: meta.id,
|
|
55
|
+
threadId,
|
|
56
|
+
agentSessionId: meta.agentSessionId ?? null,
|
|
57
|
+
peer: meta.channelId ?? chatPeer ?? null,
|
|
58
|
+
selfAID: meta.selfAID ?? selfAID,
|
|
59
|
+
baseagent: meta.baseagent ?? null,
|
|
60
|
+
name: meta.name ?? null,
|
|
61
|
+
chatType: meta.chatType ?? null,
|
|
62
|
+
projectPath: meta.projectPath ?? null,
|
|
63
|
+
updatedAt: meta.updatedAt ?? null,
|
|
64
|
+
updatedAtStr: meta.updatedAtStr ?? null,
|
|
65
|
+
createdAtStr: meta.createdAtStr ?? null,
|
|
66
|
+
metaFile,
|
|
67
|
+
source,
|
|
68
|
+
indexed: isIndexed(meta.selfAID ?? selfAID, meta.channelId ?? chatPeer ?? null, threadId, meta.id),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* id 形态判定。
|
|
73
|
+
* - `meta_...` 按文件名直接命中,不读文件内容
|
|
74
|
+
* - `thread-...` / uuid 需要扫末行比对,成本高但只在必要时发生
|
|
75
|
+
*/
|
|
76
|
+
function classifyId(id) {
|
|
77
|
+
if (id.startsWith('meta_'))
|
|
78
|
+
return 'session';
|
|
79
|
+
if (id.startsWith('thread-'))
|
|
80
|
+
return 'thread';
|
|
81
|
+
return 'agentSession';
|
|
82
|
+
}
|
|
83
|
+
async function cmdGetMeta(args) {
|
|
84
|
+
const jsonFormat = hasFlag(args, '--format') && argValue(args, '--format') === 'json'
|
|
85
|
+
|| hasFlag(args, '--json');
|
|
86
|
+
// 位置参数 = 第一个不是 flag、也不属于任何 flag 取值的参数。
|
|
87
|
+
// 不能简单取"首个非 - 开头",否则 `--self <aid>` 的值会被当成 id。
|
|
88
|
+
const VALUE_FLAGS = new Set(['--self', '--format']);
|
|
89
|
+
let rawId;
|
|
90
|
+
for (let i = 0; i < args.length; i++) {
|
|
91
|
+
const a = args[i];
|
|
92
|
+
if (a.startsWith('-')) {
|
|
93
|
+
if (VALUE_FLAGS.has(a))
|
|
94
|
+
i++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
rawId = a;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (!rawId) {
|
|
101
|
+
console.error('用法: ec getmeta <sessionId|threadId|agentSessionId> [--self <aid>] [--format json]');
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const id = rawId.endsWith('.jsonl') ? rawId.slice(0, -'.jsonl'.length) : rawId;
|
|
106
|
+
const selfFilter = argValue(args, '--self');
|
|
107
|
+
const kind = classifyId(id);
|
|
108
|
+
const p = resolvePaths();
|
|
109
|
+
const chatDirs = scanChatDirs(p.sessionsDir).filter(c => !selfFilter || c.selfAID === selfFilter);
|
|
110
|
+
const hits = [];
|
|
111
|
+
for (const chat of chatDirs) {
|
|
112
|
+
// 主会话 meta 在目录根,话题在 _threads/,已删除话题在 _trash/
|
|
113
|
+
const locations = [
|
|
114
|
+
{ dir: chat.dirPath, source: 'meta' },
|
|
115
|
+
{ dir: path.join(chat.dirPath, '_threads'), source: 'thread' },
|
|
116
|
+
{ dir: path.join(chat.dirPath, '_trash'), source: 'trash' },
|
|
117
|
+
];
|
|
118
|
+
for (const loc of locations) {
|
|
119
|
+
let entries;
|
|
120
|
+
try {
|
|
121
|
+
entries = fs.readdirSync(loc.dir);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (!entry.endsWith('.jsonl') || entry.includes('.fork-history'))
|
|
128
|
+
continue;
|
|
129
|
+
const entryId = entry.slice(0, -'.jsonl'.length);
|
|
130
|
+
// 文件名直接命中时只读这一个文件;否则必须读末行比对
|
|
131
|
+
if (kind === 'session' && entryId !== id)
|
|
132
|
+
continue;
|
|
133
|
+
const filePath = path.join(loc.dir, entry);
|
|
134
|
+
const meta = readLastJsonlLine(filePath);
|
|
135
|
+
if (!meta)
|
|
136
|
+
continue;
|
|
137
|
+
if (kind === 'thread' && (meta.threadId ?? '') !== id)
|
|
138
|
+
continue;
|
|
139
|
+
if (kind === 'agentSession' && (meta.agentSessionId ?? '') !== id)
|
|
140
|
+
continue;
|
|
141
|
+
if (kind === 'session' && meta.id !== id && entryId !== id)
|
|
142
|
+
continue;
|
|
143
|
+
hits.push(makeHit(meta, filePath, loc.source, chat.selfAID, chat.channelId));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 兜底:active.json 的 id/threadId/agentSessionId 也可能命中(active 是 meta 的最新快照)
|
|
148
|
+
if (hits.length === 0) {
|
|
149
|
+
for (const chat of chatDirs) {
|
|
150
|
+
const active = readJsonFile(path.join(chat.dirPath, 'active.json'));
|
|
151
|
+
if (!active)
|
|
152
|
+
continue;
|
|
153
|
+
const match = kind === 'session'
|
|
154
|
+
? active.id === id
|
|
155
|
+
: kind === 'thread'
|
|
156
|
+
? (active.threadId ?? '') === id
|
|
157
|
+
: (active.agentSessionId ?? '') === id;
|
|
158
|
+
if (!match)
|
|
159
|
+
continue;
|
|
160
|
+
hits.push(makeHit(active, path.join(chat.dirPath, 'active.json'), 'active', chat.selfAID, chat.channelId));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (hits.length === 0) {
|
|
164
|
+
if (jsonFormat) {
|
|
165
|
+
console.log(JSON.stringify({ ok: false, id, kind, hits: [] }, null, 2));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.error(`✗ 未找到会话 ${id}(${kind === 'session' ? 'ec 会话 id' : kind === 'thread' ? '话题 id' : 'provider 会话 id'};已扫描 ${chatDirs.length} 个 chat 目录)`);
|
|
169
|
+
}
|
|
170
|
+
process.exitCode = 1;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (jsonFormat) {
|
|
174
|
+
console.log(JSON.stringify({ ok: true, id, kind, hits }, null, 2));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const sourceLabel = {
|
|
178
|
+
meta: '主会话 meta 文件',
|
|
179
|
+
thread: '话题 meta 文件',
|
|
180
|
+
active: 'active.json 快照',
|
|
181
|
+
trash: '已删除(_trash)',
|
|
182
|
+
};
|
|
183
|
+
for (const h of hits) {
|
|
184
|
+
console.log(`会话 id: ${h.sessionId}`);
|
|
185
|
+
console.log(` 话题 id: ${h.threadId || '(主会话)'}`);
|
|
186
|
+
console.log(` provider 会话: ${h.agentSessionId ?? '(空)'}`);
|
|
187
|
+
console.log(` 来源: ${sourceLabel[h.source]}${h.source === 'trash' ? ' ⚠ 话题已删除' : ''}`);
|
|
188
|
+
console.log(` 索引: ${h.indexed ? '已登记' : '未登记(仅磁盘存档)'}`);
|
|
189
|
+
console.log(` 名称: ${h.name ?? '(未命名)'}`);
|
|
190
|
+
if (h.chatType)
|
|
191
|
+
console.log(` 类型: ${h.chatType === 'group' ? '群聊' : '单聊'}`);
|
|
192
|
+
console.log(` 对端: ${h.peer ?? ''}`);
|
|
193
|
+
console.log(` 本端 AID: ${h.selfAID ?? ''}`);
|
|
194
|
+
if (h.baseagent)
|
|
195
|
+
console.log(` 基座: ${h.baseagent}`);
|
|
196
|
+
if (h.projectPath)
|
|
197
|
+
console.log(` 项目: ${h.projectPath}`);
|
|
198
|
+
if (h.updatedAtStr)
|
|
199
|
+
console.log(` 最近更新: ${h.updatedAtStr}`);
|
|
200
|
+
console.log(` 文件: ${h.metaFile}`);
|
|
201
|
+
console.log('');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export async function runGetMetaCommand(args) {
|
|
205
|
+
const sub = args[0];
|
|
206
|
+
if (!sub || sub === 'help' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
207
|
+
console.log(HELP_TEXT);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
await cmdGetMeta(args);
|
|
211
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -37,6 +37,7 @@ Agent 与会话:
|
|
|
37
37
|
ctl 当前托管会话的控制命令
|
|
38
38
|
queue 查询或操作 agent 消息队列
|
|
39
39
|
session 按 ec 会话 id 查询本地 session 存档(含 agentSessionId)
|
|
40
|
+
getmeta 按会话 id / threadId / agentSessionId 查询会话元数据
|
|
40
41
|
task 恢复暂停或异常中断的会话任务
|
|
41
42
|
trigger 管理定时与事件触发器
|
|
42
43
|
handoff 查询和完成跨会话回流
|
|
@@ -449,6 +450,11 @@ export async function main(args) {
|
|
|
449
450
|
await runSessionCommand(strippedArgs.slice(1));
|
|
450
451
|
break;
|
|
451
452
|
}
|
|
453
|
+
case 'getmeta': {
|
|
454
|
+
const { runGetMetaCommand } = await import('./getmeta-command.js');
|
|
455
|
+
await runGetMetaCommand(strippedArgs.slice(1));
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
452
458
|
case 'group': {
|
|
453
459
|
const { suppressSdkLogs } = await import('../aun/aid/index.js');
|
|
454
460
|
suppressSdkLogs();
|
|
@@ -21,6 +21,9 @@ const MANAGED_TASK_COMMANDS = new Set([
|
|
|
21
21
|
// `session` 按 ec 会话 id 只读查询本地 session 存档(meta 文件尾行的
|
|
22
22
|
// agentSessionId 等),不触碰 daemon 与进程生命周期。
|
|
23
23
|
'session',
|
|
24
|
+
// `getmeta` 按 ec 会话 id / threadId / agentSessionId 之一只读查询本地
|
|
25
|
+
// 会话存档,同样不触碰 daemon 与进程生命周期。
|
|
26
|
+
'getmeta',
|
|
24
27
|
// `skill` 管理 EC 技能(安装/开关/校验)。它的主要使用者就是 agent 自己——
|
|
25
28
|
// ec-skill 技能的 SKILL.md 明确指导 agent 调用它。脚本只读写
|
|
26
29
|
// `thread` 提供话题链操作,通过 thread.op IPC 鉴权
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* 整理会话(sessionType=memory)另有 create / update / archive 三个写命令,
|
|
12
12
|
* 实现见 core/memory/memory-writer.ts,在这里只做 CLI 分发与权限委托。
|
|
13
13
|
*/
|
|
14
|
+
import fs from 'node:fs';
|
|
14
15
|
import { resolvePaths } from '../paths.js';
|
|
15
16
|
import { ipcQuery } from '../ipc.js';
|
|
16
17
|
import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
|
|
@@ -32,8 +33,8 @@ ec memory note --slug <slug> [--tags '<tag1>,<tag2>'] '<content>'
|
|
|
32
33
|
|
|
33
34
|
选项:
|
|
34
35
|
--slug <slug> 文件名标识,字母/数字/连字符,长度 1-64(必填)
|
|
35
|
-
--tags <...>
|
|
36
|
-
--speakers <...> 群聊专用:本条 note
|
|
36
|
+
--tags <...> 标签列表,单引号包住、逗号分隔;取值见 \$KITS_DOCS/evolcore/memory-tags.md
|
|
37
|
+
--speakers <...> 群聊专用:本条 note 涉及的发言人,单引号包住、逗号分隔,
|
|
37
38
|
如 --speakers '<aid1>,<aid2>';
|
|
38
39
|
整理会话据此把发言人信息分流写入各自的 relation 记忆
|
|
39
40
|
|
|
@@ -119,7 +120,13 @@ function assertFormatValues(args) {
|
|
|
119
120
|
});
|
|
120
121
|
}
|
|
121
122
|
/**
|
|
122
|
-
* 解析 `--key value`
|
|
123
|
+
* 解析 `--key value` 与位置参数。
|
|
124
|
+
*
|
|
125
|
+
* flag 判定采用**精确匹配已知 flag 集合**,而非 `startsWith('--')`——正文是自由
|
|
126
|
+
* 文本,可能以 `--` 开头(如「--as-session 通道测试」),把它当未知参数拒绝会让
|
|
127
|
+
* 正文受命令行位置影响。不在已知集合里的 token 一律按正文处理。
|
|
128
|
+
*
|
|
129
|
+
* 另支持 POSIX `--` end-of-options:其后所有 token 无条件按正文。
|
|
123
130
|
*/
|
|
124
131
|
function parseArgs(args, known) {
|
|
125
132
|
assertFormatValues(args);
|
|
@@ -127,10 +134,12 @@ function parseArgs(args, known) {
|
|
|
127
134
|
const positional = [];
|
|
128
135
|
for (let i = 0; i < args.length; i++) {
|
|
129
136
|
const a = args[i];
|
|
130
|
-
if (a
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
if (a === '--') {
|
|
138
|
+
positional.push(...args.slice(i + 1));
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (COMMON_FLAGS.includes(a) || known.includes(a)) {
|
|
142
|
+
if (i + 1 >= args.length || args[i + 1] === '--' || isKnownFlagToken(args[i + 1], known)) {
|
|
134
143
|
// --touch 这类布尔开关不带值
|
|
135
144
|
flags.set(a, '');
|
|
136
145
|
continue;
|
|
@@ -143,6 +152,9 @@ function parseArgs(args, known) {
|
|
|
143
152
|
}
|
|
144
153
|
return { flags, positional };
|
|
145
154
|
}
|
|
155
|
+
function isKnownFlagToken(token, known) {
|
|
156
|
+
return COMMON_FLAGS.includes(token) || known.includes(token);
|
|
157
|
+
}
|
|
146
158
|
function parseTags(raw) {
|
|
147
159
|
if (raw === undefined)
|
|
148
160
|
return undefined;
|
|
@@ -262,22 +274,41 @@ function parseCliArgs(args, known, json) {
|
|
|
262
274
|
* 否则读 EVOLCORE_SESSION_ID。
|
|
263
275
|
*/
|
|
264
276
|
function requireManagedSession(cmd, flags, json) {
|
|
265
|
-
const
|
|
266
|
-
if (flags.has('--debug') && !
|
|
277
|
+
const explicitDebugSessionId = flags.get('--debug');
|
|
278
|
+
if (flags.has('--debug') && !explicitDebugSessionId) {
|
|
267
279
|
fail(json, 'INVALID_ARGUMENT', '--debug 需要提供 sessionId(如 --debug meta_20260910_1789013149542)');
|
|
268
280
|
}
|
|
269
|
-
|
|
281
|
+
// `ec … --as-session <id>` 由 debug-mode.ts 在进程启动时注入会话环境,
|
|
282
|
+
// 与 `--debug <id>` 是同一种调试身份。判据:注入了 delegation token、
|
|
283
|
+
// 但来自从属会话的 daemon runtime epoch,这是 debug-issue-token 的固定形态
|
|
284
|
+
// (真正的托管任务两者都有)。两者都必须走 daemon 的 debug 分支:
|
|
285
|
+
// 该分支只认本地 control token,不接受 task delegation。
|
|
286
|
+
const asSessionDebug = !explicitDebugSessionId
|
|
287
|
+
&& !!process.env.EVOLCORE_SESSION_ID
|
|
288
|
+
&& !!readDelegationCredential()
|
|
289
|
+
&& !process.env.EVOLCORE_DAEMON_RUNTIME_EPOCH;
|
|
290
|
+
const debugMode = !!explicitDebugSessionId || asSessionDebug;
|
|
291
|
+
const sessionId = explicitDebugSessionId || process.env.EVOLCORE_SESSION_ID;
|
|
270
292
|
if (!sessionId) {
|
|
271
|
-
const message = `EVOLCORE_SESSION_ID 未设置(${cmd} 仅在 evolcore 托管环境中可用;调试可加 --debug <sessionId>)`;
|
|
293
|
+
const message = `EVOLCORE_SESSION_ID 未设置(${cmd} 仅在 evolcore 托管环境中可用;调试可加 --debug <sessionId> 或 --as-session <sessionId>)`;
|
|
272
294
|
fail(json, 'NO_SESSION', message, { text: `错误: ${message}` });
|
|
273
295
|
}
|
|
274
|
-
if (
|
|
275
|
-
console.error(`[debug] 模拟会话 ${
|
|
296
|
+
if (explicitDebugSessionId)
|
|
297
|
+
console.error(`[debug] 模拟会话 ${explicitDebugSessionId}`);
|
|
298
|
+
else if (asSessionDebug)
|
|
299
|
+
console.error(`[debug] --as-session 会话 ${sessionId}`);
|
|
300
|
+
let controlToken;
|
|
301
|
+
if (debugMode) {
|
|
302
|
+
try {
|
|
303
|
+
controlToken = fs.readFileSync(resolvePaths().controlToken, 'utf8').trim() || undefined;
|
|
304
|
+
}
|
|
305
|
+
catch { /* 交给 daemon 报错 */ }
|
|
306
|
+
}
|
|
276
307
|
return {
|
|
277
308
|
sessionId,
|
|
278
309
|
socketPath: resolvePaths().socket,
|
|
279
|
-
delegationToken
|
|
280
|
-
...(
|
|
310
|
+
// debug 身份必须用 control token;带 delegationToken 会被 daemon 直接拒绝
|
|
311
|
+
...(debugMode ? { controlToken, debug: true } : { delegationToken: readDelegationCredential() }),
|
|
281
312
|
};
|
|
282
313
|
}
|
|
283
314
|
// ── note ───────────────────────────────────────────────────────────────────
|
|
@@ -128,9 +128,13 @@ function printItem(item) {
|
|
|
128
128
|
const running = obj.running ? ' [运行中]' : '';
|
|
129
129
|
// 内部话题(daemon 会话记录)不是真实联系人,标注出来避免误认
|
|
130
130
|
const internal = obj.internal ? ' [内部会话记录·无消息]' : '';
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
// 主会话的 threadId 是空串;显示成 /main 比留一个空尾巴好读。
|
|
132
|
+
console.log(`${obj.peer}/${obj.threadId || 'main'}${running}${internal}`);
|
|
133
|
+
// 名称只放名称本身:状态、标签各自单独一行,避免污染标题(标题会被 summary set --name 引用)。
|
|
134
|
+
// 主会话(threadId 为空)加 `[主会话]` 前缀,与普通话题区分开;前缀只在展示层加,
|
|
135
|
+
// 不进索引与 session 的 name 字段,避免被 summary set --name 当成真实标题。
|
|
136
|
+
const namePrefix = obj.threadId ? '' : '[主会话] ';
|
|
137
|
+
console.log(` 名称: ${namePrefix}${obj.name}`);
|
|
134
138
|
if (obj.topicStatus)
|
|
135
139
|
console.log(` 状态: ${obj.topicStatus}`);
|
|
136
140
|
if (obj.label)
|
|
@@ -230,7 +234,11 @@ export async function runThreadCommand(args) {
|
|
|
230
234
|
const slash = ref.lastIndexOf('/');
|
|
231
235
|
if (slash < 0)
|
|
232
236
|
return { peer: ref };
|
|
233
|
-
|
|
237
|
+
const raw = ref.slice(slash + 1);
|
|
238
|
+
// 主会话的 threadId 是空串;`<peer>/main` 是同义写法,`list` 也这样显示。
|
|
239
|
+
if (raw === 'main')
|
|
240
|
+
return { peer: ref.slice(0, slash), threadId: '' };
|
|
241
|
+
return { peer: ref.slice(0, slash), threadId: raw };
|
|
234
242
|
}
|
|
235
243
|
switch (sub) {
|
|
236
244
|
case 'list': {
|
|
@@ -208,7 +208,10 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
208
208
|
else {
|
|
209
209
|
if (!route)
|
|
210
210
|
return { ok: false, code: 'UNKNOWN_FIELD', reason: `Unknown config field: ${field}` };
|
|
211
|
-
|
|
211
|
+
const coerced = coerceConfigValue(rawValue, route, field);
|
|
212
|
+
if (!coerced.ok)
|
|
213
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: coerced.reason };
|
|
214
|
+
value = coerced.value;
|
|
212
215
|
}
|
|
213
216
|
}
|
|
214
217
|
return {
|
|
@@ -506,18 +509,32 @@ function buildGlobalCanonicalArgv(command) {
|
|
|
506
509
|
argv.push('--format', command.format);
|
|
507
510
|
return argv;
|
|
508
511
|
}
|
|
509
|
-
function coerceConfigValue(raw, route) {
|
|
512
|
+
function coerceConfigValue(raw, route, field) {
|
|
510
513
|
if (route.merge === 'list')
|
|
511
|
-
return [raw];
|
|
514
|
+
return { ok: true, value: [raw] };
|
|
512
515
|
if (route.enum)
|
|
513
|
-
return raw;
|
|
516
|
+
return { ok: true, value: raw };
|
|
517
|
+
if (route.merge === 'dict' && field === route.field) {
|
|
518
|
+
const trimmed = raw.trim();
|
|
519
|
+
let parsed;
|
|
520
|
+
try {
|
|
521
|
+
parsed = JSON.parse(trimmed);
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return { ok: false, reason: `Config field ${route.field} requires a JSON object value` };
|
|
525
|
+
}
|
|
526
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
527
|
+
return { ok: false, reason: `Config field ${route.field} requires a JSON object value` };
|
|
528
|
+
}
|
|
529
|
+
return { ok: true, value: parsed };
|
|
530
|
+
}
|
|
514
531
|
if (raw === 'true')
|
|
515
|
-
return true;
|
|
532
|
+
return { ok: true, value: true };
|
|
516
533
|
if (raw === 'false')
|
|
517
|
-
return false;
|
|
534
|
+
return { ok: true, value: false };
|
|
518
535
|
if (/^-?\d+(\.\d+)?$/.test(raw))
|
|
519
|
-
return Number(raw);
|
|
520
|
-
return raw;
|
|
536
|
+
return { ok: true, value: Number(raw) };
|
|
537
|
+
return { ok: true, value: raw };
|
|
521
538
|
}
|
|
522
539
|
function parseOwnersValue(raw) {
|
|
523
540
|
let values;
|
|
@@ -331,14 +331,21 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
331
331
|
};
|
|
332
332
|
}
|
|
333
333
|
let matchResult = matchCommandPermission(operation, opMeta.category, roleDef.commandPermissions || {}, opMeta.dangerous);
|
|
334
|
+
// `ec.thread.*` 是 agent-tool 专用的自数据操作:索引、会话、摘要都在
|
|
335
|
+
// `agents/<self>/data/threads/` 下,目标对端只是数据条目,不是访问边界。
|
|
336
|
+
// 因此只继承 `session.topic.list` 的"读"授权,不继承它的 currentRelationOnly /
|
|
337
|
+
// ownPeerOnly —— 那两条针对的是"当前关系下的会话视图"(menu/slash 场景),
|
|
338
|
+
// 套到话题链上会让 member 无法发现任何第三方话题。
|
|
334
339
|
if (!matchResult && operation.startsWith('ec.thread.') && opMeta.category === 'read' && intent.scope === 'relation') {
|
|
335
340
|
const permission = roleDef.commandPermissions?.['session.topic.list'];
|
|
336
|
-
if (permission?.allow)
|
|
341
|
+
if (permission?.allow) {
|
|
342
|
+
const { currentRelationOnly: _relOnly, ownPeerOnly: _ownPeer, ...restConstraints } = permission.constraints ?? {};
|
|
337
343
|
matchResult = {
|
|
338
|
-
permission: { ...permission, scopes: ['relation'], constraints: { ...
|
|
344
|
+
permission: { ...permission, scopes: ['relation'], constraints: { ...restConstraints, targetCurrentAgentOnly: true } },
|
|
339
345
|
matchedRule: 'session.topic.list',
|
|
340
346
|
rank: 1000,
|
|
341
347
|
};
|
|
348
|
+
}
|
|
342
349
|
}
|
|
343
350
|
// Agent self-service contact add deliberately reuses the same low-trust
|
|
344
351
|
// relation boundary as ec.msg.send. Older per-Agent role files may still
|
|
@@ -768,6 +775,10 @@ function checkCurrentRelation(ctx, resolvedConfigCommand) {
|
|
|
768
775
|
if (!ctx.selfAid || !requestedSelf || requestedSelf !== ctx.selfAid) {
|
|
769
776
|
return { ok: false, reason: 'Only the current agent relation can be targeted' };
|
|
770
777
|
}
|
|
778
|
+
// 话题作用域的历史读取(`ec msg history --thread <id>`)目标是话题本身,
|
|
779
|
+
// 不是当前关系的对端;消息日志仍落在调用方自己的会话目录里,因此不比对对端。
|
|
780
|
+
if (ctx.intent.operation === 'ec.msg.history' && stringArg(ctx.intent.args.threadId))
|
|
781
|
+
return { ok: true };
|
|
771
782
|
const requestedPeer = resolvedConfigCommand?.peerKey || stringArg(ctx.intent.args.peerKey) || stringArg(ctx.intent.args.peer);
|
|
772
783
|
if (!ctx.peerKey || !requestedPeer) {
|
|
773
784
|
return { ok: false, reason: 'Current relation context is required' };
|
|
@@ -917,6 +928,14 @@ function checkOwnPeer(ctx) {
|
|
|
917
928
|
const isMsgSend = ctx.intent.operation === 'ec.msg.send' || ctx.intent.operation === 'ec.msg.file';
|
|
918
929
|
if (isMsgSend)
|
|
919
930
|
return { ok: true };
|
|
931
|
+
// Same reasoning for `ec.thread.*`: args.peer is the *indexed peer whose local
|
|
932
|
+
// thread data is being read or written*. All of it lives under
|
|
933
|
+
// `agents/<self>/data/threads/`, so targeting a peer other than the current
|
|
934
|
+
// relation's counterpart does not cross a data boundary — it is the whole
|
|
935
|
+
// point of cross-topic routing. Ownership of the data stays with the caller,
|
|
936
|
+
// which `targetCurrentAgentOnly` already covers.
|
|
937
|
+
if (ctx.intent.operation.startsWith('ec.thread.'))
|
|
938
|
+
return { ok: true };
|
|
920
939
|
if (ctx.actorId) {
|
|
921
940
|
if (requestedPeerId && requestedPeerId !== ctx.actorId) {
|
|
922
941
|
return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerId) };
|
|
@@ -390,7 +390,7 @@ export function wrapChannelSend(inst, dependencies) {
|
|
|
390
390
|
from: owningAgent?.aid ?? envelope.agentName ?? session.selfAID ?? 'self',
|
|
391
391
|
to: String(target || envelope.channelId),
|
|
392
392
|
sessionId: session.id,
|
|
393
|
-
threadId: envelope.replyContext?.threadId || session.threadId ||
|
|
393
|
+
threadId: envelope.replyContext?.threadId || session.threadId || '',
|
|
394
394
|
chatType: isGroup ? 'group' : 'private',
|
|
395
395
|
groupId: isGroup ? String(session.metadata?.groupId || session.channelId) : null,
|
|
396
396
|
msgId: `${envelope.taskId || 'out'}_${Date.now()}`,
|