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
|
@@ -4,10 +4,17 @@ import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
|
4
4
|
export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
|
|
5
5
|
export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
|
|
6
6
|
export class AgentDelegationRegistry {
|
|
7
|
+
runtimeEpoch = crypto.randomBytes(16).toString('hex');
|
|
7
8
|
grantsByHash = new Map();
|
|
8
9
|
activeHashBySession = new Map();
|
|
9
10
|
approvedCommands = new Map();
|
|
11
|
+
getRuntimeEpoch() {
|
|
12
|
+
return this.runtimeEpoch;
|
|
13
|
+
}
|
|
10
14
|
issue(input) {
|
|
15
|
+
if (input.executionIdentity && !isValidFullAccessExecutionIdentity(input.executionIdentity)) {
|
|
16
|
+
throw new Error('invalid fullaccess task execution identity');
|
|
17
|
+
}
|
|
11
18
|
this.revokeSession(input.sessionId);
|
|
12
19
|
const token = crypto.randomBytes(32).toString('base64url');
|
|
13
20
|
const tokenHash = hashDelegationToken(token);
|
|
@@ -21,6 +28,7 @@ export class AgentDelegationRegistry {
|
|
|
21
28
|
ok: false,
|
|
22
29
|
code: 'DELEGATION_REQUIRED',
|
|
23
30
|
reason: 'An active task delegation token is required',
|
|
31
|
+
diagnosticCause: 'missing_token',
|
|
24
32
|
};
|
|
25
33
|
}
|
|
26
34
|
const tokenHash = hashDelegationToken(token);
|
|
@@ -39,6 +47,13 @@ export class AgentDelegationRegistry {
|
|
|
39
47
|
ok: false,
|
|
40
48
|
code: 'INVALID_DELEGATION',
|
|
41
49
|
reason: 'The task delegation token is invalid, revoked, or belongs to another session',
|
|
50
|
+
diagnosticCause: grant
|
|
51
|
+
? grant.sessionId !== sessionId
|
|
52
|
+
? 'session_mismatch'
|
|
53
|
+
: 'task_replaced'
|
|
54
|
+
: commandHash
|
|
55
|
+
? 'approved_command_unavailable'
|
|
56
|
+
: 'unknown_token',
|
|
42
57
|
};
|
|
43
58
|
}
|
|
44
59
|
armApprovedCommand(input) {
|
|
@@ -134,7 +149,7 @@ export function authorizeDelegatedAunMsgSend(registry, input) {
|
|
|
134
149
|
if (!validation.ok)
|
|
135
150
|
return validation;
|
|
136
151
|
const grant = validation.grant;
|
|
137
|
-
if (grant.selfAid !== input.aid) {
|
|
152
|
+
if (grant.selfAid !== input.aid && !hasTrustedFullAccessDelegation(grant)) {
|
|
138
153
|
return { ok: false, code: 'INVALID_DELEGATION', reason: 'Delegation self agent does not match sender' };
|
|
139
154
|
}
|
|
140
155
|
if (grant.messageId && grant.messageId !== input.messageId) {
|
|
@@ -157,6 +172,7 @@ export function authorizeDelegatedAunMsgSend(registry, input) {
|
|
|
157
172
|
conversationId: channelId,
|
|
158
173
|
processOwners: [],
|
|
159
174
|
fromControlChannel: false,
|
|
175
|
+
...trustedExecutionIdentityInput(grant),
|
|
160
176
|
});
|
|
161
177
|
const subject = { ...builtSubject, peerKey: grant.peerKey };
|
|
162
178
|
const decision = authorizeOperation({
|
|
@@ -180,6 +196,32 @@ export function authorizeDelegatedAunMsgSend(registry, input) {
|
|
|
180
196
|
}
|
|
181
197
|
return { ok: true, grant };
|
|
182
198
|
}
|
|
199
|
+
/** Build the only AuthGateway input shape accepted for a task-held fullaccess claim. */
|
|
200
|
+
export function trustedExecutionIdentityInput(grant) {
|
|
201
|
+
return grant.executionIdentity && isValidFullAccessExecutionIdentity(grant.executionIdentity)
|
|
202
|
+
? {
|
|
203
|
+
trustedProcessRole: 'fullaccess-run',
|
|
204
|
+
trustedFullAccessAuthorization: grant.executionIdentity,
|
|
205
|
+
}
|
|
206
|
+
: {};
|
|
207
|
+
}
|
|
208
|
+
export function hasTrustedFullAccessDelegation(grant) {
|
|
209
|
+
return !!grant.executionIdentity && isValidFullAccessExecutionIdentity(grant.executionIdentity);
|
|
210
|
+
}
|
|
211
|
+
function isValidFullAccessExecutionIdentity(identity) {
|
|
212
|
+
if (identity.permissionMode !== 'fullaccess'
|
|
213
|
+
|| identity.processRole !== 'fullaccess-run'
|
|
214
|
+
|| identity.dataScope !== 'daemon')
|
|
215
|
+
return false;
|
|
216
|
+
if (identity.source === 'fullaccess-command') {
|
|
217
|
+
return typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0;
|
|
218
|
+
}
|
|
219
|
+
return identity.source === 'trigger'
|
|
220
|
+
&& typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
221
|
+
&& typeof identity.triggerId === 'string' && identity.triggerId.length > 0
|
|
222
|
+
&& typeof identity.runId === 'string' && identity.runId.length > 0
|
|
223
|
+
&& typeof identity.attemptId === 'string' && identity.attemptId.length > 0;
|
|
224
|
+
}
|
|
183
225
|
function hashDelegationToken(token) {
|
|
184
226
|
return crypto.createHash('sha256').update(token).digest('hex');
|
|
185
227
|
}
|
|
@@ -8,6 +8,11 @@ import { resolveRuntimePermissionMode } from '../role/runtime-policy.js';
|
|
|
8
8
|
export function isCrossAgentTriggerOperationAllowed(input) {
|
|
9
9
|
return input.control || input.daemonOwner === true || input.taskAgentAid === input.targetAgentAid;
|
|
10
10
|
}
|
|
11
|
+
/** A delegated identity may authorize ordinary work, but it is not a human approval for persistent host execution. */
|
|
12
|
+
export function canApprovePersistentFullAccessTrigger(input) {
|
|
13
|
+
return input.daemonOwner
|
|
14
|
+
&& (input.source === 'direct-user' || input.source === 'management-channel');
|
|
15
|
+
}
|
|
11
16
|
export function buildAuthSubject(input) {
|
|
12
17
|
const chatType = input.chatType === 'group' ? 'group' : 'private';
|
|
13
18
|
const actorId = input.actorId;
|
|
@@ -22,17 +27,31 @@ export function buildAuthSubject(input) {
|
|
|
22
27
|
peerType: input.peerType,
|
|
23
28
|
});
|
|
24
29
|
const processOwners = input.processOwners ?? [];
|
|
30
|
+
const fullAccessAuthorization = input.trustedFullAccessAuthorization;
|
|
31
|
+
const isFullAccessRun = input.trustedProcessRole === 'fullaccess-run'
|
|
32
|
+
&& fullAccessAuthorization?.permissionMode === 'fullaccess'
|
|
33
|
+
&& fullAccessAuthorization.processRole === 'fullaccess-run'
|
|
34
|
+
&& fullAccessAuthorization.dataScope === 'daemon';
|
|
25
35
|
const isDaemonService = input.trustedProcessRole === 'daemon-service';
|
|
26
|
-
const isDaemonOwner = !isDaemonService && isProcessOwner({
|
|
36
|
+
const isDaemonOwner = !isDaemonService && !isFullAccessRun && isProcessOwner({
|
|
27
37
|
actor: roleDetail.actor,
|
|
28
38
|
processOwners,
|
|
29
39
|
});
|
|
30
40
|
const suppliedRole = input.identity?.role && input.identity.role !== 'none' ? input.identity.role : undefined;
|
|
31
41
|
const role = suppliedRole ?? roleDetail.effectiveRole ?? 'none';
|
|
32
|
-
const processRole =
|
|
42
|
+
const processRole = isFullAccessRun
|
|
43
|
+
? 'fullaccess-run'
|
|
44
|
+
: isDaemonService ? 'daemon-service' : isDaemonOwner ? 'daemon-owner' : 'none';
|
|
45
|
+
const dataScope = processRole === 'fullaccess-run'
|
|
46
|
+
|| processRole === 'daemon-service'
|
|
47
|
+
|| processRole === 'daemon-owner'
|
|
48
|
+
? 'daemon'
|
|
49
|
+
: role === 'owner' || role === 'admin' ? 'agent' : 'relation';
|
|
33
50
|
return {
|
|
34
51
|
selfAid: input.selfAid,
|
|
52
|
+
agentName: input.agentName,
|
|
35
53
|
actorId,
|
|
54
|
+
principalId: roleDetail.actor.principalId,
|
|
36
55
|
requestId: input.requestId,
|
|
37
56
|
sessionId: input.sessionId,
|
|
38
57
|
channel: input.channel,
|
|
@@ -44,6 +63,8 @@ export function buildAuthSubject(input) {
|
|
|
44
63
|
role,
|
|
45
64
|
relationRole: role,
|
|
46
65
|
processRole,
|
|
66
|
+
dataScope,
|
|
67
|
+
authorizedBy: isFullAccessRun ? fullAccessAuthorization.authorizedBy : undefined,
|
|
47
68
|
roleSource: roleDetail.source,
|
|
48
69
|
identity: input.identity ?? roleToSessionIdentity(role === 'none' ? null : role),
|
|
49
70
|
isDaemonOwner,
|
|
@@ -51,8 +72,12 @@ export function buildAuthSubject(input) {
|
|
|
51
72
|
allowAccess: processRole !== 'none' || (suppliedRole
|
|
52
73
|
? checkRoleAccess(suppliedRole, input.selfAid)
|
|
53
74
|
: roleDetail.allowAccess && checkRoleAccess(role, input.selfAid)),
|
|
54
|
-
|
|
55
|
-
??
|
|
75
|
+
canApprovePersistentFullAccess: input.canApprovePersistentFullAccess
|
|
76
|
+
?? (isDaemonOwner && !isDaemonService),
|
|
77
|
+
permissionMode: isFullAccessRun
|
|
78
|
+
? 'fullaccess'
|
|
79
|
+
: input.permissionMode
|
|
80
|
+
?? resolveRuntimePermissionMode({ selfAid: input.selfAid, role }).effectiveValue,
|
|
56
81
|
};
|
|
57
82
|
}
|
|
58
83
|
function isProcessOwner(input) {
|
|
@@ -97,6 +122,7 @@ export function authorizeOperation(params) {
|
|
|
97
122
|
peerKey: params.subject.peerKey,
|
|
98
123
|
role: params.subject.role,
|
|
99
124
|
processRole: params.subject.processRole,
|
|
125
|
+
dataScope: params.subject.dataScope,
|
|
100
126
|
isDaemonOwner: params.subject.isDaemonOwner,
|
|
101
127
|
fromControlChannel: params.subject.fromControlChannel,
|
|
102
128
|
allowExplicitRelationTarget: params.allowExplicitRelationTarget,
|
|
@@ -162,9 +188,18 @@ function auditDecision(params, decision) {
|
|
|
162
188
|
requestId: params.auditMetadata?.requestId ?? params.subject.requestId,
|
|
163
189
|
sessionId: params.auditMetadata?.sessionId ?? params.subject.sessionId,
|
|
164
190
|
agentAid: params.auditMetadata?.agentAid ?? params.subject.selfAid,
|
|
191
|
+
agentName: params.auditMetadata?.agentName ?? params.subject.agentName ?? params.subject.selfAid,
|
|
165
192
|
permissionMode: params.auditMetadata?.permissionMode
|
|
166
193
|
?? (decision.allow ? decision.permissionMode ?? params.subject.permissionMode : params.subject.permissionMode),
|
|
167
194
|
toolName: params.auditMetadata?.toolName,
|
|
195
|
+
errorCode: params.auditMetadata?.errorCode
|
|
196
|
+
?? (decision.allow
|
|
197
|
+
? undefined
|
|
198
|
+
: params.auditMetadata?.decisionSource === 'approval'
|
|
199
|
+
? 'USER_DENIED'
|
|
200
|
+
: params.auditMetadata?.decisionSource === 'infrastructure'
|
|
201
|
+
? 'EXECUTION_FAILED'
|
|
202
|
+
: 'POLICY_DENIED'),
|
|
168
203
|
policyCode: params.auditMetadata?.policyCode ?? (decision.allow ? undefined : decision.code),
|
|
169
204
|
decisionSource: params.auditMetadata?.decisionSource ?? (decision.allow ? 'command' : 'policy'),
|
|
170
205
|
source: params.source,
|
|
@@ -178,6 +213,8 @@ function auditDecision(params, decision) {
|
|
|
178
213
|
channelId: params.subject.channelId,
|
|
179
214
|
role: decision.allow ? decision.role : params.subject.role,
|
|
180
215
|
processRole: params.subject.processRole,
|
|
216
|
+
dataScope: params.subject.dataScope,
|
|
217
|
+
authorizedBy: params.subject.authorizedBy,
|
|
181
218
|
isDaemonOwner: params.subject.isDaemonOwner,
|
|
182
219
|
fromControlChannel: params.subject.fromControlChannel,
|
|
183
220
|
decision: decision.allow ? 'allow' : 'deny',
|
|
@@ -2,13 +2,15 @@ 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 } from '../audit/event-key.js';
|
|
5
|
+
import { buildAuthorizationEventKey, buildToolLifecycleEventKey } from '../audit/event-key.js';
|
|
6
|
+
import { normalizeExecutionPermissionMode } from '../permission/mode.js';
|
|
6
7
|
export function auditCommandAuthorization(event) {
|
|
7
8
|
const shouldAudit = event.source === 'menu.cli' ||
|
|
8
9
|
event.decision === 'deny' ||
|
|
9
10
|
(event.decision === 'allow' && event.dangerous) ||
|
|
10
11
|
(event.source === 'agent-tool' && event.operation.startsWith('config.')) ||
|
|
11
12
|
event.operation === 'codex.approval' ||
|
|
13
|
+
event.operation.startsWith('fullaccess.') ||
|
|
12
14
|
event.operation.startsWith('role.') ||
|
|
13
15
|
event.operation === 'cli.exec.raw';
|
|
14
16
|
if (!shouldAudit)
|
|
@@ -16,16 +18,97 @@ export function auditCommandAuthorization(event) {
|
|
|
16
18
|
const auditRecord = buildAuditRecord(event);
|
|
17
19
|
logAuditEvent(auditRecord);
|
|
18
20
|
}
|
|
21
|
+
export function auditFullAccessEvent(input) {
|
|
22
|
+
const configurationEvent = input.event === 'fullaccess.trigger.configured'
|
|
23
|
+
|| input.event === 'fullaccess.trigger.deescalated';
|
|
24
|
+
const executionEvent = input.event === 'fullaccess.execution.started'
|
|
25
|
+
|| input.event === 'fullaccess.execution.ended'
|
|
26
|
+
|| input.event === 'fullaccess.trigger.execution.started'
|
|
27
|
+
|| input.event === 'fullaccess.trigger.execution.ended';
|
|
28
|
+
// The scheduler triggers a run, but the executing subject is the task-bound
|
|
29
|
+
// fullaccess identity. Keep the original owner/configuration provenance in
|
|
30
|
+
// a separate field rather than reporting the model task as a human owner.
|
|
31
|
+
const processRole = input.processRole
|
|
32
|
+
?? (executionEvent ? 'fullaccess-run' : input.source === 'trigger' ? 'daemon-service' : 'none');
|
|
33
|
+
const actorRole = processRole === 'daemon-owner' ? 'daemon-owner' : processRole;
|
|
34
|
+
const runnerProfileActive = executionEvent && input.executionState !== 'blocked';
|
|
35
|
+
const completedEvent = input.event.endsWith('.ended') || configurationEvent;
|
|
36
|
+
auditCommandAuthorization({
|
|
37
|
+
ts: Date.now(),
|
|
38
|
+
callId: input.attemptId ?? input.runId ?? input.taskId ?? input.messageId,
|
|
39
|
+
correlationId: input.runId ?? input.taskId,
|
|
40
|
+
source: input.source === 'trigger' ? 'agent-tool' : 'slash',
|
|
41
|
+
operation: input.event,
|
|
42
|
+
// CommandScope has no separate daemon value; process is the closest
|
|
43
|
+
// existing scope for daemon-owner authorization and keeps audit records
|
|
44
|
+
// compatible with the established command-audit schema.
|
|
45
|
+
scope: 'process',
|
|
46
|
+
dangerous: true,
|
|
47
|
+
decision: input.decision ?? 'allow',
|
|
48
|
+
executed: input.executed ?? completedEvent,
|
|
49
|
+
executionState: input.executionState
|
|
50
|
+
?? (input.decision === 'deny' ? 'blocked' : completedEvent ? 'completed' : 'authorized'),
|
|
51
|
+
decisionSource: input.decision === 'deny'
|
|
52
|
+
? 'policy'
|
|
53
|
+
: configurationEvent
|
|
54
|
+
? processRole === 'daemon-owner' ? 'daemon-owner' : 'policy'
|
|
55
|
+
: processRole === 'fullaccess-run'
|
|
56
|
+
? 'fullaccess-run'
|
|
57
|
+
: input.source === 'trigger'
|
|
58
|
+
? 'trigger-definition'
|
|
59
|
+
: 'daemon-owner',
|
|
60
|
+
actorId: input.actorId,
|
|
61
|
+
processRole,
|
|
62
|
+
dataScope: input.dataScope ?? (executionEvent ? 'daemon' : undefined),
|
|
63
|
+
authorizedBy: input.authorizedBy,
|
|
64
|
+
isDaemonOwner: processRole === 'daemon-owner',
|
|
65
|
+
agentAid: input.agentAid,
|
|
66
|
+
agentName: input.agentName,
|
|
67
|
+
sessionId: input.sessionId,
|
|
68
|
+
taskId: input.taskId,
|
|
69
|
+
messageId: input.messageId,
|
|
70
|
+
permissionMode: 'fullaccess',
|
|
71
|
+
role: actorRole,
|
|
72
|
+
reason: input.reason ?? input.result ?? input.event,
|
|
73
|
+
durationMs: input.durationMs,
|
|
74
|
+
argsSummary: {
|
|
75
|
+
source: input.source,
|
|
76
|
+
triggerId: input.triggerId,
|
|
77
|
+
runId: input.runId,
|
|
78
|
+
attemptId: input.attemptId,
|
|
79
|
+
baseagent: input.baseagent,
|
|
80
|
+
model: input.model,
|
|
81
|
+
dataScope: input.dataScope ?? (executionEvent ? 'daemon' : undefined),
|
|
82
|
+
authorizedBy: input.authorizedBy,
|
|
83
|
+
...(runnerProfileActive ? {
|
|
84
|
+
sandbox: 'off',
|
|
85
|
+
approval: 'bypassed',
|
|
86
|
+
applicationPolicy: 'bypassed',
|
|
87
|
+
} : {}),
|
|
88
|
+
result: input.result,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
19
92
|
/**
|
|
20
93
|
* Record a tool-level preflight denial (e.g. protected-path block) in the
|
|
21
94
|
* structured command-audit log. This covers interactive sessions whose
|
|
22
95
|
* denials otherwise only reach `logger.warn` and `TriggerExecutionAnomaly`.
|
|
23
96
|
*/
|
|
24
97
|
export function auditToolPreflightDenial(input) {
|
|
98
|
+
const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
|
|
25
99
|
auditCommandAuthorization({
|
|
26
100
|
ts: Date.now(),
|
|
27
|
-
|
|
28
|
-
|
|
101
|
+
...(lifecycleId ? {
|
|
102
|
+
eventKey: buildToolLifecycleEventKey({
|
|
103
|
+
sessionId: input.sessionId,
|
|
104
|
+
callId: input.callId,
|
|
105
|
+
correlationId: input.correlationId,
|
|
106
|
+
requestId: input.requestId,
|
|
107
|
+
}),
|
|
108
|
+
eventPhase: 'preflight',
|
|
109
|
+
} : {}),
|
|
110
|
+
callId: input.callId ?? input.requestId,
|
|
111
|
+
correlationId: input.correlationId ?? input.callId ?? input.requestId,
|
|
29
112
|
source: 'agent-tool',
|
|
30
113
|
operation: 'tool.preflight.deny',
|
|
31
114
|
scope: 'filesystem',
|
|
@@ -34,6 +117,7 @@ export function auditToolPreflightDenial(input) {
|
|
|
34
117
|
executed: false,
|
|
35
118
|
executionState: 'blocked',
|
|
36
119
|
decisionSource: 'policy',
|
|
120
|
+
errorCode: 'POLICY_DENIED',
|
|
37
121
|
toolName: input.toolName,
|
|
38
122
|
policyCode: input.policyCode,
|
|
39
123
|
protectionClass: input.protectionClass ?? protectionClassForPolicy(input.policyCode),
|
|
@@ -45,6 +129,7 @@ export function auditToolPreflightDenial(input) {
|
|
|
45
129
|
argsSummary: input.summary ? { summary: input.summary } : undefined,
|
|
46
130
|
sessionId: input.sessionId ?? 'unknown',
|
|
47
131
|
agentAid: input.agentAid ?? 'unknown',
|
|
132
|
+
agentName: input.agentName,
|
|
48
133
|
permissionMode: input.permissionMode ?? 'unknown',
|
|
49
134
|
channel: input.channel,
|
|
50
135
|
actorId: input.actorId,
|
|
@@ -61,9 +146,19 @@ export function auditToolPreflightDenial(input) {
|
|
|
61
146
|
* decision source make policy, user, and infrastructure denials distinct.
|
|
62
147
|
*/
|
|
63
148
|
export function auditCodexApprovalDecision(input) {
|
|
149
|
+
const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
|
|
64
150
|
auditCommandAuthorization({
|
|
65
151
|
ts: Date.now(),
|
|
66
|
-
|
|
152
|
+
...(lifecycleId ? {
|
|
153
|
+
eventKey: buildToolLifecycleEventKey({
|
|
154
|
+
sessionId: input.sessionId,
|
|
155
|
+
callId: input.callId,
|
|
156
|
+
correlationId: input.correlationId,
|
|
157
|
+
requestId: input.requestId,
|
|
158
|
+
}),
|
|
159
|
+
eventPhase: 'approval',
|
|
160
|
+
} : {}),
|
|
161
|
+
callId: input.callId ?? input.correlationId ?? input.requestId,
|
|
67
162
|
source: 'agent-tool',
|
|
68
163
|
operation: 'codex.approval',
|
|
69
164
|
scope: input.toolName === 'PermissionGrant' ? 'agent' : 'filesystem',
|
|
@@ -72,10 +167,14 @@ export function auditCodexApprovalDecision(input) {
|
|
|
72
167
|
executed: false,
|
|
73
168
|
executionState: input.decision === 'allow' ? 'authorized' : 'blocked',
|
|
74
169
|
decisionSource: input.decisionSource,
|
|
170
|
+
errorCode: input.decision === 'deny'
|
|
171
|
+
? input.decisionSource === 'approval' ? 'USER_DENIED' : input.decisionSource === 'policy' ? 'POLICY_DENIED' : infrastructureErrorCode(input.policyCode)
|
|
172
|
+
: undefined,
|
|
75
173
|
requestId: input.requestId,
|
|
76
|
-
correlationId: input.correlationId ?? input.requestId,
|
|
174
|
+
correlationId: input.correlationId ?? input.callId ?? input.requestId,
|
|
77
175
|
sessionId: input.sessionId ?? 'unknown',
|
|
78
176
|
agentAid: input.agentAid ?? 'unknown',
|
|
177
|
+
agentName: input.agentName,
|
|
79
178
|
permissionMode: input.permissionMode ?? 'unknown',
|
|
80
179
|
toolName: input.toolName,
|
|
81
180
|
approvalMethod: input.method,
|
|
@@ -84,7 +183,45 @@ export function auditCodexApprovalDecision(input) {
|
|
|
84
183
|
reason: input.reason,
|
|
85
184
|
role: input.role ?? 'unknown',
|
|
86
185
|
taskId: input.taskId,
|
|
87
|
-
argsSummary: {
|
|
186
|
+
argsSummary: {
|
|
187
|
+
method: input.method,
|
|
188
|
+
...(input.parseFailure ? { parseFailure: input.parseFailure } : {}),
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Record a runtime permission decision outside a Codex app-server approval.
|
|
194
|
+
* The shared command-audit stream distinguishes policy, human, and
|
|
195
|
+
* infrastructure denials without parsing localized log text.
|
|
196
|
+
*/
|
|
197
|
+
export function auditPermissionDecision(input) {
|
|
198
|
+
auditCommandAuthorization({
|
|
199
|
+
ts: Date.now(),
|
|
200
|
+
callId: input.requestId,
|
|
201
|
+
correlationId: input.requestId,
|
|
202
|
+
requestId: input.requestId,
|
|
203
|
+
sessionId: input.sessionId,
|
|
204
|
+
agentAid: input.agentAid,
|
|
205
|
+
agentName: input.agentName,
|
|
206
|
+
permissionMode: input.permissionMode,
|
|
207
|
+
toolName: input.toolName,
|
|
208
|
+
policyCode: input.policyCode,
|
|
209
|
+
decisionSource: input.decisionSource,
|
|
210
|
+
errorCode: input.decisionSource === 'approval'
|
|
211
|
+
? 'USER_DENIED'
|
|
212
|
+
: input.decisionSource === 'policy'
|
|
213
|
+
? 'POLICY_DENIED'
|
|
214
|
+
: infrastructureErrorCode(input.policyCode),
|
|
215
|
+
source: 'agent-tool',
|
|
216
|
+
operation: 'permission.runtime',
|
|
217
|
+
scope: 'filesystem',
|
|
218
|
+
dangerous: false,
|
|
219
|
+
role: input.role ?? 'unknown',
|
|
220
|
+
taskId: input.taskId,
|
|
221
|
+
decision: 'deny',
|
|
222
|
+
executed: false,
|
|
223
|
+
executionState: 'blocked',
|
|
224
|
+
reason: input.reason,
|
|
88
225
|
});
|
|
89
226
|
}
|
|
90
227
|
/** Record a fail-closed infrastructure condition before a tool can execute. */
|
|
@@ -97,6 +234,7 @@ export function auditToolInfrastructureFailure(input) {
|
|
|
97
234
|
dangerous: false,
|
|
98
235
|
decision: 'deny',
|
|
99
236
|
decisionSource: 'infrastructure',
|
|
237
|
+
errorCode: infrastructureErrorCode(input.policyCode),
|
|
100
238
|
executed: false,
|
|
101
239
|
executionState: 'blocked',
|
|
102
240
|
callId: input.callId,
|
|
@@ -104,6 +242,7 @@ export function auditToolInfrastructureFailure(input) {
|
|
|
104
242
|
requestId: input.callId,
|
|
105
243
|
sessionId: input.sessionId ?? 'unknown',
|
|
106
244
|
agentAid: input.agentAid ?? 'unknown',
|
|
245
|
+
agentName: input.agentName,
|
|
107
246
|
permissionMode: input.permissionMode ?? 'unknown',
|
|
108
247
|
toolName: input.toolName,
|
|
109
248
|
policyCode: input.policyCode,
|
|
@@ -115,8 +254,26 @@ export function auditToolInfrastructureFailure(input) {
|
|
|
115
254
|
: undefined,
|
|
116
255
|
});
|
|
117
256
|
}
|
|
257
|
+
function infrastructureErrorCode(policyCode) {
|
|
258
|
+
const code = String(policyCode ?? '').toLowerCase();
|
|
259
|
+
if (/delegat|carrier|not.?armed/.test(code))
|
|
260
|
+
return 'DELEGATION_FAILED';
|
|
261
|
+
if (/capability|sandbox|unavailable|workspace_unknown/.test(code))
|
|
262
|
+
return 'CAPABILITY_UNAVAILABLE';
|
|
263
|
+
if (/argument|canonical|quote|shell|parse|invalid/.test(code))
|
|
264
|
+
return 'INVALID_ARGUMENT';
|
|
265
|
+
return 'EXECUTION_FAILED';
|
|
266
|
+
}
|
|
118
267
|
function buildAuditRecord(event) {
|
|
119
268
|
const executed = event.executed ?? false;
|
|
269
|
+
const decisionSource = event.decisionSource ?? (event.decision === 'deny' ? 'policy' : 'command');
|
|
270
|
+
const errorCode = event.errorCode ?? (event.decision === 'deny'
|
|
271
|
+
? decisionSource === 'approval'
|
|
272
|
+
? 'USER_DENIED'
|
|
273
|
+
: decisionSource === 'policy'
|
|
274
|
+
? 'POLICY_DENIED'
|
|
275
|
+
: 'EXECUTION_FAILED'
|
|
276
|
+
: undefined);
|
|
120
277
|
const executionState = event.executionState
|
|
121
278
|
?? (event.decision === 'deny'
|
|
122
279
|
? 'blocked'
|
|
@@ -135,18 +292,25 @@ function buildAuditRecord(event) {
|
|
|
135
292
|
operation: event.operation,
|
|
136
293
|
})
|
|
137
294
|
: undefined),
|
|
295
|
+
eventPhase: event.eventPhase,
|
|
138
296
|
callId: event.callId,
|
|
139
297
|
correlationId: event.correlationId ?? event.requestId,
|
|
140
298
|
requestId: event.requestId,
|
|
141
299
|
sessionId: event.sessionId,
|
|
142
300
|
agentAid: redactIdentifier(event.agentAid ?? event.selfAid),
|
|
143
|
-
|
|
301
|
+
agentName: resolveAgentName(event),
|
|
302
|
+
permissionMode: normalizeAuditPermissionMode(event.permissionMode),
|
|
144
303
|
toolName: event.toolName,
|
|
145
304
|
approvalMethod: event.approvalMethod,
|
|
305
|
+
errorCode,
|
|
146
306
|
policyCode: event.policyCode ?? event.code,
|
|
147
307
|
protectionClass: event.protectionClass,
|
|
148
308
|
matchedPath: event.matchedPath,
|
|
149
|
-
decisionSource
|
|
309
|
+
decisionSource,
|
|
310
|
+
ecwebRequestId: event.ecwebRequestId,
|
|
311
|
+
targetAgentAid: redactIdentifier(event.targetAgentAid),
|
|
312
|
+
endpoint: event.endpoint,
|
|
313
|
+
logicalToolName: event.logicalToolName,
|
|
150
314
|
source: event.source,
|
|
151
315
|
operation: event.operation,
|
|
152
316
|
scope: event.scope,
|
|
@@ -161,6 +325,8 @@ function buildAuditRecord(event) {
|
|
|
161
325
|
channelId: redactIdentifier(event.channelId),
|
|
162
326
|
role: event.role,
|
|
163
327
|
processRole: event.processRole,
|
|
328
|
+
dataScope: event.dataScope,
|
|
329
|
+
authorizedBy: redactIdentifier(event.authorizedBy),
|
|
164
330
|
isDaemonOwner: event.isDaemonOwner,
|
|
165
331
|
fromControlChannel: event.fromControlChannel,
|
|
166
332
|
taskId: event.taskId,
|
|
@@ -177,6 +343,26 @@ function buildAuditRecord(event) {
|
|
|
177
343
|
exitCode: event.exitCode,
|
|
178
344
|
};
|
|
179
345
|
}
|
|
346
|
+
function resolveAgentName(event) {
|
|
347
|
+
const displayName = typeof event.agentName === 'string' ? event.agentName.trim() : '';
|
|
348
|
+
if (displayName && !isUnknownAgentLabel(displayName))
|
|
349
|
+
return displayName;
|
|
350
|
+
const aid = typeof event.agentAid === 'string' ? event.agentAid.trim() : '';
|
|
351
|
+
if (aid && aid !== 'unknown')
|
|
352
|
+
return aid;
|
|
353
|
+
const selfAid = typeof event.selfAid === 'string' ? event.selfAid.trim() : '';
|
|
354
|
+
if (selfAid && selfAid !== 'unknown')
|
|
355
|
+
return selfAid;
|
|
356
|
+
return aid === 'unknown' || selfAid === 'unknown' ? 'unknown' : undefined;
|
|
357
|
+
}
|
|
358
|
+
function isUnknownAgentLabel(value) {
|
|
359
|
+
return value === 'unknown' || value === '<unknown>' || value === '<none>' || value === 'undefined' || value === 'null';
|
|
360
|
+
}
|
|
361
|
+
function normalizeAuditPermissionMode(value) {
|
|
362
|
+
if (!value || value === 'unknown')
|
|
363
|
+
return value;
|
|
364
|
+
return normalizeExecutionPermissionMode(value);
|
|
365
|
+
}
|
|
180
366
|
function redactIdentifier(value) {
|
|
181
367
|
if (!value)
|
|
182
368
|
return undefined;
|
|
@@ -211,10 +397,17 @@ function logAuditEvent(record) {
|
|
|
211
397
|
record.callId ? `call=${record.callId}` : null,
|
|
212
398
|
record.correlationId ? `correlation=${record.correlationId}` : null,
|
|
213
399
|
record.eventKey ? `eventKey=${record.eventKey}` : null,
|
|
400
|
+
record.eventPhase ? `eventPhase=${record.eventPhase}` : null,
|
|
214
401
|
record.sessionId ? `session=${record.sessionId}` : null,
|
|
402
|
+
record.agentName ? `agentName=${JSON.stringify(record.agentName)}` : null,
|
|
215
403
|
record.agentAid ? `agent=${record.agentAid}` : null,
|
|
216
404
|
record.permissionMode ? `permissionMode=${record.permissionMode}` : null,
|
|
217
405
|
record.toolName ? `tool=${record.toolName}` : null,
|
|
406
|
+
record.errorCode ? `errorCode=${record.errorCode}` : null,
|
|
407
|
+
record.ecwebRequestId ? `ecwebRequest=${record.ecwebRequestId}` : null,
|
|
408
|
+
record.targetAgentAid ? `targetAgent=${record.targetAgentAid}` : null,
|
|
409
|
+
record.endpoint ? `endpoint=${JSON.stringify(record.endpoint)}` : null,
|
|
410
|
+
record.logicalToolName ? `logicalTool=${record.logicalToolName}` : null,
|
|
218
411
|
record.policyCode ? `policy=${record.policyCode}` : null,
|
|
219
412
|
record.protectionClass ? `protectionClass=${record.protectionClass}` : null,
|
|
220
413
|
record.matchedPath ? `matchedPath=${JSON.stringify(record.matchedPath)}` : null,
|
|
@@ -296,6 +489,21 @@ function summarizeAuthorizationArgs(operation, args) {
|
|
|
296
489
|
for (const [key, value] of Object.entries(args)) {
|
|
297
490
|
if (value === undefined)
|
|
298
491
|
continue;
|
|
492
|
+
if (key === 'parseFailure' && value && typeof value === 'object' && !Array.isArray(value)) {
|
|
493
|
+
const diagnostic = value;
|
|
494
|
+
const issue = typeof diagnostic.issue === 'string' ? diagnostic.issue : undefined;
|
|
495
|
+
const offset = typeof diagnostic.offset === 'number' && Number.isFinite(diagnostic.offset)
|
|
496
|
+
? Math.max(0, Math.floor(diagnostic.offset)) : undefined;
|
|
497
|
+
const tokenIndex = typeof diagnostic.tokenIndex === 'number' && Number.isFinite(diagnostic.tokenIndex)
|
|
498
|
+
? Math.max(0, Math.floor(diagnostic.tokenIndex)) : undefined;
|
|
499
|
+
const dialect = typeof diagnostic.dialect === 'string' ? diagnostic.dialect : undefined;
|
|
500
|
+
const inputLength = typeof diagnostic.inputLength === 'number' && Number.isFinite(diagnostic.inputLength)
|
|
501
|
+
? Math.max(0, Math.floor(diagnostic.inputLength)) : undefined;
|
|
502
|
+
if (issue && offset !== undefined && tokenIndex !== undefined && dialect && inputLength !== undefined) {
|
|
503
|
+
summary.parseFailure = { issue, offset, tokenIndex, dialect, inputLength };
|
|
504
|
+
}
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
299
507
|
if (/^(?:self|peer|peerKey|target|targetId|session|sessionId|channelId)$/i.test(key)) {
|
|
300
508
|
summary[key] = typeof value === 'string' ? redactIdentifier(value) : '[redacted]';
|
|
301
509
|
continue;
|
|
@@ -95,6 +95,20 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
95
95
|
&& (resolvedConfigCommand.operationId !== operation || resolvedConfigCommand.commandScope !== intent.scope)) {
|
|
96
96
|
return denyDecision(ctx, 'NOT_ALLOWED', 'Resolved config operation does not match the command intent', operation, intent.scope, opMeta.dangerous);
|
|
97
97
|
}
|
|
98
|
+
// A fullaccess task is deliberately a daemon-scoped runtime identity. It
|
|
99
|
+
// still has to name a known operation and pass structural command parsing,
|
|
100
|
+
// but relation/Agent role rules, source lists, and daemon-owner gates are
|
|
101
|
+
// not meaningful limits for code already approved for host-level execution.
|
|
102
|
+
if (ctx.processRole === 'fullaccess-run' && ctx.dataScope === 'daemon') {
|
|
103
|
+
return {
|
|
104
|
+
allow: true,
|
|
105
|
+
operation,
|
|
106
|
+
scope: intent.scope,
|
|
107
|
+
role: 'fullaccess-run',
|
|
108
|
+
dangerous: opMeta.dangerous,
|
|
109
|
+
matchedRule: 'fullaccess-run-daemon-scope',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
98
112
|
if (ctx.chatType === 'group'
|
|
99
113
|
&& (resolvedConfigCommand ? isResolvedConfigMutation(resolvedConfigCommand) : isConfigMutationOperation(operation))
|
|
100
114
|
&& intent.scope !== 'relation') {
|
|
@@ -175,7 +189,31 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
175
189
|
if (roleDef.allowAccess === false) {
|
|
176
190
|
return denyDecision(ctx, 'ROLE_ACCESS_DENIED', `Role ${role} is not allowed to access commands`, operation, intent.scope, opMeta.dangerous);
|
|
177
191
|
}
|
|
178
|
-
|
|
192
|
+
let matchResult = matchCommandPermission(operation, opMeta.category, roleDef.commandPermissions || {}, opMeta.dangerous);
|
|
193
|
+
// Agent self-service contact add deliberately reuses the same low-trust
|
|
194
|
+
// relation boundary as ec.msg.send. Older per-Agent role files may still
|
|
195
|
+
// contain the historical contact.* deny without an explicit contact.add
|
|
196
|
+
// entry; preserve their safe current-peer/private-only send capability while
|
|
197
|
+
// allowing this new request workflow. An explicit contact.add rule wins.
|
|
198
|
+
if (operation === 'contact.add'
|
|
199
|
+
&& !Object.prototype.hasOwnProperty.call(roleDef.commandPermissions || {}, 'contact.add')) {
|
|
200
|
+
const sendPermission = roleDef.commandPermissions?.['ec.msg.send'];
|
|
201
|
+
if (sendPermission?.allow) {
|
|
202
|
+
matchResult = {
|
|
203
|
+
permission: {
|
|
204
|
+
...sendPermission,
|
|
205
|
+
scopes: ['relation'],
|
|
206
|
+
constraints: {
|
|
207
|
+
...(sendPermission.constraints ?? {}),
|
|
208
|
+
ownPeerOnly: true,
|
|
209
|
+
privateOnly: true,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
matchedRule: 'ec.msg.send',
|
|
213
|
+
rank: 1000,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
179
217
|
if (!matchResult) {
|
|
180
218
|
return denyDecision(ctx, 'NO_PERMISSION', `Role ${role} has no permission for ${operation}`, operation, intent.scope, opMeta.dangerous);
|
|
181
219
|
}
|
|
@@ -274,6 +312,8 @@ function requiresDaemonOwner(ctx, resolvedConfigCommand) {
|
|
|
274
312
|
const targetAgentAid = stringArg(args.targetAgentAid) ?? stringArg(args.self);
|
|
275
313
|
return !!taskAgentAid && !!targetAgentAid && taskAgentAid !== targetAgentAid;
|
|
276
314
|
}
|
|
315
|
+
if (operation.startsWith('role.') && ctx.fromControlChannel)
|
|
316
|
+
return true;
|
|
277
317
|
return false;
|
|
278
318
|
}
|
|
279
319
|
function hasDaemonOwnerRole(ctx) {
|
|
@@ -55,7 +55,7 @@ const GROUP_MANAGEMENT_OPERATIONS = [
|
|
|
55
55
|
sources: ['agent-tool'],
|
|
56
56
|
}));
|
|
57
57
|
const MSG_READ_OPERATIONS = [
|
|
58
|
-
['ec.msg.history', 'Read message history for
|
|
58
|
+
['ec.msg.history', 'Read message history for a controlled private relation'],
|
|
59
59
|
['ec.msg.online', 'Read online status for an AUN private relation'],
|
|
60
60
|
].map(([id, description]) => ({
|
|
61
61
|
id,
|
|
@@ -476,6 +476,14 @@ const OPERATIONS = [
|
|
|
476
476
|
description: 'Read the Agent contact block list',
|
|
477
477
|
sources: ['agent-tool'],
|
|
478
478
|
},
|
|
479
|
+
{
|
|
480
|
+
id: 'contact.add',
|
|
481
|
+
category: 'write-agent',
|
|
482
|
+
dangerous: false,
|
|
483
|
+
defaultScopes: ['relation'],
|
|
484
|
+
description: 'Request adding the current private relation as an Agent contact',
|
|
485
|
+
sources: ['agent-tool'],
|
|
486
|
+
},
|
|
479
487
|
{
|
|
480
488
|
id: 'contact.block',
|
|
481
489
|
category: 'write-agent',
|
|
@@ -7,9 +7,17 @@ import { chatDirPath } from './session/session-fs-store.js';
|
|
|
7
7
|
import { hasMessageLogOperation } from './message/message-log.js';
|
|
8
8
|
import { isDeliveryTarget, sameDeliveryTarget } from './message/message-utils.js';
|
|
9
9
|
export const BOOTSTRAP_MESSAGE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
10
|
+
export const BOOTSTRAP_NOTIFICATION_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
10
11
|
export function bootstrapInitialMessageOperationId(aid) {
|
|
11
12
|
return `bootstrap-initial:v1:${aid}`;
|
|
12
13
|
}
|
|
14
|
+
export function bootstrapInitialMessageOperationPrefix(aid) {
|
|
15
|
+
return `bootstrap-initial:v1:${aid}`;
|
|
16
|
+
}
|
|
17
|
+
/** A new operation ID is required after the notification window expires. */
|
|
18
|
+
export function bootstrapInitialRetryOperationId(aid, at = Date.now()) {
|
|
19
|
+
return `${bootstrapInitialMessageOperationPrefix(aid)}:${at}`;
|
|
20
|
+
}
|
|
13
21
|
export function postBootstrapWelcomeOperationId(aid) {
|
|
14
22
|
return `bootstrap-complete:v1:${aid}`;
|
|
15
23
|
}
|