evolcore 0.0.20 → 0.0.22
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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +311 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +30 -6
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
|
|
2
2
|
import { renderCommandCardAsText } from '../interaction-router.js';
|
|
3
|
-
import { buildEnvelope, deliveryTargetFromSession, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
|
|
3
|
+
import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
|
|
4
4
|
import { resolvePaths, getPackageRoot } from '../../paths.js';
|
|
5
|
-
import { loadDaemonConfig } from '../../config-store.js';
|
|
5
|
+
import { isFullAccessEnabled, loadDaemonConfig } from '../../config-store.js';
|
|
6
6
|
import { logger } from '../../utils/logger.js';
|
|
7
7
|
import { resolvePeerRoleDetail, roleToSessionIdentity } from '../../config/peer-role-resolver.js';
|
|
8
8
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
@@ -19,12 +19,13 @@ import { buildSessionTurnList } from '../session/session-turns.js';
|
|
|
19
19
|
import { resolveConfigCommand } from '../../config/resolved-config-op.js';
|
|
20
20
|
import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommand } from '../../config/config-operation-service.js';
|
|
21
21
|
import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
|
|
22
|
-
import { auditCommandAuthorization, hashArgv } from '../auth/authorization-audit.js';
|
|
22
|
+
import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
|
|
23
23
|
import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
|
|
24
|
+
import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
|
|
24
25
|
import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
|
|
25
26
|
import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
|
|
26
27
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
27
|
-
import { isHClassPath, isLClassPath, isSameOrDescendant } from '../protected-paths.js';
|
|
28
|
+
import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
|
|
28
29
|
import { validateModelSelectionForRole } from '../model/model-permission.js';
|
|
29
30
|
import { getModelInfo } from '../model/model-catalog.js';
|
|
30
31
|
import { constrainRuntimePermissionMode, resolveRuntimePermissionMode, validateRuntimeStringFieldOverride } from '../role/runtime-policy.js';
|
|
@@ -79,6 +80,20 @@ function writeUserSettings(updates) {
|
|
|
79
80
|
function isAdminRole(role) {
|
|
80
81
|
return isManagementRole(role);
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* A cross-channel history selector is intentionally narrower than ordinary
|
|
85
|
+
* managed reads. The persisted session, rather than a caller-controlled
|
|
86
|
+
* runtime hint, proves that this command belongs to a daemon Trigger.
|
|
87
|
+
*/
|
|
88
|
+
function isTrustedDaemonTriggerSession(session, selfAid) {
|
|
89
|
+
const channelId = String(session.channelId || '');
|
|
90
|
+
return session.channel === 'daemon'
|
|
91
|
+
&& session.channelType === 'daemon'
|
|
92
|
+
&& channelId.startsWith('trigger:')
|
|
93
|
+
&& session.selfAID?.replace(/^@/, '') === selfAid
|
|
94
|
+
&& session.metadata?.channelKey === 'daemon'
|
|
95
|
+
&& session.metadata?.peerId === channelId;
|
|
96
|
+
}
|
|
82
97
|
export class CommandHandler {
|
|
83
98
|
sessionManager;
|
|
84
99
|
messageCache;
|
|
@@ -95,12 +110,14 @@ export class CommandHandler {
|
|
|
95
110
|
agentMap;
|
|
96
111
|
primaryRunnerKey;
|
|
97
112
|
agentRegistry;
|
|
113
|
+
agentApplicationService;
|
|
98
114
|
triggerSchedulerResolver;
|
|
99
115
|
triggerSchedulerFallback;
|
|
100
116
|
triggerSchedulerFallbackAid;
|
|
101
117
|
daemonStatusProvider;
|
|
102
118
|
agentDelegationRegistry;
|
|
103
119
|
handoffRuntime;
|
|
120
|
+
contactRequestSubmitter;
|
|
104
121
|
/**
|
|
105
122
|
* Get the runner for a (channel, baseagent) pair.
|
|
106
123
|
*
|
|
@@ -195,6 +212,10 @@ export class CommandHandler {
|
|
|
195
212
|
setAgentRegistry(registry) {
|
|
196
213
|
this.agentRegistry = registry;
|
|
197
214
|
}
|
|
215
|
+
/** Inject the Core-owned agent application facade after runtime hooks exist. */
|
|
216
|
+
setAgentApplicationService(service) {
|
|
217
|
+
this.agentApplicationService = service;
|
|
218
|
+
}
|
|
198
219
|
setTriggerSchedulerResolver(resolver) {
|
|
199
220
|
this.triggerSchedulerResolver = resolver;
|
|
200
221
|
}
|
|
@@ -342,9 +363,39 @@ export class CommandHandler {
|
|
|
342
363
|
triggerPermissionFromParsed(parsed) {
|
|
343
364
|
return parsed.permissionMode ?? undefined;
|
|
344
365
|
}
|
|
345
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Persist fullaccess approval provenance as the canonical process owner,
|
|
368
|
+
* rather than the transport-specific peer id. A Feishu/other-channel
|
|
369
|
+
* alias may authenticate as a daemon-owner while its raw peer id is not an
|
|
370
|
+
* entry in daemon.json.owners; storing that alias would make the Trigger
|
|
371
|
+
* fail its next scheduler-time owner check.
|
|
372
|
+
*/
|
|
373
|
+
canonicalDaemonOwnerId(selfAid, channel, peerId, chatType) {
|
|
374
|
+
const fallback = String(peerId || '').trim();
|
|
375
|
+
try {
|
|
376
|
+
const detail = resolvePeerRoleDetail({
|
|
377
|
+
selfAid,
|
|
378
|
+
channelKey: channel,
|
|
379
|
+
channelType: this.resolveChannelType(channel),
|
|
380
|
+
chatType: chatType === 'group' ? 'group' : 'private',
|
|
381
|
+
actorId: fallback,
|
|
382
|
+
conversationId: fallback,
|
|
383
|
+
});
|
|
384
|
+
const principal = detail.actor.principalId?.trim();
|
|
385
|
+
return principal || fallback;
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return fallback;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false) {
|
|
346
392
|
if (!mode)
|
|
347
393
|
return undefined;
|
|
394
|
+
if (mode === 'fullaccess') {
|
|
395
|
+
if (!isFullAccessEnabled())
|
|
396
|
+
return 'fullaccess 功能未启用';
|
|
397
|
+
return isDaemonOwner ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
|
|
398
|
+
}
|
|
348
399
|
if (!role) {
|
|
349
400
|
if (mode === 'bypass' && !isAdmin)
|
|
350
401
|
return '无权限:只有 owner/admin 可以创建或修改 bypass trigger';
|
|
@@ -355,6 +406,19 @@ export class CommandHandler {
|
|
|
355
406
|
return `无权限:角色 ${role} 不能使用 ${mode} trigger permissionMode`;
|
|
356
407
|
return undefined;
|
|
357
408
|
}
|
|
409
|
+
auditFullAccessTriggerConfiguration(input) {
|
|
410
|
+
auditFullAccessEvent({
|
|
411
|
+
event: input.event,
|
|
412
|
+
source: 'trigger',
|
|
413
|
+
actorId: input.actorId,
|
|
414
|
+
processRole: input.isDaemonOwner ? 'daemon-owner' : 'none',
|
|
415
|
+
agentAid: input.definition.agentAid,
|
|
416
|
+
messageId: input.messageId,
|
|
417
|
+
triggerId: input.definition.id,
|
|
418
|
+
authorizedBy: input.definition.authorizedBy,
|
|
419
|
+
reason: input.reason,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
358
422
|
triggerRoleContext(selfAid, channel, peerId, chatType) {
|
|
359
423
|
const owning = this.getOwningAgent(channel);
|
|
360
424
|
if (!owning || owning.aid !== selfAid)
|
|
@@ -404,7 +468,7 @@ export class CommandHandler {
|
|
|
404
468
|
.find(definition => (definition.id === nameOrId || definition.name === nameOrId)
|
|
405
469
|
&& this.canAccessTriggerDefinition(definition, peerId, channel, isAdmin));
|
|
406
470
|
}
|
|
407
|
-
async buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
|
|
471
|
+
async buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
408
472
|
const now = Date.now();
|
|
409
473
|
const id = `trig_${now}_${crypto.randomBytes(4).toString('hex')}`;
|
|
410
474
|
const name = parsed.name ?? `trigger-${now.toString(36)}`;
|
|
@@ -475,9 +539,12 @@ export class CommandHandler {
|
|
|
475
539
|
} : undefined;
|
|
476
540
|
const permissionMode = this.triggerPermissionFromParsed(parsed);
|
|
477
541
|
const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId, chatType);
|
|
478
|
-
const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid);
|
|
542
|
+
const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner);
|
|
479
543
|
if (permissionError)
|
|
480
544
|
return { error: permissionError };
|
|
545
|
+
if (permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
|
|
546
|
+
return { error: '当前认证通道不能批准持久 fullaccess Trigger' };
|
|
547
|
+
}
|
|
481
548
|
if (parsed.baseagent !== undefined) {
|
|
482
549
|
if (typeof parsed.baseagent !== 'string' || !parsed.baseagent.trim()) {
|
|
483
550
|
return { error: 'Trigger baseagent 不能为空' };
|
|
@@ -501,6 +568,9 @@ export class CommandHandler {
|
|
|
501
568
|
};
|
|
502
569
|
}
|
|
503
570
|
}
|
|
571
|
+
const authorizedBy = permissionMode === 'fullaccess' && isDaemonOwner
|
|
572
|
+
? this.canonicalDaemonOwnerId(schedulerAid, channel, peerId, chatType)
|
|
573
|
+
: undefined;
|
|
504
574
|
const definition = {
|
|
505
575
|
$schema_version: 3.1,
|
|
506
576
|
id,
|
|
@@ -509,6 +579,7 @@ export class CommandHandler {
|
|
|
509
579
|
name,
|
|
510
580
|
createdAt: now,
|
|
511
581
|
updatedAt: now,
|
|
582
|
+
...(authorizedBy ? { authorizedBy } : {}),
|
|
512
583
|
origin,
|
|
513
584
|
source,
|
|
514
585
|
execution: {
|
|
@@ -665,8 +736,8 @@ export class CommandHandler {
|
|
|
665
736
|
replyContext: opts.replyCtx,
|
|
666
737
|
});
|
|
667
738
|
const fallbackText = renderCommandCardAsText(card);
|
|
668
|
-
const
|
|
669
|
-
if (
|
|
739
|
+
const receipt = await sendInteractionPayload(adapter, envelope, opts.interaction, fallbackText, opts.replyCtx);
|
|
740
|
+
if (isInteractionSendAccepted(receipt))
|
|
670
741
|
return null;
|
|
671
742
|
}
|
|
672
743
|
catch (e) {
|
|
@@ -711,6 +782,10 @@ export class CommandHandler {
|
|
|
711
782
|
setInteractionRouter(router) {
|
|
712
783
|
this.interactionRouter = router;
|
|
713
784
|
}
|
|
785
|
+
/** Inject the daemon-owned contact request/card workflow for Agent self-service. */
|
|
786
|
+
setContactRequestSubmitter(submitter) {
|
|
787
|
+
this.contactRequestSubmitter = submitter;
|
|
788
|
+
}
|
|
714
789
|
setStatsCollector(collector) {
|
|
715
790
|
this.statsCollector = collector;
|
|
716
791
|
}
|
|
@@ -1044,6 +1119,27 @@ export class CommandHandler {
|
|
|
1044
1119
|
async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
|
|
1045
1120
|
try {
|
|
1046
1121
|
const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
|
|
1122
|
+
// Lifecycle commands are control-plane operations. Their replies must
|
|
1123
|
+
// use notification payloads so other agents in a group do not treat the
|
|
1124
|
+
// text as ordinary model input. Normalize guard-path replies here too,
|
|
1125
|
+
// since those return before slash-handler command-specific branches.
|
|
1126
|
+
if (/^\/(?:new|renew)(?:\s|$)/i.test(content.trim())
|
|
1127
|
+
&& result && typeof result === 'object'
|
|
1128
|
+
&& (result.kind === 'command.result' || result.kind === 'command.error')) {
|
|
1129
|
+
const isError = result.kind === 'command.error';
|
|
1130
|
+
return isError
|
|
1131
|
+
? {
|
|
1132
|
+
kind: 'system.error',
|
|
1133
|
+
text: result.text,
|
|
1134
|
+
subtype: `session.${content.trim().toLowerCase().startsWith('/new') ? 'new' : 'renew'}.error`,
|
|
1135
|
+
recoverable: true,
|
|
1136
|
+
}
|
|
1137
|
+
: {
|
|
1138
|
+
kind: 'system.notice',
|
|
1139
|
+
text: result.text,
|
|
1140
|
+
subtype: `session.${content.trim().toLowerCase().startsWith('/new') ? 'new' : 'renew'}.result`,
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1047
1143
|
return result;
|
|
1048
1144
|
}
|
|
1049
1145
|
catch (error) {
|
|
@@ -1057,7 +1153,7 @@ export class CommandHandler {
|
|
|
1057
1153
|
async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
|
|
1058
1154
|
return await handleSlashCommand.call(this, content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
|
|
1059
1155
|
}
|
|
1060
|
-
async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
|
|
1156
|
+
async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
1061
1157
|
if (content === '/trigger') {
|
|
1062
1158
|
const visible = scheduler
|
|
1063
1159
|
.list()
|
|
@@ -1110,11 +1206,35 @@ export class CommandHandler {
|
|
|
1110
1206
|
const prefix = sub.startsWith('delete ') ? 'delete ' : sub.startsWith('remove ') ? 'remove ' : 'rm ';
|
|
1111
1207
|
const { definition } = extractAndFindTrigger(sub, prefix, 'delete');
|
|
1112
1208
|
const deleted = scheduler.delete(definition.id);
|
|
1209
|
+
if (deleted.execution.permissionMode === 'fullaccess') {
|
|
1210
|
+
this.auditFullAccessTriggerConfiguration({
|
|
1211
|
+
event: 'fullaccess.trigger.deescalated', definition: deleted, actorId: peerId,
|
|
1212
|
+
messageId, isDaemonOwner, reason: 'fullaccess Trigger deleted',
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1113
1215
|
return `✅ 触发器已删除:**${deleted.name}**`;
|
|
1114
1216
|
}
|
|
1115
1217
|
if (sub.startsWith('enable ')) {
|
|
1116
1218
|
const { definition } = extractAndFindTrigger(sub, 'enable ', 'enable');
|
|
1117
|
-
|
|
1219
|
+
if (definition.execution.permissionMode === 'fullaccess' && (!isDaemonOwner || !isFullAccessEnabled())) {
|
|
1220
|
+
return '❌ 只有 daemon-owner 可以启用 fullaccess Trigger,且 daemon.json.fullaccess.enabled 必须为 true';
|
|
1221
|
+
}
|
|
1222
|
+
if (definition.execution.permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
|
|
1223
|
+
return '❌ 当前认证通道不能批准持久 fullaccess Trigger';
|
|
1224
|
+
}
|
|
1225
|
+
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1226
|
+
const authorizedBy = this.canonicalDaemonOwnerId(definition.agentAid, definition.origin?.channelKey || channel, peerId);
|
|
1227
|
+
if (!authorizedBy)
|
|
1228
|
+
return '❌ 无法确认 fullaccess Trigger 的批准者';
|
|
1229
|
+
scheduler.update(definition.id, { ...definition, authorizedBy });
|
|
1230
|
+
}
|
|
1231
|
+
const enabled = scheduler.setEnabled(definition.id, true);
|
|
1232
|
+
if (enabled.execution.permissionMode === 'fullaccess') {
|
|
1233
|
+
this.auditFullAccessTriggerConfiguration({
|
|
1234
|
+
event: 'fullaccess.trigger.configured', definition: enabled, actorId: peerId,
|
|
1235
|
+
messageId, isDaemonOwner, reason: 'fullaccess Trigger enabled',
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1118
1238
|
return `✅ 触发器已启用:**${definition.name}**`;
|
|
1119
1239
|
}
|
|
1120
1240
|
if (sub.startsWith('disable ')) {
|
|
@@ -1197,7 +1317,7 @@ export class CommandHandler {
|
|
|
1197
1317
|
const result = parseTriggerUpdate(args);
|
|
1198
1318
|
if (!result.ok)
|
|
1199
1319
|
return `❌ ${result.error}`;
|
|
1200
|
-
const updated = await this.updateTriggerFromPatch(scheduler, result.nameOrId, result.value, channel, channelId, peerId, isAdmin, messageId, threadId);
|
|
1320
|
+
const updated = await this.updateTriggerFromPatch(scheduler, result.nameOrId, result.value, channel, channelId, peerId, isAdmin, messageId, threadId, isDaemonOwner, canApprovePersistentFullAccess);
|
|
1201
1321
|
if (!updated.ok)
|
|
1202
1322
|
return `❌ ${updated.error}`;
|
|
1203
1323
|
const nextStr = updated.trigger.nextFireAt ? new Date(updated.trigger.nextFireAt).toLocaleString() : '未计算';
|
|
@@ -1213,7 +1333,7 @@ export class CommandHandler {
|
|
|
1213
1333
|
const result = parseTriggerSet(args);
|
|
1214
1334
|
if (!result.ok)
|
|
1215
1335
|
return `❌ ${result.error}`;
|
|
1216
|
-
const reg = await this.registerTriggerFromParsed(result.value, channel, channelId, peerId, messageId, chatType, threadId, isAdmin);
|
|
1336
|
+
const reg = await this.registerTriggerFromParsed(result.value, channel, channelId, peerId, messageId, chatType, threadId, isAdmin, isDaemonOwner, canApprovePersistentFullAccess);
|
|
1217
1337
|
if (!reg.ok)
|
|
1218
1338
|
return `❌ ${reg.error}`;
|
|
1219
1339
|
const nextStr = reg.trigger.nextFireAt ? new Date(reg.trigger.nextFireAt).toLocaleString() : '未计算';
|
|
@@ -1232,13 +1352,13 @@ export class CommandHandler {
|
|
|
1232
1352
|
/trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
|
|
1233
1353
|
/trigger delete <名称|ID> — 删除触发器`;
|
|
1234
1354
|
}
|
|
1235
|
-
async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
|
|
1355
|
+
async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
1236
1356
|
const scheduler = this.getTriggerSchedulerForChannel(channel);
|
|
1237
1357
|
if (!scheduler)
|
|
1238
1358
|
return '⚠️ 触发器功能未启用';
|
|
1239
|
-
return await this.handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId);
|
|
1359
|
+
return await this.handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner, canApprovePersistentFullAccess);
|
|
1240
1360
|
}
|
|
1241
|
-
async updateTriggerFromPatch(scheduler, nameOrId, patch, channel, channelId, peerId, isAdmin, messageId, threadId) {
|
|
1361
|
+
async updateTriggerFromPatch(scheduler, nameOrId, patch, channel, channelId, peerId, isAdmin, messageId, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
1242
1362
|
const definition = this.findTriggerDefinition(scheduler, nameOrId, peerId, channel, isAdmin);
|
|
1243
1363
|
if (!definition) {
|
|
1244
1364
|
return { ok: false, error: isAdmin ? `未找到触发器:${nameOrId}` : `未找到触发器 "${nameOrId}",或无权限修改` };
|
|
@@ -1275,6 +1395,22 @@ export class CommandHandler {
|
|
|
1275
1395
|
catch (err) {
|
|
1276
1396
|
return { ok: false, error: err?.message || String(err) };
|
|
1277
1397
|
}
|
|
1398
|
+
if (updated.execution.permissionMode === 'fullaccess') {
|
|
1399
|
+
if (!isDaemonOwner) {
|
|
1400
|
+
return { ok: false, code: 'FORBIDDEN', error: '无权限:只有 daemon-owner 可以配置 fullaccess Trigger' };
|
|
1401
|
+
}
|
|
1402
|
+
if (!canApprovePersistentFullAccess) {
|
|
1403
|
+
return { ok: false, error: '当前认证通道不能批准持久 fullaccess Trigger', code: 'FORBIDDEN' };
|
|
1404
|
+
}
|
|
1405
|
+
updated = {
|
|
1406
|
+
...updated,
|
|
1407
|
+
authorizedBy: this.canonicalDaemonOwnerId(definition.agentAid, definition.origin?.channelKey || channel, peerId),
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
const { authorizedBy: _authorizedBy, ...deescalated } = updated;
|
|
1412
|
+
updated = deescalated;
|
|
1413
|
+
}
|
|
1278
1414
|
const target = this.primaryFeedbackTarget(updated);
|
|
1279
1415
|
if (!isAdmin && target && !sameFeedbackTarget(target, definition.origin)) {
|
|
1280
1416
|
return { ok: false, error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
|
|
@@ -1283,14 +1419,35 @@ export class CommandHandler {
|
|
|
1283
1419
|
return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
|
|
1284
1420
|
}
|
|
1285
1421
|
const triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId);
|
|
1286
|
-
const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid);
|
|
1287
|
-
if (permissionError)
|
|
1288
|
-
return {
|
|
1422
|
+
const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner);
|
|
1423
|
+
if (permissionError) {
|
|
1424
|
+
return {
|
|
1425
|
+
ok: false,
|
|
1426
|
+
error: permissionError,
|
|
1427
|
+
code: /fullaccess 功能未启用/.test(permissionError) ? 'NOT_SUPPORTED'
|
|
1428
|
+
: /无权限|只有 daemon-owner/.test(permissionError) ? 'FORBIDDEN'
|
|
1429
|
+
: undefined,
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1289
1432
|
const runtimeFieldError = this.validateTriggerRuntimeFields(updated, triggerRole.role, triggerRole.baseagent);
|
|
1290
1433
|
if (runtimeFieldError)
|
|
1291
1434
|
return { ok: false, error: runtimeFieldError };
|
|
1292
1435
|
try {
|
|
1293
1436
|
const saved = scheduler.update(definition.id, updated);
|
|
1437
|
+
if (saved.execution.permissionMode === 'fullaccess') {
|
|
1438
|
+
this.auditFullAccessTriggerConfiguration({
|
|
1439
|
+
event: 'fullaccess.trigger.configured', definition: saved, actorId: peerId,
|
|
1440
|
+
messageId, isDaemonOwner, reason: definition.execution.permissionMode === 'fullaccess'
|
|
1441
|
+
? 'fullaccess Trigger updated'
|
|
1442
|
+
: 'Trigger elevated to fullaccess',
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
else if (definition.execution.permissionMode === 'fullaccess') {
|
|
1446
|
+
this.auditFullAccessTriggerConfiguration({
|
|
1447
|
+
event: 'fullaccess.trigger.deescalated', definition, actorId: peerId,
|
|
1448
|
+
messageId, isDaemonOwner, reason: 'Trigger permissionMode changed from fullaccess',
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1294
1451
|
return {
|
|
1295
1452
|
ok: true,
|
|
1296
1453
|
trigger: this.definitionToTriggerView(saved, scheduler),
|
|
@@ -1304,12 +1461,18 @@ export class CommandHandler {
|
|
|
1304
1461
|
/** 从已解析的 trigger 参数组装 definition 并注册。文本路径(handleTrigger)与 menu 路径共用。
|
|
1305
1462
|
* parsed 形状 = parseTriggerSet 的 result.value(ParsedTriggerSet)。
|
|
1306
1463
|
* 失败 return { ok:false, error };成功 return { ok:true, trigger }。 */
|
|
1307
|
-
async registerTriggerFromParsed(parsed, channel, channelId, peerId, messageId, chatType, threadId, isAdmin = false) {
|
|
1308
|
-
const built = await this.buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId);
|
|
1464
|
+
async registerTriggerFromParsed(parsed, channel, channelId, peerId, messageId, chatType, threadId, isAdmin = false, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
1465
|
+
const built = await this.buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner, canApprovePersistentFullAccess);
|
|
1309
1466
|
if ('error' in built)
|
|
1310
1467
|
return { ok: false, error: built.error };
|
|
1311
1468
|
try {
|
|
1312
1469
|
const created = built.scheduler.create(built.definition, [], { enable: true });
|
|
1470
|
+
if (created.execution.permissionMode === 'fullaccess') {
|
|
1471
|
+
this.auditFullAccessTriggerConfiguration({
|
|
1472
|
+
event: 'fullaccess.trigger.configured', definition: created, actorId: peerId,
|
|
1473
|
+
messageId, isDaemonOwner, reason: 'fullaccess Trigger created',
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1313
1476
|
return { ok: true, trigger: this.definitionToTriggerView(created, built.scheduler) };
|
|
1314
1477
|
}
|
|
1315
1478
|
catch (err) {
|
|
@@ -1534,6 +1697,7 @@ export class CommandHandler {
|
|
|
1534
1697
|
chatType: grant.chatType,
|
|
1535
1698
|
conversationId,
|
|
1536
1699
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1700
|
+
...trustedExecutionIdentityInput(grant),
|
|
1537
1701
|
});
|
|
1538
1702
|
const executeOne = (fieldArgv) => {
|
|
1539
1703
|
const resolved = resolveConfigCommand(fieldArgv, {
|
|
@@ -1606,9 +1770,19 @@ export class CommandHandler {
|
|
|
1606
1770
|
const resolved = resolveContactCommand(argv);
|
|
1607
1771
|
if (!resolved.ok)
|
|
1608
1772
|
return { ok: false, code: resolved.code, error: resolved.reason };
|
|
1609
|
-
if (resolved.command.selfAid !== grant.selfAid) {
|
|
1773
|
+
if (resolved.command.selfAid !== grant.selfAid && !hasTrustedFullAccessDelegation(grant)) {
|
|
1610
1774
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent contact book can be targeted' };
|
|
1611
1775
|
}
|
|
1776
|
+
const selfServiceAdd = resolved.command.kind === 'add';
|
|
1777
|
+
const primaryId = 'primaryId' in resolved.command ? resolved.command.primaryId : undefined;
|
|
1778
|
+
if (selfServiceAdd && !hasTrustedFullAccessDelegation(grant)) {
|
|
1779
|
+
if (grant.channelType !== 'aun' || grant.chatType !== 'private') {
|
|
1780
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'contact add requires the current AUN private relation' };
|
|
1781
|
+
}
|
|
1782
|
+
if (primaryId !== conversationId) {
|
|
1783
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'contact add may only target the current private peer' };
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1612
1786
|
const subject = buildAuthSubject({
|
|
1613
1787
|
selfAid: grant.selfAid,
|
|
1614
1788
|
actorId: grant.actorId,
|
|
@@ -1618,17 +1792,25 @@ export class CommandHandler {
|
|
|
1618
1792
|
chatType: grant.chatType,
|
|
1619
1793
|
conversationId,
|
|
1620
1794
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1795
|
+
...trustedExecutionIdentityInput(grant),
|
|
1621
1796
|
});
|
|
1622
1797
|
const decision = authorizeOperation({
|
|
1623
1798
|
source: 'agent-tool',
|
|
1624
1799
|
subject,
|
|
1625
1800
|
intent: {
|
|
1626
1801
|
operation: resolved.command.operationId,
|
|
1627
|
-
scope: 'agent',
|
|
1802
|
+
scope: selfServiceAdd ? 'relation' : 'agent',
|
|
1628
1803
|
source: 'agent-tool',
|
|
1629
1804
|
args: {
|
|
1630
1805
|
self: resolved.command.selfAid,
|
|
1631
|
-
...(
|
|
1806
|
+
...(primaryId ? { primaryId } : {}),
|
|
1807
|
+
...(selfServiceAdd
|
|
1808
|
+
? {
|
|
1809
|
+
targetId: primaryId,
|
|
1810
|
+
peer: primaryId,
|
|
1811
|
+
peerKey: `aun#${primaryId}`,
|
|
1812
|
+
}
|
|
1813
|
+
: {}),
|
|
1632
1814
|
},
|
|
1633
1815
|
},
|
|
1634
1816
|
auditMetadata: {
|
|
@@ -1639,6 +1821,47 @@ export class CommandHandler {
|
|
|
1639
1821
|
});
|
|
1640
1822
|
if (!decision.allow)
|
|
1641
1823
|
return { ok: false, code: decision.code, error: decision.reason };
|
|
1824
|
+
if (selfServiceAdd) {
|
|
1825
|
+
if (!this.contactRequestSubmitter) {
|
|
1826
|
+
return { ok: false, code: 'CONTACT_REQUEST_UNAVAILABLE', error: 'Contact request workflow is not configured' };
|
|
1827
|
+
}
|
|
1828
|
+
try {
|
|
1829
|
+
const submission = await this.contactRequestSubmitter({
|
|
1830
|
+
selfAid: grant.selfAid,
|
|
1831
|
+
applicantAid: primaryId,
|
|
1832
|
+
sourceChannelKey: grant.channel,
|
|
1833
|
+
});
|
|
1834
|
+
if (submission.code === 'blocked') {
|
|
1835
|
+
return {
|
|
1836
|
+
ok: false,
|
|
1837
|
+
code: 'CONTACT_BLOCKED',
|
|
1838
|
+
error: `目标联系人 ${primaryId} 已被拉黑。请先解除拉黑:ec contact unblock ${primaryId} --self ${grant.selfAid}`,
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
return {
|
|
1842
|
+
ok: true,
|
|
1843
|
+
result: {
|
|
1844
|
+
ok: true,
|
|
1845
|
+
operation: 'add',
|
|
1846
|
+
selfAid: grant.selfAid,
|
|
1847
|
+
primaryId: primaryId,
|
|
1848
|
+
status: submission.code,
|
|
1849
|
+
changed: submission.code === 'submitted' || submission.code === 'resubmitted',
|
|
1850
|
+
requestId: submission.requestId,
|
|
1851
|
+
expiresAt: submission.expiresAt,
|
|
1852
|
+
contactRevision: submission.contactRevision,
|
|
1853
|
+
reviewCardSent: submission.reviewCardSent === true,
|
|
1854
|
+
},
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
catch (error) {
|
|
1858
|
+
return {
|
|
1859
|
+
ok: false,
|
|
1860
|
+
code: error?.code ?? 'CONTACT_REQUEST_FAILED',
|
|
1861
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1642
1865
|
const result = await executeResolvedContactCommand(resolved.command, grant.actorId);
|
|
1643
1866
|
return result.ok
|
|
1644
1867
|
? { ok: true, result }
|
|
@@ -1663,7 +1886,7 @@ export class CommandHandler {
|
|
|
1663
1886
|
if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
|
|
1664
1887
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
|
|
1665
1888
|
}
|
|
1666
|
-
if (parsed.command.aid !== grantSelfAid) {
|
|
1889
|
+
if (parsed.command.aid !== grantSelfAid && !hasTrustedFullAccessDelegation(delegation.grant)) {
|
|
1667
1890
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
|
|
1668
1891
|
}
|
|
1669
1892
|
let conversationId;
|
|
@@ -1682,6 +1905,7 @@ export class CommandHandler {
|
|
|
1682
1905
|
chatType: delegation.grant.chatType,
|
|
1683
1906
|
conversationId,
|
|
1684
1907
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1908
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
1685
1909
|
});
|
|
1686
1910
|
const managementRole = isManagementRole(subject.role);
|
|
1687
1911
|
const decision = authorizeOperation({
|
|
@@ -1733,7 +1957,7 @@ export class CommandHandler {
|
|
|
1733
1957
|
if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
|
|
1734
1958
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
|
|
1735
1959
|
}
|
|
1736
|
-
if (parsed.command.from !== grantSelfAid) {
|
|
1960
|
+
if (parsed.command.from !== grantSelfAid && !hasTrustedFullAccessDelegation(grant)) {
|
|
1737
1961
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can perform managed group operations' };
|
|
1738
1962
|
}
|
|
1739
1963
|
let conversationId;
|
|
@@ -1752,12 +1976,16 @@ export class CommandHandler {
|
|
|
1752
1976
|
chatType: grant.chatType,
|
|
1753
1977
|
conversationId,
|
|
1754
1978
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1979
|
+
...trustedExecutionIdentityInput(grant),
|
|
1755
1980
|
});
|
|
1756
|
-
const managementRole = isManagementRole(subject.role);
|
|
1757
|
-
if (
|
|
1981
|
+
const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
|
|
1982
|
+
if (subject.dataScope !== 'daemon'
|
|
1983
|
+
&& !managementRole
|
|
1984
|
+
&& (grant.channelType !== 'aun' || !isManagedGroupRelationRead(parsed.command))) {
|
|
1758
1985
|
return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `Role ${subject.role} cannot manage Agent group membership or settings` };
|
|
1759
1986
|
}
|
|
1760
|
-
if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner')
|
|
1987
|
+
if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner')
|
|
1988
|
+
&& subject.dataScope !== 'daemon' && subject.role !== 'owner') {
|
|
1761
1989
|
return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `${parsed.command.kind} requires Agent owner permission` };
|
|
1762
1990
|
}
|
|
1763
1991
|
const decision = authorizeOperation({
|
|
@@ -1849,7 +2077,7 @@ export class CommandHandler {
|
|
|
1849
2077
|
};
|
|
1850
2078
|
}
|
|
1851
2079
|
}
|
|
1852
|
-
/** Agent-managed
|
|
2080
|
+
/** Agent-managed msg read entrypoint. AUN credentials and history roots remain daemon-owned. */
|
|
1853
2081
|
async handleMsgOperation(argv, sessionId, delegationToken, delegationCommandHash) {
|
|
1854
2082
|
if (!this.agentDelegationRegistry) {
|
|
1855
2083
|
return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
|
|
@@ -1902,28 +2130,53 @@ export class CommandHandler {
|
|
|
1902
2130
|
if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
|
|
1903
2131
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
|
|
1904
2132
|
}
|
|
1905
|
-
if (
|
|
1906
|
-
return { ok: false, code: 'SCOPE_MISMATCH', error: '
|
|
2133
|
+
if (parsed.command.from !== grantSelfAid && !hasTrustedFullAccessDelegation(grant)) {
|
|
2134
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
|
|
1907
2135
|
}
|
|
1908
|
-
|
|
2136
|
+
const isSelectorHistory = parsed.command.kind === 'history'
|
|
2137
|
+
&& !!parsed.command.channelKey
|
|
2138
|
+
&& !!parsed.command.peerId;
|
|
2139
|
+
let conversationId;
|
|
1909
2140
|
try {
|
|
1910
|
-
|
|
2141
|
+
conversationId = parsePeerKey(grant.peerKey).channelId.replace(/^@/, '');
|
|
1911
2142
|
}
|
|
1912
2143
|
catch {
|
|
1913
2144
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
|
|
1914
2145
|
}
|
|
1915
|
-
if (
|
|
1916
|
-
|
|
2146
|
+
if (isSelectorHistory) {
|
|
2147
|
+
if (!hasTrustedFullAccessDelegation(grant) && !isTrustedDaemonTriggerSession(session, grantSelfAid)) {
|
|
2148
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Cross-channel history selectors require a trusted daemon Trigger session' };
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
else {
|
|
2152
|
+
if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'private')) {
|
|
2153
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
|
|
2154
|
+
}
|
|
2155
|
+
if (!hasTrustedFullAccessDelegation(grant) && parsed.command.target !== conversationId) {
|
|
2156
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
const selectorChannel = isSelectorHistory
|
|
2160
|
+
? tryParseChannelKey(parsed.command.channelKey)
|
|
2161
|
+
: undefined;
|
|
2162
|
+
if (isSelectorHistory && !selectorChannel) {
|
|
2163
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: 'Invalid channel selector' };
|
|
1917
2164
|
}
|
|
2165
|
+
const targetId = isSelectorHistory ? parsed.command.peerId : parsed.command.target;
|
|
2166
|
+
const targetPeerKey = isSelectorHistory
|
|
2167
|
+
? formatPeerKey(selectorChannel.type, targetId)
|
|
2168
|
+
: formatPeerKey('aun', targetId);
|
|
1918
2169
|
const subject = buildAuthSubject({
|
|
1919
2170
|
selfAid: grant.selfAid,
|
|
1920
2171
|
actorId: grant.actorId,
|
|
1921
2172
|
channel: grant.channel,
|
|
1922
2173
|
channelType: grant.channelType,
|
|
1923
|
-
channelId:
|
|
2174
|
+
channelId: conversationId,
|
|
1924
2175
|
chatType: grant.chatType,
|
|
1925
|
-
conversationId
|
|
2176
|
+
conversationId,
|
|
2177
|
+
...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole) } : {}),
|
|
1926
2178
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2179
|
+
...trustedExecutionIdentityInput(grant),
|
|
1927
2180
|
});
|
|
1928
2181
|
const decision = authorizeOperation({
|
|
1929
2182
|
source: 'agent-tool',
|
|
@@ -1934,9 +2187,15 @@ export class CommandHandler {
|
|
|
1934
2187
|
source: 'agent-tool',
|
|
1935
2188
|
args: {
|
|
1936
2189
|
self: parsed.command.from,
|
|
1937
|
-
targetId
|
|
1938
|
-
peer:
|
|
1939
|
-
peerKey:
|
|
2190
|
+
targetId,
|
|
2191
|
+
peer: targetId,
|
|
2192
|
+
peerKey: targetPeerKey,
|
|
2193
|
+
...(isSelectorHistory ? {
|
|
2194
|
+
channelKey: parsed.command.channelKey,
|
|
2195
|
+
peerId: parsed.command.peerId,
|
|
2196
|
+
selectorPeerKey: targetPeerKey,
|
|
2197
|
+
triggerSessionId: session.id,
|
|
2198
|
+
} : {}),
|
|
1940
2199
|
...(parsed.command.session ? { session: parsed.command.session } : {}),
|
|
1941
2200
|
...(parsed.command.limit !== undefined ? { limit: parsed.command.limit } : {}),
|
|
1942
2201
|
...(parsed.command.before !== undefined ? { before: parsed.command.before } : {}),
|
|
@@ -1949,6 +2208,9 @@ export class CommandHandler {
|
|
|
1949
2208
|
argvHash: hashArgv(parsed.command.canonicalArgv),
|
|
1950
2209
|
taskId: grant.taskId,
|
|
1951
2210
|
messageId: grant.messageId,
|
|
2211
|
+
sessionId,
|
|
2212
|
+
agentAid: grant.selfAid,
|
|
2213
|
+
toolName: 'ec.msg',
|
|
1952
2214
|
},
|
|
1953
2215
|
});
|
|
1954
2216
|
if (!decision.allow)
|
|
@@ -1984,7 +2246,7 @@ export class CommandHandler {
|
|
|
1984
2246
|
if (sessionSelfAid && sessionSelfAid !== actorAid) {
|
|
1985
2247
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
|
|
1986
2248
|
}
|
|
1987
|
-
if (grant.channelType !== 'aun' || grant.chatType !== 'group') {
|
|
2249
|
+
if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'group')) {
|
|
1988
2250
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed fs operations require the current AUN group relation' };
|
|
1989
2251
|
}
|
|
1990
2252
|
let currentGroup;
|
|
@@ -1994,10 +2256,13 @@ export class CommandHandler {
|
|
|
1994
2256
|
catch {
|
|
1995
2257
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
|
|
1996
2258
|
}
|
|
1997
|
-
if ((
|
|
2259
|
+
if (!hasTrustedFullAccessDelegation(grant)
|
|
2260
|
+
&& ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId !== currentGroup)) {
|
|
1998
2261
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
|
|
1999
2262
|
}
|
|
2000
|
-
parsed.command.actorAid =
|
|
2263
|
+
parsed.command.actorAid = hasTrustedFullAccessDelegation(grant)
|
|
2264
|
+
? (parsed.command.actorAid ?? actorAid)
|
|
2265
|
+
: actorAid;
|
|
2001
2266
|
const subject = buildAuthSubject({
|
|
2002
2267
|
selfAid: grant.selfAid,
|
|
2003
2268
|
actorId: grant.actorId,
|
|
@@ -2007,6 +2272,7 @@ export class CommandHandler {
|
|
|
2007
2272
|
chatType: grant.chatType,
|
|
2008
2273
|
conversationId: currentGroup,
|
|
2009
2274
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2275
|
+
...trustedExecutionIdentityInput(grant),
|
|
2010
2276
|
});
|
|
2011
2277
|
const intentArgs = {
|
|
2012
2278
|
self: actorAid,
|
|
@@ -2139,6 +2405,7 @@ export class CommandHandler {
|
|
|
2139
2405
|
chatType: grant.chatType,
|
|
2140
2406
|
conversationId,
|
|
2141
2407
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2408
|
+
...trustedExecutionIdentityInput(grant),
|
|
2142
2409
|
});
|
|
2143
2410
|
const ctlIntent = this.resolveCtlIntent(cmd, grantSelfAid, grant.peerKey);
|
|
2144
2411
|
if (!ctlIntent)
|