evolcore 0.0.29 → 0.0.30

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 (68) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/agents/codex-runner.js +61 -14
  3. package/dist/aun/aid/agentmd.js +224 -19
  4. package/dist/aun/aid/index.js +2 -1
  5. package/dist/aun/aid/managed-operation.js +70 -4
  6. package/dist/aun/group/index.js +1 -0
  7. package/dist/{core → aun}/group/invite-service.js +4 -4
  8. package/dist/channels/daemon.js +1 -1
  9. package/dist/cli/agent-command.js +7 -7
  10. package/dist/cli/aun-commands.js +97 -37
  11. package/dist/cli/data-command.js +2 -1
  12. package/dist/cli/managed-command-guard.js +2 -2
  13. package/dist/cli/model.js +1 -1
  14. package/dist/cli/response.js +1 -1
  15. package/dist/cli/trigger-command.js +52 -29
  16. package/dist/config/boot-log.js +95 -1
  17. package/dist/config/builtin-roles.js +1 -0
  18. package/dist/config/peer-role-resolver.js +21 -0
  19. package/dist/core/agent-ipc-registration.js +409 -0
  20. package/dist/core/agent-runtime.js +435 -0
  21. package/dist/core/auth/delegation-authorization.js +58 -0
  22. package/dist/core/auth/operation-catalog.js +8 -0
  23. package/dist/core/bootstrap-service.js +65 -1
  24. package/dist/core/channel-runtime.js +435 -0
  25. package/dist/core/command/command-handler.js +4 -1
  26. package/dist/core/daemon-bootstrap.js +151 -0
  27. package/dist/core/daemon-control.js +205 -0
  28. package/dist/core/daemon-lifecycle.js +225 -0
  29. package/dist/core/data-migration.js +4 -1
  30. package/dist/core/handoff/ipc-registration.js +156 -0
  31. package/dist/core/ipc-registration.js +457 -0
  32. package/dist/core/memory/consolidation-trigger.js +8 -0
  33. package/dist/core/memory/consolidation.js +838 -0
  34. package/dist/core/message/aun-ipc-sender.js +226 -0
  35. package/dist/core/message/outbound.js +165 -0
  36. package/dist/core/permission/approval-gateway.js +13 -0
  37. package/dist/core/permission/ec-command-parser.js +80 -35
  38. package/dist/core/permission/sandbox-runtime.js +23 -2
  39. package/dist/core/permission/tool-error-code.js +1 -0
  40. package/dist/core/permission/tool-policy.js +61 -24
  41. package/dist/core/session/session-idle-monitor.js +17 -0
  42. package/dist/index.js +352 -4841
  43. package/dist/ipc.js +2 -1
  44. package/dist/stats/db.js +24 -0
  45. package/dist/stats/index.js +1 -1
  46. package/dist/stats/writer.js +25 -1
  47. package/dist/trigger/application-service.js +157 -0
  48. package/dist/trigger/authorization.js +371 -0
  49. package/dist/trigger/ipc-service.js +406 -0
  50. package/dist/trigger/local-files.js +133 -0
  51. package/dist/trigger/manager.js +235 -22
  52. package/dist/trigger/scheduler.js +30 -5
  53. package/dist/trigger/script-executor.js +176 -39
  54. package/dist/trigger/script-sandbox.js +458 -0
  55. package/dist/trigger/system-definitions.js +188 -0
  56. package/dist/trigger/validation.js +0 -3
  57. package/dist/utils/aid-bind.js +1 -1
  58. package/dist/utils/evolcore-version.js +74 -0
  59. package/dist/utils/logger.js +16 -0
  60. package/kits/docs/evolcore/INDEX.md +1 -1
  61. package/kits/docs/evolcore/aid.md +9 -1
  62. package/kits/docs/evolcore/model.md +3 -2
  63. package/kits/docs/evolcore/rpc.md +4 -0
  64. package/kits/docs/evolcore/trigger.md +15 -1
  65. package/kits/schemas/relation-config.schema.9.json +1 -1
  66. package/kits/templates/system-fragments/commands.md +0 -1
  67. package/package.json +1 -1
  68. package/dist/core/memory/launch-consolidation.js +0 -395
@@ -98,6 +98,12 @@ function renderAgentmdGet(aid, result, formatJson) {
98
98
  console.error(`⚠ 签名验证失败: ${verification.reason ?? '未知原因'}`);
99
99
  else
100
100
  console.error('ℹ️ 未签名');
101
+ console.error(`来源: ${result.source === 'remote' ? '远端' : '本地缓存'}`);
102
+ console.error(`内容 SHA-256: ${result.contentSha256}`);
103
+ if (result.remoteVersion?.etag)
104
+ console.error(`远端 ETag: ${result.remoteVersion.etag}`);
105
+ if (result.remoteVersion?.lastModified)
106
+ console.error(`远端 Last-Modified: ${result.remoteVersion.lastModified}`);
101
107
  }
