evolcore 0.0.22 → 0.0.24
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 +74 -14
- package/README.md +4 -0
- package/bin/codex-managed-acl.ps1 +160 -0
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +384 -95
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +30 -2
- package/dist/agents/claude-runner.js +179 -240
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +90 -20
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +369 -0
- package/dist/agents/codex-runner.js +839 -192
- package/dist/agents/ecagent-runner.js +27 -34
- package/dist/agents/gemini-runner.js +62 -8
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +43 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +57 -5
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +7 -1
- package/dist/channels/aun.js +178 -77
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wechat.js +81 -10
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +108 -24
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +289 -61
- package/dist/cli/index.js +22 -2
- package/dist/cli/init.js +2 -10
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/cli/task-context.js +12 -2
- package/dist/cli/trigger-command.js +9 -1
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +10 -7
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +75 -18
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +14 -2
- package/dist/core/auth/remote-access-store.js +136 -0
- 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 +7 -2
- package/dist/core/command/command-handler.js +128 -30
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +6 -1
- package/dist/core/command/menu-handler.js +410 -98
- package/dist/core/command/menu-protocol.js +123 -0
- package/dist/core/command/slash-gate.js +4 -6
- package/dist/core/command/slash-handler.js +199 -70
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +3 -0
- 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-queue.js +20 -15
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +147 -94
- package/dist/core/model/catalog-service.js +310 -0
- package/dist/core/model/model-catalog.js +134 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +5 -2
- package/dist/core/permission/approval-gateway.js +183 -32
- package/dist/core/permission/protected-paths.js +63 -4
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-policy.js +257 -31
- package/dist/core/runtime-lock.js +13 -8
- 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 +214 -57
- package/dist/ipc.js +191 -102
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +77 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +16 -2
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +70 -15
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +13 -0
- package/kits/docs/evolcore/group-collaboration.md +7 -2
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/templates/roles/admin.json +2 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +8 -1
- package/package.json +16 -4
- package/dist/utils/codex-cli.js +0 -43
|
@@ -18,8 +18,16 @@ export function bootstrapInitialMessageOperationPrefix(aid) {
|
|
|
18
18
|
export function bootstrapInitialRetryOperationId(aid, at = Date.now()) {
|
|
19
19
|
return `${bootstrapInitialMessageOperationPrefix(aid)}:${at}`;
|
|
20
20
|
}
|
|
21
|
-
export function postBootstrapWelcomeOperationId(aid) {
|
|
22
|
-
|
|
21
|
+
export function postBootstrapWelcomeOperationId(aid, owner) {
|
|
22
|
+
const normalizedAid = aid.replace(/^@/, '');
|
|
23
|
+
return owner ? `bootstrap-complete:v2:${normalizedAid}:${owner}` : `bootstrap-complete:v1:${normalizedAid}`;
|
|
24
|
+
}
|
|
25
|
+
export function isPostBootstrapWelcomeOperationId(operationId, aid) {
|
|
26
|
+
if (!operationId)
|
|
27
|
+
return false;
|
|
28
|
+
const normalizedAid = aid.replace(/^@/, '');
|
|
29
|
+
return operationId === postBootstrapWelcomeOperationId(normalizedAid)
|
|
30
|
+
|| operationId.startsWith(`bootstrap-complete:v2:${normalizedAid}:`);
|
|
23
31
|
}
|
|
24
32
|
export function renderPostBootstrapWelcome(aid, owner, ownerName) {
|
|
25
33
|
const mdPath = agentMdPath(aid);
|
|
@@ -36,9 +44,13 @@ export function renderPostBootstrapWelcome(aid, owner, ownerName) {
|
|
|
36
44
|
});
|
|
37
45
|
}
|
|
38
46
|
export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName, delivery = { chatType: 'private' }) {
|
|
39
|
-
const operationId = postBootstrapWelcomeOperationId(aid);
|
|
47
|
+
const operationId = postBootstrapWelcomeOperationId(aid, owner);
|
|
40
48
|
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aid);
|
|
41
|
-
|
|
49
|
+
const legacyOperationId = postBootstrapWelcomeOperationId(aid);
|
|
50
|
+
if (hasMessageLogOperation(chatDir, operationId) || hasMessageLogOperation(chatDir, legacyOperationId))
|
|
51
|
+
return;
|
|
52
|
+
const legacy = outbox.findByDedupeKey(aid, legacyOperationId);
|
|
53
|
+
if (legacy?.channelId === owner)
|
|
42
54
|
return;
|
|
43
55
|
const existing = outbox.findByDedupeKey(aid, operationId);
|
|
44
56
|
if (existing
|
|
@@ -68,21 +80,30 @@ export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName, deliver
|
|
|
68
80
|
});
|
|
69
81
|
}
|
|
70
82
|
export function hasPendingPostBootstrapWelcomeOutbox(aid) {
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
return findPostBootstrapWelcomeOutbox(aid)
|
|
84
|
+
.some(entry => outbox.isDeliveryForChannel(entry.delivery, entry.channelId));
|
|
85
|
+
}
|
|
86
|
+
/** Keep pending welcomes from the single-owner v1 format retryable after upgrades. */
|
|
87
|
+
export function findPostBootstrapWelcomeOutbox(aid, opts = {}) {
|
|
88
|
+
const normalizedAid = aid.replace(/^@/, '');
|
|
89
|
+
const entries = outbox.findByDedupePrefix(normalizedAid, `bootstrap-complete:v2:${normalizedAid}:`, opts);
|
|
90
|
+
const legacy = outbox.findByDedupeKey(normalizedAid, postBootstrapWelcomeOperationId(normalizedAid), opts);
|
|
91
|
+
return [...entries, ...(legacy ? [legacy] : [])];
|
|
92
|
+
}
|
|
93
|
+
export function pendingPostBootstrapWelcomeOwners(aid) {
|
|
94
|
+
return findPostBootstrapWelcomeOutbox(aid)
|
|
95
|
+
.filter(entry => outbox.isDeliveryForChannel(entry.delivery, entry.channelId))
|
|
96
|
+
.map(entry => entry.channelId)
|
|
97
|
+
.filter((owner, index, all) => all.indexOf(owner) === index);
|
|
73
98
|
}
|
|
74
99
|
/** Bind the durable completion welcome to the fresh post-bootstrap session. */
|
|
75
|
-
export function bindPostBootstrapWelcomeOutboxSession(aid, sessionId) {
|
|
76
|
-
const
|
|
77
|
-
|
|
100
|
+
export function bindPostBootstrapWelcomeOutboxSession(aid, sessionId, owner) {
|
|
101
|
+
const normalizedAid = aid.replace(/^@/, '');
|
|
102
|
+
const entries = findPostBootstrapWelcomeOutbox(normalizedAid)
|
|
103
|
+
.filter(entry => (!owner || entry.channelId === owner) && outbox.isDeliveryForChannel(entry.delivery, entry.channelId));
|
|
104
|
+
// A session is scoped to one peer: never silently pick an arbitrary Owner.
|
|
105
|
+
if (!entries.length || (!owner && new Set(entries.map(entry => entry.channelId)).size !== 1))
|
|
78
106
|
return false;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return outbox.replaceIfRouteMatches(aid, entry, {
|
|
82
|
-
...entry,
|
|
83
|
-
context: {
|
|
84
|
-
...(entry.context ?? {}),
|
|
85
|
-
sessionId,
|
|
86
|
-
},
|
|
87
|
-
}) === 'replaced';
|
|
107
|
+
return entries.every(entry => entry.context?.sessionId === sessionId
|
|
108
|
+
|| outbox.replaceIfRouteMatches(normalizedAid, entry, { ...entry, context: { ...(entry.context ?? {}), sessionId } }) === 'replaced');
|
|
88
109
|
}
|
|
@@ -52,32 +52,38 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
|
|
|
52
52
|
reloaded: false,
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
55
|
+
const pendingOwners = welcome?.pendingPostBootstrapWelcomeOwners?.();
|
|
56
|
+
const sessionOwners = pendingOwners?.length ? pendingOwners : [undefined];
|
|
57
|
+
for (const owner of sessionOwners) {
|
|
58
|
+
let ownerSession;
|
|
59
|
+
try {
|
|
60
|
+
ownerSession = await sessions.ensurePostBootstrapMainSession(owner);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
code: 'MAIN_SESSION_CREATE_FAILED',
|
|
66
|
+
error: e instanceof Error ? e.message : `Failed to create the post-bootstrap main session for ${aid}`,
|
|
67
|
+
reloaded: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (!ownerSession.sessionId) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
code: 'MAIN_SESSION_CREATE_FAILED',
|
|
74
|
+
error: `Failed to create the post-bootstrap main session for ${aid}`,
|
|
75
|
+
reloaded: false,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (welcomePending && welcome && !await welcome.bindPostBootstrapWelcomeToSession(ownerSession.sessionId, owner)) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
code: 'WELCOME_SESSION_BIND_FAILED',
|
|
82
|
+
error: `Failed to bind the post-bootstrap welcome to session ${ownerSession.sessionId}`,
|
|
83
|
+
reloaded: false,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
mainSession ??= ownerSession;
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
89
|
const result = service.completeBootstrap(aid);
|
|
@@ -13,10 +13,11 @@ function defaultTypeConfig() {
|
|
|
13
13
|
return { mode: 'inherit', overrides: {} };
|
|
14
14
|
}
|
|
15
15
|
function resolveBaseagent(input) {
|
|
16
|
-
|
|
16
|
+
const value = input.baseagent
|
|
17
17
|
|| input.sessionBaseagent
|
|
18
|
-
|| input.config?.active_baseagent
|
|
19
|
-
|
|
18
|
+
|| input.config?.active_baseagent;
|
|
19
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
20
|
+
return normalized || undefined;
|
|
20
21
|
}
|
|
21
22
|
export function resolveCapabilityContext(input) {
|
|
22
23
|
const aid = input.aid || input.config?.aid;
|
|
@@ -25,9 +26,12 @@ export function resolveCapabilityContext(input) {
|
|
|
25
26
|
const projectPath = input.config?.projects?.defaultPath || input.projectPath || input.sessionProjectPath || null;
|
|
26
27
|
if (!projectPath)
|
|
27
28
|
return { error: '无法解析当前 agent projectPath', code: 'NO_PROJECT' };
|
|
29
|
+
const baseagent = resolveBaseagent(input);
|
|
30
|
+
if (!baseagent)
|
|
31
|
+
return { error: '无法解析当前 agent 的 baseagent', code: 'BASEAGENT_REQUIRED' };
|
|
28
32
|
return {
|
|
29
33
|
aid,
|
|
30
|
-
baseagent
|
|
34
|
+
baseagent,
|
|
31
35
|
projectPath,
|
|
32
36
|
};
|
|
33
37
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os from 'os';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { execCodexCliSync } from '../../../
|
|
3
|
+
import { execCodexCliSync } from '../../../agents/baseagent.js';
|
|
4
4
|
import { discoverSkillDirectories } from '../skill-discovery.js';
|
|
5
5
|
function discoverSkills(ctx) {
|
|
6
6
|
const roots = [
|
|
@@ -1,4 +1,5 @@
|
|
|
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';
|
|
@@ -297,6 +298,7 @@ async function runCreateInBackground(opts, w, service) {
|
|
|
297
298
|
const createOptions = {
|
|
298
299
|
aid: opts.aid, name: opts.name, baseagent: opts.baseagent,
|
|
299
300
|
project: opts.project, owner: opts.owner,
|
|
301
|
+
description: opts.description,
|
|
300
302
|
onPhase: (phase, state, detail) => {
|
|
301
303
|
if (state === 'begin') {
|
|
302
304
|
curPhase = phase;
|
|
@@ -393,6 +395,9 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
|
|
|
393
395
|
const model = typeof a.model === 'string' && a.model.trim() ? a.model.trim() : undefined;
|
|
394
396
|
const rawEffort = typeof a.effort === 'string' && a.effort.trim() ? a.effort.trim() : undefined;
|
|
395
397
|
const effort = rawEffort && rawEffort !== 'auto' ? rawEffort : undefined;
|
|
398
|
+
const description = typeof a.description === 'string' && a.description.trim()
|
|
399
|
+
? a.description.trim()
|
|
400
|
+
: undefined;
|
|
396
401
|
// D3: accepted=true 必须意味着后续 query 能查到该 AID 的创建状态。
|
|
397
402
|
let progressWriter;
|
|
398
403
|
try {
|
|
@@ -405,7 +410,7 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
|
|
|
405
410
|
void runCreateInBackground({
|
|
406
411
|
aid: a.aid, name: a.name, baseagent: a.baseagent,
|
|
407
412
|
project: a.project, owner,
|
|
408
|
-
model, effort, chatmode: a.chatmode,
|
|
413
|
+
model, effort, description, chatmode: a.chatmode,
|
|
409
414
|
eventBus,
|
|
410
415
|
}, progressWriter, service).catch(e => logger.error(`[agent-control] runCreateInBackground unhandled ${a.aid}: ${e?.message || e}`));
|
|
411
416
|
return { data: { accepted: true, aid: a.aid } };
|
|
@@ -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,13 +108,19 @@ export class CommandHandler {
|
|
|
108
108
|
interactionRouter;
|
|
109
109
|
statsCollector;
|
|
110
110
|
agentMap;
|
|
111
|
+
legacySingleRunnerMode;
|
|
111
112
|
primaryRunnerKey;
|
|
112
113
|
agentRegistry;
|
|
113
114
|
agentApplicationService;
|
|
114
115
|
triggerSchedulerResolver;
|
|
115
116
|
triggerSchedulerFallback;
|
|
116
117
|
triggerSchedulerFallbackAid;
|
|
118
|
+
/** Runtime-only owner for the daemon trigger scheduler. It is intentionally
|
|
119
|
+
* kept outside EvolAgentRegistry because the daemon channel is not an Agent. */
|
|
120
|
+
daemonTriggerOwnerAid;
|
|
121
|
+
daemonTriggerChannel = 'daemon';
|
|
117
122
|
daemonStatusProvider;
|
|
123
|
+
daemonMonitorProvider;
|
|
118
124
|
agentDelegationRegistry;
|
|
119
125
|
handoffRuntime;
|
|
120
126
|
contactRequestSubmitter;
|
|
@@ -126,23 +132,42 @@ export class CommandHandler {
|
|
|
126
132
|
* different backend; that would corrupt session metadata.
|
|
127
133
|
*/
|
|
128
134
|
getAgent(channel, baseagent) {
|
|
129
|
-
|
|
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) {
|
|
130
148
|
const owner = this.agentRegistry?.resolveByChannel(channel);
|
|
131
149
|
const evolName = owner?.name || '<unknown>';
|
|
132
|
-
const key = `${evolName}::${
|
|
150
|
+
const key = `${evolName}::${requestedBaseagent}`;
|
|
133
151
|
if (this.agentMap.has(key))
|
|
134
152
|
return this.agentMap.get(key);
|
|
135
153
|
if (owner) {
|
|
136
|
-
throw new BaseagentRunnerUnavailableError(evolName,
|
|
154
|
+
throw new BaseagentRunnerUnavailableError(evolName, requestedBaseagent, this.getAvailableBaseagentsForOwner(evolName));
|
|
137
155
|
}
|
|
138
|
-
const
|
|
139
|
-
.
|
|
140
|
-
if (
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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>', []);
|
|
146
171
|
}
|
|
147
172
|
getAvailableBaseagentsForOwner(evolName) {
|
|
148
173
|
const prefix = `${evolName}::`;
|
|
@@ -200,11 +225,13 @@ export class CommandHandler {
|
|
|
200
225
|
this.eventBus = eventBus;
|
|
201
226
|
if (agentRunnerOrMap instanceof Map) {
|
|
202
227
|
this.agentMap = agentRunnerOrMap;
|
|
228
|
+
this.legacySingleRunnerMode = false;
|
|
203
229
|
this.primaryRunnerKey = primaryRunnerKey || '<unknown>::claude';
|
|
204
230
|
}
|
|
205
231
|
else {
|
|
206
232
|
// 测试 / 单 runner 路径:占位 agent name 用 '<unknown>'
|
|
207
233
|
this.agentMap = new Map([[`<unknown>::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
|
|
234
|
+
this.legacySingleRunnerMode = true;
|
|
208
235
|
this.primaryRunnerKey = `<unknown>::${agentRunnerOrMap.name}`;
|
|
209
236
|
}
|
|
210
237
|
}
|
|
@@ -219,6 +246,32 @@ export class CommandHandler {
|
|
|
219
246
|
setTriggerSchedulerResolver(resolver) {
|
|
220
247
|
this.triggerSchedulerResolver = resolver;
|
|
221
248
|
}
|
|
249
|
+
/** Bind the virtual daemon channel to its trigger scheduler owner AID. */
|
|
250
|
+
setDaemonTriggerOwner(aid, channel = 'daemon') {
|
|
251
|
+
const normalizedAid = String(aid || '').trim();
|
|
252
|
+
if (!normalizedAid)
|
|
253
|
+
throw new Error('daemon trigger owner AID is required');
|
|
254
|
+
this.daemonTriggerOwnerAid = normalizedAid;
|
|
255
|
+
this.daemonTriggerChannel = String(channel || 'daemon').trim() || 'daemon';
|
|
256
|
+
}
|
|
257
|
+
/** Used by control-menu routing; daemon owner is not in the Agent registry. */
|
|
258
|
+
getDaemonTriggerOwnerAid(channel = 'daemon') {
|
|
259
|
+
if (channel !== this.daemonTriggerChannel)
|
|
260
|
+
return undefined;
|
|
261
|
+
if (this.daemonTriggerOwnerAid)
|
|
262
|
+
return this.daemonTriggerOwnerAid;
|
|
263
|
+
try {
|
|
264
|
+
const controlAid = String(loadDaemonConfig().aid || '__daemon__').trim() || '__daemon__';
|
|
265
|
+
const existing = this.agentRegistry?.get?.(controlAid);
|
|
266
|
+
// Match index.ts: only a runnable (pre-start `stopped`) Agent causes
|
|
267
|
+
// the daemon scheduler owner to receive the collision suffix.
|
|
268
|
+
const conflicts = !!existing && (existing.status === undefined || existing.status === 'stopped');
|
|
269
|
+
return conflicts ? `${controlAid}#daemon` : controlAid;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
222
275
|
/** Compatibility entrypoint for older tests/callers that inject a single scheduler. */
|
|
223
276
|
setTriggerScheduler(scheduler, manager) {
|
|
224
277
|
this.triggerSchedulerFallback = scheduler;
|
|
@@ -233,6 +286,9 @@ export class CommandHandler {
|
|
|
233
286
|
setDaemonStatusProvider(provider) {
|
|
234
287
|
this.daemonStatusProvider = provider;
|
|
235
288
|
}
|
|
289
|
+
setDaemonMonitorProvider(provider) {
|
|
290
|
+
this.daemonMonitorProvider = provider;
|
|
291
|
+
}
|
|
236
292
|
/** 返回管理当前通道的 EvolAgent,无则返回 null */
|
|
237
293
|
getOwningAgent(channel) {
|
|
238
294
|
if (!this.agentRegistry)
|
|
@@ -240,7 +296,9 @@ export class CommandHandler {
|
|
|
240
296
|
return this.agentRegistry.resolveByChannel(channel);
|
|
241
297
|
}
|
|
242
298
|
getTriggerSchedulerForChannel(channel) {
|
|
243
|
-
const agentAid = this.getOwningAgent(channel)?.aid
|
|
299
|
+
const agentAid = this.getOwningAgent(channel)?.aid
|
|
300
|
+
?? this.getDaemonTriggerOwnerAid(channel)
|
|
301
|
+
?? tryParseChannelKey(channel)?.selfAID;
|
|
244
302
|
return agentAid ? this.triggerSchedulerResolver?.(agentAid) : this.triggerSchedulerFallback;
|
|
245
303
|
}
|
|
246
304
|
getTriggerSchedulerForAgent(agentAid) {
|
|
@@ -388,13 +446,13 @@ export class CommandHandler {
|
|
|
388
446
|
return fallback;
|
|
389
447
|
}
|
|
390
448
|
}
|
|
391
|
-
validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false) {
|
|
449
|
+
validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
|
|
392
450
|
if (!mode)
|
|
393
451
|
return undefined;
|
|
394
452
|
if (mode === 'fullaccess') {
|
|
395
453
|
if (!isFullAccessEnabled())
|
|
396
454
|
return 'fullaccess 功能未启用';
|
|
397
|
-
return
|
|
455
|
+
return canApprovePersistentFullAccess ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
|
|
398
456
|
}
|
|
399
457
|
if (!role) {
|
|
400
458
|
if (mode === 'bypass' && !isAdmin)
|
|
@@ -450,7 +508,9 @@ export class CommandHandler {
|
|
|
450
508
|
return modelDecision.message || '当前角色不允许使用该 Trigger 模型';
|
|
451
509
|
}
|
|
452
510
|
if (definition.execution.effort) {
|
|
453
|
-
|
|
511
|
+
if (!executionBaseagent)
|
|
512
|
+
return '无法确定 Trigger 会话的 baseagent';
|
|
513
|
+
const base = executionBaseagent;
|
|
454
514
|
const effortDecision = validateRuntimeStringFieldOverride({
|
|
455
515
|
selfAid: definition.agentAid,
|
|
456
516
|
role,
|
|
@@ -539,7 +599,7 @@ export class CommandHandler {
|
|
|
539
599
|
} : undefined;
|
|
540
600
|
const permissionMode = this.triggerPermissionFromParsed(parsed);
|
|
541
601
|
const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId, chatType);
|
|
542
|
-
const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner);
|
|
602
|
+
const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner, canApprovePersistentFullAccess);
|
|
543
603
|
if (permissionError)
|
|
544
604
|
return { error: permissionError };
|
|
545
605
|
if (permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
|
|
@@ -568,7 +628,7 @@ export class CommandHandler {
|
|
|
568
628
|
};
|
|
569
629
|
}
|
|
570
630
|
}
|
|
571
|
-
const authorizedBy = permissionMode === 'fullaccess' &&
|
|
631
|
+
const authorizedBy = permissionMode === 'fullaccess' && canApprovePersistentFullAccess
|
|
572
632
|
? this.canonicalDaemonOwnerId(schedulerAid, channel, peerId, chatType)
|
|
573
633
|
: undefined;
|
|
574
634
|
const definition = {
|
|
@@ -964,8 +1024,8 @@ export class CommandHandler {
|
|
|
964
1024
|
return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source);
|
|
965
1025
|
}
|
|
966
1026
|
/** menu.update — 写入新值。 */
|
|
967
|
-
async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu') {
|
|
968
|
-
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);
|
|
969
1029
|
}
|
|
970
1030
|
/** menu.action — 触发动词。 */
|
|
971
1031
|
async execMenuAction(cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext, deferSideEffect) {
|
|
@@ -975,9 +1035,9 @@ export class CommandHandler {
|
|
|
975
1035
|
async execMenuForEcweb(payload, auth) {
|
|
976
1036
|
return await menuExecMenuForEcweb.call(this, payload, auth);
|
|
977
1037
|
}
|
|
978
|
-
/**
|
|
979
|
-
async execMenuForControl(payload, peerId) {
|
|
980
|
-
return await menuExecMenuForControl.call(this, payload, peerId);
|
|
1038
|
+
/** Control AID Menu entry: daemon-owner or validated remote-access token. */
|
|
1039
|
+
async execMenuForControl(payload, peerId, auth) {
|
|
1040
|
+
return await menuExecMenuForControl.call(this, payload, peerId, auth);
|
|
981
1041
|
}
|
|
982
1042
|
/**
|
|
983
1043
|
* CLI 透传执行:spawn `node dist/cli/index.js <argv>` 子进程,捕获输出回传。
|
|
@@ -1067,7 +1127,7 @@ export class CommandHandler {
|
|
|
1067
1127
|
/** 把 menu.action 委派给已有 slash 命令处理逻辑,把 OutboundPayload 包成结构化结果。 */
|
|
1068
1128
|
async delegateAsAction(action, slashCmd, channel, channelId, userId, opts = {}) {
|
|
1069
1129
|
try {
|
|
1070
|
-
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);
|
|
1071
1131
|
if (result == null) {
|
|
1072
1132
|
// null / undefined: 命令未识别或前置守卫拦截(如 idle 检查),视为失败
|
|
1073
1133
|
return { error: '命令未执行(可能被前置守卫拦截)', code: 'EXEC_FAILED' };
|
|
@@ -1344,7 +1404,7 @@ export class CommandHandler {
|
|
|
1344
1404
|
/trigger list [--all] — 查看所有触发器
|
|
1345
1405
|
/trigger create <参数> — 创建触发器(set 为兼容别名)
|
|
1346
1406
|
/trigger update <名称|ID> <参数> — 修改触发器
|
|
1347
|
-
常用参数:--
|
|
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
|
|
1348
1408
|
/trigger enable <名称|ID> — 启用触发器
|
|
1349
1409
|
/trigger disable <名称|ID> — 暂停触发器
|
|
1350
1410
|
/trigger show <名称|ID> — 查看触发器详情
|
|
@@ -1396,7 +1456,7 @@ export class CommandHandler {
|
|
|
1396
1456
|
return { ok: false, error: err?.message || String(err) };
|
|
1397
1457
|
}
|
|
1398
1458
|
if (updated.execution.permissionMode === 'fullaccess') {
|
|
1399
|
-
if (!isDaemonOwner) {
|
|
1459
|
+
if (!isDaemonOwner && !canApprovePersistentFullAccess) {
|
|
1400
1460
|
return { ok: false, code: 'FORBIDDEN', error: '无权限:只有 daemon-owner 可以配置 fullaccess Trigger' };
|
|
1401
1461
|
}
|
|
1402
1462
|
if (!canApprovePersistentFullAccess) {
|
|
@@ -1419,7 +1479,7 @@ export class CommandHandler {
|
|
|
1419
1479
|
return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
|
|
1420
1480
|
}
|
|
1421
1481
|
const triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId);
|
|
1422
|
-
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);
|
|
1423
1483
|
if (permissionError) {
|
|
1424
1484
|
return {
|
|
1425
1485
|
ok: false,
|
|
@@ -1886,7 +1946,10 @@ export class CommandHandler {
|
|
|
1886
1946
|
if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
|
|
1887
1947
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
|
|
1888
1948
|
}
|
|
1889
|
-
|
|
1949
|
+
const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put';
|
|
1950
|
+
if (requiresSelfAid
|
|
1951
|
+
&& parsed.command.aid !== grantSelfAid
|
|
1952
|
+
&& !hasTrustedFullAccessDelegation(delegation.grant)) {
|
|
1890
1953
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
|
|
1891
1954
|
}
|
|
1892
1955
|
let conversationId;
|
|
@@ -1926,7 +1989,12 @@ export class CommandHandler {
|
|
|
1926
1989
|
if (!decision.allow)
|
|
1927
1990
|
return { ok: false, code: decision.code, error: decision.reason };
|
|
1928
1991
|
try {
|
|
1929
|
-
return {
|
|
1992
|
+
return {
|
|
1993
|
+
ok: true,
|
|
1994
|
+
result: await executeManagedAidOperation(parsed.command, {
|
|
1995
|
+
showView: managedAidShowViewForRole(subject.role),
|
|
1996
|
+
}),
|
|
1997
|
+
};
|
|
1930
1998
|
}
|
|
1931
1999
|
catch (error) {
|
|
1932
2000
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2152,7 +2220,14 @@ export class CommandHandler {
|
|
|
2152
2220
|
if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'private')) {
|
|
2153
2221
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
|
|
2154
2222
|
}
|
|
2155
|
-
|
|
2223
|
+
// A management role may read history for any peer, one peer per query.
|
|
2224
|
+
// `issuedRole` is resolved daemon-side from the persisted relation
|
|
2225
|
+
// config, so it cannot be spoofed by the calling task. Narrower roles
|
|
2226
|
+
// stay pinned here, and authorizeOperation below remains the authority
|
|
2227
|
+
// for every role (member carries currentRelationOnly; visitor is denied).
|
|
2228
|
+
const mayReadCrossPeer = hasTrustedFullAccessDelegation(grant)
|
|
2229
|
+
|| (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole));
|
|
2230
|
+
if (!mayReadCrossPeer && parsed.command.target !== conversationId) {
|
|
2156
2231
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
|
|
2157
2232
|
}
|
|
2158
2233
|
}
|
|
@@ -2422,6 +2497,29 @@ export class CommandHandler {
|
|
|
2422
2497
|
});
|
|
2423
2498
|
if (!ctlDecision.allow)
|
|
2424
2499
|
return { ok: false, code: ctlDecision.code, error: ctlDecision.reason };
|
|
2500
|
+
if (cmd.startsWith('/status ') && cmd !== '/status --format json') {
|
|
2501
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: 'status 仅支持可选参数 --format json' };
|
|
2502
|
+
}
|
|
2503
|
+
if (cmd === '/status --format json') {
|
|
2504
|
+
const context = {
|
|
2505
|
+
selfAid: grantSelfAid,
|
|
2506
|
+
sessionId,
|
|
2507
|
+
taskId: grant.taskId,
|
|
2508
|
+
...(grant.messageId ? { messageId: grant.messageId } : {}),
|
|
2509
|
+
baseagent: session.baseagent,
|
|
2510
|
+
channel: grant.channelType,
|
|
2511
|
+
channelKey: grant.channel,
|
|
2512
|
+
channelId: conversationId,
|
|
2513
|
+
chatType: grant.chatType,
|
|
2514
|
+
peerKey: grant.peerKey,
|
|
2515
|
+
peerId: conversationId,
|
|
2516
|
+
peerRole: ctlSubject.role,
|
|
2517
|
+
permissionMode: grant.permissionMode ?? ctlSubject.permissionMode,
|
|
2518
|
+
projectPath: path.resolve(session.projectPath),
|
|
2519
|
+
...(grant.sessionRuntimeDir ? { tmpDir: grant.sessionRuntimeDir } : {}),
|
|
2520
|
+
};
|
|
2521
|
+
return { ok: true, result: JSON.stringify(context, null, 2), data: context };
|
|
2522
|
+
}
|
|
2425
2523
|
if (cmd.startsWith('/model info ')) {
|
|
2426
2524
|
const modelId = cmd.slice('/model info '.length).trim();
|
|
2427
2525
|
if (!modelId || /\s/.test(modelId))
|