evolcore 0.0.23 → 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 +50 -0
- package/README.md +3 -1
- package/bin/codex-managed-acl.ps1 +7 -2
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +17 -6
- package/dist/agents/baseagent-availability.js +4 -4
- package/dist/agents/baseagent.js +29 -1
- package/dist/agents/claude-runner.js +148 -13
- 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 +438 -59
- package/dist/agents/ecagent-runner.js +17 -3
- package/dist/agents/gemini-runner.js +60 -2
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -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/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 +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +60 -20
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +148 -17
- package/dist/cli/index.js +2 -2
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/config/builtin-roles.js +2 -0
- 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 +63 -3
- package/dist/core/auth/operation-catalog.js +10 -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 +7 -2
- package/dist/core/command/command-handler.js +87 -25
- package/dist/core/command/menu-catalog.js +3 -1
- package/dist/core/command/menu-handler.js +274 -70
- package/dist/core/command/menu-protocol.js +6 -2
- package/dist/core/command/slash-gate.js +1 -3
- package/dist/core/command/slash-handler.js +157 -48
- 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-utils.js +6 -3
- package/dist/core/message/response-engine.js +73 -21
- 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 +13 -6
- 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 +174 -28
- package/dist/ipc.js +68 -21
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/error-utils.js +11 -4
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +24 -1
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/templates/roles/admin.json +2 -0
- package/package.json +7 -3
- package/AGENTS.md +0 -62
- package/dist/utils/codex-cli.js +0 -43
|
@@ -48,6 +48,8 @@ export function getManagementCommandPermissions(role) {
|
|
|
48
48
|
'config.unset': { allow: true, scopes: ['agent', 'relation'] },
|
|
49
49
|
'role.policy.write': { allow: true, dangerous: true, scopes: ['agent'], constraints: { requireAgentAdmin: true } },
|
|
50
50
|
'ec.aid.show': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
|
|
51
|
+
'ec.aid.lookup': { allow: true, scopes: ['agent'] },
|
|
52
|
+
'ec.aid.agentmd.get': { allow: true, scopes: ['agent'] },
|
|
51
53
|
'ec.aid.agentmd.put': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
|
|
52
54
|
'ec.fs.mkdir': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
53
55
|
'ec.fs.upload': { allow: true, permissionMode: 'auto', scopes: ['relation'], constraints: { groupOnly: true, currentRelationOnly: true, targetCurrentAgentOnly: true } },
|
|
@@ -33,10 +33,11 @@ export function buildAuthSubject(input) {
|
|
|
33
33
|
&& fullAccessAuthorization.processRole === 'fullaccess-run'
|
|
34
34
|
&& fullAccessAuthorization.dataScope === 'daemon';
|
|
35
35
|
const isDaemonService = input.trustedProcessRole === 'daemon-service';
|
|
36
|
-
const
|
|
36
|
+
const isTrustedDaemonOwner = input.trustedProcessRole === 'daemon-owner';
|
|
37
|
+
const isDaemonOwner = !isDaemonService && !isFullAccessRun && (isTrustedDaemonOwner || isProcessOwner({
|
|
37
38
|
actor: roleDetail.actor,
|
|
38
39
|
processOwners,
|
|
39
|
-
});
|
|
40
|
+
}));
|
|
40
41
|
const suppliedRole = input.identity?.role && input.identity.role !== 'none' ? input.identity.role : undefined;
|
|
41
42
|
const role = suppliedRole ?? roleDetail.effectiveRole ?? 'none';
|
|
42
43
|
const processRole = isFullAccessRun
|
|
@@ -10,6 +10,8 @@ export function auditCommandAuthorization(event) {
|
|
|
10
10
|
(event.decision === 'allow' && event.dangerous) ||
|
|
11
11
|
(event.source === 'agent-tool' && event.operation.startsWith('config.')) ||
|
|
12
12
|
event.operation === 'codex.approval' ||
|
|
13
|
+
event.operation === 'permission.approval' ||
|
|
14
|
+
event.operation === 'permission.runtime' ||
|
|
13
15
|
event.operation.startsWith('fullaccess.') ||
|
|
14
16
|
event.operation.startsWith('role.') ||
|
|
15
17
|
event.operation === 'cli.exec.raw';
|
|
@@ -198,33 +200,78 @@ export function auditCodexApprovalDecision(input) {
|
|
|
198
200
|
* infrastructure denials without parsing localized log text.
|
|
199
201
|
*/
|
|
200
202
|
export function auditPermissionDecision(input) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
requestId: input.requestId,
|
|
203
|
+
const requestId = input.requestId ?? `perm-audit-${crypto.randomUUID()}`;
|
|
204
|
+
auditPermissionLifecycle({
|
|
205
|
+
phase: 'final',
|
|
206
|
+
requestId,
|
|
206
207
|
sessionId: input.sessionId,
|
|
207
208
|
agentAid: input.agentAid,
|
|
208
209
|
agentName: input.agentName,
|
|
209
210
|
permissionMode: input.permissionMode,
|
|
210
211
|
toolName: input.toolName,
|
|
212
|
+
decision: 'deny',
|
|
213
|
+
decisionSource: input.decisionSource,
|
|
214
|
+
policyCode: input.policyCode,
|
|
215
|
+
reason: input.reason,
|
|
216
|
+
role: input.role,
|
|
217
|
+
taskId: input.taskId,
|
|
218
|
+
summary: input.summary,
|
|
219
|
+
operation: 'permission.runtime',
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Emit a canonical request/response/final lifecycle for runtime approvals.
|
|
224
|
+
* All three phases share one eventKey and complete correlation fields so
|
|
225
|
+
* daily analysis can group them without parsing runner-specific log text.
|
|
226
|
+
*/
|
|
227
|
+
export function auditPermissionLifecycle(input) {
|
|
228
|
+
const sessionId = input.sessionId ?? 'unknown';
|
|
229
|
+
auditCommandAuthorization({
|
|
230
|
+
ts: Date.now(),
|
|
231
|
+
eventKey: buildToolLifecycleEventKey({
|
|
232
|
+
sessionId,
|
|
233
|
+
callId: input.callId,
|
|
234
|
+
correlationId: input.correlationId,
|
|
235
|
+
requestId: input.requestId,
|
|
236
|
+
}),
|
|
237
|
+
eventPhase: input.phase,
|
|
238
|
+
callId: input.callId ?? input.requestId,
|
|
239
|
+
correlationId: input.correlationId ?? input.callId ?? input.requestId,
|
|
240
|
+
requestId: input.requestId,
|
|
241
|
+
sessionId,
|
|
242
|
+
agentAid: input.agentAid ?? 'unknown',
|
|
243
|
+
agentName: input.agentName,
|
|
244
|
+
permissionMode: input.permissionMode ?? 'unknown',
|
|
245
|
+
toolName: input.toolName,
|
|
246
|
+
approvalMethod: input.approvalMethod,
|
|
211
247
|
policyCode: input.policyCode,
|
|
212
248
|
decisionSource: input.decisionSource,
|
|
213
|
-
errorCode: input.
|
|
214
|
-
? '
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
249
|
+
errorCode: input.decision === 'deny'
|
|
250
|
+
? input.decisionSource === 'approval'
|
|
251
|
+
? 'USER_DENIED'
|
|
252
|
+
: input.decisionSource === 'policy'
|
|
253
|
+
? 'POLICY_DENIED'
|
|
254
|
+
: infrastructureErrorCode(input.policyCode)
|
|
255
|
+
: undefined,
|
|
218
256
|
source: 'agent-tool',
|
|
219
|
-
operation: 'permission.
|
|
257
|
+
operation: input.operation ?? 'permission.approval',
|
|
220
258
|
scope: 'filesystem',
|
|
221
259
|
dangerous: false,
|
|
222
260
|
role: input.role ?? 'unknown',
|
|
223
261
|
taskId: input.taskId,
|
|
224
|
-
argsSummary: input.summary
|
|
225
|
-
|
|
262
|
+
argsSummary: input.summary || input.response
|
|
263
|
+
? {
|
|
264
|
+
...(input.summary ? { summary: input.summary } : {}),
|
|
265
|
+
...(input.response ? { response: input.response } : {}),
|
|
266
|
+
}
|
|
267
|
+
: undefined,
|
|
268
|
+
decision: input.decision,
|
|
226
269
|
executed: false,
|
|
227
|
-
executionState: '
|
|
270
|
+
executionState: input.phase === 'request'
|
|
271
|
+
? 'requested'
|
|
272
|
+
: input.phase === 'response'
|
|
273
|
+
? 'responded'
|
|
274
|
+
: input.decision === 'deny' ? 'blocked' : 'authorized',
|
|
228
275
|
reason: input.reason,
|
|
229
276
|
});
|
|
230
277
|
}
|
|
@@ -270,7 +317,7 @@ function infrastructureErrorCode(policyCode) {
|
|
|
270
317
|
}
|
|
271
318
|
function buildAuditRecord(event) {
|
|
272
319
|
const executed = event.executed ?? false;
|
|
273
|
-
const decisionSource = event.decisionSource ?? (event.decision === 'deny' ? 'policy' : 'command');
|
|
320
|
+
const decisionSource = event.decisionSource ?? (event.decision === 'deny' ? 'policy' : event.decision === 'pending' ? 'approval' : 'command');
|
|
274
321
|
const errorCode = event.errorCode ?? (event.decision === 'deny'
|
|
275
322
|
? decisionSource === 'approval'
|
|
276
323
|
? 'USER_DENIED'
|
|
@@ -281,9 +328,11 @@ function buildAuditRecord(event) {
|
|
|
281
328
|
const executionState = event.executionState
|
|
282
329
|
?? (event.decision === 'deny'
|
|
283
330
|
? 'blocked'
|
|
284
|
-
:
|
|
285
|
-
?
|
|
286
|
-
:
|
|
331
|
+
: event.decision === 'pending'
|
|
332
|
+
? 'requested'
|
|
333
|
+
: executed
|
|
334
|
+
? (event.exitCode === undefined || event.exitCode === 0 ? 'completed' : 'failed')
|
|
335
|
+
: 'authorized');
|
|
287
336
|
const lifecycleId = event.callId ?? event.correlationId ?? event.requestId;
|
|
288
337
|
return {
|
|
289
338
|
ts: event.ts,
|
|
@@ -386,7 +435,11 @@ function logAuditEvent(record) {
|
|
|
386
435
|
: routineEcwebRoleRead
|
|
387
436
|
? 'debug'
|
|
388
437
|
: 'info';
|
|
389
|
-
const marker = record.decision === 'deny'
|
|
438
|
+
const marker = record.decision === 'deny'
|
|
439
|
+
? 'DENY'
|
|
440
|
+
: record.decision === 'pending'
|
|
441
|
+
? 'PENDING'
|
|
442
|
+
: record.dangerous ? 'DANGEROUS_ALLOW' : 'ALLOW';
|
|
390
443
|
const message = [
|
|
391
444
|
`[CommandAudit:${marker}]`,
|
|
392
445
|
`operation=${record.operation}`,
|
|
@@ -34,6 +34,9 @@ const DAEMON_SERVICE_OPERATIONS = new Set([
|
|
|
34
34
|
'trigger.cancel',
|
|
35
35
|
'trigger.delete',
|
|
36
36
|
'trigger.run',
|
|
37
|
+
'agent.reload',
|
|
38
|
+
'system.restart',
|
|
39
|
+
'system.upgrade',
|
|
37
40
|
]);
|
|
38
41
|
export function authorizeCommand(ctx) {
|
|
39
42
|
return authorizeCommandInternal(ctx);
|
|
@@ -121,6 +124,47 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
121
124
|
return denyDecision(ctx, 'NOT_ALLOWED', `Operation ${operation} is not available from source ${ctx.source}`, operation, intent.scope, opMeta.dangerous);
|
|
122
125
|
}
|
|
123
126
|
if (ctx.processRole === 'daemon-service') {
|
|
127
|
+
// ECWeb uses the authenticated daemon service for its read-only menu
|
|
128
|
+
// bootstrap (model/effort, agent metadata, trigger views, etc.). These
|
|
129
|
+
// queries are safe to authorize by catalog category; mutating operations
|
|
130
|
+
// remain restricted to the explicit daemon-service allowlist below.
|
|
131
|
+
const ECWEB_SERVICE_READ_OPERATIONS = new Set([
|
|
132
|
+
'system.status', 'model.current', 'model.list', 'baseagent.models.list',
|
|
133
|
+
'model.effort.current', 'agent.baseagent.current', 'agent.baseagent.list',
|
|
134
|
+
'agent.show',
|
|
135
|
+
'agent.list',
|
|
136
|
+
'capability.read',
|
|
137
|
+
'gateway.read', 'stats.summary', 'stats.session', 'stats.context',
|
|
138
|
+
'role.policy.read', 'role.relation.read', 'config.read',
|
|
139
|
+
'model.info', 'model.check',
|
|
140
|
+
'trigger.list', 'trigger.show', 'trigger.history', 'trigger.eventCatalog',
|
|
141
|
+
]);
|
|
142
|
+
if (ECWEB_SERVICE_READ_OPERATIONS.has(operation)) {
|
|
143
|
+
return {
|
|
144
|
+
allow: true,
|
|
145
|
+
operation,
|
|
146
|
+
scope: intent.scope,
|
|
147
|
+
role: 'daemon-service',
|
|
148
|
+
dangerous: opMeta.dangerous,
|
|
149
|
+
matchedRule: 'daemon-service-read',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// The in-process service may perform system lifecycle actions only when
|
|
153
|
+
// the request came through the authenticated ECWeb management surface.
|
|
154
|
+
// A raw daemon-service subject on the control source is not a human owner.
|
|
155
|
+
if ((operation === 'system.restart' || operation === 'system.upgrade') && ctx.source === 'control') {
|
|
156
|
+
return denyDecision(ctx, 'NOT_ALLOWED', `Daemon service is not authorized for ${operation}`, operation, intent.scope, opMeta.dangerous, 'daemon-service');
|
|
157
|
+
}
|
|
158
|
+
if (ctx.source !== 'control' && (operation === 'observable.current' || operation === 'observable.update')) {
|
|
159
|
+
return {
|
|
160
|
+
allow: true,
|
|
161
|
+
operation,
|
|
162
|
+
scope: intent.scope,
|
|
163
|
+
role: 'daemon-service',
|
|
164
|
+
dangerous: opMeta.dangerous,
|
|
165
|
+
matchedRule: 'ecweb-daemon-service-agent-management',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
124
168
|
if (!DAEMON_SERVICE_OPERATIONS.has(operation)) {
|
|
125
169
|
return denyDecision(ctx, 'NOT_ALLOWED', `Daemon service is not authorized for ${operation}`, operation, intent.scope, opMeta.dangerous, 'daemon-service');
|
|
126
170
|
}
|
|
@@ -189,6 +233,20 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
189
233
|
if (roleDef.allowAccess === false) {
|
|
190
234
|
return denyDecision(ctx, 'ROLE_ACCESS_DENIED', `Role ${role} is not allowed to access commands`, operation, intent.scope, opMeta.dangerous);
|
|
191
235
|
}
|
|
236
|
+
// AID lookup and public agent.md reads are available to every accessible
|
|
237
|
+
// role and may target any AID that passed managed-command validation. The
|
|
238
|
+
// operation source is restricted to agent-tool by the catalog.
|
|
239
|
+
if ((operation === 'ec.aid.lookup' || operation === 'ec.aid.agentmd.get')
|
|
240
|
+
&& (intent.scope === 'relation' || intent.scope === 'agent')) {
|
|
241
|
+
return {
|
|
242
|
+
allow: true,
|
|
243
|
+
operation,
|
|
244
|
+
scope: intent.scope,
|
|
245
|
+
role,
|
|
246
|
+
dangerous: opMeta.dangerous,
|
|
247
|
+
matchedRule: 'public-aid-read',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
192
250
|
let matchResult = matchCommandPermission(operation, opMeta.category, roleDef.commandPermissions || {}, opMeta.dangerous);
|
|
193
251
|
// Agent self-service contact add deliberately reuses the same low-trust
|
|
194
252
|
// relation boundary as ec.msg.send. Older per-Agent role files may still
|
|
@@ -317,7 +375,9 @@ function requiresDaemonOwner(ctx, resolvedConfigCommand) {
|
|
|
317
375
|
return false;
|
|
318
376
|
}
|
|
319
377
|
function hasDaemonOwnerRole(ctx) {
|
|
320
|
-
return ctx.isDaemonOwner === true
|
|
378
|
+
return ctx.isDaemonOwner === true
|
|
379
|
+
|| ctx.processRole === 'daemon-owner'
|
|
380
|
+
|| ctx.processRole === 'daemon-service';
|
|
321
381
|
}
|
|
322
382
|
function daemonOwnerDenialReasonCode(ctx) {
|
|
323
383
|
if ((ctx.intent.operation === 'agent.show' || ctx.intent.operation === 'agent.reload')
|
|
@@ -340,8 +400,8 @@ function isCurrentAgentTarget(ctx) {
|
|
|
340
400
|
return !!ctx.selfAid && !!targetAid && targetAid === ctx.selfAid;
|
|
341
401
|
}
|
|
342
402
|
function daemonOwnerRequiredMessage(operation) {
|
|
343
|
-
return
|
|
344
|
-
+ 'Agent owner/admin
|
|
403
|
+
return `无权限:${operation} 是 daemon 级操作,需要 DaemonOwner。`
|
|
404
|
+
+ 'Agent owner/admin 和 permissionMode=bypass 均不能授予此权限。';
|
|
345
405
|
}
|
|
346
406
|
function getRuleRank(rule, permission, operation, category, isDangerous) {
|
|
347
407
|
const denyOffset = permission.allow ? 0 : 1;
|
|
@@ -116,6 +116,14 @@ const QUEUE_OPERATIONS = [['read', 'Read the current Agent message queue'], ['cl
|
|
|
116
116
|
*/
|
|
117
117
|
const OPERATIONS = [
|
|
118
118
|
// ── Model Operations ──
|
|
119
|
+
{
|
|
120
|
+
id: 'baseagent.models.list',
|
|
121
|
+
category: 'read',
|
|
122
|
+
dangerous: false,
|
|
123
|
+
defaultScopes: ['process'],
|
|
124
|
+
description: '查询主机默认 Baseagent 模型目录',
|
|
125
|
+
sources: ['menu', 'ecweb', 'control'],
|
|
126
|
+
},
|
|
119
127
|
{
|
|
120
128
|
id: 'model.list',
|
|
121
129
|
category: 'read',
|
|
@@ -917,7 +925,7 @@ const OPERATIONS = [
|
|
|
917
925
|
category: 'read',
|
|
918
926
|
dangerous: false,
|
|
919
927
|
defaultScopes: ['relation', 'agent'],
|
|
920
|
-
description: 'Look up
|
|
928
|
+
description: 'Look up a public AID record',
|
|
921
929
|
sources: ['agent-tool'],
|
|
922
930
|
},
|
|
923
931
|
{
|
|
@@ -925,7 +933,7 @@ const OPERATIONS = [
|
|
|
925
933
|
category: 'read',
|
|
926
934
|
dangerous: false,
|
|
927
935
|
defaultScopes: ['relation', 'agent'],
|
|
928
|
-
description: 'Read and verify
|
|
936
|
+
description: 'Read and verify a public Agent agent.md',
|
|
929
937
|
sources: ['agent-tool'],
|
|
930
938
|
},
|
|
931
939
|
{
|
|
@@ -2,8 +2,9 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { resolvePaths } from '../../paths.js';
|
|
5
|
-
export const REMOTE_ACCESS_TOKEN_TTL_MS =
|
|
6
|
-
|
|
5
|
+
export const REMOTE_ACCESS_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
6
|
+
// 与 owner 绑定二维码(BindService)的默认有效期保持一致:10 分钟。
|
|
7
|
+
export const REMOTE_ACCESS_PAIRING_TTL_MS = 10 * 60 * 1000;
|
|
7
8
|
export function remoteAccessTokenStorePath() {
|
|
8
9
|
return path.join(resolvePaths().instanceDir, 'control-access-tokens.json');
|
|
9
10
|
}
|
|
@@ -20,16 +20,16 @@ export class AgentLoader {
|
|
|
20
20
|
* Iterate over all EvolAgents in the registry × all registered plugins.
|
|
21
21
|
* Each successful (agent, plugin) pair yields one runner instance.
|
|
22
22
|
*/
|
|
23
|
-
createAll(registry, callbacks, creationErrors) {
|
|
23
|
+
async createAll(registry, callbacks, creationErrors) {
|
|
24
24
|
const instances = [];
|
|
25
25
|
const allAgents = registry.runnableAgents();
|
|
26
26
|
for (const agent of allAgents) {
|
|
27
|
-
instances.push(...this.createForAgent(agent, callbacks, creationErrors));
|
|
27
|
+
instances.push(...await this.createForAgent(agent, callbacks, creationErrors));
|
|
28
28
|
}
|
|
29
29
|
return instances;
|
|
30
30
|
}
|
|
31
31
|
/** Create runners for one EvolAgent, used by runtime hot-load. */
|
|
32
|
-
createForAgent(agent, callbacks, creationErrors) {
|
|
32
|
+
async createForAgent(agent, callbacks, creationErrors) {
|
|
33
33
|
const instances = [];
|
|
34
34
|
for (const [pluginName, plugin] of this.plugins) {
|
|
35
35
|
if (!plugin.isEnabled(agent)) {
|
|
@@ -37,7 +37,7 @@ export class AgentLoader {
|
|
|
37
37
|
continue;
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
|
-
const instance = plugin.createAgent(agent, callbacks);
|
|
40
|
+
const instance = await plugin.createAgent(agent, callbacks);
|
|
41
41
|
if (!instance) {
|
|
42
42
|
logger.debug(`Plugin '${pluginName}' returned null for agent '${agent.name}', skipping`);
|
|
43
43
|
continue;
|
|
@@ -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 } };
|