evolcore 0.0.23 → 0.0.25
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 +583 -516
- package/LICENSE +21 -21
- package/README.md +455 -453
- package/bin/codex-managed-acl.ps1 +160 -155
- package/bin/codex-managed-hook.mjs +209 -92
- package/bin/ec-safe-output.js +327 -245
- package/bin/ec.js +29 -29
- package/bin/install-codex-managed-hooks.mjs +555 -544
- package/dist/agents/baseagent-availability.js +4 -4
- package/dist/agents/baseagent.js +29 -1
- package/dist/agents/claude-runner.js +152 -14
- package/dist/agents/codex/app-server-client.js +18 -8
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +215 -0
- package/dist/agents/codex-runner.js +445 -63
- package/dist/agents/ecagent-runner.js +31 -16
- package/dist/agents/gemini-runner.js +82 -4
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/agentmd.js +56 -4
- package/dist/aun/aid/index.js +1 -0
- package/dist/aun/aid/managed-operation.js +80 -6
- package/dist/aun/aid/store.js +41 -0
- package/dist/aun/msg/managed-operation.js +6 -1
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +4 -5
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +147 -76
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +214 -89
- package/dist/cli/agent-create.js +576 -0
- package/dist/cli/agent.js +129 -676
- package/dist/cli/aun-commands.js +419 -313
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +20 -20
- package/dist/cli/contact.js +10 -10
- package/dist/cli/ctl-command.js +22 -20
- package/dist/cli/daemon-commands.js +163 -32
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +7 -7
- package/dist/cli/index.js +79 -79
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +11 -11
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +462 -38
- package/dist/cli/stats.js +161 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +3 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +20 -3
- package/dist/config/resolved-config-op.js +39 -1
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +72 -19
- package/dist/core/auth/operation-authorizer.js +125 -7
- package/dist/core/auth/operation-catalog.js +26 -2
- package/dist/core/auth/remote-access-store.js +3 -2
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +60 -26
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +173 -36
- package/dist/core/command/menu-catalog.js +4 -1
- package/dist/core/command/menu-handler.js +315 -75
- package/dist/core/command/menu-protocol.js +7 -2
- package/dist/core/command/slash-gate.js +1 -3
- package/dist/core/command/slash-handler.js +166 -57
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +85 -27
- package/dist/core/model/catalog-service.js +3 -1
- package/dist/core/model/model-catalog.js +10 -0
- package/dist/core/model/model-permission.js +4 -1
- package/dist/core/permission/approval-gateway.js +169 -33
- package/dist/core/permission/tool-policy.js +56 -10
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +327 -134
- package/dist/ipc.js +68 -21
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +11 -4
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +24 -1
- package/dist/utils/welcome.js +22 -22
- package/dist/utils/windows-autostart.js +1 -1
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +365 -364
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +71 -69
- package/kits/docs/evolcore/agent-create.md +170 -156
- package/kits/docs/evolcore/agent.md +83 -73
- package/kits/docs/evolcore/aid.md +64 -62
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +195 -195
- package/kits/docs/evolcore/contact.md +68 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +34 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +165 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +50 -50
- package/kits/docs/evolcore/trigger.md +575 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +270 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +25 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +195 -183
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +39 -37
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +427 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +201 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +96 -82
- package/kits/templates/roles/member.json +97 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +86 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +38 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +98 -94
- package/AGENTS.md +0 -62
- package/dist/utils/codex-cli.js +0 -43
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { agentDelete, agentEnable, agentDisable, agentList, agentShow, agentSet, agentReload, } from '../../cli/agent.js';
|
|
2
|
+
import { agentCreateNonInteractive } from '../../cli/agent-create.js';
|
|
2
3
|
import { logger } from '../../utils/logger.js';
|
|
3
4
|
import { resolvePaths, resolveRoot } from '../../paths.js';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import { ConfigTarget, ensureFile as cfgEnsure, read as cfgRead, write as cfgWrite, } from '../../config/config-manager.js';
|
|
6
7
|
import { CreateStatusWriter, readCreateStatus } from '../message/create-status.js';
|
|
7
8
|
import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../../utils/project-path.js';
|
|
8
|
-
import { uploadAvatar } from '../../utils/avatar-upload.js';
|
|
9
|
+
import { uploadAvatar, uploadAvatarUrl } from '../../utils/avatar-upload.js';
|
|
9
10
|
import { agentmdGet, agentmdPut, updateAgentMdFrontmatterName } from '../../aun/aid/agentmd.js';
|
|
10
11
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
11
12
|
import { isSupportedBaseagent } from '../../agents/baseagent.js';
|
|
12
|
-
const SUPPORTED_AGENT_PATCH_FIELDS = new Set(['aid', 'name', '
|
|
13
|
+
const SUPPORTED_AGENT_PATCH_FIELDS = new Set(['aid', 'name', 'active_baseagent', 'baseagents', 'projects', 'owners', 'chatmode', 'channels', 'channelOwners']);
|
|
13
14
|
const HIDDEN_VALUE = '[hidden]';
|
|
14
15
|
const SENSITIVE_CONFIG_KEYS = new Set([
|
|
15
16
|
'apikey',
|
|
@@ -47,6 +48,42 @@ function classifyAgentMdError(error) {
|
|
|
47
48
|
return 'INVALID_ARGS';
|
|
48
49
|
return 'UPLOAD_FAILED';
|
|
49
50
|
}
|
|
51
|
+
/** Canonical avatar mutation for the `aid.avatar` menu action. */
|
|
52
|
+
export async function execAidAvatar(args) {
|
|
53
|
+
const a = args ?? {};
|
|
54
|
+
const aid = typeof a.aid === 'string' ? a.aid.trim().replace(/^@/, '') : '';
|
|
55
|
+
if (!aid)
|
|
56
|
+
return { error: '缺少 aid', code: 'INVALID_ARGS' };
|
|
57
|
+
if (!isValidAid(aid))
|
|
58
|
+
return { error: `无效 aid: ${aid}`, code: 'INVALID_ARGS' };
|
|
59
|
+
const hasData = a.data !== undefined;
|
|
60
|
+
const hasUrl = a.url !== undefined;
|
|
61
|
+
if ((hasData ? 1 : 0) + (hasUrl ? 1 : 0) > 1) {
|
|
62
|
+
return { error: '头像数据源必须在 data 与 url 中二选一', code: 'INVALID_ARGS' };
|
|
63
|
+
}
|
|
64
|
+
if (!hasData && !hasUrl) {
|
|
65
|
+
return { error: '缺少头像数据源:需要 data 或 url', code: 'INVALID_ARGS' };
|
|
66
|
+
}
|
|
67
|
+
const result = hasUrl
|
|
68
|
+
? await uploadAvatarUrl(aid, a.url, { aunPath: resolveRoot() })
|
|
69
|
+
: await uploadAvatar(aid, a.data, { aunPath: resolveRoot() });
|
|
70
|
+
if (!result.ok) {
|
|
71
|
+
return { error: `头像上传失败: ${result.error}`, code: result.code ?? 'UPLOAD_FAILED' };
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
data: {
|
|
75
|
+
aid,
|
|
76
|
+
avatar: result.publicUrl,
|
|
77
|
+
objectKey: result.objectKey,
|
|
78
|
+
sha256: result.sha256,
|
|
79
|
+
mime: result.mime,
|
|
80
|
+
width: result.width,
|
|
81
|
+
height: result.height,
|
|
82
|
+
aspectRatio: result.aspectRatio,
|
|
83
|
+
saved: true,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
50
87
|
async function updateAgentMdName(aid, value) {
|
|
51
88
|
const name = typeof value === 'string' ? value.trim() : '';
|
|
52
89
|
if (!name)
|
|
@@ -297,6 +334,8 @@ async function runCreateInBackground(opts, w, service) {
|
|
|
297
334
|
const createOptions = {
|
|
298
335
|
aid: opts.aid, name: opts.name, baseagent: opts.baseagent,
|
|
299
336
|
project: opts.project, owner: opts.owner,
|
|
337
|
+
description: opts.description,
|
|
338
|
+
tags: opts.tags,
|
|
300
339
|
onPhase: (phase, state, detail) => {
|
|
301
340
|
if (state === 'begin') {
|
|
302
341
|
curPhase = phase;
|
|
@@ -393,6 +432,10 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
|
|
|
393
432
|
const model = typeof a.model === 'string' && a.model.trim() ? a.model.trim() : undefined;
|
|
394
433
|
const rawEffort = typeof a.effort === 'string' && a.effort.trim() ? a.effort.trim() : undefined;
|
|
395
434
|
const effort = rawEffort && rawEffort !== 'auto' ? rawEffort : undefined;
|
|
435
|
+
const description = typeof a.description === 'string' && a.description.trim()
|
|
436
|
+
? a.description.trim()
|
|
437
|
+
: undefined;
|
|
438
|
+
const tags = Array.isArray(a.tags) ? a.tags : undefined;
|
|
396
439
|
// D3: accepted=true 必须意味着后续 query 能查到该 AID 的创建状态。
|
|
397
440
|
let progressWriter;
|
|
398
441
|
try {
|
|
@@ -405,7 +448,7 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
|
|
|
405
448
|
void runCreateInBackground({
|
|
406
449
|
aid: a.aid, name: a.name, baseagent: a.baseagent,
|
|
407
450
|
project: a.project, owner,
|
|
408
|
-
model, effort, chatmode: a.chatmode,
|
|
451
|
+
model, effort, description, tags, chatmode: a.chatmode,
|
|
409
452
|
eventBus,
|
|
410
453
|
}, progressWriter, service).catch(e => logger.error(`[agent-control] runCreateInBackground unhandled ${a.aid}: ${e?.message || e}`));
|
|
411
454
|
return { data: { accepted: true, aid: a.aid } };
|
|
@@ -456,14 +499,6 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
|
|
|
456
499
|
if (action === 'update') {
|
|
457
500
|
const result = await execAgentUpdate(a);
|
|
458
501
|
if (!('error' in result)) {
|
|
459
|
-
if (result.data?.avatar) {
|
|
460
|
-
eventBus?.publish({
|
|
461
|
-
type: 'agent:avatar_updated',
|
|
462
|
-
aid: a.aid,
|
|
463
|
-
avatar: result.data.avatar,
|
|
464
|
-
timestamp: Date.now(),
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
502
|
eventBus?.publish({
|
|
468
503
|
type: 'agent:updated',
|
|
469
504
|
aid: a.aid,
|
|
@@ -496,12 +531,6 @@ export async function execAgentUpdate(args) {
|
|
|
496
531
|
if (p.aid !== undefined) {
|
|
497
532
|
return { error: 'aid 不可修改(AUN 身份绑定,如需换 AID 请删除后重建)', code: 'INVALID_ARGS' };
|
|
498
533
|
}
|
|
499
|
-
if (p.avatar !== undefined) {
|
|
500
|
-
const otherKeys = Object.keys(p).filter(k => k !== 'avatar');
|
|
501
|
-
if (otherKeys.length > 0) {
|
|
502
|
-
return { error: 'avatar 不能与其他 patch 字段同时提交,请拆分为两次 update', code: 'INVALID_ARGS' };
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
534
|
if (p.name !== undefined) {
|
|
506
535
|
const otherKeys = Object.keys(p).filter(k => k !== 'name');
|
|
507
536
|
if (otherKeys.length > 0) {
|
|
@@ -517,13 +546,6 @@ export async function execAgentUpdate(args) {
|
|
|
517
546
|
const config = cfgRead(ConfigTarget.Agent, { self: a.aid });
|
|
518
547
|
if (!config)
|
|
519
548
|
return { error: `Agent "${a.aid}" not found`, code: 'NOT_FOUND' };
|
|
520
|
-
if (p.avatar !== undefined) {
|
|
521
|
-
const result = await uploadAvatar(a.aid, p.avatar, { aunPath: resolveRoot() });
|
|
522
|
-
if (!result.ok) {
|
|
523
|
-
return { error: `头像上传失败: ${result.error}`, code: result.code ?? 'UPLOAD_FAILED' };
|
|
524
|
-
}
|
|
525
|
-
return { data: { aid: a.aid, avatar: result.publicUrl, saved: true } };
|
|
526
|
-
}
|
|
527
549
|
let touched = false;
|
|
528
550
|
const data = { aid: a.aid };
|
|
529
551
|
const hasConfigPatch = p.active_baseagent !== undefined || p.baseagents !== undefined || p.projects !== undefined || p.owners !== undefined || p.chatmode !== undefined || p.channels !== undefined || p.channelOwners !== undefined;
|
|
@@ -559,7 +581,19 @@ export async function execAgentUpdate(args) {
|
|
|
559
581
|
delete config.projects;
|
|
560
582
|
}
|
|
561
583
|
if (p.owners !== undefined) {
|
|
562
|
-
|
|
584
|
+
if (!Array.isArray(p.owners) || p.owners.length === 0) {
|
|
585
|
+
return { error: 'owners must contain at least one owner', code: 'LAST_OWNER_PROTECTED' };
|
|
586
|
+
}
|
|
587
|
+
const owners = [];
|
|
588
|
+
for (const rawOwner of p.owners) {
|
|
589
|
+
if (typeof rawOwner !== 'string' || !rawOwner.trim() || !isValidAid(rawOwner.trim())) {
|
|
590
|
+
return { error: `Invalid owner AID: ${JSON.stringify(rawOwner)}`, code: 'INVALID_ARGS' };
|
|
591
|
+
}
|
|
592
|
+
const owner = rawOwner.trim();
|
|
593
|
+
if (!owners.includes(owner))
|
|
594
|
+
owners.push(owner);
|
|
595
|
+
}
|
|
596
|
+
config.owners = owners;
|
|
563
597
|
touched = true;
|
|
564
598
|
}
|
|
565
599
|
if (p.chatmode !== undefined) {
|
|
@@ -156,6 +156,14 @@ function parseStatsCommand(argv, source) {
|
|
|
156
156
|
}
|
|
157
157
|
function parseAgentCommand(argv, source) {
|
|
158
158
|
const subcmd = argv[1];
|
|
159
|
+
if (subcmd === 'persona') {
|
|
160
|
+
const tail = argv.at(-2) === '--format' && argv.at(-1) === 'json' ? argv.slice(0, -2) : argv;
|
|
161
|
+
const isShow = tail.length === 3 && tail[2] === 'show';
|
|
162
|
+
const isWrite = tail.length === 4 && (tail[2] === '--text' || tail[2] === '--file');
|
|
163
|
+
return isShow || isWrite
|
|
164
|
+
? recognized('agent.persona', 'agent', source, {}, argv)
|
|
165
|
+
: raw(source, argv, 'Invalid agent persona command');
|
|
166
|
+
}
|
|
159
167
|
const parsed = parseArgs(argv.slice(2));
|
|
160
168
|
const args = parsed.args;
|
|
161
169
|
if (parsed.positionals.length > 0)
|
|
@@ -170,6 +178,16 @@ function parseAgentCommand(argv, source) {
|
|
|
170
178
|
}
|
|
171
179
|
function parseAidCommand(argv, source) {
|
|
172
180
|
const subcmd = argv[1];
|
|
181
|
+
if (subcmd === 'avatar') {
|
|
182
|
+
const tail = argv.slice(2);
|
|
183
|
+
const aid = tail[0];
|
|
184
|
+
const sourceFlag = tail[1];
|
|
185
|
+
if (!aid || (sourceFlag !== '--file' && sourceFlag !== '--url') || !tail[2] || tail[2].startsWith('-')
|
|
186
|
+
|| (tail.length !== 3 && !(tail.length === 5 && tail[3] === '--format' && tail[4] === 'json'))) {
|
|
187
|
+
return raw(source, argv, 'Invalid aid avatar command');
|
|
188
|
+
}
|
|
189
|
+
return recognized('ec.aid.avatar', 'agent', source, { aid, ...(sourceFlag === '--file' ? { file: tail[2] } : { url: tail[2] }) }, argv, true);
|
|
190
|
+
}
|
|
173
191
|
const parsed = parseArgs(argv.slice(2));
|
|
174
192
|
const args = parsed.args;
|
|
175
193
|
if (parsed.positionals.length > 0)
|
|
@@ -22,7 +22,7 @@ import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
|
|
|
22
22
|
import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
|
|
23
23
|
import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
|
|
24
24
|
import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
|
|
25
|
-
import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
|
|
25
|
+
import { executeManagedAidOperation, ManagedAidOperationError, managedAidShowViewForRole, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
|
|
26
26
|
import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
|
|
27
27
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
28
28
|
import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
|
|
@@ -108,6 +108,7 @@ export class CommandHandler {
|
|
|
108
108
|
interactionRouter;
|
|
109
109
|
statsCollector;
|
|
110
110
|
agentMap;
|
|
111
|
+
legacySingleRunnerMode;
|
|
111
112
|
primaryRunnerKey;
|
|
112
113
|
agentRegistry;
|
|
113
114
|
agentApplicationService;
|
|
@@ -131,23 +132,42 @@ export class CommandHandler {
|
|
|
131
132
|
* different backend; that would corrupt session metadata.
|
|
132
133
|
*/
|
|
133
134
|
getAgent(channel, baseagent) {
|
|
134
|
-
|
|
135
|
+
const requestedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
|
|
136
|
+
// Support callers that intentionally provide a single-runner map keyed by
|
|
137
|
+
// the backend name. This exact lookup does not select another agent.
|
|
138
|
+
if (requestedBaseagent && this.agentMap.has(requestedBaseagent)) {
|
|
139
|
+
return this.agentMap.get(requestedBaseagent);
|
|
140
|
+
}
|
|
141
|
+
// A caller that passed one runner explicitly has already selected the
|
|
142
|
+
// backend; this compatibility path does not exist for multi-agent maps.
|
|
143
|
+
if (this.legacySingleRunnerMode && this.agentMap.size === 1
|
|
144
|
+
&& this.agentMap.values().next().value?.name === requestedBaseagent) {
|
|
145
|
+
return this.agentMap.values().next().value;
|
|
146
|
+
}
|
|
147
|
+
if (channel && requestedBaseagent) {
|
|
135
148
|
const owner = this.agentRegistry?.resolveByChannel(channel);
|
|
136
149
|
const evolName = owner?.name || '<unknown>';
|
|
137
|
-
const key = `${evolName}::${
|
|
150
|
+
const key = `${evolName}::${requestedBaseagent}`;
|
|
138
151
|
if (this.agentMap.has(key))
|
|
139
152
|
return this.agentMap.get(key);
|
|
140
153
|
if (owner) {
|
|
141
|
-
throw new BaseagentRunnerUnavailableError(evolName,
|
|
154
|
+
throw new BaseagentRunnerUnavailableError(evolName, requestedBaseagent, this.getAvailableBaseagentsForOwner(evolName));
|
|
142
155
|
}
|
|
143
|
-
const
|
|
144
|
-
.
|
|
145
|
-
if (
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
const matches = [...this.agentMap.entries()]
|
|
157
|
+
.filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
|
|
158
|
+
if (matches.length === 1)
|
|
159
|
+
return matches[0][1];
|
|
160
|
+
logger.error(`[RunnerLookup] unavailable channel=${channel ?? '<none>'} baseagent=${requestedBaseagent} owner=${evolName} keys=${[...this.agentMap.keys()].join(',')}`);
|
|
161
|
+
throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent, []);
|
|
162
|
+
}
|
|
163
|
+
if (requestedBaseagent) {
|
|
164
|
+
const matches = [...this.agentMap.entries()]
|
|
165
|
+
.filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
|
|
166
|
+
if (matches.length === 1)
|
|
167
|
+
return matches[0][1];
|
|
168
|
+
}
|
|
169
|
+
logger.error(`[RunnerLookup] unavailable channel=${channel ?? '<none>'} baseagent=${requestedBaseagent || '<missing>'} keys=${[...this.agentMap.keys()].join(',')}`);
|
|
170
|
+
throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
|
|
151
171
|
}
|
|
152
172
|
getAvailableBaseagentsForOwner(evolName) {
|
|
153
173
|
const prefix = `${evolName}::`;
|
|
@@ -205,11 +225,13 @@ export class CommandHandler {
|
|
|
205
225
|
this.eventBus = eventBus;
|
|
206
226
|
if (agentRunnerOrMap instanceof Map) {
|
|
207
227
|
this.agentMap = agentRunnerOrMap;
|
|
228
|
+
this.legacySingleRunnerMode = false;
|
|
208
229
|
this.primaryRunnerKey = primaryRunnerKey || '<unknown>::claude';
|
|
209
230
|
}
|
|
210
231
|
else {
|
|
211
232
|
// 测试 / 单 runner 路径:占位 agent name 用 '<unknown>'
|
|
212
233
|
this.agentMap = new Map([[`<unknown>::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
|
|
234
|
+
this.legacySingleRunnerMode = true;
|
|
213
235
|
this.primaryRunnerKey = `<unknown>::${agentRunnerOrMap.name}`;
|
|
214
236
|
}
|
|
215
237
|
}
|
|
@@ -424,13 +446,13 @@ export class CommandHandler {
|
|
|
424
446
|
return fallback;
|
|
425
447
|
}
|
|
426
448
|
}
|
|
427
|
-
validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false) {
|
|
449
|
+
validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
428
450
|
if (!mode)
|
|
429
451
|
return undefined;
|
|
430
452
|
if (mode === 'fullaccess') {
|
|
431
453
|
if (!isFullAccessEnabled())
|
|
432
454
|
return 'fullaccess 功能未启用';
|
|
433
|
-
return
|
|
455
|
+
return canApprovePersistentFullAccess ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
|
|
434
456
|
}
|
|
435
457
|
if (!role) {
|
|
436
458
|
if (mode === 'bypass' && !isAdmin)
|
|
@@ -486,7 +508,9 @@ export class CommandHandler {
|
|
|
486
508
|
return modelDecision.message || '当前角色不允许使用该 Trigger 模型';
|
|
487
509
|
}
|
|
488
510
|
if (definition.execution.effort) {
|
|
489
|
-
|
|
511
|
+
if (!executionBaseagent)
|
|
512
|
+
return '无法确定 Trigger 会话的 baseagent';
|
|
513
|
+
const base = executionBaseagent;
|
|
490
514
|
const effortDecision = validateRuntimeStringFieldOverride({
|
|
491
515
|
selfAid: definition.agentAid,
|
|
492
516
|
role,
|
|
@@ -575,7 +599,7 @@ export class CommandHandler {
|
|
|
575
599
|
} : undefined;
|
|
576
600
|
const permissionMode = this.triggerPermissionFromParsed(parsed);
|
|
577
601
|
const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId, chatType);
|
|
578
|
-
const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner);
|
|
602
|
+
const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner, canApprovePersistentFullAccess);
|
|
579
603
|
if (permissionError)
|
|
580
604
|
return { error: permissionError };
|
|
581
605
|
if (permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
|
|
@@ -604,7 +628,7 @@ export class CommandHandler {
|
|
|
604
628
|
};
|
|
605
629
|
}
|
|
606
630
|
}
|
|
607
|
-
const authorizedBy = permissionMode === 'fullaccess' &&
|
|
631
|
+
const authorizedBy = permissionMode === 'fullaccess' && canApprovePersistentFullAccess
|
|
608
632
|
? this.canonicalDaemonOwnerId(schedulerAid, channel, peerId, chatType)
|
|
609
633
|
: undefined;
|
|
610
634
|
const definition = {
|
|
@@ -1000,8 +1024,8 @@ export class CommandHandler {
|
|
|
1000
1024
|
return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source);
|
|
1001
1025
|
}
|
|
1002
1026
|
/** menu.update — 写入新值。 */
|
|
1003
|
-
async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu') {
|
|
1004
|
-
return await menuExecMenuUpdate.call(this, cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel, args, authSubject, source);
|
|
1027
|
+
async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu', transportReplyContext) {
|
|
1028
|
+
return await menuExecMenuUpdate.call(this, cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel, args, authSubject, source, transportReplyContext);
|
|
1005
1029
|
}
|
|
1006
1030
|
/** menu.action — 触发动词。 */
|
|
1007
1031
|
async execMenuAction(cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext, deferSideEffect) {
|
|
@@ -1103,7 +1127,7 @@ export class CommandHandler {
|
|
|
1103
1127
|
/** 把 menu.action 委派给已有 slash 命令处理逻辑,把 OutboundPayload 包成结构化结果。 */
|
|
1104
1128
|
async delegateAsAction(action, slashCmd, channel, channelId, userId, opts = {}) {
|
|
1105
1129
|
try {
|
|
1106
|
-
const result = await this._handleInternal(slashCmd, channel, channelId, undefined, userId,
|
|
1130
|
+
const result = await this._handleInternal(slashCmd, channel, channelId, undefined, userId, opts.threadId, opts.chatType, undefined, undefined, undefined, opts.overrideIdentity, opts.authSubject, opts.replyContext);
|
|
1107
1131
|
if (result == null) {
|
|
1108
1132
|
// null / undefined: 命令未识别或前置守卫拦截(如 idle 检查),视为失败
|
|
1109
1133
|
return { error: '命令未执行(可能被前置守卫拦截)', code: 'EXEC_FAILED' };
|
|
@@ -1375,17 +1399,17 @@ export class CommandHandler {
|
|
|
1375
1399
|
const nextStr = reg.trigger.nextFireAt ? new Date(reg.trigger.nextFireAt).toLocaleString() : '未计算';
|
|
1376
1400
|
return `✅ 触发器已注册:**${reg.trigger.name}**\n下次触发:${nextStr}`;
|
|
1377
1401
|
}
|
|
1378
|
-
return `❌ 未知子命令。用法:
|
|
1379
|
-
/trigger — 查看活跃触发器
|
|
1380
|
-
/trigger list [--all] — 查看所有触发器
|
|
1381
|
-
/trigger create <参数> — 创建触发器(set 为兼容别名)
|
|
1382
|
-
/trigger update <名称|ID> <参数> — 修改触发器
|
|
1383
|
-
常用参数:--delay/--at/--cron/--every、--prompt、--description、--model、--effort、--permission(省略则继承;update 可用 inherit 清除覆盖)、--feedback、--target-channel/--target-channel-id、--trigger-thread、--baseagent、--on-error、--retry-attempts/--retry-backoff、--script-runtime/--script-args/--script-timeout
|
|
1384
|
-
/trigger enable <名称|ID> — 启用触发器
|
|
1385
|
-
/trigger disable <名称|ID> — 暂停触发器
|
|
1386
|
-
/trigger show <名称|ID> — 查看触发器详情
|
|
1387
|
-
/trigger run <名称|ID> [--dry-run] — 手动触发
|
|
1388
|
-
/trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
|
|
1402
|
+
return `❌ 未知子命令。用法:
|
|
1403
|
+
/trigger — 查看活跃触发器
|
|
1404
|
+
/trigger list [--all] — 查看所有触发器
|
|
1405
|
+
/trigger create <参数> — 创建触发器(set 为兼容别名)
|
|
1406
|
+
/trigger update <名称|ID> <参数> — 修改触发器
|
|
1407
|
+
常用参数:--delay/--at/--cron/--every、--prompt、--description、--model、--effort、--permission(省略则继承;update 可用 inherit 清除覆盖)、--feedback、--target-channel/--target-channel-id、--trigger-thread、--baseagent、--on-error、--retry-attempts/--retry-backoff、--script-runtime/--script-args/--script-timeout
|
|
1408
|
+
/trigger enable <名称|ID> — 启用触发器
|
|
1409
|
+
/trigger disable <名称|ID> — 暂停触发器
|
|
1410
|
+
/trigger show <名称|ID> — 查看触发器详情
|
|
1411
|
+
/trigger run <名称|ID> [--dry-run] — 手动触发
|
|
1412
|
+
/trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
|
|
1389
1413
|
/trigger delete <名称|ID> — 删除触发器`;
|
|
1390
1414
|
}
|
|
1391
1415
|
async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
@@ -1432,7 +1456,7 @@ export class CommandHandler {
|
|
|
1432
1456
|
return { ok: false, error: err?.message || String(err) };
|
|
1433
1457
|
}
|
|
1434
1458
|
if (updated.execution.permissionMode === 'fullaccess') {
|
|
1435
|
-
if (!isDaemonOwner) {
|
|
1459
|
+
if (!isDaemonOwner && !canApprovePersistentFullAccess) {
|
|
1436
1460
|
return { ok: false, code: 'FORBIDDEN', error: '无权限:只有 daemon-owner 可以配置 fullaccess Trigger' };
|
|
1437
1461
|
}
|
|
1438
1462
|
if (!canApprovePersistentFullAccess) {
|
|
@@ -1455,7 +1479,7 @@ export class CommandHandler {
|
|
|
1455
1479
|
return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
|
|
1456
1480
|
}
|
|
1457
1481
|
const triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId);
|
|
1458
|
-
const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner);
|
|
1482
|
+
const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner, canApprovePersistentFullAccess);
|
|
1459
1483
|
if (permissionError) {
|
|
1460
1484
|
return {
|
|
1461
1485
|
ok: false,
|
|
@@ -1922,7 +1946,10 @@ export class CommandHandler {
|
|
|
1922
1946
|
if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
|
|
1923
1947
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
|
|
1924
1948
|
}
|
|
1925
|
-
|
|
1949
|
+
const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put' || parsed.command.kind === 'avatar';
|
|
1950
|
+
if (requiresSelfAid
|
|
1951
|
+
&& parsed.command.aid !== grantSelfAid
|
|
1952
|
+
&& (parsed.command.kind === 'avatar' || !hasTrustedFullAccessDelegation(delegation.grant))) {
|
|
1926
1953
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
|
|
1927
1954
|
}
|
|
1928
1955
|
let conversationId;
|
|
@@ -1961,8 +1988,88 @@ export class CommandHandler {
|
|
|
1961
1988
|
});
|
|
1962
1989
|
if (!decision.allow)
|
|
1963
1990
|
return { ok: false, code: decision.code, error: decision.reason };
|
|
1991
|
+
let avatarBuffer;
|
|
1992
|
+
if (parsed.command.kind === 'avatar' && parsed.command.filePath) {
|
|
1993
|
+
const filePath = String(parsed.command.filePath || '').trim();
|
|
1994
|
+
if (typeof session.projectPath !== 'string' || !session.projectPath.trim()) {
|
|
1995
|
+
return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar file requires a current session project path' };
|
|
1996
|
+
}
|
|
1997
|
+
const projectPath = path.resolve(session.projectPath);
|
|
1998
|
+
const managedTempPath = this.getManagedSessionTempDir(session);
|
|
1999
|
+
const resolvedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(projectPath, filePath);
|
|
2000
|
+
const allowedRoots = [projectPath, managedTempPath].filter((value) => !!value);
|
|
2001
|
+
if (!filePath || !allowedRoots.some(root => isSameOrDescendant(resolvedPath, root))) {
|
|
2002
|
+
return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar file must be inside the current session project or managed TMPDIR' };
|
|
2003
|
+
}
|
|
2004
|
+
if (isHClassPath(resolvedPath) || isLClassPath(resolvedPath)) {
|
|
2005
|
+
return { ok: false, code: 'PROTECTED_AVATAR_PATH', error: 'Avatar file cannot be read from a protected EvolCore path' };
|
|
2006
|
+
}
|
|
2007
|
+
let fd;
|
|
2008
|
+
try {
|
|
2009
|
+
const realProjectPath = fs.realpathSync(projectPath);
|
|
2010
|
+
const realPath = fs.realpathSync(resolvedPath);
|
|
2011
|
+
let realManagedTempPath;
|
|
2012
|
+
try {
|
|
2013
|
+
realManagedTempPath = managedTempPath ? fs.realpathSync(managedTempPath) : undefined;
|
|
2014
|
+
}
|
|
2015
|
+
catch { }
|
|
2016
|
+
const realAllowedRoots = [realProjectPath, realManagedTempPath].filter((value) => !!value);
|
|
2017
|
+
if (!realAllowedRoots.some(root => isSameOrDescendant(realPath, root))) {
|
|
2018
|
+
return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar file resolves outside the current session project or managed TMPDIR' };
|
|
2019
|
+
}
|
|
2020
|
+
if (isHClassPath(realPath) || isLClassPath(realPath)) {
|
|
2021
|
+
return { ok: false, code: 'PROTECTED_AVATAR_PATH', error: 'Avatar file cannot be read from a protected EvolCore path' };
|
|
2022
|
+
}
|
|
2023
|
+
const stat = fs.lstatSync(resolvedPath);
|
|
2024
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
2025
|
+
return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source must be a regular non-symlink file' };
|
|
2026
|
+
}
|
|
2027
|
+
if (realManagedTempPath && isSameOrDescendant(realPath, realManagedTempPath) && stat.nlink > 1) {
|
|
2028
|
+
return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar source cannot be a hard link inside managed TMPDIR' };
|
|
2029
|
+
}
|
|
2030
|
+
if (stat.size < 1 || stat.size > 500 * 1024) {
|
|
2031
|
+
return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source must be between 1 byte and 500KB' };
|
|
2032
|
+
}
|
|
2033
|
+
// Open the user-supplied node with no-follow semantics after checking
|
|
2034
|
+
// its canonical ancestors; the bytes passed to the uploader come from
|
|
2035
|
+
// this descriptor rather than from a second path lookup.
|
|
2036
|
+
fd = fs.openSync(resolvedPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
2037
|
+
const finalStat = fs.fstatSync(fd);
|
|
2038
|
+
if (!finalStat.isFile() || finalStat.size < 1 || finalStat.size > 500 * 1024 || finalStat.nlink !== stat.nlink) {
|
|
2039
|
+
return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source changed during validation' };
|
|
2040
|
+
}
|
|
2041
|
+
avatarBuffer = fs.readFileSync(fd);
|
|
2042
|
+
}
|
|
2043
|
+
catch (error) {
|
|
2044
|
+
return { ok: false, code: 'INVALID_AVATAR_FILE', error: error instanceof Error ? error.message : String(error) };
|
|
2045
|
+
}
|
|
2046
|
+
finally {
|
|
2047
|
+
if (fd !== undefined)
|
|
2048
|
+
fs.closeSync(fd);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
1964
2051
|
try {
|
|
1965
|
-
|
|
2052
|
+
const result = await executeManagedAidOperation(parsed.command, {
|
|
2053
|
+
showView: managedAidShowViewForRole(subject.role),
|
|
2054
|
+
...(avatarBuffer ? { avatarBuffer } : {}),
|
|
2055
|
+
});
|
|
2056
|
+
if (result.kind === 'avatar' && result.avatar) {
|
|
2057
|
+
this.eventBus.publish({
|
|
2058
|
+
type: 'agent:avatar_updated',
|
|
2059
|
+
aid: result.aid,
|
|
2060
|
+
avatar: result.avatar,
|
|
2061
|
+
...(result.sha256 ? { sha256: result.sha256 } : {}),
|
|
2062
|
+
...(result.mime ? { mime: result.mime } : {}),
|
|
2063
|
+
...(result.width ? { width: result.width } : {}),
|
|
2064
|
+
...(result.height ? { height: result.height } : {}),
|
|
2065
|
+
...(result.aspectRatio ? { aspectRatio: result.aspectRatio } : {}),
|
|
2066
|
+
timestamp: Date.now(),
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
return {
|
|
2070
|
+
ok: true,
|
|
2071
|
+
result,
|
|
2072
|
+
};
|
|
1966
2073
|
}
|
|
1967
2074
|
catch (error) {
|
|
1968
2075
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2188,7 +2295,14 @@ export class CommandHandler {
|
|
|
2188
2295
|
if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'private')) {
|
|
2189
2296
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
|
|
2190
2297
|
}
|
|
2191
|
-
|
|
2298
|
+
// A management role may read history for any peer, one peer per query.
|
|
2299
|
+
// `issuedRole` is resolved daemon-side from the persisted relation
|
|
2300
|
+
// config, so it cannot be spoofed by the calling task. Narrower roles
|
|
2301
|
+
// stay pinned here, and authorizeOperation below remains the authority
|
|
2302
|
+
// for every role (member carries currentRelationOnly; visitor is denied).
|
|
2303
|
+
const mayReadCrossPeer = hasTrustedFullAccessDelegation(grant)
|
|
2304
|
+
|| (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole));
|
|
2305
|
+
if (!mayReadCrossPeer && parsed.command.target !== conversationId) {
|
|
2192
2306
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
|
|
2193
2307
|
}
|
|
2194
2308
|
}
|
|
@@ -2458,6 +2572,29 @@ export class CommandHandler {
|
|
|
2458
2572
|
});
|
|
2459
2573
|
if (!ctlDecision.allow)
|
|
2460
2574
|
return { ok: false, code: ctlDecision.code, error: ctlDecision.reason };
|
|
2575
|
+
if (cmd.startsWith('/status ') && cmd !== '/status --format json') {
|
|
2576
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: 'status 仅支持可选参数 --format json' };
|
|
2577
|
+
}
|
|
2578
|
+
if (cmd === '/status --format json') {
|
|
2579
|
+
const context = {
|
|
2580
|
+
selfAid: grantSelfAid,
|
|
2581
|
+
sessionId,
|
|
2582
|
+
taskId: grant.taskId,
|
|
2583
|
+
...(grant.messageId ? { messageId: grant.messageId } : {}),
|
|
2584
|
+
baseagent: session.baseagent,
|
|
2585
|
+
channel: grant.channelType,
|
|
2586
|
+
channelKey: grant.channel,
|
|
2587
|
+
channelId: conversationId,
|
|
2588
|
+
chatType: grant.chatType,
|
|
2589
|
+
peerKey: grant.peerKey,
|
|
2590
|
+
peerId: conversationId,
|
|
2591
|
+
peerRole: ctlSubject.role,
|
|
2592
|
+
permissionMode: grant.permissionMode ?? ctlSubject.permissionMode,
|
|
2593
|
+
projectPath: path.resolve(session.projectPath),
|
|
2594
|
+
...(grant.sessionRuntimeDir ? { tmpDir: grant.sessionRuntimeDir } : {}),
|
|
2595
|
+
};
|
|
2596
|
+
return { ok: true, result: JSON.stringify(context, null, 2), data: context };
|
|
2597
|
+
}
|
|
2461
2598
|
if (cmd.startsWith('/model info ')) {
|
|
2462
2599
|
const modelId = cmd.slice('/model info '.length).trim();
|
|
2463
2600
|
if (!modelId || /\s/.test(modelId))
|
|
@@ -79,10 +79,12 @@ const ENTRIES = [
|
|
|
79
79
|
{ id: 'chatmode.agent.nothuman', type: 'setting', contexts: ['agent'], name: 'chatmode', label: 'Agent 非人类对话模式', section: 'agent', order: 43, query: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.current', scope: 'agent' }, options: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.current', scope: 'agent' }, update: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.update', scope: 'agent' }, control: 'select' },
|
|
80
80
|
{ id: 'activity.agent', type: 'setting', contexts: ['agent'], name: 'activity', label: 'Agent 中间输出', section: 'agent', order: 50, query: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, options: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, update: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.update', scope: 'agent' }, control: 'select' },
|
|
81
81
|
{ id: 'observable.agent', type: 'setting', contexts: ['agent'], name: 'observable', label: '观察模式', section: 'agent', order: 60, query: { cmd: '/observable', operation: 'observable.current', scope: 'agent' }, update: { cmd: '/observable', operation: 'observable.update', scope: 'agent' }, valueSchema: { type: 'boolean' }, control: 'toggle' },
|
|
82
|
+
{ id: 'aid.avatar', type: 'action', contexts: ['agent'], name: 'aid', label: '更换头像', section: 'agent', order: 70, action: { cmd: '/aid', action: 'avatar', operation: 'ec.aid.avatar', scope: 'agent' }, valueSchema: { type: 'object', properties: { data: { type: 'string' }, url: { type: 'string', format: 'uri' } }, oneOf: [{ required: ['data'] }, { required: ['url'] }] }, control: 'action' },
|
|
82
83
|
{ id: 'system.status', type: 'list', contexts: ['process'], name: 'system', label: '系统状态', section: 'process', order: 10, query: { cmd: '/system', operation: 'system.status', scope: 'process' }, control: 'list' },
|
|
83
84
|
{ id: 'system.monitor', type: 'list', contexts: ['process'], name: 'system', label: '运行监控快照', section: 'process', order: 11, query: { cmd: '/system', args: { view: 'monitor' }, operation: 'system.status', scope: 'process' }, control: 'list' },
|
|
84
85
|
{ id: 'system.logs', type: 'list', contexts: ['process'], name: 'system', label: 'Daemon 日志', section: 'process', order: 12, query: { cmd: '/system', args: { view: 'logs' }, operation: 'system.status', scope: 'process' }, options: { cmd: '/system', args: { option: 'logs' }, operation: 'system.status', scope: 'process', mode: 'dynamic' }, control: 'list' },
|
|
85
86
|
{ id: 'config.rawFile', type: 'list', contexts: ['process'], name: 'config', label: '原始配置文件', section: 'process', order: 13, query: { cmd: '/config', args: { view: 'rawFile' }, operation: 'config.read', scope: 'process' }, options: { cmd: '/config', args: { option: 'rawFile' }, operation: 'config.read', scope: 'process', mode: 'dynamic' }, control: 'list' },
|
|
87
|
+
{ id: 'baseagent.models', type: 'list', contexts: ['process'], name: 'baseagent-models', label: 'Baseagent 模型目录', section: 'process', order: 41, query: { cmd: '/baseagent-models', args: { baseagent: 'claude' }, operation: 'baseagent.models.list', scope: 'process' }, control: 'list' },
|
|
86
88
|
{ id: 'system.restart', type: 'action', contexts: ['process'], name: 'system', label: '重启服务', section: 'process', order: 20, action: { cmd: '/system', action: 'restart', operation: 'system.restart', scope: 'process' } },
|
|
87
89
|
{ id: 'system.upgrade', type: 'action', contexts: ['process'], name: 'system', label: '升级服务', section: 'process', order: 30, action: { cmd: '/system', action: 'upgrade', operation: 'system.upgrade', scope: 'process' } },
|
|
88
90
|
{ id: 'agent.list', type: 'list', contexts: ['process'], name: 'agent', label: 'Agent 列表', section: 'process', order: 40, options: { cmd: '/agent', args: { options: 'all' }, operation: 'agent.list', scope: 'control', mode: 'dynamic' }, control: 'list' },
|
|
@@ -110,7 +112,8 @@ export async function resolveMenuCatalogContext(input) {
|
|
|
110
112
|
throw { code: 'FORBIDDEN', message: 'control catalog context requires a control subject' };
|
|
111
113
|
}
|
|
112
114
|
if (requested === 'process') {
|
|
113
|
-
if (input.channelScope !== 'control' || !subject.fromControlChannel
|
|
115
|
+
if (input.channelScope !== 'control' || !subject.fromControlChannel
|
|
116
|
+
|| (!subject.isDaemonOwner && subject.processRole !== 'daemon-service')) {
|
|
114
117
|
throw {
|
|
115
118
|
code: 'NOT_ALLOWED',
|
|
116
119
|
message: formatAuthorizationDenial('Process context requires a DaemonOwner control subject.', 'DAEMON_OWNER_REQUIRED'),
|