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
@@ -15,6 +15,8 @@ import { sameGroupAid } from '../../aun/group/index.js';
15
15
  import crypto from 'crypto';
16
16
  import path from 'path';
17
17
  import fs from 'fs';
18
+ import { parseDocument } from 'yaml';
19
+ import { agentmdGet } from '../../aun/aid/agentmd.js';
18
20
  import { parseDuration, parseTriggerSet, parseTriggerUpdate } from '../../trigger/parser.js';
19
21
  import { applyTriggerPatch, replaceEditableTriggerDefinition } from '../../trigger/patch.js';
20
22
  import { definitionRevision } from '../../trigger/validation.js';
@@ -25,6 +27,8 @@ import { resolveConfigCommand } from '../../config/resolved-config-op.js';
25
27
  import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommandAsync } from '../../config/config-operation-service.js';
26
28
  import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
27
29
  import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
30
+ import { ResponseModeRegistry } from '../../response-system/registry.js';
31
+ import { registerBuiltinModes } from '../../response-system/modes/index.js';
28
32
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
29
33
  import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
30
34
  import { executeManagedAidOperation, ManagedAidOperationError, managedAidShowViewForRole, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
@@ -1853,10 +1857,23 @@ export class CommandHandler {
1853
1857
  const delegation = this.agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
1854
1858
  if (!delegation.ok)
1855
1859
  return { ok: false, code: delegation.code, error: delegation.reason };
1860
+ return this.executeConfigOperation(argv, sessionId, delegation.grant);
1861
+ }
1862
+ async executeConfigOperation(argv, sessionId, grant) {
1856
1863
  const session = await this.sessionManager.getSessionById(sessionId);
1857
1864
  if (!session)
1858
1865
  return { ok: false, code: 'INVALID_SESSION', error: 'Invalid session' };
1859
- const grant = delegation.grant;
1866
+ if (session.selfAID !== grant.selfAid || grant.sessionId !== sessionId) {
1867
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation does not match the current session' };
1868
+ }
1869
+ if (argv[0] === 'agent') {
1870
+ const tail = argv.at(-2) === '--format' && argv.at(-1) === 'json' ? argv.slice(0, -2) : argv;
1871
+ const sub = tail[1];
1872
+ if ((sub !== 'get' && sub !== 'set') || tail.length !== (sub === 'get' ? 4 : 5)) {
1873
+ return { ok: false, code: 'INVALID_ARGS', error: 'Expected agent get/set <aid> <key> [value] [--format json]' };
1874
+ }
1875
+ argv = ['config', sub, ...tail.slice(3), '--self', tail[2]];
1876
+ }
1860
1877
  let conversationId;
1861
1878
  try {
1862
1879
  conversationId = parsePeerKey(grant.peerKey).channelId;
@@ -2549,39 +2566,76 @@ export class CommandHandler {
2549
2566
  if (sessionSelfAid && sessionSelfAid !== actorAid) {
2550
2567
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
2551
2568
  }
2552
- if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'group')) {
2553
- return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed fs operations require the current AUN group relation' };
2554
- }
2569
+ const isPersonalTarget = parsed.command.targetAid === actorAid;
2555
2570
  let currentGroup;
2556
- try {
2557
- currentGroup = parsePeerKey(grant.peerKey).channelId;
2558
- }
2559
- catch {
2560
- return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
2571
+ if (isPersonalTarget) {
2572
+ if (grant.issuedRole !== 'owner' && !hasTrustedFullAccessDelegation(grant)) {
2573
+ return { ok: false, code: 'NO_PERMISSION', error: 'Managed personal storage requires the current Agent owner relation' };
2574
+ }
2575
+ if (parsed.command.actorAid && parsed.command.actorAid !== actorAid) {
2576
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent can access its personal storage' };
2577
+ }
2561
2578
  }
2562
- if (!hasTrustedFullAccessDelegation(grant)
2563
- && ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || !sameGroupAid(parsed.command.groupId, currentGroup))) {
2564
- return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
2579
+ else {
2580
+ const trustedFullAccess = hasTrustedFullAccessDelegation(grant);
2581
+ if (trustedFullAccess) {
2582
+ let targetType;
2583
+ try {
2584
+ const card = await agentmdGet(parsed.command.targetAid, { withVerification: true, remoteOnly: true });
2585
+ if (card.verification.status !== 'verified') {
2586
+ return { ok: false, code: 'INVALID_TARGET', error: 'Target AID agent.md verification did not succeed' };
2587
+ }
2588
+ const frontmatter = card.content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
2589
+ const document = frontmatter ? parseDocument(frontmatter) : undefined;
2590
+ targetType = document && document.errors.length === 0
2591
+ ? String(document.toJS()?.type ?? '').trim().toLowerCase()
2592
+ : undefined;
2593
+ }
2594
+ catch {
2595
+ return { ok: false, code: 'INVALID_TARGET', error: 'Target AID agent.md could not be verified' };
2596
+ }
2597
+ if (targetType !== 'group') {
2598
+ return { ok: false, code: 'INVALID_TARGET', error: 'Managed group storage requires a verified group AID' };
2599
+ }
2600
+ }
2601
+ if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'group')) {
2602
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed group fs operations require the current AUN group relation' };
2603
+ }
2604
+ try {
2605
+ currentGroup = parsePeerKey(grant.peerKey).channelId;
2606
+ }
2607
+ catch {
2608
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
2609
+ }
2610
+ if (parsed.command.actorAid && parsed.command.actorAid !== actorAid) {
2611
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed fs cannot switch the execution identity with --as' };
2612
+ }
2613
+ if (!hasTrustedFullAccessDelegation(grant)
2614
+ && !sameGroupAid(parsed.command.targetAid, currentGroup)) {
2615
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
2616
+ }
2565
2617
  }
2566
- parsed.command.actorAid = hasTrustedFullAccessDelegation(grant)
2567
- ? (parsed.command.actorAid ?? actorAid)
2618
+ const resolvedCommand = parsed.command;
2619
+ resolvedCommand.storageBackend = isPersonalTarget ? 'personal' : 'group';
2620
+ resolvedCommand.actorAid = hasTrustedFullAccessDelegation(grant) && !isPersonalTarget
2621
+ ? (resolvedCommand.actorAid ?? actorAid)
2568
2622
  : actorAid;
2569
2623
  const subject = buildAuthSubject({
2570
2624
  selfAid: grant.selfAid,
2571
2625
  actorId: grant.actorId,
2572
2626
  channel: grant.channel,
2573
2627
  channelType: grant.channelType,
2574
- channelId: currentGroup,
2628
+ channelId: currentGroup ?? actorAid,
2575
2629
  chatType: grant.chatType,
2576
- conversationId: currentGroup,
2630
+ conversationId: currentGroup ?? actorAid,
2577
2631
  processOwners: loadDaemonConfig().owners ?? [],
2578
2632
  ...trustedExecutionIdentityInput(grant),
2579
2633
  });
2580
2634
  const intentArgs = {
2581
2635
  self: actorAid,
2582
- targetId: currentGroup,
2583
- peer: currentGroup,
2584
- peerKey: formatPeerKey('aun', currentGroup),
2636
+ targetId: parsed.command.targetAid,
2637
+ peer: currentGroup ?? actorAid,
2638
+ peerKey: formatPeerKey('aun', currentGroup ?? actorAid),
2585
2639
  path: parsed.command.remotePath,
2586
2640
  targetPath: parsed.command.remotePath,
2587
2641
  ...(parsed.command.remoteSourcePath ? { sourcePath: parsed.command.remoteSourcePath } : {}),
@@ -2598,7 +2652,7 @@ export class CommandHandler {
2598
2652
  subject,
2599
2653
  intent: {
2600
2654
  operation: parsed.command.operationId,
2601
- scope: 'relation',
2655
+ scope: isPersonalTarget ? 'agent' : 'relation',
2602
2656
  source: 'agent-tool',
2603
2657
  args: intentArgs,
2604
2658
  },
@@ -2655,7 +2709,7 @@ export class CommandHandler {
2655
2709
  }
2656
2710
  }
2657
2711
  try {
2658
- return { ok: true, result: await executeManagedFsOperation(parsed.command) };
2712
+ return { ok: true, result: await executeManagedFsOperation(resolvedCommand) };
2659
2713
  }
2660
2714
  catch (error) {
2661
2715
  return {
@@ -2668,6 +2722,95 @@ export class CommandHandler {
2668
2722
  setAgentDelegationRegistry(registry) {
2669
2723
  this.agentDelegationRegistry = registry;
2670
2724
  }
2725
+ async handleManagedCliOperation(argv, sessionId, grant) {
2726
+ const invalid = (error) => ({ ok: false, code: 'INVALID_ARGUMENT', error });
2727
+ if (argv[0] === 'ctl') {
2728
+ if (!argv[1] || /\s/.test(argv[1]))
2729
+ return invalid('Expected a ctl subcommand');
2730
+ return this.handleCtl('/' + argv.slice(1).join(' '), sessionId, grant);
2731
+ }
2732
+ if (argv[0] !== 'model' && argv[0] !== 'response')
2733
+ return invalid('Expected a model or response command');
2734
+ if (argv.includes('--self') || argv.includes('--peer') || argv.includes('--role')) {
2735
+ return { ok: false, code: 'NOT_ALLOWED', error: 'Managed commands always use the authenticated current relation' };
2736
+ }
2737
+ const args = argv.slice(2);
2738
+ const formatIndex = args.indexOf('--format');
2739
+ if (formatIndex >= 0) {
2740
+ if (args[formatIndex + 1] !== 'json' || args.lastIndexOf('--format') !== formatIndex)
2741
+ return invalid('--format must be json');
2742
+ args.splice(formatIndex, 2);
2743
+ }
2744
+ const sub = argv[1];
2745
+ if (argv[0] === 'model') {
2746
+ let command;
2747
+ if (['list', 'current', 'reset', 'check'].includes(sub) && args.length === 0) {
2748
+ command = `/model ${sub}`;
2749
+ }
2750
+ else if ((sub === 'info' || sub === 'effort') && args.length === 1 && args[0] && !/\s|^--/.test(args[0])) {
2751
+ command = sub === 'info' ? `/model info ${args[0]}` : `/effort ${args[0]}`;
2752
+ }
2753
+ else if (sub === 'use' && args[0] && !/\s|^--/.test(args[0])
2754
+ && !['list', 'current', 'reset', 'check', 'info'].includes(args[0])
2755
+ && (args.length === 1 || (args.length === 3 && args[1] === '--effort' && args[2] && !/\s|^--/.test(args[2])))) {
2756
+ command = `/model ${args[0]}${args.length === 3 ? ` ${args[2]}` : ''}`;
2757
+ }
2758
+ else
2759
+ return invalid('Invalid managed model arguments');
2760
+ return this.handleCtl(command, sessionId, grant);
2761
+ }
2762
+ const execute = (fieldArgs) => this.executeConfigOperation(['config', ...fieldArgs], sessionId, grant);
2763
+ const failure = (response) => ({
2764
+ ok: false, code: response.code, error: response.error, data: response.data,
2765
+ });
2766
+ const emit = (data, result) => ({ ok: true, data, result });
2767
+ if (sub === 'set' && args.length === 1) {
2768
+ const registry = new ResponseModeRegistry();
2769
+ registerBuiltinModes(registry);
2770
+ if (!registry.has(args[0]))
2771
+ return { ok: false, code: 'UNKNOWN_MODE', error: `Unknown mode: ${args[0]}` };
2772
+ const result = await execute(['set', 'responseMode', args[0]]);
2773
+ if (!result.ok)
2774
+ return failure(result);
2775
+ return emit({ ok: true, scope: 'relation', mode: args[0] }, `✓ 已设置响应模式为 ${args[0]}(作用域: relation)`);
2776
+ }
2777
+ if (sub === 'reset' && args.length === 0) {
2778
+ const result = await execute(['unset', 'responseMode']);
2779
+ if (!result.ok)
2780
+ return failure(result);
2781
+ return emit({ ok: true, scope: 'relation' }, '✓ 已清除当前关系的响应模式设置');
2782
+ }
2783
+ if (sub === 'current' && args.length === 0) {
2784
+ const modeResult = await execute(['get', 'responseMode']);
2785
+ if (!modeResult.ok)
2786
+ return failure(modeResult);
2787
+ const paramsResult = await execute(['get', 'responseModeParams']);
2788
+ if (!paramsResult.ok)
2789
+ return failure(paramsResult);
2790
+ if (!modeResult.result?.ok || modeResult.result.subcommand !== 'get'
2791
+ || !paramsResult.result?.ok || paramsResult.result.subcommand !== 'get')
2792
+ return invalid('Unexpected config result');
2793
+ const configuredMode = typeof modeResult.result.value === 'string' ? modeResult.result.value : undefined;
2794
+ const registry = new ResponseModeRegistry();
2795
+ registerBuiltinModes(registry);
2796
+ const mode = configuredMode ?? registry.getPreferredId();
2797
+ return emit({ ok: true, scope: 'relation', responseMode: mode ?? null,
2798
+ source: configuredMode ? 'config' : 'preferred', sourceTarget: modeResult.result.source?.target ?? null,
2799
+ responseModeParams: paramsResult.result.value ?? null }, `当前响应模式: ${mode ?? '(无)'}`);
2800
+ }
2801
+ if (sub === 'config' && (args.length === 0 || (args.length === 1 && !args[0].startsWith('--') && args[0] !== 'set'))) {
2802
+ const result = await execute(['get', 'responseModeParams']);
2803
+ if (!result.ok)
2804
+ return failure(result);
2805
+ if (!result.result?.ok || result.result.subcommand !== 'get')
2806
+ return invalid('Unexpected config result');
2807
+ const params = (result.result.value ?? {});
2808
+ return args.length === 1
2809
+ ? emit({ ok: true, mode: args[0], config: params[args[0]] ?? {} }, `${args[0]} 配置: ${JSON.stringify(params[args[0]] ?? {}, null, 2)}`)
2810
+ : emit({ ok: true, responseModeParams: params }, `所有模式配置: ${JSON.stringify(params, null, 2)}`);
2811
+ }
2812
+ return { ok: false, code: 'NOT_ALLOWED', error: `response ${sub} is not available in a managed task` };
2813
+ }
2671
2814
  /**
2672
2815
  * Agent ctl 入口:通过 IPC 接收 Agent 自主管理指令
2673
2816
  * 复用现有 slash cmd 逻辑,权限继承 session 用户角色
@@ -19,13 +19,17 @@ export async function executeDiagIpc(service, registry, owners, cmd) {
19
19
  try {
20
20
  if (!Array.isArray(cmd.argv) || cmd.argv.some((v) => typeof v !== 'string'))
21
21
  throw new Error('Invalid diag argv');
22
- return await service.execute(parseDiagArgs(cmd.argv));
22
+ if (cmd.argv[0] !== 'diag')
23
+ throw new Error('Expected a diag command');
24
+ return await service.execute(parseDiagArgs(cmd.argv.slice(1)));
23
25
  }
24
26
  catch (error) {
25
27
  return { ok: false, code: error.code ?? 'INVALID_ARGUMENT', message: error.message };
26
28
  }
27
29
  }
28
30
  export function parseDiagArgs(args) {
31
+ if (args.at(-2) === '--format' && args.at(-1) === 'json')
32
+ args = args.slice(0, -2);
29
33
  const words = [];
30
34
  let ownerAid;
31
35
  let literal = false;
@@ -2,6 +2,8 @@ import { isManagementRole } from '../../config/builtin-roles.js';
2
2
  import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput, } from '../auth/agent-delegation.js';
3
3
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
4
4
  import { parsePeerKey } from '../relation/peer-identity.js';
5
+ import { parseReturn } from '../../cli/handoff-command.js';
6
+ import { readTriggerLocalFile } from '../../trigger/local-files.js';
5
7
  export function registerHandoffIpc({ ipcServer, agentDelegationRegistry, responseEngine, sessionManager, agentRegistry, handoffRuntime, getProcessOwners, }) {
6
8
  const resolveManagedHandoffScope = async (params, operation) => {
7
9
  const delegation = agentDelegationRegistry.validate(params.delegationToken, params.sessionId, params.delegationCommandHash);
@@ -67,6 +69,8 @@ export function registerHandoffIpc({ ipcServer, agentDelegationRegistry, respons
67
69
  managementRole,
68
70
  role: subject.role,
69
71
  actorId: delegation.grant.actorId,
72
+ projectPath: session.projectPath,
73
+ sessionRuntimeDir: delegation.grant.sessionRuntimeDir,
70
74
  };
71
75
  };
72
76
  const resolveHandoffQueryScope = async (params, operation) => {
@@ -87,6 +91,22 @@ export function registerHandoffIpc({ ipcServer, agentDelegationRegistry, respons
87
91
  const scope = await resolveManagedHandoffScope({ ...params, sessionId: params.sessionId }, 'ec.handoff.return');
88
92
  if (!scope.ok)
89
93
  return scope;
94
+ if (!params.argv || params.argv[0] !== 'handoff' || params.argv[1] !== 'return') {
95
+ return { ok: false, code: 'ARGUMENT_MISMATCH', error: 'Handoff return requires its approved argv' };
96
+ }
97
+ try {
98
+ const parsed = parseReturn(params.argv.slice(2), {
99
+ canonical: true,
100
+ readFile: file => new TextDecoder('utf-8', { fatal: true }).decode(readTriggerLocalFile(file, {
101
+ selfAid: scope.aid, projectPath: scope.projectPath,
102
+ sessionRuntimeDir: scope.sessionRuntimeDir, cwd: scope.projectPath,
103
+ })),
104
+ });
105
+ params = { ...params, ...parsed };
106
+ }
107
+ catch (error) {
108
+ return { ok: false, code: 'HANDOFF_RETURN_CONTENT_FORBIDDEN', error: error instanceof Error ? error.message : String(error) };
109
+ }
90
110
  return responseEngine.returnAuthorizedHandoffResult({ sessionId: params.sessionId, handoffId: params.handoffId, content: params.content }, scope.aid);
91
111
  };
92
112
  const recoverHandoff = async (params) => {
@@ -98,6 +118,24 @@ export function registerHandoffIpc({ ipcServer, agentDelegationRegistry, respons
98
118
  if (scope.role !== 'owner') {
99
119
  return { ok: false, code: 'ROLE_ACCESS_DENIED', error: 'handoff recovery requires the current Agent owner' };
100
120
  }
121
+ if (!params.argv || params.argv[0] !== 'handoff' || params.argv[1] !== 'recover-return') {
122
+ return { ok: false, code: 'ARGUMENT_MISMATCH', error: 'Handoff recovery requires its approved argv' };
123
+ }
124
+ try {
125
+ const parsed = parseReturn(params.argv.slice(2), {
126
+ canonical: true,
127
+ readFile: file => new TextDecoder('utf-8', { fatal: true }).decode(readTriggerLocalFile(file, {
128
+ selfAid: scope.aid, projectPath: scope.projectPath,
129
+ sessionRuntimeDir: scope.sessionRuntimeDir, cwd: scope.projectPath,
130
+ })),
131
+ });
132
+ if (!parsed.handoffId)
133
+ return { ok: false, code: 'INVALID_HANDOFF_ID', error: 'Handoff recovery requires an ID' };
134
+ params = { ...params, handoffId: parsed.handoffId, content: parsed.content };
135
+ }
136
+ catch (error) {
137
+ return { ok: false, code: 'HANDOFF_RETURN_CONTENT_FORBIDDEN', error: error instanceof Error ? error.message : String(error) };
138
+ }
101
139
  return responseEngine.recoverAuthorizedHandoffResult({
102
140
  sessionId: params.sessionId,
103
141
  handoffId: params.handoffId,
@@ -4,7 +4,8 @@ import crypto from 'crypto';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { executeTaskResume } from './task/task-resume-operation.js';
7
- import { isHClassPath, isSameOrDescendant } from './permission/protected-paths.js';
7
+ import { isHClassPath, isLClassPath, isSameOrDescendant } from './permission/protected-paths.js';
8
+ import { resolveStorageCommandArgv } from '../aun/storage/index.js';
8
9
  import { computeBuildId } from '../utils/evolcore-version.js';
9
10
  import { loadAgent } from '../config-store.js';
10
11
  import { executeLocalInitializationCommand, hasSuppliedInitialization } from '../cli/agent-create.js';
@@ -75,6 +76,7 @@ export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationReg
75
76
  handoffRuntime,
76
77
  agentApplicationService,
77
78
  agentInitializationService,
79
+ executeConfigOperation: (argv, sessionId, token, commandHash) => commandHandler.handleConfigOperation(argv, sessionId, token, commandHash),
78
80
  });
79
81
  registerOperationIpcProviders({
80
82
  ipcServer,
@@ -220,8 +222,12 @@ export function registerRuntimeIpcProviders({ ipcServer, channelInstances, messa
220
222
  });
221
223
  logger.info('[Memory] 开发仓安装:已启用 ec memory --debug <sessionId>');
222
224
  }
223
- ipcServer.setTaskRuntimeContextAuthorizer(({ sessionId, delegationToken, delegationCommandHash }) => {
224
- const authorization = agentDelegationRegistry.validateForContext(delegationToken, sessionId, delegationCommandHash);
225
+ ipcServer.setTaskRuntimeContextAuthorizer(({ sessionId, delegationToken, delegationCommandHash, consumeApproval }) => {
226
+ if (consumeApproval && !agentDelegationRegistry.validate)
227
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Command authorization is unavailable' };
228
+ const authorization = consumeApproval
229
+ ? agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash)
230
+ : agentDelegationRegistry.validateForContext(delegationToken, sessionId, delegationCommandHash);
225
231
  return authorization.ok
226
232
  ? { ok: true }
227
233
  : { ok: false, code: authorization.code, error: authorization.reason };
@@ -383,8 +389,17 @@ export function registerOperationIpcProviders({ ipcServer, sessionManager, messa
383
389
  recover: recoverTask,
384
390
  }));
385
391
  ipcServer.setQueueOperationExecutor(async (params) => {
392
+ try {
393
+ if (!params.argv)
394
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Managed queue operation requires argv' };
395
+ const { parseQueueOperationArgv } = await import('../cli/queue-command.js');
396
+ params = { ...params, ...parseQueueOperationArgv(params.argv) };
397
+ }
398
+ catch (error) {
399
+ return { ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) };
400
+ }
386
401
  const operation = `ec.queue.${params.action ?? 'read'}`;
387
- const authorization = await authorizeManagedAgentOperation(params, operation);
402
+ const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: params.agent }, operation);
388
403
  if (!authorization.ok)
389
404
  return authorization;
390
405
  const handle = agentRegistry.get(authorization.aid);
@@ -419,10 +434,57 @@ export function registerOperationIpcProviders({ ipcServer, sessionManager, messa
419
434
  return authorization.ok ? { ok: true } : authorization;
420
435
  });
421
436
  ipcServer.setStorageAuthorizationExecutor(async (params) => {
422
- const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: params.aid }, params.operation);
423
- return authorization.ok ? { ok: true } : authorization;
437
+ try {
438
+ if (!params.argv)
439
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Managed storage requires argv' };
440
+ const command = resolveStorageCommandArgv(params.argv);
441
+ if (command.sub === 'upload')
442
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Upload requires the storage upload endpoint' };
443
+ const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: command.aid }, command.operation);
444
+ if (!authorization.ok)
445
+ return authorization;
446
+ const { storageDownload, storageLs, storageRm, storageQuota } = await import('../aun/storage/index.js');
447
+ if (command.sub === 'ls')
448
+ return storageLs(authorization.aid, command.prefix);
449
+ if (command.sub === 'quota')
450
+ return storageQuota(authorization.aid);
451
+ if (command.sub === 'rm') {
452
+ const result = await storageRm(authorization.aid, command.remotePath);
453
+ return { ...result, objectKey: command.remotePath };
454
+ }
455
+ const session = await sessionManager.getSessionById(params.sessionId);
456
+ if (!session)
457
+ return { ok: false, code: 'SESSION_NOT_FOUND', error: 'current session not found' };
458
+ const defaultName = path.basename(command.url.replace(/^https?:\/\//, '').split('/').slice(1).join('/'));
459
+ const destination = path.resolve(session.projectPath, command.localPath ?? defaultName);
460
+ const roots = [session.projectPath, authorization.sessionRuntimeDir].filter((root) => !!root).map(root => fs.realpathSync(root));
461
+ let parent = path.dirname(destination);
462
+ while (!fs.existsSync(parent) && path.dirname(parent) !== parent)
463
+ parent = path.dirname(parent);
464
+ const realParent = fs.realpathSync(parent);
465
+ if (!roots.some(root => isSameOrDescendant(destination, root) && isSameOrDescendant(realParent, root))
466
+ || isHClassPath(destination) || isLClassPath(destination)
467
+ || (fs.existsSync(destination) && (!fs.lstatSync(destination).isFile() || fs.lstatSync(destination).isSymbolicLink()))) {
468
+ return { ok: false, code: 'INVALID_LOCAL_PATH', error: 'Download destination must be an unprotected project/TMPDIR file' };
469
+ }
470
+ return storageDownload(authorization.aid, command.url, destination);
471
+ }
472
+ catch (error) {
473
+ return { ok: false, code: 'STORAGE_OPERATION_FAILED', error: error instanceof Error ? error.message : String(error) };
474
+ }
424
475
  });
425
476
  ipcServer.setStorageUploadExecutor(async (params) => {
477
+ try {
478
+ if (!params.argv)
479
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Managed storage upload requires argv' };
480
+ const command = resolveStorageCommandArgv(params.argv);
481
+ if (command.sub !== 'upload')
482
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Upload endpoint does not match argv' };
483
+ params = { ...params, aid: command.aid, localFile: command.localFile, remotePath: command.remotePath, isPublic: command.isPublic };
484
+ }
485
+ catch (error) {
486
+ return { ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) };
487
+ }
426
488
  const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: params.aid }, 'storage.upload');
427
489
  if (!authorization.ok)
428
490
  return authorization;
@@ -438,7 +500,9 @@ export function registerOperationIpcProviders({ ipcServer, sessionManager, messa
438
500
  }
439
501
  const localFile = fs.realpathSync(requestedFile);
440
502
  const stat = fs.lstatSync(localFile);
441
- if (!isSameOrDescendant(localFile, projectPath) || isHClassPath(localFile)) {
503
+ const inProject = isSameOrDescendant(localFile, projectPath);
504
+ const inRuntime = authorization.sessionRuntimeDir && isSameOrDescendant(localFile, fs.realpathSync(authorization.sessionRuntimeDir));
505
+ if ((!inProject && !inRuntime) || isHClassPath(localFile) || isLClassPath(localFile)) {
442
506
  return { ok: false, code: 'INVALID_LOCAL_PATH', error: 'Upload source must be an unprotected file inside the current session project' };
443
507
  }
444
508
  if (!stat.isFile() || stat.isSymbolicLink()) {
@@ -5,7 +5,10 @@ import { classifyAunPayloadForLog } from './message-log.js';
5
5
  import { isValidAunDeliveryForChannel } from './outbound.js';
6
6
  import { isHClassPath } from '../permission/protected-paths.js';
7
7
  import { evaluateOutboundContactAdmission } from './message-admission.js';
8
- import { normalizeAunMentionEntries } from '../../aun/msg/mention-schema.js';
8
+ import { normalizeAunMentionEntries, withAunAttention } from '../../aun/msg/mention-schema.js';
9
+ import { resolveManagedSendArgv } from '../../cli/aun-commands.js';
10
+ import { readTriggerLocalFile, resolveTriggerLocalPath } from '../../trigger/local-files.js';
11
+ import { sameGroupAid } from '../../aun/group/identity.js';
9
12
  export function createAunHandoffSender(sessionManager, channelInstances) {
10
13
  return async (handoff) => {
11
14
  const targetSession = await sessionManager.getSessionById(handoff.target_session_id);
@@ -57,9 +60,25 @@ export function createAunHandoffSender(sessionManager, channelInstances) {
57
60
  }
58
61
  export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegationRegistry, responseEngine, handoffRuntime, }) {
59
62
  const sender = async (params) => {
60
- const runtimeCausation = params.originSessionId
61
- ? responseEngine.getTaskRuntimeContext(params.originSessionId)?.causation
62
- : undefined;
63
+ let textFile;
64
+ try {
65
+ if (params.argv !== undefined) {
66
+ const command = resolveManagedSendArgv(params.argv);
67
+ textFile = command.textFile;
68
+ params = { ...params, ...command };
69
+ }
70
+ else if (!params.aid || !params.to || !params.scope) {
71
+ return { ok: false, code: 'INVALID_REQUEST', error: 'Managed AUN sends require command parameters' };
72
+ }
73
+ }
74
+ catch (error) {
75
+ return { ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) };
76
+ }
77
+ const runtime = params.originSessionId ? responseEngine.getTaskRuntimeContext(params.originSessionId) : undefined;
78
+ const runtimeCausation = runtime?.causation;
79
+ if (params.argv !== undefined && !params.delegationCommandHash) {
80
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Managed AUN sends require an approved command hash' };
81
+ }
63
82
  const delegation = authorizeDelegatedAunMsgSend(agentDelegationRegistry, {
64
83
  delegationToken: params.delegationToken,
65
84
  delegationCommandHash: params.delegationCommandHash,
@@ -73,6 +92,30 @@ export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegat
73
92
  if (!delegation.ok) {
74
93
  return { ok: false, error: delegation.reason, code: delegation.code };
75
94
  }
95
+ try {
96
+ const fileScope = runtime?.projectPath ? { selfAid: delegation.grant.selfAid, projectPath: runtime.projectPath,
97
+ sessionRuntimeDir: delegation.grant.sessionRuntimeDir, cwd: runtime.projectPath } : undefined;
98
+ if ((textFile || params.file) && !fileScope)
99
+ throw new Error('Current task project context is unavailable');
100
+ if (textFile) {
101
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(readTriggerLocalFile(textFile, fileScope));
102
+ if (!text || text.includes('\0'))
103
+ throw new Error('Message file must contain nonempty UTF-8 text');
104
+ params.payload = { type: 'text', text };
105
+ }
106
+ if (params.file)
107
+ params.file = { ...params.file, filePath: resolveTriggerLocalPath(params.file.filePath, fileScope) };
108
+ if (params.payload)
109
+ params.payload = withAunAttention(params.payload);
110
+ const currentRelation = runtime?.channel === 'aun' && runtime.selfAid === params.aid
111
+ && runtime.chatType === (params.scope === 'group' ? 'group' : 'private')
112
+ && !!runtime.channelId && (params.scope === 'group' ? sameGroupAid(runtime.channelId, params.to) : runtime.channelId === params.to);
113
+ params.thread = typeof params.payload?.thread_id === 'string' && params.payload.thread_id.trim()
114
+ ? params.payload.thread_id.trim() : params.thread ?? (currentRelation ? runtime?.threadId : undefined);
115
+ }
116
+ catch (error) {
117
+ return { ok: false, code: 'INVALID_LOCAL_FILE', error: error instanceof Error ? error.message : String(error) };
118
+ }
76
119
  const inst = channelInstances.find((candidate) => {
77
120
  if (candidate.channelType !== 'aun')
78
121
  return false;
@@ -170,6 +213,7 @@ export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegat
170
213
  channelType: delegation.grant.channelType,
171
214
  chatType: delegation.grant.chatType,
172
215
  peerKey: delegation.grant.peerKey,
216
+ approvalInteractionPolicy: delegation.grant.approvalInteractionPolicy,
173
217
  },
174
218
  causation: runtimeCausation,
175
219
  });