evolcore 0.0.12 → 0.0.14
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 +37 -0
- package/bin/codex-managed-hook.mjs +4 -1
- package/bin/ec-safe-output.js +26 -7
- package/bin/install-codex-managed-hooks.mjs +201 -0
- package/dist/agents/claude-runner.js +105 -11
- package/dist/agents/codex-app-server-client.js +176 -7
- package/dist/agents/codex-runner.js +248 -84
- package/dist/agents/ecagent-runner.js +21 -2
- package/dist/agents/gemini-runner.js +9 -4
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +176 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +514 -150
- package/dist/channels/daemon.js +2 -0
- package/dist/cli/agent.js +9 -96
- package/dist/cli/aun-commands.js +1 -1
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +65 -19
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/task-context.js +172 -0
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +7 -0
- package/dist/config/config-manager.js +27 -3
- package/dist/config/contact-book-store.js +7 -2
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +30 -1
- package/dist/core/auth/authorization-audit.js +47 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +10 -1
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +8 -1
- package/dist/core/command/command-handler.js +201 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +34 -1
- package/dist/core/message/message-bridge.js +68 -13
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +242 -48
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +649 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +266 -56
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/runtime-lock.js +101 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +191 -89
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/response-system/engines/v1/proactive-flow.js +92 -8
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/history.js +42 -7
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +17 -0
- package/dist/utils/logger.js +37 -4
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +7 -0
- package/kits/templates/roles/member.json +8 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -14,9 +14,47 @@ export function auditCommandAuthorization(event) {
|
|
|
14
14
|
const auditRecord = buildAuditRecord(event);
|
|
15
15
|
logAuditEvent(auditRecord);
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Record a tool-level preflight denial (e.g. protected-path block) in the
|
|
19
|
+
* structured command-audit log. This covers interactive sessions whose
|
|
20
|
+
* denials otherwise only reach `logger.warn` and `TriggerExecutionAnomaly`.
|
|
21
|
+
*/
|
|
22
|
+
export function auditToolPreflightDenial(input) {
|
|
23
|
+
auditCommandAuthorization({
|
|
24
|
+
ts: Date.now(),
|
|
25
|
+
source: 'agent-tool',
|
|
26
|
+
operation: 'tool.preflight.deny',
|
|
27
|
+
scope: 'filesystem',
|
|
28
|
+
dangerous: false,
|
|
29
|
+
decision: 'deny',
|
|
30
|
+
decisionSource: 'policy',
|
|
31
|
+
toolName: input.toolName,
|
|
32
|
+
policyCode: input.policyCode,
|
|
33
|
+
reason: input.reason,
|
|
34
|
+
argsSummary: input.summary ? { summary: input.summary } : undefined,
|
|
35
|
+
sessionId: input.sessionId,
|
|
36
|
+
agentAid: input.agentAid,
|
|
37
|
+
permissionMode: input.permissionMode,
|
|
38
|
+
channel: input.channel,
|
|
39
|
+
actorId: input.actorId,
|
|
40
|
+
role: input.role ?? 'unknown',
|
|
41
|
+
selfAid: input.selfAid,
|
|
42
|
+
peerKey: input.peerKey,
|
|
43
|
+
requestId: input.requestId,
|
|
44
|
+
taskId: input.taskId,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
17
47
|
function buildAuditRecord(event) {
|
|
18
48
|
return {
|
|
19
49
|
ts: event.ts,
|
|
50
|
+
correlationId: event.correlationId ?? event.requestId,
|
|
51
|
+
requestId: event.requestId,
|
|
52
|
+
sessionId: event.sessionId,
|
|
53
|
+
agentAid: redactIdentifier(event.agentAid ?? event.selfAid),
|
|
54
|
+
permissionMode: event.permissionMode,
|
|
55
|
+
toolName: event.toolName,
|
|
56
|
+
policyCode: event.policyCode ?? event.code,
|
|
57
|
+
decisionSource: event.decisionSource,
|
|
20
58
|
source: event.source,
|
|
21
59
|
operation: event.operation,
|
|
22
60
|
scope: event.scope,
|
|
@@ -71,6 +109,14 @@ function logAuditEvent(record) {
|
|
|
71
109
|
`actor=${record.actorId || 'unknown'}`,
|
|
72
110
|
record.taskId ? `task=${record.taskId}` : null,
|
|
73
111
|
record.messageId ? `message=${record.messageId}` : null,
|
|
112
|
+
record.requestId ? `request=${record.requestId}` : null,
|
|
113
|
+
record.correlationId ? `correlation=${record.correlationId}` : null,
|
|
114
|
+
record.sessionId ? `session=${record.sessionId}` : null,
|
|
115
|
+
record.agentAid ? `agent=${record.agentAid}` : null,
|
|
116
|
+
record.permissionMode ? `permissionMode=${record.permissionMode}` : null,
|
|
117
|
+
record.toolName ? `tool=${record.toolName}` : null,
|
|
118
|
+
record.policyCode ? `policy=${record.policyCode}` : null,
|
|
119
|
+
record.decisionSource ? `decisionSource=${record.decisionSource}` : null,
|
|
74
120
|
record.code ? `code=${record.code}` : null,
|
|
75
121
|
record.matchedRule ? `rule=${record.matchedRule}` : null,
|
|
76
122
|
record.dangerous ? 'dangerous=true' : null,
|
|
@@ -78,6 +124,7 @@ function logAuditEvent(record) {
|
|
|
78
124
|
.filter(Boolean)
|
|
79
125
|
.join(' ');
|
|
80
126
|
logger[level](message);
|
|
127
|
+
logger[level](`[CommandAuditRecord] ${JSON.stringify(record)}`);
|
|
81
128
|
if (record.reason)
|
|
82
129
|
logger[level](` reason: ${record.reason}`);
|
|
83
130
|
if (record.argvHash)
|
|
@@ -170,6 +170,7 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
170
170
|
dangerous: opMeta.dangerous,
|
|
171
171
|
matchedRule,
|
|
172
172
|
constraints: permission.constraints,
|
|
173
|
+
permissionMode: permission.permissionMode,
|
|
173
174
|
};
|
|
174
175
|
}
|
|
175
176
|
function matchCommandPermission(operation, category, commandPerms, isDangerous) {
|
|
@@ -65,7 +65,7 @@ const MSG_READ_OPERATIONS = [
|
|
|
65
65
|
description,
|
|
66
66
|
sources: ['agent-tool'],
|
|
67
67
|
}));
|
|
68
|
-
const GROUP_FS_READ_OPERATIONS = ['ls', 'stat', 'lstat', 'cat', 'find', 'df'].map(kind => ({
|
|
68
|
+
const GROUP_FS_READ_OPERATIONS = ['ls', 'stat', 'lstat', 'cat', 'find', 'df', 'getfacl'].map(kind => ({
|
|
69
69
|
id: `ec.fs.${kind}`,
|
|
70
70
|
category: 'read',
|
|
71
71
|
dangerous: false,
|
|
@@ -73,6 +73,14 @@ const GROUP_FS_READ_OPERATIONS = ['ls', 'stat', 'lstat', 'cat', 'find', 'df'].ma
|
|
|
73
73
|
description: `Read current group filesystem (${kind})`,
|
|
74
74
|
sources: ['agent-tool'],
|
|
75
75
|
}));
|
|
76
|
+
const GROUP_FS_WRITE_OPERATIONS = [['mkdir', 'write-own'], ['upload', 'write-own'], ['copy', 'write-own'], ['move', 'write-own'], ['remove', 'write-own'], ['setfacl', 'write-agent']].map(([kind, category]) => ({
|
|
77
|
+
id: `ec.fs.${kind}`,
|
|
78
|
+
category,
|
|
79
|
+
dangerous: false,
|
|
80
|
+
defaultScopes: ['relation'],
|
|
81
|
+
description: `Write current group filesystem (${kind})`,
|
|
82
|
+
sources: ['agent-tool'],
|
|
83
|
+
}));
|
|
76
84
|
const HANDOFF_READ_OPERATIONS = ['status', 'list', 'trace'].map(kind => ({
|
|
77
85
|
id: `ec.handoff.${kind}`,
|
|
78
86
|
category: 'read',
|
|
@@ -856,6 +864,7 @@ const OPERATIONS = [
|
|
|
856
864
|
...GROUP_MANAGEMENT_OPERATIONS,
|
|
857
865
|
...MSG_READ_OPERATIONS,
|
|
858
866
|
...GROUP_FS_READ_OPERATIONS,
|
|
867
|
+
...GROUP_FS_WRITE_OPERATIONS,
|
|
859
868
|
...HANDOFF_READ_OPERATIONS,
|
|
860
869
|
...HANDOFF_WRITE_OPERATIONS,
|
|
861
870
|
...QUEUE_OPERATIONS,
|
|
@@ -31,14 +31,21 @@ export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName) {
|
|
|
31
31
|
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aid);
|
|
32
32
|
if (hasMessageLogOperation(chatDir, operationId))
|
|
33
33
|
return;
|
|
34
|
+
const existing = outbox.findByDedupeKey(aid, operationId);
|
|
35
|
+
if (existing?.delivery?.chatType === 'private' && existing.channelId === owner)
|
|
36
|
+
return;
|
|
37
|
+
if (existing)
|
|
38
|
+
outbox.remove(aid, existing.id);
|
|
34
39
|
outbox.enqueue(aid, {
|
|
35
40
|
channelId: owner,
|
|
41
|
+
delivery: { chatType: 'private' },
|
|
36
42
|
dedupeKey: operationId,
|
|
37
43
|
critical: true,
|
|
38
44
|
type: 'text',
|
|
39
45
|
text: renderPostBootstrapWelcome(aid, owner, ownerName),
|
|
40
46
|
ttl: BOOTSTRAP_MESSAGE_TTL_MS,
|
|
41
47
|
context: {
|
|
48
|
+
delivery: { chatType: 'private' },
|
|
42
49
|
metadata: {
|
|
43
50
|
source: 'daemon',
|
|
44
51
|
chatmode: 'interactive',
|
|
@@ -49,12 +56,12 @@ export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName) {
|
|
|
49
56
|
});
|
|
50
57
|
}
|
|
51
58
|
export function hasPendingPostBootstrapWelcomeOutbox(aid) {
|
|
52
|
-
return
|
|
59
|
+
return outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid))?.delivery?.chatType === 'private';
|
|
53
60
|
}
|
|
54
61
|
/** Bind the durable completion welcome to the fresh post-bootstrap session. */
|
|
55
62
|
export function bindPostBootstrapWelcomeOutboxSession(aid, sessionId) {
|
|
56
63
|
const entry = outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid));
|
|
57
|
-
if (!entry)
|
|
64
|
+
if (!entry || entry.delivery?.chatType !== 'private')
|
|
58
65
|
return false;
|
|
59
66
|
if (entry.context?.sessionId === sessionId)
|
|
60
67
|
return true;
|
|
@@ -140,7 +140,13 @@ export class BootstrapService {
|
|
|
140
140
|
this.inFlight.delete(key);
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
// The bootstrap prompt is deliberately a private message to the configured
|
|
144
|
+
// owner. An inbound owner message may have arrived from a group, in which
|
|
145
|
+
// case ctx.channelId is the group AID and must not be paired with the
|
|
146
|
+
// private delivery route below (that would call message.send(to=group)).
|
|
147
|
+
const channelId = channelType === 'aun'
|
|
148
|
+
? recipientId
|
|
149
|
+
: ctx.channelId || this.defaultChannelIdForConnection(channelType, recipientId);
|
|
144
150
|
if (!channelId) {
|
|
145
151
|
this.inFlight.delete(key);
|
|
146
152
|
return false;
|
|
@@ -166,6 +172,7 @@ export class BootstrapService {
|
|
|
166
172
|
channelId,
|
|
167
173
|
agentName: aid,
|
|
168
174
|
replyContext: {
|
|
175
|
+
delivery: { chatType: 'private' },
|
|
169
176
|
metadata: {
|
|
170
177
|
source: 'daemon',
|
|
171
178
|
persistRequired: true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
|
|
2
2
|
import { renderCommandCardAsText } from '../interaction-router.js';
|
|
3
|
-
import { buildEnvelope, sendInteractionPayload } from '../message/message-utils.js';
|
|
3
|
+
import { buildEnvelope, deliveryTargetFromSession, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
|
|
4
4
|
import { resolvePaths, getPackageRoot } from '../../paths.js';
|
|
5
5
|
import { loadDaemonConfig } from '../../config-store.js';
|
|
6
6
|
import { logger } from '../../utils/logger.js';
|
|
@@ -18,6 +18,7 @@ import { displaySessionTitle } from '../session/session-title.js';
|
|
|
18
18
|
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
|
+
import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
|
|
21
22
|
import { hashArgv } from '../auth/authorization-audit.js';
|
|
22
23
|
import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
|
|
23
24
|
import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
|
|
@@ -132,6 +133,15 @@ export class CommandHandler {
|
|
|
132
133
|
.filter(key => key.startsWith(prefix))
|
|
133
134
|
.map(key => key.slice(prefix.length));
|
|
134
135
|
}
|
|
136
|
+
/** Return the runner-owned temporary root for a managed session, if any. */
|
|
137
|
+
getManagedSessionTempDir(session) {
|
|
138
|
+
try {
|
|
139
|
+
return this.getAgent(session.channel, session.baseagent).getManagedTempDir?.(session.id);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
135
145
|
formatBaseagentUnavailable(error) {
|
|
136
146
|
const available = error.availableBaseagents.length ? error.availableBaseagents.join(', ') : '(none)';
|
|
137
147
|
return `❌ 当前会话绑定的 baseagent 不可用: ${error.baseagent}\nAgent: ${error.evolagentName}\n可用: ${available}\n请使用 /baseagent 切换到可用后端。`;
|
|
@@ -321,6 +331,7 @@ export class CommandHandler {
|
|
|
321
331
|
channelId: definition.origin.channelId,
|
|
322
332
|
session: definition.origin.session,
|
|
323
333
|
threadId: definition.origin.threadId,
|
|
334
|
+
chatType: definition.origin.delivery?.chatType,
|
|
324
335
|
};
|
|
325
336
|
}
|
|
326
337
|
canAccessTriggerDefinition(definition, peerId, channel, isAdmin) {
|
|
@@ -403,9 +414,6 @@ export class CommandHandler {
|
|
|
403
414
|
}
|
|
404
415
|
const targetChannelType = this.resolveChannelType(feedbackChannel);
|
|
405
416
|
const feedbackChannelId = parsed.targetChannelId ?? peerId;
|
|
406
|
-
if (targetChannelType === 'aun' && parsed.targetChannelId && !parsed.targetChannelId.includes('.')) {
|
|
407
|
-
return { error: `AUN 渠道的 --target-channel-id 必须是 AID 格式(如 user.agentid.pub),收到:"${parsed.targetChannelId}"` };
|
|
408
|
-
}
|
|
409
417
|
const schedulerAid = parsed.agentId
|
|
410
418
|
?? this.getOwningAgent(feedbackChannel)?.aid
|
|
411
419
|
?? tryParseChannelKey(feedbackChannel)?.selfAID
|
|
@@ -414,11 +422,22 @@ export class CommandHandler {
|
|
|
414
422
|
if (!schedulerAid || !scheduler) {
|
|
415
423
|
return { error: `目标 agent 不存在或未就绪:${schedulerAid ?? feedbackChannel}` };
|
|
416
424
|
}
|
|
425
|
+
if (targetChannelType === 'aun' && parsed.targetChannelId && !parsed.targetChatType) {
|
|
426
|
+
return { error: 'AUN 目标必须显式指定 --target-chat-type private 或 group' };
|
|
427
|
+
}
|
|
428
|
+
const targetDelivery = targetChannelType === 'aun'
|
|
429
|
+
? parsed.targetChatType === 'group'
|
|
430
|
+
? { chatType: 'group', groupId: feedbackChannelId }
|
|
431
|
+
: parsed.targetChatType === 'private'
|
|
432
|
+
? { chatType: 'private' }
|
|
433
|
+
: { chatType: 'private' }
|
|
434
|
+
: undefined;
|
|
417
435
|
const target = {
|
|
418
436
|
channelKey: feedbackChannel,
|
|
419
437
|
channelId: feedbackChannelId,
|
|
420
438
|
session: parsed.targetSession,
|
|
421
439
|
threadId: parsed.targetSession === 'thread' ? parsed.targetThreadId : undefined,
|
|
440
|
+
chatType: targetDelivery?.chatType,
|
|
422
441
|
};
|
|
423
442
|
if (target.session === 'thread') {
|
|
424
443
|
const adapter = this.adapters.get(feedbackChannel);
|
|
@@ -439,6 +458,7 @@ export class CommandHandler {
|
|
|
439
458
|
threadId: !originIsGroup ? threadId : undefined,
|
|
440
459
|
peerId,
|
|
441
460
|
sessionKey: activeSession?.sessionKey,
|
|
461
|
+
delivery: { chatType: 'private' },
|
|
442
462
|
};
|
|
443
463
|
if (!isAdmin && parsed.feedbackStrategy === 'target' && !sameFeedbackTarget(target, origin)) {
|
|
444
464
|
return { error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
|
|
@@ -618,7 +638,7 @@ export class CommandHandler {
|
|
|
618
638
|
}
|
|
619
639
|
/** 从 session 提取渠道预构建的回复上下文 */
|
|
620
640
|
getReplyContext(session) {
|
|
621
|
-
return session
|
|
641
|
+
return replyContextFromSession(session);
|
|
622
642
|
}
|
|
623
643
|
/**
|
|
624
644
|
* 发送 CommandCard 卡片。卡片成功返回 null(调用方直接 return),失败返回降级文本。
|
|
@@ -1426,7 +1446,7 @@ export class CommandHandler {
|
|
|
1426
1446
|
* - taskId/chatmode:从 processing_state 和 effective chatmode 注入
|
|
1427
1447
|
*/
|
|
1428
1448
|
buildCtlReplyContext(session) {
|
|
1429
|
-
const ctx = {};
|
|
1449
|
+
const ctx = { delivery: deliveryTargetFromSession(session) };
|
|
1430
1450
|
const meta = session.metadata;
|
|
1431
1451
|
if (meta?.replyContext?.threadId)
|
|
1432
1452
|
ctx.threadId = meta.replyContext.threadId;
|
|
@@ -1454,7 +1474,7 @@ export class CommandHandler {
|
|
|
1454
1474
|
if (encrypted != null)
|
|
1455
1475
|
ctx.metadata.encrypted = encrypted;
|
|
1456
1476
|
}
|
|
1457
|
-
return
|
|
1477
|
+
return ctx;
|
|
1458
1478
|
}
|
|
1459
1479
|
resolveEffectiveChatmodeForSession(session) {
|
|
1460
1480
|
const peerType = session.metadata?.peerType;
|
|
@@ -1509,35 +1529,51 @@ export class CommandHandler {
|
|
|
1509
1529
|
conversationId,
|
|
1510
1530
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1511
1531
|
});
|
|
1512
|
-
const
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
subject,
|
|
1520
|
-
intent: {
|
|
1521
|
-
operation: resolved.command.operationId,
|
|
1522
|
-
scope: resolved.command.commandScope,
|
|
1532
|
+
const executeOne = (fieldArgv) => {
|
|
1533
|
+
const resolved = resolveConfigCommand(fieldArgv, {
|
|
1534
|
+
defaultRelation: { self: grant.selfAid, peerKey: grant.peerKey },
|
|
1535
|
+
});
|
|
1536
|
+
if (!resolved.ok)
|
|
1537
|
+
return { ok: false, code: resolved.code, error: resolved.reason };
|
|
1538
|
+
const decision = authorizeOperation({
|
|
1523
1539
|
source: 'agent-tool',
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1540
|
+
subject,
|
|
1541
|
+
intent: {
|
|
1542
|
+
operation: resolved.command.operationId,
|
|
1543
|
+
scope: resolved.command.commandScope,
|
|
1544
|
+
source: 'agent-tool',
|
|
1545
|
+
args: {
|
|
1546
|
+
...(resolved.command.self ? { self: resolved.command.self } : {}),
|
|
1547
|
+
...(resolved.command.peerKey ? { peer: resolved.command.peerKey, peerKey: resolved.command.peerKey } : {}),
|
|
1548
|
+
...('field' in resolved.command && resolved.command.field ? { field: resolved.command.field } : {}),
|
|
1549
|
+
},
|
|
1550
|
+
dangerous: resolved.command.dangerous,
|
|
1528
1551
|
},
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
const
|
|
1552
|
+
resolvedConfigCommand: resolved.command,
|
|
1553
|
+
auditMetadata: {
|
|
1554
|
+
argvHash: hashArgv(resolved.command.canonicalArgv),
|
|
1555
|
+
taskId: grant.taskId,
|
|
1556
|
+
messageId: grant.messageId,
|
|
1557
|
+
},
|
|
1558
|
+
});
|
|
1559
|
+
if (!decision.allow)
|
|
1560
|
+
return { ok: false, code: decision.code, error: decision.reason };
|
|
1561
|
+
return executeResolvedConfigCommand(resolved.command, { role: subject.role });
|
|
1562
|
+
};
|
|
1563
|
+
const batch = splitConfigBatchGetArgv(argv);
|
|
1564
|
+
if (batch && batch.fields.length > 1) {
|
|
1565
|
+
const results = batch.fields.map(field => {
|
|
1566
|
+
const result = executeOne(['config', 'get', field, ...batch.rest]);
|
|
1567
|
+
if (!result.ok)
|
|
1568
|
+
return { ok: false, field, code: result.code, error: result.error };
|
|
1569
|
+
if (result.subcommand !== 'get') {
|
|
1570
|
+
return { ok: false, field, code: 'UNEXPECTED_RESULT', error: `config get ${field} produced a non-get result` };
|
|
1571
|
+
}
|
|
1572
|
+
return result;
|
|
1573
|
+
});
|
|
1574
|
+
return { ok: true, result: { ok: true, subcommand: 'batch-get', results } };
|
|
1575
|
+
}
|
|
1576
|
+
const result = executeOne(argv);
|
|
1541
1577
|
return result.ok
|
|
1542
1578
|
? { ok: true, result }
|
|
1543
1579
|
: { ok: false, code: result.code, error: result.error, ...(result.data !== undefined ? { data: result.data } : {}) };
|
|
@@ -1747,23 +1783,41 @@ export class CommandHandler {
|
|
|
1747
1783
|
const filePath = String(parsed.command.filePath || '');
|
|
1748
1784
|
const projectPath = path.resolve(session.projectPath);
|
|
1749
1785
|
const resolvedPath = path.resolve(projectPath, filePath);
|
|
1750
|
-
|
|
1751
|
-
|
|
1786
|
+
const managedTempPath = this.getManagedSessionTempDir(session);
|
|
1787
|
+
const allowedPaths = [projectPath, managedTempPath].filter((value) => !!value);
|
|
1788
|
+
if (!filePath || !allowedPaths.some(root => isSameOrDescendant(resolvedPath, root))) {
|
|
1789
|
+
return { ok: false, code: 'INVALID_RULES_PATH', error: 'Rules file must be inside the current session project or managed TMPDIR' };
|
|
1752
1790
|
}
|
|
1753
1791
|
if (isHClassPath(resolvedPath) || isLClassPath(resolvedPath)) {
|
|
1754
1792
|
return { ok: false, code: 'PROTECTED_RULES_PATH', error: 'Rules file cannot be read from a protected EvolCore path' };
|
|
1755
1793
|
}
|
|
1794
|
+
let rulesFd;
|
|
1756
1795
|
try {
|
|
1757
1796
|
const realProjectPath = fs.realpathSync(projectPath);
|
|
1758
1797
|
const realPath = fs.realpathSync(resolvedPath);
|
|
1759
|
-
|
|
1760
|
-
|
|
1798
|
+
let realManagedTempPath;
|
|
1799
|
+
try {
|
|
1800
|
+
realManagedTempPath = managedTempPath ? fs.realpathSync(managedTempPath) : undefined;
|
|
1801
|
+
}
|
|
1802
|
+
catch {
|
|
1803
|
+
// The session may have closed and removed its temporary directory.
|
|
1804
|
+
}
|
|
1805
|
+
const realAllowedPaths = [realProjectPath, realManagedTempPath].filter((value) => !!value);
|
|
1806
|
+
if (!realAllowedPaths.some(root => isSameOrDescendant(realPath, root))) {
|
|
1807
|
+
return { ok: false, code: 'INVALID_RULES_PATH', error: 'Rules file resolves outside the current session project or managed TMPDIR' };
|
|
1761
1808
|
}
|
|
1762
|
-
|
|
1809
|
+
if (isHClassPath(realPath) || isLClassPath(realPath)) {
|
|
1810
|
+
return { ok: false, code: 'PROTECTED_RULES_PATH', error: 'Rules file cannot be read from a protected EvolCore path' };
|
|
1811
|
+
}
|
|
1812
|
+
rulesFd = fs.openSync(realPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
1813
|
+
const stat = fs.fstatSync(rulesFd);
|
|
1763
1814
|
if (!stat.isFile() || stat.size < 1 || stat.size > 4 * 1024) {
|
|
1764
1815
|
return { ok: false, code: 'INVALID_RULES_CONTENT', error: 'Rules file must be a regular file from 1 to 4096 bytes' };
|
|
1765
1816
|
}
|
|
1766
|
-
|
|
1817
|
+
if (realManagedTempPath && isSameOrDescendant(realPath, realManagedTempPath) && stat.nlink > 1) {
|
|
1818
|
+
return { ok: false, code: 'INVALID_RULES_PATH', error: 'Rules file cannot be a hard link' };
|
|
1819
|
+
}
|
|
1820
|
+
rulesContent = new TextDecoder('utf-8', { fatal: true }).decode(fs.readFileSync(rulesFd));
|
|
1767
1821
|
}
|
|
1768
1822
|
catch (error) {
|
|
1769
1823
|
return {
|
|
@@ -1772,6 +1826,10 @@ export class CommandHandler {
|
|
|
1772
1826
|
error: error instanceof Error ? error.message : String(error),
|
|
1773
1827
|
};
|
|
1774
1828
|
}
|
|
1829
|
+
finally {
|
|
1830
|
+
if (rulesFd !== undefined)
|
|
1831
|
+
fs.closeSync(rulesFd);
|
|
1832
|
+
}
|
|
1775
1833
|
}
|
|
1776
1834
|
try {
|
|
1777
1835
|
return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent) };
|
|
@@ -1860,7 +1918,7 @@ export class CommandHandler {
|
|
|
1860
1918
|
};
|
|
1861
1919
|
}
|
|
1862
1920
|
}
|
|
1863
|
-
/** Agent-managed current-group filesystem
|
|
1921
|
+
/** Agent-managed current-group filesystem entrypoint. */
|
|
1864
1922
|
async handleFsOperation(argv, sessionId, delegationToken, delegationCommandHash) {
|
|
1865
1923
|
if (!this.agentDelegationRegistry) {
|
|
1866
1924
|
return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
|
|
@@ -1881,7 +1939,7 @@ export class CommandHandler {
|
|
|
1881
1939
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
|
|
1882
1940
|
}
|
|
1883
1941
|
if (grant.channelType !== 'aun' || grant.chatType !== 'group') {
|
|
1884
|
-
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed fs
|
|
1942
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed fs operations require the current AUN group relation' };
|
|
1885
1943
|
}
|
|
1886
1944
|
let currentGroup;
|
|
1887
1945
|
try {
|
|
@@ -1904,6 +1962,22 @@ export class CommandHandler {
|
|
|
1904
1962
|
conversationId: currentGroup,
|
|
1905
1963
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1906
1964
|
});
|
|
1965
|
+
const intentArgs = {
|
|
1966
|
+
self: actorAid,
|
|
1967
|
+
targetId: currentGroup,
|
|
1968
|
+
peer: currentGroup,
|
|
1969
|
+
peerKey: formatPeerKey('aun', currentGroup),
|
|
1970
|
+
path: parsed.command.remotePath,
|
|
1971
|
+
targetPath: parsed.command.remotePath,
|
|
1972
|
+
...(parsed.command.remoteSourcePath ? { sourcePath: parsed.command.remoteSourcePath } : {}),
|
|
1973
|
+
...(parsed.command.localPath ? { localPath: parsed.command.localPath } : {}),
|
|
1974
|
+
...(parsed.command.recursive !== undefined ? { recursive: parsed.command.recursive } : {}),
|
|
1975
|
+
...(parsed.command.parents !== undefined ? { parents: parsed.command.parents } : {}),
|
|
1976
|
+
...(parsed.command.overwrite !== undefined ? { overwrite: parsed.command.overwrite } : {}),
|
|
1977
|
+
...(parsed.command.aclGrantee ? { aclGrantee: parsed.command.aclGrantee } : {}),
|
|
1978
|
+
...(parsed.command.aclPerms ? { aclPerms: parsed.command.aclPerms } : {}),
|
|
1979
|
+
...(parsed.command.aclRemove !== undefined ? { aclRemove: parsed.command.aclRemove } : {}),
|
|
1980
|
+
};
|
|
1907
1981
|
const decision = authorizeOperation({
|
|
1908
1982
|
source: 'agent-tool',
|
|
1909
1983
|
subject,
|
|
@@ -1911,17 +1985,60 @@ export class CommandHandler {
|
|
|
1911
1985
|
operation: parsed.command.operationId,
|
|
1912
1986
|
scope: 'relation',
|
|
1913
1987
|
source: 'agent-tool',
|
|
1914
|
-
args:
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
},
|
|
1988
|
+
args: intentArgs,
|
|
1989
|
+
},
|
|
1990
|
+
auditMetadata: {
|
|
1991
|
+
argvHash: hashArgv(parsed.command.canonicalArgv), taskId: grant.taskId, messageId: grant.messageId,
|
|
1992
|
+
sessionId, agentAid: actorAid, toolName: 'ec.fs',
|
|
1920
1993
|
},
|
|
1921
|
-
auditMetadata: { argvHash: hashArgv(parsed.command.canonicalArgv), taskId: grant.taskId, messageId: grant.messageId },
|
|
1922
1994
|
});
|
|
1923
1995
|
if (!decision.allow)
|
|
1924
1996
|
return { ok: false, code: decision.code, error: decision.reason };
|
|
1997
|
+
if (parsed.command.kind === 'upload') {
|
|
1998
|
+
const localPath = String(parsed.command.localPath || '').trim();
|
|
1999
|
+
const projectPath = path.resolve(session.projectPath);
|
|
2000
|
+
if (!localPath)
|
|
2001
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: 'Managed upload source is required' };
|
|
2002
|
+
const resolvedPath = path.resolve(projectPath, localPath);
|
|
2003
|
+
const managedTempPath = this.getManagedSessionTempDir(session);
|
|
2004
|
+
const allowedRoots = [projectPath, managedTempPath].filter((value) => !!value);
|
|
2005
|
+
if (!allowedRoots.some(root => isSameOrDescendant(resolvedPath, root))) {
|
|
2006
|
+
return { ok: false, code: 'INVALID_LOCAL_PATH', error: 'Upload source must be inside the current session project or managed TMPDIR' };
|
|
2007
|
+
}
|
|
2008
|
+
if (isHClassPath(resolvedPath) || isLClassPath(resolvedPath)) {
|
|
2009
|
+
return { ok: false, code: 'PROTECTED_LOCAL_PATH', error: 'Upload source cannot be a protected EvolCore path' };
|
|
2010
|
+
}
|
|
2011
|
+
try {
|
|
2012
|
+
const realProjectPath = fs.realpathSync(projectPath);
|
|
2013
|
+
const stat = fs.lstatSync(resolvedPath);
|
|
2014
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
2015
|
+
return { ok: false, code: 'INVALID_LOCAL_FILE', error: 'Upload source must be a regular non-symlink file' };
|
|
2016
|
+
}
|
|
2017
|
+
const realPath = fs.realpathSync(resolvedPath);
|
|
2018
|
+
let realManagedTempPath;
|
|
2019
|
+
try {
|
|
2020
|
+
realManagedTempPath = managedTempPath ? fs.realpathSync(managedTempPath) : undefined;
|
|
2021
|
+
}
|
|
2022
|
+
catch {
|
|
2023
|
+
// A removed or invalid session temp root is not an upload root.
|
|
2024
|
+
}
|
|
2025
|
+
const realAllowedRoots = [realProjectPath, realManagedTempPath].filter((value) => !!value);
|
|
2026
|
+
if (!realAllowedRoots.some(root => isSameOrDescendant(realPath, root))
|
|
2027
|
+
|| isHClassPath(realPath) || isLClassPath(realPath)) {
|
|
2028
|
+
return { ok: false, code: 'PROTECTED_LOCAL_PATH', error: 'Upload source resolves outside the allowed project or managed TMPDIR path' };
|
|
2029
|
+
}
|
|
2030
|
+
if (realManagedTempPath && isSameOrDescendant(realPath, realManagedTempPath) && stat.nlink > 1) {
|
|
2031
|
+
return { ok: false, code: 'INVALID_LOCAL_PATH', error: 'Upload source cannot be a hard link inside managed TMPDIR' };
|
|
2032
|
+
}
|
|
2033
|
+
if (stat.size > 16 * 1024 * 1024) {
|
|
2034
|
+
return { ok: false, code: 'LOCAL_FILE_TOO_LARGE', error: 'Managed upload is limited to 16 MiB' };
|
|
2035
|
+
}
|
|
2036
|
+
parsed.command.localPath = realPath;
|
|
2037
|
+
}
|
|
2038
|
+
catch (error) {
|
|
2039
|
+
return { ok: false, code: 'INVALID_LOCAL_FILE', error: error instanceof Error ? error.message : String(error) };
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
1925
2042
|
try {
|
|
1926
2043
|
return { ok: true, result: await executeManagedFsOperation(parsed.command) };
|
|
1927
2044
|
}
|
|
@@ -2126,6 +2243,14 @@ export class CommandHandler {
|
|
|
2126
2243
|
const showId = args.includes('--showid');
|
|
2127
2244
|
const formatJson = args.includes('--format json');
|
|
2128
2245
|
const full = args.includes('--full');
|
|
2246
|
+
const after = parseQueueTimeArg(this.extractArg(args, '--after'));
|
|
2247
|
+
const before = parseQueueTimeArg(this.extractArg(args, '--before'));
|
|
2248
|
+
if (this.extractArg(args, '--after') && after === undefined)
|
|
2249
|
+
return { ok: false, error: '❌ --after 必须是 epoch 毫秒或 ISO 时间' };
|
|
2250
|
+
if (this.extractArg(args, '--before') && before === undefined)
|
|
2251
|
+
return { ok: false, error: '❌ --before 必须是 epoch 毫秒或 ISO 时间' };
|
|
2252
|
+
if (after !== undefined && before !== undefined && after >= before)
|
|
2253
|
+
return { ok: false, error: '❌ --after 必须早于 --before' };
|
|
2129
2254
|
// 操作分支
|
|
2130
2255
|
if (args.includes('--clear')) {
|
|
2131
2256
|
const count = this.messageQueue.clearBySession(sessionId);
|
|
@@ -2147,13 +2272,19 @@ export class CommandHandler {
|
|
|
2147
2272
|
: { ok: false, error: `❌ 当前无处理中任务` };
|
|
2148
2273
|
}
|
|
2149
2274
|
// 查询
|
|
2150
|
-
const items = this.messageQueue.getQueueItemsBySession(sessionId);
|
|
2275
|
+
const items = this.messageQueue.getQueueItemsBySession(sessionId, { after, before });
|
|
2151
2276
|
if (formatJson) {
|
|
2152
2277
|
return { ok: true, result: JSON.stringify({ items }, null, 2) };
|
|
2153
2278
|
}
|
|
2154
2279
|
return { ok: true, result: renderQueueItemsCtl(items, showId, full) };
|
|
2155
2280
|
}
|
|
2156
2281
|
}
|
|
2282
|
+
function parseQueueTimeArg(value) {
|
|
2283
|
+
if (!value)
|
|
2284
|
+
return undefined;
|
|
2285
|
+
const parsed = /^\d+$/.test(value) ? Number(value) : Date.parse(value);
|
|
2286
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
2287
|
+
}
|
|
2157
2288
|
/**
|
|
2158
2289
|
* ctl 专用渲染:不显示 session 标识列(因为只有一个 session)
|
|
2159
2290
|
*/
|
|
@@ -2176,14 +2307,33 @@ function renderQueueItemsCtl(items, showId, full) {
|
|
|
2176
2307
|
parts.push(` ${name} `);
|
|
2177
2308
|
}
|
|
2178
2309
|
const content = full ? item.preview.replace('...', '') : item.preview;
|
|
2179
|
-
|
|
2310
|
+
const timing = [
|
|
2311
|
+
item.receivedAt != null ? `recv=${new Date(item.receivedAt).toISOString()}` : undefined,
|
|
2312
|
+
item.enqueuedAt != null ? `enq=${new Date(item.enqueuedAt).toISOString()}` : undefined,
|
|
2313
|
+
item.queueWaitMs != null ? `wait=${formatQueueDuration(item.queueWaitMs)}` : undefined,
|
|
2314
|
+
].filter(Boolean).join(' ');
|
|
2315
|
+
parts.push(`${timing ? `${timing} ` : ''}"${content}"`);
|
|
2180
2316
|
lines.push(parts.join(''));
|
|
2181
2317
|
}
|
|
2182
2318
|
return lines.join('\n');
|
|
2183
2319
|
}
|
|
2320
|
+
function formatQueueDuration(ms) {
|
|
2321
|
+
const sec = Math.max(0, Math.round(ms / 1000));
|
|
2322
|
+
return sec >= 60 ? `${Math.floor(sec / 60)}m${sec % 60}s` : `${sec}s`;
|
|
2323
|
+
}
|
|
2184
2324
|
function sameFeedbackTarget(target, origin) {
|
|
2185
2325
|
if (!origin)
|
|
2186
2326
|
return false;
|
|
2327
|
+
const targetChannelType = tryParseChannelKey(target.channelKey)?.type
|
|
2328
|
+
?? target.channelKey.split('#', 1)[0];
|
|
2329
|
+
const targetChatType = target.chatType
|
|
2330
|
+
?? (targetChannelType === 'aun' ? undefined : 'private');
|
|
2331
|
+
if (!targetChatType)
|
|
2332
|
+
return false;
|
|
2333
|
+
const originDelivery = origin.delivery;
|
|
2334
|
+
const originChatType = originDelivery?.chatType ?? 'private';
|
|
2335
|
+
if (targetChatType !== originChatType)
|
|
2336
|
+
return false;
|
|
2187
2337
|
return target.channelKey === origin.channelKey
|
|
2188
2338
|
&& target.channelId === origin.channelId
|
|
2189
2339
|
&& target.session === origin.session
|
|
@@ -131,6 +131,8 @@ export async function resolveMenuCatalogContext(input) {
|
|
|
131
131
|
const rebuilt = buildAuthSubject({
|
|
132
132
|
selfAid: subject.selfAid,
|
|
133
133
|
actorId: subject.actorId,
|
|
134
|
+
requestId: subject.requestId,
|
|
135
|
+
sessionId: subject.sessionId,
|
|
134
136
|
channel: subject.channel,
|
|
135
137
|
channelType: transport,
|
|
136
138
|
channelId: subject.actorId,
|
|
@@ -191,6 +193,8 @@ export async function resolveMenuCatalogContext(input) {
|
|
|
191
193
|
const rebuilt = buildAuthSubject({
|
|
192
194
|
selfAid: subject.selfAid,
|
|
193
195
|
actorId: subject.actorId,
|
|
196
|
+
requestId: subject.requestId,
|
|
197
|
+
sessionId: subject.sessionId,
|
|
194
198
|
channel: subject.channel,
|
|
195
199
|
channelType: transport,
|
|
196
200
|
channelId: canonicalGroupId,
|