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.
- package/CHANGELOG.md +11 -0
- package/dist/agents/codex-runner.js +28 -5
- package/dist/agents/ecagent-runner.js +4 -0
- package/dist/agents/gemini-runner.js +4 -0
- package/dist/aun/aid/store.js +0 -81
- package/dist/aun/group/fs-download.js +1 -10
- package/dist/aun/msg/managed-operation.js +130 -32
- package/dist/aun/service-proxy.js +12 -18
- package/dist/aun/storage/index.js +29 -0
- package/dist/channels/aun.js +10 -135
- package/dist/cli/agent-command.js +6 -7
- package/dist/cli/agent-create.js +43 -24
- package/dist/cli/agent.js +15 -69
- package/dist/cli/aun-commands.js +149 -5
- package/dist/cli/ctl-command.js +1 -2
- package/dist/cli/daemon-commands.js +1 -1
- package/dist/cli/diag.js +2 -2
- package/dist/cli/handoff-command.js +15 -5
- package/dist/cli/memory-command.js +85 -11
- package/dist/cli/model.js +14 -138
- package/dist/cli/queue-command.js +46 -0
- package/dist/cli/response.js +5 -54
- package/dist/cli/skill-command.js +9 -3
- package/dist/cli/stats.js +10 -4
- package/dist/cli/task-command.js +1 -0
- package/dist/cli/task-context.js +15 -0
- package/dist/cli/trigger-command.js +73 -49
- package/dist/config/builtin-roles.js +8 -7
- package/dist/core/agent-ipc-registration.js +65 -22
- package/dist/core/auth/agent-delegation.js +17 -1
- package/dist/core/auth/delegation-authorization.js +1 -0
- package/dist/core/auth/operation-catalog.js +11 -3
- package/dist/core/command/command-handler.js +164 -21
- package/dist/core/command/diag-service.js +5 -1
- package/dist/core/handoff/ipc-registration.js +38 -0
- package/dist/core/ipc-registration.js +71 -7
- package/dist/core/message/aun-ipc-sender.js +48 -4
- package/dist/core/message/message-queue.js +131 -3
- package/dist/core/message/response-engine.js +30 -2
- package/dist/core/permission/sandbox-runtime.js +14 -1
- package/dist/core/task/task-recovery-service.js +2 -0
- package/dist/core/task/task-resume-operation.js +11 -0
- package/dist/index.js +2 -2
- package/dist/ipc.js +146 -8
- package/dist/trigger/authorization.js +2 -1
- package/dist/trigger/ipc-service.js +49 -35
- package/dist/trigger/script-sandbox.js +58 -77
- package/kits/docs/AUTHORING.md +1 -1
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/evolcore/INDEX.md +2 -2
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/trigger.md +2 -2
- package/kits/rules/01-overview.md +0 -9
- package/kits/rules/06-channel.md +7 -1
- package/kits/templates/system-fragments/commands.md +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
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
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
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
|
|
2567
|
-
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
423
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
});
|