evolcore 0.0.31 → 0.0.32

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 (56) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/agents/codex-runner.js +28 -5
  3. package/dist/agents/ecagent-runner.js +4 -0
  4. package/dist/agents/gemini-runner.js +4 -0
  5. package/dist/aun/aid/store.js +0 -81
  6. package/dist/aun/group/fs-download.js +1 -10
  7. package/dist/aun/msg/managed-operation.js +130 -32
  8. package/dist/aun/service-proxy.js +12 -18
  9. package/dist/aun/storage/index.js +29 -0
  10. package/dist/channels/aun.js +10 -135
  11. package/dist/cli/agent-command.js +6 -7
  12. package/dist/cli/agent-create.js +43 -24
  13. package/dist/cli/agent.js +15 -69
  14. package/dist/cli/aun-commands.js +149 -5
  15. package/dist/cli/ctl-command.js +1 -2
  16. package/dist/cli/daemon-commands.js +1 -1
  17. package/dist/cli/diag.js +2 -2
  18. package/dist/cli/handoff-command.js +15 -5
  19. package/dist/cli/memory-command.js +85 -11
  20. package/dist/cli/model.js +14 -138
  21. package/dist/cli/queue-command.js +46 -0
  22. package/dist/cli/response.js +5 -54
  23. package/dist/cli/skill-command.js +9 -3
  24. package/dist/cli/stats.js +10 -4
  25. package/dist/cli/task-command.js +1 -0
  26. package/dist/cli/task-context.js +15 -0
  27. package/dist/cli/trigger-command.js +73 -49
  28. package/dist/config/builtin-roles.js +8 -7
  29. package/dist/core/agent-ipc-registration.js +65 -22
  30. package/dist/core/auth/agent-delegation.js +17 -1
  31. package/dist/core/auth/delegation-authorization.js +1 -0
  32. package/dist/core/auth/operation-catalog.js +11 -3
  33. package/dist/core/command/command-handler.js +164 -21
  34. package/dist/core/command/diag-service.js +5 -1
  35. package/dist/core/handoff/ipc-registration.js +38 -0
  36. package/dist/core/ipc-registration.js +71 -7
  37. package/dist/core/message/aun-ipc-sender.js +48 -4
  38. package/dist/core/message/message-queue.js +131 -3
  39. package/dist/core/message/response-engine.js +30 -2
  40. package/dist/core/permission/sandbox-runtime.js +14 -1
  41. package/dist/core/task/task-recovery-service.js +2 -0
  42. package/dist/core/task/task-resume-operation.js +11 -0
  43. package/dist/index.js +2 -2
  44. package/dist/ipc.js +146 -8
  45. package/dist/trigger/authorization.js +2 -1
  46. package/dist/trigger/ipc-service.js +49 -35
  47. package/dist/trigger/script-sandbox.js +58 -77
  48. package/kits/docs/AUTHORING.md +1 -1
  49. package/kits/docs/channels/aun.md +8 -0
  50. package/kits/docs/evolcore/INDEX.md +2 -2
  51. package/kits/docs/evolcore/task.md +2 -0
  52. package/kits/docs/evolcore/trigger.md +2 -2
  53. package/kits/rules/01-overview.md +0 -9
  54. package/kits/rules/06-channel.md +7 -1
  55. package/kits/templates/system-fragments/commands.md +1 -1
  56. package/package.json +1 -1
