evolcore 0.0.31 → 0.0.33
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 +35 -0
- package/bin/install-codex-managed-hooks.mjs +124 -37
- package/dist/agents/claude-runner.js +29 -2
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +76 -44
- package/dist/agents/codex-runner.js +51 -24
- package/dist/agents/ecagent-runner.js +9 -13
- package/dist/agents/gemini-runner.js +4 -0
- package/dist/aun/aid/store.js +0 -81
- package/dist/aun/group/fs-download.js +1 -10
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/managed-operation.js +130 -32
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/aun/service-proxy.js +12 -18
- package/dist/aun/storage/index.js +29 -0
- package/dist/channels/aun.js +38 -137
- package/dist/channels/daemon.js +257 -2
- package/dist/cli/agent-command.js +64 -31
- package/dist/cli/agent-create.js +43 -24
- package/dist/cli/agent.js +16 -70
- package/dist/cli/aun-commands.js +512 -32
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +1 -2
- package/dist/cli/daemon-commands.js +82 -13
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/diag.js +2 -2
- package/dist/cli/handoff-command.js +18 -5
- package/dist/cli/index.js +81 -64
- package/dist/cli/init.js +2 -2
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +93 -12
- package/dist/cli/model.js +14 -138
- package/dist/cli/queue-command.js +51 -0
- package/dist/cli/response.js +5 -54
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +85 -3
- package/dist/cli/stats.js +10 -4
- package/dist/cli/task-command.js +1 -0
- package/dist/cli/task-context.js +16 -0
- package/dist/cli/thread-command.js +620 -0
- package/dist/cli/trigger-command.js +76 -50
- package/dist/config/builtin-roles.js +9 -7
- package/dist/config/config-field-policy.js +2 -3
- package/dist/config/config-manager.js +7 -2
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +15 -0
- package/dist/core/agent-ipc-registration.js +65 -22
- package/dist/core/auth/agent-delegation.js +42 -2
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-authorization.js +1 -0
- package/dist/core/auth/operation-authorizer.js +24 -1
- package/dist/core/auth/operation-catalog.js +44 -3
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/command-handler.js +224 -45
- package/dist/core/command/diag-service.js +5 -1
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +48 -1
- package/dist/core/command/menu-handler.js +92 -16
- package/dist/core/command/menu-protocol.js +24 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +193 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/handoff/ipc-registration.js +38 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +210 -9
- package/dist/core/memory/consolidation-context.js +49 -16
- package/dist/core/memory/consolidation-ops-records.js +35 -0
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +174 -180
- package/dist/core/message/aun-ipc-sender.js +207 -8
- package/dist/core/message/im-renderer.js +149 -6
- package/dist/core/message/message-bridge.js +13 -3
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +131 -3
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +379 -69
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +126 -7
- package/dist/core/permission/sandbox-runtime.js +14 -1
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +54 -9
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +4 -1
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/task/task-recovery-service.js +2 -0
- package/dist/core/task/task-resume-operation.js +11 -0
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +777 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +10 -7
- package/dist/ipc.js +234 -17
- package/dist/paths.js +23 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/trigger/authorization.js +59 -29
- package/dist/trigger/feedback.js +7 -1
- package/dist/trigger/ipc-service.js +49 -35
- package/dist/trigger/parser.js +63 -1
- package/dist/trigger/script-sandbox.js +58 -77
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/error-utils.js +22 -1
- package/kits/docs/AUTHORING.md +1 -1
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +39 -28
- package/kits/docs/evolcore/agent.md +19 -4
- package/kits/docs/evolcore/ec-capabilities.md +4 -0
- package/kits/docs/evolcore/group-collaboration.md +3 -5
- package/kits/docs/evolcore/group.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +1 -1
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/msg.md +5 -8
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/evolcore/trigger.md +2 -2
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/eck_manifest.json +61 -26
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +0 -9
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +7 -1
- package/kits/rules/07-memory-and-thread.md +202 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +5 -0
- package/kits/templates/memory-consolidation/generalize.md +5 -0
- package/kits/templates/memory-consolidation/profile.md +5 -0
- package/kits/templates/memory-consolidation/promote.md +5 -0
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +66 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +2 -1
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/reply.md +3 -2
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +3 -1
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
package/dist/cli/aun-commands.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
3
4
|
import { agentMdPath, resolvePaths, resolveRoot } from '../paths.js';
|
|
4
5
|
import { isExplicitGroupId, normalizeGroupAid, sameGroupAid } from '../aun/group/index.js';
|
|
5
6
|
import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
|
|
6
7
|
import { ipcQuery } from '../ipc.js';
|
|
8
|
+
import { isValidAid as validSendAid } from '../aun/aid/validation.js';
|
|
7
9
|
import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
|
|
8
10
|
import { isManagementRole } from '../config/builtin-roles.js';
|
|
9
11
|
import { readBestTaskRuntimeContext } from './task-context.js';
|
|
10
12
|
import { assertEcSingleLine } from './cli-argv.js';
|
|
11
13
|
import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
|
|
12
14
|
import { ManagedGroupOperationError } from '../aun/msg/managed-operation.js';
|
|
15
|
+
import { parseDuration } from '../trigger/parser.js';
|
|
13
16
|
// ==================== AID ====================
|
|
14
17
|
function resolveAunPath(args) {
|
|
15
18
|
const idx = args.indexOf('--aun-path');
|
|
@@ -1088,6 +1091,7 @@ Options:
|
|
|
1088
1091
|
}
|
|
1089
1092
|
// ==================== Storage ====================
|
|
1090
1093
|
export async function cmdStorage(args) {
|
|
1094
|
+
args = canonicalizeEvolcoreCommandArgv(['ec', 'storage', ...args]).slice(2);
|
|
1091
1095
|
const sub = args[0];
|
|
1092
1096
|
const aunPath = resolveAunPath(args);
|
|
1093
1097
|
const formatJson = getArgValue(args, '--format') === 'json';
|
|
@@ -1142,12 +1146,9 @@ Options:
|
|
|
1142
1146
|
console.error('用法: ec storage upload <aid> <local-file> <remote-path> [--public]');
|
|
1143
1147
|
process.exit(1);
|
|
1144
1148
|
}
|
|
1145
|
-
if (!fs.existsSync(localFile)) {
|
|
1146
|
-
console.error(`❌ 文件不存在: ${localFile}`);
|
|
1147
|
-
process.exit(1);
|
|
1148
|
-
}
|
|
1149
1149
|
const result = await ipcQuery(resolvePaths().socket, {
|
|
1150
1150
|
type: 'storage.upload',
|
|
1151
|
+
argv: ['storage', ...args],
|
|
1151
1152
|
aid,
|
|
1152
1153
|
localFile,
|
|
1153
1154
|
remotePath,
|
|
@@ -1172,12 +1173,25 @@ Options:
|
|
|
1172
1173
|
return;
|
|
1173
1174
|
}
|
|
1174
1175
|
const authorization = await ipcQuery(resolvePaths().socket, {
|
|
1175
|
-
type: 'storage.auth', operation, aid, sessionId: process.env.EVOLCORE_SESSION_ID, delegationToken,
|
|
1176
|
+
type: 'storage.auth', argv: ['storage', ...args], operation, aid, sessionId: process.env.EVOLCORE_SESSION_ID, delegationToken,
|
|
1176
1177
|
}, 10_000);
|
|
1177
1178
|
if (!authorization)
|
|
1178
1179
|
failManagedGroup(formatJson, 'DAEMON_UNAVAILABLE', 'Unable to connect to the evolcore daemon');
|
|
1179
1180
|
if (!authorization.ok)
|
|
1180
1181
|
failManagedGroup(formatJson, authorization.code || 'STORAGE_AUTH_FAILED', authorization.error || 'Storage operation denied');
|
|
1182
|
+
if (sub === 'ls') {
|
|
1183
|
+
const objects = authorization.result?.objects ?? authorization.result ?? [];
|
|
1184
|
+
console.log(Array.isArray(objects) && !objects.length ? '(空)' : JSON.stringify(objects, null, 2));
|
|
1185
|
+
}
|
|
1186
|
+
else if (sub === 'quota')
|
|
1187
|
+
console.log(JSON.stringify(authorization.result, null, 2));
|
|
1188
|
+
else if (formatJson)
|
|
1189
|
+
console.log(JSON.stringify(authorization));
|
|
1190
|
+
else if (sub === 'download')
|
|
1191
|
+
console.log(`✓ 已下载: ${authorization.localPath} (${authorization.size} bytes)`);
|
|
1192
|
+
else
|
|
1193
|
+
console.log(`✓ 已删除: ${authorization.objectKey}`);
|
|
1194
|
+
return;
|
|
1181
1195
|
}
|
|
1182
1196
|
const { storageUpload, storageDownload, storageLs, storageRm, storageQuota } = await import('../aun/storage/index.js');
|
|
1183
1197
|
if (sub === 'upload') {
|
|
@@ -1295,6 +1309,176 @@ Options:
|
|
|
1295
1309
|
process.exit(1);
|
|
1296
1310
|
}
|
|
1297
1311
|
// ==================== Msg ====================
|
|
1312
|
+
/**
|
|
1313
|
+
* 构造跨话题复查 Trigger 的定义。
|
|
1314
|
+
*
|
|
1315
|
+
* `ec msg send --thread T2 --check-every 5m` 发送成功后自动创建。
|
|
1316
|
+
*
|
|
1317
|
+
* 使用 `interval` 而非 `delay`:周期触发,只有显式
|
|
1318
|
+
* `ec trigger cancel` 或 `ec trigger update --every <新时长>` 才改变其状态。
|
|
1319
|
+
* 一次性 Trigger 会静默消失,无法承担「一直跟进直到显式结束」的语义。
|
|
1320
|
+
*/
|
|
1321
|
+
export function buildThreadCheckTrigger(input) {
|
|
1322
|
+
const now = Date.now();
|
|
1323
|
+
return {
|
|
1324
|
+
$schema_version: 3.1,
|
|
1325
|
+
agentAid: input.agentAid,
|
|
1326
|
+
id: input.id,
|
|
1327
|
+
name: input.name,
|
|
1328
|
+
enabled: true,
|
|
1329
|
+
createdAt: now,
|
|
1330
|
+
updatedAt: now,
|
|
1331
|
+
// T10: labels 携带 kind + 业务上下文,渲染层按 kind=thread-check 路由到专属模板
|
|
1332
|
+
labels: {
|
|
1333
|
+
kind: 'thread-check',
|
|
1334
|
+
originThreadId: input.originThreadId,
|
|
1335
|
+
targetThreadId: input.targetThreadId,
|
|
1336
|
+
originPeer: input.originPeer,
|
|
1337
|
+
checkEvery: input.checkEvery,
|
|
1338
|
+
},
|
|
1339
|
+
...(input.origin ? { origin: input.origin } : {}),
|
|
1340
|
+
// 周期触发:每 everyMs 唤醒一次,直到显式取消/调整
|
|
1341
|
+
source: { type: 'interval', everyMs: input.everyMs },
|
|
1342
|
+
execution: {
|
|
1343
|
+
type: 'trigger_session',
|
|
1344
|
+
prompt: input.prompt,
|
|
1345
|
+
thread: 'by_trigger',
|
|
1346
|
+
onError: 'retry',
|
|
1347
|
+
noopSentinel: '[[NOOP]]',
|
|
1348
|
+
},
|
|
1349
|
+
feedback: {
|
|
1350
|
+
strategy: 'target',
|
|
1351
|
+
target: {
|
|
1352
|
+
channelKey: input.channelKey,
|
|
1353
|
+
channelId: input.targetChannelId,
|
|
1354
|
+
session: 'thread',
|
|
1355
|
+
threadId: input.targetThreadId,
|
|
1356
|
+
chatType: input.feedbackChatType ?? 'private',
|
|
1357
|
+
},
|
|
1358
|
+
},
|
|
1359
|
+
reliability: { concurrency: 'forbid', missedPolicy: 'run_once', retry: { maxAttempts: 0, backoffMs: 30_000 } },
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* 解析并校验 `--check-every`。返回 `{ durationMs }` 或错误。
|
|
1364
|
+
* 私聊与群聊两条跨话题发送路径共用,保证错误码一致。
|
|
1365
|
+
*/
|
|
1366
|
+
function parseCheckEveryArgs(checkEveryRaw, hasCheckEvery) {
|
|
1367
|
+
if (!hasCheckEvery) {
|
|
1368
|
+
return { code: 'CHECK_EVERY_REQUIRED', error: '跨话题发送必须指定 --check-every <时长>(如 5m / 30m / 2h)' };
|
|
1369
|
+
}
|
|
1370
|
+
if (checkEveryRaw === undefined)
|
|
1371
|
+
return { durationMs: 5 * 60 * 1000 };
|
|
1372
|
+
const parsed = parseDuration(checkEveryRaw);
|
|
1373
|
+
if (parsed === null) {
|
|
1374
|
+
return { code: 'INVALID_DURATION', error: `--check-every 时长格式无效: ${checkEveryRaw}(示例: 5m / 30m / 2h / 1d)` };
|
|
1375
|
+
}
|
|
1376
|
+
return { durationMs: parsed };
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* 跨话题发送成功后的统一收尾:创建周期复查 Trigger + 写入本端 thread-inbox。
|
|
1380
|
+
*
|
|
1381
|
+
* 私聊(`ec msg send --thread`)与群聊(`ec group send --thread`)共用,
|
|
1382
|
+
* 保证两条路径行为一致(M2 修复前群聊路径完全不做这两件事)。
|
|
1383
|
+
*
|
|
1384
|
+
* 设计见 `docs/thread-chain/thread-context-mechanism.md` §十、`thread-cli-spec-v2.md` §3.4。
|
|
1385
|
+
* 任意一步失败都不影响消息已发出的事实:返回 `triggerId` 为 undefined 时调用方照常输出发送结果。
|
|
1386
|
+
*/
|
|
1387
|
+
async function applyCrossThreadSendEffects(input) {
|
|
1388
|
+
const runtime = await readBestTaskRuntimeContext();
|
|
1389
|
+
const originThreadId = runtime?.threadId || 'main';
|
|
1390
|
+
const originPeer = runtime?.channelId || input.targetChannelId;
|
|
1391
|
+
const originSessionId = runtime?.sessionId;
|
|
1392
|
+
if (!originSessionId)
|
|
1393
|
+
return { inboxWritten: false };
|
|
1394
|
+
// Trigger 的 id 与 name 是两个不同的东西(见 src/trigger/validation.ts:727):
|
|
1395
|
+
// id = `trig_<ts>_<8位hex>` —— 唯一的持久化标识,`ec trigger show/update/cancel` 只认它
|
|
1396
|
+
// name = `check-<目标threadId>-from-<来源threadId>-<唯一后缀>` —— 人读标签,不参与定位
|
|
1397
|
+
//
|
|
1398
|
+
// 这里**在 IPC 之前**就把 id 定下来,有三个理由:
|
|
1399
|
+
// 1. prompt 里要写 `ec trigger cancel <id>`,而 prompt 是 definition 的一部分,
|
|
1400
|
+
// create 之前就得拼完,拿不到 daemon 的返回值;
|
|
1401
|
+
// 2. definition 带上 id 后 daemon 直接采用(normalizeV3Phase2 认 `raw.id`),
|
|
1402
|
+
// 落盘 id 与 prompt 中的 id 天然一致,不存在"返回值和 prompt 两套值"的漂移;
|
|
1403
|
+
// 3. D7:此前这里把拼出来的 name 当 id 用,导致 inbox 的 triggerId、`--format json`
|
|
1404
|
+
// 的 trigger_id、prompt 里的参考命令三处全部不可用(`ec trigger cancel` 报 not found)。
|
|
1405
|
+
const triggerSeed = `${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
|
|
1406
|
+
const triggerId = `trig_${triggerSeed}`;
|
|
1407
|
+
// D8:name 必须每次不同。此前用固定的 `check-<T>-from-<origin>`,同一
|
|
1408
|
+
// (目标, 来源) 对第二次发送就撞 `trigger name already exists`(assertUnique 按 name 去重),
|
|
1409
|
+
// 结果消息照发、inbox 照写、Trigger 静默不建 —— 完全无感知。
|
|
1410
|
+
// 每次 `--check-every` 都该有自己的 new Trigger(要复用应走 update),所以追加唯一后缀。
|
|
1411
|
+
const triggerName = `check-${input.targetThreadId}-from-${originThreadId}-${triggerSeed.split('_')[1]}`;
|
|
1412
|
+
const userPromptPart = input.triggerPromptExtra
|
|
1413
|
+
? input.triggerPromptExtra
|
|
1414
|
+
: `请检查该话题的最新进度,根据情况:继续等待、调整周期(ec trigger update ${triggerId} --every <时长>)或结束复查(ec trigger cancel ${triggerId})。`;
|
|
1415
|
+
const prompt = [
|
|
1416
|
+
`【Trigger 复查】话题 ${input.targetThreadId}(对端 ${input.targetChannelId})`,
|
|
1417
|
+
`这是周期复查:每 ${input.checkEveryLabel} 唤醒一次,直到你显式结束它。`,
|
|
1418
|
+
``,
|
|
1419
|
+
userPromptPart,
|
|
1420
|
+
``,
|
|
1421
|
+
`参考命令:`,
|
|
1422
|
+
` ec thread history ${input.targetChannelId}/${input.targetThreadId} --limit 10`,
|
|
1423
|
+
` ec thread history ${originPeer}/${originThreadId} --after ${Date.now()}`,
|
|
1424
|
+
` ec trigger cancel ${triggerId}`,
|
|
1425
|
+
` ec trigger update ${triggerId} --every <时长>`,
|
|
1426
|
+
`Trigger id:${triggerId}`,
|
|
1427
|
+
`Trigger 名称:${triggerName}`,
|
|
1428
|
+
].join('\n');
|
|
1429
|
+
let createdTriggerId;
|
|
1430
|
+
try {
|
|
1431
|
+
const { formatChannelKey } = await import('../core/channel-loader.js');
|
|
1432
|
+
const triggerDef = buildThreadCheckTrigger({
|
|
1433
|
+
agentAid: input.selfAid,
|
|
1434
|
+
id: triggerId,
|
|
1435
|
+
name: triggerName,
|
|
1436
|
+
prompt,
|
|
1437
|
+
everyMs: input.durationMs,
|
|
1438
|
+
checkEvery: input.checkEveryLabel,
|
|
1439
|
+
channelKey: formatChannelKey({ type: 'aun', selfAID: input.selfAid, name: 'main' }),
|
|
1440
|
+
targetChannelId: input.targetChannelId,
|
|
1441
|
+
targetThreadId: input.targetThreadId,
|
|
1442
|
+
originThreadId,
|
|
1443
|
+
originPeer,
|
|
1444
|
+
feedbackChatType: input.feedbackChatType,
|
|
1445
|
+
});
|
|
1446
|
+
const triggerRes = await ipcQuery(resolvePaths().socket, {
|
|
1447
|
+
type: 'trigger.create',
|
|
1448
|
+
definition: triggerDef,
|
|
1449
|
+
files: [],
|
|
1450
|
+
enable: true,
|
|
1451
|
+
// daemon 侧 authenticatedTriggerActor 先看 actorSessionId:缺了它会回落到
|
|
1452
|
+
// controlToken 分支(CLI 从不读 control.token),必然抛
|
|
1453
|
+
// "valid local control token is required"。与 trigger-command.ts 的组装保持一致。
|
|
1454
|
+
actorSessionId: originSessionId,
|
|
1455
|
+
delegationToken: readDelegationCredential(),
|
|
1456
|
+
}, 30_000);
|
|
1457
|
+
if (!triggerRes?.ok) {
|
|
1458
|
+
process.stderr.write(`⚠ Trigger 创建失败: ${triggerRes?.error ?? 'unknown'}(inbox 仍会写入)\n`);
|
|
1459
|
+
}
|
|
1460
|
+
else {
|
|
1461
|
+
// 以 daemon 落盘的结果为准(definition 已带 id,正常应与预设值相同;
|
|
1462
|
+
// 若 daemon 因任何原因改写了 id,回填真值而不是本地预设值)。
|
|
1463
|
+
createdTriggerId = triggerRes.trigger?.id ?? triggerId;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
catch (e) {
|
|
1467
|
+
process.stderr.write(`⚠ Trigger 创建异常: ${e?.message ?? e}(inbox 仍会写入)\n`);
|
|
1468
|
+
}
|
|
1469
|
+
const { ThreadService } = await import('../core/thread/index.js');
|
|
1470
|
+
ThreadService.appendInbox(input.selfAid, {
|
|
1471
|
+
messageId: input.messageId,
|
|
1472
|
+
content: input.contentSummary,
|
|
1473
|
+
timestamp: input.timestamp,
|
|
1474
|
+
originThreadId,
|
|
1475
|
+
originPeer,
|
|
1476
|
+
originSessionId,
|
|
1477
|
+
targetSessionId: input.targetSessionId,
|
|
1478
|
+
...(createdTriggerId ? { triggerId: createdTriggerId } : {}),
|
|
1479
|
+
});
|
|
1480
|
+
return { triggerId: createdTriggerId, inboxWritten: true };
|
|
1481
|
+
}
|
|
1298
1482
|
export async function cmdMsg(args) {
|
|
1299
1483
|
args = canonicalizeEvolcoreCommandArgv(['ec', 'msg', ...args]).slice(2);
|
|
1300
1484
|
const sub = args[0];
|
|
@@ -1305,7 +1489,7 @@ export async function cmdMsg(args) {
|
|
|
1305
1489
|
console.log(`用法: ec msg <command> <from-aid> [args...] [options]
|
|
1306
1490
|
|
|
1307
1491
|
Commands:
|
|
1308
|
-
send <from> <to> <text>
|
|
1492
|
+
send <from> <to> <text> 发送文本
|
|
1309
1493
|
send <from> <to> --text-from-file <path> 从文件读取文本内容
|
|
1310
1494
|
send <from> <to> --file <path> [--as <type>] 发送文件(image|video|voice|file)
|
|
1311
1495
|
send <from> <to> --link <url> [--title T] 发送链接卡片
|
|
@@ -1316,6 +1500,10 @@ Commands:
|
|
|
1316
1500
|
online <from> <target-aid> [<target-aid>...] 查询在线状态
|
|
1317
1501
|
history <from> [<target-aid>|selector] [筛选选项] 查询本地消息历史
|
|
1318
1502
|
|
|
1503
|
+
托管会话简化语法(EVOLCORE_SESSION_ID 已设时):
|
|
1504
|
+
send <text> --thread <id> from/to 从运行时上下文与话题索引自动推断
|
|
1505
|
+
send <text> 发送到当前会话对端
|
|
1506
|
+
|
|
1319
1507
|
Options:
|
|
1320
1508
|
--text-from-file <path> 从文件读取文本(UTF-8),用于超长消息或避免 Shell 转义
|
|
1321
1509
|
--handoff-context <text> 仅托管会话:为目标会话补充本地上下文,不发给对端
|
|
@@ -1324,7 +1512,7 @@ Options:
|
|
|
1324
1512
|
--format json 输出 JSON 格式
|
|
1325
1513
|
--encrypt 强制端到端加密(未指定时使用 daemon 默认值)
|
|
1326
1514
|
--no-encrypt 强制明文发送(优先于 --encrypt)
|
|
1327
|
-
--thread <id>
|
|
1515
|
+
--thread <id> 指定目标话题 ID(话题必须存在于本端索引;from/to 可省略)
|
|
1328
1516
|
--session <latest|all|id> 按 EvolCore 逻辑会话过滤历史(默认 latest)
|
|
1329
1517
|
--before <time> 只返回该时间之前的历史
|
|
1330
1518
|
--after <time> 只返回该时间之后的历史
|
|
@@ -1333,7 +1521,10 @@ Options:
|
|
|
1333
1521
|
--channel-name <name> 历史渠道实例名(非 AUN 渠道必须匹配 Agent 配置)
|
|
1334
1522
|
--channel-key <key> 非 AUN 已落盘渠道的完整实例键(与 --peer-id 成对)
|
|
1335
1523
|
--peer-id <id> 非 AUN 渠道的原生对端 ID(与 --channel-key 成对)
|
|
1336
|
-
--
|
|
1524
|
+
--check-every <duration> 跨话题发送后自动创建**周期**复查 Trigger(如 5m/30m/2h),跨话题时**必填**
|
|
1525
|
+
每 duration 唤醒一次,直到显式 ec trigger cancel 结束
|
|
1526
|
+
Trigger 名:check-<目标threadId>-from-<来源threadId>-<唯一后缀>
|
|
1527
|
+
取消/调整用返回的 trigger_id(trig_…),show/update/cancel 只认 id
|
|
1337
1528
|
--content-type <mime> 显式覆盖 MIME(仅 --file 模式)
|
|
1338
1529
|
--as <type> 附件类型:image|video|voice|file(仅 --file 模式)
|
|
1339
1530
|
--text <说明> 附件说明文字(仅 --file 模式)
|
|
@@ -1344,12 +1535,12 @@ Options:
|
|
|
1344
1535
|
(用于发送恰好等于某 flag 的文本,如 send a b -- --encrypt)
|
|
1345
1536
|
|
|
1346
1537
|
示例:
|
|
1347
|
-
ec msg send alice.agentid.pub bob.agentid.pub "hello"
|
|
1348
|
-
ec msg send alice.agentid.pub bob.agentid.pub --text-from-file long-message.txt
|
|
1349
|
-
ec msg send alice.agentid.pub bob.agentid.pub "讨论项目A" --thread "
|
|
1350
|
-
ec msg send alice.agentid.pub bob.agentid.pub --file ./pic.png
|
|
1351
|
-
ec msg send alice.agentid.pub bob.agentid.pub --file ./demo.mp4 --as video
|
|
1352
|
-
ec msg send alice.agentid.pub bob.agentid.pub --link https://example.com --title "AUN"
|
|
1538
|
+
ec msg send alice.agentid.pub bob.agentid.pub "hello"
|
|
1539
|
+
ec msg send alice.agentid.pub bob.agentid.pub --text-from-file long-message.txt
|
|
1540
|
+
ec msg send alice.agentid.pub bob.agentid.pub "讨论项目A" --thread "thread-xxx"
|
|
1541
|
+
ec msg send alice.agentid.pub bob.agentid.pub --file ./pic.png
|
|
1542
|
+
ec msg send alice.agentid.pub bob.agentid.pub --file ./demo.mp4 --as video
|
|
1543
|
+
ec msg send alice.agentid.pub bob.agentid.pub --link https://example.com --title "AUN"
|
|
1353
1544
|
ec msg pull alice.agentid.pub --app my-bot
|
|
1354
1545
|
ec msg ack alice.agentid.pub 42 --app my-bot
|
|
1355
1546
|
ec msg recall alice.agentid.pub msg-uuid-1 msg-uuid-2
|
|
@@ -1359,13 +1550,16 @@ Options:
|
|
|
1359
1550
|
return;
|
|
1360
1551
|
}
|
|
1361
1552
|
if (sub === 'history' && wantsHelp(args)) {
|
|
1362
|
-
console.log(`用法: ec msg history <self-aid> [<target-aid>
|
|
1553
|
+
console.log(`用法: ec msg history [<self-aid> [<target-aid>]] [--thread <id>] [options]
|
|
1554
|
+
|
|
1555
|
+
托管会话中 --thread <id> 时 self-aid 和 target-aid 均可省略,从运行时上下文自动推断。
|
|
1363
1556
|
|
|
1364
1557
|
查询指定会话对的本地历史,不调用 message.pull,也不影响收件箱游标。非 AUN selector 按已落盘的 session metadata、完整 channelKey 与原生 peer ID 精确读取,不能传入文件路径。
|
|
1365
1558
|
|
|
1366
1559
|
托管会话限制:普通会话只能使用当前 self AID 和当前 AUN 私聊对端;可信 daemon Trigger 可用精确的 --channel-key/--peer-id selector 读取已落盘的非 AUN 私聊历史。托管任务不支持 --channel、--channel-name、--aun-path 或 --app。
|
|
1367
1560
|
|
|
1368
1561
|
Options:
|
|
1562
|
+
--thread <id> 按话题 ID 过滤(托管会话中 from/to 可省略)
|
|
1369
1563
|
--session <latest|all|id> 默认 latest;all 查询全部;也可指定会话 ID
|
|
1370
1564
|
--limit <N> 返回最新 N 条(默认 50,最大 500)
|
|
1371
1565
|
--before <time> 开区间;epoch 毫秒或 ISO 时间
|
|
@@ -1380,9 +1574,41 @@ Options:
|
|
|
1380
1574
|
ec msg history alice.agentid.pub bob.agentid.pub --session latest
|
|
1381
1575
|
ec msg history alice.agentid.pub bob.agentid.pub --session all
|
|
1382
1576
|
ec msg history alice.agentid.pub bob.agentid.pub --session meta_20260713_1783920000000 --format json
|
|
1383
|
-
ec msg history alice.agentid.pub --channel-key feishu#alice.agentid.pub#main --peer-id ou_xxx --session all --format json
|
|
1577
|
+
ec msg history alice.agentid.pub --channel-key feishu#alice.agentid.pub#main --peer-id ou_xxx --session all --format json
|
|
1578
|
+
ec msg history --thread thread-xxx-0001-aaaa --limit 20`);
|
|
1384
1579
|
return;
|
|
1385
1580
|
}
|
|
1581
|
+
// P1b.6.2: 托管会话 send 时,from/to 可省略——从 runtime 和话题索引推断
|
|
1582
|
+
// history --thread 时同理:from/to 均可省略
|
|
1583
|
+
if (process.env.EVOLCORE_SESSION_ID && (sub === 'send' || (sub === 'history' && getArgValue(args, '--thread')))) {
|
|
1584
|
+
const { isValidAid: checkAid } = await import('../aun/aid/index.js');
|
|
1585
|
+
const hasFrom = typeof args[1] === 'string' && checkAid(args[1]);
|
|
1586
|
+
const hasTo = typeof args[2] === 'string' && checkAid(args[2]);
|
|
1587
|
+
if (!hasFrom || !hasTo) {
|
|
1588
|
+
const runtime = await readBestTaskRuntimeContext();
|
|
1589
|
+
if (runtime?.selfAid) {
|
|
1590
|
+
const inferredFrom = runtime.selfAid.replace(/^@/, '');
|
|
1591
|
+
let inferredTo;
|
|
1592
|
+
const threadArg = getArgValue(args, '--thread');
|
|
1593
|
+
if (threadArg) {
|
|
1594
|
+
const { ThreadService } = await import('../core/thread/index.js');
|
|
1595
|
+
inferredTo = ThreadService.findThreadById(inferredFrom, threadArg)?.peer;
|
|
1596
|
+
}
|
|
1597
|
+
if (!inferredTo) {
|
|
1598
|
+
inferredTo = runtime.channelId?.replace(/^@/, '');
|
|
1599
|
+
}
|
|
1600
|
+
if (!hasFrom && !hasTo && inferredTo) {
|
|
1601
|
+
args.splice(1, 0, inferredFrom, inferredTo);
|
|
1602
|
+
}
|
|
1603
|
+
else if (!hasFrom && inferredTo) {
|
|
1604
|
+
args.splice(1, 0, inferredFrom);
|
|
1605
|
+
}
|
|
1606
|
+
else if (!hasTo && inferredTo) {
|
|
1607
|
+
args.splice(2, 0, inferredTo);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1386
1612
|
const from = args[1];
|
|
1387
1613
|
if (!from) {
|
|
1388
1614
|
console.error('❌ 缺少 <from-aid> 参数');
|
|
@@ -1398,6 +1624,8 @@ Options:
|
|
|
1398
1624
|
renderManagedMsgResult(sub, result, formatJson);
|
|
1399
1625
|
return;
|
|
1400
1626
|
}
|
|
1627
|
+
if (process.env.EVOLCORE_SESSION_ID && sub === 'send')
|
|
1628
|
+
return runManagedSend(['msg', ...args], formatJson);
|
|
1401
1629
|
const { msgSend, msgPull, msgAck, msgRecall, msgOnline, msgHistory } = await import('../aun/msg/index.js');
|
|
1402
1630
|
const commonOpts = { aunPath, slotId: appSlot };
|
|
1403
1631
|
const requireConsumerSlot = (operation) => {
|
|
@@ -1503,6 +1731,7 @@ Options:
|
|
|
1503
1731
|
before,
|
|
1504
1732
|
after,
|
|
1505
1733
|
direction: (getArgValue(args, '--direction') ?? 'all'),
|
|
1734
|
+
threadId: getArgValue(args, '--thread'),
|
|
1506
1735
|
});
|
|
1507
1736
|
if (!result.ok) {
|
|
1508
1737
|
if (format === 'json')
|
|
@@ -1612,11 +1841,38 @@ Options:
|
|
|
1612
1841
|
? false
|
|
1613
1842
|
: args.includes('--encrypt') ? true : undefined;
|
|
1614
1843
|
const thread = getArgValue(args, '--thread');
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1844
|
+
// P1b.6.5: 话题存在性校验 — --thread 指定的话题必须在本端索引里存在
|
|
1845
|
+
let threadEntry;
|
|
1846
|
+
if (thread) {
|
|
1847
|
+
const { ThreadService, normalizeThreadPeer } = await import('../core/thread/index.js');
|
|
1848
|
+
threadEntry = ThreadService.findThreadById(from, thread);
|
|
1849
|
+
if (!threadEntry || normalizeThreadPeer(threadEntry.peer) !== normalizeThreadPeer(to)) {
|
|
1850
|
+
if (formatJson) {
|
|
1851
|
+
console.log(JSON.stringify({ ok: false, code: 'TOPIC_NOT_FOUND', error: `话题不存在: ${thread}` }));
|
|
1852
|
+
}
|
|
1853
|
+
else {
|
|
1854
|
+
console.error(`❌ 话题不存在: ${thread}`);
|
|
1855
|
+
}
|
|
1856
|
+
process.exit(1);
|
|
1857
|
+
}
|
|
1619
1858
|
}
|
|
1859
|
+
// T8: 跨话题发送必须显式指定复查周期(发送前校验,避免消息已发出才报错)
|
|
1860
|
+
const checkEveryRaw = getArgValue(args, '--check-every');
|
|
1861
|
+
const hasCheckEvery = args.includes('--check-every') || checkEveryRaw !== undefined;
|
|
1862
|
+
const triggerPromptExtra = getArgValue(args, '--trigger-prompt');
|
|
1863
|
+
let checkEveryDurationMs;
|
|
1864
|
+
if (thread) {
|
|
1865
|
+
const parsedCheck = parseCheckEveryArgs(checkEveryRaw, hasCheckEvery);
|
|
1866
|
+
if ('error' in parsedCheck) {
|
|
1867
|
+
if (formatJson)
|
|
1868
|
+
console.log(JSON.stringify({ ok: false, code: parsedCheck.code, error: parsedCheck.error }));
|
|
1869
|
+
else
|
|
1870
|
+
console.error(`❌ ${parsedCheck.error}`);
|
|
1871
|
+
process.exit(1);
|
|
1872
|
+
}
|
|
1873
|
+
checkEveryDurationMs = parsedCheck.durationMs;
|
|
1874
|
+
}
|
|
1875
|
+
// --return 参数已移除:跨会话 send 不再需要指定返回策略(内部默认 none)。
|
|
1620
1876
|
// SDK 尚未提供字节进度回调,只展示上传开始和成功完成。
|
|
1621
1877
|
const onUploadProgress = formatJson ? undefined : (info) => {
|
|
1622
1878
|
if (info.phase === 'upload')
|
|
@@ -1624,7 +1880,8 @@ Options:
|
|
|
1624
1880
|
else if (info.phase === 'done')
|
|
1625
1881
|
process.stderr.write(' ✓ upload complete\n');
|
|
1626
1882
|
};
|
|
1627
|
-
const
|
|
1883
|
+
const runtime = await readBestTaskRuntimeContext();
|
|
1884
|
+
const result = await msgSend({ from, to, body, encrypt, thread, handoffContext: getArgValue(args, '--handoff-context'), onUploadProgress, ...commonOpts });
|
|
1628
1885
|
if (!result.ok) {
|
|
1629
1886
|
if (formatJson) {
|
|
1630
1887
|
console.log(JSON.stringify(result));
|
|
@@ -1634,6 +1891,31 @@ Options:
|
|
|
1634
1891
|
}
|
|
1635
1892
|
process.exit(1);
|
|
1636
1893
|
}
|
|
1894
|
+
// T2: 跨话题 send 成功后创建复查 Trigger 并写入本端 inbox(Thread Context)。
|
|
1895
|
+
// 私有逻辑集中在 applyCrossThreadSendEffects,与群聊路径共用。
|
|
1896
|
+
let createdTriggerId;
|
|
1897
|
+
if (thread && threadEntry && result.message_id && runtime?.sessionId) {
|
|
1898
|
+
const effects = await applyCrossThreadSendEffects({
|
|
1899
|
+
selfAid: from,
|
|
1900
|
+
targetThreadId: thread,
|
|
1901
|
+
targetSessionId: threadEntry.sessionId,
|
|
1902
|
+
targetChannelId: to,
|
|
1903
|
+
feedbackChatType: 'private',
|
|
1904
|
+
messageId: result.message_id,
|
|
1905
|
+
contentSummary: body.mode === 'text' ? body.text
|
|
1906
|
+
: body.mode === 'link' ? `[link] ${body.url}`
|
|
1907
|
+
: body.mode === 'file' ? `[file] ${body.filePath}`
|
|
1908
|
+
: '[payload]',
|
|
1909
|
+
timestamp: result.timestamp ?? Date.now(),
|
|
1910
|
+
durationMs: checkEveryDurationMs ?? 5 * 60 * 1000,
|
|
1911
|
+
checkEveryLabel: checkEveryRaw || '5m',
|
|
1912
|
+
triggerPromptExtra,
|
|
1913
|
+
});
|
|
1914
|
+
createdTriggerId = effects.triggerId;
|
|
1915
|
+
}
|
|
1916
|
+
// E4: 把复查 Trigger ID 回填到返回值,兑现 spec §3.4 的 `{ message_id, trigger_id }` 契约。
|
|
1917
|
+
if (createdTriggerId)
|
|
1918
|
+
result.trigger_id = createdTriggerId;
|
|
1637
1919
|
if (formatJson) {
|
|
1638
1920
|
console.log(JSON.stringify(result));
|
|
1639
1921
|
}
|
|
@@ -1836,7 +2118,7 @@ visitor 不开放历史;群权限仍由托管会话校验。`);
|
|
|
1836
2118
|
console.log(`用法: ec group <command> <from-aid> [args...] [options]
|
|
1837
2119
|
|
|
1838
2120
|
消息:
|
|
1839
|
-
send <from> <group-id> <text>
|
|
2121
|
+
send <from> <group-id> <text> 发送群文本
|
|
1840
2122
|
send <from> <group-id> --file <path> [--as <type>] 发送群文件
|
|
1841
2123
|
send <from> <group-id> --payload <json> 发送自定义 payload
|
|
1842
2124
|
pull <from> <group-id> [--after-seq N] [--limit N] --app <name> 拉取群消息(必须使用独立 slot)
|
|
@@ -1886,8 +2168,11 @@ Options:
|
|
|
1886
2168
|
--format json 输出 JSON 格式
|
|
1887
2169
|
--encrypt 强制端到端加密(未指定时使用 daemon 默认值;仅 send)
|
|
1888
2170
|
--no-encrypt 强制明文发送(优先于 --encrypt;仅 send)
|
|
1889
|
-
--thread <id> 指定话题 ID(仅 send
|
|
1890
|
-
--
|
|
2171
|
+
--thread <id> 指定话题 ID(仅 send;话题必须存在于本端索引)
|
|
2172
|
+
--check-every <时长> 跨话题发送(带 --thread)时**必填**,创建周期复查 Trigger(如 5m/30m/2h)
|
|
2173
|
+
无值时默认 5m;Trigger 名:check-<目标threadId>-from-<来源threadId>-<唯一后缀>
|
|
2174
|
+
取消/调整用返回的 trigger_id(trig_…),show/update/cancel 只认 id
|
|
2175
|
+
--trigger-prompt <text> 自定义复查 Trigger prompt 中的用户段(仅 --check-every)
|
|
1891
2176
|
--mention <aid> 发送时 @ 某个成员(可多次,或用逗号分隔多个 aid)
|
|
1892
2177
|
--mention-all 发送时 @ 所有人
|
|
1893
2178
|
--content-type <mime> 显式覆盖 MIME(仅 --file 模式)
|
|
@@ -1901,9 +2186,10 @@ Options:
|
|
|
1901
2186
|
|
|
1902
2187
|
格式示例(.example.invalid 是保留的虚构域,以下 AID 均不是真实地址):
|
|
1903
2188
|
ec group create sender.example.invalid "Dev Team" --visibility private
|
|
1904
|
-
ec group send sender.example.invalid 12345.example.invalid "hello team"
|
|
2189
|
+
ec group send sender.example.invalid 12345.example.invalid "hello team"
|
|
1905
2190
|
ec group send sender.example.invalid 12345.example.invalid "@member 看下 PR" --mention member.example.invalid
|
|
1906
2191
|
ec group send sender.example.invalid 12345.example.invalid --file ./arch.png
|
|
2192
|
+
ec group send sender.example.invalid g-12345.example.invalid "进度?" --thread thread-xxx --check-every 30m
|
|
1907
2193
|
ec group rules sender.example.invalid 12345.example.invalid set ./rules.md
|
|
1908
2194
|
ec group invite sender.example.invalid 12345.example.invalid member.example.invalid
|
|
1909
2195
|
ec group members sender.example.invalid 12345.example.invalid
|
|
@@ -1935,6 +2221,21 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
1935
2221
|
renderManagedGroupResult(args, result, formatJson);
|
|
1936
2222
|
return;
|
|
1937
2223
|
}
|
|
2224
|
+
if (process.env.EVOLCORE_SESSION_ID && sub === 'send') {
|
|
2225
|
+
if (args.includes('--aun-path') || args.includes('--app')) {
|
|
2226
|
+
failManagedGroup(formatJson, 'NOT_ALLOWED', '--aun-path and --app are not allowed in a managed task');
|
|
2227
|
+
}
|
|
2228
|
+
let groupId;
|
|
2229
|
+
try {
|
|
2230
|
+
groupId = normalizeGroupAid(args[2] ?? '', from.slice(from.indexOf('.') + 1));
|
|
2231
|
+
}
|
|
2232
|
+
catch (error) {
|
|
2233
|
+
failManagedGroup(formatJson, 'INVALID_ARGUMENT', `Invalid group AID: ${error instanceof Error ? error.message : String(error)}`);
|
|
2234
|
+
}
|
|
2235
|
+
const runtime = await readBestTaskRuntimeContext();
|
|
2236
|
+
validateManagedGroupScope(from, groupId, runtime, formatJson);
|
|
2237
|
+
return runManagedSend(['group', ...args], formatJson);
|
|
2238
|
+
}
|
|
1938
2239
|
const { groupSend, groupPull, groupAck, groupHistory, parseGroupHistoryArgs, groupCreate, groupInfo, groupList, groupUpdate, groupDissolve, groupSuspend, groupResume, groupJoin, groupLeave, groupInvite, groupInvites, groupAcceptInvite, groupRejectInvite, groupRevokeInvite, groupKick, groupMembers, groupOnline, groupSetRole, groupTransferOwner, groupBan, groupUnban, groupBanlist, groupBlock, groupUnblock, groupBlocklist, groupReportStatus, groupMemberStatuses, groupRules, groupUpdateRules, groupRulesFileGet, groupRulesFileSet, groupRulesFilePublish, } = await import('../aun/msg/index.js');
|
|
1939
2240
|
const commonOpts = { aunPath, slotId: appSlot };
|
|
1940
2241
|
if (['block', 'unblock', 'blocklist', 'report-status', 'member-statuses'].includes(sub)) {
|
|
@@ -2128,7 +2429,16 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
2128
2429
|
failManagedGroup(formatJson, 'NOT_ALLOWED', '--aun-path and --app are not allowed in a managed task');
|
|
2129
2430
|
}
|
|
2130
2431
|
const runtime = await readBestTaskRuntimeContext();
|
|
2131
|
-
|
|
2432
|
+
// 跨话题发送:目标群由 --thread 话题决定,不受「只能操作当前群」限制;
|
|
2433
|
+
// 发送者仍必须是当前 self AID,daemon 侧按目标话题 relation 复核。
|
|
2434
|
+
const threadArg = getArgValue(args, '--thread');
|
|
2435
|
+
const { resolveThreadRouteTarget } = await import('../core/thread/thread-route.js');
|
|
2436
|
+
if (threadArg && resolveThreadRouteTarget(from, threadArg, 'group', groupId)) {
|
|
2437
|
+
validateManagedGroupScope(from, undefined, runtime, formatJson);
|
|
2438
|
+
}
|
|
2439
|
+
else {
|
|
2440
|
+
validateManagedGroupScope(from, groupId, runtime, formatJson);
|
|
2441
|
+
}
|
|
2132
2442
|
}
|
|
2133
2443
|
const optionsEnd = args.indexOf('--', 3);
|
|
2134
2444
|
const optionArgs = optionsEnd >= 0 ? args.slice(0, optionsEnd) : args;
|
|
@@ -2136,7 +2446,7 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
2136
2446
|
const fileVal = getSendArgValue('--file');
|
|
2137
2447
|
const payloadVal = getSendArgValue('--payload');
|
|
2138
2448
|
const bodyPositionals = collectPositional(args, 3);
|
|
2139
|
-
const bodyFlags = ['--file', '--payload', '--as', '--content-type', '--text', '--transcript', '--thread', '--
|
|
2449
|
+
const bodyFlags = ['--file', '--payload', '--as', '--content-type', '--text', '--transcript', '--thread', '--check-every', '--trigger-prompt'];
|
|
2140
2450
|
for (let index = 3; index < args.length; index++) {
|
|
2141
2451
|
const flag = args[index];
|
|
2142
2452
|
if (flag === '--')
|
|
@@ -2209,9 +2519,24 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
2209
2519
|
? false
|
|
2210
2520
|
: args.includes('--encrypt') ? true : undefined;
|
|
2211
2521
|
const thread = getSendArgValue('--thread');
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2522
|
+
// M2: 群话题跨话题发送与私聊同样必须指定 --check-every,并同样创建复查 Trigger + 写 inbox。
|
|
2523
|
+
const groupCheckEveryRaw = getSendArgValue('--check-every');
|
|
2524
|
+
const groupHasCheckEvery = optionArgs.includes('--check-every') || groupCheckEveryRaw !== undefined;
|
|
2525
|
+
const groupTriggerPromptExtra = getSendArgValue('--trigger-prompt');
|
|
2526
|
+
let groupCheckEveryMs;
|
|
2527
|
+
let groupThreadEntry;
|
|
2528
|
+
if (thread) {
|
|
2529
|
+
// 话题必须存在于本端索引(与私聊路径一致,不自动创建)。
|
|
2530
|
+
const { ThreadService } = await import('../core/thread/index.js');
|
|
2531
|
+
groupThreadEntry = ThreadService.findThreadById(from, thread);
|
|
2532
|
+
if (!groupThreadEntry || normalizeGroupAid(groupThreadEntry.peer, from.slice(from.indexOf('.') + 1)) !== groupId) {
|
|
2533
|
+
failManagedGroup(formatJson, 'TOPIC_NOT_FOUND', `话题不存在: ${thread}`);
|
|
2534
|
+
}
|
|
2535
|
+
const parsedCheck = parseCheckEveryArgs(groupCheckEveryRaw, groupHasCheckEvery);
|
|
2536
|
+
if ('error' in parsedCheck) {
|
|
2537
|
+
failManagedGroup(formatJson, parsedCheck.code, parsedCheck.error);
|
|
2538
|
+
}
|
|
2539
|
+
groupCheckEveryMs = parsedCheck.durationMs;
|
|
2215
2540
|
}
|
|
2216
2541
|
const result = await groupSend({
|
|
2217
2542
|
from,
|
|
@@ -2220,9 +2545,33 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
2220
2545
|
mentions: mentions.length ? mentions : undefined,
|
|
2221
2546
|
encrypt: encryptGroup,
|
|
2222
2547
|
thread,
|
|
2223
|
-
returnPolicy: returnPolicyRaw,
|
|
2224
2548
|
...commonOpts,
|
|
2225
2549
|
});
|
|
2550
|
+
// T2.6: 群聊跨话题发送成功后,同样写入本端 inbox(与私聊路径一致)
|
|
2551
|
+
if (result && result.ok !== false && thread && groupThreadEntry) {
|
|
2552
|
+
const r = result;
|
|
2553
|
+
const messageId = r.message_id ?? r.message?.message_id;
|
|
2554
|
+
if (messageId) {
|
|
2555
|
+
const effects = await applyCrossThreadSendEffects({
|
|
2556
|
+
selfAid: from,
|
|
2557
|
+
targetThreadId: thread,
|
|
2558
|
+
targetSessionId: groupThreadEntry.sessionId,
|
|
2559
|
+
targetChannelId: groupId,
|
|
2560
|
+
feedbackChatType: 'group',
|
|
2561
|
+
messageId: String(messageId),
|
|
2562
|
+
contentSummary: body.mode === 'text' ? body.text
|
|
2563
|
+
: body.mode === 'link' ? `[link] ${body.url}`
|
|
2564
|
+
: body.mode === 'file' ? `[file] ${body.filePath}`
|
|
2565
|
+
: '[payload]',
|
|
2566
|
+
timestamp: r.timestamp ?? Date.now(),
|
|
2567
|
+
durationMs: groupCheckEveryMs ?? 5 * 60 * 1000,
|
|
2568
|
+
checkEveryLabel: groupCheckEveryRaw || '5m',
|
|
2569
|
+
triggerPromptExtra: groupTriggerPromptExtra,
|
|
2570
|
+
});
|
|
2571
|
+
if (effects.triggerId)
|
|
2572
|
+
result.trigger_id = effects.triggerId;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2226
2575
|
outputResult(result, () => {
|
|
2227
2576
|
const r = result;
|
|
2228
2577
|
if (r.handoff_id && r.status === 'delivered')
|
|
@@ -2737,6 +3086,135 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
|
|
|
2737
3086
|
process.exit(1);
|
|
2738
3087
|
}
|
|
2739
3088
|
// ==================== Main ====================
|
|
3089
|
+
async function runManagedSend(argv, formatJson) {
|
|
3090
|
+
const delegationToken = readDelegationCredential();
|
|
3091
|
+
if (!delegationToken)
|
|
3092
|
+
failManagedGroup(formatJson, 'DELEGATION_REQUIRED', 'Managed send requires delegation');
|
|
3093
|
+
const runtime = await readBestTaskRuntimeContext();
|
|
3094
|
+
const result = await ipcQuery(resolvePaths().socket, {
|
|
3095
|
+
type: 'aun-msg-send', argv, delegationToken,
|
|
3096
|
+
originSessionId: process.env.EVOLCORE_SESSION_ID, originMessageId: runtime?.messageId,
|
|
3097
|
+
}, null);
|
|
3098
|
+
if (!result)
|
|
3099
|
+
failManagedGroup(formatJson, 'AUN_DAEMON_UNAVAILABLE', 'Unable to connect to the EvolCore daemon');
|
|
3100
|
+
if (!result.ok)
|
|
3101
|
+
failManagedGroup(formatJson, result.code ?? 'AUN_SEND_FAILED', result.error ?? 'Send failed');
|
|
3102
|
+
if (formatJson)
|
|
3103
|
+
console.log(JSON.stringify(result));
|
|
3104
|
+
else if (result.handoff_id)
|
|
3105
|
+
console.log(`✓ handoff ${result.handoff_id} ${result.status === 'delivered' ? '已投递' : '已排队'} ${result.message_id ?? ''}`.trimEnd());
|
|
3106
|
+
else
|
|
3107
|
+
console.log(`✓ 已发送 message_id=${result.message_id ?? '-'} seq=${result.seq ?? result.message?.seq ?? '-'}`);
|
|
3108
|
+
}
|
|
3109
|
+
export function resolveManagedSendArgv(argv) {
|
|
3110
|
+
if (!['msg', 'group'].includes(argv[0]) || argv[1] !== 'send' || !validSendAid(argv[2]))
|
|
3111
|
+
throw new Error('Invalid managed send command');
|
|
3112
|
+
const scope = argv[0];
|
|
3113
|
+
const aid = argv[2];
|
|
3114
|
+
const to = scope === 'group' ? normalizeGroupAid(argv[3], aid.slice(aid.indexOf('.') + 1)) : argv[3];
|
|
3115
|
+
if (!to || !validSendAid(to))
|
|
3116
|
+
throw new Error('Invalid send target');
|
|
3117
|
+
const values = new Map();
|
|
3118
|
+
const booleans = new Set();
|
|
3119
|
+
const text = [];
|
|
3120
|
+
const mentions = [];
|
|
3121
|
+
const valueFlags = new Set(['--format', '--file', '--payload', '--text-from-file', '--link', '--title', '--description', '--as', '--content-type', '--text', '--transcript', '--thread', '--check-every', '--trigger-prompt', '--return', '--handoff-context', '--mention']);
|
|
3122
|
+
const booleanFlags = new Set(['--encrypt', '--no-encrypt', '--mention-all']);
|
|
3123
|
+
for (let index = 4; index < argv.length; index++) {
|
|
3124
|
+
const flag = argv[index];
|
|
3125
|
+
if (flag === '--') {
|
|
3126
|
+
text.push(...argv.slice(index + 1));
|
|
3127
|
+
break;
|
|
3128
|
+
}
|
|
3129
|
+
if (flag === '--app' || flag === '--aun-path')
|
|
3130
|
+
throw new Error(`${flag} is not allowed for managed sends`);
|
|
3131
|
+
if (booleanFlags.has(flag))
|
|
3132
|
+
booleans.add(flag);
|
|
3133
|
+
else if (valueFlags.has(flag)) {
|
|
3134
|
+
const value = argv[++index];
|
|
3135
|
+
if (value === undefined || valueFlags.has(value) || booleanFlags.has(value))
|
|
3136
|
+
throw new Error(`${flag} requires a value`);
|
|
3137
|
+
if (flag === '--mention') {
|
|
3138
|
+
for (const mention of value.split(',').map(item => item.trim()).filter(Boolean)) {
|
|
3139
|
+
if (!validSendAid(mention))
|
|
3140
|
+
throw new Error('Invalid mention AID');
|
|
3141
|
+
mentions.push({ aid: mention });
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
else {
|
|
3145
|
+
if (values.has(flag))
|
|
3146
|
+
throw new Error(`Duplicate send option: ${flag}`);
|
|
3147
|
+
values.set(flag, value);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
else
|
|
3151
|
+
text.push(flag);
|
|
3152
|
+
}
|
|
3153
|
+
if (booleans.has('--encrypt') && booleans.has('--no-encrypt'))
|
|
3154
|
+
throw new Error('Encryption options are mutually exclusive');
|
|
3155
|
+
if (values.has('--format') && values.get('--format') !== 'json')
|
|
3156
|
+
throw new Error('--format only supports json');
|
|
3157
|
+
const returnPolicy = values.get('--return');
|
|
3158
|
+
if (returnPolicy !== undefined && !['none', 'required'].includes(returnPolicy))
|
|
3159
|
+
throw new Error('Invalid return policy');
|
|
3160
|
+
const handoffContext = values.get('--handoff-context');
|
|
3161
|
+
if (handoffContext !== undefined && (scope !== 'msg' || !handoffContext.trim() || handoffContext.length > 65536))
|
|
3162
|
+
throw new Error('Invalid private handoff context');
|
|
3163
|
+
const thread = values.get('--thread');
|
|
3164
|
+
const checkEvery = values.get('--check-every');
|
|
3165
|
+
const triggerPromptExtra = values.get('--trigger-prompt');
|
|
3166
|
+
if (!thread && (checkEvery !== undefined || triggerPromptExtra !== undefined))
|
|
3167
|
+
throw new Error('Cross-thread options require --thread');
|
|
3168
|
+
let checkEveryMs;
|
|
3169
|
+
if (thread) {
|
|
3170
|
+
if (checkEvery === undefined)
|
|
3171
|
+
throw new Error('Cross-thread sends require --check-every');
|
|
3172
|
+
const parsedCheckEveryMs = parseDuration(checkEvery);
|
|
3173
|
+
if (parsedCheckEveryMs === null)
|
|
3174
|
+
throw new Error('Invalid --check-every duration');
|
|
3175
|
+
checkEveryMs = parsedCheckEveryMs;
|
|
3176
|
+
}
|
|
3177
|
+
if (scope === 'msg' && (mentions.length || booleans.has('--mention-all')))
|
|
3178
|
+
throw new Error('Mentions require a group send');
|
|
3179
|
+
if (scope === 'group' && (values.has('--link') || values.has('--text-from-file')))
|
|
3180
|
+
throw new Error('Unsupported group send body');
|
|
3181
|
+
const modes = ['--file', '--payload', '--text-from-file', '--link'].filter(flag => values.has(flag));
|
|
3182
|
+
if (modes.length > 1 || (modes.length && text.length))
|
|
3183
|
+
throw new Error('Message body modes are mutually exclusive');
|
|
3184
|
+
let payload;
|
|
3185
|
+
let file;
|
|
3186
|
+
if (values.has('--file')) {
|
|
3187
|
+
const as = values.get('--as');
|
|
3188
|
+
if (as && !['image', 'video', 'voice', 'file'].includes(as))
|
|
3189
|
+
throw new Error('Invalid attachment type');
|
|
3190
|
+
if (values.has('--transcript') && as !== 'voice')
|
|
3191
|
+
throw new Error('--transcript requires --as voice');
|
|
3192
|
+
file = { filePath: values.get('--file'), as, contentType: values.get('--content-type'), text: values.get('--text'), transcript: values.get('--transcript') };
|
|
3193
|
+
}
|
|
3194
|
+
else if (values.has('--payload')) {
|
|
3195
|
+
payload = JSON.parse(values.get('--payload'));
|
|
3196
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
3197
|
+
throw new Error('--payload must be a JSON object');
|
|
3198
|
+
}
|
|
3199
|
+
else if (values.has('--link')) {
|
|
3200
|
+
assertEcSingleLine(values.get('--title'), '--title');
|
|
3201
|
+
payload = { type: 'link', url: values.get('--link'), ...(values.get('--title') ? { title: values.get('--title') } : {}),
|
|
3202
|
+
...(values.get('--description') ? { description: values.get('--description') } : {}) };
|
|
3203
|
+
}
|
|
3204
|
+
else if (!values.has('--text-from-file')) {
|
|
3205
|
+
if (!text.join(' '))
|
|
3206
|
+
throw new Error('Message content is required');
|
|
3207
|
+
payload = { type: 'text', text: text.join(' ') };
|
|
3208
|
+
}
|
|
3209
|
+
if (!file && ['--as', '--content-type', '--text', '--transcript'].some(flag => values.has(flag)))
|
|
3210
|
+
throw new Error('Attachment options require --file');
|
|
3211
|
+
if (booleans.has('--mention-all'))
|
|
3212
|
+
mentions.push({ scope: 'all' });
|
|
3213
|
+
return { aid, to, scope, payload, file, textFile: values.get('--text-from-file'),
|
|
3214
|
+
encrypt: booleans.has('--no-encrypt') ? false : booleans.has('--encrypt') ? true : undefined,
|
|
3215
|
+
thread, checkEvery, checkEveryMs: checkEveryMs ?? undefined, triggerPromptExtra, returnPolicy: returnPolicy, handoffContext,
|
|
3216
|
+
mentions: mentions.length ? mentions : undefined };
|
|
3217
|
+
}
|
|
2740
3218
|
/**
|
|
2741
3219
|
* 收集位置参数(从 startIdx 开始)。
|
|
2742
3220
|
*
|
|
@@ -2756,7 +3234,9 @@ const VALUE_FLAGS = new Set([
|
|
|
2756
3234
|
'--as', '--content-type', '--mention', '--visibility', '--join-mode',
|
|
2757
3235
|
'--group-id', '--name', '--message', '--answer', '--page', '--size',
|
|
2758
3236
|
'--member-type',
|
|
2759
|
-
'--aun-path', '--thread', '--
|
|
3237
|
+
'--aun-path', '--thread', '--handoff-context',
|
|
3238
|
+
// 跨话题发送的复查参数:漏登记会让 collectPositional 把它们当正文(正文污染)
|
|
3239
|
+
'--check-every', '--trigger-prompt',
|
|
2760
3240
|
]);
|
|
2761
3241
|
const BOOLEAN_FLAGS = new Set([
|
|
2762
3242
|
'--encrypt', '--no-encrypt', '--mention-all', '--clear',
|