evolcore 0.0.24 → 0.0.25

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 (197) hide show
  1. package/CHANGELOG.md +578 -561
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +160 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +29 -29
  8. package/bin/install-codex-managed-hooks.mjs +555 -555
  9. package/dist/agents/claude-runner.js +4 -1
  10. package/dist/agents/codex-runner.js +7 -4
  11. package/dist/agents/ecagent-runner.js +14 -13
  12. package/dist/agents/gemini-runner.js +22 -2
  13. package/dist/aun/aid/agentmd.js +56 -4
  14. package/dist/aun/aid/index.js +1 -0
  15. package/dist/aun/aid/managed-operation.js +53 -1
  16. package/dist/aun/storage/client.js +4 -1
  17. package/dist/aun/storage/index.js +1 -1
  18. package/dist/cli/agent-command.js +186 -87
  19. package/dist/cli/agent-create.js +42 -4
  20. package/dist/cli/agent.js +103 -1
  21. package/dist/cli/aun-commands.js +360 -294
  22. package/dist/cli/bench.js +20 -20
  23. package/dist/cli/config.js +20 -20
  24. package/dist/cli/contact.js +10 -10
  25. package/dist/cli/ctl-command.js +22 -22
  26. package/dist/cli/daemon-commands.js +15 -15
  27. package/dist/cli/fs-command.js +67 -67
  28. package/dist/cli/handoff-command.js +7 -7
  29. package/dist/cli/index.js +77 -77
  30. package/dist/cli/link-rules.js +21 -21
  31. package/dist/cli/menu-client.js +11 -11
  32. package/dist/cli/net-check.js +22 -22
  33. package/dist/cli/queue-command.js +29 -29
  34. package/dist/cli/response.js +30 -30
  35. package/dist/cli/restart-monitor.js +30 -30
  36. package/dist/cli/stats.js +51 -51
  37. package/dist/cli/trigger-command.js +56 -56
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-manager.js +15 -0
  40. package/dist/config/config-operation-service.js +20 -3
  41. package/dist/config/resolved-config-op.js +39 -1
  42. package/dist/core/auth/operation-authorizer.js +62 -4
  43. package/dist/core/auth/operation-catalog.js +16 -0
  44. package/dist/core/command/agent-control.js +54 -25
  45. package/dist/core/command/cli-intent-parser.js +18 -0
  46. package/dist/core/command/command-handler.js +91 -16
  47. package/dist/core/command/menu-catalog.js +1 -0
  48. package/dist/core/command/menu-handler.js +41 -5
  49. package/dist/core/command/menu-protocol.js +1 -0
  50. package/dist/core/command/slash-handler.js +9 -9
  51. package/dist/core/event-catalog.js +5 -0
  52. package/dist/core/evolagent.js +3 -3
  53. package/dist/core/message/response-engine.js +12 -6
  54. package/dist/core/permission/tool-policy.js +45 -6
  55. package/dist/index.js +209 -162
  56. package/dist/stats/db.js +159 -159
  57. package/dist/stats/eck-vars.js +9 -9
  58. package/dist/stats/query.js +139 -139
  59. package/dist/stats/role-budget.js +6 -6
  60. package/dist/stats/writer.js +51 -51
  61. package/dist/utils/avatar-upload.js +282 -23
  62. package/dist/utils/error-dict.json +160 -160
  63. package/dist/utils/macos-autostart.js +22 -22
  64. package/dist/utils/model-prices.jsonl +23 -23
  65. package/dist/utils/welcome.js +22 -22
  66. package/ecagent/LICENSE +21 -21
  67. package/ecagent/NOTICE +11 -11
  68. package/ecagent/README.md +61 -61
  69. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  70. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  71. package/ecagent/dist/harness/messages.js +7 -7
  72. package/kits/docs/GUIDE.md +20 -20
  73. package/kits/docs/INDEX.md +66 -66
  74. package/kits/docs/aun/CHEATSHEET.md +19 -19
  75. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  76. package/kits/docs/channels/aun.md +56 -56
  77. package/kits/docs/channels/feishu.md +56 -56
  78. package/kits/docs/context-assembly.md +365 -364
  79. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  80. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  81. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  82. package/kits/docs/eck_templates/runtime.template.md +19 -19
  83. package/kits/docs/evolcore/INDEX.md +71 -69
  84. package/kits/docs/evolcore/agent-create.md +170 -156
  85. package/kits/docs/evolcore/agent.md +83 -73
  86. package/kits/docs/evolcore/aid.md +64 -62
  87. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  88. package/kits/docs/evolcore/config.md +195 -195
  89. package/kits/docs/evolcore/contact.md +68 -68
  90. package/kits/docs/evolcore/ctl.md +46 -46
  91. package/kits/docs/evolcore/event.md +216 -216
  92. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  93. package/kits/docs/evolcore/fs.md +110 -110
  94. package/kits/docs/evolcore/group-collaboration.md +243 -243
  95. package/kits/docs/evolcore/group-fs.md +17 -17
  96. package/kits/docs/evolcore/group-rules.md +251 -251
  97. package/kits/docs/evolcore/group.md +152 -152
  98. package/kits/docs/evolcore/menu-client.md +34 -34
  99. package/kits/docs/evolcore/model.md +50 -50
  100. package/kits/docs/evolcore/msg.md +165 -165
  101. package/kits/docs/evolcore/response.md +75 -75
  102. package/kits/docs/evolcore/rpc.md +37 -37
  103. package/kits/docs/evolcore/self-summary.md +29 -29
  104. package/kits/docs/evolcore/stats.md +83 -83
  105. package/kits/docs/evolcore/storage.md +50 -50
  106. package/kits/docs/evolcore/trigger.md +575 -575
  107. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  108. package/kits/docs/identity/PATH_OPS.md +16 -16
  109. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  110. package/kits/docs/identity/identity-tools.md +26 -26
  111. package/kits/docs/path-registry.md +62 -62
  112. package/kits/docs/prompt-loading-architecture.md +270 -269
  113. package/kits/docs/venues/aun-group.md +45 -45
  114. package/kits/docs/venues/aun-private.md +10 -10
  115. package/kits/docs/venues/client-desktop.md +10 -10
  116. package/kits/docs/venues/client-mobile.md +10 -10
  117. package/kits/docs/venues/feishu-group.md +13 -13
  118. package/kits/docs/venues/feishu-private.md +9 -9
  119. package/kits/docs/venues/group.md +25 -25
  120. package/kits/docs/venues/private.md +10 -10
  121. package/kits/eck_manifest.auxiliary.json +43 -43
  122. package/kits/eck_manifest.json +195 -183
  123. package/kits/eck_message_manifest.json +74 -74
  124. package/kits/migrations/README-role-config-v4.md +32 -32
  125. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  126. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  127. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  128. package/kits/migrations/rename-config-file.mjs +99 -99
  129. package/kits/rules/01-overview.md +162 -162
  130. package/kits/rules/02-navigation.md +96 -96
  131. package/kits/rules/03-identity.md +39 -37
  132. package/kits/rules/04-relation.md +75 -75
  133. package/kits/rules/05-venue.md +60 -60
  134. package/kits/rules/06-channel.md +63 -63
  135. package/kits/schemas/_meta.json +53 -53
  136. package/kits/schemas/agent-config.schema.1.json +177 -177
  137. package/kits/schemas/agent-config.schema.10.json +395 -395
  138. package/kits/schemas/agent-config.schema.11.json +421 -421
  139. package/kits/schemas/agent-config.schema.12.json +427 -427
  140. package/kits/schemas/agent-config.schema.2.json +239 -239
  141. package/kits/schemas/agent-config.schema.3.json +119 -119
  142. package/kits/schemas/agent-config.schema.4.json +208 -208
  143. package/kits/schemas/agent-config.schema.5.json +326 -326
  144. package/kits/schemas/agent-config.schema.6.json +322 -322
  145. package/kits/schemas/agent-config.schema.7.json +303 -303
  146. package/kits/schemas/agent-config.schema.8.json +304 -304
  147. package/kits/schemas/agent-config.schema.9.json +364 -364
  148. package/kits/schemas/contact-book.schema.1.json +36 -36
  149. package/kits/schemas/contact-book.schema.2.json +43 -43
  150. package/kits/schemas/contact-book.schema.3.json +67 -67
  151. package/kits/schemas/daemon.schema.1.json +91 -91
  152. package/kits/schemas/daemon.schema.2.json +101 -101
  153. package/kits/schemas/daemon.schema.3.json +123 -123
  154. package/kits/schemas/daemon.schema.4.json +136 -136
  155. package/kits/schemas/daemon.schema.5.json +135 -135
  156. package/kits/schemas/daemon.schema.6.json +131 -131
  157. package/kits/schemas/defaults.schema.1.json +81 -81
  158. package/kits/schemas/defaults.schema.2.json +85 -85
  159. package/kits/schemas/defaults.schema.3.json +73 -73
  160. package/kits/schemas/defaults.schema.4.json +107 -107
  161. package/kits/schemas/defaults.schema.5.json +119 -119
  162. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  163. package/kits/schemas/migrations/README.md +30 -30
  164. package/kits/schemas/relation-config.schema.1.json +158 -158
  165. package/kits/schemas/relation-config.schema.2.json +73 -73
  166. package/kits/schemas/relation-config.schema.3.json +50 -50
  167. package/kits/schemas/relation-config.schema.4.json +47 -47
  168. package/kits/schemas/relation-config.schema.5.json +47 -47
  169. package/kits/schemas/relation-config.schema.6.json +46 -46
  170. package/kits/schemas/relation-config.schema.7.json +59 -59
  171. package/kits/schemas/relation-config.schema.8.json +103 -103
  172. package/kits/schemas/role-config.schema.1.json +201 -201
  173. package/kits/schemas/role-registry.schema.1.json +35 -35
  174. package/kits/schemas/single-session.schema.1.json +31 -31
  175. package/kits/schemas/single-session.schema.2.json +57 -57
  176. package/kits/schemas/single-session.schema.3.json +32 -32
  177. package/kits/templates/bootstrap-welcome.md +17 -17
  178. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  179. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  180. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  181. package/kits/templates/message-fragments/inject-default.md +2 -2
  182. package/kits/templates/message-fragments/item.md +2 -2
  183. package/kits/templates/roles/admin.json +96 -84
  184. package/kits/templates/roles/member.json +97 -96
  185. package/kits/templates/roles/owner.json +9 -9
  186. package/kits/templates/roles/visitor.json +86 -85
  187. package/kits/templates/system-fragments/baseagent.md +14 -14
  188. package/kits/templates/system-fragments/bootstrap.md +38 -29
  189. package/kits/templates/system-fragments/channel.md +64 -64
  190. package/kits/templates/system-fragments/commands.md +28 -28
  191. package/kits/templates/system-fragments/identity.md +13 -13
  192. package/kits/templates/system-fragments/relation.md +19 -19
  193. package/kits/templates/system-fragments/session.md +59 -59
  194. package/kits/templates/system-fragments/venue.md +31 -31
  195. package/package.json +98 -98
  196. package/skills/eclink/SKILL.md +0 -84
  197. package/skills/eclink/agents/openai.yaml +0 -4
