evolcore 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +583 -516
- package/LICENSE +21 -21
- package/README.md +455 -453
- package/bin/codex-managed-acl.ps1 +160 -155
- package/bin/codex-managed-hook.mjs +209 -92
- package/bin/ec-safe-output.js +327 -245
- package/bin/ec.js +29 -29
- package/bin/install-codex-managed-hooks.mjs +555 -544
- package/dist/agents/baseagent-availability.js +4 -4
- package/dist/agents/baseagent.js +29 -1
- package/dist/agents/claude-runner.js +152 -14
- package/dist/agents/codex/app-server-client.js +18 -8
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +215 -0
- package/dist/agents/codex-runner.js +445 -63
- package/dist/agents/ecagent-runner.js +31 -16
- package/dist/agents/gemini-runner.js +82 -4
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/agentmd.js +56 -4
- package/dist/aun/aid/index.js +1 -0
- package/dist/aun/aid/managed-operation.js +80 -6
- package/dist/aun/aid/store.js +41 -0
- package/dist/aun/msg/managed-operation.js +6 -1
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +4 -5
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +147 -76
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +214 -89
- package/dist/cli/agent-create.js +576 -0
- package/dist/cli/agent.js +129 -676
- package/dist/cli/aun-commands.js +419 -313
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +20 -20
- package/dist/cli/contact.js +10 -10
- package/dist/cli/ctl-command.js +22 -20
- package/dist/cli/daemon-commands.js +163 -32
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +7 -7
- package/dist/cli/index.js +79 -79
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +11 -11
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +462 -38
- package/dist/cli/stats.js +161 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +3 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +20 -3
- package/dist/config/resolved-config-op.js +39 -1
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +72 -19
- package/dist/core/auth/operation-authorizer.js +125 -7
- package/dist/core/auth/operation-catalog.js +26 -2
- package/dist/core/auth/remote-access-store.js +3 -2
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +60 -26
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +173 -36
- package/dist/core/command/menu-catalog.js +4 -1
- package/dist/core/command/menu-handler.js +315 -75
- package/dist/core/command/menu-protocol.js +7 -2
- package/dist/core/command/slash-gate.js +1 -3
- package/dist/core/command/slash-handler.js +166 -57
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +85 -27
- package/dist/core/model/catalog-service.js +3 -1
- package/dist/core/model/model-catalog.js +10 -0
- package/dist/core/model/model-permission.js +4 -1
- package/dist/core/permission/approval-gateway.js +169 -33
- package/dist/core/permission/tool-policy.js +56 -10
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +327 -134
- package/dist/ipc.js +68 -21
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +11 -4
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +24 -1
- package/dist/utils/welcome.js +22 -22
- package/dist/utils/windows-autostart.js +1 -1
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +365 -364
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +71 -69
- package/kits/docs/evolcore/agent-create.md +170 -156
- package/kits/docs/evolcore/agent.md +83 -73
- package/kits/docs/evolcore/aid.md +64 -62
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +195 -195
- package/kits/docs/evolcore/contact.md +68 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +34 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +165 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +50 -50
- package/kits/docs/evolcore/trigger.md +575 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +270 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +25 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +195 -183
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +39 -37
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +427 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +201 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +96 -82
- package/kits/templates/roles/member.json +97 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +86 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +38 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +98 -94
- package/AGENTS.md +0 -62
- package/dist/utils/codex-cli.js +0 -43
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
package/dist/cli/agent.js
CHANGED
|
@@ -1,32 +1,19 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { ConfigTarget, write as cfgWrite, ensureFile as cfgEnsure, read as cfgRead, initConfigManager } from '../config/config-manager.js';
|
|
3
|
+
import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, agentPersonalDir, aunPath as defaultAunPath } from '../paths.js';
|
|
4
|
+
import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
|
|
5
|
+
import { initConfigManager } from '../config/config-manager.js';
|
|
7
6
|
import { resolveConfigCommand } from '../config/resolved-config-op.js';
|
|
8
7
|
import { executeResolvedConfigCommand } from '../config/config-operation-service.js';
|
|
9
|
-
import { ipcQuery } from '../ipc.js';
|
|
8
|
+
import { ipcQuery, IpcTransportError } from '../ipc.js';
|
|
10
9
|
import { CONFIG_SCHEMA_VERSION } from '../types.js';
|
|
11
10
|
import { ensureInitialRoleRegistry } from '../config/role-service.js';
|
|
12
|
-
import { isValidChannelName } from '../core/channel-loader.js';
|
|
13
|
-
import { commandExists } from '../utils/cross-platform.js';
|
|
14
|
-
import { getCodexAppServerAvailability, isCodexAppServerAvailable } from '../agents/codex-runner.js';
|
|
15
|
-
import { resolveEcagentConfig } from '../agents/baseagent.js';
|
|
16
11
|
import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/project-path.js';
|
|
12
|
+
import { isValidChannelName } from '../core/channel-loader.js';
|
|
17
13
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
|
|
18
14
|
import { readTaskRuntimeContextFromEnv } from './task-context.js';
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
function withStaticOwner(config, owner) {
|
|
22
|
-
const value = owner?.trim();
|
|
23
|
-
if (!value)
|
|
24
|
-
return config;
|
|
25
|
-
const owners = config.owners ?? [];
|
|
26
|
-
if (owners.includes(value))
|
|
27
|
-
return config;
|
|
28
|
-
return { ...config, owners: [...owners, value] };
|
|
29
|
-
}
|
|
15
|
+
import { isHClassPath, isLClassPath, isSameOrDescendant } from '../core/permission/protected-paths.js';
|
|
16
|
+
export { agentCreateInteractive, agentCreateNonInteractive, parseAgentOwnerArgs, resolveAgentCreatePlan, validateAgentCreatePlan, validateAgentOwnerOptions, } from './agent-create.js';
|
|
30
17
|
/** Route agent lifecycle commands from a managed task through daemon IPC. */
|
|
31
18
|
export async function runManagedAgentOperation(argv) {
|
|
32
19
|
const sessionId = process.env.EVOLCORE_SESSION_ID;
|
|
@@ -53,174 +40,32 @@ export async function runManagedAgentOperation(argv) {
|
|
|
53
40
|
return { ok: false, code: 'DAEMON_UNAVAILABLE', error: error instanceof Error ? error.message : String(error) };
|
|
54
41
|
}
|
|
55
42
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const AUTO_BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
|
|
59
|
-
function isBaseagentAvailable(baseagent) {
|
|
60
|
-
if (baseagent === 'codex')
|
|
61
|
-
return isCodexAppServerAvailable();
|
|
62
|
-
if (baseagent === 'ecagent')
|
|
63
|
-
return true;
|
|
64
|
-
return commandExists(baseagent);
|
|
65
|
-
}
|
|
66
|
-
function detectAvailableBaseagents() {
|
|
67
|
-
return BASEAGENT_CANDIDATES.filter(isBaseagentAvailable);
|
|
68
|
-
}
|
|
69
|
-
function pickDefaultBaseagent(available) {
|
|
70
|
-
return AUTO_BASEAGENT_CANDIDATES.find(candidate => available.includes(candidate)) ?? null;
|
|
71
|
-
}
|
|
72
|
-
function readProjectMetadata(project) {
|
|
73
|
-
try {
|
|
74
|
-
const packageJsonPath = path.join(project, 'package.json');
|
|
75
|
-
if (!fs.existsSync(packageJsonPath))
|
|
76
|
-
return {};
|
|
77
|
-
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
78
|
-
return {
|
|
79
|
-
name: typeof parsed.name === 'string' ? parsed.name.trim() : undefined,
|
|
80
|
-
description: typeof parsed.description === 'string' ? parsed.description.trim() : undefined,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return {};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
function projectSlug(project) {
|
|
88
|
-
const basename = path.basename(project) || 'agent';
|
|
89
|
-
const slug = basename
|
|
90
|
-
.normalize('NFKD')
|
|
91
|
-
.replace(/[^a-zA-Z0-9-]+/g, '-')
|
|
92
|
-
.replace(/^-+|-+$/g, '')
|
|
93
|
-
.toLowerCase()
|
|
94
|
-
.slice(0, 63)
|
|
95
|
-
.replace(/-+$/g, '');
|
|
96
|
-
return slug || 'agent';
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Resolve non-interactive creation defaults without performing any network or
|
|
100
|
-
* filesystem mutation. The CLI uses this for both execution and --dry-run.
|
|
101
|
-
*/
|
|
102
|
-
export function resolveAgentCreatePlan(options) {
|
|
103
|
-
const aid = options.aid.trim();
|
|
104
|
-
const project = path.resolve(options.project || process.cwd());
|
|
105
|
-
const metadata = readProjectMetadata(project);
|
|
106
|
-
const defaults = loadDefaults();
|
|
107
|
-
const daemon = loadDaemonConfig();
|
|
108
|
-
const configuredBaseagent = defaults?.active_baseagent;
|
|
109
|
-
const available = detectAvailableBaseagents();
|
|
110
|
-
const defaultBaseagent = configuredBaseagent && available.includes(configuredBaseagent)
|
|
111
|
-
? configuredBaseagent
|
|
112
|
-
: pickDefaultBaseagent(available) ?? undefined;
|
|
113
|
-
const baseagent = options.baseagent || defaultBaseagent;
|
|
114
|
-
const baseagentSource = options.baseagent
|
|
115
|
-
? 'argument'
|
|
116
|
-
: configuredBaseagent && baseagent === configuredBaseagent
|
|
117
|
-
? 'defaults'
|
|
118
|
-
: 'detected';
|
|
119
|
-
const owner = options.owner?.trim() || daemon.owners?.[0];
|
|
120
|
-
const ownerSource = options.owner?.trim() ? 'argument' : owner ? 'daemon' : 'none';
|
|
121
|
-
const name = options.name?.trim() || metadata.name || projectSlug(project);
|
|
122
|
-
const description = options.description?.trim() || metadata.description || DEFAULT_AGENT_DESCRIPTION;
|
|
123
|
-
const configExists = isValidAid(aid)
|
|
124
|
-
&& fs.existsSync(path.join(resolvePaths().agentsDir, aid, 'config.json'));
|
|
125
|
-
return {
|
|
126
|
-
aid,
|
|
127
|
-
project,
|
|
128
|
-
baseagent,
|
|
129
|
-
owner,
|
|
130
|
-
name,
|
|
131
|
-
description,
|
|
132
|
-
force: options.force === true,
|
|
133
|
-
configExists,
|
|
134
|
-
baseagentSource,
|
|
135
|
-
ownerSource,
|
|
136
|
-
nameSource: options.name?.trim() ? 'argument' : metadata.name ? 'package' : 'directory',
|
|
137
|
-
descriptionSource: options.description?.trim() ? 'argument' : metadata.description ? 'package' : 'default',
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
export function validateAgentCreatePlan(plan) {
|
|
141
|
-
if (!isValidAid(plan.aid)) {
|
|
142
|
-
return `Invalid AID "${plan.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`;
|
|
143
|
-
}
|
|
144
|
-
if (plan.configExists && !plan.force) {
|
|
145
|
-
return `Agent "${plan.aid}" already exists (use --force to overwrite)`;
|
|
146
|
-
}
|
|
147
|
-
if (!plan.baseagent) {
|
|
148
|
-
return 'No usable baseagent detected. Pass --baseagent or configure a baseagent in agents/defaults.json.';
|
|
149
|
-
}
|
|
150
|
-
if (!BASEAGENT_CANDIDATES.includes(plan.baseagent)) {
|
|
151
|
-
return `Invalid baseagent: ${plan.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`;
|
|
152
|
-
}
|
|
153
|
-
if (!isBaseagentAvailable(plan.baseagent)) {
|
|
154
|
-
return `${plan.baseagent} is not available in the current environment`;
|
|
155
|
-
}
|
|
156
|
-
if (plan.baseagent === 'ecagent') {
|
|
157
|
-
const reason = ecagentUnavailableReason();
|
|
158
|
-
if (reason)
|
|
159
|
-
return reason;
|
|
160
|
-
}
|
|
161
|
-
if (plan.owner && !isValidAid(plan.owner)) {
|
|
162
|
-
return `Invalid owner: ${plan.owner}`;
|
|
163
|
-
}
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
function ecagentUnavailableReason() {
|
|
167
|
-
try {
|
|
168
|
-
resolveEcagentConfig({ agents: { ecagent: {} } });
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
return error instanceof Error ? error.message : String(error);
|
|
173
|
-
}
|
|
43
|
+
function getAgentMdPath(aid) {
|
|
44
|
+
return getAgentMdPathFromPaths(aid);
|
|
174
45
|
}
|
|
175
|
-
function
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
const DEFAULT_CHATMODE = { private: 'interactive', group: 'proactive', nothuman: 'proactive' };
|
|
179
|
-
/**
|
|
180
|
-
* 写入新 agent 的初始配置(所有字段统一在 config.json)。
|
|
181
|
-
* mentionMode 不写初始值,继承 schema 出厂默认(disabled)。
|
|
182
|
-
*/
|
|
183
|
-
function saveInitialBehavior(aid, baseagent) {
|
|
184
|
-
const behavior = {
|
|
185
|
-
active_baseagent: baseagent,
|
|
186
|
-
baseagents: buildBaseagentsBlock(baseagent),
|
|
187
|
-
chatmode: { ...DEFAULT_CHATMODE },
|
|
188
|
-
};
|
|
189
|
-
const sel = { self: aid };
|
|
190
|
-
const existingBehavior = cfgRead(ConfigTarget.Agent, sel) || {};
|
|
191
|
-
cfgEnsure(ConfigTarget.Agent, sel);
|
|
192
|
-
cfgWrite(ConfigTarget.Agent, { ...existingBehavior, ...behavior }, sel);
|
|
46
|
+
function toPosix(value) {
|
|
47
|
+
return value ? value.replace(/\\/g, '/') : null;
|
|
193
48
|
}
|
|
194
49
|
function readAgentMdIdentity(aid) {
|
|
195
|
-
const
|
|
50
|
+
const file = getAgentMdPath(aid);
|
|
196
51
|
try {
|
|
197
|
-
if (!fs.existsSync(
|
|
52
|
+
if (!fs.existsSync(file))
|
|
198
53
|
return { name: null, description: null };
|
|
199
|
-
const content = fs.readFileSync(
|
|
200
|
-
const
|
|
201
|
-
if (!
|
|
54
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
55
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
|
|
56
|
+
if (!frontmatter)
|
|
202
57
|
return { name: null, description: null };
|
|
203
|
-
const
|
|
204
|
-
const nameMatch = fm.match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
205
|
-
const descMatch = fm.match(/^description:\s*["']?(.+?)["']?\s*$/m);
|
|
58
|
+
const body = frontmatter[1];
|
|
206
59
|
return {
|
|
207
|
-
name:
|
|
208
|
-
description:
|
|
60
|
+
name: body.match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim() || null,
|
|
61
|
+
description: body.match(/^description:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim() || null,
|
|
209
62
|
};
|
|
210
63
|
}
|
|
211
64
|
catch {
|
|
212
65
|
return { name: null, description: null };
|
|
213
66
|
}
|
|
214
67
|
}
|
|
215
|
-
|
|
216
|
-
return getAgentMdPathFromPaths(aid);
|
|
217
|
-
}
|
|
218
|
-
/** Normalize path separators to forward slashes for cross-platform consistent display. */
|
|
219
|
-
function toPosix(p) {
|
|
220
|
-
if (!p)
|
|
221
|
-
return null;
|
|
222
|
-
return p.replace(/\\/g, '/');
|
|
223
|
-
}
|
|
68
|
+
// ==================== Helpers ====================
|
|
224
69
|
// ==================== agentList ====================
|
|
225
70
|
function toAgentListItem(info, stale = false) {
|
|
226
71
|
const storedStatus = info.status || 'unknown';
|
|
@@ -360,499 +205,6 @@ export async function agentShow(aid) {
|
|
|
360
205
|
...(stale ? { stale: true } : {}),
|
|
361
206
|
};
|
|
362
207
|
}
|
|
363
|
-
export async function agentCreateInteractive(opts = {}) {
|
|
364
|
-
const p = resolvePaths();
|
|
365
|
-
const ownRl = !opts.rl;
|
|
366
|
-
const rl = opts.rl ?? readline.createInterface({
|
|
367
|
-
input: opts.stdin || process.stdin,
|
|
368
|
-
output: opts.stdout || process.stdout,
|
|
369
|
-
});
|
|
370
|
-
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
371
|
-
try {
|
|
372
|
-
const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
|
|
373
|
-
const aidPrompt = opts.suggestedName
|
|
374
|
-
? `AID [${opts.suggestedName}]: `
|
|
375
|
-
: 'AID (e.g. mybot.agentid.pub): ';
|
|
376
|
-
let aid = '';
|
|
377
|
-
while (!aid) {
|
|
378
|
-
const aidInput = (await ask(aidPrompt)).trim();
|
|
379
|
-
const candidate = aidInput || opts.suggestedName;
|
|
380
|
-
if (!candidate) {
|
|
381
|
-
console.log(' ⚠ AID is required.');
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
if (!isValidAid(candidate)) {
|
|
385
|
-
console.log(` ⚠ Invalid AID "${candidate}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
aid = candidate;
|
|
389
|
-
}
|
|
390
|
-
const agentDirPath = path.join(p.agentsDir, aid);
|
|
391
|
-
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
392
|
-
if (configExists) {
|
|
393
|
-
const ans = (await ask(`Agent "${aid}" already exists. Overwrite config.json? (AID 与 agent.md 保留) [y/N]: `)).trim().toLowerCase();
|
|
394
|
-
if (ans !== 'y' && ans !== 'yes') {
|
|
395
|
-
return { ok: false, error: 'Aborted.' };
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
console.log(`\nCreating agent: ${aid}\n`);
|
|
399
|
-
let aidCreated = false;
|
|
400
|
-
try {
|
|
401
|
-
const result = await aidCreate(aid);
|
|
402
|
-
try {
|
|
403
|
-
await result.client.close();
|
|
404
|
-
}
|
|
405
|
-
catch { /* ignore */ }
|
|
406
|
-
aidCreated = !result.alreadyExisted;
|
|
407
|
-
console.log(` ✓ AID ${result.alreadyExisted ? 'reused' : 'created'}: ${aid}`);
|
|
408
|
-
}
|
|
409
|
-
catch (e) {
|
|
410
|
-
return { ok: false, error: `AID creation failed: ${e?.message || e}` };
|
|
411
|
-
}
|
|
412
|
-
// Project path
|
|
413
|
-
let suggestedProjectPath = '';
|
|
414
|
-
try {
|
|
415
|
-
const defaults = loadDefaults();
|
|
416
|
-
const rootPath = agentProjectRootFromDefaults(defaults, p.root);
|
|
417
|
-
suggestedProjectPath = deriveAgentProjectPath(rootPath, aid);
|
|
418
|
-
}
|
|
419
|
-
catch {
|
|
420
|
-
suggestedProjectPath = deriveAgentProjectPath(agentProjectRootFromDefaults(null, p.root), aid);
|
|
421
|
-
}
|
|
422
|
-
const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
|
|
423
|
-
const projectPath = projectInput || suggestedProjectPath;
|
|
424
|
-
if (!path.isAbsolute(projectPath)) {
|
|
425
|
-
return { ok: false, error: 'Project path must be an absolute path.' };
|
|
426
|
-
}
|
|
427
|
-
if (!fs.existsSync(projectPath)) {
|
|
428
|
-
const create = (await ask(`Project path does not exist. Create? [Y/n]: `)).trim().toLowerCase();
|
|
429
|
-
if (create === '' || create === 'y' || create === 'yes') {
|
|
430
|
-
try {
|
|
431
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
432
|
-
}
|
|
433
|
-
catch (e) {
|
|
434
|
-
return { ok: false, error: `Failed to create project path "${projectPath}": ${e?.message || e}` };
|
|
435
|
-
}
|
|
436
|
-
console.log(` ✓ Created ${projectPath}`);
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
return { ok: false, error: 'Aborted.' };
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
// Baseagent
|
|
443
|
-
const available = detectAvailableBaseagents();
|
|
444
|
-
if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason())) {
|
|
445
|
-
return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server, or configure bundled ecagent.` };
|
|
446
|
-
}
|
|
447
|
-
const defaultBa = pickDefaultBaseagent(available);
|
|
448
|
-
let baseagent;
|
|
449
|
-
if (available.length === 1 && defaultBa) {
|
|
450
|
-
console.log(` Baseagent: ${defaultBa}`);
|
|
451
|
-
baseagent = defaultBa;
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
let chosen = null;
|
|
455
|
-
while (chosen === null) {
|
|
456
|
-
const defaultHint = defaultBa ? ` [${defaultBa}]` : '';
|
|
457
|
-
const input = (await ask(`Baseagent (${available.join('/')})${defaultHint}: `)).trim() || defaultBa;
|
|
458
|
-
if (!input) {
|
|
459
|
-
console.log(' ecagent is optional; enter ecagent to select it explicitly.');
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
if (!BASEAGENT_CANDIDATES.includes(input)) {
|
|
463
|
-
console.log(` Invalid choice. Options: ${BASEAGENT_CANDIDATES.join('/')}`);
|
|
464
|
-
continue;
|
|
465
|
-
}
|
|
466
|
-
if (!available.includes(input)) {
|
|
467
|
-
console.log(` ${input} is not available in the current environment. Available: ${available.join('/')}`);
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
if (input === 'ecagent') {
|
|
471
|
-
const reason = ecagentUnavailableReason();
|
|
472
|
-
if (reason) {
|
|
473
|
-
console.log(` ecagent is unavailable: ${reason}`);
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
chosen = input;
|
|
478
|
-
}
|
|
479
|
-
baseagent = chosen;
|
|
480
|
-
}
|
|
481
|
-
// Owner: only prompt if daemon (daemon.json) has owners configured.
|
|
482
|
-
// If daemon has no owner, agent owner is meaningless and should be skipped entirely.
|
|
483
|
-
const { loadDaemonConfig } = await import('../config-store.js');
|
|
484
|
-
const daemonCfg = loadDaemonConfig();
|
|
485
|
-
const daemonHasOwner = daemonCfg.owners && daemonCfg.owners.length > 0;
|
|
486
|
-
let owner;
|
|
487
|
-
let skipQrBinding = false;
|
|
488
|
-
if (daemonHasOwner) {
|
|
489
|
-
const defaultOwner = daemonCfg.owners[0];
|
|
490
|
-
while (true) {
|
|
491
|
-
const prompt = `Owner AID [${defaultOwner}]: `;
|
|
492
|
-
const ownerInput = (await ask(prompt)).trim();
|
|
493
|
-
// User pressed Enter - use default owner
|
|
494
|
-
if (!ownerInput) {
|
|
495
|
-
owner = defaultOwner;
|
|
496
|
-
break;
|
|
497
|
-
}
|
|
498
|
-
// User entered "new" or "qr" - trigger QR binding flow
|
|
499
|
-
if (ownerInput.toLowerCase() === 'new' || ownerInput.toLowerCase() === 'qr') {
|
|
500
|
-
owner = undefined;
|
|
501
|
-
skipQrBinding = false;
|
|
502
|
-
break;
|
|
503
|
-
}
|
|
504
|
-
// User entered a specific AID - validate and use it
|
|
505
|
-
if (!isValidAid(ownerInput)) {
|
|
506
|
-
console.log(` ⚠ Invalid Owner AID "${ownerInput}": must be a valid multi-level domain (e.g. alice.agentid.pub)`);
|
|
507
|
-
console.log(` 提示: 直接回车使用 ${defaultOwner},输入 "new" 扫码绑定新 AID`);
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
owner = ownerInput;
|
|
511
|
-
skipQrBinding = true; // Skip QR if user manually entered an AID
|
|
512
|
-
break;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
// Name + description for agent.md
|
|
516
|
-
const defaultName = aid.split('.')[0];
|
|
517
|
-
const agentName = (await ask(`Display name [${defaultName}]: `)).trim() || defaultName;
|
|
518
|
-
const agentDescription = (await ask('Description (optional): ')).trim() || '';
|
|
519
|
-
if (ownRl)
|
|
520
|
-
rl.close();
|
|
521
|
-
const agentConfig = withStaticOwner({
|
|
522
|
-
$schema_version: CONFIG_SCHEMA_VERSION,
|
|
523
|
-
aid,
|
|
524
|
-
enabled: true,
|
|
525
|
-
lifecycle: 'created',
|
|
526
|
-
channels: [],
|
|
527
|
-
projects: { defaultPath: projectPath },
|
|
528
|
-
}, owner);
|
|
529
|
-
saveAgent(agentConfig);
|
|
530
|
-
ensureInitialRoleRegistry(aid);
|
|
531
|
-
saveInitialBehavior(aid, baseagent);
|
|
532
|
-
ensureAgentDirSkeleton(aid);
|
|
533
|
-
// Generate and upload agent.md
|
|
534
|
-
let agentmdUploaded = false;
|
|
535
|
-
try {
|
|
536
|
-
const { buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
|
|
537
|
-
const content = buildInitialAgentMd({ aid, name: agentName, description: agentDescription });
|
|
538
|
-
const aunPath = process.env.AUN_HOME || defaultAunPath();
|
|
539
|
-
// agentmdPut 会写本地文件到 agentMdPath(aid) 并调用 publishAgentMd
|
|
540
|
-
// Upload with retry (3 attempts, 2s delay between retries)
|
|
541
|
-
const MAX_ATTEMPTS = 3;
|
|
542
|
-
const RETRY_DELAY_MS = 2000;
|
|
543
|
-
let lastError;
|
|
544
|
-
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
545
|
-
try {
|
|
546
|
-
if (attempt > 1) {
|
|
547
|
-
process.stdout.write(` ↻ agent.md 上传重试 (${attempt}/${MAX_ATTEMPTS})...\n`);
|
|
548
|
-
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
549
|
-
}
|
|
550
|
-
await agentmdPut(content, { aid, aunPath });
|
|
551
|
-
agentmdUploaded = true;
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
catch (e) {
|
|
555
|
-
lastError = e;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
if (!agentmdUploaded) {
|
|
559
|
-
console.warn(` ⚠ agent.md upload failed: ${lastError?.message || lastError}`);
|
|
560
|
-
console.warn(` → Retry later with: ec aid agentmd put ${aid}`);
|
|
561
|
-
}
|
|
562
|
-
// Yield to allow the SDK WebSocket to fully close before IPC
|
|
563
|
-
await new Promise(r => setTimeout(r, 0));
|
|
564
|
-
}
|
|
565
|
-
catch (e) {
|
|
566
|
-
console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
|
|
567
|
-
}
|
|
568
|
-
// Attempt hot-load via IPC (if daemon is running).
|
|
569
|
-
// Cold-starting a new agent (connecting AUN WebSocket) routinely takes
|
|
570
|
-
// >3s, so use a generous timeout to avoid a false "service not running"
|
|
571
|
-
// report while the daemon actually finishes bringing the agent online.
|
|
572
|
-
let hotLoaded = false;
|
|
573
|
-
let hotLoadError;
|
|
574
|
-
try {
|
|
575
|
-
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid }, 30_000);
|
|
576
|
-
if (ipcResult?.ok) {
|
|
577
|
-
hotLoaded = true;
|
|
578
|
-
}
|
|
579
|
-
else if (ipcResult) {
|
|
580
|
-
hotLoadError = ipcResult.error;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
catch { /* daemon not running */ }
|
|
584
|
-
let ownerBoundAid;
|
|
585
|
-
let ownerBindSkipped = false;
|
|
586
|
-
let ownerBindInterrupted = false;
|
|
587
|
-
// Only prompt for agent owner binding if:
|
|
588
|
-
// 1. User didn't manually input owner during setup (owner is undefined)
|
|
589
|
-
// 2. User didn't skip QR binding explicitly (skipQrBinding is false)
|
|
590
|
-
// 3. We're in an interactive terminal
|
|
591
|
-
// 4. The daemon (daemon.json) has at least one owner configured
|
|
592
|
-
if (!owner && !skipQrBinding && process.stdin.isTTY && daemonHasOwner) {
|
|
593
|
-
console.log('\n📡 Agent owner 绑定 — 请用 evol app 扫描二维码,10 分钟内有效\n');
|
|
594
|
-
try {
|
|
595
|
-
const { runAgentOwnerQrBindFlow } = await import('./init-channel.js');
|
|
596
|
-
const bound = await runAgentOwnerQrBindFlow(aid, agentName);
|
|
597
|
-
if (bound.action === 'bound') {
|
|
598
|
-
ownerBoundAid = bound.boundAid;
|
|
599
|
-
console.log(` ✓ 已配置 agent owner: ${bound.boundAid}`);
|
|
600
|
-
}
|
|
601
|
-
else if (bound.action === 'manual') {
|
|
602
|
-
ownerBindSkipped = true;
|
|
603
|
-
ownerBoundAid = await promptAgentOwnerManually(aid);
|
|
604
|
-
}
|
|
605
|
-
else {
|
|
606
|
-
ownerBindInterrupted = true;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
catch (e) {
|
|
610
|
-
ownerBindSkipped = true;
|
|
611
|
-
console.log(` ⚠ 扫码绑定不可用: ${e?.message || e}`);
|
|
612
|
-
ownerBoundAid = await promptAgentOwnerManually(aid);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
return {
|
|
616
|
-
ok: true,
|
|
617
|
-
aid,
|
|
618
|
-
configPath: toPosix(path.join(agentDirPath, 'config.json')),
|
|
619
|
-
aidCreated,
|
|
620
|
-
agentmdUploaded,
|
|
621
|
-
hotLoaded,
|
|
622
|
-
hotLoadError,
|
|
623
|
-
ownerBoundAid,
|
|
624
|
-
ownerBindSkipped,
|
|
625
|
-
ownerBindInterrupted,
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
finally {
|
|
629
|
-
if (ownRl) {
|
|
630
|
-
try {
|
|
631
|
-
rl.close();
|
|
632
|
-
}
|
|
633
|
-
catch { /* ignore */ }
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
async function promptAgentOwnerManually(aid) {
|
|
638
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
639
|
-
try {
|
|
640
|
-
while (true) {
|
|
641
|
-
const raw = await new Promise(resolve => rl.question('\n扫码未完成。手动输入 agent owner AID(留空跳过): ', resolve));
|
|
642
|
-
const owner = raw.trim();
|
|
643
|
-
if (!owner) {
|
|
644
|
-
console.log(' 已跳过 agent owner 配置');
|
|
645
|
-
return undefined;
|
|
646
|
-
}
|
|
647
|
-
const { isValidAid } = await import('../aun/aid/index.js');
|
|
648
|
-
if (!isValidAid(owner)) {
|
|
649
|
-
console.log(' ⚠ Owner AID 格式无效(需合法多级域名,如 alice.agentid.pub)');
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
const agent = loadAgent(aid);
|
|
653
|
-
if (!agent) {
|
|
654
|
-
console.log(` ⚠ 无法加载 agent 配置: ${aid}`);
|
|
655
|
-
return undefined;
|
|
656
|
-
}
|
|
657
|
-
saveAgent(withStaticOwner(agent, owner));
|
|
658
|
-
try {
|
|
659
|
-
const result = await ipcQuery(resolvePaths().socket, { type: 'evolagent.reload', name: aid }, 30_000);
|
|
660
|
-
if (result?.ok)
|
|
661
|
-
console.log(' ✓ agent owner 已热重载');
|
|
662
|
-
}
|
|
663
|
-
catch { /* daemon not running */ }
|
|
664
|
-
console.log(` ✓ 已配置 agent owner: ${owner}`);
|
|
665
|
-
return owner;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
finally {
|
|
669
|
-
rl.close();
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
export async function agentCreateNonInteractive(opts, runtime = {}) {
|
|
673
|
-
const p = resolvePaths();
|
|
674
|
-
const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
|
|
675
|
-
opts.onPhase?.('validating', 'begin');
|
|
676
|
-
/** 校验失败:透出 failed 进度并返回原结构(控制流不变)。 */
|
|
677
|
-
const failValidating = (error) => {
|
|
678
|
-
opts.onPhase?.('validating', 'failed', error);
|
|
679
|
-
return { ok: false, error };
|
|
680
|
-
};
|
|
681
|
-
if (!isValidAid(opts.aid)) {
|
|
682
|
-
return failValidating(`Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
|
|
683
|
-
}
|
|
684
|
-
const agentDirPath = path.join(p.agentsDir, opts.aid);
|
|
685
|
-
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
686
|
-
if (configExists && !opts.force) {
|
|
687
|
-
return failValidating(`Agent "${opts.aid}" already exists: ${agentDirPath}/config.json (use --force to overwrite)`);
|
|
688
|
-
}
|
|
689
|
-
// Baseagent
|
|
690
|
-
const available = detectAvailableBaseagents();
|
|
691
|
-
if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason())) {
|
|
692
|
-
return failValidating(`No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server, or configure bundled ecagent.`);
|
|
693
|
-
}
|
|
694
|
-
let baseagent;
|
|
695
|
-
if (opts.baseagent) {
|
|
696
|
-
if (!BASEAGENT_CANDIDATES.includes(opts.baseagent)) {
|
|
697
|
-
return failValidating(`Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`);
|
|
698
|
-
}
|
|
699
|
-
if (!available.includes(opts.baseagent)) {
|
|
700
|
-
const reason = opts.baseagent === 'codex'
|
|
701
|
-
? getCodexAppServerAvailability().reason
|
|
702
|
-
: undefined;
|
|
703
|
-
return failValidating(reason || `${opts.baseagent} is not available in the current environment (available: ${available.join('/')})`);
|
|
704
|
-
}
|
|
705
|
-
if (opts.baseagent === 'ecagent') {
|
|
706
|
-
const reason = ecagentUnavailableReason();
|
|
707
|
-
if (reason)
|
|
708
|
-
return failValidating(reason);
|
|
709
|
-
}
|
|
710
|
-
baseagent = opts.baseagent;
|
|
711
|
-
}
|
|
712
|
-
else {
|
|
713
|
-
const defaultBaseagent = pickDefaultBaseagent(available);
|
|
714
|
-
if (!defaultBaseagent)
|
|
715
|
-
return failValidating('ecagent is optional; pass --baseagent ecagent after configuring its API key');
|
|
716
|
-
baseagent = defaultBaseagent;
|
|
717
|
-
}
|
|
718
|
-
if (!path.isAbsolute(opts.project)) {
|
|
719
|
-
return failValidating(`--project must be absolute: ${opts.project}`);
|
|
720
|
-
}
|
|
721
|
-
if (!fs.existsSync(opts.project)) {
|
|
722
|
-
try {
|
|
723
|
-
fs.mkdirSync(opts.project, { recursive: true });
|
|
724
|
-
}
|
|
725
|
-
catch (e) {
|
|
726
|
-
return failValidating(`Failed to create ${opts.project}: ${e?.message || e}`);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
if (opts.owner && !isValidAid(opts.owner)) {
|
|
730
|
-
return failValidating(`Invalid owner: ${opts.owner}`);
|
|
731
|
-
}
|
|
732
|
-
opts.onPhase?.('validating', 'done');
|
|
733
|
-
// Register AID
|
|
734
|
-
opts.onPhase?.('registering_aid', 'begin');
|
|
735
|
-
let aidCreated = false;
|
|
736
|
-
try {
|
|
737
|
-
const result = await aidCreate(opts.aid);
|
|
738
|
-
try {
|
|
739
|
-
await result.client.close();
|
|
740
|
-
}
|
|
741
|
-
catch { /* ignore */ }
|
|
742
|
-
aidCreated = !result.alreadyExisted;
|
|
743
|
-
opts.onPhase?.('registering_aid', 'done', aidCreated ? 'created' : 'existed');
|
|
744
|
-
}
|
|
745
|
-
catch (e) {
|
|
746
|
-
const error = `AID creation failed: ${e?.message || e}`;
|
|
747
|
-
opts.onPhase?.('registering_aid', 'failed', error);
|
|
748
|
-
return { ok: false, error };
|
|
749
|
-
}
|
|
750
|
-
// Force 模式下若 agent 已存在,保留生命周期(避免重复 bootstrap/welcome)。
|
|
751
|
-
let preservedLifecycle = 'created';
|
|
752
|
-
if (configExists) {
|
|
753
|
-
try {
|
|
754
|
-
const existing = loadAgent(opts.aid);
|
|
755
|
-
if (existing?.lifecycle)
|
|
756
|
-
preservedLifecycle = existing.lifecycle;
|
|
757
|
-
}
|
|
758
|
-
catch { /* ignore */ }
|
|
759
|
-
}
|
|
760
|
-
const agentConfig = withStaticOwner({
|
|
761
|
-
$schema_version: CONFIG_SCHEMA_VERSION,
|
|
762
|
-
aid: opts.aid,
|
|
763
|
-
enabled: true,
|
|
764
|
-
lifecycle: preservedLifecycle,
|
|
765
|
-
channels: [],
|
|
766
|
-
projects: { defaultPath: opts.project },
|
|
767
|
-
}, opts.owner);
|
|
768
|
-
opts.onPhase?.('config_saved', 'begin');
|
|
769
|
-
saveAgent(agentConfig);
|
|
770
|
-
ensureInitialRoleRegistry(opts.aid);
|
|
771
|
-
saveInitialBehavior(opts.aid, baseagent);
|
|
772
|
-
ensureAgentDirSkeleton(opts.aid);
|
|
773
|
-
opts.onPhase?.('config_saved', 'done');
|
|
774
|
-
// Generate and upload agent.md
|
|
775
|
-
opts.onPhase?.('uploading_agentmd', 'begin');
|
|
776
|
-
let agentmdUploaded = false;
|
|
777
|
-
try {
|
|
778
|
-
const { buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
|
|
779
|
-
const content = buildInitialAgentMd({
|
|
780
|
-
aid: opts.aid,
|
|
781
|
-
name: opts.name,
|
|
782
|
-
description: opts.description,
|
|
783
|
-
});
|
|
784
|
-
const aunPath = process.env.AUN_HOME || defaultAunPath();
|
|
785
|
-
// agentmdPut 会写本地文件到 agentMdPath(aid) 并调用 publishAgentMd
|
|
786
|
-
const MAX_ATTEMPTS = 3;
|
|
787
|
-
const RETRY_DELAY_MS = 2000;
|
|
788
|
-
let lastError;
|
|
789
|
-
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
790
|
-
try {
|
|
791
|
-
if (attempt > 1)
|
|
792
|
-
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
793
|
-
await agentmdPut(content, { aid: opts.aid, aunPath });
|
|
794
|
-
agentmdUploaded = true;
|
|
795
|
-
opts.onPhase?.('uploading_agentmd', 'done');
|
|
796
|
-
break;
|
|
797
|
-
}
|
|
798
|
-
catch (e) {
|
|
799
|
-
lastError = e;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
if (!agentmdUploaded) {
|
|
803
|
-
console.warn(`⚠ agent.md upload failed: ${lastError?.message || lastError}`);
|
|
804
|
-
console.warn(` Retry later with: ec aid agentmd put ${opts.aid}`);
|
|
805
|
-
opts.onPhase?.('uploading_agentmd', 'warn', `upload failed: ${lastError?.message || lastError}`);
|
|
806
|
-
}
|
|
807
|
-
await new Promise(r => setTimeout(r, 0));
|
|
808
|
-
}
|
|
809
|
-
catch (e) {
|
|
810
|
-
console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
|
|
811
|
-
opts.onPhase?.('uploading_agentmd', 'warn', `generation failed: ${e?.message || e}`);
|
|
812
|
-
}
|
|
813
|
-
// Core-owned callers invoke the already-loaded runtime directly. Standalone
|
|
814
|
-
// CLI callers retain the IPC path so this module remains usable without a
|
|
815
|
-
// daemon instance in the current process.
|
|
816
|
-
let hotLoaded = false;
|
|
817
|
-
let hotLoadError;
|
|
818
|
-
opts.onPhase?.('hot_loading', 'begin');
|
|
819
|
-
try {
|
|
820
|
-
if (runtime.hotLoadAgent) {
|
|
821
|
-
await runtime.hotLoadAgent(opts.aid);
|
|
822
|
-
hotLoaded = true;
|
|
823
|
-
opts.onPhase?.('hot_loading', 'done');
|
|
824
|
-
}
|
|
825
|
-
else {
|
|
826
|
-
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid }, 30_000);
|
|
827
|
-
if (ipcResult?.ok) {
|
|
828
|
-
hotLoaded = true;
|
|
829
|
-
opts.onPhase?.('hot_loading', 'done');
|
|
830
|
-
}
|
|
831
|
-
else if (ipcResult) {
|
|
832
|
-
hotLoadError = ipcResult.error;
|
|
833
|
-
opts.onPhase?.('hot_loading', 'warn', hotLoadError);
|
|
834
|
-
}
|
|
835
|
-
else {
|
|
836
|
-
opts.onPhase?.('hot_loading', 'warn', 'daemon not running');
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
catch (error) {
|
|
841
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
842
|
-
hotLoadError = message;
|
|
843
|
-
opts.onPhase?.('hot_loading', 'warn', message);
|
|
844
|
-
}
|
|
845
|
-
return {
|
|
846
|
-
ok: true,
|
|
847
|
-
aid: opts.aid,
|
|
848
|
-
ownerAid: opts.owner,
|
|
849
|
-
configPath: toPosix(path.join(agentDirPath, 'config.json')),
|
|
850
|
-
aidCreated,
|
|
851
|
-
agentmdUploaded,
|
|
852
|
-
hotLoaded,
|
|
853
|
-
hotLoadError,
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
208
|
// ==================== agentSyncAids (deprecated) ====================
|
|
857
209
|
/** @deprecated sync-aids 已废弃,不再从 CLI 调用 */
|
|
858
210
|
export async function agentSyncAids() {
|
|
@@ -930,32 +282,32 @@ export async function agentReload(aid, options = {}) {
|
|
|
930
282
|
}
|
|
931
283
|
// Full resync
|
|
932
284
|
try {
|
|
933
|
-
const result = await ipcQuery(p.socket, { type: 'evolagent.resync' }, AGENT_RESYNC_TIMEOUT_MS);
|
|
285
|
+
const result = await ipcQuery(p.socket, { type: 'evolagent.resync' }, AGENT_RESYNC_TIMEOUT_MS, { throwOnError: true });
|
|
934
286
|
if (result === null) {
|
|
935
|
-
return { ok: false, error: '
|
|
287
|
+
return { ok: false, code: 'IPC_INVALID_RESPONSE', error: 'daemon IPC 未返回有效响应,请检查 ec status' };
|
|
936
288
|
}
|
|
937
289
|
if (result?.ok) {
|
|
938
290
|
return { ok: true, results: result.results || [] };
|
|
939
291
|
}
|
|
940
292
|
return { ok: false, error: result?.error || 'unknown error' };
|
|
941
293
|
}
|
|
942
|
-
catch {
|
|
943
|
-
return { ok: false, error: '
|
|
294
|
+
catch (error) {
|
|
295
|
+
return { ok: false, code: error instanceof IpcTransportError ? `IPC_${error.code}` : 'IPC_FAILED', error: String(error instanceof Error ? error.message : error) };
|
|
944
296
|
}
|
|
945
297
|
}
|
|
946
298
|
// Single agent reload
|
|
947
299
|
try {
|
|
948
|
-
const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid, force: options.force === true }, AGENT_RELOAD_TIMEOUT_MS);
|
|
300
|
+
const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid, force: options.force === true }, AGENT_RELOAD_TIMEOUT_MS, { throwOnError: true });
|
|
949
301
|
if (result === null) {
|
|
950
|
-
return { ok: false, error: '
|
|
302
|
+
return { ok: false, code: 'IPC_INVALID_RESPONSE', error: 'daemon IPC 未返回有效响应,请检查 ec status' };
|
|
951
303
|
}
|
|
952
304
|
if (result?.ok) {
|
|
953
305
|
return { ok: true };
|
|
954
306
|
}
|
|
955
307
|
return { ok: false, code: result?.code, error: result?.error || 'unknown error' };
|
|
956
308
|
}
|
|
957
|
-
catch {
|
|
958
|
-
return { ok: false, error: '
|
|
309
|
+
catch (error) {
|
|
310
|
+
return { ok: false, code: error instanceof IpcTransportError ? `IPC_${error.code}` : 'IPC_FAILED', error: String(error instanceof Error ? error.message : error) };
|
|
959
311
|
}
|
|
960
312
|
}
|
|
961
313
|
// ==================== agentEnable / agentDisable ====================
|
|
@@ -1105,6 +457,107 @@ export async function agentSet(aid, key, rawValue, options = {}) {
|
|
|
1105
457
|
catch { }
|
|
1106
458
|
return { ok: true, aid, key, value, reloaded };
|
|
1107
459
|
}
|
|
460
|
+
export const AGENT_PERSONA_MAX_BYTES = 64 * 1024;
|
|
461
|
+
function validatePersonaContent(content) {
|
|
462
|
+
if (content.includes('\0'))
|
|
463
|
+
return 'persona 只能包含文本内容';
|
|
464
|
+
if (Buffer.byteLength(content, 'utf8') > AGENT_PERSONA_MAX_BYTES) {
|
|
465
|
+
return `persona 不能超过 ${AGENT_PERSONA_MAX_BYTES} bytes`;
|
|
466
|
+
}
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
function readPersonaSource(file) {
|
|
470
|
+
const resolved = path.resolve(file);
|
|
471
|
+
if (!/\.(?:md|txt)$/i.test(resolved)) {
|
|
472
|
+
return { ok: false, code: 'INVALID_ARGS', error: '--file 只接受 .md 或 .txt 文本文件' };
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
const stat = fs.statSync(resolved);
|
|
476
|
+
if (!stat.isFile())
|
|
477
|
+
return { ok: false, code: 'INVALID_ARGS', error: `--file 必须指向普通文件: ${resolved}` };
|
|
478
|
+
const content = fs.readFileSync(resolved, 'utf8');
|
|
479
|
+
const error = validatePersonaContent(content);
|
|
480
|
+
return error ? { ok: false, code: 'INVALID_ARGS', error } : { ok: true, content };
|
|
481
|
+
}
|
|
482
|
+
catch (e) {
|
|
483
|
+
return { ok: false, code: 'READ_FAILED', error: `无法读取 --file: ${e?.message || e}` };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/** Read or replace the current Agent's personal/persona.md. */
|
|
487
|
+
export async function agentPersona(aid, options = {}) {
|
|
488
|
+
if (options.text !== undefined && options.file !== undefined)
|
|
489
|
+
return { ok: false, code: 'INVALID_ARGS', error: '--text 与 --file 不能同时使用' };
|
|
490
|
+
const managedSession = !!process.env.EVOLCORE_SESSION_ID && !options.trustedLocal;
|
|
491
|
+
let text = options.text;
|
|
492
|
+
if (options.file !== undefined) {
|
|
493
|
+
if (managedSession) {
|
|
494
|
+
const resolved = path.resolve(options.file);
|
|
495
|
+
const projectRoot = path.resolve(process.cwd());
|
|
496
|
+
const tempRoot = process.env.TMPDIR?.trim() && path.isAbsolute(process.env.TMPDIR)
|
|
497
|
+
? path.resolve(process.env.TMPDIR)
|
|
498
|
+
: undefined;
|
|
499
|
+
const allowed = isSameOrDescendant(resolved, projectRoot)
|
|
500
|
+
|| (!!tempRoot && isSameOrDescendant(resolved, tempRoot));
|
|
501
|
+
if (!allowed || isHClassPath(resolved) || isLClassPath(resolved)) {
|
|
502
|
+
return { ok: false, code: 'INVALID_ARGS', error: '--file 必须位于当前项目或会话 $TMPDIR,且不能读取 EvolCore 受保护路径' };
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const stat = fs.lstatSync(resolved);
|
|
506
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
507
|
+
return { ok: false, code: 'INVALID_ARGS', error: '--file 必须指向当前项目或会话 $TMPDIR 内的普通文件' };
|
|
508
|
+
}
|
|
509
|
+
const realPath = fs.realpathSync(resolved);
|
|
510
|
+
const realProjectRoot = fs.realpathSync(projectRoot);
|
|
511
|
+
const realTempRoot = tempRoot ? fs.realpathSync(tempRoot) : undefined;
|
|
512
|
+
const realAllowed = isSameOrDescendant(realPath, realProjectRoot)
|
|
513
|
+
|| (!!realTempRoot && isSameOrDescendant(realPath, realTempRoot));
|
|
514
|
+
if (!realAllowed || isHClassPath(realPath) || isLClassPath(realPath)) {
|
|
515
|
+
return { ok: false, code: 'INVALID_ARGS', error: '--file 解析后越出允许目录或命中 EvolCore 受保护路径' };
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (e) {
|
|
519
|
+
return { ok: false, code: 'READ_FAILED', error: `无法校验 --file: ${e?.message || e}` };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const source = readPersonaSource(options.file);
|
|
523
|
+
if (!source.ok)
|
|
524
|
+
return source;
|
|
525
|
+
text = source.content;
|
|
526
|
+
}
|
|
527
|
+
if (text !== undefined) {
|
|
528
|
+
const error = validatePersonaContent(text);
|
|
529
|
+
if (error)
|
|
530
|
+
return { ok: false, code: 'INVALID_ARGS', error };
|
|
531
|
+
}
|
|
532
|
+
const managedArgs = options.text !== undefined
|
|
533
|
+
? ['persona', '--text', text]
|
|
534
|
+
: options.file !== undefined
|
|
535
|
+
? ['persona', '--text', text]
|
|
536
|
+
: ['persona', 'show'];
|
|
537
|
+
if (managedSession) {
|
|
538
|
+
const managed = await runManagedAgentOperation(managedArgs);
|
|
539
|
+
if (!managed.ok)
|
|
540
|
+
return managed;
|
|
541
|
+
return { ok: true, aid, content: managed.content ?? null, ...(managed.path ? { path: managed.path } : {}), ...(managed.saved ? { saved: true } : {}) };
|
|
542
|
+
}
|
|
543
|
+
const personaPath = path.join(agentPersonalDir(aid), 'persona.md');
|
|
544
|
+
if (text === undefined) {
|
|
545
|
+
try {
|
|
546
|
+
return { ok: true, aid, content: fs.existsSync(personaPath) ? fs.readFileSync(personaPath, 'utf8') : null, path: personaPath };
|
|
547
|
+
}
|
|
548
|
+
catch (e) {
|
|
549
|
+
return { ok: false, code: 'READ_FAILED', error: e?.message || String(e) };
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
fs.mkdirSync(path.dirname(personaPath), { recursive: true });
|
|
554
|
+
fs.writeFileSync(personaPath, text, 'utf8');
|
|
555
|
+
return { ok: true, aid, content: text, path: personaPath, saved: true };
|
|
556
|
+
}
|
|
557
|
+
catch (e) {
|
|
558
|
+
return { ok: false, code: 'WRITE_FAILED', error: e?.message || String(e) };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
1108
561
|
/**
|
|
1109
562
|
* Add or overwrite a channel instance on a per-agent config.
|
|
1110
563
|
*
|