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.
Files changed (206) hide show
  1. package/CHANGELOG.md +93 -778
  2. package/README.md +45 -10
  3. package/bin/ec-safe-output.js +89 -24
  4. package/dist/agents/baseagent.js +46 -0
  5. package/dist/agents/claude-runner.js +198 -48
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +203 -8
  8. package/dist/agents/runner-types.js +2 -2
  9. package/dist/aun/aid/agentmd.js +79 -0
  10. package/dist/aun/aid/encryption-seed-policy.js +29 -0
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/aid/store.js +2 -5
  13. package/dist/aun/outbox.js +8 -0
  14. package/dist/channels/aun.js +241 -116
  15. package/dist/channels/daemon.js +18 -4
  16. package/dist/channels/dingtalk.js +1005 -260
  17. package/dist/channels/feishu.js +184 -56
  18. package/dist/channels/qqbot.js +23 -1
  19. package/dist/channels/wechat.js +303 -155
  20. package/dist/channels/wecom.js +1132 -164
  21. package/dist/cli/agent-command.js +2 -1
  22. package/dist/cli/aun-commands.js +98 -43
  23. package/dist/cli/bench.js +2 -2
  24. package/dist/cli/contact.js +71 -0
  25. package/dist/cli/ctl-command.js +2 -2
  26. package/dist/cli/daemon-commands.js +77 -214
  27. package/dist/cli/handoff-command.js +4 -3
  28. package/dist/cli/help.js +9 -5
  29. package/dist/cli/index.js +132 -116
  30. package/dist/cli/init-channel.js +242 -129
  31. package/dist/cli/init.js +70 -31
  32. package/dist/cli/model.js +2 -1
  33. package/dist/cli/net-check.js +2 -2
  34. package/dist/cli/queue-command.js +30 -6
  35. package/dist/cli/raw-key-input.js +25 -0
  36. package/dist/cli/response.js +5 -6
  37. package/dist/cli/restart-monitor.js +25 -1
  38. package/dist/cli/stats.js +6 -4
  39. package/dist/cli/trigger-command.js +146 -25
  40. package/dist/cli/version.js +6 -1
  41. package/dist/config/access-policy-domain.js +38 -0
  42. package/dist/config/access-policy.js +134 -0
  43. package/dist/config/builtin-role-templates.js +42 -17
  44. package/dist/config/builtin-roles.js +31 -6
  45. package/dist/config/config-field-policy.js +17 -5
  46. package/dist/config/config-manager.js +409 -29
  47. package/dist/config/config-operation-service.js +35 -38
  48. package/dist/config/contact-alias.js +68 -0
  49. package/dist/config/contact-bind-code.js +274 -0
  50. package/dist/config/contact-book-store.js +622 -0
  51. package/dist/config/contact-book-v2-startup.js +35 -0
  52. package/dist/config/contact-book.js +185 -300
  53. package/dist/config/contact-operation-service.js +116 -0
  54. package/dist/config/contact-request-service.js +331 -0
  55. package/dist/config/peer-role-resolver.js +136 -55
  56. package/dist/config/role-ranks.js +18 -0
  57. package/dist/config/role-service.js +16 -19
  58. package/dist/config/role-store.js +16 -5
  59. package/dist/config/roles.js +10 -1
  60. package/dist/config/schema-registry.js +49 -24
  61. package/dist/config-store.js +14 -4
  62. package/dist/core/auth/agent-delegation.js +105 -11
  63. package/dist/core/auth/authorization-audit.js +16 -1
  64. package/dist/core/auth/operation-authorizer.js +10 -0
  65. package/dist/core/auth/operation-catalog.js +105 -1
  66. package/dist/core/auth/trigger-authorization.js +15 -0
  67. package/dist/core/bootstrap-service.js +2 -9
  68. package/dist/core/channel-loader.js +6 -2
  69. package/dist/core/command/command-handler.js +113 -21
  70. package/dist/core/command/connect-menu.js +599 -0
  71. package/dist/core/command/evol-menu-version-gate.js +38 -0
  72. package/dist/core/command/group-menu.js +421 -0
  73. package/dist/core/command/menu-handler.js +382 -100
  74. package/dist/core/command/menu-protocol.js +8 -2
  75. package/dist/core/command/menu-token-store.js +102 -0
  76. package/dist/core/command/role-menu.js +128 -29
  77. package/dist/core/command/slash-gate.js +1 -1
  78. package/dist/core/command/slash-handler.js +186 -49
  79. package/dist/core/daemon-file-cache.js +40 -6
  80. package/dist/core/event-catalog.js +99 -0
  81. package/dist/core/evolagent-registry.js +4 -40
  82. package/dist/core/evolagent.js +20 -9
  83. package/dist/core/handoff/runtime.js +162 -37
  84. package/dist/core/handoff/store.js +46 -2
  85. package/dist/core/handoff/types.js +1 -0
  86. package/dist/core/inference/text-inference.js +16 -74
  87. package/dist/core/message/file-markers.js +7 -0
  88. package/dist/core/message/im-renderer.js +20 -14
  89. package/dist/core/message/inbound-admission.js +124 -0
  90. package/dist/core/message/message-bridge.js +670 -57
  91. package/dist/core/message/message-log.js +1 -0
  92. package/dist/core/message/message-queue.js +195 -10
  93. package/dist/core/message/message-utils.js +8 -2
  94. package/dist/core/message/peer-mode.js +7 -8
  95. package/dist/core/message/response-engine.js +642 -96
  96. package/dist/core/message/send-receipt.js +24 -0
  97. package/dist/core/message/stream-debouncer.js +11 -2
  98. package/dist/core/permission/approval-gateway.js +17 -10
  99. package/dist/core/permission/ec-command-parser.js +101 -46
  100. package/dist/core/permission/tool-policy.js +115 -38
  101. package/dist/core/protected-paths.js +38 -11
  102. package/dist/core/session/session-fs-store.js +44 -3
  103. package/dist/core/session/session-manager.js +86 -8
  104. package/dist/core/session/session-mapper.js +2 -0
  105. package/dist/core/session/session-renew.js +125 -69
  106. package/dist/core/session/session-turn-coordinator.js +5 -1
  107. package/dist/eck/kit-renderer.js +12 -1
  108. package/dist/eck/message-renderer.js +79 -1
  109. package/dist/index.js +285 -94
  110. package/dist/ipc.js +114 -8
  111. package/dist/paths.js +3 -0
  112. package/dist/response-system/config-resolver.js +29 -0
  113. package/dist/response-system/coordinator.js +8 -32
  114. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  115. package/dist/response-system/index.js +1 -0
  116. package/dist/response-system/modes/single-session/index.js +7 -4
  117. package/dist/stats/billing.js +20 -8
  118. package/dist/trigger/manager.js +3 -0
  119. package/dist/trigger/parser.js +126 -11
  120. package/dist/trigger/patch.js +12 -2
  121. package/dist/trigger/scheduler.js +444 -40
  122. package/dist/trigger/validation.js +13 -0
  123. package/dist/utils/aid-bind.js +43 -29
  124. package/dist/utils/error-dict.json +7 -0
  125. package/dist/utils/evolcore-version.js +21 -0
  126. package/dist/utils/instance-registry.js +14 -7
  127. package/dist/utils/log-writer.js +46 -0
  128. package/dist/utils/logger.js +2 -2
  129. package/dist/utils/media-cache.js +4 -1
  130. package/dist/utils/model-prices.jsonl +6 -3
  131. package/dist/utils/restart-safety.js +31 -0
  132. package/dist/utils/stable-semver.js +21 -0
  133. package/dist/utils/stats.js +33 -9
  134. package/dist/utils/system-memory.js +62 -0
  135. package/kits/docs/INDEX.md +2 -1
  136. package/kits/docs/channels/aun.md +4 -13
  137. package/kits/docs/evolcore/INDEX.md +5 -3
  138. package/kits/docs/evolcore/agent.md +9 -1
  139. package/kits/docs/evolcore/aid.md +5 -2
  140. package/kits/docs/evolcore/config.md +47 -2
  141. package/kits/docs/evolcore/contact.md +61 -0
  142. package/kits/docs/evolcore/fs.md +9 -0
  143. package/kits/docs/evolcore/group-rules.md +46 -4
  144. package/kits/docs/evolcore/group.md +15 -6
  145. package/kits/docs/evolcore/model.md +4 -1
  146. package/kits/docs/evolcore/msg.md +12 -6
  147. package/kits/docs/evolcore/response.md +16 -21
  148. package/kits/docs/evolcore/rpc.md +2 -0
  149. package/kits/docs/evolcore/stats.md +15 -2
  150. package/kits/docs/evolcore/storage.md +1 -0
  151. package/kits/docs/evolcore/trigger.md +42 -10
  152. package/kits/docs/path-registry.md +36 -17
  153. package/kits/eck_manifest.json +12 -0
  154. package/kits/eck_message_manifest.json +12 -1
  155. package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
  156. package/kits/rules/01-overview.md +17 -7
  157. package/kits/rules/02-navigation.md +38 -18
  158. package/kits/rules/03-identity.md +28 -24
  159. package/kits/rules/04-relation.md +44 -28
  160. package/kits/rules/05-venue.md +31 -15
  161. package/kits/rules/06-channel.md +11 -7
  162. package/kits/schemas/_meta.json +21 -7
  163. package/kits/schemas/agent-config.schema.6.json +322 -0
  164. package/kits/schemas/agent-config.schema.7.json +303 -0
  165. package/kits/schemas/agent-config.schema.8.json +304 -0
  166. package/kits/schemas/agent-config.schema.9.json +374 -0
  167. package/kits/schemas/contact-book.schema.2.json +43 -0
  168. package/kits/schemas/contact-book.schema.3.json +67 -0
  169. package/kits/schemas/daemon.schema.1.json +3 -2
  170. package/kits/schemas/daemon.schema.2.json +101 -0
  171. package/kits/schemas/daemon.schema.3.json +123 -0
  172. package/kits/schemas/defaults.schema.2.json +85 -0
  173. package/kits/schemas/defaults.schema.3.json +73 -0
  174. package/kits/schemas/relation-config.schema.5.json +47 -0
  175. package/kits/schemas/relation-config.schema.6.json +46 -0
  176. package/kits/schemas/relation-config.schema.7.json +59 -0
  177. package/kits/schemas/role-config.schema.1.json +1 -0
  178. package/kits/schemas/role-registry.schema.1.json +2 -2
  179. package/kits/schemas/single-session.schema.2.json +57 -0
  180. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  181. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  182. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  183. package/kits/templates/roles/admin.json +47 -0
  184. package/kits/templates/roles/member.json +5 -0
  185. package/kits/templates/roles/owner.json +1 -0
  186. package/kits/templates/roles/visitor.json +5 -0
  187. package/kits/templates/system-fragments/channel.md +12 -2
  188. package/kits/templates/system-fragments/commands.md +3 -1
  189. package/kits/templates/system-fragments/identity.md +3 -1
  190. package/kits/templates/system-fragments/relation.md +1 -1
  191. package/kits/templates/system-fragments/session.md +6 -2
  192. package/package.json +7 -5
  193. package/MIGRATION-0.5.0.md +0 -378
  194. package/ROLE_ACCESS_CONTROL.md +0 -174
  195. package/assets/brand/evolcore/README.md +0 -19
  196. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  197. package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
  198. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  199. package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
  200. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  201. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  202. package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
  203. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  204. package/assets/brand/evolcore/evolcore-logo.svg +0 -14
  205. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  206. 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 (sub === 'template') {
16
- handleTemplate(args.slice(1));
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
- --feedback <target|origin|silent> (create 参数模式仅支持 target)
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
- // Support two modes: --file (full JSON) or flags (parser-based)
196
- if (hasFlag(args, '--file')) {
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 { parseTriggerSet } = await import('../trigger/parser.js');
212
- const flagStr = args.filter(a => a !== '--enable' && a !== '--json' && !a.startsWith('--format')).join(' ');
213
- const parseResult = parseTriggerSet(flagStr);
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' ? { thread: parsed.triggerThread ?? 'by_trigger' } : {}),
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.find(a => !a.startsWith('-') && a !== 'show');
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)
@@ -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
- const isJson = args.includes('--format') && args.includes('json') || args.includes('--json');
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
- /** Load the versioned package template that defines a built-in role policy. */
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
- return { definition: definition, file };
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
  }