ec-web 0.0.24 → 0.0.26
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/dist/paths.js +8 -1
- package/dist/server.js +7 -5
- package/dist/sources/aid.js +28 -13
- package/dist/sources/basic-settings.js +7 -33
- package/dist/sources/relation-roles.js +23 -9
- package/dist/sources/role-definitions.js +6 -5
- package/dist/sources/types.js +1 -0
- package/dist/static/app.js +1 -1
- package/dist/static/index.html +1 -1
- package/package.json +2 -2
package/dist/paths.js
CHANGED
|
@@ -48,7 +48,14 @@ function resolveInstanceSocketPath(root) {
|
|
|
48
48
|
const hash = crypto.createHash('sha1').update(canonicalRoot).digest('hex').slice(0, 12);
|
|
49
49
|
return `\\\\.\\pipe\\daemon-${hash}`;
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
const filesystemPath = path.join(root, 'data', 'instance', 'daemon.sock');
|
|
52
|
+
if (Buffer.byteLength(filesystemPath) < 100)
|
|
53
|
+
return filesystemPath;
|
|
54
|
+
const hash = crypto.createHash('sha1').update(root).digest('hex').slice(0, 20);
|
|
55
|
+
const shortFilesystemPath = path.join(os.tmpdir(), `.ec-${hash.slice(0, 15)}.sock`);
|
|
56
|
+
if (Buffer.byteLength(shortFilesystemPath) < 100)
|
|
57
|
+
return shortFilesystemPath;
|
|
58
|
+
return `\0evolcore-${hash}`;
|
|
52
59
|
}
|
|
53
60
|
export function resolvePaths() {
|
|
54
61
|
const root = resolveRoot();
|
package/dist/server.js
CHANGED
|
@@ -40,6 +40,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
40
40
|
const STATIC_DIR = path.join(__dirname, 'static');
|
|
41
41
|
const DEFAULT_PORT = 42705;
|
|
42
42
|
const PROTOCOL_VERSION = 1; // 与 evolcore ping response 对齐的软校验版本
|
|
43
|
+
const ECWEB_TRIGGER_IPC_TYPES = new Set(['trigger.history', 'trigger.eventCatalog']);
|
|
43
44
|
function daemonOwnerMenuAuth(localDirect, actorAid) {
|
|
44
45
|
return {
|
|
45
46
|
localDirect,
|
|
@@ -48,6 +49,7 @@ function daemonOwnerMenuAuth(localDirect, actorAid) {
|
|
|
48
49
|
// authentication gate (local-direct exemption or a valid bearer token).
|
|
49
50
|
// ECWeb is the daemon-owner management surface, so remote tokens receive
|
|
50
51
|
// the same persistent fullaccess approval capability as local requests.
|
|
52
|
+
daemonOwner: true,
|
|
51
53
|
canApprovePersistentFullAccess: true,
|
|
52
54
|
};
|
|
53
55
|
}
|
|
@@ -614,7 +616,7 @@ function handleConnection(ws, req, log, tokenRecord) {
|
|
|
614
616
|
return;
|
|
615
617
|
}
|
|
616
618
|
if (msg.type === 'ipc' && msg.payload) {
|
|
617
|
-
if (typeof msg.payload.type !== 'string' || !msg.payload.type
|
|
619
|
+
if (typeof msg.payload.type !== 'string' || !ECWEB_TRIGGER_IPC_TYPES.has(msg.payload.type)) {
|
|
618
620
|
send({ type: 'ipc.response', requestId: msg.requestId, data: { ok: false, error: 'unsupported ipc payload' } });
|
|
619
621
|
return;
|
|
620
622
|
}
|
|
@@ -622,9 +624,7 @@ function handleConnection(ws, req, log, tokenRecord) {
|
|
|
622
624
|
const timeoutMs = Number.isFinite(Number(msg.timeoutMs)) && Number(msg.timeoutMs) > 0
|
|
623
625
|
? Math.min(Number(msg.timeoutMs), 120_000)
|
|
624
626
|
: 10000;
|
|
625
|
-
const payload = msg.payload
|
|
626
|
-
? { ...msg.payload, controlToken: daemonTriggerControlToken() }
|
|
627
|
-
: msg.payload;
|
|
627
|
+
const payload = { ...msg.payload, controlToken: daemonTriggerControlToken() };
|
|
628
628
|
const resp = await ipcQuery(p.socket, payload, timeoutMs);
|
|
629
629
|
send({ type: 'ipc.response', requestId: msg.requestId, data: resp ?? { ok: false, error: 'daemon unreachable' } });
|
|
630
630
|
return;
|
|
@@ -796,7 +796,9 @@ export async function startWatchWebServer(opts = {}) {
|
|
|
796
796
|
res.end(JSON.stringify({ success: false, error: 'unauthorized' }));
|
|
797
797
|
return;
|
|
798
798
|
}
|
|
799
|
-
|
|
799
|
+
// Pairing grants daemon-owner management authority, just like local
|
|
800
|
+
// access. Authorize above; do not downgrade settings by network origin.
|
|
801
|
+
void handleBasicSettingsApi(req, res);
|
|
800
802
|
}
|
|
801
803
|
else if (req.method === 'GET' && (req.url || '').startsWith('/api/stats/')) {
|
|
802
804
|
// Stats API — 本地直连自动发 token(免鉴权),远程需鉴权
|
package/dist/sources/aid.js
CHANGED
|
@@ -65,28 +65,43 @@ function scanAidActivity() {
|
|
|
65
65
|
}
|
|
66
66
|
return result;
|
|
67
67
|
}
|
|
68
|
-
// evolcore
|
|
69
|
-
//
|
|
68
|
+
// evolcore 版本:使用 /system 的轻量 version 视图。daemon 进程级、整轮 watch 不变,
|
|
69
|
+
// 故缓存一次并合并 in-flight 请求,避免每秒重复发 IPC。
|
|
70
70
|
const versionCache = new Map();
|
|
71
|
+
const versionInFlight = new Map();
|
|
71
72
|
function authContextKey(params = {}) {
|
|
72
73
|
const auth = watchMenuRequestAuth(params);
|
|
73
74
|
return `${auth.localDirect ? 'local' : 'remote'}:${auth.actorAid || ''}`;
|
|
74
75
|
}
|
|
75
76
|
async function fetchVersion(socket, params = {}) {
|
|
76
|
-
const key = authContextKey(params)
|
|
77
|
+
const key = `${socket}\0${authContextKey(params)}`;
|
|
77
78
|
const cached = versionCache.get(key);
|
|
78
79
|
if (cached)
|
|
79
80
|
return cached;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
const pending = versionInFlight.get(key);
|
|
82
|
+
if (pending)
|
|
83
|
+
return pending;
|
|
84
|
+
const request = (async () => {
|
|
85
|
+
try {
|
|
86
|
+
const r = await ipcQuery(socket, {
|
|
87
|
+
type: 'menu.exec',
|
|
88
|
+
payload: { type: 'menu.query', id: 'aid-sys-q', name: 'system', args: { view: 'version' } },
|
|
89
|
+
auth: watchMenuRequestAuth(params),
|
|
90
|
+
}, 4000);
|
|
91
|
+
const v = r?.ok ? r.response?.data?.version : null;
|
|
92
|
+
if (v)
|
|
93
|
+
versionCache.set(key, v);
|
|
94
|
+
return v ?? null;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
versionInFlight.delete(key);
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
versionInFlight.set(key, request);
|
|
104
|
+
return request;
|
|
90
105
|
}
|
|
91
106
|
async function buildSnapshot(fallback = null, params = {}) {
|
|
92
107
|
const p = resolvePaths();
|
|
@@ -580,7 +580,7 @@ export function restartRequiredForChanges(file, changes) {
|
|
|
580
580
|
const prefixes = file === 'daemon' ? RESTART_DAEMON_PREFIXES : RESTART_DEFAULTS_PREFIXES;
|
|
581
581
|
return changes.map(change => change.path).filter(fieldPath => prefixes.some(prefix => fieldPath === prefix || fieldPath.startsWith(prefix)));
|
|
582
582
|
}
|
|
583
|
-
function redactSecrets(config, file, secrets, secretRefs
|
|
583
|
+
function redactSecrets(config, file, secrets, secretRefs) {
|
|
584
584
|
const redacted = clone(config);
|
|
585
585
|
if (file !== 'defaults' || !isRecord(redacted.baseagents))
|
|
586
586
|
return redacted;
|
|
@@ -600,26 +600,6 @@ function redactSecrets(config, file, secrets, secretRefs, options = {}) {
|
|
|
600
600
|
raw.apiKey = '******';
|
|
601
601
|
}
|
|
602
602
|
}
|
|
603
|
-
// Headers and query parameters are also common credential channels. They
|
|
604
|
-
// remain visible to local callers for editing, but remote snapshots must
|
|
605
|
-
// not disclose any map value, including values under custom key names.
|
|
606
|
-
if (options.redactConnectionMaps) {
|
|
607
|
-
for (const field of ['headers', 'queryParams']) {
|
|
608
|
-
if (!Object.prototype.hasOwnProperty.call(raw, field))
|
|
609
|
-
continue;
|
|
610
|
-
const fieldPath = `defaults.baseagents.${name}.${field}`;
|
|
611
|
-
if (isRecord(raw[field])) {
|
|
612
|
-
raw[field] = Object.fromEntries(Object.keys(raw[field]).map(key => {
|
|
613
|
-
secrets[`${fieldPath}.${key}`] = true;
|
|
614
|
-
return [key, '******'];
|
|
615
|
-
}));
|
|
616
|
-
}
|
|
617
|
-
else {
|
|
618
|
-
secrets[fieldPath] = true;
|
|
619
|
-
raw[field] = '******';
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
603
|
}
|
|
624
604
|
return redacted;
|
|
625
605
|
}
|
|
@@ -670,7 +650,7 @@ export function collectLogTypeStats(configured, logsDir = resolvePaths().logs) {
|
|
|
670
650
|
export function collectLogTypes(configured, logsDir = resolvePaths().logs) {
|
|
671
651
|
return collectLogTypeStats(configured, logsDir).types;
|
|
672
652
|
}
|
|
673
|
-
async function readSnapshot(
|
|
653
|
+
async function readSnapshot() {
|
|
674
654
|
const modules = await getConfigManager();
|
|
675
655
|
const daemonFile = modules.read(modules.ConfigTarget.Process, undefined, { cache: false, expand: false });
|
|
676
656
|
const defaultsFile = modules.read(modules.ConfigTarget.Defaults, undefined, { cache: false, expand: false });
|
|
@@ -682,8 +662,8 @@ async function readSnapshot(options = {}) {
|
|
|
682
662
|
const secretRefs = {};
|
|
683
663
|
const logTypeStats = collectLogTypeStats(rawDaemon?.watch?.logTypes);
|
|
684
664
|
return {
|
|
685
|
-
daemon: redactSecrets(rawDaemon, 'daemon', secrets, secretRefs
|
|
686
|
-
defaults: redactSecrets(rawDefaults, 'defaults', secrets, secretRefs
|
|
665
|
+
daemon: redactSecrets(rawDaemon, 'daemon', secrets, secretRefs),
|
|
666
|
+
defaults: redactSecrets(rawDefaults, 'defaults', secrets, secretRefs),
|
|
687
667
|
revisions: {
|
|
688
668
|
daemon: basicSettingsRevision(rawDaemon),
|
|
689
669
|
defaults: basicSettingsRevision(rawDefaults),
|
|
@@ -758,18 +738,15 @@ function basicSettingsPath(rawUrl) {
|
|
|
758
738
|
return rawUrl.split('?')[0].replace(/\/+$/, '') || '/';
|
|
759
739
|
}
|
|
760
740
|
}
|
|
761
|
-
export async function handleBasicSettingsApi(req, res
|
|
741
|
+
export async function handleBasicSettingsApi(req, res) {
|
|
762
742
|
try {
|
|
763
743
|
const url = new URL(req.url || '', 'http://localhost');
|
|
764
744
|
const pathname = basicSettingsPath(req.url || '');
|
|
765
745
|
if (req.method === 'GET' && pathname === '/api/basic-settings') {
|
|
766
|
-
sendJson(res, 200, { success: true, data: await readSnapshot(
|
|
746
|
+
sendJson(res, 200, { success: true, data: await readSnapshot() });
|
|
767
747
|
return;
|
|
768
748
|
}
|
|
769
749
|
if (req.method === 'GET' && pathname === '/api/basic-settings/secret') {
|
|
770
|
-
if (options.remoteAccess) {
|
|
771
|
-
throw Object.assign(new Error('Remote access cannot reveal configuration secrets'), { statusCode: 403 });
|
|
772
|
-
}
|
|
773
750
|
const file = url.searchParams.get('file') || 'defaults';
|
|
774
751
|
const fieldPath = url.searchParams.get('path') || '';
|
|
775
752
|
if (!requestedSecretPath(file, fieldPath)) {
|
|
@@ -785,9 +762,6 @@ export async function handleBasicSettingsApi(req, res, options = {}) {
|
|
|
785
762
|
return;
|
|
786
763
|
}
|
|
787
764
|
if (req.method === 'PATCH' && pathname === '/api/basic-settings') {
|
|
788
|
-
if (options.remoteAccess) {
|
|
789
|
-
throw Object.assign(new Error('Remote access cannot mutate basic settings'), { statusCode: 403 });
|
|
790
|
-
}
|
|
791
765
|
const body = await readJsonBody(req);
|
|
792
766
|
if (!isRecord(body) || (body.file !== 'daemon' && body.file !== 'defaults'))
|
|
793
767
|
throw new Error('file must be daemon or defaults');
|
|
@@ -822,7 +796,7 @@ export async function handleBasicSettingsApi(req, res, options = {}) {
|
|
|
822
796
|
changed: mutation.changed,
|
|
823
797
|
restartRequired: mutation.changed && restartPaths.length > 0,
|
|
824
798
|
restartPaths: mutation.changed ? restartPaths : [],
|
|
825
|
-
data: await readSnapshot(
|
|
799
|
+
data: await readSnapshot(),
|
|
826
800
|
});
|
|
827
801
|
return;
|
|
828
802
|
}
|
|
@@ -24,9 +24,11 @@ async function getParentModules() {
|
|
|
24
24
|
async function getCommandModules() {
|
|
25
25
|
const permissionPath = resolveParentDistModule('core', 'auth', 'operation-authorizer.js');
|
|
26
26
|
const auditPath = resolveParentDistModule('core', 'auth', 'authorization-audit.js');
|
|
27
|
+
const runtimePolicyPath = resolveParentDistModule('core', 'role', 'runtime-policy.js');
|
|
27
28
|
const permission = await import(toFileUrl(permissionPath));
|
|
28
29
|
const audit = await import(toFileUrl(auditPath));
|
|
29
|
-
|
|
30
|
+
const runtimePolicy = await import(toFileUrl(runtimePolicyPath));
|
|
31
|
+
return { permission, audit, runtimePolicy };
|
|
30
32
|
}
|
|
31
33
|
export const ROLE_ASSIGNMENT_POLICY = {
|
|
32
34
|
owner: {
|
|
@@ -38,15 +40,17 @@ export const ROLE_ASSIGNMENT_POLICY = {
|
|
|
38
40
|
revoke: 'user-roles',
|
|
39
41
|
},
|
|
40
42
|
};
|
|
41
|
-
function isManagementRoleName(role) {
|
|
42
|
-
return role === 'owner' || role === 'admin';
|
|
43
|
-
}
|
|
44
43
|
function isAssignableUserRole(role) {
|
|
45
|
-
|
|
44
|
+
// `admin` is a management role, but it is still a valid relation role.
|
|
45
|
+
// `owner` and `daemon-owner` remain reserved for process/Agent config and
|
|
46
|
+
// can never be assigned through a relation.
|
|
47
|
+
return typeof role === 'string' && !!role && role !== 'owner' && role !== 'daemon-owner';
|
|
46
48
|
}
|
|
47
49
|
export function canAssignRole(actorRole, targetRole) {
|
|
48
50
|
if (!isAssignableUserRole(targetRole))
|
|
49
51
|
return false;
|
|
52
|
+
if (targetRole === 'admin')
|
|
53
|
+
return actorRole === 'owner';
|
|
50
54
|
return actorRole === 'owner' || actorRole === 'admin';
|
|
51
55
|
}
|
|
52
56
|
export function canRevokeRole(actorRole, targetRole) {
|
|
@@ -54,6 +58,8 @@ export function canRevokeRole(actorRole, targetRole) {
|
|
|
54
58
|
return true;
|
|
55
59
|
if (actorRole !== 'admin' || !targetRole)
|
|
56
60
|
return false;
|
|
61
|
+
if (targetRole === 'admin')
|
|
62
|
+
return false;
|
|
57
63
|
return isAssignableUserRole(targetRole);
|
|
58
64
|
}
|
|
59
65
|
async function getAgentsFromIpc() {
|
|
@@ -74,7 +80,8 @@ function resolveEcwebActor(modules, aid, auth) {
|
|
|
74
80
|
const processOwners = Array.isArray(processConfig?.owners)
|
|
75
81
|
? processConfig.owners.map((value) => String(value || '').trim()).filter(Boolean)
|
|
76
82
|
: [];
|
|
77
|
-
const daemonOwner =
|
|
83
|
+
const daemonOwner = auth.daemonOwner === true || !!auth.localDirect
|
|
84
|
+
|| (!!auth.actorAid && processOwners.includes(auth.actorAid));
|
|
78
85
|
const actorRole = daemonOwner
|
|
79
86
|
? 'owner'
|
|
80
87
|
: auth.actorAid
|
|
@@ -89,11 +96,11 @@ function resolveEcwebActor(modules, aid, auth) {
|
|
|
89
96
|
return {
|
|
90
97
|
daemonOwner,
|
|
91
98
|
actorRole,
|
|
92
|
-
actorId: auth.localDirect ? 'local-direct' : auth.actorAid || undefined,
|
|
99
|
+
actorId: auth.localDirect ? 'local-direct' : auth.actorAid || (daemonOwner ? 'ecweb-remote' : undefined),
|
|
93
100
|
};
|
|
94
101
|
}
|
|
95
102
|
async function authorizeRoleAssignmentRead(modules, aid, auth) {
|
|
96
|
-
const { permission, audit } = await getCommandModules();
|
|
103
|
+
const { permission, audit, runtimePolicy } = await getCommandModules();
|
|
97
104
|
const actor = resolveEcwebActor(modules, aid, auth);
|
|
98
105
|
const decision = permission.authorizeCommand({
|
|
99
106
|
intent: {
|
|
@@ -118,6 +125,7 @@ async function authorizeRoleAssignmentRead(modules, aid, auth) {
|
|
|
118
125
|
actorId: actor.actorId,
|
|
119
126
|
selfAid: aid,
|
|
120
127
|
role: actor.actorRole,
|
|
128
|
+
permissionMode: runtimePolicy.resolveRuntimePermissionMode({ selfAid: aid, role: actor.actorRole }).effectiveValue,
|
|
121
129
|
processRole: actor.daemonOwner ? 'daemon-owner' : 'none',
|
|
122
130
|
isDaemonOwner: actor.daemonOwner,
|
|
123
131
|
decision: decision.allow ? 'allow' : 'deny',
|
|
@@ -696,11 +704,17 @@ async function buildSnapshot(auth = {}) {
|
|
|
696
704
|
if (!readDecision.allow)
|
|
697
705
|
continue;
|
|
698
706
|
const roleConfig = modules.roles.readRolesConfig(aid);
|
|
707
|
+
const availableRoles = Object.keys(roleConfig.roles || {});
|
|
708
|
+
// Management roles are resolved separately from user-role definitions.
|
|
709
|
+
// Include the assignable management role in the ECWeb snapshot so relation
|
|
710
|
+
// editors can offer it; owner remains reserved for config.json owners[].
|
|
711
|
+
if (roleExists(modules.roles, aid, 'admin'))
|
|
712
|
+
availableRoles.push('admin');
|
|
699
713
|
snapshotAgents.push({
|
|
700
714
|
aid,
|
|
701
715
|
displayName: agent.displayName ?? agent.personalName,
|
|
702
716
|
name: agent.name,
|
|
703
|
-
roles:
|
|
717
|
+
roles: [...new Set(availableRoles)].filter(role => isAssignableUserRole(role)),
|
|
704
718
|
defaultRoles: roleConfig.defaultRoles ?? { private: null, group: null },
|
|
705
719
|
assignments: listRelationRoleAssignments(modules.config, aid),
|
|
706
720
|
});
|
|
@@ -105,8 +105,8 @@ function isReadMethod(method) {
|
|
|
105
105
|
async function authorizeRolePolicyOperation(modules, processConfig, auth, aid, operation, args) {
|
|
106
106
|
const actor = auth.actorAid || '';
|
|
107
107
|
const processOwners = Array.isArray(processConfig?.owners) ? processConfig.owners.map(String) : [];
|
|
108
|
-
const syntheticOwner =
|
|
109
|
-
const actorId = auth.localDirect ? 'local-direct' : actor || undefined;
|
|
108
|
+
const syntheticOwner = isDaemonOwnerAuth(processConfig, auth);
|
|
109
|
+
const actorId = auth.localDirect ? 'local-direct' : actor || (syntheticOwner ? 'ecweb-remote' : undefined);
|
|
110
110
|
const subject = modules.buildAuthSubject({
|
|
111
111
|
selfAid: aid,
|
|
112
112
|
actorId,
|
|
@@ -116,6 +116,7 @@ async function authorizeRolePolicyOperation(modules, processConfig, auth, aid, o
|
|
|
116
116
|
chatType: 'private',
|
|
117
117
|
conversationId: actor || 'ecweb',
|
|
118
118
|
processOwners,
|
|
119
|
+
trustedProcessRole: syntheticOwner ? 'daemon-owner' : undefined,
|
|
119
120
|
...(syntheticOwner ? {
|
|
120
121
|
roleDetail: {
|
|
121
122
|
effectiveRole: 'owner',
|
|
@@ -123,8 +124,8 @@ async function authorizeRolePolicyOperation(modules, processConfig, auth, aid, o
|
|
|
123
124
|
isAuthenticated: true,
|
|
124
125
|
allowAccess: true,
|
|
125
126
|
roleExists: true,
|
|
126
|
-
//
|
|
127
|
-
//
|
|
127
|
+
// Keep the token's actor provenance separate from the management
|
|
128
|
+
// authority established by the ECWeb authentication gate.
|
|
128
129
|
actor: {
|
|
129
130
|
operatorId: actorId || 'ecweb',
|
|
130
131
|
operatorChannelKey: `ecweb#${aid}`,
|
|
@@ -155,7 +156,7 @@ async function authorizeRolePolicyOperation(modules, processConfig, auth, aid, o
|
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
158
|
function isDaemonOwnerAuth(processConfig, auth) {
|
|
158
|
-
if (auth.localDirect)
|
|
159
|
+
if (auth.daemonOwner === true || auth.localDirect)
|
|
159
160
|
return true;
|
|
160
161
|
const actor = typeof auth.actorAid === 'string' ? auth.actorAid.trim() : '';
|
|
161
162
|
const processOwners = Array.isArray(processConfig?.owners)
|
package/dist/sources/types.js
CHANGED
|
@@ -13,6 +13,7 @@ export function watchMenuAuth(params) {
|
|
|
13
13
|
return {
|
|
14
14
|
localDirect: auth?.localDirect === true,
|
|
15
15
|
actorAid: typeof auth?.actorAid === 'string' ? auth.actorAid : null,
|
|
16
|
+
daemonOwner: auth?.daemonOwner === true,
|
|
16
17
|
// This marker is injected by the ECWeb server, never read from browser
|
|
17
18
|
// request parameters. Authenticated ECWeb has daemon-owner authority.
|
|
18
19
|
canApprovePersistentFullAccess: auth?.canApprovePersistentFullAccess === true,
|
package/dist/static/app.js
CHANGED
|
@@ -9296,7 +9296,7 @@ function assignedRoleFor(scope, item) {
|
|
|
9296
9296
|
function assignableRolesForAgent(agentAid) {
|
|
9297
9297
|
const agent = (state.roles?.agents || []).find(item => item.aid === agentAid);
|
|
9298
9298
|
const roles = agent?.roles || Object.keys(state.roleDefinitions?.roles || {});
|
|
9299
|
-
return Array.from(new Set(roles)).filter(role => role !== 'owner'
|
|
9299
|
+
return Array.from(new Set(roles)).filter(role => role !== 'owner');
|
|
9300
9300
|
}
|
|
9301
9301
|
|
|
9302
9302
|
function memberMatchesSearch(member) {
|
package/dist/static/index.html
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
</nav>
|
|
51
51
|
<span style="flex:1"></span>
|
|
52
|
-
2026-09-
|
|
52
|
+
<span class="build-ts" style="font-size:10px;color:var(--dim)" data-i18n-title="common.buildTime" title="构建时间">2026-09-18 10:34</span>
|
|
53
53
|
<span id="conn-status" class="conn-status" data-i18n="status.connecting">连接中…</span>
|
|
54
54
|
<button id="lang-btn" class="theme-btn" data-i18n-title="language.buttonTitle" title="切换语言">🌐</button>
|
|
55
55
|
<button id="theme-btn" class="theme-btn" data-i18n-title="theme.buttonTitle" title="主题">🌗</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ec-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"description": "Web console and monitoring dashboard for EvolCore",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dist/"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc && node -e \"try{require('child_process').execFileSync('chmod',['+x','dist/index.js'])}catch{}\" && node -e \"const fs=require('fs'),path=require('path');fs.cpSync('src/static','dist/static',{recursive:true});const f='dist/static/index.html',n=new Date(),ts=n.getFullYear()+'-'+String(n.getMonth()+1).padStart(2,'0')+'-'+String(n.getDate()).padStart(2,'0')+' '+String(n.getHours()).padStart(2,'0')+':'+String(n.getMinutes()).padStart(2,'0');fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace(/(\\<span class=\\\"build-ts\\\"[^>]*\\>)[^<]+(\\<\\/span\\>)/,
|
|
13
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc && node -e \"try{require('child_process').execFileSync('chmod',['+x','dist/index.js'])}catch{}\" && node -e \"const fs=require('fs'),path=require('path');fs.cpSync('src/static','dist/static',{recursive:true});const f='dist/static/index.html',n=new Date(),ts=n.getFullYear()+'-'+String(n.getMonth()+1).padStart(2,'0')+'-'+String(n.getDate()).padStart(2,'0')+' '+String(n.getHours()).padStart(2,'0')+':'+String(n.getMinutes()).padStart(2,'0');fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace(/(\\<span class=\\\"build-ts\\\"[^>]*\\>)[^<]+(\\<\\/span\\>)/,(_match,open,close)=>open+ts+close))\"",
|
|
14
14
|
"dev": "node dev.mjs",
|
|
15
15
|
"prepublishOnly": "npm run build"
|
|
16
16
|
},
|