evolcore 0.0.17 → 0.0.18
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 +32 -0
- package/bin/codex-managed-hook.mjs +16 -7
- package/bin/install-codex-managed-hooks.mjs +4 -2
- package/dist/agents/claude-runner.js +113 -24
- package/dist/agents/codex-app-server-client.js +6 -1
- package/dist/agents/codex-runner.js +21 -6
- package/dist/agents/ecagent-runner.js +39 -10
- package/dist/agents/gemini-runner.js +90 -19
- package/dist/aun/aid/store.js +36 -0
- package/dist/aun/msg/p2p.js +20 -8
- package/dist/channels/aun.js +159 -21
- package/dist/cli/agent-command.js +67 -6
- package/dist/cli/agent.js +26 -0
- package/dist/cli/command-log.js +23 -4
- package/dist/cli/daemon-commands.js +53 -12
- package/dist/cli/init.js +21 -5
- package/dist/cli/restart-monitor.js +13 -6
- package/dist/cli/watch-logs.js +2 -2
- package/dist/config/builtin-roles.js +5 -1
- package/dist/config/role-ranks.js +4 -0
- package/dist/core/audit/event-key.js +29 -0
- package/dist/core/audit/log-integrity.js +13 -3
- package/dist/core/auth/auth-gateway.js +14 -18
- package/dist/core/auth/authorization-audit.js +110 -3
- package/dist/core/auth/authorization-denial.js +17 -0
- package/dist/core/auth/operation-authorizer.js +143 -18
- package/dist/core/auth/operation-catalog.js +21 -5
- package/dist/core/bootstrap-messages.js +11 -6
- package/dist/core/bootstrap-service.js +26 -4
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +25 -16
- package/dist/core/command/command-handler.js +50 -4
- package/dist/core/command/group-menu.js +1 -1
- package/dist/core/command/menu-catalog.js +32 -7
- package/dist/core/command/menu-handler.js +59 -23
- package/dist/core/command/menu-protocol.js +196 -0
- package/dist/core/command/slash-gate.js +14 -5
- package/dist/core/command/slash-handler.js +81 -99
- package/dist/core/event-catalog.js +18 -0
- package/dist/core/message/message-bridge.js +72 -9
- package/dist/core/message/pause-controller.js +53 -0
- package/dist/core/message/response-engine.js +97 -11
- package/dist/core/permission/sandbox-runtime.js +79 -13
- package/dist/core/permission/tool-policy.js +1 -1
- package/dist/index.js +357 -48
- package/dist/ipc.js +75 -4
- package/dist/utils/atomic-write.js +45 -11
- package/dist/utils/logger.js +27 -0
- package/dist/utils/windows-autostart.js +740 -83
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/agent-harness.js +6 -4
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/group-rules.md +2 -1
- package/kits/docs/identity/ROLE_DETAIL.md +3 -1
- package/kits/eck_manifest.json +25 -16
- package/kits/rules/01-overview.md +5 -5
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/04-relation.md +4 -4
- package/kits/rules/05-venue.md +5 -5
- package/kits/templates/bootstrap-welcome.md +3 -1
- package/kits/templates/system-fragments/bootstrap.md +17 -9
- package/package.json +1 -1
|
@@ -5,6 +5,36 @@ 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
|
+
const DAEMON_OWNER_PROCESS_OPERATIONS = new Set([
|
|
9
|
+
'agent.list',
|
|
10
|
+
'agent.create',
|
|
11
|
+
'agent.delete',
|
|
12
|
+
'agent.enable',
|
|
13
|
+
'agent.disable',
|
|
14
|
+
'system.restart',
|
|
15
|
+
'system.upgrade',
|
|
16
|
+
'gateway.read',
|
|
17
|
+
'gateway.write',
|
|
18
|
+
'stats.peers',
|
|
19
|
+
'stats.groups',
|
|
20
|
+
'stats.sqlReadonly',
|
|
21
|
+
'stats.rebuild',
|
|
22
|
+
'aid.listLocal',
|
|
23
|
+
'aid.showLocal',
|
|
24
|
+
'aid.lookupRemote',
|
|
25
|
+
]);
|
|
26
|
+
const DAEMON_SERVICE_OPERATIONS = new Set([
|
|
27
|
+
'trigger.list',
|
|
28
|
+
'trigger.show',
|
|
29
|
+
'trigger.history',
|
|
30
|
+
'trigger.eventCatalog',
|
|
31
|
+
'trigger.create',
|
|
32
|
+
'trigger.update',
|
|
33
|
+
'trigger.setEnabled',
|
|
34
|
+
'trigger.cancel',
|
|
35
|
+
'trigger.delete',
|
|
36
|
+
'trigger.run',
|
|
37
|
+
]);
|
|
8
38
|
export function authorizeCommand(ctx) {
|
|
9
39
|
return authorizeCommandInternal(ctx);
|
|
10
40
|
}
|
|
@@ -42,7 +72,8 @@ export function evaluateRoleOperationCapability(params) {
|
|
|
42
72
|
peerKey: capabilityPeer,
|
|
43
73
|
role: params.role,
|
|
44
74
|
chatType: params.chatType,
|
|
45
|
-
|
|
75
|
+
processRole: 'none',
|
|
76
|
+
isDaemonOwner: false,
|
|
46
77
|
fromControlChannel: params.fromControlChannel,
|
|
47
78
|
source: 'menu',
|
|
48
79
|
});
|
|
@@ -75,6 +106,45 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
75
106
|
if (!sourceAllowed) {
|
|
76
107
|
return denyDecision(ctx, 'NOT_ALLOWED', `Operation ${operation} is not available from source ${ctx.source}`, operation, intent.scope, opMeta.dangerous);
|
|
77
108
|
}
|
|
109
|
+
if (ctx.processRole === 'daemon-service') {
|
|
110
|
+
if (!DAEMON_SERVICE_OPERATIONS.has(operation)) {
|
|
111
|
+
return denyDecision(ctx, 'NOT_ALLOWED', `Daemon service is not authorized for ${operation}`, operation, intent.scope, opMeta.dangerous, 'daemon-service');
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
allow: true,
|
|
115
|
+
operation,
|
|
116
|
+
scope: intent.scope,
|
|
117
|
+
role: 'daemon-service',
|
|
118
|
+
dangerous: opMeta.dangerous,
|
|
119
|
+
matchedRule: 'daemon-service',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (requiresDaemonOwner(ctx, resolvedConfigCommand)) {
|
|
123
|
+
if (!hasDaemonOwnerRole(ctx)) {
|
|
124
|
+
const reasonCode = daemonOwnerDenialReasonCode(ctx);
|
|
125
|
+
return denyDecision(ctx, 'NOT_ALLOWED', daemonOwnerDenialMessage(ctx), operation, intent.scope, opMeta.dangerous, 'daemon-owner', reasonCode);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
allow: true,
|
|
129
|
+
operation,
|
|
130
|
+
scope: intent.scope,
|
|
131
|
+
role: 'daemon-owner',
|
|
132
|
+
dangerous: opMeta.dangerous,
|
|
133
|
+
matchedRule: 'daemon-owner',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Any authenticated role may inspect the current Agent through the daemon's
|
|
137
|
+
// redacted view. Cross-Agent inspection is handled by requiresDaemonOwner().
|
|
138
|
+
if (operation === 'agent.show' && isCurrentAgentTarget(ctx)) {
|
|
139
|
+
return {
|
|
140
|
+
allow: true,
|
|
141
|
+
operation,
|
|
142
|
+
scope: intent.scope,
|
|
143
|
+
role,
|
|
144
|
+
dangerous: opMeta.dangerous,
|
|
145
|
+
matchedRule: 'self-agent-read',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
78
148
|
if (resolvedConfigCommand) {
|
|
79
149
|
const targetCheck = checkResolvedConfigTarget(ctx, resolvedConfigCommand);
|
|
80
150
|
if (!targetCheck.ok) {
|
|
@@ -84,23 +154,17 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
84
154
|
if (!identityMutationCheck.ok) {
|
|
85
155
|
return denyDecision(ctx, 'NOT_ALLOWED', identityMutationCheck.reason || 'Static management identities cannot be changed by this role', operation, intent.scope, opMeta.dangerous);
|
|
86
156
|
}
|
|
87
|
-
if (resolvedConfigCommand.kind === 'global' && !ctx.isDaemonOwner) {
|
|
88
|
-
return denyDecision(ctx, 'NOT_ALLOWED', `Global config command ${operation} requires daemon owner permission`, operation, intent.scope, opMeta.dangerous);
|
|
89
|
-
}
|
|
90
|
-
if (role === 'owner' && !ctx.isDaemonOwner && intent.scope === 'process') {
|
|
91
|
-
return denyDecision(ctx, 'SCOPE_MISMATCH', 'Agent owners cannot access process, defaults, or global config commands', operation, intent.scope, opMeta.dangerous);
|
|
92
|
-
}
|
|
93
157
|
if (!isManagementRole(role) && intent.scope !== 'relation') {
|
|
94
158
|
return denyDecision(ctx, 'SCOPE_MISMATCH', `Role ${role} may only access relation-scoped config`, operation, intent.scope, opMeta.dangerous);
|
|
95
159
|
}
|
|
96
|
-
if (
|
|
160
|
+
if (role === 'owner') {
|
|
97
161
|
return {
|
|
98
162
|
allow: true,
|
|
99
163
|
operation,
|
|
100
164
|
scope: intent.scope,
|
|
101
|
-
role
|
|
165
|
+
role,
|
|
102
166
|
dangerous: opMeta.dangerous,
|
|
103
|
-
matchedRule:
|
|
167
|
+
matchedRule: 'agent-owner',
|
|
104
168
|
};
|
|
105
169
|
}
|
|
106
170
|
}
|
|
@@ -159,7 +223,7 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
159
223
|
if (permission.constraints) {
|
|
160
224
|
const constraintCheck = checkConstraints(ctx, permission.constraints, resolvedConfigCommand);
|
|
161
225
|
if (!constraintCheck.ok) {
|
|
162
|
-
return denyDecision(ctx, constraintCheck.code ?? 'ARGUMENT_MISMATCH', constraintCheck.reason || 'Command arguments do not satisfy permission constraints', operation, intent.scope, opMeta.dangerous, matchedRule);
|
|
226
|
+
return denyDecision(ctx, constraintCheck.code ?? 'ARGUMENT_MISMATCH', constraintCheck.reason || 'Command arguments do not satisfy permission constraints', operation, intent.scope, opMeta.dangerous, matchedRule, constraintCheck.reasonCode);
|
|
163
227
|
}
|
|
164
228
|
}
|
|
165
229
|
return {
|
|
@@ -186,6 +250,59 @@ function matchCommandPermission(operation, category, commandPerms, isDangerous)
|
|
|
186
250
|
matches.sort((a, b) => b.rank - a.rank);
|
|
187
251
|
return matches[0];
|
|
188
252
|
}
|
|
253
|
+
function requiresDaemonOwner(ctx, resolvedConfigCommand) {
|
|
254
|
+
const { operation, scope, args } = ctx.intent;
|
|
255
|
+
if (scope === 'process' || scope === 'control')
|
|
256
|
+
return true;
|
|
257
|
+
if (DAEMON_OWNER_PROCESS_OPERATIONS.has(operation))
|
|
258
|
+
return true;
|
|
259
|
+
if (resolvedConfigCommand
|
|
260
|
+
&& (resolvedConfigCommand.kind === 'global'
|
|
261
|
+
|| resolvedConfigCommand.configScope === 'process'
|
|
262
|
+
|| resolvedConfigCommand.configScope === 'defaults'))
|
|
263
|
+
return true;
|
|
264
|
+
if (operation === 'agent.reload' && hasDaemonOwnerRole(ctx)) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
if (operation === 'agent.show' || operation === 'agent.reload') {
|
|
268
|
+
return !isCurrentAgentTarget(ctx);
|
|
269
|
+
}
|
|
270
|
+
if (operation.startsWith('trigger.')) {
|
|
271
|
+
if (ctx.fromControlChannel)
|
|
272
|
+
return true;
|
|
273
|
+
const taskAgentAid = stringArg(args.taskAgentAid);
|
|
274
|
+
const targetAgentAid = stringArg(args.targetAgentAid) ?? stringArg(args.self);
|
|
275
|
+
return !!taskAgentAid && !!targetAgentAid && taskAgentAid !== targetAgentAid;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
function hasDaemonOwnerRole(ctx) {
|
|
280
|
+
return ctx.isDaemonOwner === true || ctx.processRole === 'daemon-owner';
|
|
281
|
+
}
|
|
282
|
+
function daemonOwnerDenialReasonCode(ctx) {
|
|
283
|
+
if ((ctx.intent.operation === 'agent.show' || ctx.intent.operation === 'agent.reload')
|
|
284
|
+
&& ctx.intent.scope !== 'control'
|
|
285
|
+
&& !ctx.fromControlChannel
|
|
286
|
+
&& !isCurrentAgentTarget(ctx)) {
|
|
287
|
+
return 'TARGET_SELF_ONLY';
|
|
288
|
+
}
|
|
289
|
+
return 'DAEMON_OWNER_REQUIRED';
|
|
290
|
+
}
|
|
291
|
+
function daemonOwnerDenialMessage(ctx) {
|
|
292
|
+
return daemonOwnerDenialReasonCode(ctx) === 'TARGET_SELF_ONLY'
|
|
293
|
+
? `${ctx.intent.operation} may only target the current Agent from a managed Agent task.`
|
|
294
|
+
: daemonOwnerRequiredMessage(ctx.intent.operation);
|
|
295
|
+
}
|
|
296
|
+
function isCurrentAgentTarget(ctx) {
|
|
297
|
+
const targetAid = stringArg(ctx.intent.args.targetAgentAid)
|
|
298
|
+
?? stringArg(ctx.intent.args.aid)
|
|
299
|
+
?? stringArg(ctx.intent.args.self);
|
|
300
|
+
return !!ctx.selfAid && !!targetAid && targetAid === ctx.selfAid;
|
|
301
|
+
}
|
|
302
|
+
function daemonOwnerRequiredMessage(operation) {
|
|
303
|
+
return `${operation} is a daemon-level operation and requires DaemonOwner. `
|
|
304
|
+
+ 'Agent owner/admin and permissionMode=bypass do not grant this permission.';
|
|
305
|
+
}
|
|
189
306
|
function getRuleRank(rule, permission, operation, category, isDangerous) {
|
|
190
307
|
const denyOffset = permission.allow ? 0 : 1;
|
|
191
308
|
if (rule === operation)
|
|
@@ -215,8 +332,10 @@ function checkConstraints(ctx, constraints, resolvedConfigCommand) {
|
|
|
215
332
|
return peerCheck;
|
|
216
333
|
}
|
|
217
334
|
if (constraints.ownAgentOnly || constraints.targetCurrentAgentOnly) {
|
|
218
|
-
const controlOwnerOverride = ctx.fromControlChannel && ctx
|
|
219
|
-
const targetAid = stringArg(ctx.intent.args.
|
|
335
|
+
const controlOwnerOverride = ctx.fromControlChannel && hasDaemonOwnerRole(ctx);
|
|
336
|
+
const targetAid = stringArg(ctx.intent.args.targetAgentAid)
|
|
337
|
+
?? stringArg(ctx.intent.args.aid)
|
|
338
|
+
?? stringArg(ctx.intent.args.self);
|
|
220
339
|
if (!controlOwnerOverride && (!ctx.selfAid || !targetAid || targetAid !== ctx.selfAid)) {
|
|
221
340
|
return { ok: false, reason: 'Only the current agent can be targeted' };
|
|
222
341
|
}
|
|
@@ -238,8 +357,13 @@ function checkConstraints(ctx, constraints, resolvedConfigCommand) {
|
|
|
238
357
|
reason: `Denied ${ctx.intent.operation}: role=${ctx.role}, source_chat=${ctx.chatType ?? 'unknown'}, target_group=${target}. This role may access group operations only from a group-chat task${sameGroup}; the target being a group is not sufficient.`,
|
|
239
358
|
};
|
|
240
359
|
}
|
|
241
|
-
if (constraints.requireDaemonOwner && !ctx
|
|
242
|
-
return {
|
|
360
|
+
if (constraints.requireDaemonOwner && !hasDaemonOwnerRole(ctx)) {
|
|
361
|
+
return {
|
|
362
|
+
ok: false,
|
|
363
|
+
code: 'NOT_ALLOWED',
|
|
364
|
+
reason: daemonOwnerRequiredMessage(ctx.intent.operation),
|
|
365
|
+
reasonCode: 'DAEMON_OWNER_REQUIRED',
|
|
366
|
+
};
|
|
243
367
|
}
|
|
244
368
|
if (constraints.requireControlChannel && !ctx.fromControlChannel) {
|
|
245
369
|
return { ok: false, reason: 'This command requires the control channel' };
|
|
@@ -310,7 +434,7 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
|
|
|
310
434
|
if (policy === 'role-overridable-write'
|
|
311
435
|
&& resolvedConfigOp.fieldRule.access === 'owner-only'
|
|
312
436
|
&& ctx.role !== 'owner'
|
|
313
|
-
&& !ctx
|
|
437
|
+
&& !hasDaemonOwnerRole(ctx)) {
|
|
314
438
|
return { ok: false, reason: `Config field ${field} can only be changed by an owner` };
|
|
315
439
|
}
|
|
316
440
|
const roleDef = getRoleDefinition(ctx.role, ctx.selfAid);
|
|
@@ -386,7 +510,7 @@ function checkManagementIdentityMutation(ctx, command) {
|
|
|
386
510
|
if (command.configScope !== 'agent') {
|
|
387
511
|
return { ok: false, reason: `${field} requires an explicit current-agent target` };
|
|
388
512
|
}
|
|
389
|
-
if (ctx
|
|
513
|
+
if (hasDaemonOwnerRole(ctx) || ctx.role === 'owner')
|
|
390
514
|
return { ok: true };
|
|
391
515
|
return { ok: false, reason: `Only an Agent owner can modify ${field}` };
|
|
392
516
|
}
|
|
@@ -625,7 +749,7 @@ function globMatches(pattern, value) {
|
|
|
625
749
|
function stringArg(value) {
|
|
626
750
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
627
751
|
}
|
|
628
|
-
function denyDecision(ctx, code, reason, operation, scope, dangerous, matchedRule) {
|
|
752
|
+
function denyDecision(ctx, code, reason, operation, scope, dangerous, matchedRule, reasonCode) {
|
|
629
753
|
return {
|
|
630
754
|
allow: false,
|
|
631
755
|
code,
|
|
@@ -635,5 +759,6 @@ function denyDecision(ctx, code, reason, operation, scope, dangerous, matchedRul
|
|
|
635
759
|
role: ctx.role,
|
|
636
760
|
dangerous,
|
|
637
761
|
matchedRule,
|
|
762
|
+
...(reasonCode ? { reasonCode } : {}),
|
|
638
763
|
};
|
|
639
764
|
}
|
|
@@ -539,7 +539,7 @@ const OPERATIONS = [
|
|
|
539
539
|
dangerous: false,
|
|
540
540
|
defaultScopes: ['control'],
|
|
541
541
|
description: '列出所有 agent(敏感信息)',
|
|
542
|
-
sources: ['menu', 'menu.cli', 'control'],
|
|
542
|
+
sources: ['menu', 'menu.cli', 'agent-tool', 'control'],
|
|
543
543
|
},
|
|
544
544
|
{
|
|
545
545
|
id: 'agent.show',
|
|
@@ -547,7 +547,7 @@ const OPERATIONS = [
|
|
|
547
547
|
dangerous: false,
|
|
548
548
|
defaultScopes: ['control', 'agent'],
|
|
549
549
|
description: '查看 agent 详情(敏感信息)',
|
|
550
|
-
sources: ['menu', 'menu.cli', 'control'],
|
|
550
|
+
sources: ['menu', 'menu.cli', 'agent-tool', 'control'],
|
|
551
551
|
},
|
|
552
552
|
{
|
|
553
553
|
id: 'agent.baseagent.current',
|
|
@@ -587,7 +587,23 @@ const OPERATIONS = [
|
|
|
587
587
|
dangerous: true,
|
|
588
588
|
defaultScopes: ['control'],
|
|
589
589
|
description: '创建新 agent',
|
|
590
|
-
sources: ['menu', 'menu.cli', 'control'],
|
|
590
|
+
sources: ['menu', 'menu.cli', 'agent-tool', 'control'],
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
id: 'agent.enable',
|
|
594
|
+
category: 'process',
|
|
595
|
+
dangerous: true,
|
|
596
|
+
defaultScopes: ['control'],
|
|
597
|
+
description: '启用 agent',
|
|
598
|
+
sources: ['menu', 'menu.cli', 'agent-tool', 'control'],
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: 'agent.disable',
|
|
602
|
+
category: 'process',
|
|
603
|
+
dangerous: true,
|
|
604
|
+
defaultScopes: ['control'],
|
|
605
|
+
description: '停用 agent',
|
|
606
|
+
sources: ['menu', 'menu.cli', 'agent-tool', 'control'],
|
|
591
607
|
},
|
|
592
608
|
{
|
|
593
609
|
id: 'agent.reload',
|
|
@@ -595,7 +611,7 @@ const OPERATIONS = [
|
|
|
595
611
|
dangerous: true,
|
|
596
612
|
defaultScopes: ['control', 'agent'],
|
|
597
613
|
description: '重载 agent 配置',
|
|
598
|
-
sources: ['slash', 'menu', 'control'],
|
|
614
|
+
sources: ['slash', 'menu', 'agent-tool', 'control'],
|
|
599
615
|
},
|
|
600
616
|
{
|
|
601
617
|
id: 'agent.bootstrapComplete',
|
|
@@ -611,7 +627,7 @@ const OPERATIONS = [
|
|
|
611
627
|
dangerous: true,
|
|
612
628
|
defaultScopes: ['control'],
|
|
613
629
|
description: '删除 agent(危险操作)',
|
|
614
|
-
sources: ['menu', 'menu.cli', 'control'],
|
|
630
|
+
sources: ['menu', 'menu.cli', 'agent-tool', 'control'],
|
|
615
631
|
},
|
|
616
632
|
// ── System Operations ──
|
|
617
633
|
{
|
|
@@ -5,6 +5,7 @@ import { generateWelcomeMessage } from '../utils/welcome.js';
|
|
|
5
5
|
import * as outbox from '../aun/outbox.js';
|
|
6
6
|
import { chatDirPath } from './session/session-fs-store.js';
|
|
7
7
|
import { hasMessageLogOperation } from './message/message-log.js';
|
|
8
|
+
import { isDeliveryTarget, sameDeliveryTarget } from './message/message-utils.js';
|
|
8
9
|
export const BOOTSTRAP_MESSAGE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
9
10
|
export function bootstrapInitialMessageOperationId(aid) {
|
|
10
11
|
return `bootstrap-initial:v1:${aid}`;
|
|
@@ -26,26 +27,29 @@ export function renderPostBootstrapWelcome(aid, owner, ownerName) {
|
|
|
26
27
|
includeBindingNote: true,
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
|
-
export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName) {
|
|
30
|
+
export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName, delivery = { chatType: 'private' }) {
|
|
30
31
|
const operationId = postBootstrapWelcomeOperationId(aid);
|
|
31
32
|
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aid);
|
|
32
33
|
if (hasMessageLogOperation(chatDir, operationId))
|
|
33
34
|
return;
|
|
34
35
|
const existing = outbox.findByDedupeKey(aid, operationId);
|
|
35
|
-
if (existing
|
|
36
|
+
if (existing
|
|
37
|
+
&& existing.channelId === owner
|
|
38
|
+
&& isDeliveryTarget(existing.delivery)
|
|
39
|
+
&& sameDeliveryTarget(existing.delivery, delivery))
|
|
36
40
|
return;
|
|
37
41
|
if (existing)
|
|
38
42
|
outbox.remove(aid, existing.id);
|
|
39
43
|
outbox.enqueue(aid, {
|
|
40
44
|
channelId: owner,
|
|
41
|
-
delivery
|
|
45
|
+
delivery,
|
|
42
46
|
dedupeKey: operationId,
|
|
43
47
|
critical: true,
|
|
44
48
|
type: 'text',
|
|
45
49
|
text: renderPostBootstrapWelcome(aid, owner, ownerName),
|
|
46
50
|
ttl: BOOTSTRAP_MESSAGE_TTL_MS,
|
|
47
51
|
context: {
|
|
48
|
-
delivery
|
|
52
|
+
delivery,
|
|
49
53
|
metadata: {
|
|
50
54
|
source: 'daemon',
|
|
51
55
|
chatmode: 'interactive',
|
|
@@ -56,12 +60,13 @@ export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName) {
|
|
|
56
60
|
});
|
|
57
61
|
}
|
|
58
62
|
export function hasPendingPostBootstrapWelcomeOutbox(aid) {
|
|
59
|
-
|
|
63
|
+
const entry = outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid));
|
|
64
|
+
return !!entry && outbox.isDeliveryForChannel(entry.delivery, entry.channelId);
|
|
60
65
|
}
|
|
61
66
|
/** Bind the durable completion welcome to the fresh post-bootstrap session. */
|
|
62
67
|
export function bindPostBootstrapWelcomeOutboxSession(aid, sessionId) {
|
|
63
68
|
const entry = outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid));
|
|
64
|
-
if (!entry || entry.delivery
|
|
69
|
+
if (!entry || !outbox.isDeliveryForChannel(entry.delivery, entry.channelId))
|
|
65
70
|
return false;
|
|
66
71
|
if (entry.context?.sessionId === sessionId)
|
|
67
72
|
return true;
|
|
@@ -144,9 +144,31 @@ export class BootstrapService {
|
|
|
144
144
|
// owner. An inbound owner message may have arrived from a group, in which
|
|
145
145
|
// case ctx.channelId is the group AID and must not be paired with the
|
|
146
146
|
// private delivery route below (that would call message.send(to=group)).
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
// A configured AUN recipient may itself be a group AID. Ask the channel's
|
|
148
|
+
// authoritative group endpoint before choosing the transport; never infer
|
|
149
|
+
// group/private semantics from the AID string or from the inbound venue.
|
|
150
|
+
let isGroupRecipient = false;
|
|
151
|
+
try {
|
|
152
|
+
isGroupRecipient = channelType === 'aun'
|
|
153
|
+
? (await ctx.adapter.isGroup?.(recipientId)) === true
|
|
154
|
+
: false;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
// Group/private routing is security-sensitive. If the authoritative
|
|
158
|
+
// probe fails, do not guess a route, and release the single-flight key
|
|
159
|
+
// so a later connection or retry can recover.
|
|
160
|
+
this.inFlight.delete(key);
|
|
161
|
+
logger.warn(`[Bootstrap] Failed to resolve recipient type for ${aid}: ${error instanceof Error ? error.message : String(error)}`);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const delivery = isGroupRecipient === true
|
|
165
|
+
? { chatType: 'group', groupId: recipientId }
|
|
166
|
+
: { chatType: 'private' };
|
|
167
|
+
const channelId = delivery.chatType === 'group'
|
|
168
|
+
? delivery.groupId
|
|
169
|
+
: channelType === 'aun'
|
|
170
|
+
? recipientId
|
|
171
|
+
: ctx.channelId || this.defaultChannelIdForConnection(channelType, recipientId);
|
|
150
172
|
if (!channelId) {
|
|
151
173
|
this.inFlight.delete(key);
|
|
152
174
|
return false;
|
|
@@ -172,7 +194,7 @@ export class BootstrapService {
|
|
|
172
194
|
channelId,
|
|
173
195
|
agentName: aid,
|
|
174
196
|
replyContext: {
|
|
175
|
-
delivery
|
|
197
|
+
delivery,
|
|
176
198
|
metadata: {
|
|
177
199
|
source: 'daemon',
|
|
178
200
|
persistRequired: true,
|
|
@@ -44,14 +44,13 @@ function ensureLoaded() {
|
|
|
44
44
|
}
|
|
45
45
|
function persist() {
|
|
46
46
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
47
|
+
// Keep the protected mount target present even when there are no pending
|
|
48
|
+
// associations. Removing it races with Bubblewrap's H-class projection.
|
|
51
49
|
atomicWriteJson(associationFile(), [...associations].map(([messageId, association]) => ({
|
|
52
50
|
messageId,
|
|
53
51
|
...association,
|
|
54
52
|
})));
|
|
53
|
+
removeAssociationBackups();
|
|
55
54
|
}
|
|
56
55
|
catch {
|
|
57
56
|
}
|
|
@@ -106,6 +105,10 @@ export function clearAunCausationForTests() {
|
|
|
106
105
|
function removeAssociationFiles() {
|
|
107
106
|
const file = associationFile();
|
|
108
107
|
fs.rmSync(file, { force: true });
|
|
108
|
+
removeAssociationBackups();
|
|
109
|
+
}
|
|
110
|
+
function removeAssociationBackups() {
|
|
111
|
+
const file = associationFile();
|
|
109
112
|
fs.rmSync(`${file}_`, { force: true });
|
|
110
113
|
fs.rmSync(`${file}__`, { force: true });
|
|
111
114
|
}
|
|
@@ -11,22 +11,18 @@ import { isValidAid } from '../../aun/aid/validation.js';
|
|
|
11
11
|
const SUPPORTED_AGENT_PATCH_FIELDS = new Set(['aid', 'name', 'avatar', 'active_baseagent', 'baseagents', 'projects', 'owners', 'chatmode', 'channels', 'channelOwners']);
|
|
12
12
|
const HIDDEN_VALUE = '[hidden]';
|
|
13
13
|
const SENSITIVE_CONFIG_KEYS = new Set([
|
|
14
|
-
'apiKey',
|
|
15
14
|
'apikey',
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'clientId',
|
|
20
|
-
'clientSecret',
|
|
21
|
-
'secret',
|
|
22
|
-
'token',
|
|
23
|
-
'accessToken',
|
|
15
|
+
'appid',
|
|
16
|
+
'appsecret',
|
|
17
|
+
'botid',
|
|
24
18
|
'authorization',
|
|
19
|
+
'clientid',
|
|
25
20
|
'credential',
|
|
26
21
|
'credentials',
|
|
27
22
|
'password',
|
|
28
|
-
'
|
|
29
|
-
'
|
|
23
|
+
'privatekey',
|
|
24
|
+
'encryptionseed',
|
|
25
|
+
'aeskey',
|
|
30
26
|
]);
|
|
31
27
|
const HIDDEN_SETTINGS_KEYS = new Set([
|
|
32
28
|
'pathToClaudeCodeExecutable',
|
|
@@ -225,7 +221,7 @@ function sanitizeConfigValue(value) {
|
|
|
225
221
|
if (out.effort === undefined)
|
|
226
222
|
out.effort = sanitizeConfigValue(raw);
|
|
227
223
|
}
|
|
228
|
-
else if (
|
|
224
|
+
else if (isSensitiveConfigKey(key)) {
|
|
229
225
|
out[key] = raw == null || raw === '' ? raw : HIDDEN_VALUE;
|
|
230
226
|
}
|
|
231
227
|
else if (key === 'projects') {
|
|
@@ -237,6 +233,18 @@ function sanitizeConfigValue(value) {
|
|
|
237
233
|
}
|
|
238
234
|
return out;
|
|
239
235
|
}
|
|
236
|
+
function isSensitiveConfigKey(key) {
|
|
237
|
+
const normalized = key.replace(/[-_\s]/g, '').toLowerCase();
|
|
238
|
+
return SENSITIVE_CONFIG_KEYS.has(normalized)
|
|
239
|
+
|| normalized.endsWith('token')
|
|
240
|
+
|| normalized.endsWith('secret')
|
|
241
|
+
|| normalized.endsWith('password')
|
|
242
|
+
|| normalized.endsWith('credential')
|
|
243
|
+
|| normalized.endsWith('credentials')
|
|
244
|
+
|| normalized.endsWith('privatekey')
|
|
245
|
+
|| normalized.endsWith('apikey')
|
|
246
|
+
|| normalized.endsWith('authorization');
|
|
247
|
+
}
|
|
240
248
|
function buildSafeAgentConfig(config) {
|
|
241
249
|
if (!config)
|
|
242
250
|
return null;
|
|
@@ -592,14 +600,15 @@ export async function execAgentQuery(args) {
|
|
|
592
600
|
}
|
|
593
601
|
const config = cfgRead(ConfigTarget.Agent, { self: aid });
|
|
594
602
|
const safeConfig = buildSafeAgentConfig(config);
|
|
603
|
+
const safeAgentConfig = safeConfig ?? {};
|
|
595
604
|
const data = {
|
|
596
605
|
...res,
|
|
597
606
|
config: {
|
|
598
607
|
...(res.config ?? {}),
|
|
599
|
-
active_baseagent:
|
|
600
|
-
projects:
|
|
601
|
-
baseagents:
|
|
602
|
-
owners:
|
|
608
|
+
active_baseagent: safeAgentConfig.active_baseagent ?? res.config?.baseagent ?? null,
|
|
609
|
+
projects: safeAgentConfig.projects ?? {},
|
|
610
|
+
baseagents: safeAgentConfig.baseagents ?? {},
|
|
611
|
+
owners: safeAgentConfig.owners ?? res.config?.owners ?? [],
|
|
603
612
|
},
|
|
604
613
|
safeConfig,
|
|
605
614
|
};
|
|
@@ -19,7 +19,7 @@ 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 { hashArgv } from '../auth/authorization-audit.js';
|
|
22
|
+
import { auditCommandAuthorization, hashArgv } from '../auth/authorization-audit.js';
|
|
23
23
|
import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
|
|
24
24
|
import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
|
|
25
25
|
import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
|
|
@@ -27,7 +27,7 @@ import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
|
27
27
|
import { isHClassPath, isLClassPath, isSameOrDescendant } from '../protected-paths.js';
|
|
28
28
|
import { validateModelSelectionForRole } from '../model/model-permission.js';
|
|
29
29
|
import { getModelInfo } from '../model/model-catalog.js';
|
|
30
|
-
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride } from '../role/runtime-policy.js';
|
|
30
|
+
import { constrainRuntimePermissionMode, resolveRuntimePermissionMode, validateRuntimeStringFieldOverride } from '../role/runtime-policy.js';
|
|
31
31
|
import { parsePeerKey } from '../relation/peer-identity.js';
|
|
32
32
|
import { isQuickCommand } from './slash-gate.js';
|
|
33
33
|
import { handleSlashCommand } from './slash-handler.js';
|
|
@@ -1002,7 +1002,13 @@ export class CommandHandler {
|
|
|
1002
1002
|
}
|
|
1003
1003
|
const payload = result;
|
|
1004
1004
|
if (payload.kind === 'command.error') {
|
|
1005
|
-
return {
|
|
1005
|
+
return {
|
|
1006
|
+
error: payload.text || '执行失败',
|
|
1007
|
+
code: payload.reason || 'EXEC_FAILED',
|
|
1008
|
+
...(payload.reasonCode ? {
|
|
1009
|
+
data: { reasonCode: payload.reasonCode, nextStep: payload.nextStep },
|
|
1010
|
+
} : {}),
|
|
1011
|
+
};
|
|
1006
1012
|
}
|
|
1007
1013
|
const data = payload.structured && typeof payload.structured === 'object'
|
|
1008
1014
|
? { ...payload.structured }
|
|
@@ -1771,6 +1777,7 @@ export class CommandHandler {
|
|
|
1771
1777
|
},
|
|
1772
1778
|
},
|
|
1773
1779
|
auditMetadata: {
|
|
1780
|
+
correlationId: grant.taskId,
|
|
1774
1781
|
argvHash: hashArgv(parsed.command.canonicalArgv),
|
|
1775
1782
|
taskId: grant.taskId,
|
|
1776
1783
|
messageId: grant.messageId,
|
|
@@ -1854,8 +1861,41 @@ export class CommandHandler {
|
|
|
1854
1861
|
if (!session)
|
|
1855
1862
|
return { ok: false, code: 'INVALID_SESSION', error: 'Invalid session' };
|
|
1856
1863
|
const parsed = resolveManagedMsgOperation(argv);
|
|
1857
|
-
if (!parsed.ok)
|
|
1864
|
+
if (!parsed.ok) {
|
|
1865
|
+
const grant = delegation.grant;
|
|
1866
|
+
auditCommandAuthorization({
|
|
1867
|
+
ts: Date.now(),
|
|
1868
|
+
source: 'agent-tool',
|
|
1869
|
+
operation: 'ec.msg.managed.parse',
|
|
1870
|
+
scope: 'relation',
|
|
1871
|
+
dangerous: false,
|
|
1872
|
+
decision: 'deny',
|
|
1873
|
+
executed: false,
|
|
1874
|
+
executionState: 'blocked',
|
|
1875
|
+
decisionSource: 'policy',
|
|
1876
|
+
policyCode: parsed.code,
|
|
1877
|
+
code: parsed.code,
|
|
1878
|
+
reason: parsed.reason,
|
|
1879
|
+
correlationId: grant.taskId,
|
|
1880
|
+
requestId: grant.taskId,
|
|
1881
|
+
sessionId,
|
|
1882
|
+
agentAid: grant.selfAid,
|
|
1883
|
+
permissionMode: resolveRuntimePermissionMode({ selfAid: grant.selfAid, role: grant.issuedRole }).effectiveValue,
|
|
1884
|
+
actorId: grant.actorId,
|
|
1885
|
+
selfAid: grant.selfAid,
|
|
1886
|
+
peerKey: grant.peerKey,
|
|
1887
|
+
channel: grant.channel,
|
|
1888
|
+
role: grant.issuedRole,
|
|
1889
|
+
taskId: grant.taskId,
|
|
1890
|
+
messageId: grant.messageId,
|
|
1891
|
+
argvHash: hashArgv(argv),
|
|
1892
|
+
argsSummary: {
|
|
1893
|
+
subcommand: String(argv[1] ?? argv[0] ?? '<missing>'),
|
|
1894
|
+
providedOptions: argv.filter(value => typeof value === 'string' && value.startsWith('--')).join(','),
|
|
1895
|
+
},
|
|
1896
|
+
});
|
|
1858
1897
|
return { ok: false, code: parsed.code, error: parsed.reason };
|
|
1898
|
+
}
|
|
1859
1899
|
const grant = delegation.grant;
|
|
1860
1900
|
const grantSelfAid = grant.selfAid.replace(/^@/, '');
|
|
1861
1901
|
const sessionSelfAid = session.selfAID?.replace(/^@/, '');
|
|
@@ -1897,9 +1937,15 @@ export class CommandHandler {
|
|
|
1897
1937
|
targetId: parsed.command.target,
|
|
1898
1938
|
peer: parsed.command.target,
|
|
1899
1939
|
peerKey: formatPeerKey('aun', parsed.command.target),
|
|
1940
|
+
...(parsed.command.session ? { session: parsed.command.session } : {}),
|
|
1941
|
+
...(parsed.command.limit !== undefined ? { limit: parsed.command.limit } : {}),
|
|
1942
|
+
...(parsed.command.before !== undefined ? { before: parsed.command.before } : {}),
|
|
1943
|
+
...(parsed.command.after !== undefined ? { after: parsed.command.after } : {}),
|
|
1944
|
+
...(parsed.command.direction ? { direction: parsed.command.direction } : {}),
|
|
1900
1945
|
},
|
|
1901
1946
|
},
|
|
1902
1947
|
auditMetadata: {
|
|
1948
|
+
correlationId: grant.taskId,
|
|
1903
1949
|
argvHash: hashArgv(parsed.command.canonicalArgv),
|
|
1904
1950
|
taskId: grant.taskId,
|
|
1905
1951
|
messageId: grant.messageId,
|
|
@@ -225,7 +225,7 @@ function updateSetting(key, target, value, args, context) {
|
|
|
225
225
|
if (!allowed.has(raw)) {
|
|
226
226
|
throw new GroupMenuError('INVALID_ARGUMENT', `Invalid ${key} value: ${raw || '<empty>'}`);
|
|
227
227
|
}
|
|
228
|
-
if (key === 'rulesPolicy' && context.role !== 'owner'
|
|
228
|
+
if (key === 'rulesPolicy' && context.role !== 'owner') {
|
|
229
229
|
throw new GroupMenuError('PERMISSION_DENIED', 'rulesPolicy can only be changed by an Agent owner');
|
|
230
230
|
}
|
|
231
231
|
const expectedRevision = stringArg(args?.expectedRevision);
|