evolcore 0.0.25 → 0.0.26
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 +607 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +160 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +50 -12
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +121 -38
- package/dist/agents/ecagent-runner.js +4 -9
- package/dist/agents/gemini-runner.js +2 -7
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +81 -8
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +105 -84
- package/dist/cli/agent-create.js +112 -10
- package/dist/cli/agent.js +16 -5
- package/dist/cli/aun-commands.js +297 -296
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +47 -28
- package/dist/cli/contact.js +70 -13
- package/dist/cli/ctl-command.js +22 -22
- package/dist/cli/daemon-commands.js +105 -40
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +41 -9
- package/dist/cli/index.js +83 -77
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +46 -31
- 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 +87 -40
- package/dist/cli/stats.js +51 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-operation-service.js +145 -0
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +27 -7
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +25 -4
- package/dist/core/auth/operation-catalog.js +35 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/command-handler.js +83 -26
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +12 -3
- package/dist/core/command/menu-handler.js +47 -26
- package/dist/core/command/menu-protocol.js +5 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +86 -21
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +47 -28
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +2 -1
- package/dist/index.js +280 -163
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/welcome.js +22 -22
- 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 +364 -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 -71
- package/kits/docs/evolcore/agent-create.md +170 -165
- package/kits/docs/evolcore/agent.md +66 -66
- package/kits/docs/evolcore/aid.md +60 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +202 -195
- package/kits/docs/evolcore/contact.md +75 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +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 +42 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +166 -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 +269 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +37 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +190 -190
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- 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 +92 -92
- package/kits/templates/roles/member.json +96 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +85 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +31 -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 +100 -98
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
|
|
2
|
+
import { parseMenuClientArgs } from '../../cli/menu-client.js';
|
|
2
3
|
import { renderCommandCardAsText } from '../interaction-router.js';
|
|
3
4
|
import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
|
|
4
5
|
import { resolvePaths, getPackageRoot } from '../../paths.js';
|
|
@@ -17,7 +18,7 @@ import { tryParseChannelKey } from '../channel-loader.js';
|
|
|
17
18
|
import { displaySessionTitle } from '../session/session-title.js';
|
|
18
19
|
import { buildSessionTurnList } from '../session/session-turns.js';
|
|
19
20
|
import { resolveConfigCommand } from '../../config/resolved-config-op.js';
|
|
20
|
-
import { AUTHENTICATED_CONFIG_ROLE_ENV,
|
|
21
|
+
import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommandAsync } from '../../config/config-operation-service.js';
|
|
21
22
|
import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
|
|
22
23
|
import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
|
|
23
24
|
import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
|
|
@@ -123,6 +124,43 @@ export class CommandHandler {
|
|
|
123
124
|
daemonMonitorProvider;
|
|
124
125
|
agentDelegationRegistry;
|
|
125
126
|
handoffRuntime;
|
|
127
|
+
diagService;
|
|
128
|
+
setDiagService(service) { this.diagService = service; }
|
|
129
|
+
async handleMenuClientOperation(argv, sessionId, delegationToken, delegationCommandHash, send) {
|
|
130
|
+
const validation = this.agentDelegationRegistry?.validate(delegationToken, sessionId, delegationCommandHash);
|
|
131
|
+
if (!validation?.ok)
|
|
132
|
+
return { ok: false, code: validation?.code ?? 'DELEGATION_REQUIRED', error: validation?.reason ?? 'Task delegation is required' };
|
|
133
|
+
const grant = validation.grant;
|
|
134
|
+
const session = await this.sessionManager.getSessionById(sessionId);
|
|
135
|
+
if (!session || session.selfAID !== grant.selfAid || argv[0] !== 'request' || argv[1] !== grant.selfAid)
|
|
136
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Menu requests must use the current Agent identity and session' };
|
|
137
|
+
try {
|
|
138
|
+
const channelId = parsePeerKey(grant.peerKey).channelId;
|
|
139
|
+
const subject = buildAuthSubject({ selfAid: grant.selfAid, actorId: grant.actorId, channel: grant.channel,
|
|
140
|
+
channelType: grant.channelType, channelId, chatType: grant.chatType, conversationId: channelId,
|
|
141
|
+
processOwners: [], ...trustedExecutionIdentityInput(grant) });
|
|
142
|
+
const decision = authorizeOperation({ source: 'agent-tool', subject,
|
|
143
|
+
intent: { operation: 'ec.menu.request', scope: 'relation', source: 'agent-tool', args: { self: grant.selfAid, peer: argv[2], targetId: argv[2] } },
|
|
144
|
+
auditMetadata: { taskId: grant.taskId, sessionId, messageId: grant.messageId } });
|
|
145
|
+
if (!decision.allow)
|
|
146
|
+
return { ok: false, code: decision.code, error: decision.reason };
|
|
147
|
+
const options = parseMenuClientArgs(argv, file => {
|
|
148
|
+
const target = fs.realpathSync(path.resolve(session.projectPath, file));
|
|
149
|
+
const roots = [session.projectPath, this.getManagedSessionTempDir(session)].filter((v) => !!v)
|
|
150
|
+
.map(root => fs.realpathSync(root));
|
|
151
|
+
if (!roots.some(root => isSameOrDescendant(target, root)) || isHClassPath(target) || isLClassPath(target))
|
|
152
|
+
throw new Error('Menu request file must be an unprotected file in the project or session TMPDIR');
|
|
153
|
+
const stat = fs.statSync(target);
|
|
154
|
+
if (!stat.isFile() || stat.nlink > 1 || stat.size > 128 * 1024)
|
|
155
|
+
throw new Error('Menu request file must be a regular file of at most 128 KiB');
|
|
156
|
+
return fs.readFileSync(target, 'utf8');
|
|
157
|
+
});
|
|
158
|
+
return { ok: true, response: await send(options) };
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
return { ok: false, code: error.code ?? 'MENU_REQUEST_FAILED', error: error.message };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
126
164
|
contactRequestSubmitter;
|
|
127
165
|
/**
|
|
128
166
|
* Get the runner for a (channel, baseagent) pair.
|
|
@@ -983,13 +1021,14 @@ export class CommandHandler {
|
|
|
983
1021
|
getMenuItems(role, chatType = 'private', scope = 'agent', authSubject) {
|
|
984
1022
|
return menuGetMenuItems.call(this, role, chatType, scope, authSubject);
|
|
985
1023
|
}
|
|
986
|
-
async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent') {
|
|
1024
|
+
async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent', paircodeAllowed) {
|
|
987
1025
|
return await getMenuCatalog({
|
|
988
1026
|
subject,
|
|
989
1027
|
channelType: this.resolveChannelType(channel),
|
|
990
1028
|
channelScope,
|
|
991
1029
|
channelId,
|
|
992
1030
|
request,
|
|
1031
|
+
...(paircodeAllowed === undefined ? {} : { paircodeAllowed }),
|
|
993
1032
|
});
|
|
994
1033
|
}
|
|
995
1034
|
async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent') {
|
|
@@ -1176,9 +1215,9 @@ export class CommandHandler {
|
|
|
1176
1215
|
/**
|
|
1177
1216
|
* 主命令处理入口
|
|
1178
1217
|
*/
|
|
1179
|
-
async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
|
|
1218
|
+
async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext, enforceGroupSlashPolicy = false) {
|
|
1180
1219
|
try {
|
|
1181
|
-
const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
|
|
1220
|
+
const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext, enforceGroupSlashPolicy);
|
|
1182
1221
|
// Lifecycle commands are control-plane operations. Their replies must
|
|
1183
1222
|
// use notification payloads so other agents in a group do not treat the
|
|
1184
1223
|
// text as ordinary model input. Normalize guard-path replies here too,
|
|
@@ -1210,8 +1249,10 @@ export class CommandHandler {
|
|
|
1210
1249
|
throw error;
|
|
1211
1250
|
}
|
|
1212
1251
|
}
|
|
1213
|
-
async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext
|
|
1214
|
-
|
|
1252
|
+
async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext,
|
|
1253
|
+
// Menu and delegated CLI operations reuse this implementation without the chat-only policy.
|
|
1254
|
+
applyGroupSlashPolicy = false) {
|
|
1255
|
+
return await handleSlashCommand.call(this, content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext, applyGroupSlashPolicy);
|
|
1215
1256
|
}
|
|
1216
1257
|
async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
1217
1258
|
if (content === '/trigger') {
|
|
@@ -1399,17 +1440,17 @@ export class CommandHandler {
|
|
|
1399
1440
|
const nextStr = reg.trigger.nextFireAt ? new Date(reg.trigger.nextFireAt).toLocaleString() : '未计算';
|
|
1400
1441
|
return `✅ 触发器已注册:**${reg.trigger.name}**\n下次触发:${nextStr}`;
|
|
1401
1442
|
}
|
|
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 的兼容别名)
|
|
1443
|
+
return `❌ 未知子命令。用法:
|
|
1444
|
+
/trigger — 查看活跃触发器
|
|
1445
|
+
/trigger list [--all] — 查看所有触发器
|
|
1446
|
+
/trigger create <参数> — 创建触发器(set 为兼容别名)
|
|
1447
|
+
/trigger update <名称|ID> <参数> — 修改触发器
|
|
1448
|
+
常用参数:--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
|
|
1449
|
+
/trigger enable <名称|ID> — 启用触发器
|
|
1450
|
+
/trigger disable <名称|ID> — 暂停触发器
|
|
1451
|
+
/trigger show <名称|ID> — 查看触发器详情
|
|
1452
|
+
/trigger run <名称|ID> [--dry-run] — 手动触发
|
|
1453
|
+
/trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
|
|
1413
1454
|
/trigger delete <名称|ID> — 删除触发器`;
|
|
1414
1455
|
}
|
|
1415
1456
|
async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
@@ -1677,8 +1718,9 @@ export class CommandHandler {
|
|
|
1677
1718
|
buildCtlReplyContext(session) {
|
|
1678
1719
|
const ctx = { delivery: deliveryTargetFromSession(session) };
|
|
1679
1720
|
const meta = session.metadata;
|
|
1680
|
-
|
|
1681
|
-
|
|
1721
|
+
const threadId = session.threadId || meta?.replyContext?.threadId;
|
|
1722
|
+
if (threadId)
|
|
1723
|
+
ctx.threadId = threadId;
|
|
1682
1724
|
if (meta?.replyContext?.peerId)
|
|
1683
1725
|
ctx.peerId = meta.replyContext.peerId;
|
|
1684
1726
|
if (!ctx.peerId && meta?.peerId)
|
|
@@ -1759,7 +1801,7 @@ export class CommandHandler {
|
|
|
1759
1801
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1760
1802
|
...trustedExecutionIdentityInput(grant),
|
|
1761
1803
|
});
|
|
1762
|
-
const executeOne = (fieldArgv) => {
|
|
1804
|
+
const executeOne = async (fieldArgv) => {
|
|
1763
1805
|
const resolved = resolveConfigCommand(fieldArgv, {
|
|
1764
1806
|
defaultRelation: { self: grant.selfAid, peerKey: grant.peerKey },
|
|
1765
1807
|
});
|
|
@@ -1780,6 +1822,7 @@ export class CommandHandler {
|
|
|
1780
1822
|
dangerous: resolved.command.dangerous,
|
|
1781
1823
|
},
|
|
1782
1824
|
resolvedConfigCommand: resolved.command,
|
|
1825
|
+
allowExplicitRelationTarget: true,
|
|
1783
1826
|
auditMetadata: {
|
|
1784
1827
|
argvHash: hashArgv(resolved.command.canonicalArgv),
|
|
1785
1828
|
taskId: grant.taskId,
|
|
@@ -1788,22 +1831,35 @@ export class CommandHandler {
|
|
|
1788
1831
|
});
|
|
1789
1832
|
if (!decision.allow)
|
|
1790
1833
|
return { ok: false, code: decision.code, error: decision.reason };
|
|
1791
|
-
|
|
1834
|
+
const roleContext = {
|
|
1835
|
+
self: grant.selfAid,
|
|
1836
|
+
actorRole: subject.role,
|
|
1837
|
+
actorRoleSource: subject.roleSource,
|
|
1838
|
+
actorAllowAccess: subject.allowAccess,
|
|
1839
|
+
actorAid: grant.actorId,
|
|
1840
|
+
isDaemonOwner: subject.isDaemonOwner,
|
|
1841
|
+
fromControlChannel: subject.fromControlChannel,
|
|
1842
|
+
chatType: grant.chatType,
|
|
1843
|
+
};
|
|
1844
|
+
return await executeResolvedConfigCommandAsync(resolved.command, {
|
|
1845
|
+
role: subject.role,
|
|
1846
|
+
roleContext,
|
|
1847
|
+
});
|
|
1792
1848
|
};
|
|
1793
1849
|
const batch = splitConfigBatchGetArgv(argv);
|
|
1794
1850
|
if (batch && batch.fields.length > 1) {
|
|
1795
|
-
const results = batch.fields.map(field => {
|
|
1796
|
-
const result = executeOne(['config', 'get', field, ...batch.rest]);
|
|
1851
|
+
const results = await Promise.all(batch.fields.map(async (field) => {
|
|
1852
|
+
const result = await executeOne(['config', 'get', field, ...batch.rest]);
|
|
1797
1853
|
if (!result.ok)
|
|
1798
1854
|
return { ok: false, field, code: result.code, error: result.error };
|
|
1799
1855
|
if (result.subcommand !== 'get') {
|
|
1800
1856
|
return { ok: false, field, code: 'UNEXPECTED_RESULT', error: `config get ${field} produced a non-get result` };
|
|
1801
1857
|
}
|
|
1802
1858
|
return result;
|
|
1803
|
-
});
|
|
1859
|
+
}));
|
|
1804
1860
|
return { ok: true, result: { ok: true, subcommand: 'batch-get', results } };
|
|
1805
1861
|
}
|
|
1806
|
-
const result = executeOne(argv);
|
|
1862
|
+
const result = await executeOne(argv);
|
|
1807
1863
|
return result.ok
|
|
1808
1864
|
? { ok: true, result }
|
|
1809
1865
|
: { ok: false, code: result.code, error: result.error, ...(result.data !== undefined ? { data: result.data } : {}) };
|
|
@@ -1827,7 +1883,8 @@ export class CommandHandler {
|
|
|
1827
1883
|
catch {
|
|
1828
1884
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
|
|
1829
1885
|
}
|
|
1830
|
-
const
|
|
1886
|
+
const resolvedArgv = argv.includes('--self') ? argv : [...argv, '--self', grant.selfAid];
|
|
1887
|
+
const resolved = resolveContactCommand(resolvedArgv);
|
|
1831
1888
|
if (!resolved.ok)
|
|
1832
1889
|
return { ok: false, code: resolved.code, error: resolved.reason };
|
|
1833
1890
|
if (resolved.command.selfAid !== grant.selfAid && !hasTrustedFullAccessDelegation(grant)) {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { isValidAid } from '../../aun/aid/validation.js';
|
|
2
|
+
import { remoteAccessService } from '../auth/remote-access-store.js';
|
|
3
|
+
export const DIAGNOSTIC_AGENT_AID = 'diag.agentid.cn';
|
|
4
|
+
/** IPC verifies local control authority; managed calls must independently prove their actor. */
|
|
5
|
+
export async function executeDiagIpc(service, registry, owners, cmd) {
|
|
6
|
+
if (cmd.sessionId !== undefined) {
|
|
7
|
+
if (typeof cmd.sessionId !== 'string' || !cmd.sessionId)
|
|
8
|
+
return { ok: false, code: 'INVALID_SESSION', message: '无效托管会话' };
|
|
9
|
+
const auth = registry.validate(cmd.delegationToken, cmd.sessionId, cmd.delegationCommandHash);
|
|
10
|
+
if (!auth.ok)
|
|
11
|
+
return { ok: false, code: auth.code, message: auth.reason };
|
|
12
|
+
if (auth.grant.channelType !== 'aun' || !owners.includes(auth.grant.actorId))
|
|
13
|
+
return { ok: false, code: 'DAEMON_OWNER_REQUIRED', message: '托管诊断发起需要本机 AUN DaemonOwner' };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
if (!Array.isArray(cmd.argv) || cmd.argv.some((v) => typeof v !== 'string'))
|
|
17
|
+
throw new Error('Invalid diag argv');
|
|
18
|
+
return await service.execute(parseDiagArgs(cmd.argv));
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return { ok: false, code: error.code ?? 'INVALID_ARGUMENT', message: error.message };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function parseDiagArgs(args) {
|
|
25
|
+
const words = [];
|
|
26
|
+
let ownerAid;
|
|
27
|
+
let literal = false;
|
|
28
|
+
for (let i = 0; i < args.length; i++) {
|
|
29
|
+
const word = args[i];
|
|
30
|
+
if (!literal && word === '--') {
|
|
31
|
+
literal = true;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!literal && word === '--owner') {
|
|
35
|
+
if (ownerAid || !args[i + 1] || !isValidAid(args[i + 1]))
|
|
36
|
+
throw new Error('--owner 需要一个有效 AID');
|
|
37
|
+
ownerAid = args[++i];
|
|
38
|
+
}
|
|
39
|
+
else if (!literal && word.startsWith('--'))
|
|
40
|
+
throw new Error(`不支持的参数:${word}`);
|
|
41
|
+
else
|
|
42
|
+
words.push(word);
|
|
43
|
+
}
|
|
44
|
+
const problem = words.join(' ').trim();
|
|
45
|
+
if (problem.toLowerCase() === 'stop') {
|
|
46
|
+
if (ownerAid)
|
|
47
|
+
throw new Error('diag stop 不需要指定对象');
|
|
48
|
+
return { action: 'stop' };
|
|
49
|
+
}
|
|
50
|
+
if (!problem || problem.length > 16_000)
|
|
51
|
+
throw new Error('请提供 1 到 16000 字符的问题描述');
|
|
52
|
+
return { action: 'start', problem, ...(ownerAid ? { ownerAid } : {}) };
|
|
53
|
+
}
|
|
54
|
+
export class DiagService {
|
|
55
|
+
context;
|
|
56
|
+
send;
|
|
57
|
+
tokens;
|
|
58
|
+
constructor(context, send, tokens = remoteAccessService) {
|
|
59
|
+
this.context = context;
|
|
60
|
+
this.send = send;
|
|
61
|
+
this.tokens = tokens;
|
|
62
|
+
}
|
|
63
|
+
/** Local CLI authority is checked by IPC; slash identity is authenticated AUN peerId. */
|
|
64
|
+
async execute(command, actorAid) {
|
|
65
|
+
const { controlAid, owners } = this.context();
|
|
66
|
+
if (!controlAid || !isValidAid(controlAid))
|
|
67
|
+
return { ok: false, code: 'CONTROL_AID_REQUIRED', message: '本机尚未配置 Control AID' };
|
|
68
|
+
if (actorAid !== undefined && !owners.includes(actorAid))
|
|
69
|
+
return { ok: false, code: 'DAEMON_OWNER_REQUIRED', message: '只有本机 DaemonOwner 可以发起或停止诊断' };
|
|
70
|
+
if (command.action === 'stop') {
|
|
71
|
+
const previous = this.tokens.revokeDiagnosticToken(controlAid);
|
|
72
|
+
if (!previous)
|
|
73
|
+
return { ok: true, message: '当前没有诊断授权' };
|
|
74
|
+
const diag = previous.diagnostic;
|
|
75
|
+
try {
|
|
76
|
+
await this.send(diag.agentAid, `诊断授权已取消。目标 daemon:${controlAid};case:${diag.caseId};沟通 Owner:${diag.ownerAid}。请停止这次诊断的后续访问,并告知 Owner。`);
|
|
77
|
+
return { ok: true, message: '诊断授权已撤销,已通知诊断 Agent' };
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { ok: true, code: 'DIAG_STOP_NOTICE_FAILED', message: '诊断授权已撤销;取消通知发送失败,诊断 Agent 后续访问将被拒绝' };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const ownerAid = actorAid ?? command.ownerAid ?? owners[0];
|
|
84
|
+
if (!ownerAid || !isValidAid(ownerAid) || !owners.includes(ownerAid))
|
|
85
|
+
return { ok: false, code: 'DAEMON_OWNER_REQUIRED', message: '请配置本机 owners,或指定其中一个有效 Owner AID' };
|
|
86
|
+
if (!command.problem?.trim() || command.problem.length > 16_000)
|
|
87
|
+
return { ok: false, code: 'INVALID_ARGUMENT', message: '请提供 1 到 16000 字符的问题描述' };
|
|
88
|
+
let record;
|
|
89
|
+
try {
|
|
90
|
+
record = this.tokens.issueDiagnosticToken(controlAid, DIAGNOSTIC_AGENT_AID, ownerAid);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return { ok: false, code: error.code ?? 'DIAG_AUTH_FAILED', message: error.message };
|
|
94
|
+
}
|
|
95
|
+
const caseId = record.diagnostic.caseId;
|
|
96
|
+
const invitation = [
|
|
97
|
+
'请协助诊断以下主机,并主动联系指定 Owner 展示进展、追问和报告。',
|
|
98
|
+
`目标 daemon Control AID:${controlAid}`, `沟通 Owner AID:${ownerAid}`, `case:${caseId}`,
|
|
99
|
+
`远程访问 Token:${record.token}`,
|
|
100
|
+
'请通过标准 Menu 调用诊断;Token 放入 E2EE 受保护头。权限复用该主机既有 Control Menu,包含 restart。有效访问滑动续期,空闲 60 分钟失效。',
|
|
101
|
+
'先给 Owner 发开始消息,使用通用 handoff 保留目标、Token、问题和发现;无需等待 Owner 回复即可开始。授权失效后停止访问。',
|
|
102
|
+
`问题描述:\n${command.problem.trim()}`,
|
|
103
|
+
].join('\n');
|
|
104
|
+
try {
|
|
105
|
+
await this.send(DIAGNOSTIC_AGENT_AID, invitation);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.tokens.revokeDiagnosticToken(controlAid, record.token);
|
|
109
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
110
|
+
return { ok: false, code: 'DIAG_INVITATION_FAILED', message: `未能确认诊断邀请发送成功:${reason}。本次授权已撤销,请重试` };
|
|
111
|
+
}
|
|
112
|
+
// A stop may race with delivery; never report an already revoked grant as active.
|
|
113
|
+
if (this.tokens.getDiagnosticToken(controlAid)?.token !== record.token)
|
|
114
|
+
return { ok: false, code: 'DIAG_CANCELLED', message: '邀请发送期间授权已撤销,本次诊断已取消' };
|
|
115
|
+
return { ok: true, caseId, ownerAid, agentAid: DIAGNOSTIC_AGENT_AID,
|
|
116
|
+
message: `诊断邀请已发出,远程诊断 Agent 将联系 ${ownerAid},请在与 ${DIAGNOSTIC_AGENT_AID} 的会话中查看处理过程。` };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -87,6 +87,7 @@ const ENTRIES = [
|
|
|
87
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' },
|
|
88
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' } },
|
|
89
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' } },
|
|
90
|
+
{ id: 'system.paircode', type: 'action', contexts: ['process'], name: 'system', label: '获取配对码', section: 'process', order: 35, action: { cmd: '/system', action: 'paircode', operation: 'system.paircode', scope: 'process' } },
|
|
90
91
|
{ 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' },
|
|
91
92
|
{ id: 'agent.create', type: 'action', contexts: ['process'], name: 'agent', label: '创建 Agent', section: 'process', order: 50, action: { cmd: '/agent', action: 'create', operation: 'agent.create', scope: 'control' } },
|
|
92
93
|
{ id: 'agent.reload', type: 'action', contexts: ['process'], name: 'agent', label: '重载 Agent', section: 'process', order: 60, action: { cmd: '/agent', action: 'reload', operation: 'agent.reload', scope: 'control' } },
|
|
@@ -304,11 +305,18 @@ export async function resolveExplicitMenuExecutionContext(input) {
|
|
|
304
305
|
}
|
|
305
306
|
function entryVisible(entry, context) { return entry.contexts.includes(context.kind); }
|
|
306
307
|
function intentFor(binding, context) { return { operation: binding.operation, scope: binding.scope, source: 'menu', args: { ...bindingArgs(binding, context) } }; }
|
|
307
|
-
async function operationView(binding, context) {
|
|
308
|
+
async function operationView(binding, context, options = {}) {
|
|
308
309
|
if (!binding)
|
|
309
310
|
return undefined;
|
|
310
311
|
if (binding.supported === false)
|
|
311
312
|
return { supported: false, allowed: false, reason: { code: 'NOT_SUPPORTED', message: 'operation is not supported' } };
|
|
313
|
+
if (binding.operation === 'system.paircode' && options.paircodeAllowed === false) {
|
|
314
|
+
return {
|
|
315
|
+
supported: true,
|
|
316
|
+
allowed: false,
|
|
317
|
+
reason: { code: 'NOT_ALLOWED', message: 'Pairing code issuance requires encrypted DaemonOwner authentication' },
|
|
318
|
+
};
|
|
319
|
+
}
|
|
312
320
|
if (binding.operation === 'group.rulespolicy.update' && context.subject.role !== 'owner') {
|
|
313
321
|
return { supported: true, allowed: false, reason: { code: 'PERMISSION_DENIED', message: 'rulesPolicy can only be changed by an Agent owner' } };
|
|
314
322
|
}
|
|
@@ -331,16 +339,17 @@ function wireBinding(binding, context) {
|
|
|
331
339
|
export async function getMenuCatalog(input) {
|
|
332
340
|
const context = await resolveMenuCatalogContext(input);
|
|
333
341
|
const entries = [];
|
|
342
|
+
const paircodeAllowed = input.paircodeAllowed ?? input.channelScope !== 'control';
|
|
334
343
|
for (const entry of ENTRIES.filter(candidate => entryVisible(candidate, context)).sort((a, b) => a.order - b.order)) {
|
|
335
344
|
const operations = {};
|
|
336
345
|
for (const [key, binding] of [['query', entry.query], ['options', entry.options], ['update', entry.update], ['action', entry.action]]) {
|
|
337
|
-
const view = await operationView(binding, context);
|
|
346
|
+
const view = await operationView(binding, context, { paircodeAllowed });
|
|
338
347
|
if (view)
|
|
339
348
|
operations[key] = view;
|
|
340
349
|
}
|
|
341
350
|
entries.push({ id: entry.id, type: entry.type, name: entry.name, label: entry.label, section: entry.section, order: entry.order, target: { scope: context.kind === 'agent' ? 'agent' : context.kind === 'process' ? 'process' : 'relation', ...(context.self ? { self: context.self } : {}), ...(context.peerKey ? { peerKey: context.peerKey } : {}) }, valueSchema: entry.valueSchema ?? { type: 'object' }, operations, wire: { name: entry.name, operations: { ...(wireBinding(entry.query, context) ? { query: wireBinding(entry.query, context) } : {}), ...(wireBinding(entry.options, context) ? { options: wireBinding(entry.options, context) } : {}), ...(wireBinding(entry.update, context) ? { update: wireBinding(entry.update, context) } : {}), ...(wireBinding(entry.action, context) ? { action: wireBinding(entry.action, context) } : {}) } }, ui: { control: entry.control ?? 'action' } });
|
|
342
351
|
}
|
|
343
|
-
const policyRevision = hash({ role: context.subject.role, self: context.subject.selfAid, peerKey: context.subject.peerKey, groupMembershipRole: context.groupMembershipRole, baselineRole: context.baselineRole, roles: readRolesConfig(context.subject.selfAid) });
|
|
352
|
+
const policyRevision = hash({ role: context.subject.role, self: context.subject.selfAid, peerKey: context.subject.peerKey, groupMembershipRole: context.groupMembershipRole, baselineRole: context.baselineRole, paircodeAllowed, roles: readRolesConfig(context.subject.selfAid) });
|
|
344
353
|
const data = { $schema_version: 1, kind: 'menu.catalog', complete: true, catalogRevision: hash({ entries, context: context.contextKey, policyRevision }), policyRevision, generatedAt: Date.now(), context: { contextKey: context.contextKey, kind: context.kind, channelScope: context.channelScope, transport: context.transport, chatType: context.chatType, ...(context.self ? { self: context.self } : {}), ...(context.actor ? { actor: context.actor } : {}), ...(context.groupId ? { groupId: context.groupId } : {}), ...(context.peerKey ? { peerKey: context.peerKey } : {}), requesterRole: context.requesterRole, ...(context.groupMembershipRole ? { groupMembershipRole: context.groupMembershipRole } : {}), ...(context.baselineRole !== undefined ? { baselineRole: context.baselineRole } : {}) }, features: { batchValuesV1: true, verifiedExplicitGroupContext: context.kind === 'group_relation' }, entries };
|
|
345
354
|
return data;
|
|
346
355
|
}
|
|
@@ -40,7 +40,7 @@ import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
|
|
|
40
40
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
41
41
|
import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
|
|
42
42
|
import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
|
|
43
|
-
import { validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
43
|
+
import { remoteAccessService, validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
44
44
|
import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
|
|
45
45
|
import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
|
|
46
46
|
import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
|
|
@@ -1015,6 +1015,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
1015
1015
|
return intent('system.upgrade', 'process', { action });
|
|
1016
1016
|
if (action === 'check')
|
|
1017
1017
|
return intent('system.status', 'process', { action });
|
|
1018
|
+
if (action === 'paircode')
|
|
1019
|
+
return intent('system.paircode', 'process', { action });
|
|
1018
1020
|
}
|
|
1019
1021
|
if (cmdBase === '/agent') {
|
|
1020
1022
|
if (action === 'reload')
|
|
@@ -1767,7 +1769,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1767
1769
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'current');
|
|
1768
1770
|
const currentModel = requestedModel || readMenuModel(target, agent).value || agent.getModel();
|
|
1769
1771
|
if (models.length > 0)
|
|
1770
|
-
return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : '')
|
|
1772
|
+
return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : ''), selected: modelMatches(agent, m, currentModel) }));
|
|
1771
1773
|
}
|
|
1772
1774
|
return null;
|
|
1773
1775
|
}
|
|
@@ -2372,6 +2374,24 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2372
2374
|
}
|
|
2373
2375
|
if (cmdBase === '/system') {
|
|
2374
2376
|
const view = args?.view ?? 'status';
|
|
2377
|
+
if (view === 'version') {
|
|
2378
|
+
const data = {};
|
|
2379
|
+
try {
|
|
2380
|
+
const pkgPath = path.join(getPackageRoot(), 'package.json');
|
|
2381
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
2382
|
+
if (pkg?.version)
|
|
2383
|
+
data.version = pkg.version;
|
|
2384
|
+
}
|
|
2385
|
+
catch { }
|
|
2386
|
+
try {
|
|
2387
|
+
const fp = path.join(getPackageRoot(), 'node_modules', '@agentunion', 'fastaun', 'package.json');
|
|
2388
|
+
const fp2 = JSON.parse(fs.readFileSync(fp, 'utf-8'));
|
|
2389
|
+
if (fp2?.version)
|
|
2390
|
+
data.fastaunVersion = fp2.version;
|
|
2391
|
+
}
|
|
2392
|
+
catch { }
|
|
2393
|
+
return { data };
|
|
2394
|
+
}
|
|
2375
2395
|
if (view === 'monitor') {
|
|
2376
2396
|
if (typeof this.daemonMonitorProvider !== 'function') {
|
|
2377
2397
|
return { error: 'Daemon monitor snapshot is unavailable', code: 'DEPENDENCY_UNAVAILABLE' };
|
|
@@ -3885,6 +3905,13 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3885
3905
|
this.eventBus.publish({ type: 'system:restart', channel, channelId });
|
|
3886
3906
|
return { data: { action: 'restart', success: true } };
|
|
3887
3907
|
}
|
|
3908
|
+
if (action === 'paircode') {
|
|
3909
|
+
// Keep Menu pairing on the same daemon-owned store as /pair, ECWeb, and
|
|
3910
|
+
// the local IPC endpoint. The store reuses an unexpired code and creates
|
|
3911
|
+
// a new one only after the shared TTL has elapsed.
|
|
3912
|
+
const pair = remoteAccessService.getPairingCode(subject.actorId);
|
|
3913
|
+
return { data: { code: pair.code, expiresAt: pair.expiresAt } };
|
|
3914
|
+
}
|
|
3888
3915
|
if (action === 'check') {
|
|
3889
3916
|
const r = await this.delegateAsAction(action, '/check', channel, channelId, userId, { overrideIdentity, authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3890
3917
|
// delegateAsAction 会把 structured 展开到 data 顶层,并保留 data.structured 兼容旧客户端。
|
|
@@ -4221,6 +4248,12 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4221
4248
|
});
|
|
4222
4249
|
const trustedIdentity = trustedSubject.identity;
|
|
4223
4250
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
4251
|
+
if (payload?.type === 'menu.action'
|
|
4252
|
+
&& menuCommandToken(cmd) === '/system'
|
|
4253
|
+
&& payload.action === 'paircode'
|
|
4254
|
+
&& (context.authMethod !== 'daemon-owner' || (context.source === 'control' && !context.encrypted))) {
|
|
4255
|
+
return ecwebErr(id, name, 'NOT_ALLOWED', 'Pairing code issuance requires encrypted DaemonOwner authentication');
|
|
4256
|
+
}
|
|
4224
4257
|
try {
|
|
4225
4258
|
if (menuCommandToken(cmd) === '/baseagent-models') {
|
|
4226
4259
|
if (payload.type !== 'menu.query')
|
|
@@ -4239,6 +4272,8 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4239
4272
|
channelScope: 'control',
|
|
4240
4273
|
channelId: targetChannel,
|
|
4241
4274
|
request: payload.args.context,
|
|
4275
|
+
paircodeAllowed: context.authMethod === 'daemon-owner'
|
|
4276
|
+
&& (context.source !== 'control' || context.encrypted),
|
|
4242
4277
|
});
|
|
4243
4278
|
return menuSuccess({ id, ...(name ? { name } : {}) }, data);
|
|
4244
4279
|
}
|
|
@@ -4343,15 +4378,6 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
4343
4378
|
const validationError = validateMenuRequest(payload ?? {});
|
|
4344
4379
|
if (validationError)
|
|
4345
4380
|
return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
|
|
4346
|
-
const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
|
|
4347
|
-
const owners = loadDaemonConfig().owners ?? [];
|
|
4348
|
-
if (isProcessLevel && owners.length === 0) {
|
|
4349
|
-
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4350
|
-
code: 'NOT_ALLOWED',
|
|
4351
|
-
message: formatAuthorizationDenial('Configure daemon.json.owners before using process-level operations.', 'DAEMON_OWNER_REQUIRED'),
|
|
4352
|
-
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
4353
|
-
});
|
|
4354
|
-
}
|
|
4355
4381
|
if (!trusted || (!trusted.localDirect && trusted.authenticated !== true)) {
|
|
4356
4382
|
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4357
4383
|
code: 'PERMISSION_DENIED',
|
|
@@ -4359,29 +4385,22 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
4359
4385
|
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
4360
4386
|
});
|
|
4361
4387
|
}
|
|
4362
|
-
//
|
|
4363
|
-
//
|
|
4364
|
-
//
|
|
4365
|
-
|
|
4366
|
-
// without requiring owner-bound pairing metadata.
|
|
4388
|
+
// ECWeb authentication grants daemon-owner authority independently of the
|
|
4389
|
+
// configured owners. Prefer an owner for audit/Trigger attribution when
|
|
4390
|
+
// available; otherwise keep local and remote management actors distinct.
|
|
4391
|
+
const owners = loadDaemonConfig().owners ?? [];
|
|
4367
4392
|
const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
|
|
4368
4393
|
? trusted.actorAid
|
|
4369
|
-
: owners[0] ?? (trusted.localDirect ? 'local-direct' :
|
|
4370
|
-
if (!userId) {
|
|
4371
|
-
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4372
|
-
code: 'PERMISSION_DENIED',
|
|
4373
|
-
message: 'ECWeb operation requires configured daemon owners',
|
|
4374
|
-
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
4375
|
-
});
|
|
4376
|
-
}
|
|
4394
|
+
: owners[0] ?? (trusted.localDirect ? 'local-direct' : 'ecweb-remote');
|
|
4377
4395
|
return execMenuForSystemControl.call(this, payload, {
|
|
4378
4396
|
source: 'ecweb',
|
|
4379
4397
|
actorAid: userId,
|
|
4398
|
+
authMethod: trusted.localDirect ? 'daemon-owner' : 'remote-access-token',
|
|
4399
|
+
encrypted: trusted.localDirect,
|
|
4380
4400
|
localDirect: trusted.localDirect,
|
|
4381
4401
|
// The ECWeb server/IPC control-token gate authenticates this management
|
|
4382
4402
|
// request. All authenticated ECWeb connections are equally eligible to
|
|
4383
|
-
// approve persistent fullaccess
|
|
4384
|
-
// a current configured owner.
|
|
4403
|
+
// approve persistent fullaccess without owner-bound token metadata.
|
|
4385
4404
|
canApprovePersistentFullAccess: true,
|
|
4386
4405
|
owners,
|
|
4387
4406
|
});
|
|
@@ -4415,6 +4434,8 @@ export async function execMenuForControl(payload, peerId, auth = {}) {
|
|
|
4415
4434
|
return execMenuForSystemControl.call(this, payload, {
|
|
4416
4435
|
source: 'control',
|
|
4417
4436
|
actorAid: managementActor,
|
|
4437
|
+
authMethod: authorization.allowed ? authorization.authMethod : 'daemon-owner',
|
|
4438
|
+
encrypted: auth.encrypted === true,
|
|
4418
4439
|
localDirect: false,
|
|
4419
4440
|
canApprovePersistentFullAccess: false,
|
|
4420
4441
|
owners,
|
|
@@ -567,6 +567,11 @@ function sanitizeLogArgv(value, depth, seen) {
|
|
|
567
567
|
export function sanitizeMenuLogValue(value, key = '', depth = 0, seen = new WeakSet()) {
|
|
568
568
|
if (isSensitiveLogKey(key))
|
|
569
569
|
return '[REDACTED]';
|
|
570
|
+
// Pairing codes are intentionally returned as `code` on the wire for
|
|
571
|
+
// compatibility with /pair and the ECWeb API. Do not persist six-digit
|
|
572
|
+
// credentials in menu-protocol.log.
|
|
573
|
+
if (key === 'code' && typeof value === 'string' && /^\d{6}$/.test(value))
|
|
574
|
+
return '[REDACTED]';
|
|
570
575
|
if (value === null || value === undefined || typeof value === 'number' || typeof value === 'boolean')
|
|
571
576
|
return value;
|
|
572
577
|
if (typeof value === 'bigint')
|