evolcore 0.0.2 → 0.0.4
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 +93 -778
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +198 -48
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +203 -8
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/aun/outbox.js +8 -0
- package/dist/channels/aun.js +241 -116
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +1005 -260
- package/dist/channels/feishu.js +184 -56
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +1132 -164
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +98 -43
- 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 +4 -3
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +242 -129
- package/dist/cli/init.js +70 -31
- 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 +146 -25
- package/dist/cli/version.js +6 -1
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +42 -17
- package/dist/config/builtin-roles.js +31 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +409 -29
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +622 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +185 -300
- package/dist/config/contact-operation-service.js +116 -0
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +136 -55
- 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/schema-registry.js +49 -24
- package/dist/config-store.js +14 -4
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +16 -1
- package/dist/core/auth/operation-authorizer.js +10 -0
- package/dist/core/auth/operation-catalog.js +105 -1
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +113 -21
- package/dist/core/command/connect-menu.js +599 -0
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +382 -100
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/role-menu.js +128 -29
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +186 -49
- package/dist/core/daemon-file-cache.js +40 -6
- package/dist/core/event-catalog.js +99 -0
- package/dist/core/evolagent-registry.js +4 -40
- package/dist/core/evolagent.js +20 -9
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +20 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +670 -57
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +195 -10
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +642 -96
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +115 -38
- package/dist/core/protected-paths.js +38 -11
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +86 -8
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +285 -94
- package/dist/ipc.js +114 -8
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +126 -11
- package/dist/trigger/patch.js +12 -2
- package/dist/trigger/scheduler.js +444 -40
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/logger.js +2 -2
- 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/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/channels/aun.md +4 -13
- 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/config.md +47 -2
- package/kits/docs/evolcore/contact.md +61 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +15 -6
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +12 -6
- 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 +42 -10
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +21 -7
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +47 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/commands.md +3 -1
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +7 -5
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- 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
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { resolvePaths } from '../paths.js';
|
|
4
4
|
import { ipcQuery } from '../ipc.js';
|
|
5
|
-
import { isHelpFlag } from './help.js';
|
|
5
|
+
import { isHelpFlag, wantsHelp } from './help.js';
|
|
6
6
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
|
|
7
7
|
import { readTaskRuntimeContextFromEnv } from './task-context.js';
|
|
8
8
|
import { normalizeTriggerDefinition, resolveScriptPath, } from '../trigger/validation.js';
|
|
@@ -12,12 +12,25 @@ export async function cmdTrigger(args) {
|
|
|
12
12
|
printHelp();
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
if (
|
|
16
|
-
|
|
15
|
+
if (wantsHelp(args)) {
|
|
16
|
+
printHelp();
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
const rest = args.slice(1);
|
|
20
20
|
const json = hasFlag(rest, '--json') || flagValue(rest, '--format') === 'json';
|
|
21
|
+
if (sub === 'template') {
|
|
22
|
+
try {
|
|
23
|
+
handleTemplate(rest);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (json)
|
|
27
|
+
console.log(JSON.stringify({ ok: false, error: err?.message || String(err) }, null, 2));
|
|
28
|
+
else
|
|
29
|
+
console.error(err?.message || String(err));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
21
34
|
try {
|
|
22
35
|
switch (sub) {
|
|
23
36
|
case 'list':
|
|
@@ -30,6 +43,7 @@ export async function cmdTrigger(args) {
|
|
|
30
43
|
await showHistory(rest, json);
|
|
31
44
|
return;
|
|
32
45
|
case 'create':
|
|
46
|
+
case 'set':
|
|
33
47
|
await createTrigger(rest, json);
|
|
34
48
|
return;
|
|
35
49
|
case 'update':
|
|
@@ -69,23 +83,27 @@ function printHelp() {
|
|
|
69
83
|
list [--agent <aid>] [--all] [--json]
|
|
70
84
|
show [--agent <aid>] <triggerId> [--json]
|
|
71
85
|
history [--agent <aid>] [triggerId] [--limit <n>] [--json]
|
|
72
|
-
create --file <trigger.json|trigger-dir> [--enable] [--json]
|
|
86
|
+
create --exec <script|trigger-session|target-session> --file <trigger.json|trigger-dir> [--enable] [--json]
|
|
73
87
|
OR
|
|
74
|
-
create [--agent <aid>] --cron <expr>|--event <pattern> --prompt <text> [--enable] [其他flag]
|
|
88
|
+
create [--agent <aid>] --exec <script|trigger-session|target-session> --cron <expr>|--event <pattern> (--prompt <text>|--prompt-file <path>) [--enable] [其他flag]
|
|
89
|
+
set ... (create 的兼容别名)
|
|
75
90
|
update [--agent <aid>] <triggerId> <参数...> [--if-revision <sha256:...>] [--json]
|
|
76
91
|
enable [--agent <aid>] <triggerId>
|
|
77
92
|
disable [--agent <aid>] <triggerId>
|
|
78
93
|
cancel [--agent <aid>] <triggerId> (disable 的兼容别名)
|
|
79
|
-
delete [--agent <aid>] <triggerId>
|
|
94
|
+
delete|remove|rm [--agent <aid>] <triggerId>
|
|
80
95
|
run [--agent <aid>] <triggerId> [--dry-run] [--json]
|
|
81
96
|
template list|show <name> [--json]
|
|
82
97
|
|
|
83
98
|
Create 参数模式支持:
|
|
84
99
|
--once | --delay <时长> | --at <ISO时间> | --cron <表达式> | --every <时长> | --event <事件模式>
|
|
85
|
-
--exec <script|trigger-session|target-session>
|
|
86
|
-
--prompt <文本>
|
|
100
|
+
--exec <script|trigger-session|target-session> (必填)
|
|
101
|
+
--prompt <文本> | --prompt-file <路径>
|
|
102
|
+
Shell 双引号内的双引号、反引号和变量形式的美元符号都需在前面加反斜杠;trigger 保存的是还原后的原文
|
|
87
103
|
--script-path <路径> --script-runtime <node|python|bash>
|
|
88
|
-
--
|
|
104
|
+
--script-args <JSON> --script-timeout <时长>
|
|
105
|
+
--feedback <target> (origin/silent 仅可通过 --file 导入)
|
|
106
|
+
--baseagent <name> (仅 --exec trigger-session)
|
|
89
107
|
--model <模型> --effort <low|medium|high|xhigh|max>
|
|
90
108
|
--max-runs <次数> --max-duration <时长: 30s|15m|2h|1d>
|
|
91
109
|
--permission <readonly|auto|request|bypass>(省略则继承当前身份配置)
|
|
@@ -95,14 +113,18 @@ Create 参数模式支持:
|
|
|
95
113
|
--trigger-thread <per-run|by-trigger>
|
|
96
114
|
--name <名称>
|
|
97
115
|
|
|
116
|
+
通用输出参数:
|
|
117
|
+
--json | --format json
|
|
118
|
+
|
|
98
119
|
Update 仅支持常用字段:
|
|
99
120
|
--once | --delay | --at | --cron | --every | --event [--tz]
|
|
100
|
-
--prompt <文本> --name <名称>
|
|
121
|
+
--prompt <文本> | --prompt-file <路径> --name <名称>
|
|
101
122
|
--model <模型> --effort <强度> --permission <权限模式|inherit>(inherit 清除覆盖)
|
|
102
123
|
--max-runs <次数> --max-duration <时长>
|
|
103
124
|
--concurrency <forbid|replace|allow> --missed-policy <skip|run-once|run-all>
|
|
104
125
|
|
|
105
|
-
省略 --agent 时使用当前 agent AID
|
|
126
|
+
省略 --agent 时使用当前 agent AID;无会话上下文或跨身份操作时需要显式指定。
|
|
127
|
+
任务会话中的跨身份操作仅允许目标 Agent 上具备 owner/admin 权限的调用者执行 list/show/history;创建、修改、启停、删除和运行仍只允许当前 Agent。`);
|
|
106
128
|
}
|
|
107
129
|
async function listTriggers(args, json) {
|
|
108
130
|
const agentAid = resolveAgentAid(args);
|
|
@@ -120,7 +142,7 @@ async function listTriggers(args, json) {
|
|
|
120
142
|
}
|
|
121
143
|
async function showTrigger(args, json) {
|
|
122
144
|
const agentAid = resolveAgentAid(args);
|
|
123
|
-
const triggerId = positional(args, 0, ['--agent']);
|
|
145
|
+
const triggerId = positional(args, 0, ['--agent', '--format']);
|
|
124
146
|
const res = await request({ type: 'trigger.show', agentAid, triggerId });
|
|
125
147
|
if (json)
|
|
126
148
|
return printJson(res);
|
|
@@ -149,6 +171,8 @@ async function showTrigger(args, json) {
|
|
|
149
171
|
console.log(`limit state: ${parts.join(', ')}`);
|
|
150
172
|
}
|
|
151
173
|
console.log(`execution: ${t.execution.type}`);
|
|
174
|
+
if (t.execution.type === 'trigger_session')
|
|
175
|
+
console.log(`baseagent: ${t.execution.baseagent ?? 'legacy/inherit'}`);
|
|
152
176
|
if (t.execution.type === 'script')
|
|
153
177
|
console.log(`script: ${scriptCommandLabel(t.execution.script)}`);
|
|
154
178
|
console.log(`model: ${t.execution.model ?? 'inherit'}`);
|
|
@@ -192,10 +216,20 @@ async function showHistory(args, json) {
|
|
|
192
216
|
}
|
|
193
217
|
}
|
|
194
218
|
async function createTrigger(args, json) {
|
|
195
|
-
|
|
196
|
-
|
|
219
|
+
if (!hasOption(args, '--exec')) {
|
|
220
|
+
throw new Error('创建 trigger 必须显式指定 --exec script|trigger-session|target-session');
|
|
221
|
+
}
|
|
222
|
+
const executionType = executionTypeFromFlag(args);
|
|
223
|
+
// --file is reserved for full trigger-definition imports.
|
|
224
|
+
if (hasOption(args, '--file')) {
|
|
225
|
+
if (hasOption(args, '--prompt-file')) {
|
|
226
|
+
throw new Error('--file(完整定义导入)与 --prompt-file(prompt 正文)不能同时指定');
|
|
227
|
+
}
|
|
197
228
|
const file = requireFlag(args, '--file');
|
|
198
229
|
const { definition, files } = loadDefinitionWithFiles(file);
|
|
230
|
+
if (definition.execution.type !== executionType) {
|
|
231
|
+
throw new Error(`--exec ${flagValue(args, '--exec')} 与 trigger.json 的 execution.type=${definition.execution.type} 不一致`);
|
|
232
|
+
}
|
|
199
233
|
const res = await request({
|
|
200
234
|
type: 'trigger.create',
|
|
201
235
|
definition,
|
|
@@ -208,9 +242,12 @@ async function createTrigger(args, json) {
|
|
|
208
242
|
return;
|
|
209
243
|
}
|
|
210
244
|
// Flag mode: use parser
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
245
|
+
const usesPromptFile = hasOption(args, '--prompt-file');
|
|
246
|
+
if (usesPromptFile && executionType === 'script') {
|
|
247
|
+
throw new Error('--exec script 不接受 --prompt-file;请使用 --script-path 和 --script-runtime');
|
|
248
|
+
}
|
|
249
|
+
const { parseTriggerSetArgv } = await import('../trigger/parser.js');
|
|
250
|
+
const parseResult = parseTriggerSetArgv(withPromptFromFile(triggerCreateArgs(args)), { allowLongPrompt: usesPromptFile });
|
|
214
251
|
if (!parseResult.ok) {
|
|
215
252
|
throw new Error(parseResult.error);
|
|
216
253
|
}
|
|
@@ -246,7 +283,10 @@ async function createTrigger(args, json) {
|
|
|
246
283
|
...(parsed.model ? { model: parsed.model } : {}),
|
|
247
284
|
...(parsed.effort ? { effort: parsed.effort } : {}),
|
|
248
285
|
...(parsed.permissionMode ? { permissionMode: parsed.permissionMode } : {}),
|
|
249
|
-
...(parsed.executionType === 'trigger_session' ? {
|
|
286
|
+
...(parsed.executionType === 'trigger_session' ? {
|
|
287
|
+
thread: parsed.triggerThread ?? 'by_trigger',
|
|
288
|
+
...(parsed.baseagent ? { baseagent: parsed.baseagent } : {}),
|
|
289
|
+
} : {}),
|
|
250
290
|
onError: 'retry',
|
|
251
291
|
noopSentinel: '[[NOOP]]',
|
|
252
292
|
},
|
|
@@ -272,6 +312,22 @@ async function createTrigger(args, json) {
|
|
|
272
312
|
return printJson(res);
|
|
273
313
|
console.log(`✓ created ${res.trigger.id} (${res.trigger.name})`);
|
|
274
314
|
}
|
|
315
|
+
function triggerCreateArgs(args) {
|
|
316
|
+
const out = [];
|
|
317
|
+
for (let i = 0; i < args.length; i++) {
|
|
318
|
+
const arg = args[i];
|
|
319
|
+
if (arg === '--enable' || arg === '--json')
|
|
320
|
+
continue;
|
|
321
|
+
if (arg === '--format') {
|
|
322
|
+
i += 1;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (arg.startsWith('--format='))
|
|
326
|
+
continue;
|
|
327
|
+
out.push(arg);
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
275
331
|
function sourceFromParsed(parsed) {
|
|
276
332
|
if (parsed.scheduleType === 'once')
|
|
277
333
|
return { type: 'once' };
|
|
@@ -291,6 +347,16 @@ function sourceFromParsed(parsed) {
|
|
|
291
347
|
return { type: 'event', eventPattern: parsed.scheduleValue };
|
|
292
348
|
throw new Error(`unsupported scheduleType: ${parsed.scheduleType}`);
|
|
293
349
|
}
|
|
350
|
+
function executionTypeFromFlag(args) {
|
|
351
|
+
const value = requireFlag(args, '--exec');
|
|
352
|
+
if (value === 'script')
|
|
353
|
+
return 'script';
|
|
354
|
+
if (value === 'trigger-session' || value === 'trigger_session')
|
|
355
|
+
return 'trigger_session';
|
|
356
|
+
if (value === 'target-session' || value === 'target_session')
|
|
357
|
+
return 'target_session';
|
|
358
|
+
throw new Error('--exec 只接受 script、trigger-session 或 target-session');
|
|
359
|
+
}
|
|
294
360
|
function limitsFromParsed(parsed) {
|
|
295
361
|
const limits = {};
|
|
296
362
|
if (parsed.maxRuns !== undefined)
|
|
@@ -322,9 +388,10 @@ function shellQuoteArg(value) {
|
|
|
322
388
|
}
|
|
323
389
|
async function updateTrigger(args, json) {
|
|
324
390
|
const agentAid = resolveAgentAid(args);
|
|
325
|
-
const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision']);
|
|
391
|
+
const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file']);
|
|
392
|
+
const usesPromptFile = hasOption(args, '--prompt-file');
|
|
326
393
|
const { parseTriggerUpdateArgv } = await import('../trigger/parser.js');
|
|
327
|
-
const parsed = parseTriggerUpdateArgv(triggerId, triggerUpdateArgs(args));
|
|
394
|
+
const parsed = parseTriggerUpdateArgv(triggerId, withPromptFromFile(triggerUpdateArgs(args)), { allowLongPrompt: usesPromptFile });
|
|
328
395
|
if (!parsed.ok)
|
|
329
396
|
throw new Error(parsed.error);
|
|
330
397
|
const expectedRevision = flagValue(args, '--if-revision');
|
|
@@ -333,6 +400,7 @@ async function updateTrigger(args, json) {
|
|
|
333
400
|
agentAid,
|
|
334
401
|
triggerId,
|
|
335
402
|
patch: parsed.value,
|
|
403
|
+
...(usesPromptFile ? { promptFile: true } : {}),
|
|
336
404
|
...(expectedRevision ? { expectedRevision } : {}),
|
|
337
405
|
}, 30_000);
|
|
338
406
|
if (json)
|
|
@@ -360,9 +428,59 @@ function triggerUpdateArgs(args) {
|
|
|
360
428
|
}
|
|
361
429
|
return out;
|
|
362
430
|
}
|
|
431
|
+
function withPromptFromFile(args) {
|
|
432
|
+
const fileIndexes = [];
|
|
433
|
+
const filePaths = [];
|
|
434
|
+
for (let index = 0; index < args.length; index++) {
|
|
435
|
+
const arg = args[index];
|
|
436
|
+
if (arg === '--prompt-file') {
|
|
437
|
+
const value = args[index + 1];
|
|
438
|
+
if (!value || value.startsWith('--'))
|
|
439
|
+
throw new Error('missing --prompt-file');
|
|
440
|
+
fileIndexes.push(index, index + 1);
|
|
441
|
+
filePaths.push(value);
|
|
442
|
+
index += 1;
|
|
443
|
+
}
|
|
444
|
+
else if (arg.startsWith('--prompt-file=')) {
|
|
445
|
+
const value = arg.slice('--prompt-file='.length);
|
|
446
|
+
if (!value)
|
|
447
|
+
throw new Error('missing --prompt-file');
|
|
448
|
+
fileIndexes.push(index);
|
|
449
|
+
filePaths.push(value);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (fileIndexes.length === 0)
|
|
453
|
+
return args;
|
|
454
|
+
if (filePaths.length > 1)
|
|
455
|
+
throw new Error('--prompt-file 只能指定一次');
|
|
456
|
+
if (hasOption(args, '--prompt'))
|
|
457
|
+
throw new Error('--prompt-file 与 --prompt 不能同时指定');
|
|
458
|
+
const filePath = filePaths[0];
|
|
459
|
+
let stat;
|
|
460
|
+
try {
|
|
461
|
+
stat = fs.statSync(filePath);
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
throw new Error(`无法读取 prompt 文件 ${filePath}: ${err?.message || String(err)}`);
|
|
465
|
+
}
|
|
466
|
+
if (!stat.isFile())
|
|
467
|
+
throw new Error(`prompt 文件必须是普通文件: ${filePath}`);
|
|
468
|
+
let prompt;
|
|
469
|
+
try {
|
|
470
|
+
prompt = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
throw new Error(`无法读取 prompt 文件 ${filePath}: ${err?.message || String(err)}`);
|
|
474
|
+
}
|
|
475
|
+
const fileIndexSet = new Set(fileIndexes);
|
|
476
|
+
return [
|
|
477
|
+
...args.filter((_, index) => !fileIndexSet.has(index)),
|
|
478
|
+
'--prompt', prompt,
|
|
479
|
+
];
|
|
480
|
+
}
|
|
363
481
|
async function setEnabled(args, enabled, json) {
|
|
364
482
|
const agentAid = resolveAgentAid(args);
|
|
365
|
-
const triggerId = positional(args, 0, ['--agent']);
|
|
483
|
+
const triggerId = positional(args, 0, ['--agent', '--format']);
|
|
366
484
|
const res = await request({ type: 'trigger.setEnabled', agentAid, triggerId, enabled });
|
|
367
485
|
if (json)
|
|
368
486
|
return printJson(res);
|
|
@@ -370,7 +488,7 @@ async function setEnabled(args, enabled, json) {
|
|
|
370
488
|
}
|
|
371
489
|
async function cancelTrigger(args, json) {
|
|
372
490
|
const agentAid = resolveAgentAid(args);
|
|
373
|
-
const triggerId = positional(args, 0, ['--agent']);
|
|
491
|
+
const triggerId = positional(args, 0, ['--agent', '--format']);
|
|
374
492
|
const res = await request({ type: 'trigger.cancel', agentAid, triggerId });
|
|
375
493
|
if (json)
|
|
376
494
|
return printJson(res);
|
|
@@ -378,7 +496,7 @@ async function cancelTrigger(args, json) {
|
|
|
378
496
|
}
|
|
379
497
|
async function deleteTrigger(args, json) {
|
|
380
498
|
const agentAid = resolveAgentAid(args);
|
|
381
|
-
const triggerId = positional(args, 0, ['--agent']);
|
|
499
|
+
const triggerId = positional(args, 0, ['--agent', '--format']);
|
|
382
500
|
const res = await request({ type: 'trigger.delete', agentAid, triggerId });
|
|
383
501
|
if (json)
|
|
384
502
|
return printJson(res);
|
|
@@ -386,7 +504,7 @@ async function deleteTrigger(args, json) {
|
|
|
386
504
|
}
|
|
387
505
|
async function runTrigger(args, json) {
|
|
388
506
|
const agentAid = resolveAgentAid(args);
|
|
389
|
-
const triggerId = positional(args, 0, ['--agent']);
|
|
507
|
+
const triggerId = positional(args, 0, ['--agent', '--format']);
|
|
390
508
|
const res = await request({ type: 'trigger.run', agentAid, triggerId, dryRun: hasFlag(args, '--dry-run') }, 120_000);
|
|
391
509
|
if (json)
|
|
392
510
|
return printJson(res);
|
|
@@ -406,7 +524,7 @@ function handleTemplate(args) {
|
|
|
406
524
|
return;
|
|
407
525
|
}
|
|
408
526
|
if (sub === 'show') {
|
|
409
|
-
const name = args.
|
|
527
|
+
const name = optionalPositional(args.slice(1), 0, ['--format']);
|
|
410
528
|
if (!name)
|
|
411
529
|
throw new Error('missing template name');
|
|
412
530
|
if (json)
|
|
@@ -482,6 +600,9 @@ function flagValue(args, name) {
|
|
|
482
600
|
function hasFlag(args, name) {
|
|
483
601
|
return args.includes(name);
|
|
484
602
|
}
|
|
603
|
+
function hasOption(args, name) {
|
|
604
|
+
return args.includes(name) || args.some(arg => arg.startsWith(`${name}=`));
|
|
605
|
+
}
|
|
485
606
|
function positional(args, index, flagsWithValue) {
|
|
486
607
|
const value = optionalPositional(args, index, flagsWithValue);
|
|
487
608
|
if (!value)
|
package/dist/cli/version.js
CHANGED
|
@@ -5,6 +5,7 @@ import fs from 'fs';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { MAIN_PACKAGE_NAME, PRODUCT_DISPLAY_NAME, WEB_PACKAGE_NAME } from '../product.js';
|
|
8
|
+
import { getArgValue, wantsHelp } from './help.js';
|
|
8
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const PACKAGE_ROOT = path.resolve(__dirname, '../..');
|
|
10
11
|
const RESET = '\x1b[0m';
|
|
@@ -44,7 +45,11 @@ function findInstalledVersion(pkgName) {
|
|
|
44
45
|
return { name: pkg.name || pkgName, version: pkg.version || '?', buildTs: ts || undefined, path: dir };
|
|
45
46
|
}
|
|
46
47
|
export function handleVersion(args) {
|
|
47
|
-
|
|
48
|
+
if (wantsHelp(args)) {
|
|
49
|
+
console.log(`用法: ec version [--format json|--json]\n\n显示 EvolCore、EC Web、AUN SDK、WebSocket 与 Node.js 版本。`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const isJson = getArgValue(args, '--format') === 'json' || args.includes('--json');
|
|
48
53
|
// 主包
|
|
49
54
|
const mainPkg = readPkgJson(PACKAGE_ROOT);
|
|
50
55
|
const mainEntry = path.join(PACKAGE_ROOT, 'dist', 'cli', 'index.js');
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isValidAid } from '../aun/aid/validation.js';
|
|
2
|
+
export function validAgentOwnerIds(agent) {
|
|
3
|
+
return [...new Set((agent?.owners ?? [])
|
|
4
|
+
.map(value => String(value || '').trim())
|
|
5
|
+
.filter(isValidAid))];
|
|
6
|
+
}
|
|
7
|
+
export function configuredChannelKeys(selfAid, agent) {
|
|
8
|
+
const keys = (agent?.channels ?? [])
|
|
9
|
+
.filter(channel => channel && channel.enabled !== false)
|
|
10
|
+
.map(channel => {
|
|
11
|
+
const type = String(channel.type || '').trim().toLowerCase();
|
|
12
|
+
const name = String(channel.name || '').trim();
|
|
13
|
+
return type && name ? `${type}#${selfAid}#${name}` : '';
|
|
14
|
+
})
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
keys.push(`aun#${selfAid}#main`);
|
|
17
|
+
return [...new Set(keys)];
|
|
18
|
+
}
|
|
19
|
+
export function validateAccessPolicyDomain(selfAid, agent) {
|
|
20
|
+
const access = agent.access;
|
|
21
|
+
if (!access)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (access.policyMode !== 'open' && validAgentOwnerIds(agent).length === 0) {
|
|
24
|
+
return {
|
|
25
|
+
code: 'restricted_mode_without_owner',
|
|
26
|
+
message: `Agent ${selfAid} cannot use ${access.policyMode} without an Owner`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const configured = new Set(configuredChannelKeys(selfAid, agent));
|
|
30
|
+
const unknown = access.allowedGroupChannels.find(channelKey => !configured.has(channelKey));
|
|
31
|
+
if (unknown) {
|
|
32
|
+
return {
|
|
33
|
+
code: 'unknown_group_access_channel',
|
|
34
|
+
message: `Group access channel is not configured or enabled for ${selfAid}: ${unknown}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { ConfigError, ConfigTarget, mutateConfig, read, validateConfig, } from './config-manager.js';
|
|
3
|
+
import { configuredChannelKeys, validAgentOwnerIds, validateAccessPolicyDomain, } from './access-policy-domain.js';
|
|
4
|
+
export class AccessPolicyError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
data;
|
|
7
|
+
constructor(code, message, data) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.data = data;
|
|
11
|
+
this.name = 'AccessPolicyError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export const DEFAULT_ACCESS_POLICY = Object.freeze({
|
|
15
|
+
policyMode: 'open',
|
|
16
|
+
allowedGroupChannels: Object.freeze([]),
|
|
17
|
+
requestLimit: Object.freeze({ maxTimes: 600, coolDown: 60 * 60 }),
|
|
18
|
+
});
|
|
19
|
+
export function listAgentOwners(selfAid) {
|
|
20
|
+
const agent = read(ConfigTarget.Agent, { self: selfAid }, { cache: true });
|
|
21
|
+
return validAgentOwnerIds(agent);
|
|
22
|
+
}
|
|
23
|
+
export function listConfiguredChannelKeys(selfAid) {
|
|
24
|
+
const agent = read(ConfigTarget.Agent, { self: selfAid }, { cache: true });
|
|
25
|
+
if (!agent)
|
|
26
|
+
return [];
|
|
27
|
+
return configuredChannelKeys(selfAid, agent);
|
|
28
|
+
}
|
|
29
|
+
/** Missing AgentConfig.access uses the compatibility view; malformed Agent config never does. */
|
|
30
|
+
export function readAccessPolicy(selfAid) {
|
|
31
|
+
return readAccessPolicySnapshot(selfAid).policy;
|
|
32
|
+
}
|
|
33
|
+
export function readAccessPolicySnapshot(selfAid) {
|
|
34
|
+
const agent = read(ConfigTarget.Agent, { self: selfAid }, { cache: true });
|
|
35
|
+
return accessPolicySnapshotFromAgent(selfAid, agent);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Atomically updates AgentConfig.access through Config Manager's file-scoped
|
|
39
|
+
* mutation path. The callback always receives the newest persisted policy.
|
|
40
|
+
*/
|
|
41
|
+
export function mutateAccessPolicy(input) {
|
|
42
|
+
try {
|
|
43
|
+
const result = mutateConfig(ConfigTarget.Agent, { self: input.selfAid }, agent => {
|
|
44
|
+
const current = accessPolicySnapshotFromAgent(input.selfAid, agent);
|
|
45
|
+
if (input.expectedAccessRevision !== current.accessRevision) {
|
|
46
|
+
throw new AccessPolicyError('access_policy_revision_conflict', 'Access policy changed since it was read', {
|
|
47
|
+
expectedAccessRevision: input.expectedAccessRevision,
|
|
48
|
+
accessRevision: current.accessRevision,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const next = input.update(clonePolicy(current.policy));
|
|
52
|
+
assertAccessPolicyWrite(input, agent, current.policy, next);
|
|
53
|
+
agent.access = clonePolicy(next);
|
|
54
|
+
return {
|
|
55
|
+
policy: clonePolicy(next),
|
|
56
|
+
accessRevision: accessPolicyRevision(next),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
return { ...result.value, changed: result.changed };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof AccessPolicyError)
|
|
63
|
+
throw error;
|
|
64
|
+
if (error instanceof ConfigError && error.code === 'LOCK_TIMEOUT') {
|
|
65
|
+
throw new AccessPolicyError('access_policy_temporarily_unavailable', 'Access policy is temporarily unavailable');
|
|
66
|
+
}
|
|
67
|
+
if (error instanceof ConfigError) {
|
|
68
|
+
throw new AccessPolicyError('invalid_access_policy', error.message);
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function accessPolicySnapshotFromAgent(selfAid, agent) {
|
|
74
|
+
if (!agent) {
|
|
75
|
+
throw new AccessPolicyError('invalid_access_policy', `Agent config does not exist: ${selfAid}`);
|
|
76
|
+
}
|
|
77
|
+
const errors = validateConfig(ConfigTarget.Agent, agent);
|
|
78
|
+
if (errors.length > 0) {
|
|
79
|
+
throw new AccessPolicyError('invalid_access_policy', `Invalid access policy for ${selfAid}: ${errors.join('; ')}`);
|
|
80
|
+
}
|
|
81
|
+
const issue = validateAccessPolicyDomain(selfAid, agent);
|
|
82
|
+
if (issue)
|
|
83
|
+
throw new AccessPolicyError(issue.code, issue.message);
|
|
84
|
+
const access = agent.access ?? DEFAULT_ACCESS_POLICY;
|
|
85
|
+
const policy = clonePolicy(access);
|
|
86
|
+
return { policy, accessRevision: accessPolicyRevision(policy) };
|
|
87
|
+
}
|
|
88
|
+
function assertAccessPolicyWrite(input, agent, current, policy) {
|
|
89
|
+
const owners = validAgentOwnerIds(agent);
|
|
90
|
+
const normalizedActorAid = String(input.actorAid || '').trim();
|
|
91
|
+
const isOwner = input.actorRole === 'owner' || owners.includes(normalizedActorAid);
|
|
92
|
+
const isGroupChannelAdmin = input.actorRole === 'admin' && input.resource === 'access.allowedGroupChannels';
|
|
93
|
+
if (!isOwner && !isGroupChannelAdmin) {
|
|
94
|
+
if (input.resource === 'access.allowedGroupChannels') {
|
|
95
|
+
throw new AccessPolicyError('access_policy_manager_required', 'Only an Agent Owner or Admin can change allowed group channels');
|
|
96
|
+
}
|
|
97
|
+
throw new AccessPolicyError('access_policy_owner_required', 'Only an Agent Owner can change access policy');
|
|
98
|
+
}
|
|
99
|
+
if (isGroupChannelAdmin) {
|
|
100
|
+
if (current.policyMode !== policy.policyMode
|
|
101
|
+
|| current.requestLimit.maxTimes !== policy.requestLimit.maxTimes
|
|
102
|
+
|| current.requestLimit.coolDown !== policy.requestLimit.coolDown) {
|
|
103
|
+
throw new AccessPolicyError('access_policy_owner_required', 'An Agent Admin may only change allowed group channels');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (policy.policyMode !== 'open' && owners.length === 0) {
|
|
107
|
+
throw new AccessPolicyError('restricted_mode_without_owner', `Agent ${input.selfAid} cannot use ${policy.policyMode} without an Owner`);
|
|
108
|
+
}
|
|
109
|
+
const configured = new Set(configuredChannelKeys(input.selfAid, agent));
|
|
110
|
+
const unknown = policy.allowedGroupChannels.find(channelKey => !configured.has(channelKey));
|
|
111
|
+
if (unknown) {
|
|
112
|
+
throw new AccessPolicyError('unknown_group_access_channel', `Group access channel is not configured or enabled for ${input.selfAid}: ${unknown}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function clonePolicy(policy) {
|
|
116
|
+
return {
|
|
117
|
+
policyMode: policy.policyMode,
|
|
118
|
+
allowedGroupChannels: [...policy.allowedGroupChannels],
|
|
119
|
+
requestLimit: { ...policy.requestLimit },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function accessPolicyRevision(policy) {
|
|
123
|
+
return `sha256:${crypto.createHash('sha256').update(stableStringify(policy)).digest('hex')}`;
|
|
124
|
+
}
|
|
125
|
+
function stableStringify(value) {
|
|
126
|
+
return JSON.stringify(sortValue(value));
|
|
127
|
+
}
|
|
128
|
+
function sortValue(value) {
|
|
129
|
+
if (Array.isArray(value))
|
|
130
|
+
return value.map(sortValue);
|
|
131
|
+
if (!value || typeof value !== 'object')
|
|
132
|
+
return value;
|
|
133
|
+
return Object.fromEntries(Object.keys(value).sort().map(key => [key, sortValue(value[key])]));
|
|
134
|
+
}
|
|
@@ -1,30 +1,55 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import path from 'path';
|
|
3
2
|
import { kitsRoleTemplatesDir } from '../paths.js';
|
|
3
|
+
import { createErrorCapturingReader, fileCache } from '../core/daemon-file-cache.js';
|
|
4
4
|
import { validateRoleConfig } from './role-schema.js';
|
|
5
|
+
import { expectedRoleRank } from './role-ranks.js';
|
|
5
6
|
export const BUILTIN_ROLE_IDS = ['owner', 'admin', 'member', 'visitor'];
|
|
6
|
-
|
|
7
|
-
export function readBuiltinRoleTemplate(roleId) {
|
|
8
|
-
const file = path.join(kitsRoleTemplatesDir(), `${roleId}.json`);
|
|
9
|
-
let raw;
|
|
10
|
-
try {
|
|
11
|
-
raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
return {
|
|
15
|
-
definition: null,
|
|
16
|
-
file,
|
|
17
|
-
error: `cannot read built-in role template: ${error instanceof Error ? error.message : String(error)}`,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
7
|
+
export function validateBuiltinRoleTemplateDefinition(roleId, raw) {
|
|
20
8
|
const issues = validateRoleConfig(raw);
|
|
21
9
|
if (issues.length) {
|
|
22
10
|
return {
|
|
23
11
|
definition: null,
|
|
24
|
-
file,
|
|
25
12
|
error: `built-in role template failed validation: ${issues.map(issue => `${issue.field} ${issue.message}`).join('; ')}`,
|
|
26
13
|
};
|
|
27
14
|
}
|
|
28
15
|
const { $schema_version: _schemaVersion, ...definition } = raw;
|
|
29
|
-
|
|
16
|
+
if (BUILTIN_ROLE_IDS.includes(roleId)) {
|
|
17
|
+
const expectedRank = expectedRoleRank(roleId);
|
|
18
|
+
if (definition.rank !== expectedRank) {
|
|
19
|
+
return {
|
|
20
|
+
definition: null,
|
|
21
|
+
error: `built-in role template rank must be ${expectedRank}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return { definition: definition };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load the versioned package template that defines a built-in role policy.
|
|
29
|
+
*
|
|
30
|
+
* 模板随包发布、只靠进程重启或 kit 缓存失效刷新 → fileCache 的 on-reload 策略,
|
|
31
|
+
* 归入 'kits' 组。缓存的是"读盘 + JSON.parse"结果;校验(validateRoleConfig)仍每次现做。
|
|
32
|
+
*/
|
|
33
|
+
export function readBuiltinRoleTemplate(roleId) {
|
|
34
|
+
const file = path.join(kitsRoleTemplatesDir(), `${roleId}.json`);
|
|
35
|
+
// 默认 reader 会把所有读盘异常吞成 null(分不清 ENOENT 与 EACCES),
|
|
36
|
+
// 这里注入带错误捕获的 reader 把真实错误原文带出来,保持与直读时一致的报错。
|
|
37
|
+
const reader = createErrorCapturingReader();
|
|
38
|
+
const parsed = fileCache.get(file, (text) => {
|
|
39
|
+
if (text === null) {
|
|
40
|
+
return { error: `cannot read built-in role template: ${reader.lastError()}` };
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return { raw: JSON.parse(text) };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return { error: `cannot read built-in role template: ${error instanceof Error ? error.message : String(error)}` };
|
|
47
|
+
}
|
|
48
|
+
}, { policy: 'on-reload', group: 'kits', read: reader.read });
|
|
49
|
+
if ('error' in parsed)
|
|
50
|
+
return { definition: null, file, error: parsed.error };
|
|
51
|
+
// 缓存返回的是同一个对象引用,而 validateBuiltinRoleTemplateDefinition 里的解构
|
|
52
|
+
// 只隔离顶层——permissions / commandPermissions / usageLimits 等嵌套对象仍会被
|
|
53
|
+
// 所有调用方共享,任何一处改写都会污染后续读者拿到的授权策略。整份深拷贝后再校验。
|
|
54
|
+
return { ...validateBuiltinRoleTemplateDefinition(roleId, structuredClone(parsed.raw)), file };
|
|
30
55
|
}
|