evolcore 0.0.25 → 0.0.27
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 +632 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +203 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +172 -33
- package/dist/agents/codex/app-server-client.js +76 -31
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +434 -446
- package/dist/agents/ecagent-runner.js +174 -34
- package/dist/agents/gemini-runner.js +8 -7
- package/dist/agents/runner-types.js +75 -19
- package/dist/aun/aid/agentmd.js +151 -36
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +4 -4
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +17 -13
- 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} +162 -39
- package/dist/aun/{menu-client.js → menu/client.js} +85 -6
- 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 +85 -19
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +19 -9
- package/dist/aun/msg/upload.js +43 -80
- 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 +842 -318
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +212 -89
- package/dist/cli/agent-create.js +626 -11
- package/dist/cli/agent.js +125 -13
- package/dist/cli/aun-commands.js +487 -337
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +55 -33
- package/dist/cli/contact.js +72 -15
- package/dist/cli/ctl-command.js +24 -24
- package/dist/cli/daemon-commands.js +140 -63
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +70 -70
- package/dist/cli/handoff-command.js +46 -14
- package/dist/cli/index.js +91 -79
- package/dist/cli/init.js +34 -1
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +5 -1
- package/dist/cli/menu-client.js +51 -32
- package/dist/cli/model.js +3 -3
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +31 -31
- package/dist/cli/response.js +32 -32
- package/dist/cli/restart-monitor.js +103 -42
- package/dist/cli/stats.js +51 -51
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +74 -64
- package/dist/config/builtin-roles.js +11 -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 +160 -0
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +97 -8
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +30 -7
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +7 -1
- 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 +53 -49
- package/dist/core/auth/operation-authorizer.js +90 -7
- package/dist/core/auth/operation-catalog.js +145 -4
- package/dist/core/auth/remote-access-store.js +155 -10
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +111 -20
- 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 +159 -65
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +46 -7
- package/dist/core/command/menu-handler.js +392 -100
- package/dist/core/command/menu-protocol.js +45 -6
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +123 -43
- 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/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +146 -34
- package/dist/core/message/message-bridge.js +163 -20
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +99 -41
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +755 -144
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +112 -41
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +108 -17
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +9 -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 +34 -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 +588 -217
- package/dist/ipc.js +193 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- 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/cross-platform.js +30 -1
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +61 -2
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +26 -14
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +196 -23
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +60 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +69 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +379 -367
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +76 -71
- package/kits/docs/evolcore/agent-create.md +202 -165
- package/kits/docs/evolcore/agent.md +99 -67
- package/kits/docs/evolcore/aid.md +68 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +241 -195
- package/kits/docs/evolcore/contact.md +75 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +244 -244
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +267 -152
- package/kits/docs/evolcore/menu-client.md +58 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +169 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +53 -50
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +623 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +269 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +42 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +217 -190
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +166 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- package/kits/rules/04-relation.md +76 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +65 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +451 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +215 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +92 -92
- package/kits/templates/roles/member.json +101 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +90 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +53 -34
- package/kits/templates/system-fragments/channel.md +28 -64
- package/kits/templates/system-fragments/commands.md +36 -28
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/reply.md +56 -0
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +104 -98
- 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,9 +1,22 @@
|
|
|
1
|
+
import { assertAgentPolicyAuthorization } from '../role/runtime-policy.js';
|
|
1
2
|
import crypto from 'crypto';
|
|
2
3
|
import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
|
|
3
4
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
4
5
|
import { logger } from '../../utils/logger.js';
|
|
5
6
|
export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
|
|
7
|
+
export const CODEX_SESSION_TOKEN_ENV = 'EVOLCORE_CODEX_SESSION_TOKEN';
|
|
6
8
|
export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the credential carried by an EC CLI process. A live task token has
|
|
11
|
+
* priority; persistent Codex threads fall back to their session credential.
|
|
12
|
+
*/
|
|
13
|
+
export function readDelegationCredential(env = process.env) {
|
|
14
|
+
const taskToken = env[AGENT_DELEGATION_TOKEN_ENV]?.trim();
|
|
15
|
+
if (taskToken)
|
|
16
|
+
return taskToken;
|
|
17
|
+
const sessionToken = env[CODEX_SESSION_TOKEN_ENV]?.trim();
|
|
18
|
+
return sessionToken || undefined;
|
|
19
|
+
}
|
|
7
20
|
export class AgentDelegationRegistry {
|
|
8
21
|
runtimeEpoch = crypto.randomBytes(16).toString('hex');
|
|
9
22
|
grantsByHash = new Map();
|
|
@@ -24,6 +37,13 @@ export class AgentDelegationRegistry {
|
|
|
24
37
|
return token;
|
|
25
38
|
}
|
|
26
39
|
validate(token, sessionId, commandHash) {
|
|
40
|
+
return this.validateToken(token, sessionId, commandHash, true);
|
|
41
|
+
}
|
|
42
|
+
/** Authenticate a CLI context lookup without spending its approved operation. */
|
|
43
|
+
validateForContext(token, sessionId, commandHash) {
|
|
44
|
+
return this.validateToken(token, sessionId, commandHash, false);
|
|
45
|
+
}
|
|
46
|
+
validateToken(token, sessionId, commandHash, consumeApproval) {
|
|
27
47
|
if (!token) {
|
|
28
48
|
return {
|
|
29
49
|
ok: false,
|
|
@@ -37,10 +57,19 @@ export class AgentDelegationRegistry {
|
|
|
37
57
|
if (grant
|
|
38
58
|
&& grant.sessionId === sessionId
|
|
39
59
|
&& this.activeHashBySession.get(sessionId) === tokenHash) {
|
|
60
|
+
if (grant.executionIdentity?.source === 'agent-policy') {
|
|
61
|
+
try {
|
|
62
|
+
assertAgentPolicyAuthorization(grant.executionIdentity);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
this.revokeSession(sessionId);
|
|
66
|
+
return { ok: false, code: 'INVALID_DELEGATION', reason: 'Agent fullaccess policy was revoked', diagnosticCause: 'task_replaced' };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
40
69
|
return { ok: true, grant };
|
|
41
70
|
}
|
|
42
71
|
const approved = commandHash
|
|
43
|
-
? this.
|
|
72
|
+
? this.readApprovedCommand(tokenHash, sessionId, commandHash, consumeApproval)
|
|
44
73
|
: undefined;
|
|
45
74
|
if (approved)
|
|
46
75
|
return { ok: true, grant: approved };
|
|
@@ -66,11 +95,12 @@ export class AgentDelegationRegistry {
|
|
|
66
95
|
const grant = activeTokenHash ? this.grantsByHash.get(activeTokenHash) : undefined;
|
|
67
96
|
if (!grant || grant.taskId !== input.taskId)
|
|
68
97
|
return false;
|
|
69
|
-
|
|
98
|
+
const sessionToken = input.sessionToken ?? input.carrierToken;
|
|
99
|
+
if (!sessionToken || !isDelegationCommandHash(input.commandHash))
|
|
70
100
|
return false;
|
|
71
101
|
this.deleteExpiredApprovedCommands();
|
|
72
|
-
const
|
|
73
|
-
const key = approvedCommandKey(
|
|
102
|
+
const sessionTokenHash = hashDelegationToken(sessionToken);
|
|
103
|
+
const key = approvedCommandKey(sessionTokenHash, input.sessionId, input.commandHash);
|
|
74
104
|
const existing = this.approvedCommands.get(key);
|
|
75
105
|
const ttlMs = Math.max(1, Math.min(input.ttlMs ?? AGENT_DELEGATION_COMMAND_TTL_MS, AGENT_DELEGATION_COMMAND_TTL_MS));
|
|
76
106
|
this.approvedCommands.set(key, {
|
|
@@ -105,10 +135,10 @@ export class AgentDelegationRegistry {
|
|
|
105
135
|
this.activeHashBySession.delete(sessionId);
|
|
106
136
|
this.deleteApprovedCommandsForSession(sessionId);
|
|
107
137
|
}
|
|
108
|
-
|
|
138
|
+
readApprovedCommand(sessionTokenHash, sessionId, commandHash, consume) {
|
|
109
139
|
if (!isDelegationCommandHash(commandHash))
|
|
110
140
|
return undefined;
|
|
111
|
-
const key = approvedCommandKey(
|
|
141
|
+
const key = approvedCommandKey(sessionTokenHash, sessionId, commandHash);
|
|
112
142
|
const approved = this.approvedCommands.get(key);
|
|
113
143
|
if (!approved)
|
|
114
144
|
return undefined;
|
|
@@ -124,10 +154,12 @@ export class AgentDelegationRegistry {
|
|
|
124
154
|
this.approvedCommands.delete(key);
|
|
125
155
|
return undefined;
|
|
126
156
|
}
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
157
|
+
if (consume) {
|
|
158
|
+
if (approved.remainingUses <= 1)
|
|
159
|
+
this.approvedCommands.delete(key);
|
|
160
|
+
else
|
|
161
|
+
approved.remainingUses--;
|
|
162
|
+
}
|
|
131
163
|
return grant;
|
|
132
164
|
}
|
|
133
165
|
deleteExpiredApprovedCommands() {
|
|
@@ -216,8 +248,10 @@ function isValidFullAccessExecutionIdentity(identity) {
|
|
|
216
248
|
|| identity.processRole !== 'fullaccess-run'
|
|
217
249
|
|| identity.dataScope !== 'daemon')
|
|
218
250
|
return false;
|
|
219
|
-
if (identity.source === 'fullaccess-command') {
|
|
220
|
-
return typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
251
|
+
if (identity.source === 'fullaccess-command' || identity.source === 'agent-policy') {
|
|
252
|
+
return typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
253
|
+
&& (identity.source !== 'agent-policy'
|
|
254
|
+
|| (typeof identity.agentAid === 'string' && identity.agentAid.length > 0));
|
|
221
255
|
}
|
|
222
256
|
return identity.source === 'trigger'
|
|
223
257
|
&& typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
@@ -228,8 +262,8 @@ function isValidFullAccessExecutionIdentity(identity) {
|
|
|
228
262
|
function hashDelegationToken(token) {
|
|
229
263
|
return crypto.createHash('sha256').update(token).digest('hex');
|
|
230
264
|
}
|
|
231
|
-
function approvedCommandKey(
|
|
232
|
-
return JSON.stringify([
|
|
265
|
+
function approvedCommandKey(sessionTokenHash, sessionId, commandHash) {
|
|
266
|
+
return JSON.stringify([sessionTokenHash, sessionId, commandHash]);
|
|
233
267
|
}
|
|
234
268
|
function isDelegationCommandHash(value) {
|
|
235
269
|
return /^[a-f0-9]{64}$/.test(value);
|
|
@@ -70,6 +70,7 @@ export function buildAuthSubject(input) {
|
|
|
70
70
|
identity: input.identity ?? roleToSessionIdentity(role === 'none' ? null : role),
|
|
71
71
|
isDaemonOwner,
|
|
72
72
|
fromControlChannel: !!input.fromControlChannel,
|
|
73
|
+
ecwebManagement: input.ecwebManagement === true && isTrustedDaemonOwner && input.fromControlChannel === true,
|
|
73
74
|
allowAccess: processRole !== 'none' || (suppliedRole
|
|
74
75
|
? checkRoleAccess(suppliedRole, input.selfAid)
|
|
75
76
|
: roleDetail.allowAccess && checkRoleAccess(role, input.selfAid)),
|
|
@@ -114,6 +115,7 @@ export function authorizeOperation(params) {
|
|
|
114
115
|
return access;
|
|
115
116
|
}
|
|
116
117
|
const ctx = {
|
|
118
|
+
principalId: params.subject.principalId,
|
|
117
119
|
intent: params.intent,
|
|
118
120
|
actorId: params.subject.actorId,
|
|
119
121
|
channel: params.subject.channel,
|
|
@@ -2,7 +2,7 @@ import { logger } from '../../utils/logger.js';
|
|
|
2
2
|
import { LogWriter } from '../../utils/log-writer.js';
|
|
3
3
|
import { resolvePaths } from '../../paths.js';
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
|
-
import { buildAuthorizationEventKey, buildToolLifecycleEventKey } from '../audit/event-key.js';
|
|
5
|
+
import { buildAuthorizationEventKey, buildToolLifecycleEventKey, normalizeToolCorrelation } from '../audit/event-key.js';
|
|
6
6
|
import { normalizeExecutionPermissionMode } from '../permission/mode.js';
|
|
7
7
|
export function auditCommandAuthorization(event) {
|
|
8
8
|
const shouldAudit = event.source === 'menu.cli' ||
|
|
@@ -14,6 +14,8 @@ export function auditCommandAuthorization(event) {
|
|
|
14
14
|
event.operation === 'permission.runtime' ||
|
|
15
15
|
event.operation.startsWith('fullaccess.') ||
|
|
16
16
|
event.operation.startsWith('role.') ||
|
|
17
|
+
event.operation === 'file.root.storage' ||
|
|
18
|
+
event.operation.startsWith('file.storage.') ||
|
|
17
19
|
event.operation === 'cli.exec.raw';
|
|
18
20
|
if (!shouldAudit)
|
|
19
21
|
return;
|
|
@@ -25,6 +27,8 @@ export function auditFullAccessEvent(input) {
|
|
|
25
27
|
|| input.event === 'fullaccess.trigger.deescalated';
|
|
26
28
|
const executionEvent = input.event === 'fullaccess.execution.started'
|
|
27
29
|
|| input.event === 'fullaccess.execution.ended'
|
|
30
|
+
|| input.event === 'fullaccess.agent-policy.execution.started'
|
|
31
|
+
|| input.event === 'fullaccess.agent-policy.execution.ended'
|
|
28
32
|
|| input.event === 'fullaccess.trigger.execution.started'
|
|
29
33
|
|| input.event === 'fullaccess.trigger.execution.ended';
|
|
30
34
|
// The scheduler triggers a run, but the executing subject is the task-bound
|
|
@@ -39,7 +43,7 @@ export function auditFullAccessEvent(input) {
|
|
|
39
43
|
ts: Date.now(),
|
|
40
44
|
callId: input.attemptId ?? input.runId ?? input.taskId ?? input.messageId,
|
|
41
45
|
correlationId: input.runId ?? input.taskId,
|
|
42
|
-
source: input.source === '
|
|
46
|
+
source: input.source === 'fullaccess-command' ? 'slash' : 'agent-tool',
|
|
43
47
|
operation: input.event,
|
|
44
48
|
// CommandScope has no separate daemon value; process is the closest
|
|
45
49
|
// existing scope for daemon-owner authorization and keeps audit records
|
|
@@ -97,20 +101,11 @@ export function auditFullAccessEvent(input) {
|
|
|
97
101
|
* denials otherwise only reach `logger.warn` and `TriggerExecutionAnomaly`.
|
|
98
102
|
*/
|
|
99
103
|
export function auditToolPreflightDenial(input) {
|
|
100
|
-
const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
|
|
101
104
|
auditCommandAuthorization({
|
|
102
105
|
ts: Date.now(),
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
callId: input.callId,
|
|
107
|
-
correlationId: input.correlationId,
|
|
108
|
-
requestId: input.requestId,
|
|
109
|
-
}),
|
|
110
|
-
eventPhase: 'preflight',
|
|
111
|
-
} : {}),
|
|
112
|
-
callId: input.callId ?? input.requestId,
|
|
113
|
-
correlationId: input.correlationId ?? input.callId ?? input.requestId,
|
|
106
|
+
eventKey: buildToolLifecycleEventKey(input),
|
|
107
|
+
eventPhase: 'preflight',
|
|
108
|
+
...normalizeToolCorrelation(input),
|
|
114
109
|
source: 'agent-tool',
|
|
115
110
|
operation: 'tool.preflight.deny',
|
|
116
111
|
scope: 'filesystem',
|
|
@@ -138,29 +133,20 @@ export function auditToolPreflightDenial(input) {
|
|
|
138
133
|
role: input.role ?? 'unknown',
|
|
139
134
|
selfAid: input.selfAid,
|
|
140
135
|
peerKey: input.peerKey,
|
|
141
|
-
requestId: input.requestId,
|
|
142
136
|
taskId: input.taskId,
|
|
143
137
|
});
|
|
144
138
|
}
|
|
145
139
|
/**
|
|
146
140
|
* Emit one canonical, structured record for every Codex app-server approval
|
|
147
|
-
* decision.
|
|
141
|
+
* decision. Prefer the tool call id over the approval request id; method and
|
|
148
142
|
* decision source make policy, user, and infrastructure denials distinct.
|
|
149
143
|
*/
|
|
150
144
|
export function auditCodexApprovalDecision(input) {
|
|
151
|
-
const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
|
|
152
145
|
auditCommandAuthorization({
|
|
153
146
|
ts: Date.now(),
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
callId: input.callId,
|
|
158
|
-
correlationId: input.correlationId,
|
|
159
|
-
requestId: input.requestId,
|
|
160
|
-
}),
|
|
161
|
-
eventPhase: 'approval',
|
|
162
|
-
} : {}),
|
|
163
|
-
callId: input.callId ?? input.correlationId ?? input.requestId,
|
|
147
|
+
eventKey: buildToolLifecycleEventKey(input),
|
|
148
|
+
eventPhase: 'approval',
|
|
149
|
+
...normalizeToolCorrelation(input),
|
|
164
150
|
source: 'agent-tool',
|
|
165
151
|
operation: 'codex.approval',
|
|
166
152
|
scope: input.toolName === 'PermissionGrant' ? 'agent' : 'filesystem',
|
|
@@ -172,8 +158,6 @@ export function auditCodexApprovalDecision(input) {
|
|
|
172
158
|
errorCode: input.decision === 'deny'
|
|
173
159
|
? input.decisionSource === 'approval' ? 'USER_DENIED' : input.decisionSource === 'policy' ? 'POLICY_DENIED' : infrastructureErrorCode(input.policyCode)
|
|
174
160
|
: undefined,
|
|
175
|
-
requestId: input.requestId,
|
|
176
|
-
correlationId: input.correlationId ?? input.callId ?? input.requestId,
|
|
177
161
|
sessionId: input.sessionId ?? 'unknown',
|
|
178
162
|
agentAid: input.agentAid ?? 'unknown',
|
|
179
163
|
agentName: input.agentName,
|
|
@@ -204,6 +188,8 @@ export function auditPermissionDecision(input) {
|
|
|
204
188
|
auditPermissionLifecycle({
|
|
205
189
|
phase: 'final',
|
|
206
190
|
requestId,
|
|
191
|
+
callId: input.callId,
|
|
192
|
+
correlationId: input.correlationId,
|
|
207
193
|
sessionId: input.sessionId,
|
|
208
194
|
agentAid: input.agentAid,
|
|
209
195
|
agentName: input.agentName,
|
|
@@ -235,9 +221,7 @@ export function auditPermissionLifecycle(input) {
|
|
|
235
221
|
requestId: input.requestId,
|
|
236
222
|
}),
|
|
237
223
|
eventPhase: input.phase,
|
|
238
|
-
|
|
239
|
-
correlationId: input.correlationId ?? input.callId ?? input.requestId,
|
|
240
|
-
requestId: input.requestId,
|
|
224
|
+
...normalizeToolCorrelation(input),
|
|
241
225
|
sessionId,
|
|
242
226
|
agentAid: input.agentAid ?? 'unknown',
|
|
243
227
|
agentName: input.agentName,
|
|
@@ -279,6 +263,8 @@ export function auditPermissionLifecycle(input) {
|
|
|
279
263
|
export function auditToolInfrastructureFailure(input) {
|
|
280
264
|
auditCommandAuthorization({
|
|
281
265
|
ts: Date.now(),
|
|
266
|
+
eventKey: buildToolLifecycleEventKey(input),
|
|
267
|
+
eventPhase: 'final',
|
|
282
268
|
source: 'agent-tool',
|
|
283
269
|
operation: 'tool.execution.blocked',
|
|
284
270
|
scope: 'filesystem',
|
|
@@ -288,9 +274,7 @@ export function auditToolInfrastructureFailure(input) {
|
|
|
288
274
|
errorCode: infrastructureErrorCode(input.policyCode),
|
|
289
275
|
executed: false,
|
|
290
276
|
executionState: 'blocked',
|
|
291
|
-
|
|
292
|
-
correlationId: input.callId,
|
|
293
|
-
requestId: input.callId,
|
|
277
|
+
...normalizeToolCorrelation(input),
|
|
294
278
|
sessionId: input.sessionId ?? 'unknown',
|
|
295
279
|
agentAid: input.agentAid ?? 'unknown',
|
|
296
280
|
agentName: input.agentName,
|
|
@@ -300,8 +284,20 @@ export function auditToolInfrastructureFailure(input) {
|
|
|
300
284
|
reason: input.reason,
|
|
301
285
|
role: input.role ?? 'unknown',
|
|
302
286
|
taskId: input.taskId,
|
|
303
|
-
argsSummary: input.occurrences
|
|
304
|
-
? {
|
|
287
|
+
argsSummary: input.occurrences || input.sandboxFailure
|
|
288
|
+
? {
|
|
289
|
+
...(input.occurrences ? { occurrences: input.occurrences } : {}),
|
|
290
|
+
// Flat primitive fields survive the audit argument redactor and can
|
|
291
|
+
// be aggregated without reparsing (possibly truncated) stderr.
|
|
292
|
+
...(input.sandboxFailure ? {
|
|
293
|
+
sandboxFailureKind: input.sandboxFailure.kind,
|
|
294
|
+
sandboxFailureObservation: input.sandboxFailure.observation,
|
|
295
|
+
sandboxInitializationFailures: input.sandboxFailure.counters.total,
|
|
296
|
+
sandboxApplySeccompFailures: input.sandboxFailure.counters.applySeccomp,
|
|
297
|
+
...Object.fromEntries(Object.entries(input.sandboxFailure.counters.byKind)
|
|
298
|
+
.map(([kind, count]) => [`sandboxFailures_${kind}`, count])),
|
|
299
|
+
} : {}),
|
|
300
|
+
}
|
|
305
301
|
: undefined,
|
|
306
302
|
});
|
|
307
303
|
}
|
|
@@ -333,23 +329,31 @@ function buildAuditRecord(event) {
|
|
|
333
329
|
: executed
|
|
334
330
|
? (event.exitCode === undefined || event.exitCode === 0 ? 'completed' : 'failed')
|
|
335
331
|
: 'authorized');
|
|
336
|
-
const
|
|
332
|
+
const ecwebRequestId = event.source === 'ecweb' ? event.ecwebRequestId : undefined;
|
|
333
|
+
const requestId = event.requestId ?? ecwebRequestId;
|
|
334
|
+
const correlationId = event.correlationId ?? event.callId ?? requestId;
|
|
335
|
+
const lifecycleId = event.callId ?? correlationId ?? requestId;
|
|
337
336
|
return {
|
|
338
337
|
ts: event.ts,
|
|
339
|
-
eventKey: event.eventKey ?? (
|
|
340
|
-
?
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
338
|
+
eventKey: event.eventKey ?? (ecwebRequestId
|
|
339
|
+
? `ecweb:${crypto.createHash('sha256').update(JSON.stringify([
|
|
340
|
+
ecwebRequestId, event.targetAgentAid ?? event.selfAid, event.operation,
|
|
341
|
+
])).digest('hex').slice(0, 20)}`
|
|
342
|
+
: lifecycleId
|
|
343
|
+
? buildAuthorizationEventKey({
|
|
344
|
+
sessionId: event.sessionId,
|
|
345
|
+
callId: event.callId,
|
|
346
|
+
correlationId,
|
|
347
|
+
requestId,
|
|
348
|
+
operation: event.operation,
|
|
349
|
+
})
|
|
350
|
+
: undefined),
|
|
348
351
|
eventPhase: event.eventPhase,
|
|
349
352
|
callId: event.callId,
|
|
350
|
-
correlationId
|
|
351
|
-
requestId
|
|
353
|
+
correlationId,
|
|
354
|
+
requestId,
|
|
352
355
|
sessionId: event.sessionId,
|
|
356
|
+
...(event.source === 'ecweb' && !event.sessionId ? { sessionScope: 'not_applicable' } : {}),
|
|
353
357
|
agentAid: redactIdentifier(event.agentAid ?? event.selfAid),
|
|
354
358
|
agentName: resolveAgentName(event),
|
|
355
359
|
permissionMode: normalizeAuditPermissionMode(event.permissionMode),
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { getRoleDefinition } from '../../config/roles.js';
|
|
2
2
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
3
|
-
import { relationFieldWriteOperation, resolveRoleFieldPermission, } from '../../config/config-field-policy.js';
|
|
3
|
+
import { isUnsupportedBaseagentConfigField, relationFieldWriteOperation, resolveRoleFieldPermission, } from '../../config/config-field-policy.js';
|
|
4
4
|
import { isResolvedConfigMutation, } from '../../config/resolved-config-op.js';
|
|
5
5
|
import { normalizePeer } from '../model/config-scope.js';
|
|
6
6
|
import { getOperationMeta } from './operation-catalog.js';
|
|
7
7
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
8
|
+
import { sameGroupAid } from '../../aun/group/index.js';
|
|
8
9
|
const DAEMON_OWNER_PROCESS_OPERATIONS = new Set([
|
|
9
10
|
'agent.list',
|
|
10
11
|
'agent.create',
|
|
12
|
+
'agent.initialize',
|
|
11
13
|
'agent.delete',
|
|
12
14
|
'agent.enable',
|
|
13
15
|
'agent.disable',
|
|
14
16
|
'system.restart',
|
|
15
17
|
'system.upgrade',
|
|
18
|
+
'system.paircode',
|
|
16
19
|
'gateway.read',
|
|
17
20
|
'gateway.write',
|
|
18
21
|
'stats.peers',
|
|
@@ -98,6 +101,12 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
98
101
|
&& (resolvedConfigCommand.operationId !== operation || resolvedConfigCommand.commandScope !== intent.scope)) {
|
|
99
102
|
return denyDecision(ctx, 'NOT_ALLOWED', 'Resolved config operation does not match the command intent', operation, intent.scope, opMeta.dangerous);
|
|
100
103
|
}
|
|
104
|
+
if (resolvedConfigCommand?.kind === 'field'
|
|
105
|
+
&& resolvedConfigCommand.configScope === 'agent'
|
|
106
|
+
&& (resolvedConfigCommand.field === 'fullaccess' || resolvedConfigCommand.field.startsWith('fullaccess.'))
|
|
107
|
+
&& resolvedConfigCommand.subcommand !== 'get') {
|
|
108
|
+
return denyDecision(ctx, 'NOT_ALLOWED', 'Agent fullaccess can only be configured in ECWeb Agent settings', operation, intent.scope, true);
|
|
109
|
+
}
|
|
101
110
|
// A fullaccess task is deliberately a daemon-scoped runtime identity. It
|
|
102
111
|
// still has to name a known operation and pass structural command parsing,
|
|
103
112
|
// but relation/Agent role rules, source lists, and daemon-owner gates are
|
|
@@ -204,6 +213,14 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
204
213
|
matchedRule: 'self-agent-read',
|
|
205
214
|
};
|
|
206
215
|
}
|
|
216
|
+
// Cross-Agent reads are a management/control-plane capability. A custom
|
|
217
|
+
// role must not widen this boundary merely by granting itself agent.show.
|
|
218
|
+
if (operation === 'agent.show'
|
|
219
|
+
&& !isCurrentAgentTarget(ctx)
|
|
220
|
+
&& !isManagementRole(role)
|
|
221
|
+
&& !hasDaemonOwnerRole(ctx)) {
|
|
222
|
+
return denyDecision(ctx, 'NOT_ALLOWED', `${operation} may only target the current Agent from a managed Agent task.`, operation, intent.scope, opMeta.dangerous, undefined, 'TARGET_SELF_ONLY');
|
|
223
|
+
}
|
|
207
224
|
if (operation === 'agent.persona' && isCurrentAgentTarget(ctx)
|
|
208
225
|
&& (role === 'owner' || hasDaemonOwnerRole(ctx))) {
|
|
209
226
|
return {
|
|
@@ -215,11 +232,30 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
215
232
|
matchedRule: role === 'owner' ? 'agent-owner-persona' : 'daemon-owner-persona',
|
|
216
233
|
};
|
|
217
234
|
}
|
|
235
|
+
if (operation === 'agent.welcome') {
|
|
236
|
+
if (!isCurrentAgentTarget(ctx) || ctx.chatType !== 'private'
|
|
237
|
+
|| (role !== 'owner' && !(role === 'admin' && intent.args.action === 'show'))) {
|
|
238
|
+
return denyDecision(ctx, 'NOT_ALLOWED', 'welcome requires the current Agent Owner in private (admin may read)', operation, intent.scope, opMeta.dangerous);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
allow: true,
|
|
242
|
+
operation,
|
|
243
|
+
scope: intent.scope,
|
|
244
|
+
role,
|
|
245
|
+
dangerous: opMeta.dangerous,
|
|
246
|
+
matchedRule: 'self-agent-welcome',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
218
249
|
if (resolvedConfigCommand) {
|
|
219
250
|
const targetCheck = checkResolvedConfigTarget(ctx, resolvedConfigCommand);
|
|
220
251
|
if (!targetCheck.ok) {
|
|
221
252
|
return denyDecision(ctx, intent.scope === 'relation' ? 'ARGUMENT_MISMATCH' : 'SCOPE_MISMATCH', targetCheck.reason || 'Config target is outside the current task context', operation, intent.scope, opMeta.dangerous);
|
|
222
253
|
}
|
|
254
|
+
if (resolvedConfigCommand.kind === 'field'
|
|
255
|
+
&& resolvedConfigCommand.subcommand === 'get'
|
|
256
|
+
&& isUnsupportedBaseagentConfigField(resolvedConfigCommand.field)) {
|
|
257
|
+
return denyDecision(ctx, 'UNSUPPORTED_FIELD', `Config field ${resolvedConfigCommand.field} is not supported by this version`, operation, intent.scope, opMeta.dangerous);
|
|
258
|
+
}
|
|
223
259
|
const identityMutationCheck = checkManagementIdentityMutation(ctx, resolvedConfigCommand);
|
|
224
260
|
if (!identityMutationCheck.ok) {
|
|
225
261
|
return denyDecision(ctx, 'NOT_ALLOWED', identityMutationCheck.reason || 'Static management identities cannot be changed by this role', operation, intent.scope, opMeta.dangerous);
|
|
@@ -227,7 +263,9 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
227
263
|
if (!isManagementRole(role) && intent.scope !== 'relation') {
|
|
228
264
|
return denyDecision(ctx, 'SCOPE_MISMATCH', `Role ${role} may only access relation-scoped config`, operation, intent.scope, opMeta.dangerous);
|
|
229
265
|
}
|
|
230
|
-
if (role === 'owner'
|
|
266
|
+
if (role === 'owner'
|
|
267
|
+
&& isCurrentAgentTarget(ctx)
|
|
268
|
+
&& !isCrossRelationConfigRead(ctx, resolvedConfigCommand)) {
|
|
231
269
|
return {
|
|
232
270
|
allow: true,
|
|
233
271
|
operation,
|
|
@@ -627,6 +665,19 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
|
|
|
627
665
|
}
|
|
628
666
|
return { ok: true };
|
|
629
667
|
}
|
|
668
|
+
function isCrossRelationConfigRead(ctx, command) {
|
|
669
|
+
if (!command || command.kind !== 'field' || command.subcommand !== 'get' || command.configScope !== 'relation') {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
if (!command.peerKey || !ctx.peerKey)
|
|
673
|
+
return true;
|
|
674
|
+
try {
|
|
675
|
+
return normalizeRelationPeer(command.peerKey) !== normalizeRelationPeer(ctx.peerKey);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
630
681
|
function checkResolvedConfigTarget(ctx, command) {
|
|
631
682
|
if (command.kind === 'global' || command.configScope === 'process' || command.configScope === 'defaults') {
|
|
632
683
|
return { ok: true };
|
|
@@ -642,7 +693,13 @@ function checkResolvedConfigTarget(ctx, command) {
|
|
|
642
693
|
}
|
|
643
694
|
return { ok: true };
|
|
644
695
|
}
|
|
645
|
-
if (ctx.allowExplicitRelationTarget
|
|
696
|
+
if (ctx.allowExplicitRelationTarget
|
|
697
|
+
&& isManagementRole(ctx.role)
|
|
698
|
+
&& command.kind === 'field'
|
|
699
|
+
&& (command.operationId.startsWith('role.') || command.subcommand === 'get')) {
|
|
700
|
+
// Management reads may select another relation of this Agent. This only
|
|
701
|
+
// relaxes target matching; admin field grants/constraints are checked later.
|
|
702
|
+
// Ordinary config writes and whole-config commands remain current-relation only.
|
|
646
703
|
if (!ctx.selfAid || !command.self || command.self !== ctx.selfAid) {
|
|
647
704
|
return { ok: false, reason: 'Only the current agent relation can be targeted' };
|
|
648
705
|
}
|
|
@@ -682,7 +739,8 @@ function checkCurrentRelation(ctx, resolvedConfigCommand) {
|
|
|
682
739
|
return { ok: false, reason: 'Current relation context is required' };
|
|
683
740
|
}
|
|
684
741
|
try {
|
|
685
|
-
if (normalizeRelationPeer(requestedPeer) !== normalizeRelationPeer(ctx.peerKey)
|
|
742
|
+
if (normalizeRelationPeer(requestedPeer) !== normalizeRelationPeer(ctx.peerKey)
|
|
743
|
+
&& !sameAunGroupRelation(ctx, requestedPeer, ctx.peerKey)) {
|
|
686
744
|
if (ctx.intent.operation.startsWith('ec.group.')) {
|
|
687
745
|
const currentGroup = peerIdFromArg(ctx.peerKey);
|
|
688
746
|
const targetGroup = stringArg(ctx.intent.args.targetId) ?? peerIdFromArg(requestedPeer);
|
|
@@ -777,6 +835,20 @@ function collectStringValues(value, out) {
|
|
|
777
835
|
out.add(item);
|
|
778
836
|
}
|
|
779
837
|
}
|
|
838
|
+
function sameAunGroupRelation(ctx, requested, current) {
|
|
839
|
+
if (ctx.chatType !== 'group'
|
|
840
|
+
|| (!ctx.intent.operation.startsWith('ec.group.') && !ctx.intent.operation.startsWith('ec.fs.')))
|
|
841
|
+
return false;
|
|
842
|
+
try {
|
|
843
|
+
const target = parsePeerKey(requested);
|
|
844
|
+
const relation = parsePeerKey(current);
|
|
845
|
+
return target.channelType === 'aun' && relation.channelType === 'aun'
|
|
846
|
+
&& sameGroupAid(target.channelId, relation.channelId);
|
|
847
|
+
}
|
|
848
|
+
catch {
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
780
852
|
function checkOwnPeer(ctx) {
|
|
781
853
|
const argPeer = stringArg(ctx.intent.args.peer);
|
|
782
854
|
const argPeerKey = stringArg(ctx.intent.args.peerKey);
|
|
@@ -801,15 +873,16 @@ function checkOwnPeer(ctx) {
|
|
|
801
873
|
candidateKeys.add(`${legacyChannelType}::${argPeer}`);
|
|
802
874
|
}
|
|
803
875
|
return candidateKeys.has(expectedPeerKey)
|
|
876
|
+
|| [...candidateKeys].some(key => sameAunGroupRelation(ctx, key, expectedPeerKey))
|
|
804
877
|
? { ok: true }
|
|
805
878
|
: { ok: false, reason: 'Only the current group relation can be targeted' };
|
|
806
879
|
}
|
|
807
880
|
if (ctx.actorId) {
|
|
808
881
|
if (requestedPeerId && requestedPeerId !== ctx.actorId) {
|
|
809
|
-
return { ok: false, reason:
|
|
882
|
+
return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerId) };
|
|
810
883
|
}
|
|
811
884
|
if (requestedPeerKeyId && requestedPeerKeyId !== ctx.actorId) {
|
|
812
|
-
return { ok: false, reason:
|
|
885
|
+
return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerKeyId) };
|
|
813
886
|
}
|
|
814
887
|
}
|
|
815
888
|
if (!expectedPeerKey)
|
|
@@ -827,10 +900,20 @@ function checkOwnPeer(ctx) {
|
|
|
827
900
|
candidateKeys.add(`${legacyChannelType}::${argPeer}`);
|
|
828
901
|
}
|
|
829
902
|
if (!candidateKeys.has(expectedPeerKey)) {
|
|
830
|
-
return { ok: false, reason:
|
|
903
|
+
return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerId ?? requestedPeerKeyId) };
|
|
831
904
|
}
|
|
832
905
|
return { ok: true };
|
|
833
906
|
}
|
|
907
|
+
function ownPeerOnlyDenialReason(ctx, requestedPeer) {
|
|
908
|
+
if (ctx.intent.operation !== 'ec.msg.send' && ctx.intent.operation !== 'ec.msg.file') {
|
|
909
|
+
return 'Only the actor peer can be targeted';
|
|
910
|
+
}
|
|
911
|
+
const currentPeer = ctx.actorId || peerIdFromArg(ctx.peerKey || '') || '当前私聊对端';
|
|
912
|
+
const target = requestedPeer || '指定目标';
|
|
913
|
+
return `当前托管会话只能联系当前私聊对端(${currentPeer}),不能联系第三方目标(${target})。`
|
|
914
|
+
+ ' 如果本次请求来自 handoff,请执行 ec handoff return <handoff-id> "<失败原因>"(已有命令)将失败原因反馈给原会话;'
|
|
915
|
+
+ '否则请让目标先直接发起会话,或由 Agent owner 为当前角色配置允许 relay/第三方外联后再发送。';
|
|
916
|
+
}
|
|
834
917
|
function peerIdFromArg(value) {
|
|
835
918
|
try {
|
|
836
919
|
return parsePeerKey(value).channelId;
|