@@ -27,6 +27,7 @@ function normalizeTaskRuntimeContext(value) {
27
27
  return {
28
28
  taskId: optionalString(value.taskId),
29
29
  sessionId: optionalString(value.sessionId),
30
+ projectPath: optionalAbsolutePath(value.projectPath),
30
31
  messageId: optionalString(value.messageId),
31
32
  baseagent: optionalString(value.baseagent),
32
33
  channel: optionalString(value.channel),
@@ -347,6 +348,20 @@ export function parseTaskRuntimeContext(raw) {
347
348
  export function readTaskRuntimeContextFromEnv(env = process.env) {
348
349
  return parseTaskRuntimeContext(env[TASK_RUNTIME_CONTEXT_ENV]);
349
350
  }
351
+ export async function authorizeLocalCommand(argv) {
352
+ const sessionId = process.env.EVOLCORE_SESSION_ID;
353
+ const delegationToken = readDelegationCredential();
354
+ if (!sessionId || !delegationToken)
355
+ throw new Error('Local command requires an active task delegation');
356
+ const [{ resolvePaths }, { ipcQuery }] = await Promise.all([import('../paths.js'), import('../ipc.js')]);
357
+ const result = await ipcQuery(resolvePaths().socket, {
358
+ type: 'local-command-context', argv, sessionId, delegationToken,
359
+ }, 10_000);
360
+ if (!result?.ok || !result.context?.selfAid || !result.context.peerRole) {
361
+ throw new Error(result?.error ?? 'Daemon command authorization is unavailable');
362
+ }
363
+ return result.context;
364
+ }
350
365
  export async function readTaskRuntimeContextFromDaemon(sessionId, timeoutMs = 500, env = process.env) {
351
366
  if (!sessionId)
352
367
  return undefined;
@@ -7,10 +7,11 @@ 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 { MAIN_CLI_BIN } from '../product.js';
10
11
  import { readTriggerLocalFile, resolveTriggerLocalPath } from '../trigger/local-files.js';
11
12
  import { normalizeTriggerDefinition, resolveScriptPath, safeRelativePath, } from '../trigger/validation.js';
12
13
  export async function cmdTrigger(args) {
13
- args = canonicalizeEvolcoreCommandArgv(['ec', 'trigger', ...args]).slice(2);
14
+ args = canonicalizeEvolcoreCommandArgv([MAIN_CLI_BIN, 'trigger', ...args]).slice(2);
14
15
  const sub = args[0];
15
16
  if (sub === 'create' || sub === 'set' || sub === 'update') {
16
17
  const nameIndex = args.indexOf('--name');
@@ -41,29 +42,27 @@ export async function cmdTrigger(args) {
41
42
  return;
42
43
  }
43
44
  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;
45
+ if (process.env.EVOLCORE_SESSION_ID) {
46
+ const response = await request({ type: 'trigger.op', argv: ['trigger', ...args] }, sub === 'run' ? 120_000 : 30_000);
47
+ if (json)
48
+ return printJson(response);
49
+ if (sub === 'list')
50
+ return listTriggers(rest, false, response);
51
+ if (sub === 'show')
52
+ return showTrigger(rest, false, response);
53
+ if (sub === 'history')
54
+ return showHistory(rest, false, response);
55
+ if (sub === 'run') {
56
+ console.log(`${response.result.status}${response.result.reason ? ` (${response.result.reason})` : ''}: ${response.result.runId}`);
57
+ if (response.result.error)
58
+ console.log(response.result.error);
59
+ }
60
+ else {
61
+ const verb = { create: 'created', set: 'created', update: 'updated', enable: 'enabled',
62
+ disable: 'disabled', cancel: 'disabled', delete: 'deleted', remove: 'deleted', rm: 'deleted' }[sub];
63
+ console.log(`✓ ${verb} ${response.trigger.id}${['create', 'set', 'update'].includes(sub) ? ` (${response.trigger.name})` : ''}`);
66
64
  }
65
+ return;
67
66
  }
68
67
  switch (sub) {
69
68
  case 'list':
@@ -77,10 +76,10 @@ export async function cmdTrigger(args) {
77
76
  return;
78
77
  case 'create':
79
78
  case 'set':
80
- await createTrigger(rest, json, fileScope);
79
+ await createTrigger(rest, json);
81
80
  return;
82
81
  case 'update':
83
- await updateTrigger(rest, json, fileScope);
82
+ await updateTrigger(rest, json);
84
83
  return;
85
84
  case 'enable':
86
85
  case 'disable':
@@ -175,9 +174,9 @@ Update 参数:
175
174
  省略 --agent 时使用当前 agent AID;无会话上下文或跨身份操作时需要显式指定。
176
175
  任务会话中的跨身份操作仅允许目标 Agent 上具备 owner/admin 权限的调用者执行 list/show/history;创建、修改、启停、删除和运行仍只允许当前 Agent。`);
177
176
  }
178
- async function listTriggers(args, json) {
177
+ async function listTriggers(args, json, response) {
179
178
  const agentAid = resolveAgentAid(args);
180
- const res = await request({ type: 'trigger.list', agentAid, all: hasFlag(args, '--all') });
179
+ const res = response ?? await request({ type: 'trigger.list', agentAid, all: hasFlag(args, '--all') });
181
180
  if (json)
182
181
  return printJson(res);
183
182
  const triggers = res.triggers;
@@ -189,10 +188,10 @@ async function listTriggers(args, json) {
189
188
  console.log(`${t.enabled ? 'on ' : 'off'} ${t.id} ${t.name} ${sourceLabel(t)}`);
190
189
  }
191
190
  }
192
- async function showTrigger(args, json) {
191
+ async function showTrigger(args, json, response) {
193
192
  const agentAid = resolveAgentAid(args);
194
193
  const triggerId = positional(args, 0, ['--agent', '--format']);
195
- const res = await request({ type: 'trigger.show', agentAid, triggerId });
194
+ const res = response ?? await request({ type: 'trigger.show', agentAid, triggerId });
196
195
  if (json)
197
196
  return printJson(res);
198
197
  const t = res.definition;
@@ -239,7 +238,7 @@ async function showTrigger(args, json) {
239
238
  }
240
239
  }
241
240
  }
242
- async function showHistory(args, json) {
241
+ async function showHistory(args, json, response) {
243
242
  const agentAid = resolveAgentAid(args);
244
243
  const triggerId = optionalPositional(args, 0, ['--agent', '--limit', '--format']);
245
244
  const rawLimit = flagValue(args, '--limit');
@@ -247,7 +246,7 @@ async function showHistory(args, json) {
247
246
  if (!Number.isInteger(limit) || limit <= 0 || limit > 10_000) {
248
247
  throw new Error('--limit 必须是 1 到 10000 的整数');
249
248
  }
250
- const res = await request({ type: 'trigger.history', agentAid, triggerId, limit });
249
+ const res = response ?? await request({ type: 'trigger.history', agentAid, triggerId, limit });
251
250
  if (json)
252
251
  return printJson(res);
253
252
  const events = Array.isArray(res.events) ? res.events : [];
@@ -266,6 +265,12 @@ async function showHistory(args, json) {
266
265
  }
267
266
  }
268
267
  async function createTrigger(args, json, fileScope) {
268
+ const res = await request(await buildTriggerCreateRequest(args, fileScope), 30_000);
269
+ if (json)
270
+ return printJson(res);
271
+ console.log(`✓ created ${res.trigger.id} (${res.trigger.name})`);
272
+ }
273
+ export async function buildTriggerCreateRequest(args, fileScope, defaultAgentAid) {
269
274
  if (!hasOption(args, '--exec')) {
270
275
  throw new Error('创建 trigger 必须显式指定 --exec script|trigger-session|target-session');
271
276
  }
@@ -280,16 +285,12 @@ async function createTrigger(args, json, fileScope) {
280
285
  if (definition.execution.type !== executionType) {
281
286
  throw new Error(`--exec ${flagValue(args, '--exec')} 与 trigger.json 的 execution.type=${definition.execution.type} 不一致`);
282
287
  }
283
- const res = await request({
288
+ return {
284
289
  type: 'trigger.create',
285
290
  definition,
286
291
  files,
287
292
  enable: hasFlag(args, '--enable') ? true : undefined,
288
- }, 30_000);
289
- if (json)
290
- return printJson(res);
291
- console.log(`✓ created ${res.trigger.id} (${res.trigger.name})`);
292
- return;
293
+ };
293
294
  }
294
295
  // Flag mode: use parser
295
296
  const usesPromptFile = hasOption(args, '--prompt-file');
@@ -302,7 +303,7 @@ async function createTrigger(args, json, fileScope) {
302
303
  throw new Error(parseResult.error);
303
304
  }
304
305
  const parsed = parseResult.value;
305
- const agentAid = parsed.agentId ?? resolveAgentAid(args);
306
+ const agentAid = parsed.agentId ?? defaultAgentAid ?? resolveAgentAid(args);
306
307
  const eventSession = parsed.feedbackStrategy === 'event_session';
307
308
  if (parsed.feedbackStrategy !== 'target' && !eventSession) {
308
309
  throw new Error('CLI flag 模式仅支持 --feedback target;origin/silent 请使用 /trigger 或 --file 导入 Trigger V3 第二阶段 JSON');
@@ -364,15 +365,12 @@ async function createTrigger(args, json, fileScope) {
364
365
  relativePath: script.path.replace(/\\/g, '/'),
365
366
  contentBase64: readTriggerLocalFile(parsed.scriptPath, fileScope).toString('base64'),
366
367
  }] : [];
367
- const res = await request({
368
+ return {
368
369
  type: 'trigger.create',
369
370
  definition,
370
371
  files,
371
372
  enable: hasFlag(args, '--enable') ? true : undefined,
372
- }, 30_000);
373
- if (json)
374
- return printJson(res);
375
- console.log(`✓ created ${res.trigger.id} (${res.trigger.name})`);
373
+ };
376
374
  }
377
375
  function triggerCreateArgs(args) {
378
376
  const out = [];
@@ -453,7 +451,13 @@ function shellQuoteArg(value) {
453
451
  return /[\s"'\\]/.test(value) ? `"${value.replace(/(["\\$`])/g, '\\$1')}"` : value;
454
452
  }
455
453
  async function updateTrigger(args, json, fileScope) {
456
- const agentAid = resolveAgentAid(args);
454
+ const res = await request(await buildTriggerUpdateRequest(args, fileScope), 30_000);
455
+ if (json)
456
+ return printJson(res);
457
+ console.log(`✓ updated ${res.trigger.id} (${res.trigger.name})`);
458
+ }
459
+ export async function buildTriggerUpdateRequest(args, fileScope, defaultAgentAid) {
460
+ const agentAid = flagValue(args, '--agent') ?? defaultAgentAid ?? resolveAgentAid(args);
457
461
  const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file', '--script-file']);
458
462
  const usesPromptFile = hasOption(args, '--prompt-file');
459
463
  const usesScriptFile = hasOption(args, '--script-file');
@@ -469,7 +473,7 @@ async function updateTrigger(args, json, fileScope) {
469
473
  const scriptFileBase64 = scriptFilePath
470
474
  ? readTriggerLocalFile(scriptFilePath, fileScope).toString('base64')
471
475
  : undefined;
472
- const res = await request({
476
+ return {
473
477
  type: 'trigger.update',
474
478
  agentAid,
475
479
  triggerId,
@@ -477,10 +481,30 @@ async function updateTrigger(args, json, fileScope) {
477
481
  ...(usesPromptFile ? { promptFile: true } : {}),
478
482
  ...(scriptFileBase64 !== undefined ? { scriptFileBase64 } : {}),
479
483
  ...(expectedRevision ? { expectedRevision } : {}),
480
- }, 30_000);
481
- if (json)
482
- return printJson(res);
483
- console.log(`✓ updated ${res.trigger.id} (${res.trigger.name})`);
484
+ };
485
+ }
486
+ export function resolveTriggerCommandArgv(argv, defaultAgentAid) {
487
+ if (argv[0] !== 'trigger')
488
+ throw new Error('Expected a trigger command');
489
+ const sub = argv[1];
490
+ const args = argv.slice(2);
491
+ const types = {
492
+ list: 'trigger.list', show: 'trigger.show', history: 'trigger.history', create: 'trigger.create',
493
+ set: 'trigger.create', update: 'trigger.update', enable: 'trigger.setEnabled', disable: 'trigger.setEnabled',
494
+ cancel: 'trigger.cancel', delete: 'trigger.delete', remove: 'trigger.delete', rm: 'trigger.delete', run: 'trigger.run',
495
+ };
496
+ if (!types[sub])
497
+ throw new Error(`Unknown trigger command: ${sub}`);
498
+ const agentAid = flagValue(args, '--agent') ?? defaultAgentAid;
499
+ const valueFlags = ['--agent', '--format', '--limit', '--if-revision', '--prompt-file', '--script-file'];
500
+ const triggerId = ['list', 'create', 'set'].includes(sub) ? undefined : optionalPositional(args, 0, valueFlags);
501
+ if (!['list', 'history', 'create', 'set'].includes(sub) && !triggerId)
502
+ throw new Error('missing triggerId');
503
+ const limit = Number(flagValue(args, '--limit') ?? 100);
504
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 10_000)
505
+ throw new Error('Invalid history limit');
506
+ return { type: types[sub], agentAid, triggerId, limit, all: hasFlag(args, '--all'),
507
+ enabled: sub === 'enable', dryRun: hasFlag(args, '--dry-run') };
484
508
  }
485
509
  function triggerUpdateArgs(args) {
486
510
  const out = [];
@@ -42,6 +42,7 @@ export function getManagementCommandPermissions(role) {
42
42
  'role.policy.read': { allow: true, scopes: ['agent'] },
43
43
  'role.relation.read': { allow: true, scopes: ['agent'] },
44
44
  'agent.baseagent.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
45
+ 'agent.rename': { allow: true, dangerous: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
45
46
  'observable.current': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true } },
46
47
  'observable.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
47
48
  'file.root.agent': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
@@ -64,13 +65,13 @@ export function getManagementCommandPermissions(role) {
64
65
  'ec.aid.agentmd.put': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
65
66
  'ec.aid.agentmd.set': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
66
67
  'ec.aid.avatar': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentOwner: true } },
67
- 'ec.fs.mkdir': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
68
- 'ec.fs.upload': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
69
- 'ec.fs.copy': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
70
- 'ec.fs.move': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
71
- 'ec.fs.remove': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
72
- 'ec.fs.getfacl': { allow: true, scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
73
- 'ec.fs.setfacl': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
68
+ 'ec.fs.mkdir': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
69
+ 'ec.fs.upload': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
70
+ 'ec.fs.copy': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
71
+ 'ec.fs.move': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
72
+ 'ec.fs.remove': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
73
+ 'ec.fs.getfacl': { allow: true, scopes: ['relation', 'agent'] },
74
+ 'ec.fs.setfacl': { allow: true, permissionMode: 'auto', scopes: ['relation', 'agent'] },
74
75
  '*': { allow: true },
75
76
  // Reload is valid from an Agent's own channel and the daemon control plane.
76
77
  // The control-channel gate still requires a daemon owner for cross-Agent reloads.
@@ -1,13 +1,14 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { loadDaemonConfig } from '../config-store.js';
4
- import { parseAgentWelcomeArgs, parseInitializationArgs, resolveInitializationPlan, } from '../cli/agent-create.js';
4
+ import { parseAgentWelcomeArgs, parseInitializationArgs, resolveInitializationPlan, readInitializationPersona, } from '../cli/agent-create.js';
5
5
  import { trustedExecutionIdentityInput } from './auth/agent-delegation.js';
6
6
  import { authorizeOperation, buildAuthSubject } from './auth/auth-gateway.js';
7
7
  import { authorizationDenialData } from './auth/authorization-denial.js';
8
8
  import { parsePeerKey } from './relation/peer-identity.js';
9
9
  import { isHClassPath, isLClassPath, isSameOrDescendant } from './permission/protected-paths.js';
10
- export function registerAgentOperationExecutor({ ipcServer, sessionManager, agentDelegationRegistry, eventBus, responseEngine, handoffRuntime, agentApplicationService, agentInitializationService, }) {
10
+ import { isValidAid } from '../aun/aid/validation.js';
11
+ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agentDelegationRegistry, eventBus, responseEngine, handoffRuntime, agentApplicationService, agentInitializationService, executeConfigOperation, }) {
11
12
  ipcServer.setAgentOperationExecutor(async (argv, sessionId, delegationToken, delegationCommandHash) => {
12
13
  const fail = (code, error, reasonCode) => ({
13
14
  ok: false,
@@ -17,9 +18,16 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
17
18
  });
18
19
  if (!Array.isArray(argv) || argv[0] !== 'agent')
19
20
  return fail('INVALID_REQUEST', 'agent.op requires an agent command');
20
- const sub = argv[1];
21
+ const shorthand = !!argv[1] && isValidAid(argv[1]);
22
+ const sub = shorthand ? 'show' : argv[1];
21
23
  if (!sub)
22
24
  return fail('INVALID_ARGS', 'missing agent subcommand');
25
+ if (sub === 'get' || sub === 'set') {
26
+ const result = await executeConfigOperation(argv, sessionId, delegationToken, delegationCommandHash);
27
+ return result.ok
28
+ ? { ok: true, result: result.result }
29
+ : { ok: false, code: result.code ?? 'CONFIG_OPERATION_FAILED', error: result.error ?? 'config operation failed' };
30
+ }
23
31
  const value = (flag) => {
24
32
  const index = argv.indexOf(flag);
25
33
  return index >= 0 && index + 1 < argv.length && !argv[index + 1].startsWith('--') ? argv[index + 1] : undefined;
@@ -31,6 +39,8 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
31
39
  if (!taskSession || taskSession.selfAID !== delegation.grant.selfAid) {
32
40
  return fail('INVALID_DELEGATION', 'delegation does not match the current session');
33
41
  }
42
+ if (shorthand)
43
+ argv = ['agent', 'show', ...argv.slice(1)];
34
44
  const taskAid = delegation.grant.selfAid;
35
45
  let welcomeArgs;
36
46
  if (sub === 'welcome') {
@@ -58,12 +68,13 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
58
68
  : sub === 'show' ? 'agent.show'
59
69
  : sub === 'persona' ? 'agent.persona'
60
70
  : sub === 'welcome' ? 'agent.welcome'
61
- : sub === 'initialize' || (sub === 'new' && argv.includes('--initialize')) ? 'agent.initialize'
62
- : sub === 'new' ? 'agent.create'
63
- : sub === 'enable' ? 'agent.enable'
64
- : sub === 'disable' ? 'agent.disable'
65
- : sub === 'reload' ? 'agent.reload'
66
- : sub === 'delete' ? 'agent.delete' : undefined;
71
+ : sub === 'rename' ? 'agent.rename'
72
+ : sub === 'initialize' || (sub === 'new' && argv.includes('--initialize')) ? 'agent.initialize'
73
+ : sub === 'new' ? 'agent.create'
74
+ : sub === 'enable' ? 'agent.enable'
75
+ : sub === 'disable' ? 'agent.disable'
76
+ : sub === 'reload' ? 'agent.reload'
77
+ : sub === 'delete' ? 'agent.delete' : undefined;
67
78
  if (!operation)
68
79
  return fail('INVALID_ARGS', `unsupported managed agent command: ${sub}`);
69
80
  if (sub === 'new' && !argv.includes('--non-interactive') && !argv.includes('--initialize')) {
@@ -95,7 +106,7 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
95
106
  subject,
96
107
  intent: {
97
108
  operation,
98
- scope: operation === 'agent.show' || operation === 'agent.persona' || operation === 'agent.welcome'
109
+ scope: operation === 'agent.show' || operation === 'agent.persona' || operation === 'agent.welcome' || operation === 'agent.rename'
99
110
  ? 'agent'
100
111
  : operation === 'agent.reload' && targetAid
101
112
  ? 'agent'
@@ -170,15 +181,23 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
170
181
  if (!hasFormatSuffix && rawPersonaArgs.includes('--format')) {
171
182
  return fail('INVALID_ARGS', 'persona 仅支持末尾的 --format json');
172
183
  }
173
- if (personaArgs.length === 2 && personaArgs[0] === '--file') {
174
- return fail('INVALID_ARGS', '托管 persona --file 必须由 CLI 读取后作为文本提交');
175
- }
176
184
  const wantsShow = personaArgs.length === 1 && personaArgs[0] === 'show';
177
- const text = personaArgs.length === 2 && personaArgs[0] === '--text'
185
+ let text = personaArgs.length === 2 && personaArgs[0] === '--text'
178
186
  ? personaArgs[1]
179
187
  : undefined;
180
- if (!wantsShow && !(personaArgs.length === 2 && personaArgs[0] === '--text')) {
181
- return fail('INVALID_ARGS', '用法: ec agent persona show | --text <text>');
188
+ const file = personaArgs.length === 2 && personaArgs[0] === '--file' ? personaArgs[1] : undefined;
189
+ if (file !== undefined) {
190
+ try {
191
+ text = readInitializationPersona(file, true, {
192
+ projectCwd: taskSession.projectPath, tempDir: delegation.grant.sessionRuntimeDir,
193
+ });
194
+ }
195
+ catch (error) {
196
+ return fail('INVALID_ARGS', error.message);
197
+ }
198
+ }
199
+ if (!wantsShow && !(personaArgs.length === 2 && (personaArgs[0] === '--text' || personaArgs[0] === '--file'))) {
200
+ return fail('INVALID_ARGS', '用法: ec agent persona show | --text <text> | --file <path>');
182
201
  }
183
202
  if (text === undefined && !wantsShow)
184
203
  return fail('INVALID_ARGS', 'persona --text 缺少参数');
@@ -211,12 +230,32 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
211
230
  ...(result.sha256 ? { sha256: result.sha256 } : {}),
212
231
  } };
213
232
  }
233
+ if (operation === 'agent.rename') {
234
+ const rawRenameArgs = argv.slice(2);
235
+ const hasFormatSuffix = rawRenameArgs.length >= 2
236
+ && rawRenameArgs.at(-2) === '--format'
237
+ && rawRenameArgs.at(-1) === 'json';
238
+ const renameArgs = hasFormatSuffix ? rawRenameArgs.slice(0, -2) : rawRenameArgs;
239
+ if (!hasFormatSuffix && rawRenameArgs.includes('--format')) {
240
+ return fail('INVALID_ARGS', 'rename 仅支持末尾的 --format json');
241
+ }
242
+ if (renameArgs.length !== 2 || !renameArgs[0] || !renameArgs[1]) {
243
+ return fail('INVALID_ARGS', '用法: ec agent rename <aid> <name>');
244
+ }
245
+ if (/[\x00-\x1f\x7f]/u.test(renameArgs[1])) {
246
+ return fail('INVALID_ARGS', 'name 不允许换行或控制字符');
247
+ }
248
+ const result = await execAgentAction('update', { aid: targetAid ?? taskAid, patch: { name: renameArgs[1] } }, delegation.grant.actorId, eventBus, agentApplicationService, subject);
249
+ return 'error' in result ? fail(result.code, result.error) : { ok: true, result: { ok: true, ...(result.data ?? {}) } };
250
+ }
214
251
  if (operation === 'agent.initialize') {
215
252
  try {
216
253
  const parsed = parseInitializationArgs(argv.slice(1));
217
254
  const actor = delegation.grant.actorId;
218
- if (parsed.resume)
219
- return { ok: true, result: agentInitializationService.resume(parsed.aid, actor) };
255
+ if (parsed.resume) {
256
+ agentInitializationService.resume(parsed.aid, actor);
257
+ return { ok: true, result: await agentInitializationService.wait(parsed.aid, parsed.wait) };
258
+ }
220
259
  const createPermission = authorizeOperation({
221
260
  source: 'agent-tool', subject,
222
261
  intent: { operation: 'agent.create', scope: 'control', source: 'agent-tool', args: { aid: parsed.aid } },
@@ -226,14 +265,16 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
226
265
  return fail(createPermission.code, createPermission.reason);
227
266
  if (!argv.includes('--owner') && !argv.includes('--owners'))
228
267
  return fail('INITIALIZE_OWNER_MISMATCH', 'Explicit Owner required');
229
- const plan = resolveInitializationPlan(argv.slice(1));
268
+ const plan = resolveInitializationPlan(argv.slice(1), {
269
+ projectCwd: taskSession.projectPath, tempDir: delegation.grant.sessionRuntimeDir,
270
+ });
230
271
  if (plan.avatarFile?.startsWith('attachment:')) {
231
272
  const { readRelationAttachment } = await import('../utils/media-cache.js');
232
273
  plan.avatarFile = readRelationAttachment(taskAid, delegation.grant.peerKey, plan.avatarFile.slice('attachment:'.length)).attachment.filePath;
233
274
  }
234
275
  else if (plan.avatarFile) {
235
276
  const session = await sessionManager.getSessionById(sessionId);
236
- const candidate = path.resolve(plan.avatarFile);
277
+ const candidate = path.resolve(taskSession.projectPath, plan.avatarFile);
237
278
  const roots = [session?.projectPath, delegation.grant.sessionRuntimeDir]
238
279
  .filter((root) => !!root && path.isAbsolute(root))
239
280
  .map(root => fs.realpathSync(root));
@@ -245,7 +286,8 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
245
286
  }
246
287
  if (!plan.owners.includes(actor))
247
288
  return fail('INITIALIZE_OWNER_MISMATCH', 'The authenticated requesting Owner must be retained');
248
- return { ok: true, result: agentInitializationService.start(plan, actor, parsed.dryRun) };
289
+ const result = agentInitializationService.start(plan, actor, parsed.dryRun);
290
+ return { ok: true, result: parsed.dryRun ? result : await agentInitializationService.wait(parsed.aid, parsed.wait) };
249
291
  }
250
292
  catch (error) {
251
293
  return fail(error.code ?? 'INVALID_ARGS', error.message);
@@ -283,7 +325,7 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
283
325
  avatarFile: value('--avatar-file'),
284
326
  avatarUrl: value('--avatar-url'),
285
327
  force: argv.includes('--force'),
286
- });
328
+ }, taskSession.projectPath);
287
329
  const validationError = validateAgentCreatePlan(plan);
288
330
  if (validationError)
289
331
  return fail('INVALID_ARGS', validationError);
@@ -319,6 +361,7 @@ export function registerAgentOperationExecutor({ ipcServer, sessionManager, agen
319
361
  channelType: delegation.grant.channelType,
320
362
  chatType: delegation.grant.chatType,
321
363
  peerKey: delegation.grant.peerKey,
364
+ approvalInteractionPolicy: delegation.grant.approvalInteractionPolicy,
322
365
  },
323
366
  causation: responseEngine.getTaskRuntimeContext(sessionId)?.causation,
324
367
  });
@@ -4,6 +4,7 @@ import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
4
4
  import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
5
5
  import { logger } from '../../utils/logger.js';
6
6
  import { canonicalizeEvolcoreCommandArgv } from '../permission/ec-command-parser.js';
7
+ import { MAIN_CLI_BIN } from '../../product.js';
7
8
  export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
8
9
  export const CODEX_SESSION_TOKEN_ENV = 'EVOLCORE_CODEX_SESSION_TOKEN';
9
10
  export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
@@ -278,7 +279,22 @@ export function hashDelegatedCommandArgv(argv) {
278
279
  .digest('hex');
279
280
  }
280
281
  export function hashCurrentDelegatedCommand(processArgv = process.argv) {
282
+ const argv = currentDelegatedCommandArgv(processArgv);
283
+ return argv ? hashDelegatedCommandArgv([MAIN_CLI_BIN, ...argv]) : undefined;
284
+ }
285
+ /**
286
+ * Return the command arguments used by a delegated CLI invocation.
287
+ *
288
+ * The returned value excludes the executable because IPC request builders add
289
+ * the canonical executable before hashing. Keeping this conversion beside
290
+ * hashCurrentDelegatedCommand prevents typed IPC requests from hashing a
291
+ * different representation than the implicit current-command path.
292
+ */
293
+ export function currentDelegatedCommandArgv(processArgv = process.argv) {
281
294
  if (processArgv.length < 3)
282
295
  return undefined;
283
- return hashDelegatedCommandArgv(canonicalizeEvolcoreCommandArgv(['ec', ...processArgv.slice(2)]));
296
+ const canonical = canonicalizeEvolcoreCommandArgv([MAIN_CLI_BIN, ...processArgv.slice(2)]);
297
+ if (canonical[0] !== MAIN_CLI_BIN)
298
+ return undefined;
299
+ return canonical.slice(1);
284
300
  }
@@ -52,6 +52,7 @@ export function createDelegationAuthorizer({ agentDelegationRegistry, sessionMan
52
52
  aid: subject.dataScope === 'daemon' ? (params.targetAid ?? delegation.grant.selfAid) : delegation.grant.selfAid,
53
53
  dataScope: subject.dataScope,
54
54
  management: isManagementRole(subject.role),
55
+ sessionRuntimeDir: delegation.grant.sessionRuntimeDir,
55
56
  }
56
57
  : { ok: false, code: decision.code, error: decision.reason };
57
58
  };
@@ -81,15 +81,15 @@ const GROUP_FS_READ_OPERATIONS = ['ls', 'stat', 'lstat', 'cat', 'find', 'df', 'g
81
81
  category: 'read',
82
82
  dangerous: false,
83
83
  defaultScopes: ['relation', 'agent'],
84
- description: `Read current group filesystem (${kind})`,
84
+ description: `Read managed filesystem (${kind})`,
85
85
  sources: ['agent-tool'],
86
86
  }));
87
87
  const GROUP_FS_WRITE_OPERATIONS = [['mkdir', 'write-own'], ['upload', 'write-own'], ['copy', 'write-own'], ['move', 'write-own'], ['remove', 'write-own'], ['setfacl', 'write-agent']].map(([kind, category]) => ({
88
88
  id: `ec.fs.${kind}`,
89
89
  category,
90
90
  dangerous: false,
91
- defaultScopes: ['relation'],
92
- description: `Write current group filesystem (${kind})`,
91
+ defaultScopes: ['relation', 'agent'],
92
+ description: `Write managed filesystem (${kind})`,
93
93
  sources: ['agent-tool'],
94
94
  }));
95
95
  const HANDOFF_READ_OPERATIONS = ['status', 'list', 'trace'].map(kind => ({
@@ -719,6 +719,14 @@ const OPERATIONS = [
719
719
  description: '读取或更新当前 Agent 的 Bootstrap welcome.md',
720
720
  sources: ['agent-tool', 'control'],
721
721
  },
722
+ {
723
+ id: 'agent.rename',
724
+ category: 'write-agent',
725
+ dangerous: true,
726
+ defaultScopes: ['agent'],
727
+ description: '更新并发布当前 Agent 的 agent.md 显示名称',
728
+ sources: ['agent-tool'],
729
+ },
722
730
  {
723
731
  id: 'agent.create',
724
732
  category: 'process',