evolcore 0.0.2 → 0.0.3
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 +44 -793
- package/dist/agents/claude-runner.js +197 -17
- package/dist/agents/codex-runner.js +46 -3
- package/dist/aun/outbox.js +8 -0
- package/dist/channels/aun.js +21 -4
- package/dist/channels/contact-bind-code.js +134 -0
- package/dist/channels/dingtalk.js +979 -149
- package/dist/channels/feishu.js +130 -54
- package/dist/channels/wecom-card.js +101 -0
- package/dist/channels/wecom-onboarding.js +82 -0
- package/dist/channels/wecom-state.js +191 -0
- package/dist/channels/wecom.js +755 -163
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +88 -33
- package/dist/cli/bench.js +2 -2
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +77 -214
- package/dist/cli/handoff-command.js +2 -2
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +92 -97
- package/dist/cli/init.js +63 -26
- package/dist/cli/model.js +2 -1
- package/dist/cli/net-check.js +2 -2
- package/dist/cli/queue-command.js +30 -6
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +5 -6
- package/dist/cli/restart-monitor.js +25 -1
- package/dist/cli/stats.js +6 -4
- package/dist/cli/trigger-command.js +55 -15
- package/dist/cli/version.js +6 -1
- package/dist/config/builtin-role-templates.js +22 -10
- package/dist/config/builtin-roles.js +7 -1
- package/dist/config/config-manager.js +221 -17
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-book-store.js +454 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +156 -303
- package/dist/config/contact-operation-service.js +110 -0
- package/dist/config/peer-role-resolver.js +133 -54
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-service.js +16 -19
- package/dist/config/role-store.js +16 -5
- package/dist/config/roles.js +10 -1
- package/dist/config-store.js +0 -2
- package/dist/core/auth/authorization-audit.js +10 -1
- package/dist/core/auth/operation-authorizer.js +2 -0
- package/dist/core/auth/operation-catalog.js +56 -0
- package/dist/core/command/command-handler.js +105 -10
- package/dist/core/command/connect-menu.js +374 -0
- package/dist/core/command/menu-handler.js +114 -16
- package/dist/core/command/role-menu.js +128 -29
- package/dist/core/command/slash-handler.js +28 -18
- package/dist/core/daemon-file-cache.js +12 -6
- package/dist/core/event-catalog.js +70 -0
- package/dist/core/evolagent-registry.js +0 -1
- package/dist/core/evolagent.js +20 -9
- package/dist/core/message/im-renderer.js +2 -0
- package/dist/core/message/message-bridge.js +79 -8
- package/dist/core/message/message-queue.js +10 -0
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/response-engine.js +269 -25
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- package/dist/core/permission/tool-policy.js +47 -15
- package/dist/core/protected-paths.js +2 -0
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +61 -5
- package/dist/index.js +62 -6
- package/dist/ipc.js +34 -5
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +77 -2
- package/dist/trigger/patch.js +8 -1
- package/dist/trigger/scheduler.js +3 -1
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/media-cache.js +4 -1
- package/dist/utils/model-prices.jsonl +6 -3
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/evolcore/INDEX.md +5 -3
- package/kits/docs/evolcore/agent.md +9 -1
- package/kits/docs/evolcore/aid.md +5 -2
- package/kits/docs/evolcore/contact.md +57 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group.md +12 -3
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +9 -3
- package/kits/docs/evolcore/response.md +16 -21
- package/kits/docs/evolcore/rpc.md +2 -0
- package/kits/docs/evolcore/stats.md +15 -2
- package/kits/docs/evolcore/storage.md +1 -0
- package/kits/docs/evolcore/trigger.md +17 -2
- package/kits/eck_manifest.json +12 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/commands.md +3 -1
- package/package.json +4 -4
- package/assets/brand/evolcore/README.md +0 -19
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +0 -14
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +0 -10
|
@@ -13,12 +13,16 @@ function decodeSegment(s) {
|
|
|
13
13
|
/**
|
|
14
14
|
* 计算 chat 目录的完整路径。
|
|
15
15
|
* - aun: sessionsDir/aun/<urlEncode(selfAID|'_unknown')>/<urlEncode(channelId)>/
|
|
16
|
+
* - wecom: sessionsDir/wecom/<urlEncode(channelKey)>/<urlEncode(channelId)>/
|
|
16
17
|
* - 其它: sessionsDir/<channelType>/<urlEncode(channelId)>/
|
|
17
18
|
*/
|
|
18
|
-
export function chatDirPath(sessionsDir, channelType, channelId, selfAID) {
|
|
19
|
+
export function chatDirPath(sessionsDir, channelType, channelId, selfAID, channelKey) {
|
|
19
20
|
if (channelType === 'aun') {
|
|
20
21
|
return path.join(sessionsDir, channelType, encodeSegment(selfAID || '_unknown'), encodeSegment(channelId));
|
|
21
22
|
}
|
|
23
|
+
if (channelType === 'wecom' && channelKey?.startsWith('wecom#')) {
|
|
24
|
+
return path.join(sessionsDir, channelType, encodeSegment(channelKey), encodeSegment(channelId));
|
|
25
|
+
}
|
|
22
26
|
return path.join(sessionsDir, channelType, encodeSegment(channelId));
|
|
23
27
|
}
|
|
24
28
|
/** 解码目录段(用于扫描时把目录名还原为原始 channelId/selfAID) */
|
|
@@ -126,6 +130,7 @@ export function readAllJsonlLines(filePath) {
|
|
|
126
130
|
/**
|
|
127
131
|
* 扫描所有 chat 目录。
|
|
128
132
|
* - aun: channelType / selfAID / channelId (3层)
|
|
133
|
+
* - wecom: channelType / channelKey / channelId (3层;兼容旧的2层目录)
|
|
129
134
|
* - 其它: channelType / channelId (2层)
|
|
130
135
|
*/
|
|
131
136
|
export function scanChatDirs(sessionsDir) {
|
|
@@ -176,6 +181,42 @@ export function scanChatDirs(sessionsDir) {
|
|
|
176
181
|
}
|
|
177
182
|
}
|
|
178
183
|
}
|
|
184
|
+
else if (channelType === 'wecom') {
|
|
185
|
+
for (const entry of level2Entries) {
|
|
186
|
+
if (!entry.isDirectory())
|
|
187
|
+
continue;
|
|
188
|
+
const decoded = decodeSegment(entry.name);
|
|
189
|
+
const entryDir = path.join(typeDir, entry.name);
|
|
190
|
+
if (decoded.startsWith('wecom#') && decoded.split('#').length === 3) {
|
|
191
|
+
let chatEntries;
|
|
192
|
+
try {
|
|
193
|
+
chatEntries = fs.readdirSync(entryDir, { withFileTypes: true });
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
for (const chatEntry of chatEntries) {
|
|
199
|
+
if (!chatEntry.isDirectory())
|
|
200
|
+
continue;
|
|
201
|
+
results.push({
|
|
202
|
+
channelType,
|
|
203
|
+
selfAID: '',
|
|
204
|
+
channelKey: decoded,
|
|
205
|
+
channelId: decodeSegment(chatEntry.name),
|
|
206
|
+
dirPath: path.join(entryDir, chatEntry.name),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
results.push({
|
|
212
|
+
channelType,
|
|
213
|
+
selfAID: '',
|
|
214
|
+
channelId: decoded,
|
|
215
|
+
dirPath: entryDir,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
179
220
|
else {
|
|
180
221
|
// 2-layer: channelType/channelId
|
|
181
222
|
for (const chatEntry of level2Entries) {
|
|
@@ -205,8 +246,8 @@ export function scanMetaFiles(chatDir) {
|
|
|
205
246
|
throw e;
|
|
206
247
|
}
|
|
207
248
|
}
|
|
208
|
-
export function ensureChatDir(sessionsDir, channelType, channelId, selfAID) {
|
|
209
|
-
const dir = chatDirPath(sessionsDir, channelType, channelId, selfAID);
|
|
249
|
+
export function ensureChatDir(sessionsDir, channelType, channelId, selfAID, channelKey) {
|
|
250
|
+
const dir = chatDirPath(sessionsDir, channelType, channelId, selfAID, channelKey);
|
|
210
251
|
fs.mkdirSync(dir, { recursive: true });
|
|
211
252
|
fs.mkdirSync(path.join(dir, '_threads'), { recursive: true });
|
|
212
253
|
fs.mkdirSync(path.join(dir, '_trash'), { recursive: true });
|
|
@@ -23,6 +23,7 @@ export class SessionManager {
|
|
|
23
23
|
this.eventBus = eventBus;
|
|
24
24
|
this.identityResolver = identityResolver;
|
|
25
25
|
this.migrateChannelKeyFormat();
|
|
26
|
+
this.migrateWecomChannelDirs();
|
|
26
27
|
}
|
|
27
28
|
setIdentityResolver(resolver) {
|
|
28
29
|
this.identityResolver = resolver;
|
|
@@ -69,20 +70,20 @@ export class SessionManager {
|
|
|
69
70
|
* 需要明确的 channelType + selfAID 才能确定路径。
|
|
70
71
|
*/
|
|
71
72
|
resolveChatDir(channel, channelId, channelType, selfAID) {
|
|
72
|
-
return chatDirPath(this.sessionsDir, channelType, channelId, selfAID);
|
|
73
|
+
return chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel);
|
|
73
74
|
}
|
|
74
75
|
/**
|
|
75
76
|
* 给定明确的 channelType + selfAID 时直接计算路径(不扫描)。
|
|
76
77
|
* 用于 caller 已经知道完整路由信息的场景(如 message-bridge 透传)。
|
|
77
78
|
*/
|
|
78
79
|
resolveChatDirExact(channel, channelId, channelType, selfAID) {
|
|
79
|
-
return chatDirPath(this.sessionsDir, channelType, channelId, selfAID);
|
|
80
|
+
return chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel);
|
|
80
81
|
}
|
|
81
82
|
resolveChatDirFromSession(session) {
|
|
82
83
|
if (!session.channelType) {
|
|
83
84
|
throw new Error(`[SessionManager] missing channelType for session ${session.id}`);
|
|
84
85
|
}
|
|
85
|
-
return chatDirPath(this.sessionsDir, session.channelType, session.channelId, session.selfAID);
|
|
86
|
+
return chatDirPath(this.sessionsDir, session.channelType, session.channelId, session.selfAID, session.channel);
|
|
86
87
|
}
|
|
87
88
|
deriveChannelIdentity(channel, channelId) {
|
|
88
89
|
const parsed = tryParseChannelKey(channel);
|
|
@@ -253,7 +254,7 @@ export class SessionManager {
|
|
|
253
254
|
}
|
|
254
255
|
ensureChatDirForSession(session) {
|
|
255
256
|
const channelType = session.channelType || session.channel;
|
|
256
|
-
return ensureChatDir(this.sessionsDir, channelType, session.channelId, session.selfAID);
|
|
257
|
+
return ensureChatDir(this.sessionsDir, channelType, session.channelId, session.selfAID, session.channel);
|
|
257
258
|
}
|
|
258
259
|
metaFilePath(chatDir, sessionId) {
|
|
259
260
|
return path.join(chatDir, `${sessionId}.jsonl`);
|
|
@@ -678,7 +679,7 @@ export class SessionManager {
|
|
|
678
679
|
getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, baseagent, selfAID, channelType, peerType, chatType) {
|
|
679
680
|
// 使用精确路径(channelType + selfAID)
|
|
680
681
|
const chatDir = (channelType && selfAID)
|
|
681
|
-
? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfAID); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
|
|
682
|
+
? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
|
|
682
683
|
: this.ensureResolvedChatDirSafe(channel, channelId, channelType, selfAID);
|
|
683
684
|
const threadIndex = readThreadIndex(chatDir);
|
|
684
685
|
const existingEntry = threadIndex[threadId];
|
|
@@ -1584,4 +1585,59 @@ export class SessionManager {
|
|
|
1584
1585
|
}
|
|
1585
1586
|
return null;
|
|
1586
1587
|
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Move legacy `sessions/wecom/<chatId>` directories under their persisted
|
|
1590
|
+
* channelKey. The stored SessionFile.channel is authoritative; ambiguous
|
|
1591
|
+
* directories are deliberately left untouched for manual recovery.
|
|
1592
|
+
*/
|
|
1593
|
+
migrateWecomChannelDirs() {
|
|
1594
|
+
const legacyDirs = scanChatDirs(this.sessionsDir)
|
|
1595
|
+
.filter(entry => entry.channelType === 'wecom' && !entry.channelKey);
|
|
1596
|
+
let migrated = 0;
|
|
1597
|
+
for (const entry of legacyDirs) {
|
|
1598
|
+
const candidates = [];
|
|
1599
|
+
const active = readJsonFile(path.join(entry.dirPath, 'active.json'));
|
|
1600
|
+
if (active)
|
|
1601
|
+
candidates.push(active);
|
|
1602
|
+
for (const metaFile of scanMetaFiles(entry.dirPath)) {
|
|
1603
|
+
const meta = readLastJsonlLine(path.join(entry.dirPath, metaFile));
|
|
1604
|
+
if (meta)
|
|
1605
|
+
candidates.push(meta);
|
|
1606
|
+
}
|
|
1607
|
+
const threadDir = path.join(entry.dirPath, '_threads');
|
|
1608
|
+
for (const metaFile of scanMetaFiles(threadDir)) {
|
|
1609
|
+
const meta = readLastJsonlLine(path.join(threadDir, metaFile));
|
|
1610
|
+
if (meta)
|
|
1611
|
+
candidates.push(meta);
|
|
1612
|
+
}
|
|
1613
|
+
const channelKeys = new Set(candidates
|
|
1614
|
+
.map(item => item.channel)
|
|
1615
|
+
.filter((value) => typeof value === 'string' && value.startsWith('wecom#')));
|
|
1616
|
+
if (channelKeys.size !== 1) {
|
|
1617
|
+
if (channelKeys.size > 1) {
|
|
1618
|
+
logger.error(`[SessionManager] Cannot auto-migrate mixed WeCom session directory: ${entry.dirPath}`);
|
|
1619
|
+
}
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
const channelKey = [...channelKeys][0];
|
|
1623
|
+
const target = chatDirPath(this.sessionsDir, 'wecom', entry.channelId, undefined, channelKey);
|
|
1624
|
+
if (target === entry.dirPath)
|
|
1625
|
+
continue;
|
|
1626
|
+
if (fs.existsSync(target)) {
|
|
1627
|
+
logger.error(`[SessionManager] Cannot migrate WeCom session directory because target exists: ${target}`);
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
try {
|
|
1631
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
1632
|
+
fs.renameSync(entry.dirPath, target);
|
|
1633
|
+
migrated++;
|
|
1634
|
+
}
|
|
1635
|
+
catch (error) {
|
|
1636
|
+
logger.error(`[SessionManager] Failed to migrate WeCom session directory ${entry.dirPath}: ${String(error)}`);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (migrated > 0) {
|
|
1640
|
+
logger.info(`[SessionManager] Migrated ${migrated} WeCom session director${migrated === 1 ? 'y' : 'ies'} to channelKey isolation`);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1587
1643
|
}
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { loadDefaults, loadAllAgents, migrateIdentitiesIfNeeded, loadDaemonConfi
|
|
|
16
16
|
import { ConfigTarget, initConfigManager, onConfigWrite } from './config/config-manager.js';
|
|
17
17
|
import { ensureRoleConfigV4OnStartup } from './config/role-config-v4-startup.js';
|
|
18
18
|
import { ensureRoleConfigV5OnStartup } from './config/role-config-v5-startup.js';
|
|
19
|
+
import { ensureContactBookV2OnStartup } from './config/contact-book-v2-startup.js';
|
|
19
20
|
import { shouldFailFastForMissingOwners } from './config/owner-policy.js';
|
|
20
21
|
import { isManagementRole } from './config/builtin-roles.js';
|
|
21
22
|
import { checkRoleAccess, getFirstStaticAgentOwner, resolvePeerRoleDetail, roleToSessionIdentity } from './config/peer-role-resolver.js';
|
|
@@ -33,7 +34,7 @@ import { startServiceProxy } from './aun/service-proxy.js';
|
|
|
33
34
|
import { BindService } from './utils/aid-bind.js';
|
|
34
35
|
import { DingtalkChannelPlugin, registerPendingDingtalkContactBind } from './channels/dingtalk.js';
|
|
35
36
|
import { QQBotChannelPlugin } from './channels/qqbot.js';
|
|
36
|
-
import { WecomChannelPlugin } from './channels/wecom.js';
|
|
37
|
+
import { WecomChannelPlugin, registerPendingWecomContactBind } from './channels/wecom.js';
|
|
37
38
|
import { buildEnvelope } from './core/message/message-utils.js';
|
|
38
39
|
import { ResponseEngine } from './core/message/response-engine.js';
|
|
39
40
|
import { MessageQueue } from './core/message/message-queue.js';
|
|
@@ -598,6 +599,9 @@ async function main() {
|
|
|
598
599
|
}
|
|
599
600
|
// 在登记 daemon 实例前自动备份并迁移旧角色配置;失败才阻止启动。
|
|
600
601
|
try {
|
|
602
|
+
// Crash recovery must precede schema gates and migrations so they never
|
|
603
|
+
// inspect or migrate a partially applied Role Menu transaction.
|
|
604
|
+
recoverAllRoleMutationsSync();
|
|
601
605
|
const migration = await ensureRoleConfigV4OnStartup();
|
|
602
606
|
if (migration) {
|
|
603
607
|
const msg = `✓ Role config v4 migration applied automatically: ${migration.changedFiles} file(s)` +
|
|
@@ -612,6 +616,13 @@ async function main() {
|
|
|
612
616
|
logger.info(msg);
|
|
613
617
|
console.error(msg);
|
|
614
618
|
}
|
|
619
|
+
const contactMigration = await ensureContactBookV2OnStartup();
|
|
620
|
+
if (contactMigration) {
|
|
621
|
+
const msg = `✓ Contact book v2 migration applied automatically: ${contactMigration.changedFiles} file(s)` +
|
|
622
|
+
(contactMigration.backup ? `; backup: ${contactMigration.backup}` : '');
|
|
623
|
+
logger.info(msg);
|
|
624
|
+
console.error(msg);
|
|
625
|
+
}
|
|
615
626
|
}
|
|
616
627
|
catch (e) {
|
|
617
628
|
const msg = `❌ ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -639,7 +650,6 @@ async function main() {
|
|
|
639
650
|
// ── 配置体系初始化(schema 字段不相交硬约束校验)──
|
|
640
651
|
try {
|
|
641
652
|
initConfigManager();
|
|
642
|
-
recoverAllRoleMutationsSync();
|
|
643
653
|
}
|
|
644
654
|
catch (e) {
|
|
645
655
|
const msg = `❌ 配置 schema 校验失败: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -870,7 +880,9 @@ async function main() {
|
|
|
870
880
|
const permissionGateway = new PermissionGateway();
|
|
871
881
|
permissionGateway.setEventBus(eventBus);
|
|
872
882
|
onConfigWrite(({ target, selector }) => {
|
|
873
|
-
if ((target !== ConfigTarget.Agent
|
|
883
|
+
if ((target !== ConfigTarget.Agent
|
|
884
|
+
&& target !== ConfigTarget.Contact
|
|
885
|
+
&& target !== ConfigTarget.Relation) || !selector.self)
|
|
874
886
|
return;
|
|
875
887
|
const cancelled = permissionGateway.revalidatePendingApprovals(selector.self);
|
|
876
888
|
if (cancelled > 0) {
|
|
@@ -1061,6 +1073,43 @@ async function main() {
|
|
|
1061
1073
|
baseagent: primaryAgent?.baseagent || 'codex',
|
|
1062
1074
|
projectPath: primaryAgent?.projectPath || process.cwd(),
|
|
1063
1075
|
};
|
|
1076
|
+
const triggerBaseagentsForAgent = (agentAid) => {
|
|
1077
|
+
const ownerName = agentRegistry.get(agentAid)?.name
|
|
1078
|
+
?? (agentAid === daemonTriggerOwner.aid ? primaryAgent?.name : undefined);
|
|
1079
|
+
if (!ownerName)
|
|
1080
|
+
return [];
|
|
1081
|
+
const prefix = `${ownerName}::`;
|
|
1082
|
+
return [...agentMap.keys()]
|
|
1083
|
+
.filter(key => key.startsWith(prefix))
|
|
1084
|
+
.map(key => key.slice(prefix.length));
|
|
1085
|
+
};
|
|
1086
|
+
const materializeTriggerCreateBaseagent = (input, agentAid) => {
|
|
1087
|
+
const rawExecution = input.execution;
|
|
1088
|
+
if (!rawExecution || typeof rawExecution !== 'object' || Array.isArray(rawExecution))
|
|
1089
|
+
return input;
|
|
1090
|
+
const execution = rawExecution;
|
|
1091
|
+
if (execution.type !== 'trigger_session') {
|
|
1092
|
+
if (execution.baseagent !== undefined) {
|
|
1093
|
+
throw new Error('execution.baseagent is only allowed when execution.type=trigger_session');
|
|
1094
|
+
}
|
|
1095
|
+
return input;
|
|
1096
|
+
}
|
|
1097
|
+
if (execution.baseagent !== undefined
|
|
1098
|
+
&& (typeof execution.baseagent !== 'string' || !execution.baseagent.trim())) {
|
|
1099
|
+
throw new Error('execution.baseagent must be a non-empty string');
|
|
1100
|
+
}
|
|
1101
|
+
const baseagent = typeof execution.baseagent === 'string'
|
|
1102
|
+
? execution.baseagent.trim()
|
|
1103
|
+
: agentRegistry.get(agentAid)?.baseagent
|
|
1104
|
+
?? (agentAid === daemonTriggerOwner.aid ? daemonTriggerOwner.baseagent : undefined);
|
|
1105
|
+
if (!baseagent)
|
|
1106
|
+
throw new Error('unable to resolve Trigger session baseagent');
|
|
1107
|
+
const available = triggerBaseagentsForAgent(agentAid);
|
|
1108
|
+
if (!available.includes(baseagent)) {
|
|
1109
|
+
throw new Error(`Trigger baseagent unavailable for ${agentAid}: ${baseagent} (available: ${available.join(', ') || 'none'})`);
|
|
1110
|
+
}
|
|
1111
|
+
return { ...input, execution: { ...execution, baseagent } };
|
|
1112
|
+
};
|
|
1064
1113
|
const getTriggerChannel = (agentAid, channelKey) => {
|
|
1065
1114
|
if (agentAid === daemonTriggerOwner.aid && channelKey === daemonChannel.channelKey) {
|
|
1066
1115
|
return {
|
|
@@ -1117,7 +1166,7 @@ async function main() {
|
|
|
1117
1166
|
const agent = agentRegistry.get(definition.agentAid);
|
|
1118
1167
|
const modelDecision = validateModelSelectionForRole({
|
|
1119
1168
|
role: actor.role,
|
|
1120
|
-
baseagent: agent?.baseagent,
|
|
1169
|
+
baseagent: definition.execution.baseagent ?? agent?.baseagent,
|
|
1121
1170
|
requestedModel: definition.execution.model,
|
|
1122
1171
|
selfAid: definition.agentAid,
|
|
1123
1172
|
});
|
|
@@ -1125,7 +1174,9 @@ async function main() {
|
|
|
1125
1174
|
throw new Error(modelDecision.message || 'trigger model is not allowed by current role');
|
|
1126
1175
|
}
|
|
1127
1176
|
if (definition.execution.effort) {
|
|
1128
|
-
const baseagent =
|
|
1177
|
+
const baseagent = definition.execution.baseagent
|
|
1178
|
+
?? agentRegistry.get(definition.agentAid)?.baseagent
|
|
1179
|
+
?? 'claude';
|
|
1129
1180
|
const effortDecision = validateRuntimeStringFieldOverride({
|
|
1130
1181
|
selfAid: definition.agentAid,
|
|
1131
1182
|
role: actor.role,
|
|
@@ -2132,8 +2183,12 @@ async function main() {
|
|
|
2132
2183
|
ipcServer.setDingtalkContactBindExecutor({
|
|
2133
2184
|
register: (cmd) => registerPendingDingtalkContactBind(cmd),
|
|
2134
2185
|
});
|
|
2186
|
+
ipcServer.setWecomContactBindExecutor({
|
|
2187
|
+
register: (cmd) => registerPendingWecomContactBind(cmd),
|
|
2188
|
+
});
|
|
2135
2189
|
ipcServer.setMenuExecutor((payload, auth) => cmdHandler.execMenuForEcweb(payload, auth));
|
|
2136
2190
|
ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken));
|
|
2191
|
+
ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken));
|
|
2137
2192
|
cmdHandler.setDaemonStatusProvider(() => {
|
|
2138
2193
|
const aidState = controlChannel?.getAidState?.();
|
|
2139
2194
|
return {
|
|
@@ -2708,7 +2763,8 @@ async function main() {
|
|
|
2708
2763
|
}
|
|
2709
2764
|
const agentAid = requireAgent(rawDefinition.agentAid);
|
|
2710
2765
|
const actor = await authorizeTrigger(agentAid, 'trigger.create');
|
|
2711
|
-
const
|
|
2766
|
+
const materialized = materializeTriggerCreateBaseagent({ ...rawDefinition, origin: actor.origin }, agentAid);
|
|
2767
|
+
const definition = normalizeTriggerDefinition(materialized);
|
|
2712
2768
|
validateTriggerDefinitionForActor(definition, actor);
|
|
2713
2769
|
requireAgent(definition.agentAid);
|
|
2714
2770
|
validateTriggerFeedbackChannels(definition);
|
package/dist/ipc.js
CHANGED
|
@@ -8,6 +8,7 @@ import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js
|
|
|
8
8
|
import { resolvePaths } from './paths.js';
|
|
9
9
|
import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-introspect.js';
|
|
10
10
|
import { classifyProcessTree, ProcessTreeSampler } from './utils/process-tree-stats.js';
|
|
11
|
+
import { readSystemMemoryUsage } from './utils/system-memory.js';
|
|
11
12
|
const isWindows = process.platform === 'win32';
|
|
12
13
|
const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
|
|
13
14
|
export function summarizeMonitorAgents(agents, aids) {
|
|
@@ -45,7 +46,9 @@ export class IpcServer {
|
|
|
45
46
|
handoffTraceExecutor;
|
|
46
47
|
bindExecutor;
|
|
47
48
|
configOperationExecutor;
|
|
49
|
+
contactOperationExecutor;
|
|
48
50
|
dingtalkContactBindExecutor;
|
|
51
|
+
wecomContactBindExecutor;
|
|
49
52
|
// CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
|
|
50
53
|
// 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
|
|
51
54
|
// procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
|
|
@@ -75,6 +78,9 @@ export class IpcServer {
|
|
|
75
78
|
setConfigOperationExecutor(executor) {
|
|
76
79
|
this.configOperationExecutor = executor;
|
|
77
80
|
}
|
|
81
|
+
setContactOperationExecutor(executor) {
|
|
82
|
+
this.contactOperationExecutor = executor;
|
|
83
|
+
}
|
|
78
84
|
/** Inject AUN AID state aggregator for aun-aids IPC handler */
|
|
79
85
|
setAunAidProvider(provider) {
|
|
80
86
|
this.aunAidProvider = provider;
|
|
@@ -136,6 +142,10 @@ export class IpcServer {
|
|
|
136
142
|
setDingtalkContactBindExecutor(executor) {
|
|
137
143
|
this.dingtalkContactBindExecutor = executor;
|
|
138
144
|
}
|
|
145
|
+
/** Inject WeCom contact binding-code executor. */
|
|
146
|
+
setWecomContactBindExecutor(executor) {
|
|
147
|
+
this.wecomContactBindExecutor = executor;
|
|
148
|
+
}
|
|
139
149
|
/** Start the 1s background CPU sampling loop (for monitor-snapshot). Call after start(). */
|
|
140
150
|
startCpuTracking() {
|
|
141
151
|
if (this.cpuTimer)
|
|
@@ -321,6 +331,16 @@ export class IpcServer {
|
|
|
321
331
|
primaryId: cmd.primaryId,
|
|
322
332
|
});
|
|
323
333
|
}
|
|
334
|
+
case 'wecom.contact-bind.register': {
|
|
335
|
+
if (!this.wecomContactBindExecutor) {
|
|
336
|
+
return { ok: false, error: 'wecom contact bind executor not configured' };
|
|
337
|
+
}
|
|
338
|
+
return this.wecomContactBindExecutor.register({
|
|
339
|
+
selfAid: cmd.selfAid,
|
|
340
|
+
channelName: cmd.channelName,
|
|
341
|
+
primaryId: cmd.primaryId,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
324
344
|
case 'aun-aids': {
|
|
325
345
|
const aids = this.aunAidProvider ? this.aunAidProvider() : [];
|
|
326
346
|
return { ok: true, aids };
|
|
@@ -527,6 +547,18 @@ export class IpcServer {
|
|
|
527
547
|
}
|
|
528
548
|
return await this.configOperationExecutor(argv, sessionId, delegationToken);
|
|
529
549
|
}
|
|
550
|
+
case 'contact.op': {
|
|
551
|
+
if (!this.contactOperationExecutor)
|
|
552
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'contact.op not configured' };
|
|
553
|
+
const { argv, sessionId, delegationToken } = cmd;
|
|
554
|
+
if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
|
|
555
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
|
|
556
|
+
}
|
|
557
|
+
if (delegationToken !== undefined && typeof delegationToken !== 'string') {
|
|
558
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
|
|
559
|
+
}
|
|
560
|
+
return await this.contactOperationExecutor(argv, sessionId, delegationToken);
|
|
561
|
+
}
|
|
530
562
|
case 'trigger.list':
|
|
531
563
|
case 'trigger.show':
|
|
532
564
|
case 'trigger.history':
|
|
@@ -633,8 +665,7 @@ export class IpcServer {
|
|
|
633
665
|
case 'monitor-snapshot': {
|
|
634
666
|
// watch web Monitor 页用:进程级 + 系统级运行指标 + 全局 stats + per-agent 汇总。
|
|
635
667
|
const mem = process.memoryUsage();
|
|
636
|
-
const
|
|
637
|
-
const freeMem = os.freemem();
|
|
668
|
+
const systemMemory = readSystemMemoryUsage();
|
|
638
669
|
const aids = this.aunAidProvider ? this.aunAidProvider() : [];
|
|
639
670
|
const aidStats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
|
|
640
671
|
const statsMap = new Map(aidStats.map((s) => [s.aid, s]));
|
|
@@ -686,9 +717,7 @@ export class IpcServer {
|
|
|
686
717
|
processTree: processTreeDetails,
|
|
687
718
|
// 系统级:整机
|
|
688
719
|
system: {
|
|
689
|
-
|
|
690
|
-
memUsed: totalMem - freeMem,
|
|
691
|
-
memFree: freeMem,
|
|
720
|
+
...systemMemory,
|
|
692
721
|
cpuPercent: Math.round(this.sysCpuPercent * 10) / 10,
|
|
693
722
|
loadAvg: os.loadavg(), // [1m, 5m, 15m](Windows 恒 0)
|
|
694
723
|
},
|
package/dist/stats/billing.js
CHANGED
|
@@ -11,14 +11,20 @@ let _priceCacheTs = 0;
|
|
|
11
11
|
const PRICE_CACHE_TTL = 5 * 60 * 1000;
|
|
12
12
|
/**
|
|
13
13
|
* 从包路径 + 用户路径合并读取 JSONL 文件。
|
|
14
|
-
*
|
|
14
|
+
* 包基线随构建产物发布在 dist/utils,源码运行时回退到 src/utils;
|
|
15
|
+
* 用户路径($EVOLCORE_HOME/data/stats/)为追加/覆盖。
|
|
15
16
|
* 两层合并(append),用户层行追加在包层之后——查价时 effective_from 越大越优先,天然正确。
|
|
16
17
|
*/
|
|
17
18
|
function _loadJsonlMerged(evolcoreHome, filename) {
|
|
18
19
|
const results = [];
|
|
19
20
|
// 1. 包路径(基线)
|
|
20
|
-
const
|
|
21
|
-
|
|
21
|
+
const packageRoot = getPackageRoot();
|
|
22
|
+
const pkgFile = [
|
|
23
|
+
path.join(packageRoot, 'dist', 'utils', filename),
|
|
24
|
+
path.join(packageRoot, 'src', 'utils', filename),
|
|
25
|
+
path.join(packageRoot, 'data', 'stats', filename),
|
|
26
|
+
].find(candidate => fs.existsSync(candidate));
|
|
27
|
+
if (pkgFile) {
|
|
22
28
|
try {
|
|
23
29
|
const lines = fs.readFileSync(pkgFile, 'utf-8').split('\n').filter(Boolean);
|
|
24
30
|
for (const l of lines)
|
|
@@ -28,7 +34,7 @@ function _loadJsonlMerged(evolcoreHome, filename) {
|
|
|
28
34
|
}
|
|
29
35
|
// 2. 用户路径(追加/覆盖)
|
|
30
36
|
const userFile = path.join(evolcoreHome, 'data', 'stats', filename);
|
|
31
|
-
if (fs.existsSync(userFile)) {
|
|
37
|
+
if (fs.existsSync(userFile) && (!pkgFile || path.resolve(userFile) !== path.resolve(pkgFile))) {
|
|
32
38
|
try {
|
|
33
39
|
const lines = fs.readFileSync(userFile, 'utf-8').split('\n').filter(Boolean);
|
|
34
40
|
for (const l of lines)
|
|
@@ -81,10 +87,16 @@ export function resolvePriceRow(evolcoreHome, model, ts) {
|
|
|
81
87
|
export const BILLING_FNS = {
|
|
82
88
|
// 通用 per-token(Claude / OpenAI 兼容 / Kimi / MiniMax)
|
|
83
89
|
per_token_v1: (e, p) => {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
const tiers = Array.isArray(p.tiers)
|
|
91
|
+
? p.tiers
|
|
92
|
+
: [];
|
|
93
|
+
const promptTokens = e.input_tokens + e.cache_creation_tokens + e.cache_read_tokens;
|
|
94
|
+
const tier = tiers.find(t => t.up_to_input_tokens == null || promptTokens <= t.up_to_input_tokens);
|
|
95
|
+
const price = tier ?? p;
|
|
96
|
+
const r = (price.price_input ?? 0) * e.input_tokens / 1e6
|
|
97
|
+
+ (price.price_output ?? 0) * e.output_tokens / 1e6
|
|
98
|
+
+ (price.price_cache_creation ?? 0) * e.cache_creation_tokens / 1e6
|
|
99
|
+
+ (price.price_cache_read ?? 0) * e.cache_read_tokens / 1e6;
|
|
88
100
|
return p.currency === 'CNY' ? { cny: r } : { usd: r };
|
|
89
101
|
},
|
|
90
102
|
// DeepSeek cache_hit / cache_miss 口径
|
package/dist/trigger/manager.js
CHANGED
|
@@ -72,6 +72,9 @@ export class TriggerDefinitionManager {
|
|
|
72
72
|
...(origin ? { origin } : {}),
|
|
73
73
|
}, { now: Date.now() });
|
|
74
74
|
this.assertAgent(updated);
|
|
75
|
+
if (existing.execution.baseagent !== updated.execution.baseagent) {
|
|
76
|
+
throw new Error('execution.baseagent is immutable; create a new Trigger to use another baseagent');
|
|
77
|
+
}
|
|
75
78
|
updated.createdAt = existing.createdAt;
|
|
76
79
|
updated.updatedAt = Date.now();
|
|
77
80
|
this.assertUnique(updated, triggerId);
|
package/dist/trigger/parser.js
CHANGED
|
@@ -5,6 +5,14 @@ const TRIGGER_UPDATE_FLAGS = new Set([
|
|
|
5
5
|
'model', 'effort', 'permission',
|
|
6
6
|
'max-runs', 'max-duration', 'concurrency', 'missed-policy',
|
|
7
7
|
]);
|
|
8
|
+
const TRIGGER_CREATE_FLAGS = new Set([
|
|
9
|
+
'once', 'delay', 'at', 'cron', 'every', 'event', 'tz',
|
|
10
|
+
'exec', 'prompt', 'name', 'agent',
|
|
11
|
+
'script-path', 'script-runtime', 'script-args', 'script-timeout',
|
|
12
|
+
'feedback', 'target-channel', 'target-channel-id', 'target-session', 'target-thread-id',
|
|
13
|
+
'trigger-thread', 'baseagent',
|
|
14
|
+
'model', 'effort', 'permission', 'max-runs', 'max-duration',
|
|
15
|
+
]);
|
|
8
16
|
// Note: unquoted multi-word values (e.g. --prompt=hello world) are not supported.
|
|
9
17
|
// The second word would be treated as an unknown token. Always quote multi-word values:
|
|
10
18
|
// --prompt "hello world" or --prompt='hello world'
|
|
@@ -103,6 +111,14 @@ function parseModelFlag(flags) {
|
|
|
103
111
|
return { ok: false, error: '--model 不能为空' };
|
|
104
112
|
return { ok: true, value: raw };
|
|
105
113
|
}
|
|
114
|
+
function parseBaseagentFlag(flags) {
|
|
115
|
+
if (!flags.has('baseagent'))
|
|
116
|
+
return { ok: true };
|
|
117
|
+
const raw = flags.get('baseagent');
|
|
118
|
+
if (!raw || raw === true || !raw.trim())
|
|
119
|
+
return { ok: false, error: '--baseagent 不能为空' };
|
|
120
|
+
return { ok: true, value: raw.trim() };
|
|
121
|
+
}
|
|
106
122
|
function parseEffortFlag(flags) {
|
|
107
123
|
if (!flags.has('effort'))
|
|
108
124
|
return { ok: true };
|
|
@@ -251,6 +267,12 @@ function commonParsed(flags, opts = {}) {
|
|
|
251
267
|
const deprecated = rejectDeprecatedFlags(flags);
|
|
252
268
|
if (deprecated)
|
|
253
269
|
return { ok: false, error: deprecated };
|
|
270
|
+
if (!opts.update) {
|
|
271
|
+
for (const flag of flags.keys()) {
|
|
272
|
+
if (!TRIGGER_CREATE_FLAGS.has(flag))
|
|
273
|
+
return { ok: false, error: `create 不支持参数 --${flag}` };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
254
276
|
const source = parseSourceFlags(flags, opts);
|
|
255
277
|
if (!source.ok)
|
|
256
278
|
return source;
|
|
@@ -281,6 +303,9 @@ function commonParsed(flags, opts = {}) {
|
|
|
281
303
|
const model = parseModelFlag(flags);
|
|
282
304
|
if (!model.ok)
|
|
283
305
|
return model;
|
|
306
|
+
const baseagent = parseBaseagentFlag(flags);
|
|
307
|
+
if (!baseagent.ok)
|
|
308
|
+
return baseagent;
|
|
284
309
|
const effort = parseEffortFlag(flags);
|
|
285
310
|
if (!effort.ok)
|
|
286
311
|
return effort;
|
|
@@ -327,6 +352,9 @@ function commonParsed(flags, opts = {}) {
|
|
|
327
352
|
return { ok: false, error: '--target-channel 与 --target-channel-id 必须同时指定或同时省略' };
|
|
328
353
|
}
|
|
329
354
|
const inferredExecution = execution.value ?? (scriptPath ? 'script' : 'target_session');
|
|
355
|
+
if (baseagent.value && inferredExecution !== 'trigger_session') {
|
|
356
|
+
return { ok: false, error: '--baseagent 仅适用于 --exec trigger-session' };
|
|
357
|
+
}
|
|
330
358
|
if (inferredExecution === 'script') {
|
|
331
359
|
if (!scriptPath && !opts.update)
|
|
332
360
|
return { ok: false, error: '--exec script 需要 --script-path' };
|
|
@@ -357,6 +385,7 @@ function commonParsed(flags, opts = {}) {
|
|
|
357
385
|
scriptArgs,
|
|
358
386
|
scriptTimeoutMs: scriptTimeout.value,
|
|
359
387
|
triggerThread: triggerThread.value,
|
|
388
|
+
baseagent: baseagent.value,
|
|
360
389
|
maxRuns: maxRuns.value,
|
|
361
390
|
maxDuration: maxDuration.value,
|
|
362
391
|
model: model.value,
|
|
@@ -391,6 +420,46 @@ export function parseTriggerSet(args) {
|
|
|
391
420
|
scriptArgs: value.scriptArgs,
|
|
392
421
|
scriptTimeoutMs: value.scriptTimeoutMs,
|
|
393
422
|
triggerThread: value.triggerThread,
|
|
423
|
+
baseagent: value.baseagent,
|
|
424
|
+
maxRuns: value.maxRuns,
|
|
425
|
+
maxDuration: value.maxDuration,
|
|
426
|
+
model: value.model,
|
|
427
|
+
effort: value.effort,
|
|
428
|
+
permissionMode: value.permissionMode,
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
/** CLI argv parser. Unlike the slash-command string parser, this preserves
|
|
433
|
+
* shell-quoted values such as `--prompt "multi word text"` as one token. */
|
|
434
|
+
export function parseTriggerSetArgv(args) {
|
|
435
|
+
const parsedFlags = flagsFromArgv(args, 'create');
|
|
436
|
+
if (!parsedFlags.ok)
|
|
437
|
+
return parsedFlags;
|
|
438
|
+
const parsed = commonParsed(parsedFlags.flags);
|
|
439
|
+
if (!parsed.ok)
|
|
440
|
+
return parsed;
|
|
441
|
+
const value = parsed.value;
|
|
442
|
+
return {
|
|
443
|
+
ok: true,
|
|
444
|
+
value: {
|
|
445
|
+
scheduleType: value.scheduleType,
|
|
446
|
+
scheduleValue: value.scheduleValue ?? '',
|
|
447
|
+
timezone: value.timezone,
|
|
448
|
+
executionType: value.inferredExecution,
|
|
449
|
+
feedbackStrategy: value.feedbackStrategy,
|
|
450
|
+
targetChannel: value.targetChannel,
|
|
451
|
+
targetChannelId: value.targetChannelId,
|
|
452
|
+
targetSession: value.targetSession,
|
|
453
|
+
targetThreadId: value.targetThreadId,
|
|
454
|
+
agentId: value.agentId,
|
|
455
|
+
name: value.name,
|
|
456
|
+
prompt: value.prompt,
|
|
457
|
+
scriptPath: value.scriptPath,
|
|
458
|
+
scriptRuntime: value.scriptRuntime,
|
|
459
|
+
scriptArgs: value.scriptArgs,
|
|
460
|
+
scriptTimeoutMs: value.scriptTimeoutMs,
|
|
461
|
+
triggerThread: value.triggerThread,
|
|
462
|
+
baseagent: value.baseagent,
|
|
394
463
|
maxRuns: value.maxRuns,
|
|
395
464
|
maxDuration: value.maxDuration,
|
|
396
465
|
model: value.model,
|
|
@@ -426,11 +495,17 @@ export function parseTriggerUpdate(args) {
|
|
|
426
495
|
return parseTriggerUpdateFlags(nameOrId, flags);
|
|
427
496
|
}
|
|
428
497
|
export function parseTriggerUpdateArgv(nameOrId, args) {
|
|
498
|
+
const parsedFlags = flagsFromArgv(args, 'update');
|
|
499
|
+
if (!parsedFlags.ok)
|
|
500
|
+
return parsedFlags;
|
|
501
|
+
return parseTriggerUpdateFlags(nameOrId, parsedFlags.flags);
|
|
502
|
+
}
|
|
503
|
+
function flagsFromArgv(args, command) {
|
|
429
504
|
const flags = new Map();
|
|
430
505
|
for (let i = 0; i < args.length; i++) {
|
|
431
506
|
const arg = args[i];
|
|
432
507
|
if (!arg.startsWith('--'))
|
|
433
|
-
return { ok: false, error: `无法识别
|
|
508
|
+
return { ok: false, error: `无法识别 ${command} 参数: ${arg}` };
|
|
434
509
|
const equals = arg.indexOf('=');
|
|
435
510
|
if (equals > 2) {
|
|
436
511
|
flags.set(arg.slice(2, equals), arg.slice(equals + 1));
|
|
@@ -446,7 +521,7 @@ export function parseTriggerUpdateArgv(nameOrId, args) {
|
|
|
446
521
|
flags.set(key, true);
|
|
447
522
|
}
|
|
448
523
|
}
|
|
449
|
-
return
|
|
524
|
+
return { ok: true, flags };
|
|
450
525
|
}
|
|
451
526
|
function parseTriggerUpdateFlags(nameOrId, flags) {
|
|
452
527
|
if (flags.size === 0)
|
package/dist/trigger/patch.js
CHANGED
|
@@ -80,7 +80,7 @@ export function replaceEditableTriggerDefinition(existing, input) {
|
|
|
80
80
|
throw new Error('trigger definition update must be an object');
|
|
81
81
|
}
|
|
82
82
|
const editable = input;
|
|
83
|
-
|
|
83
|
+
const replacement = normalizeTriggerDefinition({
|
|
84
84
|
...editable,
|
|
85
85
|
$schema_version: TRIGGER_SCHEMA_VERSION,
|
|
86
86
|
id: existing.id,
|
|
@@ -90,6 +90,13 @@ export function replaceEditableTriggerDefinition(existing, input) {
|
|
|
90
90
|
updatedAt: existing.updatedAt,
|
|
91
91
|
origin: existing.origin,
|
|
92
92
|
});
|
|
93
|
+
assertBaseagentImmutable(existing, replacement);
|
|
94
|
+
return replacement;
|
|
95
|
+
}
|
|
96
|
+
function assertBaseagentImmutable(existing, updated) {
|
|
97
|
+
if (existing.execution.baseagent !== updated.execution.baseagent) {
|
|
98
|
+
throw new Error('execution.baseagent is immutable; create a new Trigger to use another baseagent');
|
|
99
|
+
}
|
|
93
100
|
}
|
|
94
101
|
function applySourcePatch(updated, patch) {
|
|
95
102
|
const previous = updated.source;
|