@@ -2489,7 +2489,10 @@ export class AgentRunner {
2489
2489
  const bootstrapWritePaths = modelOverride?.bootstrapAgentMdPath
2490
2490
  ? [path.resolve(modelOverride.bootstrapAgentMdPath)]
2491
2491
  : [];
2492
- const allowedWritePaths = [...new Set([...runtimeWritePaths, ...bootstrapWritePaths])];
2492
+ const personalWritePaths = modelOverride?.personalMdPath
2493
+ ? [path.resolve(modelOverride.personalMdPath)]
2494
+ : [];
2495
+ const allowedWritePaths = [...new Set([...runtimeWritePaths, ...bootstrapWritePaths, ...personalWritePaths])];
2493
2496
  await assertClaudeSettingSourcesHaveLiteralSandboxPaths(projectPath, [...settingSources], managedSettings);
2494
2497
  const sandboxProjectionDurationMs = Date.now() - sandboxProjectionStartedAt;
2495
2498
  if (sandboxProjectionDurationMs > 2_000) {
@@ -1298,7 +1298,7 @@ export class CodexRunner {
1298
1298
  }
1299
1299
  return Object.fromEntries([...paths].map(candidate => [candidate, 'write']));
1300
1300
  }
1301
- buildPermissionProfileConfig(sessionId, mode, projectPath, bootstrapAgentMdPath) {
1301
+ buildPermissionProfileConfig(sessionId, mode, projectPath, bootstrapAgentMdPath, personalMdPath) {
1302
1302
  const profileName = this.permissionProfileName(sessionId);
1303
1303
  const managedTempDir = this.ensureSessionTempDir(sessionId);
1304
1304
  const network = {
@@ -1324,6 +1324,9 @@ export class CodexRunner {
1324
1324
  ...(bootstrapAgentMdPath ? {
1325
1325
  [path.resolve(bootstrapAgentMdPath)]: mode === 'readonly' ? 'read' : 'write',
1326
1326
  } : {}),
1327
+ ...(personalMdPath ? {
1328
+ [path.resolve(personalMdPath)]: mode === 'readonly' ? 'read' : 'write',
1329
+ } : {}),
1327
1330
  ...(mode !== 'readonly' && managedTempDir ? {
1328
1331
  [managedTempDir]: 'write',
1329
1332
  [managedTempDir + '/**']: 'write',
@@ -1336,12 +1339,12 @@ export class CodexRunner {
1336
1339
  },
1337
1340
  };
1338
1341
  }
1339
- buildExecutionSandboxOptions(sessionId, mode, projectPath, bootstrapAgentMdPath) {
1342
+ buildExecutionSandboxOptions(sessionId, mode, projectPath, bootstrapAgentMdPath, personalMdPath) {
1340
1343
  const decision = resolvePhaseOneExecutionSandbox(mode, this.permissionContexts.get(sessionId)?.role);
1341
1344
  return {
1342
1345
  decision,
1343
1346
  permissionProfileConfig: decision.state === 'active'
1344
- ? this.buildPermissionProfileConfig(sessionId, decision.permissionMode, projectPath, bootstrapAgentMdPath)
1347
+ ? this.buildPermissionProfileConfig(sessionId, decision.permissionMode, projectPath, bootstrapAgentMdPath, personalMdPath)
1345
1348
  : undefined,
1346
1349
  sandbox: decision.state === 'off' ? 'danger-full-access' : undefined,
1347
1350
  };
@@ -1758,7 +1761,7 @@ export class CodexRunner {
1758
1761
  }
1759
1762
  this.chatModes.set(sessionId, callMode);
1760
1763
  const callApprovalPolicy = this.toApprovalPolicy(callMode);
1761
- const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath);
1764
+ const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath, modelOverride?.personalMdPath);
1762
1765
  const appServer = this.getAppServerClient(callMode === 'fullaccess');
1763
1766
  this.dropExpiredPendingInterrupt(sessionId);
1764
1767
  const effectiveApprovalPolicy = callApprovalPolicy;
@@ -18,8 +18,8 @@ import { appendRequestPath } from './request-identity.js';
18
18
  import { getManagedTaskTempDir } from '../cli/task-context.js';
19
19
  import { hasTrustedFullAccessContext } from './runner-types.js';
20
20
  const PROVIDER_ID = 'evolcore-gateway';
21
- const DEFAULT_SYSTEM_PROMPT = `You are ecagent, the coding agent built into EvolCore.
22
- Work directly in the current project. Inspect relevant files before changing them, keep edits scoped, and verify the result.
21
+ const DEFAULT_SYSTEM_PROMPT = `You are ecagent, the coding agent built into EvolCore.
22
+ Work directly in the current project. Inspect relevant files before changing them, keep edits scoped, and verify the result.
23
23
  Use the provided tools for filesystem and shell work. Never claim a tool action succeeded unless its result confirms success.`;
24
24
  const MAX_READ_BYTES = 1024 * 1024;
25
25
  const MAX_TOOL_OUTPUT = 200_000;
@@ -468,26 +468,27 @@ function isWithin(root, candidate) {
468
468
  const relative = path.relative(root, candidate);
469
469
  return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
470
470
  }
471
- export function resolveEcagentToolPath(projectPath, candidate, forWrite = false, bootstrapAgentMdPath, fullaccess = false) {
471
+ export function resolveEcagentToolPath(projectPath, candidate, forWrite = false, bootstrapAgentMdPath, fullaccess = false, personalMdPath) {
472
472
  const root = fs.realpathSync(projectPath);
473
473
  const resolved = path.resolve(root, candidate || '.');
474
474
  const allowedBootstrapFile = bootstrapAgentMdPath
475
475
  ? path.resolve(bootstrapAgentMdPath)
476
476
  : undefined;
477
- const isBootstrapAgentMd = allowedBootstrapFile === resolved;
478
- if (!fullaccess && !isWithin(root, resolved) && !isBootstrapAgentMd) {
477
+ const allowedPersonalFile = personalMdPath ? path.resolve(personalMdPath) : undefined;
478
+ const isManagedIdentityFile = resolved === allowedBootstrapFile || resolved === allowedPersonalFile;
479
+ if (!fullaccess && !isWithin(root, resolved) && !isManagedIdentityFile) {
479
480
  throw new Error(`Path is outside the project: ${candidate}`);
480
481
  }
481
- if (isBootstrapAgentMd) {
482
+ if (isManagedIdentityFile) {
482
483
  const parent = path.dirname(resolved);
483
484
  const canonicalParent = fs.realpathSync(parent);
484
485
  if (!isWithin(parent, canonicalParent) || !isWithin(canonicalParent, parent)) {
485
- throw new Error(`Bootstrap agent.md parent resolves outside its declared path: ${candidate}`);
486
+ throw new Error(`Managed identity file parent resolves outside its declared path: ${candidate}`);
486
487
  }
487
488
  if (fs.existsSync(resolved)) {
488
489
  const canonicalFile = fs.realpathSync(resolved);
489
490
  if (!isWithin(resolved, canonicalFile) || !isWithin(canonicalFile, resolved)) {
490
- throw new Error(`Bootstrap agent.md resolves outside its declared path: ${candidate}`);
491
+ throw new Error(`Managed identity file resolves outside its declared path: ${candidate}`);
491
492
  }
492
493
  }
493
494
  return resolved;
@@ -550,7 +551,7 @@ async function walkFiles(root, signal) {
550
551
  function textResult(text, details = {}) {
551
552
  return { content: [{ type: 'text', text: truncate(text) }], details };
552
553
  }
553
- function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullaccess = false) {
554
+ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullaccess = false, personalMdPath) {
554
555
  const read = {
555
556
  name: 'Read', label: 'Read', description: 'Read a UTF-8 text file from the project.',
556
557
  parameters: Type.Object({
@@ -559,7 +560,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
559
560
  limit: Type.Optional(Type.Number({ minimum: 1 })),
560
561
  }),
561
562
  async execute(_id, params, signal) {
562
- const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess);
563
+ const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess, personalMdPath);
563
564
  const stat = await fs.promises.stat(file);
564
565
  if (stat.size > MAX_READ_BYTES)
565
566
  throw new Error(`File exceeds ${MAX_READ_BYTES} byte read limit`);
@@ -625,7 +626,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
625
626
  name: 'Write', label: 'Write', description: 'Create or completely overwrite a project file.',
626
627
  parameters: Type.Object({ file_path: Type.String(), content: Type.String() }), executionMode: 'sequential',
627
628
  async execute(_id, params, signal) {
628
- const file = resolveEcagentToolPath(projectPath, params.file_path, true, bootstrapAgentMdPath, fullaccess);
629
+ const file = resolveEcagentToolPath(projectPath, params.file_path, true, bootstrapAgentMdPath, fullaccess, personalMdPath);
629
630
  await fs.promises.mkdir(path.dirname(file), { recursive: true });
630
631
  await fs.promises.writeFile(file, params.content, { encoding: 'utf8', signal });
631
632
  return textResult(`Wrote ${Buffer.byteLength(params.content)} bytes to ${path.relative(projectPath, file)}`, { file });
@@ -638,7 +639,7 @@ function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath, fullacc
638
639
  replace_all: Type.Optional(Type.Boolean()),
639
640
  }), executionMode: 'sequential',
640
641
  async execute(_id, params, signal) {
641
- const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess);
642
+ const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath, fullaccess, personalMdPath);
642
643
  const value = await fs.promises.readFile(file, { encoding: 'utf8', signal });
643
644
  if (!params.old_string)
644
645
  throw new Error('old_string must not be empty');
@@ -858,7 +859,7 @@ export class EcagentRunner {
858
859
  models,
859
860
  model,
860
861
  thinkingLevel: normalizeThinkingLevel(effort),
861
- tools: createTools(nodeEnv, request.projectPath, request.runtimeEnv, request.modelOverride?.bootstrapAgentMdPath, fullaccess),
862
+ tools: createTools(nodeEnv, request.projectPath, request.runtimeEnv, request.modelOverride?.bootstrapAgentMdPath, fullaccess, request.modelOverride?.personalMdPath),
862
863
  systemPrompt: [DEFAULT_SYSTEM_PROMPT, request.systemPromptAppend].filter(Boolean).join('\n\n'),
863
864
  streamOptions: {
864
865
  timeoutMs: this.config.timeoutMs,
@@ -147,6 +147,7 @@ const GEMINI_EC_COMMAND_PATTERNS = [
147
147
  // quoted arguments.
148
148
  String.raw `\x00"command":"[ ]*ec[ ]+(?:[^"\x00;&|$()<>\x60\r\n\\]|\\(?:\\|"))*"\x00`,
149
149
  ];
150
+ const GEMINI_BOOTSTRAP_PERSONA_COMMAND_PATTERN = String.raw `^\{\x00"command":"[ ]*ec[ ]+agent[ ]+persona(?:[ ]+show|[ ]+--(?:text|file)[ ]+(?:[^"\x00;&|$()<>\x60\r\n\\]|\\(?:\\|"))+)[ ]*"\x00\}$`;
150
151
  export function resolveGeminiPermissionProfile(value, bootstrap = false) {
151
152
  if (value === 'fullaccess')
152
153
  return { mode: 'fullaccess', approvalMode: 'yolo' };
@@ -186,6 +187,9 @@ function geminiExactShellCommandPattern(command) {
186
187
  // is_background or additional_permissions during bootstrap.
187
188
  return `^\\{\\x00"command":"[ ]*${escapeGeminiArgsRegex(command)}[ ]*"\\x00\\}$`;
188
189
  }
190
+ function geminiManagedIdentityPathPattern(filePath) {
191
+ return geminiJsonFieldEqualsPattern(['file_path'], path.resolve(filePath));
192
+ }
189
193
  function appendGeminiPolicyRule(lines, toolName, decision, priority, argsPattern) {
190
194
  lines.push('[[rule]]');
191
195
  lines.push(`toolName = ${Array.isArray(toolName) ? JSON.stringify(toolName) : JSON.stringify(toolName)}`);
@@ -194,7 +198,7 @@ function appendGeminiPolicyRule(lines, toolName, decision, priority, argsPattern
194
198
  lines.push(`decision = "${decision}"`);
195
199
  lines.push(`priority = ${priority}`, '');
196
200
  }
197
- export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath) {
201
+ export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath, personalMdPath) {
198
202
  const lines = [];
199
203
  if (bootstrapAgentMdPath) {
200
204
  const bootstrap = normalizeGeminiBootstrapAgentMdPath(bootstrapAgentMdPath);
@@ -212,6 +216,12 @@ export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath) {
212
216
  appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_FILE_TOOLS, 'allow', 990, writePathPattern);
213
217
  appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, geminiExactShellCommandPattern(`ec aid agentmd put ${bootstrap.aid}`));
214
218
  appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, geminiExactShellCommandPattern(`ec agent ready ${bootstrap.aid}`));
219
+ appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, GEMINI_BOOTSTRAP_PERSONA_COMMAND_PATTERN);
220
+ if (personalMdPath) {
221
+ const personalPathPattern = geminiManagedIdentityPathPattern(personalMdPath);
222
+ appendGeminiPolicyRule(lines, 'read_file', 'allow', 989, personalPathPattern);
223
+ appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_FILE_TOOLS, 'allow', 989, personalPathPattern);
224
+ }
215
225
  // These are Gemini's non-I/O task bookkeeping tools. They do not expose
216
226
  // project files, shell, network, MCP, or user-interaction capabilities.
217
227
  appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_LIFECYCLE_TOOLS, 'allow', 950);
@@ -231,6 +241,11 @@ export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath) {
231
241
  for (const pattern of GEMINI_L_CLASS_PATTERNS) {
232
242
  appendGeminiPolicyRule(lines, GEMINI_L_CLASS_WRITE_TOOLS, 'deny', 997, pattern);
233
243
  }
244
+ if (personalMdPath) {
245
+ const personalPathPattern = geminiManagedIdentityPathPattern(personalMdPath);
246
+ appendGeminiPolicyRule(lines, 'read_file', 'allow', 989, personalPathPattern);
247
+ appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_FILE_TOOLS, 'allow', 989, personalPathPattern);
248
+ }
234
249
  if (profile.mode === 'readonly') {
235
250
  appendGeminiPolicyRule(lines, GEMINI_READONLY_TOOLS, 'allow', 950);
236
251
  appendGeminiPolicyRule(lines, '*', 'deny', 900);
@@ -365,6 +380,9 @@ export class GeminiRunner {
365
380
  const bootstrapAgentMdPath = modelOverride?.bootstrapAgentMdPath
366
381
  ? normalizeGeminiBootstrapAgentMdPath(modelOverride.bootstrapAgentMdPath).filePath
367
382
  : undefined;
383
+ const personalMdPath = modelOverride?.personalMdPath
384
+ ? path.resolve(modelOverride.personalMdPath)
385
+ : undefined;
368
386
  const permissionProfile = resolveGeminiPermissionProfile(requestedPermissionMode, !!bootstrapAgentMdPath);
369
387
  const callModel = modelOverride?.model || this.model;
370
388
  const managedTaskTmpDir = getManagedTaskTempDir(runtimeEnv);
@@ -382,6 +400,7 @@ export class GeminiRunner {
382
400
  ...(runtimeDir ? [runtimeDir] : []),
383
401
  ...(runtimeLockDir ? [runtimeLockDir] : []),
384
402
  ...(bootstrapAgentMdPath ? [bootstrapAgentMdPath] : []),
403
+ ...(personalMdPath ? [personalMdPath] : []),
385
404
  ],
386
405
  });
387
406
  if (executionPermissionMode !== 'fullaccess' && !sandboxProbe && workspaceContainsHClassPaths(projectPath)) {
@@ -410,7 +429,7 @@ export class GeminiRunner {
410
429
  }
411
430
  else {
412
431
  const policyPath = path.join(managedTmpDir, `evolcore-gemini-permission-${crypto.randomUUID()}.toml`);
413
- fs.writeFileSync(policyPath, buildGeminiAdminPolicy(permissionProfile, bootstrapAgentMdPath), { mode: 0o600, flag: 'wx' });
432
+ fs.writeFileSync(policyPath, buildGeminiAdminPolicy(permissionProfile, bootstrapAgentMdPath, personalMdPath), { mode: 0o600, flag: 'wx' });
414
433
  tempFiles.push(policyPath);
415
434
  args.push(...buildGeminiPermissionArgs(permissionProfile, policyPath, !!sandboxProbe));
416
435
  }
@@ -452,6 +471,7 @@ export class GeminiRunner {
452
471
  ...(runtimeDir ? [runtimeDir] : []),
453
472
  ...(runtimeLockDir ? [runtimeLockDir] : []),
454
473
  ...(bootstrapAgentMdPath ? [bootstrapAgentMdPath] : []),
474
+ ...(personalMdPath ? [personalMdPath] : []),
455
475
  ],
456
476
  readonlyPaths: tempFiles,
457
477
  });
@@ -4,6 +4,51 @@ import { parseDocument } from 'yaml';
4
4
  import { getAidStore, loadAid, SLOT } from './store.js';
5
5
  import { agentMdPath, aidLocalDir, resolveRoot } from '../../paths.js';
6
6
  export const DEFAULT_AGENT_DESCRIPTION = 'EvolCore AI agent.';
7
+ export const AGENT_TAG_MIN_COUNT = 3;
8
+ export const AGENT_TAG_MAX_COUNT = 6;
9
+ export const AGENT_TAG_MAX_LENGTH = 50;
10
+ const AGENT_TAG_PATTERN = /^[\p{L}\p{N}]+$/u;
11
+ /** Return a human-readable validation error for one tag, or null when valid. */
12
+ export function validateAgentTag(tag) {
13
+ if (typeof tag !== 'string' || !tag.trim())
14
+ return 'must be a non-empty string';
15
+ const normalized = tag.trim();
16
+ if (Array.from(normalized).length > AGENT_TAG_MAX_LENGTH) {
17
+ return `must be at most ${AGENT_TAG_MAX_LENGTH} characters`;
18
+ }
19
+ if (!AGENT_TAG_PATTERN.test(normalized)) {
20
+ return 'must contain only Unicode letters and numbers';
21
+ }
22
+ return null;
23
+ }
24
+ /**
25
+ * Validate an agent.md tags value. Count constraints are optional because
26
+ * existing published agent.md files may contain fewer than three tags.
27
+ */
28
+ export function validateAgentTags(tags, options = {}) {
29
+ if (!Array.isArray(tags))
30
+ return ['must be an array of strings'];
31
+ const errors = [];
32
+ const normalized = new Set();
33
+ tags.forEach((tag, index) => {
34
+ const error = validateAgentTag(tag);
35
+ if (error) {
36
+ errors.push(`tag ${index + 1} ${error}`);
37
+ return;
38
+ }
39
+ const value = tag.trim();
40
+ normalized.add(value);
41
+ });
42
+ const minCount = options.minCount;
43
+ const maxCount = options.maxCount;
44
+ if (minCount !== undefined && normalized.size < minCount) {
45
+ errors.push(`must contain at least ${minCount} unique tags`);
46
+ }
47
+ if (maxCount !== undefined && normalized.size > maxCount) {
48
+ errors.push(`must contain at most ${maxCount} unique tags`);
49
+ }
50
+ return errors;
51
+ }
7
52
  const displayNameCache = new Map();
8
53
  const displayNamePending = new Map();
9
54
  const displayNameRevision = new Map();
@@ -87,7 +132,12 @@ export function buildInitialAgentMd(opts) {
87
132
  const agentName = opts.name?.trim() || opts.aid.split('.')[0];
88
133
  const agentType = opts.type || 'ai';
89
134
  const description = opts.description?.trim() || DEFAULT_AGENT_DESCRIPTION;
90
- return `---\naid: ${yamlDoubleQuote(opts.aid)}\nname: ${yamlDoubleQuote(agentName)}\ntype: ${yamlDoubleQuote(agentType)}\nversion: "1.0.0"\ndescription: ${yamlDoubleQuote(description)}\ntags:\n - evolcore\n---\n`;
135
+ const rawTags = opts.tags?.length ? opts.tags : ['evolcore'];
136
+ const tagErrors = validateAgentTags(rawTags);
137
+ if (tagErrors.length > 0)
138
+ throw new Error(`Invalid agent.md tags: ${tagErrors.join('; ')}`);
139
+ const tags = [...new Set(rawTags.map(tag => tag.trim()))];
140
+ return `---\naid: ${yamlDoubleQuote(opts.aid)}\nname: ${yamlDoubleQuote(agentName)}\ntype: ${yamlDoubleQuote(agentType)}\nversion: "1.0.0"\ndescription: ${yamlDoubleQuote(description)}\ntags:\n${tags.map(tag => ` - ${yamlDoubleQuote(tag)}\n`).join('')}---\n`;
91
141
  }
92
142
  function ensureTrailingNewline(content) {
93
143
  return content.endsWith('\n') ? content : `${content}\n`;
@@ -145,9 +195,11 @@ export function validateAgentMdFrontmatter(content, expectedAid) {
145
195
  if (description && description.length > 100) {
146
196
  errors.push('agent.md frontmatter description must be at most 100 characters');
147
197
  }
148
- if (values.tags !== undefined && (!Array.isArray(values.tags)
149
- || values.tags.some(tag => typeof tag !== 'string' || !tag.trim()))) {
150
- errors.push('agent.md frontmatter tags must be an array of non-empty strings');
198
+ if (values.tags !== undefined) {
199
+ const tagErrors = validateAgentTags(values.tags);
200
+ if (tagErrors.length > 0) {
201
+ errors.push(`agent.md frontmatter tags ${tagErrors.join('; ')}`);
202
+ }
151
203
  }
152
204
  return { valid: errors.length === 0, errors };
153
205
  }
@@ -1,3 +1,4 @@
1
+ export { AGENT_TAG_MIN_COUNT, AGENT_TAG_MAX_COUNT, AGENT_TAG_MAX_LENGTH, validateAgentTag, validateAgentTags, } from './agentmd.js';
1
2
  export { isValidAid, aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, verifySignAbility, probePkiRecoverability, appendAidLifecycle, readAidLifecycle } from './identity.js';
2
3
  export { DEFAULT_AGENT_DESCRIPTION, buildInitialAgentMd, validateAgentMdFrontmatter, agentmdGet, agentmdPut, agentmdSync, invalidateAgentDisplayName, parseAgentDisplayName, refreshAgentDisplayName, resolveAgentDisplayName, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterName, } from './agentmd.js';
3
4
  export { MIN_AUN_CORE_SDK, AUN_CORE_SDK_PKG, isAunSdkVersionOk, resolveAunCoreSdkPkg, ensureAunSdk, isAunSdkReady, downloadCaRoot, suppressSdkLogs, } from './client.js';
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { resolveRoot } from '../../paths.js';
4
4
  import { agentmdGet, agentmdPut } from './agentmd.js';
5
+ import { uploadAvatarBuffer, uploadAvatarUrl } from '../../utils/avatar-upload.js';
5
6
  import { aidLookup, aidShow } from './identity.js';
6
7
  import { isValidAid } from './validation.js';
7
8
  export class ManagedAidOperationError extends Error {
@@ -28,6 +29,32 @@ function parseAid(value) {
28
29
  return { ok: false, code: 'INVALID_AID', reason: `invalid AID: ${aid}` };
29
30
  return { ok: true, aid };
30
31
  }
32
+ function parseAvatar(args) {
33
+ const aidResult = parseAid(args[1]);
34
+ if (!aidResult.ok)
35
+ return aidResult;
36
+ const sourceFlag = args[2];
37
+ const sourceValue = args[3];
38
+ if (sourceFlag !== '--file' && sourceFlag !== '--url') {
39
+ return { ok: false, code: 'INVALID_ARGUMENT', reason: 'avatar requires exactly one of --file <path> or --url <https-url>' };
40
+ }
41
+ if (!sourceValue || sourceValue.startsWith('-')) {
42
+ return { ok: false, code: 'INVALID_ARGUMENT', reason: `${sourceFlag} requires a non-empty value` };
43
+ }
44
+ if (args.length !== 4 && !(args.length === 6 && args[4] === '--format' && args[5] === 'json')) {
45
+ return { ok: false, code: 'NOT_ALLOWED', reason: 'avatar only supports an optional --format json suffix' };
46
+ }
47
+ return {
48
+ ok: true,
49
+ command: {
50
+ kind: 'avatar',
51
+ aid: aidResult.aid,
52
+ ...(sourceFlag === '--file' ? { filePath: sourceValue } : { url: sourceValue }),
53
+ operationId: 'ec.aid.avatar',
54
+ canonicalArgv: ['aid', 'avatar', aidResult.aid, sourceFlag, sourceValue],
55
+ },
56
+ };
57
+ }
31
58
  /**
32
59
  * The daemon accepts only the narrowly scoped AID commands exposed to managed
33
60
  * tasks. Public lookup/get may name any valid AID; local show/publish remain
@@ -36,6 +63,8 @@ function parseAid(value) {
36
63
  export function resolveManagedAidOperation(argv) {
37
64
  const args = argv[0] === 'aid' ? argv.slice(1) : argv;
38
65
  const subcommand = args[0];
66
+ if (subcommand === 'avatar')
67
+ return parseAvatar(args);
39
68
  if ((subcommand === 'show' || subcommand === 'lookup') && hasOnlyOptionalJsonFormat(args, 1)) {
40
69
  const parsedAid = parseAid(args[1]);
41
70
  if (!parsedAid.ok)
@@ -68,7 +97,7 @@ export function resolveManagedAidOperation(argv) {
68
97
  return {
69
98
  ok: false,
70
99
  code: 'NOT_ALLOWED',
71
- reason: 'Managed AID operations only allow exact show, lookup, agentmd get, or agentmd put commands with an optional --format json suffix',
100
+ reason: 'Managed AID operations only allow exact show, lookup, agentmd get, agentmd put, or avatar commands with supported options',
72
101
  };
73
102
  }
74
103
  export function managedAidShowViewForRole(role) {
@@ -93,6 +122,8 @@ const defaultDependencies = () => ({
93
122
  agentmdPut,
94
123
  existsSync: fs.existsSync,
95
124
  readFileSync: fs.readFileSync,
125
+ uploadAvatarBuffer,
126
+ uploadAvatarUrl,
96
127
  });
97
128
  /** Execute a validated AID operation from the daemon-owned host filesystem. */
98
129
  export async function executeManagedAidOperation(command, options = {}) {
@@ -125,5 +156,26 @@ export async function executeManagedAidOperation(command, options = {}) {
125
156
  await deps.agentmdPut(content, { aid: command.aid, aunPath });
126
157
  return { kind: command.kind, aid: command.aid, path: localPath };
127
158
  }
159
+ case 'avatar': {
160
+ const result = command.url
161
+ ? await deps.uploadAvatarUrl(command.aid, command.url, { aunPath })
162
+ : await deps.uploadAvatarBuffer(command.aid, options.avatarBuffer ?? deps.readFileSync(command.filePath), { aunPath });
163
+ if (!result.ok) {
164
+ throw new ManagedAidOperationError(result.code ?? 'UPLOAD_FAILED', result.error ?? 'avatar upload failed');
165
+ }
166
+ return {
167
+ kind: 'avatar',
168
+ aid: command.aid,
169
+ avatar: result.publicUrl,
170
+ objectKey: result.objectKey,
171
+ sha256: result.sha256,
172
+ mime: result.mime,
173
+ width: result.width,
174
+ height: result.height,
175
+ aspectRatio: result.aspectRatio,
176
+ };
177
+ }
178
+ default:
179
+ throw new ManagedAidOperationError('NOT_ALLOWED', `Unsupported managed AID operation: ${command.kind}`);
128
180
  }
129
181
  }
@@ -17,7 +17,10 @@ function pickUrl(value) {
17
17
  return typeof value === 'string' && value.trim() ? value : undefined;
18
18
  }
19
19
  export async function storageUpload(aid, localFile, remotePath, opts) {
20
- const fileBuffer = fs.readFileSync(localFile);
20
+ const fileBuffer = Buffer.isBuffer(localFile) ? localFile : fs.readFileSync(localFile);
21
+ return storageUploadBuffer(aid, fileBuffer, remotePath, opts);
22
+ }
23
+ export async function storageUploadBuffer(aid, fileBuffer, remotePath, opts) {
21
24
  const contentType = opts?.contentType ?? 'application/octet-stream';
22
25
  const sha256 = crypto.createHash('sha256').update(fileBuffer).digest('hex');
23
26
  const createResult = await rpcCall(aid, 'storage.create_upload_session', {
@@ -1,2 +1,2 @@
1
- export { storageUpload, storageLs, storageRm, storageQuota } from './client.js';
1
+ export { storageUpload, storageUploadBuffer, storageLs, storageRm, storageQuota } from './client.js';
2
2
  export { storageDownload } from './download.js';