evolcore 0.0.26 → 0.0.28
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 +36 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +10 -5
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +70 -26
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex-runner.js +363 -421
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +13 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +544 -239
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +111 -162
- package/dist/cli/diag.js +3 -3
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +13 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +81 -19
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +105 -40
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +364 -96
- package/dist/core/command/menu-protocol.js +43 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +61 -37
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +94 -16
- package/dist/core/message/message-bridge.js +140 -13
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +729 -118
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +76 -36
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +8 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +38 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +322 -207
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +3 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/dist/utils/windows-shell-trust.js +37 -0
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +23 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
|
|
2
|
+
import { loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
3
|
+
import { closeDiagnosticSession, diagnosticTokenHash, isDiagnosticSessionActive } from './diag-service.js';
|
|
2
4
|
import { parseMenuClientArgs } from '../../cli/menu-client.js';
|
|
5
|
+
import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
|
|
3
6
|
import { renderCommandCardAsText } from '../interaction-router.js';
|
|
4
7
|
import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
|
|
5
8
|
import { resolvePaths, getPackageRoot } from '../../paths.js';
|
|
@@ -8,6 +11,7 @@ import { logger } from '../../utils/logger.js';
|
|
|
8
11
|
import { resolvePeerRoleDetail, roleToSessionIdentity } from '../../config/peer-role-resolver.js';
|
|
9
12
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
10
13
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
14
|
+
import { sameGroupAid } from '../../aun/group/index.js';
|
|
11
15
|
import crypto from 'crypto';
|
|
12
16
|
import path from 'path';
|
|
13
17
|
import fs from 'fs';
|
|
@@ -24,7 +28,7 @@ import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../au
|
|
|
24
28
|
import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
|
|
25
29
|
import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
|
|
26
30
|
import { executeManagedAidOperation, ManagedAidOperationError, managedAidShowViewForRole, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
|
|
27
|
-
import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
|
|
31
|
+
import { executeManagedGroupOperation, isManagedGroupRelationRead, isManagedGroupRelationWrite, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
|
|
28
32
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
29
33
|
import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
|
|
30
34
|
import { validateModelSelectionForRole } from '../model/model-permission.js';
|
|
@@ -99,6 +103,14 @@ export class CommandHandler {
|
|
|
99
103
|
sessionManager;
|
|
100
104
|
messageCache;
|
|
101
105
|
eventBus;
|
|
106
|
+
agentStorageClientResolver;
|
|
107
|
+
groupInviteDecisionResolver;
|
|
108
|
+
setAgentStorageClientResolver(resolver) {
|
|
109
|
+
this.agentStorageClientResolver = resolver;
|
|
110
|
+
}
|
|
111
|
+
setGroupInviteDecisionResolver(resolver) {
|
|
112
|
+
this.groupInviteDecisionResolver = resolver;
|
|
113
|
+
}
|
|
102
114
|
adapters = new Map();
|
|
103
115
|
policies = new Map();
|
|
104
116
|
channelObjects = new Map(); // name → actual channel instance (for /check)
|
|
@@ -155,7 +167,34 @@ export class CommandHandler {
|
|
|
155
167
|
throw new Error('Menu request file must be a regular file of at most 128 KiB');
|
|
156
168
|
return fs.readFileSync(target, 'utf8');
|
|
157
169
|
});
|
|
158
|
-
|
|
170
|
+
const binding = session.metadata?.diagnostic;
|
|
171
|
+
if (binding || session.metadata?.diagnosticStatus === 'cancelled') {
|
|
172
|
+
if (!isDiagnosticSessionActive(session, this.sessionManager.getChatDir(session))) {
|
|
173
|
+
return { ok: false, code: 'DIAG_CLOSED', error: '诊断授权已结束,禁止旧会话继续访问' };
|
|
174
|
+
}
|
|
175
|
+
if (binding && (options.targetAid !== binding.controlAid
|
|
176
|
+
|| diagnosticTokenHash(options.remoteAccessToken ?? options.ecwebToken ?? '') !== binding.tokenHash)) {
|
|
177
|
+
return { ok: false, code: 'DIAG_SCOPE_MISMATCH', error: '诊断请求必须使用本会话绑定的主机和 Token' };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const guardedOptions = binding
|
|
181
|
+
? {
|
|
182
|
+
...options,
|
|
183
|
+
beforeSend: () => {
|
|
184
|
+
if (!isDiagnosticSessionActive(session, this.sessionManager.getChatDir(session))) {
|
|
185
|
+
const error = new Error('诊断授权已结束,禁止旧会话继续访问');
|
|
186
|
+
error.code = 'DIAG_CLOSED';
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
: options;
|
|
192
|
+
const response = await send(guardedOptions);
|
|
193
|
+
if (binding && response?.error?.code === 'REMOTE_ACCESS_TOKEN_INVALID') {
|
|
194
|
+
closeDiagnosticSession(session, this.sessionManager.getChatDir(session));
|
|
195
|
+
this.messageQueue?.clearBySession(session.id);
|
|
196
|
+
}
|
|
197
|
+
return { ok: true, response };
|
|
159
198
|
}
|
|
160
199
|
catch (error) {
|
|
161
200
|
return { ok: false, code: error.code ?? 'MENU_REQUEST_FAILED', error: error.message };
|
|
@@ -208,19 +247,11 @@ export class CommandHandler {
|
|
|
208
247
|
throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
|
|
209
248
|
}
|
|
210
249
|
getAvailableBaseagentsForOwner(evolName) {
|
|
211
|
-
|
|
212
|
-
return [...this.agentMap.keys()]
|
|
213
|
-
.filter(key => key.startsWith(prefix))
|
|
214
|
-
.map(key => key.slice(prefix.length));
|
|
250
|
+
return loadedBaseagentIds(this.agentMap.keys(), evolName);
|
|
215
251
|
}
|
|
216
|
-
/** Return the
|
|
252
|
+
/** Return the stable temporary root for a managed session, if available. */
|
|
217
253
|
getManagedSessionTempDir(session) {
|
|
218
|
-
|
|
219
|
-
return this.getAgent(session.channel, session.baseagent).getManagedTempDir?.(session.id);
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return undefined;
|
|
223
|
-
}
|
|
254
|
+
return ensureSessionRuntimeDir(session.id);
|
|
224
255
|
}
|
|
225
256
|
formatBaseagentUnavailable(error) {
|
|
226
257
|
const available = error.availableBaseagents.length ? error.availableBaseagents.join(', ') : '(none)';
|
|
@@ -236,21 +267,10 @@ export class CommandHandler {
|
|
|
236
267
|
getAvailableBaseagents(channel) {
|
|
237
268
|
const owner = this.agentRegistry?.resolveByChannel(channel);
|
|
238
269
|
if (!owner) {
|
|
239
|
-
|
|
240
|
-
for (const key of this.agentMap.keys()) {
|
|
241
|
-
const idx = key.indexOf('::');
|
|
242
|
-
result.add(idx >= 0 ? key.slice(idx + 2) : key);
|
|
243
|
-
}
|
|
244
|
-
return [...result];
|
|
270
|
+
return loadedBaseagentIds(this.agentMap.keys());
|
|
245
271
|
}
|
|
246
272
|
const evolName = owner.name || '<unknown>';
|
|
247
|
-
|
|
248
|
-
const result = [];
|
|
249
|
-
for (const key of this.agentMap.keys()) {
|
|
250
|
-
if (key.startsWith(prefix))
|
|
251
|
-
result.push(key.slice(prefix.length));
|
|
252
|
-
}
|
|
253
|
-
return result;
|
|
273
|
+
return loadedBaseagentIds(this.agentMap.keys(), evolName);
|
|
254
274
|
}
|
|
255
275
|
/** Extract the baseagent component from `primaryRunnerKey` (e.g. `aid::claude` → `claude`). */
|
|
256
276
|
parseDefaultBaseagent() {
|
|
@@ -441,6 +461,8 @@ export class CommandHandler {
|
|
|
441
461
|
primaryFeedbackTarget(definition) {
|
|
442
462
|
if (definition.feedback.strategy === 'target')
|
|
443
463
|
return definition.feedback.target;
|
|
464
|
+
if (definition.feedback.strategy === 'event_session')
|
|
465
|
+
return undefined;
|
|
444
466
|
if (!definition.origin)
|
|
445
467
|
return undefined;
|
|
446
468
|
return {
|
|
@@ -625,10 +647,16 @@ export class CommandHandler {
|
|
|
625
647
|
if (!isAdmin && parsed.feedbackStrategy === 'target' && !sameFeedbackTarget(target, origin)) {
|
|
626
648
|
return { error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
|
|
627
649
|
}
|
|
650
|
+
if (!isAdmin && parsed.feedbackStrategy === 'event_session') {
|
|
651
|
+
return { error: '无权限:只有 owner/admin 可以使用事件来源会话' };
|
|
652
|
+
}
|
|
628
653
|
const source = this.triggerSourceFromSchedule(parsed.scheduleType, parsed.scheduleValue);
|
|
629
654
|
if (parsed.timezone && source.type === 'cron') {
|
|
630
655
|
source.timezone = parsed.timezone;
|
|
631
656
|
}
|
|
657
|
+
if (parsed.eventFilter !== undefined && source.type === 'event') {
|
|
658
|
+
source.filter = parsed.eventFilter;
|
|
659
|
+
}
|
|
632
660
|
const script = parsed.scriptPath ? {
|
|
633
661
|
path: parsed.scriptPath,
|
|
634
662
|
runtime: parsed.scriptRuntime,
|
|
@@ -688,6 +716,7 @@ export class CommandHandler {
|
|
|
688
716
|
baseagent: executionBaseagent,
|
|
689
717
|
} : {}),
|
|
690
718
|
model: parsed.model,
|
|
719
|
+
timeoutMs: parsed.executionTimeoutMs,
|
|
691
720
|
effort: parsed.effort,
|
|
692
721
|
...(permissionMode !== undefined ? { permissionMode } : {}),
|
|
693
722
|
onError: 'retry',
|
|
@@ -847,23 +876,36 @@ export class CommandHandler {
|
|
|
847
876
|
* 通用降级应答入口:按 (sessionId, fallbackCommand) 查找 pending interaction 并路由。
|
|
848
877
|
* 返回 { matched: true } 表示已处理,调用方直接返回 result。
|
|
849
878
|
*/
|
|
850
|
-
async handleInteractionFallback(command, args, sessionId, userId) {
|
|
879
|
+
async handleInteractionFallback(command, args, sessionId, userId, channelKey) {
|
|
851
880
|
if (!this.interactionRouter)
|
|
852
881
|
return { matched: false };
|
|
853
|
-
const
|
|
882
|
+
const candidates = this.interactionRouter.getPending(sessionId, command);
|
|
883
|
+
const explicitId = args.split(/\s+/, 1)[0];
|
|
884
|
+
if (explicitId.startsWith('aq-') && !candidates.includes(explicitId)) {
|
|
885
|
+
return { matched: true, result: '⚠️ 该问题不存在、已回答或已过期,请使用当前问题卡片。' };
|
|
886
|
+
}
|
|
887
|
+
const pendingId = candidates.includes(explicitId)
|
|
888
|
+
? explicitId : this.interactionRouter.findPendingByCommand(sessionId, command);
|
|
854
889
|
if (!pendingId)
|
|
855
890
|
return { matched: false };
|
|
891
|
+
if (explicitId !== pendingId && candidates.length > 1) {
|
|
892
|
+
return { matched: true, result: `有多个待回答问题,请使用 /ask <问题ID> <回答>:\n${candidates.join('\n')}` };
|
|
893
|
+
}
|
|
894
|
+
const answer = explicitId === pendingId ? args.slice(explicitId.length).trim() : args.trim();
|
|
895
|
+
if (!answer)
|
|
896
|
+
return { matched: true, result: `请使用 /ask ${pendingId} <回答>` };
|
|
856
897
|
const initiatorId = this.interactionRouter.getInitiator(pendingId);
|
|
857
898
|
if (initiatorId && initiatorId !== userId) {
|
|
858
899
|
return { matched: true, result: '⚠️ 仅卡片发起者可应答' };
|
|
859
900
|
}
|
|
860
|
-
const handled = this.interactionRouter.
|
|
901
|
+
const handled = await this.interactionRouter.handleAsync({
|
|
861
902
|
type: 'interaction.response',
|
|
862
903
|
id: pendingId,
|
|
863
|
-
action:
|
|
904
|
+
action: answer,
|
|
864
905
|
operatorId: userId,
|
|
906
|
+
...(channelKey ? { channelKey } : {}),
|
|
865
907
|
});
|
|
866
|
-
return { matched: true, result: handled ? '✓ 已回答' : '⚠️
|
|
908
|
+
return { matched: true, result: handled ? '✓ 已回答' : '⚠️ 回答未被接受,请检查选项或问题是否仍有效。' };
|
|
867
909
|
}
|
|
868
910
|
setProcessor(processor) {
|
|
869
911
|
this.processor = processor;
|
|
@@ -1003,6 +1045,7 @@ export class CommandHandler {
|
|
|
1003
1045
|
const item = {
|
|
1004
1046
|
value: s.threadId,
|
|
1005
1047
|
label: displayName,
|
|
1048
|
+
lable: s.lable || null,
|
|
1006
1049
|
};
|
|
1007
1050
|
if (s.agentSessionId) {
|
|
1008
1051
|
item.agentSessionId = s.agentSessionId;
|
|
@@ -1031,7 +1074,7 @@ export class CommandHandler {
|
|
|
1031
1074
|
...(paircodeAllowed === undefined ? {} : { paircodeAllowed }),
|
|
1032
1075
|
});
|
|
1033
1076
|
}
|
|
1034
|
-
async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent') {
|
|
1077
|
+
async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent', transportReplyContext) {
|
|
1035
1078
|
validateMenuValueBatchArgs(args);
|
|
1036
1079
|
return await execMenuValueBatch({
|
|
1037
1080
|
subject,
|
|
@@ -1042,12 +1085,12 @@ export class CommandHandler {
|
|
|
1042
1085
|
items: args?.items,
|
|
1043
1086
|
schemaVersion: args?.schemaVersion,
|
|
1044
1087
|
consistency: args?.consistency,
|
|
1045
|
-
execQuery: async (cmd, resolvedChannelId, queryArgs, context) => this.execMenuQuery(cmd, channel, resolvedChannelId, context.subject.actorId, queryArgs, context.subject.chatType, context.subject.fromControlChannel, context.subject.identity, context.subject, channelScope === 'control' ? 'control' : 'menu'),
|
|
1088
|
+
execQuery: async (cmd, resolvedChannelId, queryArgs, context) => this.execMenuQuery(cmd, channel, resolvedChannelId, context.subject.actorId, queryArgs, context.subject.chatType, context.subject.fromControlChannel, context.subject.identity, context.subject, channelScope === 'control' ? 'control' : 'menu', resolvedChannelId === channelId ? transportReplyContext : undefined),
|
|
1046
1089
|
});
|
|
1047
1090
|
}
|
|
1048
1091
|
/** 动态子菜单:根据 cmd 路径返回选项列表(供 menu.query + cmd 使用) */
|
|
1049
|
-
async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu') {
|
|
1050
|
-
return await menuGetSubMenuItems.call(this, cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel, authSubject, source);
|
|
1092
|
+
async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext) {
|
|
1093
|
+
return await menuGetSubMenuItems.call(this, cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel, authSubject, source, transportReplyContext);
|
|
1051
1094
|
}
|
|
1052
1095
|
// ── Menu Protocol exec ────────────────────────────────────────────────
|
|
1053
1096
|
async loadMenuContext(channel, channelId) {
|
|
@@ -1059,8 +1102,8 @@ export class CommandHandler {
|
|
|
1059
1102
|
return s ? null : { error: '当前无活跃会话', code: 'NO_ACTIVE_SESSION' };
|
|
1060
1103
|
}
|
|
1061
1104
|
/** menu.query — 查询当前值。 */
|
|
1062
|
-
async execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu') {
|
|
1063
|
-
return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source);
|
|
1105
|
+
async execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu', transportReplyContext) {
|
|
1106
|
+
return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source, transportReplyContext);
|
|
1064
1107
|
}
|
|
1065
1108
|
/** menu.update — 写入新值。 */
|
|
1066
1109
|
async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu', transportReplyContext) {
|
|
@@ -1513,6 +1556,9 @@ export class CommandHandler {
|
|
|
1513
1556
|
updated = deescalated;
|
|
1514
1557
|
}
|
|
1515
1558
|
const target = this.primaryFeedbackTarget(updated);
|
|
1559
|
+
if (!isAdmin && updated.feedback.strategy === 'event_session') {
|
|
1560
|
+
return { ok: false, error: '无权限:只有 owner/admin 可以使用事件来源会话' };
|
|
1561
|
+
}
|
|
1516
1562
|
if (!isAdmin && target && !sameFeedbackTarget(target, definition.origin)) {
|
|
1517
1563
|
return { ok: false, error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
|
|
1518
1564
|
}
|
|
@@ -2181,7 +2227,8 @@ export class CommandHandler {
|
|
|
2181
2227
|
const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
|
|
2182
2228
|
if (subject.dataScope !== 'daemon'
|
|
2183
2229
|
&& !managementRole
|
|
2184
|
-
&& (grant.channelType !== 'aun'
|
|
2230
|
+
&& (grant.channelType !== 'aun'
|
|
2231
|
+
|| !(isManagedGroupRelationRead(parsed.command) || isManagedGroupRelationWrite(parsed.command)))) {
|
|
2185
2232
|
return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `Role ${subject.role} cannot manage Agent group membership or settings` };
|
|
2186
2233
|
}
|
|
2187
2234
|
if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner')
|
|
@@ -2213,6 +2260,21 @@ export class CommandHandler {
|
|
|
2213
2260
|
});
|
|
2214
2261
|
if (!decision.allow)
|
|
2215
2262
|
return { ok: false, code: decision.code, error: decision.reason };
|
|
2263
|
+
if (parsed.command.kind === 'accept-invite' || parsed.command.kind === 'reject-invite') {
|
|
2264
|
+
if (subject.role !== 'owner' || grant.channelType !== 'aun' || grant.chatType !== 'private'
|
|
2265
|
+
|| parsed.command.from !== grantSelfAid || !this.groupInviteDecisionResolver) {
|
|
2266
|
+
return { ok: false, code: 'ROLE_ACCESS_DENIED', error: 'Targeted invitation decisions require the current Agent Owner' };
|
|
2267
|
+
}
|
|
2268
|
+
try {
|
|
2269
|
+
const result = await this.groupInviteDecisionResolver(grantSelfAid, grant.actorId, parsed.command.inviteId, parsed.command.kind === 'accept-invite' ? 'accept' : 'reject');
|
|
2270
|
+
return result.accepted
|
|
2271
|
+
? { ok: true, result: { ok: true, status: result.status, group_id: result.groupId, message: result.text } }
|
|
2272
|
+
: { ok: false, code: 'GROUP_INVITE_DECISION_REJECTED', error: result.text };
|
|
2273
|
+
}
|
|
2274
|
+
catch (error) {
|
|
2275
|
+
return { ok: false, code: 'GROUP_INVITE_DECISION_FAILED', error: error instanceof Error ? error.message : String(error) };
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2216
2278
|
let rulesContent;
|
|
2217
2279
|
if (parsed.command.kind === 'rules.set') {
|
|
2218
2280
|
const filePath = String(parsed.command.filePath || '');
|
|
@@ -2267,7 +2329,10 @@ export class CommandHandler {
|
|
|
2267
2329
|
}
|
|
2268
2330
|
}
|
|
2269
2331
|
try {
|
|
2270
|
-
return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent
|
|
2332
|
+
return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent, {
|
|
2333
|
+
defaultThread: grant.chatType === 'group' && conversationId === parsed.command.groupId
|
|
2334
|
+
? session.threadId || 'main' : 'all',
|
|
2335
|
+
}) };
|
|
2271
2336
|
}
|
|
2272
2337
|
catch (error) {
|
|
2273
2338
|
return {
|
|
@@ -2464,7 +2529,7 @@ export class CommandHandler {
|
|
|
2464
2529
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
|
|
2465
2530
|
}
|
|
2466
2531
|
if (!hasTrustedFullAccessDelegation(grant)
|
|
2467
|
-
&& ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId
|
|
2532
|
+
&& ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || !sameGroupAid(parsed.command.groupId, currentGroup))) {
|
|
2468
2533
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
|
|
2469
2534
|
}
|
|
2470
2535
|
parsed.command.actorAid = hasTrustedFullAccessDelegation(grant)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { isValidAid } from '../../aun/aid/validation.js';
|
|
5
|
+
import { isExplicitGroupId } from '../../aun/group/index.js';
|
|
6
|
+
import { isStaticAgentOwner } from '../../config/peer-role-resolver.js';
|
|
7
|
+
import { agentDir, agentRelationSharedDir } from '../../paths.js';
|
|
8
|
+
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
9
|
+
export class DataRootError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
data;
|
|
12
|
+
constructor(code, message, data) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'DataRootError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.data = data;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const DEFAULT_ROOT_TTL_MS = 10 * 60 * 1000;
|
|
20
|
+
const TOKEN_PREFIX = 'dr1';
|
|
21
|
+
function base64Url(value) {
|
|
22
|
+
return Buffer.from(value).toString('base64url');
|
|
23
|
+
}
|
|
24
|
+
function isInside(root, candidate) {
|
|
25
|
+
const relative = path.relative(root, candidate);
|
|
26
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
27
|
+
}
|
|
28
|
+
function authenticatedPrincipal(subject) {
|
|
29
|
+
const principalId = String(subject.principalId ?? '').trim();
|
|
30
|
+
if (!isValidAid(principalId) || isExplicitGroupId(principalId)) {
|
|
31
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'A verified private AUN principal is required');
|
|
32
|
+
}
|
|
33
|
+
return principalId;
|
|
34
|
+
}
|
|
35
|
+
export class DataRootResolver {
|
|
36
|
+
secret;
|
|
37
|
+
ttlMs;
|
|
38
|
+
now;
|
|
39
|
+
isOwner;
|
|
40
|
+
resolveAgentPath;
|
|
41
|
+
resolveRelationSharedPath;
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.secret = options.secret ?? crypto.randomBytes(32);
|
|
44
|
+
if (this.secret.length < 32)
|
|
45
|
+
throw new Error('Data root signing secret must be at least 32 bytes');
|
|
46
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_ROOT_TTL_MS;
|
|
47
|
+
this.now = options.now ?? (() => Date.now());
|
|
48
|
+
this.isOwner = options.isOwner ?? isStaticAgentOwner;
|
|
49
|
+
this.resolveAgentPath = options.resolveAgentPath ?? agentDir;
|
|
50
|
+
this.resolveRelationSharedPath = options.resolveRelationSharedPath ?? agentRelationSharedDir;
|
|
51
|
+
}
|
|
52
|
+
issueAgentRoot(subject) {
|
|
53
|
+
const targetAid = this.targetAid(subject);
|
|
54
|
+
const principalId = authenticatedPrincipal(subject);
|
|
55
|
+
if (subject.role !== 'owner' || !this.isOwner(targetAid, principalId)) {
|
|
56
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Agent data root requires Agent owner permission');
|
|
57
|
+
}
|
|
58
|
+
const rootPath = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
|
|
59
|
+
return this.issueDescriptor({ scope: 'agent', targetAid, principalId }, rootPath);
|
|
60
|
+
}
|
|
61
|
+
issueRelationRoot(subject, requestedPeerAid) {
|
|
62
|
+
const targetAid = this.targetAid(subject);
|
|
63
|
+
this.assertPrivateAun(subject);
|
|
64
|
+
const principalId = authenticatedPrincipal(subject);
|
|
65
|
+
const owner = subject.role === 'owner' && this.isOwner(targetAid, principalId);
|
|
66
|
+
const explicitPeerAid = typeof requestedPeerAid === 'string' ? requestedPeerAid.trim() : '';
|
|
67
|
+
if (requestedPeerAid !== undefined && !explicitPeerAid) {
|
|
68
|
+
throw new DataRootError('INVALID_ARGUMENT', 'args.peerAid must be a non-empty AUN AID');
|
|
69
|
+
}
|
|
70
|
+
if (explicitPeerAid && !owner) {
|
|
71
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Only the Agent owner may select another relation');
|
|
72
|
+
}
|
|
73
|
+
const peerAid = explicitPeerAid || principalId;
|
|
74
|
+
if (!isValidAid(peerAid) || isExplicitGroupId(peerAid)) {
|
|
75
|
+
throw new DataRootError('INVALID_ARGUMENT', 'args.peerAid must identify an AUN private-chat peer');
|
|
76
|
+
}
|
|
77
|
+
const peerKey = formatPeerKey('aun', peerAid);
|
|
78
|
+
const agentRoot = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
|
|
79
|
+
const rootPath = this.canonicalRoot(this.resolveRelationSharedPath(targetAid, peerKey), agentRoot);
|
|
80
|
+
return this.issueDescriptor({ scope: 'relation', targetAid, principalId, peerAid, peerKey }, rootPath);
|
|
81
|
+
}
|
|
82
|
+
resolveRoot(rootId, subject) {
|
|
83
|
+
const claims = this.verifyToken(rootId);
|
|
84
|
+
const targetAid = this.targetAid(subject);
|
|
85
|
+
const principalId = authenticatedPrincipal(subject);
|
|
86
|
+
if (claims.targetAid !== targetAid) {
|
|
87
|
+
throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Root does not belong to the receiving Agent');
|
|
88
|
+
}
|
|
89
|
+
if (claims.principalId !== principalId) {
|
|
90
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Root was issued to another authenticated principal');
|
|
91
|
+
}
|
|
92
|
+
let rootPath;
|
|
93
|
+
if (claims.scope === 'agent') {
|
|
94
|
+
if (subject.role !== 'owner' || !this.isOwner(targetAid, principalId)) {
|
|
95
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Agent data root requires Agent owner permission');
|
|
96
|
+
}
|
|
97
|
+
rootPath = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.assertPrivateAun(subject);
|
|
101
|
+
if (!claims.peerAid || !claims.peerKey || claims.peerKey !== formatPeerKey('aun', claims.peerAid)) {
|
|
102
|
+
throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Relation root claims are invalid');
|
|
103
|
+
}
|
|
104
|
+
const owner = subject.role === 'owner' && this.isOwner(targetAid, principalId);
|
|
105
|
+
if (!owner && principalId !== claims.peerAid) {
|
|
106
|
+
throw new DataRootError('ROOT_PEER_MISMATCH', 'Only the relation peer may use this root');
|
|
107
|
+
}
|
|
108
|
+
const agentRoot = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
|
|
109
|
+
rootPath = this.canonicalRoot(this.resolveRelationSharedPath(targetAid, claims.peerKey), agentRoot);
|
|
110
|
+
}
|
|
111
|
+
const revision = this.rootRevision(rootPath);
|
|
112
|
+
if (revision !== claims.revision) {
|
|
113
|
+
throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Root identity changed; request a new root');
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
scope: claims.scope,
|
|
117
|
+
targetAid,
|
|
118
|
+
...(claims.peerAid ? { peerAid: claims.peerAid } : {}),
|
|
119
|
+
...(claims.peerKey ? { peerKey: claims.peerKey } : {}),
|
|
120
|
+
rootPath,
|
|
121
|
+
revision,
|
|
122
|
+
expiresAt: claims.expiresAt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
resolvePath(rootId, subject, input, expectType) {
|
|
126
|
+
const root = this.resolveRoot(rootId, subject);
|
|
127
|
+
const raw = String(input ?? '').trim();
|
|
128
|
+
if (!raw)
|
|
129
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'A root-relative path is required');
|
|
130
|
+
if (raw.includes('\0'))
|
|
131
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'NUL is not allowed in a path');
|
|
132
|
+
if (path.isAbsolute(raw) || path.win32.isAbsolute(raw) || path.posix.isAbsolute(raw)) {
|
|
133
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'Data root paths must be relative');
|
|
134
|
+
}
|
|
135
|
+
if (raw.split(/[\\/]+/).includes('..')) {
|
|
136
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'Parent traversal is not allowed');
|
|
137
|
+
}
|
|
138
|
+
const lexicalPath = path.resolve(root.rootPath, raw);
|
|
139
|
+
if (!isInside(root.rootPath, lexicalPath)) {
|
|
140
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'Path escapes the data root');
|
|
141
|
+
}
|
|
142
|
+
let realPath;
|
|
143
|
+
let stat;
|
|
144
|
+
try {
|
|
145
|
+
realPath = fs.realpathSync(lexicalPath);
|
|
146
|
+
stat = fs.statSync(realPath);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new DataRootError('NOT_FOUND', 'File or directory does not exist');
|
|
150
|
+
}
|
|
151
|
+
if (!isInside(root.rootPath, realPath)) {
|
|
152
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'Symbolic link escapes the data root');
|
|
153
|
+
}
|
|
154
|
+
if (expectType === 'file' && !stat.isFile()) {
|
|
155
|
+
throw new DataRootError('NOT_SUPPORTED', 'Path is not a regular file');
|
|
156
|
+
}
|
|
157
|
+
if (expectType === 'directory' && !stat.isDirectory()) {
|
|
158
|
+
throw new DataRootError('NOT_A_DIRECTORY', 'Path is not a directory');
|
|
159
|
+
}
|
|
160
|
+
const relativePath = path.relative(root.rootPath, realPath).split(path.sep).join('/') || '.';
|
|
161
|
+
return { ...root, realPath, relativePath, stat };
|
|
162
|
+
}
|
|
163
|
+
issueDescriptor(base, rootPath) {
|
|
164
|
+
const issuedAt = this.now();
|
|
165
|
+
const claims = {
|
|
166
|
+
v: 1,
|
|
167
|
+
...base,
|
|
168
|
+
revision: this.rootRevision(rootPath),
|
|
169
|
+
issuedAt,
|
|
170
|
+
expiresAt: issuedAt + this.ttlMs,
|
|
171
|
+
nonce: crypto.randomBytes(12).toString('base64url'),
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
rootId: this.sign(claims),
|
|
175
|
+
scope: claims.scope,
|
|
176
|
+
targetAid: claims.targetAid,
|
|
177
|
+
...(claims.peerAid ? { peerAid: claims.peerAid } : {}),
|
|
178
|
+
...(claims.peerKey ? { peerKey: claims.peerKey } : {}),
|
|
179
|
+
virtualPath: claims.scope === 'agent'
|
|
180
|
+
? '$AGENTS_DIR/<targetAid>'
|
|
181
|
+
: '$AGENTS_DIR/<targetAid>/relations/<peerKey>/shared',
|
|
182
|
+
revision: `sha256:${claims.revision}`,
|
|
183
|
+
expiresAt: claims.expiresAt,
|
|
184
|
+
readOnly: true,
|
|
185
|
+
capabilities: { list: true, query: true, fetch: true },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
sign(claims) {
|
|
189
|
+
const encoded = base64Url(JSON.stringify(claims));
|
|
190
|
+
const signature = crypto.createHmac('sha256', this.secret).update(encoded).digest('base64url');
|
|
191
|
+
return `${TOKEN_PREFIX}.${encoded}.${signature}`;
|
|
192
|
+
}
|
|
193
|
+
verifyToken(value) {
|
|
194
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
195
|
+
throw new DataRootError('MISSING_ROOT_ID', 'A data rootId is required');
|
|
196
|
+
}
|
|
197
|
+
const parts = value.split('.');
|
|
198
|
+
if (parts.length !== 3 || parts[0] !== TOKEN_PREFIX) {
|
|
199
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId');
|
|
200
|
+
}
|
|
201
|
+
const expected = crypto.createHmac('sha256', this.secret).update(parts[1]).digest();
|
|
202
|
+
let actual;
|
|
203
|
+
try {
|
|
204
|
+
actual = Buffer.from(parts[2], 'base64url');
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId');
|
|
208
|
+
}
|
|
209
|
+
if (actual.length !== expected.length || !crypto.timingSafeEqual(actual, expected)) {
|
|
210
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId signature');
|
|
211
|
+
}
|
|
212
|
+
let claims;
|
|
213
|
+
try {
|
|
214
|
+
claims = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId payload');
|
|
218
|
+
}
|
|
219
|
+
if (claims.v !== 1
|
|
220
|
+
|| (claims.scope !== 'agent' && claims.scope !== 'relation')
|
|
221
|
+
|| typeof claims.targetAid !== 'string'
|
|
222
|
+
|| typeof claims.principalId !== 'string'
|
|
223
|
+
|| typeof claims.revision !== 'string'
|
|
224
|
+
|| !Number.isFinite(claims.expiresAt)) {
|
|
225
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId claims');
|
|
226
|
+
}
|
|
227
|
+
if (claims.expiresAt <= this.now()) {
|
|
228
|
+
throw new DataRootError('ROOT_EXPIRED', 'Data rootId expired; query the root again');
|
|
229
|
+
}
|
|
230
|
+
return claims;
|
|
231
|
+
}
|
|
232
|
+
canonicalRoot(candidate, requiredParent) {
|
|
233
|
+
let realPath;
|
|
234
|
+
let stat;
|
|
235
|
+
try {
|
|
236
|
+
realPath = fs.realpathSync(candidate);
|
|
237
|
+
stat = fs.statSync(realPath);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
throw new DataRootError('ROOT_NOT_FOUND', 'Data root does not exist');
|
|
241
|
+
}
|
|
242
|
+
if (!stat.isDirectory())
|
|
243
|
+
throw new DataRootError('ROOT_NOT_FOUND', 'Data root is not a directory');
|
|
244
|
+
if (requiredParent && !isInside(requiredParent, realPath)) {
|
|
245
|
+
throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Data root escapes the Agent directory');
|
|
246
|
+
}
|
|
247
|
+
return realPath;
|
|
248
|
+
}
|
|
249
|
+
rootRevision(rootPath) {
|
|
250
|
+
const stat = fs.statSync(rootPath);
|
|
251
|
+
return crypto.createHash('sha256')
|
|
252
|
+
.update(JSON.stringify({ rootPath, dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs }))
|
|
253
|
+
.digest('hex');
|
|
254
|
+
}
|
|
255
|
+
targetAid(subject) {
|
|
256
|
+
const targetAid = String(subject.selfAid ?? '').trim();
|
|
257
|
+
if (!isValidAid(targetAid))
|
|
258
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Receiving Agent AID is unavailable');
|
|
259
|
+
return targetAid;
|
|
260
|
+
}
|
|
261
|
+
assertPrivateAun(subject) {
|
|
262
|
+
if (subject.channelType !== 'aun' || subject.chatType !== 'private') {
|
|
263
|
+
throw new DataRootError('NOT_SUPPORTED', 'Relation data roots currently support AUN private chats only');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
export const dataRootResolver = new DataRootResolver();
|
|
268
|
+
export function dataRootFailure(error) {
|
|
269
|
+
if (error instanceof DataRootError) {
|
|
270
|
+
return { error: error.message, code: error.code, ...(error.data ? { data: error.data } : {}) };
|
|
271
|
+
}
|
|
272
|
+
return { error: 'Internal data root error', code: 'INTERNAL_ERROR' };
|
|
273
|
+
}
|