102
108
  function failManagedAid(formatJson, code, error) {
103
109
  if (formatJson)
@@ -119,7 +125,10 @@ async function runManagedAid(args, formatJson) {
119
125
  if (!parsed.ok)
120
126
  failManagedAid(formatJson, parsed.code, parsed.reason);
121
127
  const runtime = await readBestTaskRuntimeContext();
122
- const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put' || parsed.command.kind === 'avatar';
128
+ const requiresSelfAid = parsed.command.kind === 'show'
129
+ || parsed.command.kind === 'agentmd.put'
130
+ || parsed.command.kind === 'agentmd.set'
131
+ || parsed.command.kind === 'avatar';
123
132
  if (requiresSelfAid && runtime?.selfAid && parsed.command.aid !== runtime.selfAid.replace(/^@/, '')) {
124
133
  failManagedAid(formatJson, 'SCOPE_MISMATCH', `托管任务只能操作当前 self AID:${runtime.selfAid}`);
125
134
  }
@@ -155,10 +164,16 @@ async function runManagedAid(args, formatJson) {
155
164
  return;
156
165
  case 'agentmd.put':
157
166
  if (formatJson)
158
- console.log(JSON.stringify({ ok: true, aid: response.result.aid, path: response.result.path }, null, 2));
167
+ console.log(JSON.stringify({ ok: true, ...response.result }, null, 2));
159
168
  else
160
169
  console.log('✓ agent.md 已发布');
161
170
  return;
171
+ case 'agentmd.set':
172
+ if (formatJson)
173
+ console.log(JSON.stringify({ ok: true, ...response.result }, null, 2));
174
+ else
175
+ console.log(`✓ agent.md 字段已更新: ${response.result.field}`);
176
+ return;
162
177
  case 'avatar':
163
178
  if (formatJson)
164
179
  console.log(JSON.stringify({
@@ -441,6 +456,7 @@ Commands:
441
456
  lookup <aid> 远程探测 AID(是否存在 + 网关 + agent.md)
442
457
  agentmd put <aid> 读本地 agent.md → 签名 → 上传
443
458
  agentmd get <aid> 下载 agent.md → 验签 → 本地持久化
459
+ agentmd set <aid> <field> <value> 修改一个字段并签名上传
444
460
  avatar <aid> --file <path> | --url <https-url> 上传头像并更新 agent.md
445
461
 
446
462
  Options:
@@ -458,14 +474,16 @@ Options:
458
474
  ec aid delete --unrecoverable --yes
459
475
  ec aid lookup someone.agentid.pub
460
476
  ec aid agentmd put mybot.agentid.pub
461
- ec aid agentmd get someone.agentid.pub`);
477
+ ec aid agentmd get someone.agentid.pub
478
+ ec aid agentmd set mybot.agentid.pub description "A helpful agent"`);
462
479
  return;
463
480
  }
464
- if (process.env.EVOLCORE_SESSION_ID && !wantsHelp(args)) {
481
+ const isAgentmdHelp = sub === 'agentmd' && (!args[1] || isHelpFlag(args[1]));
482
+ if (process.env.EVOLCORE_SESSION_ID && !wantsHelp(args) && !isAgentmdHelp) {
465
483
  await runManagedAid(args, formatJson);
466
484
  return;
467
485
  }
468
- const { aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, agentmdPut, agentmdGet, buildInitialAgentMd, isValidAid } = await import('../aun/aid/index.js');
486
+ const { aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, agentmdPut, agentmdGet, agentmdSet, buildInitialAgentMd, isValidAid } = await import('../aun/aid/index.js');
469
487
  if (sub === 'list') {
470
488
  if (wantsHelp(args)) {
471
489
  console.log(`用法: ec aid list [筛选选项] [--no-verify] [--format json]
@@ -833,10 +851,50 @@ Options:
833
851
  const verb = args[1];
834
852
  const aid = args[2];
835
853
  if (!verb || isHelpFlag(verb) || wantsHelp(args)) {
836
- console.log(`用法: ec aid agentmd <put|get> <aid> [--format json]
854
+ console.log(`用法: ec aid agentmd <put|get|set> <aid> [field value] [--format json]
837
855
 
838
856
  put <aid> 读本地 agent.md → 用本地私钥签名 → 上传到 PKI
839
- get <aid> 从 PKI 下载 agent.md → 验签 → 持久化到本地`);
857
+ get <aid> 从 PKI 下载 agent.md → 验签 → 持久化到本地
858
+ set <aid> <field> <value>
859
+ 修改 name/type/description/version/tags 中的一个字段,签名后上传
860
+ tags 的 value 必须是 JSON 字符串数组,例如 ["coding","tools","aun"]`);
861
+ return;
862
+ }
863
+ if (verb === 'set') {
864
+ const field = args[3];
865
+ const value = args[4];
866
+ const editableFields = new Set(['name', 'type', 'description', 'version', 'tags']);
867
+ if (!aid || !field || value === undefined) {
868
+ failManagedAid(formatJson, 'INVALID_ARGUMENT', '用法: ec aid agentmd set <aid> <name|type|description|version|tags> <value>');
869
+ }
870
+ if (!isValidAid(aid)) {
871
+ failManagedAid(formatJson, 'INVALID_AID', `无效 AID 格式: ${aid}`);
872
+ }
873
+ if (!editableFields.has(field)) {
874
+ failManagedAid(formatJson, 'INVALID_ARGUMENT', `agentmd set field must be one of ${[...editableFields].join(', ')}`);
875
+ }
876
+ let parsedValue = value;
877
+ if (field === 'tags') {
878
+ try {
879
+ const parsed = JSON.parse(value);
880
+ if (!Array.isArray(parsed) || parsed.some(item => typeof item !== 'string'))
881
+ throw new Error('not an array of strings');
882
+ parsedValue = parsed;
883
+ }
884
+ catch {
885
+ failManagedAid(formatJson, 'INVALID_ARGUMENT', 'agentmd set tags value must be a JSON array of strings');
886
+ }
887
+ }
888
+ try {
889
+ const result = await agentmdSet(aid, field, parsedValue, { aunPath });
890
+ if (formatJson)
891
+ console.log(JSON.stringify({ ok: true, aid, ...result }, null, 2));
892
+ else
893
+ console.log(`✓ agent.md 字段 ${field} 已更新并发布`);
894
+ }
895
+ catch (error) {
896
+ failManagedAid(formatJson, String(error?.code ?? 'AID_OPERATION_FAILED'), String(error?.message ?? error));
897
+ }
840
898
  return;
841
899
  }
842
900
  if (verb === 'put') {
@@ -848,18 +906,21 @@ Options:
848
906
  console.error(`❌ 无效 AID 格式: ${aid}`);
849
907
  process.exit(1);
850
908
  }
851
- const localPath = agentMdPath(aid);
909
+ const localPath = agentMdPath(aid, aunPath);
852
910
  if (!fs.existsSync(localPath)) {
853
911
  console.error(`❌ 本地无 agent.md: ${aid}`);
854
912
  process.exit(1);
855
913
  }
856
914
  const content = fs.readFileSync(localPath, 'utf-8');
857
- await agentmdPut(content, { aid, aunPath });
858
- if (formatJson) {
859
- console.log(JSON.stringify({ ok: true, aid, path: localPath }, null, 2));
915
+ try {
916
+ const result = await agentmdPut(content, { aid, aunPath });
917
+ if (formatJson)
918
+ console.log(JSON.stringify({ ok: true, aid, ...result }, null, 2));
919
+ else
920
+ console.log('✓ agent.md 已发布');
860
921
  }
861
- else {
862
- console.log('✓ agent.md 已发布');
922
+ catch (error) {
923
+ failManagedAid(formatJson, String(error?.code ?? 'AID_OPERATION_FAILED'), String(error?.message ?? error));
863
924
  }
864
925
  return;
865
926
  }
@@ -888,7 +949,7 @@ Options:
888
949
  }
889
950
  return;
890
951
  }
891
- console.error(`未知子命令: aid agentmd ${verb ?? ''}\n用法: ec aid agentmd [put|get] <aid>`);
952
+ console.error(`未知子命令: aid agentmd ${verb ?? ''}\n用法: ec aid agentmd [put|get|set] <aid>`);
892
953
  process.exit(1);
893
954
  }
894
955
  if (sub === 'avatar') {
@@ -1727,6 +1788,21 @@ function printGroupSendAidHint() {
1727
1788
  console.error(`仅格式示例(.example.invalid 是保留的虚构域,不是真实 AID): ec group send ${GROUP_FROM_AID_FORMAT_EXAMPLE} ${GROUP_AID_FORMAT_EXAMPLE} "hello"`);
1728
1789
  console.error('查询真实群 AID: ec group list <发送方AID>');
1729
1790
  }
1791
+ function printGroupRulesUsage() {
1792
+ console.log(`用法:
1793
+ ec group rules <from> <group-id> get
1794
+ ec group rules <from> <group-id> set <file>
1795
+ ec group rules <from> <group-id> publish
1796
+ ec group rules <from> <group-id> [--mode open|approval|invite_only|closed] [--question Q] [--max-pending N]
1797
+
1798
+ 说明:
1799
+ get 读取当前已发布的群规则文件 /rules.md
1800
+ set 上传本地文件到群空间 /rules.md,发布 rules.content 元数据,并发送 group.rules.updated 通知
1801
+ publish 不上传文件,仅发布当前群空间 /rules.md 的元数据并发送通知
1802
+
1803
+ 兼容:
1804
+ 不带 get/set/publish 时仍使用旧的入群规则接口。`);
1805
+ }
1730
1806
  export async function cmdGroup(args) {
1731
1807
  args = canonicalizeEvolcoreCommandArgv(['ec', 'group', ...args]).slice(2);
1732
1808
  const sub = args[0];
@@ -1752,8 +1828,11 @@ export async function cmdGroup(args) {
1752
1828
  visitor 不开放历史;群权限仍由托管会话校验。`);
1753
1829
  return;
1754
1830
  }
1755
- const rulesActionHelp = sub === 'rules' && isHelpFlag(args[3]);
1756
- if (!sub || isHelpFlag(sub) || (wantsHelp(args) && !rulesActionHelp)) {
1831
+ if (sub === 'rules' && (wantsHelp(args) || isHelpFlag(args[3]))) {
1832
+ printGroupRulesUsage();
1833
+ return;
1834
+ }
1835
+ if (!sub || isHelpFlag(sub) || wantsHelp(args)) {
1757
1836
  console.log(`用法: ec group <command> <from-aid> [args...] [options]
1758
1837
 
1759
1838
  消息:
@@ -2320,21 +2399,6 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
2320
2399
  if (sub === 'rules') {
2321
2400
  const groupId = requireGroupId();
2322
2401
  const action = args[3];
2323
- const printRulesFileUsage = () => {
2324
- console.log(`用法:
2325
- ec group rules <from> <group-id> get
2326
- ec group rules <from> <group-id> set <file>
2327
- ec group rules <from> <group-id> publish
2328
- ec group rules <from> <group-id> [--mode open|approval|invite_only|closed] [--question Q] [--max-pending N]
2329
-
2330
- 说明:
2331
- get 读取当前已发布的群规则文件 /rules.md
2332
- set 上传本地文件到群空间 /rules.md,发布 rules.content 元数据,并发送 group.rules.updated 通知
2333
- publish 不上传文件,仅发布当前群空间 /rules.md 的元数据并发送通知
2334
-
2335
- 兼容:
2336
- 不带 get/set/publish 时仍使用旧的入群规则接口。`);
2337
- };
2338
2402
  const printRulesFileState = (result, verb) => {
2339
2403
  if (!result.ok) {
2340
2404
  if (formatJson) {
@@ -2381,10 +2445,6 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
2381
2445
  process.exit(1);
2382
2446
  }
2383
2447
  };
2384
- if (action && isHelpFlag(action)) {
2385
- printRulesFileUsage();
2386
- return;
2387
- }
2388
2448
  if (action === 'get') {
2389
2449
  const result = await groupRulesFileGet({ from, groupId, ...commonOpts });
2390
2450
  printRulesFileState(result, 'get');
@@ -2394,7 +2454,7 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
2394
2454
  const filePath = args[4];
2395
2455
  if (!filePath || filePath.startsWith('--')) {
2396
2456
  console.error('❌ 缺少 <file> 参数');
2397
- printRulesFileUsage();
2457
+ printGroupRulesUsage();
2398
2458
  process.exit(1);
2399
2459
  }
2400
2460
  const result = await groupRulesFileSet({ from, groupId, filePath, ...commonOpts });
@@ -2408,7 +2468,7 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
2408
2468
  }
2409
2469
  if (action && !action.startsWith('--')) {
2410
2470
  console.error(`❌ 未知 rules 动作: ${action}`);
2411
- printRulesFileUsage();
2471
+ printGroupRulesUsage();
2412
2472
  process.exit(1);
2413
2473
  }
2414
2474
  const wantsUpdate = args.includes('--mode') || args.includes('--question') || args.includes('--max-pending');
@@ -6,6 +6,7 @@ import { scanInstances } from '../utils/instance-registry.js';
6
6
  import { isProcessRunning, killProcess } from '../utils/cross-platform.js';
7
7
  import { ipcQuery } from '../ipc.js';
8
8
  import { cmdStart, cmdStop } from './daemon-commands.js';
9
+ import { wantsHelp } from './help.js';
9
10
  function printSummary(manifest) {
10
11
  const counts = manifest.operations.reduce((result, operation) => {
11
12
  result[operation.status] = (result[operation.status] ?? 0) + 1;
@@ -78,7 +79,7 @@ async function waitForMaintenanceWindow() {
78
79
  throw new Error(`cannot begin migration while runtime remains active (main=${runtime.mainCount}, restart-monitor=${runtime.monitorPids.length}, ipc=${ipc})`);
79
80
  }
80
81
  export async function cmdData(args) {
81
- if (args[0] !== 'migrate' || args.includes('--help')) {
82
+ if (args[0] !== 'migrate' || wantsHelp(args)) {
82
83
  console.log('用法: ec data migrate --dry-run | --apply [migration-id] | --verify [migration-id]');
83
84
  return;
84
85
  }
@@ -1,4 +1,4 @@
1
- import { wantsHelp } from './help.js';
1
+ import { isHelpFlag, wantsHelp } from './help.js';
2
2
  const MANAGED_TASK_COMMANDS = new Set([
3
3
  'aid',
4
4
  'agent',
@@ -51,7 +51,7 @@ export class ManagedCommandNotAllowedError extends Error {
51
51
  * Keep them outside managed tasks so they cannot read or mutate host state.
52
52
  */
53
53
  export function assertManagedTopLevelCommandAllowed(command, args, env = process.env) {
54
- if (!env.EVOLCORE_SESSION_ID || wantsHelp(args.slice(1)) || command === '--help' || command === '-h')
54
+ if (!env.EVOLCORE_SESSION_ID || isHelpFlag(command) || wantsHelp(args.slice(1)))
55
55
  return;
56
56
  if (!MANAGED_TASK_COMMANDS.has(command))
57
57
  throw new ManagedCommandNotAllowedError(command);
package/dist/cli/model.js CHANGED
@@ -601,7 +601,7 @@ async function cmdCheck(args, formatJson) {
601
601
  export async function cmdModel(args) {
602
602
  const sub = args[0];
603
603
  const formatJson = getArgValue(args, '--format') === 'json';
604
- if (!sub || isHelpFlag(sub)) {
604
+ if (!sub || isHelpFlag(sub) || wantsHelp(args)) {
605
605
  console.log(HELP);
606
606
  return;
607
607
  }
@@ -395,7 +395,7 @@ function cmdConfigSet(args, formatJson) {
395
395
  export async function cmdResponse(args) {
396
396
  const sub = args[0];
397
397
  const formatJson = getArgValue(args, '--format') === 'json';
398
- if (!sub || isHelpFlag(sub)) {
398
+ if (!sub || isHelpFlag(sub) || wantsHelp(args)) {
399
399
  console.log(HELP);
400
400
  return;
401
401
  }
@@ -7,7 +7,8 @@ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
7
7
  import { readTaskRuntimeContextFromEnv } from './task-context.js';
8
8
  import { assertEcSingleLine } from './cli-argv.js';
9
9
  import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
10
- import { normalizeTriggerDefinition, resolveScriptPath, } from '../trigger/validation.js';
10
+ import { readTriggerLocalFile, resolveTriggerLocalPath } from '../trigger/local-files.js';
11
+ import { normalizeTriggerDefinition, resolveScriptPath, safeRelativePath, } from '../trigger/validation.js';
11
12
  export async function cmdTrigger(args) {
12
13
  args = canonicalizeEvolcoreCommandArgv(['ec', 'trigger', ...args]).slice(2);
13
14
  const sub = args[0];
@@ -39,11 +40,31 @@ export async function cmdTrigger(args) {
39
40
  }
40
41
  return;
41
42
  }
42
- if (process.env.EVOLCORE_SESSION_ID
43
- && (hasOption(rest, '--file') || hasOption(rest, '--prompt-file') || hasOption(rest, '--script-path') || hasOption(rest, '--script-file'))) {
44
- throw new Error('Managed tasks cannot read trigger definitions, prompts, or scripts from local files; use inline prompt content');
45
- }
46
43
  try {
44
+ let fileScope;
45
+ if (process.env.EVOLCORE_SESSION_ID
46
+ && (hasOption(rest, '--file') || hasOption(rest, '--prompt-file') || hasOption(rest, '--script-path') || hasOption(rest, '--script-file'))) {
47
+ if (!['create', 'set', 'update'].includes(sub))
48
+ throw new Error('local file inputs are only valid for Trigger create/update');
49
+ const agentAid = resolveAgentAid(rest);
50
+ const triggerId = sub === 'update'
51
+ ? positional(rest, 0, ['--agent', '--format', '--if-revision', '--prompt-file', '--script-file'])
52
+ : undefined;
53
+ const authorization = await request({
54
+ type: 'trigger.authorizeLocalFiles',
55
+ agentAid,
56
+ operation: sub === 'update' ? 'trigger.update' : 'trigger.create',
57
+ ...(triggerId ? { triggerId } : {}),
58
+ cwd: process.cwd(),
59
+ });
60
+ if (authorization.fullAccess !== true) {
61
+ if (!authorization.fileScope || typeof authorization.fileScope.projectPath !== 'string'
62
+ || typeof authorization.fileScope.cwd !== 'string' || authorization.fileScope.selfAid !== agentAid) {
63
+ throw new Error('daemon did not provide a valid Trigger file scope');
64
+ }
65
+ fileScope = authorization.fileScope;
66
+ }
67
+ }
47
68
  switch (sub) {
48
69
  case 'list':
49
70
  await listTriggers(rest, json);
@@ -56,10 +77,10 @@ export async function cmdTrigger(args) {
56
77
  return;
57
78
  case 'create':
58
79
  case 'set':
59
- await createTrigger(rest, json);
80
+ await createTrigger(rest, json, fileScope);
60
81
  return;
61
82
  case 'update':
62
- await updateTrigger(rest, json);
83
+ await updateTrigger(rest, json, fileScope);
63
84
  return;
64
85
  case 'enable':
65
86
  case 'disable':
@@ -131,6 +152,10 @@ Create 参数模式支持:
131
152
  通用输出参数:
132
153
  --json | --format json
133
154
 
155
+ 托管文件输入:
156
+ 当前 Agent owner 经 daemon 鉴权后,可读取项目或本会话 TMPDIR 内的定义、prompt 和脚本(单文件上限 4 MiB)。
157
+ 普通脚本必须使用实际可用的进程沙箱;bypass 不授予宿主权限,fullaccess 仍要求独立宿主批准。
158
+
134
159
  Update 参数:
135
160
  --once | --delay | --at | --cron | --every | --event [--tz]
136
161
  --prompt <文本> | --prompt-file <路径> --script-file <路径> --name <名称>
@@ -240,7 +265,7 @@ async function showHistory(args, json) {
240
265
  console.log(`${timestamp} ${event.type.padEnd(18)} ${event.triggerId} ${detail}`);
241
266
  }
242
267
  }
243
- async function createTrigger(args, json) {
268
+ async function createTrigger(args, json, fileScope) {
244
269
  if (!hasOption(args, '--exec')) {
245
270
  throw new Error('创建 trigger 必须显式指定 --exec script|trigger-session|target-session');
246
271
  }
@@ -251,7 +276,7 @@ async function createTrigger(args, json) {
251
276
  throw new Error('--file(完整定义导入)与 --prompt-file(prompt 正文)不能同时指定');
252
277
  }
253
278
  const file = requireFlag(args, '--file');
254
- const { definition, files } = loadDefinitionWithFiles(file);
279
+ const { definition, files } = loadDefinitionWithFiles(file, fileScope);
255
280
  if (definition.execution.type !== executionType) {
256
281
  throw new Error(`--exec ${flagValue(args, '--exec')} 与 trigger.json 的 execution.type=${definition.execution.type} 不一致`);
257
282
  }
@@ -272,7 +297,7 @@ async function createTrigger(args, json) {
272
297
  throw new Error('--exec script 不接受 --prompt-file;请使用 --script-path 和 --script-runtime');
273
298
  }
274
299
  const { parseTriggerSetArgv } = await import('../trigger/parser.js');
275
- const parseResult = parseTriggerSetArgv(withPromptFromFile(triggerCreateArgs(args)), { allowLongPrompt: usesPromptFile });
300
+ const parseResult = parseTriggerSetArgv(withPromptFromFile(triggerCreateArgs(args), fileScope), { allowLongPrompt: usesPromptFile });
276
301
  if (!parseResult.ok) {
277
302
  throw new Error(parseResult.error);
278
303
  }
@@ -295,7 +320,7 @@ async function createTrigger(args, json) {
295
320
  throw new Error('--target-session thread 需要 --target-thread-id');
296
321
  }
297
322
  const script = parsed.scriptPath ? {
298
- path: parsed.scriptPath,
323
+ path: path.isAbsolute(parsed.scriptPath) ? path.basename(parsed.scriptPath) : safeRelativePath(parsed.scriptPath),
299
324
  runtime: parsed.scriptRuntime,
300
325
  args: parsed.scriptArgs,
301
326
  timeoutMs: parsed.scriptTimeoutMs ?? 30_000,
@@ -335,10 +360,14 @@ async function createTrigger(args, json) {
335
360
  reliability: { concurrency: 'forbid', missedPolicy: 'run_once', retry: { maxAttempts: 0, backoffMs: 30_000 } },
336
361
  limits: limitsFromParsed(parsed),
337
362
  };
363
+ const files = script ? [{
364
+ relativePath: script.path.replace(/\\/g, '/'),
365
+ contentBase64: readTriggerLocalFile(parsed.scriptPath, fileScope).toString('base64'),
366
+ }] : [];
338
367
  const res = await request({
339
368
  type: 'trigger.create',
340
369
  definition,
341
- files: [],
370
+ files,
342
371
  enable: hasFlag(args, '--enable') ? true : undefined,
343
372
  }, 30_000);
344
373
  if (json)
@@ -423,14 +452,14 @@ function shellQuoteArg(value) {
423
452
  return "''";
424
453
  return /[\s"'\\]/.test(value) ? `"${value.replace(/(["\\$`])/g, '\\$1')}"` : value;
425
454
  }
426
- async function updateTrigger(args, json) {
455
+ async function updateTrigger(args, json, fileScope) {
427
456
  const agentAid = resolveAgentAid(args);
428
457
  const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file', '--script-file']);
429
458
  const usesPromptFile = hasOption(args, '--prompt-file');
430
459
  const usesScriptFile = hasOption(args, '--script-file');
431
460
  const scriptFilePath = usesScriptFile ? requireFlag(args, '--script-file') : undefined;
432
461
  const { parseTriggerUpdateArgv } = await import('../trigger/parser.js');
433
- const parsed = parseTriggerUpdateArgv(triggerId, withPromptFromFile(triggerUpdateArgs(args)), { allowLongPrompt: usesPromptFile });
462
+ const parsed = parseTriggerUpdateArgv(triggerId, withPromptFromFile(triggerUpdateArgs(args), fileScope), { allowLongPrompt: usesPromptFile });
434
463
  if (!parsed.ok)
435
464
  throw new Error(parsed.error);
436
465
  const scriptFileRequested = parsed.value.scriptFile !== undefined;
@@ -438,7 +467,7 @@ async function updateTrigger(args, json) {
438
467
  const patch = scriptFileRequested ? { ...patchFields, scriptFile: true } : patchFields;
439
468
  const expectedRevision = flagValue(args, '--if-revision');
440
469
  const scriptFileBase64 = scriptFilePath
441
- ? fs.readFileSync(scriptFilePath).toString('base64')
470
+ ? readTriggerLocalFile(scriptFilePath, fileScope).toString('base64')
442
471
  : undefined;
443
472
  const res = await request({
444
473
  type: 'trigger.update',
@@ -474,7 +503,7 @@ function triggerUpdateArgs(args) {
474
503
  }
475
504
  return out;
476
505
  }
477
- function withPromptFromFile(args) {
506
+ function withPromptFromFile(args, fileScope) {
478
507
  const fileIndexes = [];
479
508
  const filePaths = [];
480
509
  for (let index = 0; index < args.length; index++) {
@@ -502,18 +531,9 @@ function withPromptFromFile(args) {
502
531
  if (hasOption(args, '--prompt'))
503
532
  throw new Error('--prompt-file 与 --prompt 不能同时指定');
504
533
  const filePath = filePaths[0];
505
- let stat;
506
- try {
507
- stat = fs.statSync(filePath);
508
- }
509
- catch (err) {
510
- throw new Error(`无法读取 prompt 文件 ${filePath}: ${err?.message || String(err)}`);
511
- }
512
- if (!stat.isFile())
513
- throw new Error(`prompt 文件必须是普通文件: ${filePath}`);
514
534
  let prompt;
515
535
  try {
516
- prompt = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
536
+ prompt = readTriggerLocalFile(filePath, fileScope).toString('utf8').replace(/^\uFEFF/, '');
517
537
  }
518
538
  catch (err) {
519
539
  throw new Error(`无法读取 prompt 文件 ${filePath}: ${err?.message || String(err)}`);
@@ -598,16 +618,19 @@ async function request(cmd, timeoutMs = 10_000) {
598
618
  throw new Error(res.error || 'trigger command failed');
599
619
  return res;
600
620
  }
601
- function loadDefinitionWithFiles(inputPath) {
621
+ function loadDefinitionWithFiles(inputPath, fileScope) {
622
+ inputPath = resolveTriggerLocalPath(inputPath, fileScope);
602
623
  const stat = fs.statSync(inputPath);
603
624
  const baseDir = stat.isDirectory() ? inputPath : path.dirname(inputPath);
604
625
  const jsonPath = stat.isDirectory() ? path.join(inputPath, 'trigger.json') : inputPath;
605
- const definition = normalizeTriggerDefinition(JSON.parse(fs.readFileSync(jsonPath, 'utf-8')));
626
+ const definition = normalizeTriggerDefinition(JSON.parse(readTriggerLocalFile(jsonPath, fileScope).toString('utf8')));
627
+ if (fileScope && definition.agentAid !== fileScope.selfAid)
628
+ throw new Error('imported Trigger must belong to the authorized Agent');
606
629
  const files = [];
607
630
  if (definition.execution.type === 'script') {
608
631
  const scriptAbs = resolveScriptPath(baseDir, definition.execution.script.path);
609
632
  const relativePath = path.relative(baseDir, scriptAbs).replace(/\\/g, '/');
610
- files.push({ relativePath, contentBase64: fs.readFileSync(scriptAbs).toString('base64') });
633
+ files.push({ relativePath, contentBase64: readTriggerLocalFile(scriptAbs, fileScope).toString('base64') });
611
634
  }
612
635
  return { definition, files };
613
636
  }
@@ -12,11 +12,105 @@ import fs from 'fs';
12
12
  import path from 'path';
13
13
  import { resolvePaths } from '../paths.js';
14
14
  import { logger } from '../utils/logger.js';
15
- import { readCurrent, readWVersion, writeWVersion, materializeVersion, listAllVersions, diffVersions, snapshot, isConfigSnapshotsEnabled, } from './snapshot.js';
15
+ import { autoMigrateIfNeeded } from '../config-store.js';
16
+ import { initConfigManager } from './config-manager.js';
17
+ import { ensureRoleConfigV4OnStartup, ensureRoleConfigV5OnStartup } from './role-migration-startup.js';
18
+ import { ensureAuxiliaryModelMigrationOnStartup } from './schema-migration.js';
19
+ import { ensureContactBookV2OnStartup } from './contact-book-v2-startup.js';
20
+ import { readInstalledEvolcoreVersion, readFastaunVersion } from '../utils/evolcore-version.js';
21
+ import { readCurrent, readWVersion, writeWVersion, materializeVersion, listAllVersions, diffVersions, snapshot, isConfigSnapshotsEnabled, ensureStartupConfigSnapshot, incrementSuccessCount, retentionCleanup, } from './snapshot.js';
16
22
  const MAX_LINES = 300;
17
23
  const KEEP_MIN = 150;
18
24
  const FALLBACK_MAX_TRIES = 20;
19
25
  const FALLBACK_MAX_MS = 120_000;
26
+ export async function prepareStartupConfig(recoverRoleMutations) {
27
+ recoverRoleMutations();
28
+ autoMigrateIfNeeded();
29
+ const auxiliaryModelMigration = await ensureAuxiliaryModelMigrationOnStartup();
30
+ if (auxiliaryModelMigration) {
31
+ const message = `✓ Session Renew auxiliary model migration applied automatically: ${auxiliaryModelMigration.changedFiles} file(s)`
32
+ + (auxiliaryModelMigration.backup ? `; backup: ${auxiliaryModelMigration.backup}` : '');
33
+ logger.info(message);
34
+ console.error(message);
35
+ }
36
+ const migration = await ensureRoleConfigV4OnStartup();
37
+ if (migration) {
38
+ const message = `✓ Role config v4 migration applied automatically: ${migration.changedFiles} file(s)`
39
+ + (migration.backup ? `; backup: ${migration.backup}` : '');
40
+ logger.info(message);
41
+ console.error(message);
42
+ }
43
+ const domainMigration = await ensureRoleConfigV5OnStartup();
44
+ if (domainMigration) {
45
+ const message = `✓ Standalone role config migration applied automatically: ${domainMigration.changedFiles} file(s)`
46
+ + (domainMigration.backup ? `; backup: ${domainMigration.backup}` : '');
47
+ logger.info(message);
48
+ console.error(message);
49
+ }
50
+ const contactMigration = await ensureContactBookV2OnStartup();
51
+ if (contactMigration) {
52
+ const message = `✓ Contact book v2 migration applied automatically: ${contactMigration.changedFiles} file(s)`
53
+ + (contactMigration.backup ? `; backup: ${contactMigration.backup}` : '');
54
+ logger.info(message);
55
+ console.error(message);
56
+ }
57
+ }
58
+ export async function initializeStartupConfig(abort) {
59
+ try {
60
+ initConfigManager();
61
+ }
62
+ catch (error) {
63
+ const message = `❌ 配置 schema 校验失败: ${error instanceof Error ? error.message : String(error)}`;
64
+ logger.error(message);
65
+ abort(message);
66
+ }
67
+ const snapshotsEnabled = isConfigSnapshotsEnabled();
68
+ if (process.env.EVOLCORE_DIAGNOSE === '1') {
69
+ if (!snapshotsEnabled) {
70
+ const message = '配置快照功能已关闭,无法执行版本回落;请设置 configSnapshots=true';
71
+ logger.error(`[diagnose] ${message}`);
72
+ abort(message);
73
+ }
74
+ logger.info('[diagnose] 进入自检模式,逐版本回落尝试...');
75
+ const result = await selfDiagnose();
76
+ if (result.ok) {
77
+ logger.info(`[diagnose] ✓ 回落到 ${result.actualVersion?.delta} 成功,继续启动。`);
78
+ }
79
+ else {
80
+ const message = result.message ?? '✗ 自检失败:未找到可用版本。';
81
+ logger.error(message);
82
+ abort(message);
83
+ }
84
+ }
85
+ return snapshotsEnabled;
86
+ }
87
+ export function recordStartupConfig(snapshotsEnabled) {
88
+ if (!snapshotsEnabled) {
89
+ logger.info('[config] snapshots disabled by configSnapshots=false');
90
+ return;
91
+ }
92
+ try {
93
+ const isDiagnoseMode = process.env.EVOLCORE_DIAGNOSE === '1';
94
+ const startupVersion = isDiagnoseMode ? readWVersion() : ensureStartupConfigSnapshot();
95
+ if (startupVersion)
96
+ incrementSuccessCount(startupVersion.delta);
97
+ const startMethod = isDiagnoseMode ? 'diagnose'
98
+ : (process.env.EVOLCORE_LAUNCHED_BY === 'start' ? 'manual' : 'auto');
99
+ appendBootLog({
100
+ bootedAt: new Date().toISOString(),
101
+ startMethod,
102
+ selectedVersion: readCurrent(),
103
+ actualVersion: startupVersion,
104
+ fellBack: !!(isDiagnoseMode && startupVersion && readCurrent()?.delta !== startupVersion.delta),
105
+ versions: { evolcore: readInstalledEvolcoreVersion(), '@agentunion/fastaun': readFastaunVersion(), node: process.version },
106
+ platform: `${process.platform}/${process.arch}`,
107
+ });
108
+ retentionCleanup();
109
+ }
110
+ catch (error) {
111
+ logger.warn(`[config] startup snapshot/boot-log failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ }
20
114
  export function appendBootLog(entry) {
21
115
  if (!isConfigSnapshotsEnabled())
22
116
  return;
@@ -62,6 +62,7 @@ export function getManagementCommandPermissions(role) {
62
62
  'ec.aid.lookup': { allow: true, scopes: ['agent'] },
63
63
  'ec.aid.agentmd.get': { allow: true, scopes: ['agent'] },
64
64
  'ec.aid.agentmd.put': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
65
+ 'ec.aid.agentmd.set': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
65
66
  'ec.aid.avatar': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentOwner: true } },
66
67
  'ec.fs.mkdir': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
67
68
  'ec.fs.upload': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
@@ -9,6 +9,7 @@ import { isManagementRole } from './builtin-roles.js';
9
9
  import { getRoleDefinition, getRoleRank, normalizeDefaultRole, roleExists } from './roles.js';
10
10
  import { getStoredRoleRegistry, roleRegistryExists } from './role-store.js';
11
11
  import { resolvePrincipal } from './contact-book.js';
12
+ import { tryParseChannelKey } from '../core/channel-loader.js';
12
13
  import { isValidAid } from '../aun/aid/validation.js';
13
14
  import { isExplicitGroupId } from '../aun/group/index.js';
14
15
  export function isAuthenticated(userId) {
@@ -131,6 +132,26 @@ export function resolvePeerRoleDetail(ctx) {
131
132
  ? resultFor(fallbackRole, 'builtin-fallback', actor, ctx.selfAid)
132
133
  : resultFor(null, 'none', actor, ctx.selfAid);
133
134
  }
135
+ export function createSessionIdentityResolver(agentRegistry) {
136
+ return (channel, userId, chatType, conversationId) => {
137
+ const parsed = tryParseChannelKey(channel);
138
+ const owningAgent = agentRegistry.resolveByChannel(channel);
139
+ const selfAid = owningAgent?.aid ?? parsed?.selfAID;
140
+ if (!selfAid || !userId)
141
+ return { role: 'none', mode: 'interactive' };
142
+ const actualChatType = chatType || 'private';
143
+ const actualConversationId = actualChatType === 'group' ? (conversationId || userId) : userId;
144
+ const detail = resolvePeerRoleDetail({
145
+ selfAid,
146
+ channelKey: channel,
147
+ channelType: parsed?.type || channel,
148
+ chatType: actualChatType,
149
+ actorId: userId,
150
+ conversationId: actualConversationId,
151
+ });
152
+ return roleToSessionIdentity(detail.effectiveRole);
153
+ };
154
+ }
134
155
  export function roleToSessionIdentity(role) {
135
156
  return { role: role ?? 'none', mode: 'interactive' };
136
157
  }