evolcore 0.0.12 → 0.0.14
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 +37 -0
- package/bin/codex-managed-hook.mjs +4 -1
- package/bin/ec-safe-output.js +26 -7
- package/bin/install-codex-managed-hooks.mjs +201 -0
- package/dist/agents/claude-runner.js +105 -11
- package/dist/agents/codex-app-server-client.js +176 -7
- package/dist/agents/codex-runner.js +248 -84
- package/dist/agents/ecagent-runner.js +21 -2
- package/dist/agents/gemini-runner.js +9 -4
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +176 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +514 -150
- package/dist/channels/daemon.js +2 -0
- package/dist/cli/agent.js +9 -96
- package/dist/cli/aun-commands.js +1 -1
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +65 -19
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/task-context.js +172 -0
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +7 -0
- package/dist/config/config-manager.js +27 -3
- package/dist/config/contact-book-store.js +7 -2
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +30 -1
- package/dist/core/auth/authorization-audit.js +47 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +10 -1
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +8 -1
- package/dist/core/command/command-handler.js +201 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +34 -1
- package/dist/core/message/message-bridge.js +68 -13
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +242 -48
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +649 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +266 -56
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/runtime-lock.js +101 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +191 -89
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/response-system/engines/v1/proactive-flow.js +92 -8
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/history.js +42 -7
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +17 -0
- package/dist/utils/logger.js +37 -4
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +7 -0
- package/kits/templates/roles/member.json +8 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -5,6 +5,7 @@ import { msgOnline } from './p2p.js';
|
|
|
5
5
|
import { resolveRoot } from '../../paths.js';
|
|
6
6
|
import { isExplicitGroupId } from '../group-identity.js';
|
|
7
7
|
import { isValidAid } from '../aid/validation.js';
|
|
8
|
+
import { downloadGroupFsBytes } from '../group-fs-download.js';
|
|
8
9
|
import { groupBan, groupBanlist, groupCreate, groupDissolve, groupInfo, groupInvite, groupJoin, groupKick, groupLeave, groupList, groupMembers, groupOnline, groupPull, groupAck, groupResume, groupRules, groupRulesFileGet, groupRulesFilePublish, groupRulesFileSet, groupSetRole, groupSuspend, groupTransferOwner, groupUnban, groupUpdate, groupUpdateRules, } from './group.js';
|
|
9
10
|
/** Rules uploaded by a managed task are carried as bounded UTF-8 content. */
|
|
10
11
|
export const MAX_MANAGED_GROUP_RULES_BYTES = 4 * 1024;
|
|
@@ -560,8 +561,9 @@ export class ManagedFsOperationError extends Error {
|
|
|
560
561
|
}
|
|
561
562
|
const FS_VALUE_FLAGS = new Set([
|
|
562
563
|
'--as', '--format', '--max-bytes', '--head-bytes', '--name', '--pattern', '--type', '--node-type',
|
|
563
|
-
'--size', '--mtime', '--page', '--page-size',
|
|
564
|
+
'--size', '--mtime', '--page', '--page-size', '-m', '-x',
|
|
564
565
|
]);
|
|
566
|
+
const FS_BOOLEAN_FLAGS = new Set(['-p', '--parents', '-r', '--recursive', '-f', '--force', '--overwrite']);
|
|
565
567
|
function failFs(code, reason) {
|
|
566
568
|
return { ok: false, code, reason };
|
|
567
569
|
}
|
|
@@ -587,29 +589,37 @@ function parseRemote(value, allowEmpty) {
|
|
|
587
589
|
}
|
|
588
590
|
if (remotePath === '' && allowEmpty)
|
|
589
591
|
return { groupId, remotePath: '' };
|
|
590
|
-
if (!remotePath.startsWith('/') || remotePath.length > MAX_REMOTE_PATH_LENGTH || /[\u0000-\u001f\u007f]/.test(remotePath)
|
|
592
|
+
if (!remotePath.startsWith('/') || remotePath.length > MAX_REMOTE_PATH_LENGTH || /[\u0000-\u001f\u007f]/.test(remotePath)
|
|
593
|
+
|| remotePath.split('/').includes('..')) {
|
|
591
594
|
return failFs('INVALID_PATH', 'remote group path must be a bounded absolute path');
|
|
592
595
|
}
|
|
593
596
|
return { groupId, remotePath };
|
|
594
597
|
}
|
|
595
|
-
/** Parse the daemon-owned, current-group
|
|
598
|
+
/** Parse the daemon-owned, current-group filesystem grammar. */
|
|
596
599
|
export function resolveManagedFsOperation(argv) {
|
|
597
600
|
const raw = argv[0] === 'fs' ? argv.slice(1) : argv;
|
|
598
|
-
const
|
|
599
|
-
if (!
|
|
600
|
-
return failFs('NOT_ALLOWED', `unsupported managed fs command: ${
|
|
601
|
+
const rawKind = raw[0];
|
|
602
|
+
if (!rawKind || !['ls', 'stat', 'lstat', 'cat', 'find', 'df', 'getfacl', 'setfacl', 'mkdir', 'cp', 'mv', 'rm'].includes(rawKind)) {
|
|
603
|
+
return failFs('NOT_ALLOWED', `unsupported managed fs command: ${rawKind || '<missing>'}`);
|
|
601
604
|
}
|
|
602
605
|
const values = new Map();
|
|
606
|
+
const booleanFlags = new Set();
|
|
603
607
|
const positionals = [];
|
|
604
608
|
let follow = false;
|
|
605
609
|
for (let index = 1; index < raw.length; index++) {
|
|
606
610
|
const token = raw[index];
|
|
607
611
|
if (token === '-L' || token === '--follow') {
|
|
608
|
-
if (
|
|
612
|
+
if (rawKind !== 'stat')
|
|
609
613
|
return failFs('NOT_ALLOWED', `${token} is only allowed for stat`);
|
|
610
614
|
follow = true;
|
|
611
615
|
continue;
|
|
612
616
|
}
|
|
617
|
+
if (FS_BOOLEAN_FLAGS.has(token)) {
|
|
618
|
+
if (booleanFlags.has(token))
|
|
619
|
+
return failFs('INVALID_ARGUMENT', `${token} may only be specified once`);
|
|
620
|
+
booleanFlags.add(token);
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
613
623
|
if (!token.startsWith('-')) {
|
|
614
624
|
positionals.push(token);
|
|
615
625
|
continue;
|
|
@@ -622,12 +632,10 @@ export function resolveManagedFsOperation(argv) {
|
|
|
622
632
|
if (values.has(token))
|
|
623
633
|
return failFs('INVALID_ARGUMENT', `${token} may only be specified once`);
|
|
624
634
|
const value = raw[++index];
|
|
625
|
-
if (!value || value.startsWith('
|
|
635
|
+
if (!value || value.startsWith('-'))
|
|
626
636
|
return failFs('INVALID_ARGUMENT', `${token} requires a value`);
|
|
627
637
|
values.set(token, value);
|
|
628
638
|
}
|
|
629
|
-
if (positionals.length !== 1)
|
|
630
|
-
return failFs('INVALID_ARGUMENT', `${kind} accepts exactly one remote group path`);
|
|
631
639
|
const actorAidValue = values.get('--as');
|
|
632
640
|
const actorAid = actorAidValue === undefined ? undefined : actorAidValue.replace(/^@/, '');
|
|
633
641
|
if (actorAid !== undefined && !isValidAid(actorAid))
|
|
@@ -635,22 +643,107 @@ export function resolveManagedFsOperation(argv) {
|
|
|
635
643
|
const format = values.get('--format');
|
|
636
644
|
if (format !== undefined && format !== 'json')
|
|
637
645
|
return failFs('INVALID_ARGUMENT', '--format only supports json');
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
646
|
+
const expected = rawKind === 'cp' || rawKind === 'mv' ? 2 : 1;
|
|
647
|
+
if (positionals.length !== expected)
|
|
648
|
+
return failFs('INVALID_ARGUMENT', `${rawKind} accepts ${expected} positional argument${expected === 1 ? '' : 's'}`);
|
|
649
|
+
const allowedValues = {
|
|
650
|
+
ls: new Set(['--as', '--format']), stat: new Set(['--as', '--format']), lstat: new Set(['--as', '--format']),
|
|
642
651
|
cat: new Set(['--as', '--format', '--max-bytes', '--head-bytes']),
|
|
643
652
|
find: new Set(['--as', '--format', '--name', '--pattern', '--type', '--node-type', '--size', '--mtime', '--page', '--page-size']),
|
|
644
|
-
df: new Set(['--as', '--format']),
|
|
653
|
+
df: new Set(['--as', '--format']), mkdir: new Set(['--as', '--format']), cp: new Set(['--as', '--format']),
|
|
654
|
+
mv: new Set(['--as', '--format']), rm: new Set(['--as', '--format']),
|
|
655
|
+
getfacl: new Set(['--as', '--format']), setfacl: new Set(['--as', '--format', '-m', '-x']),
|
|
645
656
|
};
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
657
|
+
const allowedBoolean = {
|
|
658
|
+
ls: new Set(), stat: new Set(), lstat: new Set(), cat: new Set(), find: new Set(), df: new Set(), getfacl: new Set(), setfacl: new Set(),
|
|
659
|
+
mkdir: new Set(['-p', '--parents']), cp: new Set(['-r', '--recursive', '-f', '--force', '--overwrite']),
|
|
660
|
+
mv: new Set(['-f', '--force', '--overwrite']), rm: new Set(['-r', '--recursive', '-f', '--force', '--overwrite']),
|
|
661
|
+
};
|
|
662
|
+
for (const flag of values.keys())
|
|
663
|
+
if (!allowedValues[rawKind].has(flag))
|
|
664
|
+
return failFs('NOT_ALLOWED', `${flag} is not allowed for managed fs ${rawKind}`);
|
|
665
|
+
for (const flag of booleanFlags)
|
|
666
|
+
if (!allowedBoolean[rawKind].has(flag))
|
|
667
|
+
return failFs('NOT_ALLOWED', `${flag} is not allowed for managed fs ${rawKind}`);
|
|
668
|
+
const overwrite = booleanFlags.has('-f') || booleanFlags.has('--force') || booleanFlags.has('--overwrite');
|
|
669
|
+
const recursive = booleanFlags.has('-r') || booleanFlags.has('--recursive');
|
|
670
|
+
const remoteOrError = (value, allowEmpty = false) => parseRemote(value, allowEmpty);
|
|
671
|
+
if (rawKind === 'getfacl') {
|
|
672
|
+
if (values.has('-m') || values.has('-x'))
|
|
673
|
+
return failFs('NOT_ALLOWED', 'getfacl does not accept ACL mutation flags');
|
|
674
|
+
const remote = remoteOrError(positionals[0]);
|
|
675
|
+
if (!('groupId' in remote))
|
|
676
|
+
return remote;
|
|
677
|
+
return { ok: true, command: {
|
|
678
|
+
kind: 'getfacl', operationId: 'ec.fs.getfacl', canonicalArgv: ['fs', 'getfacl', positionals[0], ...(actorAid ? ['--as', actorAid] : [])],
|
|
679
|
+
actorAid, groupId: remote.groupId, remotePath: remote.remotePath,
|
|
680
|
+
} };
|
|
681
|
+
}
|
|
682
|
+
if (rawKind === 'setfacl') {
|
|
683
|
+
const modify = values.get('-m');
|
|
684
|
+
const remove = values.get('-x');
|
|
685
|
+
if (!!modify === !!remove)
|
|
686
|
+
return failFs('INVALID_ARGUMENT', 'setfacl requires exactly one of -m or -x');
|
|
687
|
+
const spec = modify ?? remove ?? '';
|
|
688
|
+
const parsedAcl = parseManagedGroupAclSpec(spec, modify !== undefined);
|
|
689
|
+
if (!('grantee' in parsedAcl))
|
|
690
|
+
return parsedAcl;
|
|
691
|
+
const remote = remoteOrError(positionals[0]);
|
|
692
|
+
if (!('groupId' in remote))
|
|
693
|
+
return remote;
|
|
694
|
+
return { ok: true, command: {
|
|
695
|
+
kind: 'setfacl', operationId: 'ec.fs.setfacl', canonicalArgv: ['fs', 'setfacl', positionals[0], modify !== undefined ? '-m' : '-x', spec, ...(actorAid ? ['--as', actorAid] : [])],
|
|
696
|
+
actorAid, groupId: remote.groupId, remotePath: remote.remotePath,
|
|
697
|
+
aclGrantee: parsedAcl.grantee, ...(parsedAcl.perms ? { aclPerms: parsedAcl.perms } : {}), aclRemove: remove !== undefined,
|
|
698
|
+
} };
|
|
699
|
+
}
|
|
700
|
+
if (rawKind === 'cp') {
|
|
701
|
+
const destination = remoteOrError(positionals[1]);
|
|
702
|
+
if (!('groupId' in destination))
|
|
703
|
+
return destination;
|
|
704
|
+
const source = positionals[0].includes(':') ? remoteOrError(positionals[0]) : undefined;
|
|
705
|
+
if (source && !('groupId' in source))
|
|
706
|
+
return source;
|
|
707
|
+
if (source && source.groupId !== destination.groupId)
|
|
708
|
+
return failFs('INVALID_GROUP', 'managed cp cannot cross groups');
|
|
709
|
+
const kind = source ? 'copy' : 'upload';
|
|
710
|
+
return { ok: true, command: {
|
|
711
|
+
kind, operationId: `ec.fs.${kind}`, canonicalArgv: ['fs', 'cp', positionals[0], positionals[1], ...(actorAid ? ['--as', actorAid] : [])],
|
|
712
|
+
actorAid, groupId: destination.groupId, remotePath: destination.remotePath, destinationPath: destination.remotePath,
|
|
713
|
+
...(source ? { remoteSourcePath: source.remotePath } : { localPath: positionals[0] }), overwrite, recursive,
|
|
714
|
+
} };
|
|
715
|
+
}
|
|
716
|
+
if (rawKind === 'mv') {
|
|
717
|
+
const source = remoteOrError(positionals[0]);
|
|
718
|
+
const destination = remoteOrError(positionals[1]);
|
|
719
|
+
if (!('groupId' in source))
|
|
720
|
+
return source;
|
|
721
|
+
if (!('groupId' in destination))
|
|
722
|
+
return destination;
|
|
723
|
+
if (source.groupId !== destination.groupId)
|
|
724
|
+
return failFs('INVALID_GROUP', 'managed mv cannot cross groups');
|
|
725
|
+
return { ok: true, command: {
|
|
726
|
+
kind: 'move', operationId: 'ec.fs.move', canonicalArgv: ['fs', 'mv', positionals[0], positionals[1], ...(actorAid ? ['--as', actorAid] : [])],
|
|
727
|
+
actorAid, groupId: destination.groupId, remotePath: destination.remotePath, destinationPath: destination.remotePath,
|
|
728
|
+
remoteSourcePath: source.remotePath, overwrite,
|
|
729
|
+
} };
|
|
730
|
+
}
|
|
731
|
+
const remote = remoteOrError(positionals[0], rawKind === 'df');
|
|
651
732
|
if (!('groupId' in remote))
|
|
652
733
|
return remote;
|
|
653
|
-
|
|
734
|
+
if (rawKind === 'mkdir')
|
|
735
|
+
return { ok: true, command: {
|
|
736
|
+
kind: 'mkdir', operationId: 'ec.fs.mkdir', canonicalArgv: ['fs', 'mkdir', positionals[0], ...(actorAid ? ['--as', actorAid] : [])],
|
|
737
|
+
actorAid, groupId: remote.groupId, remotePath: remote.remotePath, parents: booleanFlags.has('-p') || booleanFlags.has('--parents'),
|
|
738
|
+
} };
|
|
739
|
+
if (rawKind === 'rm' && remote.remotePath === '/')
|
|
740
|
+
return failFs('INVALID_PATH', 'managed rm cannot target the group root');
|
|
741
|
+
if (rawKind === 'rm')
|
|
742
|
+
return { ok: true, command: {
|
|
743
|
+
kind: 'remove', operationId: 'ec.fs.remove', canonicalArgv: ['fs', 'rm', positionals[0], ...(actorAid ? ['--as', actorAid] : [])],
|
|
744
|
+
actorAid, groupId: remote.groupId, remotePath: remote.remotePath, recursive, overwrite,
|
|
745
|
+
} };
|
|
746
|
+
const effectiveKind = rawKind === 'stat' && !follow ? 'lstat' : rawKind;
|
|
654
747
|
const maxBytes = positiveInteger(values.get('--max-bytes'), '--max-bytes', MAX_MANAGED_FS_CAT_BYTES);
|
|
655
748
|
if (typeof maxBytes !== 'number' && maxBytes !== undefined)
|
|
656
749
|
return maxBytes;
|
|
@@ -664,28 +757,33 @@ export function resolveManagedFsOperation(argv) {
|
|
|
664
757
|
if (typeof pageSize !== 'number' && pageSize !== undefined)
|
|
665
758
|
return pageSize;
|
|
666
759
|
const nodeType = values.get('--type') ?? values.get('--node-type');
|
|
667
|
-
if (nodeType !== undefined && !['f', 'd', 'l'].includes(nodeType))
|
|
760
|
+
if (nodeType !== undefined && !['f', 'd', 'l'].includes(nodeType))
|
|
668
761
|
return failFs('INVALID_ARGUMENT', '--type only supports f|d|l');
|
|
762
|
+
return { ok: true, command: {
|
|
763
|
+
kind: effectiveKind, operationId: `ec.fs.${effectiveKind}`, canonicalArgv: ['fs', effectiveKind, positionals[0], ...(actorAid ? ['--as', actorAid] : [])],
|
|
764
|
+
actorAid, groupId: remote.groupId, remotePath: remote.remotePath, maxBytes, headBytes,
|
|
765
|
+
name: values.get('--name') ?? values.get('--pattern'), nodeType, size: values.get('--size'), mtime: values.get('--mtime'), page, pageSize,
|
|
766
|
+
} };
|
|
767
|
+
}
|
|
768
|
+
function parseManagedGroupAclSpec(value, requirePerms) {
|
|
769
|
+
const parts = value.split(':');
|
|
770
|
+
if (parts[0] !== 'role' || !parts[1] || parts.length !== (requirePerms ? 3 : 2)) {
|
|
771
|
+
return failFs('INVALID_ARGUMENT', 'group ACL entry must use role:member:<perms>, role:admin:<perms>, or role:<role>');
|
|
669
772
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
mtime: values.get('--mtime'),
|
|
685
|
-
page,
|
|
686
|
-
pageSize,
|
|
687
|
-
},
|
|
688
|
-
};
|
|
773
|
+
const grantee = `role:${parts[1]}`;
|
|
774
|
+
if (grantee !== 'role:member' && grantee !== 'role:admin') {
|
|
775
|
+
return failFs('INVALID_ARGUMENT', 'group ACL only supports role:member or role:admin');
|
|
776
|
+
}
|
|
777
|
+
if (!requirePerms)
|
|
778
|
+
return { grantee };
|
|
779
|
+
const perms = parts[2];
|
|
780
|
+
if (!perms || !/^(?:r|rw|rwx)$/.test(perms)) {
|
|
781
|
+
return failFs('INVALID_ARGUMENT', 'group ACL permissions must be r, rw, or rwx');
|
|
782
|
+
}
|
|
783
|
+
if (grantee === 'role:member' && perms !== 'rw') {
|
|
784
|
+
return failFs('INVALID_ARGUMENT', 'role:member may only receive rw group ACL permissions');
|
|
785
|
+
}
|
|
786
|
+
return { grantee, perms };
|
|
689
787
|
}
|
|
690
788
|
function numberFrom(value, keys) {
|
|
691
789
|
for (const key of keys) {
|
|
@@ -695,18 +793,6 @@ function numberFrom(value, keys) {
|
|
|
695
793
|
}
|
|
696
794
|
return undefined;
|
|
697
795
|
}
|
|
698
|
-
function bytesFromDownload(value) {
|
|
699
|
-
const candidate = value?.bytes ?? value?.data ?? value?.content ?? value?.blob ?? value;
|
|
700
|
-
if (candidate instanceof Uint8Array)
|
|
701
|
-
return candidate;
|
|
702
|
-
if (Buffer.isBuffer(candidate))
|
|
703
|
-
return new Uint8Array(candidate);
|
|
704
|
-
if (typeof candidate === 'string')
|
|
705
|
-
return new Uint8Array(Buffer.from(candidate, 'base64'));
|
|
706
|
-
if (Array.isArray(candidate))
|
|
707
|
-
return new Uint8Array(candidate);
|
|
708
|
-
throw new ManagedFsOperationError('FS_DOWNLOAD_INVALID', 'group filesystem returned an unsupported blob');
|
|
709
|
-
}
|
|
710
796
|
async function withClient(actorAid, fn) {
|
|
711
797
|
const store = await getAidStore({ slotId: SLOT.cli, aunPath: resolveRoot() });
|
|
712
798
|
let client;
|
|
@@ -728,7 +814,7 @@ async function withClient(actorAid, fn) {
|
|
|
728
814
|
catch { /* best effort */ }
|
|
729
815
|
}
|
|
730
816
|
}
|
|
731
|
-
/** Execute a parsed
|
|
817
|
+
/** Execute a parsed operation against the current group using daemon-owned credentials. */
|
|
732
818
|
export async function executeManagedFsOperation(command) {
|
|
733
819
|
if (!command.actorAid)
|
|
734
820
|
throw new ManagedFsOperationError('INVALID_ACTOR', 'managed fs actor is required');
|
|
@@ -736,6 +822,31 @@ export async function executeManagedFsOperation(command) {
|
|
|
736
822
|
try {
|
|
737
823
|
return await withClient(command.actorAid, async (client) => {
|
|
738
824
|
switch (command.kind) {
|
|
825
|
+
case 'mkdir':
|
|
826
|
+
return { ok: true, command: 'mkdir', path: ref, result: await client.group.fs.mkdir(ref, { parents: command.parents }) };
|
|
827
|
+
case 'upload': {
|
|
828
|
+
if (!command.localPath)
|
|
829
|
+
throw new ManagedFsOperationError('INVALID_SOURCE', 'managed upload source is required');
|
|
830
|
+
return { ok: true, command: 'upload', path: ref, localPath: command.localPath,
|
|
831
|
+
result: await client.group.fs.cp(command.localPath, ref, { parents: true, overwrite: command.overwrite, force: command.overwrite }) };
|
|
832
|
+
}
|
|
833
|
+
case 'copy': {
|
|
834
|
+
if (!command.remoteSourcePath)
|
|
835
|
+
throw new ManagedFsOperationError('INVALID_SOURCE', 'managed copy source is required');
|
|
836
|
+
const source = `${command.groupId}:${command.remoteSourcePath}`;
|
|
837
|
+
return { ok: true, command: 'copy', src: source, path: ref,
|
|
838
|
+
result: await client.group.fs.cp(source, ref, { recursive: command.recursive, overwrite: command.overwrite, force: command.overwrite }) };
|
|
839
|
+
}
|
|
840
|
+
case 'move': {
|
|
841
|
+
if (!command.remoteSourcePath)
|
|
842
|
+
throw new ManagedFsOperationError('INVALID_SOURCE', 'managed move source is required');
|
|
843
|
+
const source = `${command.groupId}:${command.remoteSourcePath}`;
|
|
844
|
+
return { ok: true, command: 'move', src: source, path: ref,
|
|
845
|
+
result: await client.group.fs.mv(source, ref, { overwrite: command.overwrite, force: command.overwrite }) };
|
|
846
|
+
}
|
|
847
|
+
case 'remove':
|
|
848
|
+
return { ok: true, command: 'remove', path: ref,
|
|
849
|
+
result: await client.group.fs.rm(ref, { recursive: command.recursive, force: command.overwrite }) };
|
|
739
850
|
case 'ls': {
|
|
740
851
|
const raw = await client.group.fs.ls(ref);
|
|
741
852
|
const value = raw;
|
|
@@ -762,6 +873,16 @@ export async function executeManagedFsOperation(command) {
|
|
|
762
873
|
}
|
|
763
874
|
case 'df':
|
|
764
875
|
return { ok: true, command: 'df', path: `${command.groupId}:`, usage: await client.group.fs.df(`${command.groupId}:`) };
|
|
876
|
+
case 'getfacl':
|
|
877
|
+
return { ok: true, command: 'getfacl', path: ref, result: await client.group.fs.getAcl(ref) };
|
|
878
|
+
case 'setfacl': {
|
|
879
|
+
if (!command.aclGrantee)
|
|
880
|
+
throw new ManagedFsOperationError('INVALID_ACL', 'group ACL grantee is required');
|
|
881
|
+
const result = command.aclRemove
|
|
882
|
+
? await client.group.fs.removeAcl(ref, { granteeAid: command.aclGrantee })
|
|
883
|
+
: await client.group.fs.setAcl(ref, { granteeAid: command.aclGrantee, perms: command.aclPerms });
|
|
884
|
+
return { ok: true, command: 'setfacl', path: ref, grantee: command.aclGrantee, removed: !!command.aclRemove, result };
|
|
885
|
+
}
|
|
765
886
|
case 'cat': {
|
|
766
887
|
const node = await client.group.fs.stat(ref);
|
|
767
888
|
const size = numberFrom(node, ['size', 'size_bytes', 'bytes']);
|
|
@@ -770,7 +891,7 @@ export async function executeManagedFsOperation(command) {
|
|
|
770
891
|
throw new ManagedFsOperationError('FS_SIZE_REQUIRED', 'group file size is required before managed cat');
|
|
771
892
|
if (size > maxBytes)
|
|
772
893
|
throw new ManagedFsOperationError('FS_CAT_TOO_LARGE', `managed cat is limited to ${maxBytes} bytes`);
|
|
773
|
-
const bytes =
|
|
894
|
+
const { bytes } = await downloadGroupFsBytes(client, ref);
|
|
774
895
|
if (bytes.byteLength > maxBytes)
|
|
775
896
|
throw new ManagedFsOperationError('FS_CAT_TOO_LARGE', `managed cat is limited to ${maxBytes} bytes`);
|
|
776
897
|
const headBytes = Math.min(command.headBytes ?? 256, MAX_MANAGED_FS_HEAD_BYTES, bytes.byteLength);
|
package/dist/aun/msg/upload.js
CHANGED
|
@@ -2,7 +2,22 @@ import crypto from 'crypto';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { guessMime, formatSize } from '../../utils/media-cache.js';
|
|
5
|
-
|
|
5
|
+
const EXT_TYPE_MAP = {
|
|
6
|
+
'.png': 'image', '.jpg': 'image', '.jpeg': 'image',
|
|
7
|
+
'.gif': 'image', '.webp': 'image', '.svg': 'image',
|
|
8
|
+
'.bmp': 'image', '.heic': 'image', '.heif': 'image',
|
|
9
|
+
'.mp4': 'video', '.mov': 'video', '.webm': 'video',
|
|
10
|
+
'.avi': 'video', '.mkv': 'video', '.m4v': 'video',
|
|
11
|
+
'.opus': 'voice', '.mp3': 'voice', '.aac': 'voice',
|
|
12
|
+
'.m4a': 'voice', '.wav': 'voice', '.flac': 'voice', '.ogg': 'voice',
|
|
13
|
+
};
|
|
14
|
+
export function inferPayloadType(filename) {
|
|
15
|
+
const ext = path.extname(filename).toLowerCase();
|
|
16
|
+
return EXT_TYPE_MAP[ext] ?? 'file';
|
|
17
|
+
}
|
|
18
|
+
export function isValidPayloadType(value) {
|
|
19
|
+
return value === 'image' || value === 'video' || value === 'voice' || value === 'file';
|
|
20
|
+
}
|
|
6
21
|
/** 单次上传最大大小(与 daemon sendFile 一致)。 */
|
|
7
22
|
export const MAX_AUN_ATTACHMENT_SIZE = 10 * 1024 * 1024;
|
|
8
23
|
/** server 端 inline 上限错误的识别特征。优先匹配错误消息里的“inline 上限”字样。 */
|
package/dist/aun/outbox.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import { agentOutboxPath } from '../paths.js';
|
|
3
3
|
import { atomicRead, atomicWrite } from '../utils/atomic-write.js';
|
|
4
|
+
import { isDeliveryTarget, isDeliveryTargetForChannel, sameDeliveryTarget } from '../core/message/message-utils.js';
|
|
4
5
|
const MAX_ENTRIES_PER_AID = 20;
|
|
5
6
|
const DEFAULT_TTL = 300_000; // 5 minutes
|
|
6
7
|
function outboxFile(aid) {
|
|
@@ -16,6 +17,9 @@ function isExpired(entry) {
|
|
|
16
17
|
return false;
|
|
17
18
|
return Date.now() - entry.ts > entry.ttl;
|
|
18
19
|
}
|
|
20
|
+
export function isDeliveryForChannel(value, channelId) {
|
|
21
|
+
return isDeliveryTargetForChannel(value, channelId);
|
|
22
|
+
}
|
|
19
23
|
function readEntries(aid) {
|
|
20
24
|
const file = outboxFile(aid);
|
|
21
25
|
try {
|
|
@@ -43,17 +47,32 @@ function writeEntries(aid, entries) {
|
|
|
43
47
|
atomicWrite(file, content);
|
|
44
48
|
}
|
|
45
49
|
export function enqueue(aid, opts) {
|
|
46
|
-
const
|
|
50
|
+
const delivery = opts.delivery;
|
|
51
|
+
if (!isDeliveryForChannel(delivery, opts.channelId)) {
|
|
52
|
+
const code = isDeliveryTarget(delivery) ? 'AUN_OUTBOUND_ROUTE_MISMATCH' : 'AUN_OUTBOUND_ROUTE_REQUIRED';
|
|
53
|
+
throw Object.assign(new Error(`invalid AUN outbox delivery route for channelId=${opts.channelId}`), { code });
|
|
54
|
+
}
|
|
55
|
+
let existingEntries = readEntries(aid);
|
|
47
56
|
if (opts.dedupeKey) {
|
|
48
57
|
const existing = existingEntries.find(entry => entry.dedupeKey === opts.dedupeKey && !isExpired(entry));
|
|
49
|
-
if (existing
|
|
58
|
+
if (existing
|
|
59
|
+
&& existing.channelId === opts.channelId
|
|
60
|
+
&& isDeliveryForChannel(existing.delivery, existing.channelId)
|
|
61
|
+
&& sameDeliveryTarget(existing.delivery, delivery)) {
|
|
50
62
|
return existing;
|
|
63
|
+
}
|
|
64
|
+
if (existing) {
|
|
65
|
+
// A pre-route entry must not block corrected code from persisting the
|
|
66
|
+
// same logical operation with an explicit delivery target.
|
|
67
|
+
existingEntries = existingEntries.filter(entry => entry.id !== existing.id);
|
|
68
|
+
}
|
|
51
69
|
}
|
|
52
70
|
const entry = {
|
|
53
71
|
id: generateId(),
|
|
54
72
|
ts: Date.now(),
|
|
55
73
|
aid,
|
|
56
74
|
channelId: opts.channelId,
|
|
75
|
+
delivery,
|
|
57
76
|
dedupeKey: opts.dedupeKey,
|
|
58
77
|
critical: opts.critical,
|
|
59
78
|
type: opts.type,
|
|
@@ -123,6 +142,9 @@ export function cleanup(aid) {
|
|
|
123
142
|
// duplicate sends while allowing a later drain trigger to pick up entries
|
|
124
143
|
// enqueued during an active pass.
|
|
125
144
|
const activeDrainTails = new Map();
|
|
145
|
+
function hasRouteForChannel(entry) {
|
|
146
|
+
return isDeliveryForChannel(entry.delivery, entry.channelId);
|
|
147
|
+
}
|
|
126
148
|
async function drainOnce(aid, sender) {
|
|
127
149
|
const entries = readEntries(aid);
|
|
128
150
|
if (entries.length === 0)
|
|
@@ -137,6 +159,13 @@ async function drainOnce(aid, sender) {
|
|
|
137
159
|
expired++;
|
|
138
160
|
continue;
|
|
139
161
|
}
|
|
162
|
+
if (!hasRouteForChannel(entry)) {
|
|
163
|
+
// A route is security-sensitive metadata. An entry whose groupId does
|
|
164
|
+
// not match its address cannot be repaired from channelId, so remove it
|
|
165
|
+
// instead of retrying the same malformed send forever.
|
|
166
|
+
expired++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
140
169
|
try {
|
|
141
170
|
entry.attempts = (entry.attempts ?? 0) + 1;
|
|
142
171
|
const ok = await sender(entry);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getAidStore, loadClient, SLOT } from '../aid/store.js';
|
|
2
|
+
export async function createShortConnection(aid, opts) {
|
|
3
|
+
const store = await getAidStore({ slotId: opts?.slotId ?? SLOT.cli, aunPath: opts?.aunPath });
|
|
4
|
+
try {
|
|
5
|
+
const client = await loadClient(store, aid);
|
|
6
|
+
await client.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
7
|
+
return {
|
|
8
|
+
async call(method, params) {
|
|
9
|
+
return client.call(method, params);
|
|
10
|
+
},
|
|
11
|
+
async close() {
|
|
12
|
+
try {
|
|
13
|
+
await client.close();
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
store.close();
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
store.close();
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function rpcCall(aid, method, params, opts) {
|
|
27
|
+
const conn = await createShortConnection(aid, opts);
|
|
28
|
+
try {
|
|
29
|
+
const result = await conn.call(method, params);
|
|
30
|
+
return { ok: true, result };
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error.code !== undefined && error.message !== undefined) {
|
|
34
|
+
return { ok: false, error: { code: error.code, message: error.message, data: error.data } };
|
|
35
|
+
}
|
|
36
|
+
return { ok: false, error: { code: -1, message: String(error.message || error) } };
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await conn.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function rpcBatch(aid, calls, opts) {
|
|
43
|
+
const conn = await createShortConnection(aid, opts);
|
|
44
|
+
const results = [];
|
|
45
|
+
try {
|
|
46
|
+
for (const { method, params } of calls) {
|
|
47
|
+
try {
|
|
48
|
+
const result = await conn.call(method, params);
|
|
49
|
+
results.push({ ok: true, result });
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error.code !== undefined && error.message !== undefined) {
|
|
53
|
+
results.push({ ok: false, error: { code: error.code, message: error.message, data: error.data } });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
results.push({ ok: false, error: { code: -1, message: String(error.message || error) } });
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await conn.close();
|
|
64
|
+
}
|
|
65
|
+
return results;
|
|
66
|
+
}
|
package/dist/aun/rpc/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { rpcCall, rpcBatch } from './
|
|
2
|
-
export { createShortConnection } from './connection.js';
|
|
1
|
+
export { rpcCall, rpcBatch, createShortConnection } from './client.js';
|
|
@@ -46,7 +46,7 @@ export async function storageUpload(aid, localFile, remotePath, opts) {
|
|
|
46
46
|
sha256,
|
|
47
47
|
content_type: contentType,
|
|
48
48
|
size_bytes: fileBuffer.length,
|
|
49
|
-
is_private: !
|
|
49
|
+
is_private: !opts?.isPublic,
|
|
50
50
|
}, { aunPath: opts?.aunPath });
|
|
51
51
|
if (!completeResult.ok) {
|
|
52
52
|
return { ok: false, objectKey: remotePath, error: rpcErrorToString(completeResult.error) };
|
|
@@ -56,7 +56,7 @@ export async function storageUpload(aid, localFile, remotePath, opts) {
|
|
|
56
56
|
if (!publicUrl) {
|
|
57
57
|
const ticketResult = await rpcCall(aid, 'storage.create_download_ticket', {
|
|
58
58
|
object_key: remotePath,
|
|
59
|
-
expire_in_seconds: 86400 * 30,
|
|
59
|
+
expire_in_seconds: 86400 * 30,
|
|
60
60
|
}, { aunPath: opts?.aunPath });
|
|
61
61
|
if (ticketResult.ok) {
|
|
62
62
|
publicUrl = pickUrl(ticketResult.result?.url) ?? pickUrl(ticketResult.result?.download_url);
|
|
@@ -66,3 +66,12 @@ export async function storageUpload(aid, localFile, remotePath, opts) {
|
|
|
66
66
|
}
|
|
67
67
|
return { ok: true, objectKey: remotePath, publicUrl };
|
|
68
68
|
}
|
|
69
|
+
export async function storageLs(aid, prefix, opts) {
|
|
70
|
+
return rpcCall(aid, 'storage.list_objects', { prefix: prefix || '' }, { aunPath: opts?.aunPath });
|
|
71
|
+
}
|
|
72
|
+
export async function storageRm(aid, remotePath, opts) {
|
|
73
|
+
return rpcCall(aid, 'storage.delete_object', { object_key: remotePath }, { aunPath: opts?.aunPath });
|
|
74
|
+
}
|
|
75
|
+
export async function storageQuota(aid, opts) {
|
|
76
|
+
return rpcCall(aid, 'storage.get_quota', {}, { aunPath: opts?.aunPath });
|
|
77
|
+
}
|