evolcore 0.0.2 → 0.0.4
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 +93 -778
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +198 -48
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +203 -8
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/aun/outbox.js +8 -0
- package/dist/channels/aun.js +241 -116
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +1005 -260
- package/dist/channels/feishu.js +184 -56
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +1132 -164
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +98 -43
- package/dist/cli/bench.js +2 -2
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +77 -214
- package/dist/cli/handoff-command.js +4 -3
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +242 -129
- package/dist/cli/init.js +70 -31
- package/dist/cli/model.js +2 -1
- package/dist/cli/net-check.js +2 -2
- package/dist/cli/queue-command.js +30 -6
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +5 -6
- package/dist/cli/restart-monitor.js +25 -1
- package/dist/cli/stats.js +6 -4
- package/dist/cli/trigger-command.js +146 -25
- package/dist/cli/version.js +6 -1
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +42 -17
- package/dist/config/builtin-roles.js +31 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +409 -29
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +622 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +185 -300
- package/dist/config/contact-operation-service.js +116 -0
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +136 -55
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-service.js +16 -19
- package/dist/config/role-store.js +16 -5
- package/dist/config/roles.js +10 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -4
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +16 -1
- package/dist/core/auth/operation-authorizer.js +10 -0
- package/dist/core/auth/operation-catalog.js +105 -1
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +113 -21
- package/dist/core/command/connect-menu.js +599 -0
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +382 -100
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/role-menu.js +128 -29
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +186 -49
- package/dist/core/daemon-file-cache.js +40 -6
- package/dist/core/event-catalog.js +99 -0
- package/dist/core/evolagent-registry.js +4 -40
- package/dist/core/evolagent.js +20 -9
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +20 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +670 -57
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +195 -10
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +642 -96
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +115 -38
- package/dist/core/protected-paths.js +38 -11
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +86 -8
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +285 -94
- package/dist/ipc.js +114 -8
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +126 -11
- package/dist/trigger/patch.js +12 -2
- package/dist/trigger/scheduler.js +444 -40
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/media-cache.js +4 -1
- package/dist/utils/model-prices.jsonl +6 -3
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +5 -3
- package/kits/docs/evolcore/agent.md +9 -1
- package/kits/docs/evolcore/aid.md +5 -2
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +61 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +15 -6
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +12 -6
- package/kits/docs/evolcore/response.md +16 -21
- package/kits/docs/evolcore/rpc.md +2 -0
- package/kits/docs/evolcore/stats.md +15 -2
- package/kits/docs/evolcore/storage.md +1 -0
- package/kits/docs/evolcore/trigger.md +42 -10
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +21 -7
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +47 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/commands.md +3 -1
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +7 -5
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/assets/brand/evolcore/README.md +0 -19
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +0 -14
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +0 -10
|
@@ -1,47 +1,77 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { agentConfig, agentContactConfig, agentRelationsDir } from '../paths.js';
|
|
5
|
+
import { formatPeerKey, parsePeerKey } from '../core/relation/peer-identity.js';
|
|
5
6
|
import { hasTrustedPrincipal } from '../core/auth/authenticated-actor.js';
|
|
6
7
|
import { ConfigTarget, read, write } from './config-manager.js';
|
|
7
8
|
import { isManagementRole } from './builtin-roles.js';
|
|
8
|
-
import { normalizeDefaultRole, roleExists
|
|
9
|
+
import { getRoleDefinition, getRoleRank, normalizeDefaultRole, roleExists } from './roles.js';
|
|
9
10
|
import { getStoredRoleRegistry, roleRegistryExists } from './role-store.js';
|
|
10
11
|
import { resolvePrincipal } from './contact-book.js';
|
|
11
12
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
13
|
+
import { isExplicitGroupId } from '../aun/group-identity.js';
|
|
12
14
|
export function isAuthenticated(userId) {
|
|
13
15
|
return isValidAid(String(userId || '').trim());
|
|
14
16
|
}
|
|
15
17
|
export function listStaticAgentOwners(aid, opts = {}) {
|
|
16
|
-
return
|
|
18
|
+
return listAgentOwners(aid, opts);
|
|
17
19
|
}
|
|
20
|
+
/** Direct AUN relations with role=admin are the addressable Agent admin set. */
|
|
18
21
|
export function listStaticAgentAdmins(aid, opts = {}) {
|
|
19
|
-
|
|
22
|
+
if (!aid)
|
|
23
|
+
return [];
|
|
24
|
+
const relations = agentRelationsDir(aid);
|
|
25
|
+
if (!fs.existsSync(relations))
|
|
26
|
+
return [];
|
|
27
|
+
const admins = new Set();
|
|
28
|
+
for (const entry of fs.readdirSync(relations, { withFileTypes: true })) {
|
|
29
|
+
if (!entry.isDirectory() || entry.name.startsWith('_'))
|
|
30
|
+
continue;
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = parsePeerKey(entry.name);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (parsed.channelType !== 'aun'
|
|
39
|
+
|| !isValidAid(parsed.channelId)
|
|
40
|
+
|| isExplicitGroupId(parsed.channelId))
|
|
41
|
+
continue;
|
|
42
|
+
const config = read(ConfigTarget.Relation, { self: aid, peerKey: entry.name }, { cache: opts.fresh !== true });
|
|
43
|
+
if (config?.targetKind === 'group')
|
|
44
|
+
continue;
|
|
45
|
+
if (config?.role === 'admin')
|
|
46
|
+
admins.add(parsed.channelId);
|
|
47
|
+
}
|
|
48
|
+
return [...admins].sort();
|
|
20
49
|
}
|
|
21
50
|
export function getFirstStaticAgentOwner(aid) {
|
|
22
51
|
return listStaticAgentOwners(aid)[0];
|
|
23
52
|
}
|
|
24
|
-
/** Revision for security-sensitive
|
|
53
|
+
/** Revision for security-sensitive owner/contact/relation authorization inputs. */
|
|
25
54
|
export function authorizationConfigRevision(aid) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
55
|
+
const fingerprints = [fileRevision(agentConfig(aid)), fileRevision(agentContactConfig(aid))];
|
|
56
|
+
const relations = agentRelationsDir(aid);
|
|
57
|
+
try {
|
|
58
|
+
for (const entry of fs.readdirSync(relations, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
59
|
+
if (!entry.isDirectory() || entry.name.startsWith('_'))
|
|
60
|
+
continue;
|
|
61
|
+
fingerprints.push(entry.name, fileRevision(path.join(relations, entry.name, 'config.json')));
|
|
33
62
|
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
fileRevision(agentContactConfig(aid)),
|
|
38
|
-
])).digest('hex');
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
return crypto.createHash('sha256').update(JSON.stringify(fingerprints)).digest('hex');
|
|
39
66
|
}
|
|
40
67
|
export function isStaticAgentOwner(aid, actorId) {
|
|
41
68
|
return !!aid && !!actorId && listStaticAgentOwners(aid).includes(actorId);
|
|
42
69
|
}
|
|
43
70
|
export function isStaticAgentAdmin(aid, actorId) {
|
|
44
|
-
|
|
71
|
+
if (!aid || !isValidAid(actorId) || isExplicitGroupId(actorId))
|
|
72
|
+
return false;
|
|
73
|
+
const config = read(ConfigTarget.Relation, { self: aid, peerKey: formatPeerKey('aun', actorId) }, { cache: true });
|
|
74
|
+
return config?.targetKind !== 'group' && config?.role === 'admin';
|
|
45
75
|
}
|
|
46
76
|
export function addStaticAgentOwner(aid, ownerAid) {
|
|
47
77
|
if (!aid || !ownerAid)
|
|
@@ -56,32 +86,46 @@ export function hasStaticAgentOwner(aid) {
|
|
|
56
86
|
}
|
|
57
87
|
export function resolvePeerRoleDetail(ctx) {
|
|
58
88
|
const operatorId = String(ctx.actorId || '').trim();
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const principal = resolvePrincipal(ctx.selfAid,
|
|
89
|
+
const channelType = String(ctx.channelType || '').trim().toLowerCase();
|
|
90
|
+
const channelKey = String(ctx.channelKey || '').trim();
|
|
91
|
+
const principal = resolvePrincipal(ctx.selfAid, channelType, operatorId, channelKey || undefined);
|
|
62
92
|
const actor = {
|
|
63
93
|
operatorId,
|
|
64
|
-
operatorChannelKey:
|
|
65
|
-
operatorChannelType,
|
|
94
|
+
operatorChannelKey: channelKey || channelType,
|
|
95
|
+
operatorChannelType: channelType,
|
|
66
96
|
...principal,
|
|
67
97
|
};
|
|
68
|
-
const
|
|
69
|
-
if (isStaticAgentOwner(ctx.selfAid,
|
|
70
|
-
|
|
98
|
+
const candidates = [];
|
|
99
|
+
if (hasTrustedPrincipal(actor) && isStaticAgentOwner(ctx.selfAid, actor.principalId)) {
|
|
100
|
+
candidates.push({ role: 'owner', source: 'agent-owner', sourcePriority: 100 });
|
|
71
101
|
}
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
const actorRole = resolveActorDirectRole(ctx.selfAid, actor, channelType, operatorId);
|
|
103
|
+
if (ctx.chatType === 'private') {
|
|
104
|
+
if (actorRole)
|
|
105
|
+
candidates.push({ role: actorRole, source: 'relation-actor', sourcePriority: 80 });
|
|
106
|
+
const defaultRole = normalizeDefaultRole(readAgentDefaultRole(ctx.selfAid, 'private'), ctx.selfAid);
|
|
107
|
+
if (defaultRole)
|
|
108
|
+
candidates.push({ role: defaultRole, source: 'agent-default', sourcePriority: 40 });
|
|
74
109
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
else {
|
|
111
|
+
if (actorRole)
|
|
112
|
+
candidates.push({ role: actorRole, source: 'relation-actor', sourcePriority: 80 });
|
|
113
|
+
const groupRole = normalizeRelationRole(readDirectRelationRole(ctx.selfAid, channelType, String(ctx.conversationId || '').trim(), 'group'), ctx.selfAid);
|
|
114
|
+
if (groupRole) {
|
|
115
|
+
candidates.push({ role: groupRole, source: 'relation-peer', sourcePriority: 60 });
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const defaultRole = normalizeDefaultRole(readAgentDefaultRole(ctx.selfAid, 'group'), ctx.selfAid);
|
|
119
|
+
if (defaultRole)
|
|
120
|
+
candidates.push({ role: defaultRole, source: 'agent-default', sourcePriority: 40 });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const winner = selectHighestRank(candidates, ctx.selfAid);
|
|
124
|
+
if (winner)
|
|
125
|
+
return resultFor(winner.role, winner.source, actor, ctx.selfAid);
|
|
126
|
+
return roleRegistryExists(ctx.selfAid) && roleExists('visitor', ctx.selfAid)
|
|
127
|
+
? resultFor('visitor', 'builtin-fallback', actor, ctx.selfAid)
|
|
128
|
+
: resultFor(null, 'none', actor, ctx.selfAid);
|
|
85
129
|
}
|
|
86
130
|
export function roleToSessionIdentity(role) {
|
|
87
131
|
return { role: role ?? 'none', mode: 'interactive' };
|
|
@@ -89,34 +133,56 @@ export function roleToSessionIdentity(role) {
|
|
|
89
133
|
export function checkRoleAccess(role, selfAid) {
|
|
90
134
|
if (!role)
|
|
91
135
|
return false;
|
|
92
|
-
|
|
136
|
+
// Owner access is an invariant. Admin access is role policy, just like the
|
|
137
|
+
// user roles, so a local admin definition may explicitly disable it.
|
|
138
|
+
if (role === 'owner')
|
|
93
139
|
return true;
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
140
|
+
const definition = getRoleDefinition(role, selfAid);
|
|
141
|
+
return !!definition && (definition.allowAccess ?? true);
|
|
142
|
+
}
|
|
143
|
+
function selectHighestRank(candidates, selfAid) {
|
|
144
|
+
let winner = null;
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
if (!normalizeRelationRole(candidate.role, selfAid) && candidate.role !== 'owner')
|
|
147
|
+
continue;
|
|
148
|
+
if (!winner) {
|
|
149
|
+
winner = candidate;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const rank = getRoleRank(candidate.role, selfAid);
|
|
153
|
+
const winnerRank = getRoleRank(winner.role, selfAid);
|
|
154
|
+
if (rank > winnerRank || (rank === winnerRank && candidate.sourcePriority > winner.sourcePriority)) {
|
|
155
|
+
winner = candidate;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return winner;
|
|
159
|
+
}
|
|
160
|
+
function resolveActorDirectRole(selfAid, actor, channelType, actorId) {
|
|
161
|
+
const role = hasTrustedPrincipal(actor)
|
|
162
|
+
? readDirectRelationRole(selfAid, 'aun', actor.principalId, 'private')
|
|
163
|
+
: actor.principalStatus === 'unmapped'
|
|
164
|
+
? readDirectRelationRole(selfAid, channelType, actorId, 'private')
|
|
165
|
+
: null;
|
|
166
|
+
return normalizeRelationRole(role, selfAid);
|
|
98
167
|
}
|
|
99
|
-
function resultFor(role, source, actor, selfAid
|
|
168
|
+
function resultFor(role, source, actor, selfAid) {
|
|
100
169
|
const exists = role ? (isManagementRole(role) || roleExists(role, selfAid)) : false;
|
|
101
170
|
const roleDef = role ? getRoleDefinition(role, selfAid) : null;
|
|
102
171
|
return {
|
|
103
172
|
effectiveRole: exists ? role : null,
|
|
104
173
|
source: exists ? source : 'none',
|
|
105
174
|
isAuthenticated: hasTrustedPrincipal(actor),
|
|
106
|
-
allowAccess:
|
|
175
|
+
allowAccess: !!(roleDef && (roleDef.allowAccess ?? true)),
|
|
107
176
|
roleExists: exists,
|
|
108
177
|
actor,
|
|
109
178
|
};
|
|
110
179
|
}
|
|
111
|
-
function
|
|
180
|
+
function listAgentOwners(aid, opts = {}) {
|
|
112
181
|
if (!aid)
|
|
113
182
|
return [];
|
|
114
183
|
try {
|
|
115
184
|
const config = read(ConfigTarget.Agent, { self: aid }, { cache: opts.fresh !== true });
|
|
116
|
-
|
|
117
|
-
if (!Array.isArray(values))
|
|
118
|
-
return [];
|
|
119
|
-
return Array.from(new Set(values.map(value => String(value || '').trim()).filter(Boolean)));
|
|
185
|
+
return Array.from(new Set((config?.owners ?? []).map(value => String(value || '').trim()).filter(Boolean)));
|
|
120
186
|
}
|
|
121
187
|
catch {
|
|
122
188
|
return [];
|
|
@@ -125,15 +191,30 @@ function listAgentAids(aid, field, opts = {}) {
|
|
|
125
191
|
function readAgentDefaultRole(aid, chatType) {
|
|
126
192
|
return getStoredRoleRegistry(aid).defaultRoles[chatType] ?? null;
|
|
127
193
|
}
|
|
128
|
-
function
|
|
194
|
+
function readDirectRelationRole(selfAid, channelType, channelId, expectedTargetKind) {
|
|
129
195
|
if (!selfAid || !channelType || !channelId)
|
|
130
196
|
return undefined;
|
|
197
|
+
if (expectedTargetKind === 'private' && channelType === 'aun' && isExplicitGroupId(channelId))
|
|
198
|
+
return undefined;
|
|
131
199
|
const peerKey = formatPeerKey(channelType, channelId);
|
|
132
200
|
const config = read(ConfigTarget.Relation, { self: selfAid, peerKey }, { cache: true });
|
|
201
|
+
if (expectedTargetKind && config?.targetKind && config.targetKind !== expectedTargetKind)
|
|
202
|
+
return undefined;
|
|
133
203
|
return config?.role;
|
|
134
204
|
}
|
|
135
|
-
function
|
|
136
|
-
if (typeof value !== 'string' || !
|
|
205
|
+
function normalizeRelationRole(value, selfAid) {
|
|
206
|
+
if (typeof value !== 'string' || !value || value === 'owner')
|
|
137
207
|
return null;
|
|
138
|
-
|
|
208
|
+
if (value === 'admin')
|
|
209
|
+
return getRoleDefinition('admin', selfAid) ? 'admin' : null;
|
|
210
|
+
return roleExists(value, selfAid) ? value : null;
|
|
211
|
+
}
|
|
212
|
+
function fileRevision(file) {
|
|
213
|
+
try {
|
|
214
|
+
const stat = fs.statSync(file);
|
|
215
|
+
return [stat.size, stat.mtimeMs].join(':');
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return 'missing';
|
|
219
|
+
}
|
|
139
220
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const ROLE_RANKS = Object.freeze({
|
|
2
|
+
owner: 900,
|
|
3
|
+
admin: 700,
|
|
4
|
+
custom: 500,
|
|
5
|
+
member: 300,
|
|
6
|
+
visitor: 100,
|
|
7
|
+
});
|
|
8
|
+
export function expectedRoleRank(role) {
|
|
9
|
+
if (role === 'owner')
|
|
10
|
+
return ROLE_RANKS.owner;
|
|
11
|
+
if (role === 'admin')
|
|
12
|
+
return ROLE_RANKS.admin;
|
|
13
|
+
if (role === 'member')
|
|
14
|
+
return ROLE_RANKS.member;
|
|
15
|
+
if (role === 'visitor')
|
|
16
|
+
return ROLE_RANKS.visitor;
|
|
17
|
+
return ROLE_RANKS.custom;
|
|
18
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
|
|
4
|
-
import {
|
|
4
|
+
import { agentRelationConfig, agentRelationsDir, agentRoleConfig, agentRolesIndex } from '../paths.js';
|
|
5
5
|
import { loadSchema } from './schema-registry.js';
|
|
6
6
|
import { clearRoleStoreCache, getStoredRoleRegistry, isRoleName, readRoleStore, roleRegistryExists, } from './role-store.js';
|
|
7
|
-
import { isManagementRole } from './builtin-roles.js';
|
|
7
|
+
import { expectedRoleRank, isManagementRole } from './builtin-roles.js';
|
|
8
|
+
import { readBuiltinRoleTemplate } from './builtin-role-templates.js';
|
|
8
9
|
import { validateRoleConfig } from './role-schema.js';
|
|
9
10
|
export class RoleServiceError extends Error {
|
|
10
11
|
code;
|
|
@@ -42,7 +43,7 @@ export function writeRoleRegistry(selfAid, value) {
|
|
|
42
43
|
export function writeRoleDefinition(selfAid, roleId, value) {
|
|
43
44
|
if (!isRoleName(roleId))
|
|
44
45
|
throw new RoleServiceError('INVALID_ROLE_ID', `Invalid role id: ${roleId}`);
|
|
45
|
-
const normalized = { ...value, $schema_version: 1 };
|
|
46
|
+
const normalized = { ...value, rank: expectedRoleRank(roleId), $schema_version: 1 };
|
|
46
47
|
validateRoleDefinition(normalized);
|
|
47
48
|
const registry = readRoleRegistry(selfAid);
|
|
48
49
|
if (!registry.roles.includes(roleId)) {
|
|
@@ -92,7 +93,7 @@ export function listRoleDiagnostics(selfAid) {
|
|
|
92
93
|
return readRoleStore(selfAid).diagnostics;
|
|
93
94
|
}
|
|
94
95
|
function writeRoleDefinitionFile(selfAid, roleId, value) {
|
|
95
|
-
const normalized = { ...value, $schema_version: 1 };
|
|
96
|
+
const normalized = { ...value, rank: expectedRoleRank(roleId), $schema_version: 1 };
|
|
96
97
|
validateRoleDefinition(normalized);
|
|
97
98
|
const file = agentRoleConfig(selfAid, roleId);
|
|
98
99
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
@@ -108,7 +109,7 @@ function validateRegistry(selfAid, value) {
|
|
|
108
109
|
throw new RoleServiceError('VISITOR_REQUIRED', 'visitor must remain registered');
|
|
109
110
|
for (const role of ['private', 'group']) {
|
|
110
111
|
const selected = registry.defaultRoles[role];
|
|
111
|
-
if (selected !== null && (
|
|
112
|
+
if (selected !== null && (selected === 'owner' || !registry.roles.includes(selected))) {
|
|
112
113
|
throw new RoleServiceError('INVALID_DEFAULT_ROLE', `defaultRoles.${role} must reference an assignable registered role`);
|
|
113
114
|
}
|
|
114
115
|
if (selected !== null && !hasValidRoleDefinition(selfAid, selected)) {
|
|
@@ -117,18 +118,14 @@ function validateRegistry(selfAid, value) {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
function hasValidRoleDefinition(selfAid, roleId) {
|
|
120
|
-
|
|
121
|
-
agentRoleConfig(selfAid, roleId)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const value = atomicReadJson(file);
|
|
126
|
-
if (value !== null && validateRoleConfig(value).length === 0)
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
catch { }
|
|
121
|
+
try {
|
|
122
|
+
const value = atomicReadJson(agentRoleConfig(selfAid, roleId));
|
|
123
|
+
if (value !== null && validateRoleConfig(value).length === 0
|
|
124
|
+
&& (value.rank === undefined || value.rank === expectedRoleRank(roleId)))
|
|
125
|
+
return true;
|
|
130
126
|
}
|
|
131
|
-
|
|
127
|
+
catch { }
|
|
128
|
+
return !!readBuiltinRoleTemplate(roleId).definition;
|
|
132
129
|
}
|
|
133
130
|
function validateRoleDefinition(value) {
|
|
134
131
|
const issues = validateRoleConfig(value);
|
|
@@ -142,13 +139,13 @@ function findRoleReferences(selfAid, roleId, registry) {
|
|
|
142
139
|
references.push('defaultRoles.private');
|
|
143
140
|
if (registry.defaultRoles.group === roleId)
|
|
144
141
|
references.push('defaultRoles.group');
|
|
145
|
-
const relations =
|
|
142
|
+
const relations = agentRelationsDir(selfAid);
|
|
146
143
|
if (!fs.existsSync(relations))
|
|
147
144
|
return references;
|
|
148
145
|
for (const entry of fs.readdirSync(relations, { withFileTypes: true })) {
|
|
149
|
-
if (!entry.isDirectory())
|
|
146
|
+
if (!entry.isDirectory() || entry.name.startsWith('_'))
|
|
150
147
|
continue;
|
|
151
|
-
const config = atomicReadJson(
|
|
148
|
+
const config = atomicReadJson(agentRelationConfig(selfAid, entry.name));
|
|
152
149
|
if (config?.role === roleId)
|
|
153
150
|
references.push(`relations/${entry.name}/role`);
|
|
154
151
|
}
|
|
@@ -2,12 +2,11 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { atomicReadJson } from '../utils/atomic-write.js';
|
|
4
4
|
import { agentRoleConfig, agentRolesDir, agentRolesIndex, kitsRoleTemplatesDir, resolvePaths, } from '../paths.js';
|
|
5
|
-
import { applyManagementRoleCeiling, getManagementRoleDefinition } from './builtin-roles.js';
|
|
5
|
+
import { applyManagementRoleCeiling, expectedRoleRank, getManagementRoleDefinition } from './builtin-roles.js';
|
|
6
6
|
import { readBuiltinRoleTemplate } from './builtin-role-templates.js';
|
|
7
7
|
import { loadSchema } from './schema-registry.js';
|
|
8
8
|
import { validateRoleConfig } from './role-schema.js';
|
|
9
9
|
const ROLE_NAME_RE = /^[a-z0-9_-]+$/;
|
|
10
|
-
const RESERVED_ROLES = new Set(['owner', 'admin']);
|
|
11
10
|
const BUILTIN_ROLES = ['owner', 'admin', 'member', 'visitor'];
|
|
12
11
|
const cache = new Map();
|
|
13
12
|
export function clearRoleStoreCache(selfAid) {
|
|
@@ -79,9 +78,11 @@ export function getStoredRoleRegistry(selfAid) {
|
|
|
79
78
|
return readRoleStore(selfAid).registry;
|
|
80
79
|
}
|
|
81
80
|
export function roleIsAssignable(selfAid, roleId) {
|
|
82
|
-
if (!roleId || !isRoleName(roleId) ||
|
|
81
|
+
if (!roleId || !isRoleName(roleId) || roleId === 'owner')
|
|
83
82
|
return false;
|
|
84
83
|
const snapshot = readRoleStore(selfAid);
|
|
84
|
+
if (roleId === 'admin')
|
|
85
|
+
return snapshot.registry.roles.includes(roleId) && !!getStoredRoleDefinition(selfAid, roleId);
|
|
85
86
|
return snapshot.registry.roles.includes(roleId) && !!snapshot.definitions[roleId];
|
|
86
87
|
}
|
|
87
88
|
function readRegistry(selfAid, diagnostics) {
|
|
@@ -106,7 +107,7 @@ function readRegistry(selfAid, diagnostics) {
|
|
|
106
107
|
const defaultRoles = { ...value.defaultRoles };
|
|
107
108
|
for (const scene of ['private', 'group']) {
|
|
108
109
|
const selected = defaultRoles[scene];
|
|
109
|
-
if (selected === null || (roles.includes(selected) &&
|
|
110
|
+
if (selected === null || (roles.includes(selected) && selected !== 'owner'))
|
|
110
111
|
continue;
|
|
111
112
|
diagnostics.push({
|
|
112
113
|
code: 'INVALID_ROLE_ASSIGNMENT',
|
|
@@ -156,7 +157,17 @@ function readDefinition(selfAid, roleId, diagnostics) {
|
|
|
156
157
|
const issues = validateRoleConfig(raw);
|
|
157
158
|
if (issues.length === 0) {
|
|
158
159
|
const { $schema_version: _schemaVersion, ...definition } = raw;
|
|
159
|
-
|
|
160
|
+
const expectedRank = expectedRoleRank(roleId);
|
|
161
|
+
if (definition.rank !== undefined && definition.rank !== expectedRank) {
|
|
162
|
+
diagnostics.push({
|
|
163
|
+
code: 'INVALID_ROLE_FILE',
|
|
164
|
+
roleId,
|
|
165
|
+
file: path.relative(resolvePaths().root, file),
|
|
166
|
+
message: `Role definition ${roleId} rank must be ${expectedRank}`,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
return { ...definition, rank: expectedRank };
|
|
160
171
|
}
|
|
161
172
|
diagnostics.push({
|
|
162
173
|
code: file === local ? 'INVALID_ROLE_FILE' : 'INVALID_ROLE_FILE',
|
package/dist/config/roles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getBuiltinRolesConfig, getManagementRoleDefinition, isManagementRole, isReservedRoleName, } from './builtin-roles.js';
|
|
1
|
+
import { getBuiltinRolesConfig, getManagementRoleDefinition, expectedRoleRank, isManagementRole, isReservedRoleName, } from './builtin-roles.js';
|
|
2
2
|
import { clearRoleStoreCache, getStoredRoleDefinition, readRoleStore, roleIsAssignable, } from './role-store.js';
|
|
3
3
|
const ROLE_NAME_RE = /^[a-z0-9_-]+$/;
|
|
4
4
|
export { getBuiltinRolesConfig };
|
|
@@ -51,5 +51,14 @@ export function normalizeDefaultRole(value, selfAid) {
|
|
|
51
51
|
return null;
|
|
52
52
|
if (typeof value !== 'string')
|
|
53
53
|
return null;
|
|
54
|
+
if (value === 'owner')
|
|
55
|
+
return null;
|
|
56
|
+
if (value === 'admin')
|
|
57
|
+
return getRoleDefinition(value, selfAid) ? value : null;
|
|
54
58
|
return roleExists(value, selfAid) ? value : null;
|
|
55
59
|
}
|
|
60
|
+
export function getRoleRank(role, selfAid) {
|
|
61
|
+
if (!role)
|
|
62
|
+
return 0;
|
|
63
|
+
return getRoleDefinition(role, selfAid)?.rank ?? expectedRoleRank(role);
|
|
64
|
+
}
|
|
@@ -14,6 +14,7 @@ import fs from 'fs';
|
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import Ajv from 'ajv';
|
|
16
16
|
import { kitsSchemasDir } from '../paths.js';
|
|
17
|
+
import { createErrorCapturingReader, fileCache } from '../core/daemon-file-cache.js';
|
|
17
18
|
let _ajv = null;
|
|
18
19
|
function ajv() {
|
|
19
20
|
if (!_ajv) {
|
|
@@ -21,13 +22,23 @@ function ajv() {
|
|
|
21
22
|
}
|
|
22
23
|
return _ajv;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* schema 随包发布,运行期不查盘 → fileCache 的 on-reload 策略,归入 'kits' 组
|
|
27
|
+
* (与 manifest/fragment 同组,一次 invalidateGroup('kits') 全刷)。
|
|
28
|
+
* 刷新时机只有三个:进程重启、`ec agent reload`、resync——后两者在调 registry.reload
|
|
29
|
+
* 之前先 invalidateKitCache()。带外改 schema 文件而不做上述任一动作,进程不会看见。
|
|
30
|
+
*/
|
|
31
|
+
const SCHEMA_CACHE_OPTS = { policy: 'on-reload', group: 'kits' };
|
|
25
32
|
export function loadMeta() {
|
|
26
|
-
if (_meta)
|
|
27
|
-
return _meta;
|
|
28
33
|
const p = path.join(kitsSchemasDir(), '_meta.json');
|
|
29
|
-
|
|
30
|
-
return
|
|
34
|
+
const reader = createErrorCapturingReader();
|
|
35
|
+
return fileCache.get(p, (raw) => {
|
|
36
|
+
// errno 文案自带路径,不再重复拼 p;原始异常挂 cause,保住 code/path/errno
|
|
37
|
+
if (raw === null) {
|
|
38
|
+
throw new Error(`[schema] cannot read meta: ${reader.lastError()}`, { cause: reader.lastErrorCause() });
|
|
39
|
+
}
|
|
40
|
+
return JSON.parse(raw);
|
|
41
|
+
}, { ...SCHEMA_CACHE_OPTS, read: reader.read });
|
|
31
42
|
}
|
|
32
43
|
/** 某 schema 的当前版本号(来自 _meta.json)。 */
|
|
33
44
|
export function currentVersion(name) {
|
|
@@ -84,7 +95,6 @@ export function readRawSchema(name, version) {
|
|
|
84
95
|
}
|
|
85
96
|
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
86
97
|
}
|
|
87
|
-
const _cache = new Map();
|
|
88
98
|
function schemaFilePath(name, version) {
|
|
89
99
|
return path.join(kitsSchemasDir(), `${name}.schema.${version}.json`);
|
|
90
100
|
}
|
|
@@ -117,22 +127,35 @@ function inferMerge(spec) {
|
|
|
117
127
|
/** 加载指定版本的 schema entry(带 ajv 编译 + 字段表)。 */
|
|
118
128
|
export function loadSchema(name, version) {
|
|
119
129
|
const ver = version ?? currentVersion(name);
|
|
120
|
-
const key = `${name}@${ver}`;
|
|
121
|
-
const cached = _cache.get(key);
|
|
122
|
-
if (cached)
|
|
123
|
-
return cached;
|
|
124
130
|
const file = schemaFilePath(name, ver);
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
const reader = createErrorCapturingReader();
|
|
132
|
+
return fileCache.get(file, (rawText) => {
|
|
133
|
+
// errno 文案自带路径,不再重复拼 file;原始异常挂 cause,保住 code/path/errno
|
|
134
|
+
if (rawText === null) {
|
|
135
|
+
throw new Error(`[schema] cannot read schema file: ${reader.lastError()}`, { cause: reader.lastErrorCause() });
|
|
136
|
+
}
|
|
137
|
+
const raw = JSON.parse(rawText);
|
|
138
|
+
return {
|
|
139
|
+
logicalName: name,
|
|
140
|
+
version: ver,
|
|
141
|
+
scope: raw['x-scope'] || name,
|
|
142
|
+
raw,
|
|
143
|
+
validate: compileSchema(raw),
|
|
144
|
+
fields: extractFields(raw),
|
|
145
|
+
};
|
|
146
|
+
}, { ...SCHEMA_CACHE_OPTS, read: reader.read });
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 每个 schema 文件都带 `$id`,而 ajv 对同一实例重复注册同名 `$id` 会抛
|
|
150
|
+
* "schema with key or id ... already exists"。缓存失效后会重新 JSON.parse 出
|
|
151
|
+
* 一个新对象(ajv 的对象级缓存认不出),因此编译前先按 `$id` 撤销旧注册,
|
|
152
|
+
* 让 reload → 重新编译这条路径可重复执行。
|
|
153
|
+
*/
|
|
154
|
+
function compileSchema(raw) {
|
|
155
|
+
const id = raw?.$id;
|
|
156
|
+
if (typeof id === 'string')
|
|
157
|
+
ajv().removeSchema(id);
|
|
158
|
+
return ajv().compile(raw);
|
|
136
159
|
}
|
|
137
160
|
/** extra_backup 不得指向 .env(构建期校验)——遍历声明里的 pattern。 */
|
|
138
161
|
export function assertExtraBackupNotEnv(extraBackup) {
|
|
@@ -146,9 +169,11 @@ export function assertExtraBackupNotEnv(extraBackup) {
|
|
|
146
169
|
}
|
|
147
170
|
}
|
|
148
171
|
}
|
|
149
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* 测试用:清空缓存。schema 与 _meta 都存在 fileCache 的 'kits' 组,
|
|
174
|
+
* 连同 ajv 实例一起重建(丢弃已注册的 $id,避免跨用例串味)。
|
|
175
|
+
*/
|
|
150
176
|
export function _resetSchemaCache() {
|
|
151
|
-
|
|
152
|
-
_meta = null;
|
|
177
|
+
fileCache.invalidateGroup('kits');
|
|
153
178
|
_ajv = null;
|
|
154
179
|
}
|
package/dist/config-store.js
CHANGED
|
@@ -25,10 +25,11 @@ import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
|
25
25
|
import { ConfigTarget, read as cfgRead, write as cfgWrite } from './config/config-manager.js';
|
|
26
26
|
import { expandVars, buildEnvResolver } from './config/merge.js';
|
|
27
27
|
import { logger } from './utils/logger.js';
|
|
28
|
+
import { parseStableSemver } from './utils/stable-semver.js';
|
|
28
29
|
/** 读 {root}/daemon.json。文件不存在返回 {},不报错。 */
|
|
29
30
|
export function loadDaemonConfig() {
|
|
30
31
|
const raw = atomicReadJson(resolvePaths().daemonConfig);
|
|
31
|
-
return raw ?? {};
|
|
32
|
+
return validateDaemonConfig(raw ?? {});
|
|
32
33
|
}
|
|
33
34
|
let eckSnapshotsConfigCache;
|
|
34
35
|
/** Parse the process-level snapshot gate once for the current daemon lifecycle. */
|
|
@@ -49,7 +50,18 @@ export function isEckSnapshotsEnabled() {
|
|
|
49
50
|
}
|
|
50
51
|
/** 原子写入 {root}/daemon.json。调用方负责传完整对象(含要保留的字段)。 */
|
|
51
52
|
export function saveDaemonConfig(value) {
|
|
52
|
-
atomicWriteJson(resolvePaths().daemonConfig, value);
|
|
53
|
+
atomicWriteJson(resolvePaths().daemonConfig, validateDaemonConfig(value));
|
|
54
|
+
}
|
|
55
|
+
function validateDaemonConfig(value) {
|
|
56
|
+
const minEvolVersion = value.aun?.minEvolVersion;
|
|
57
|
+
if (minEvolVersion !== undefined && !parseStableSemver(minEvolVersion)) {
|
|
58
|
+
throw new Error('daemon.json.aun.minEvolVersion must use stable X.Y.Z format');
|
|
59
|
+
}
|
|
60
|
+
const menuTokenRequired = value.aun?.menuTokenRequired;
|
|
61
|
+
if (menuTokenRequired !== undefined && typeof menuTokenRequired !== 'boolean') {
|
|
62
|
+
throw new Error('daemon.json.aun.menuTokenRequired must be a boolean');
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
53
65
|
}
|
|
54
66
|
const SUPPORTED_CHANNEL_TYPES = new Set([
|
|
55
67
|
'aun', 'feishu', 'wechat', 'dingtalk', 'qqbot', 'wecom',
|
|
@@ -478,7 +490,6 @@ export async function migrateProject(oldPath, newPath) {
|
|
|
478
490
|
const active = readJsonFile(activePath);
|
|
479
491
|
if (active && active.projectPath === oldAbs) {
|
|
480
492
|
active.projectPath = newAbs;
|
|
481
|
-
active.updatedAt = Date.now();
|
|
482
493
|
atomicWriteJson(activePath, active);
|
|
483
494
|
updated++;
|
|
484
495
|
}
|
|
@@ -489,7 +500,6 @@ export async function migrateProject(oldPath, newPath) {
|
|
|
489
500
|
const meta = readLastJsonlLine(metaPath);
|
|
490
501
|
if (meta && meta.projectPath === oldAbs) {
|
|
491
502
|
meta.projectPath = newAbs;
|
|
492
|
-
meta.updatedAt = Date.now();
|
|
493
503
|
appendJsonl(metaPath, meta);
|
|
494
504
|
updated++;
|
|
495
505
|
}
|