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
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
import { resolvePaths, aunPath as defaultAunPath } from '../paths.js';
|
|
5
|
+
import { loadDefaults, loadAgent, saveAgent, ensureAgentDirSkeleton, loadDaemonConfig } from '../config-store.js';
|
|
6
|
+
import { ConfigTarget, write as cfgWrite, ensureFile as cfgEnsure, read as cfgRead } from '../config/config-manager.js';
|
|
7
|
+
import { ipcQuery } from '../ipc.js';
|
|
8
|
+
import { CONFIG_SCHEMA_VERSION } from '../types.js';
|
|
9
|
+
import { ensureInitialRoleRegistry } from '../config/role-service.js';
|
|
10
|
+
import { getCodexAppServerAvailability } from '../agents/codex-runner.js';
|
|
11
|
+
import { resolveEcagentConfig } from '../agents/baseagent.js';
|
|
12
|
+
import { AUTO_BASEAGENT_CANDIDATES, BASEAGENT_CANDIDATES, detectAvailableBaseagents, } from '../agents/baseagent-availability.js';
|
|
13
|
+
import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/project-path.js';
|
|
14
|
+
import { isValidAid } from '../aun/aid/validation.js';
|
|
15
|
+
import { DEFAULT_AGENT_DESCRIPTION } from '../aun/aid/agentmd.js';
|
|
16
|
+
export function validateAgentOwnerOptions(options) {
|
|
17
|
+
if (options.owner !== undefined && options.owners !== undefined) {
|
|
18
|
+
return 'Options --owner and --owners are mutually exclusive';
|
|
19
|
+
}
|
|
20
|
+
if (options.owners !== undefined && (!Array.isArray(options.owners) || options.owners.length === 0)) {
|
|
21
|
+
return '--owners must be a non-empty JSON array of AIDs';
|
|
22
|
+
}
|
|
23
|
+
const owners = options.owners ?? (options.owner !== undefined ? [options.owner] : []);
|
|
24
|
+
if (owners.some(owner => typeof owner !== 'string' || !isValidAid(owner))) {
|
|
25
|
+
return 'Every owner must be a valid AID';
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
/** Shared by the standalone CLI and the authenticated daemon command path. */
|
|
30
|
+
export function parseAgentOwnerArgs(argv) {
|
|
31
|
+
const hasOwner = argv.includes('--owner');
|
|
32
|
+
const hasOwners = argv.includes('--owners');
|
|
33
|
+
if (hasOwner && hasOwners)
|
|
34
|
+
throw new Error('Options --owner and --owners are mutually exclusive');
|
|
35
|
+
const value = (flag) => {
|
|
36
|
+
if (argv.filter(arg => arg === flag).length !== 1)
|
|
37
|
+
throw new Error(`${flag} must be supplied once`);
|
|
38
|
+
const argument = argv[argv.indexOf(flag) + 1];
|
|
39
|
+
if (!argument || argument.startsWith('--'))
|
|
40
|
+
throw new Error(`${flag} requires a value`);
|
|
41
|
+
return argument;
|
|
42
|
+
};
|
|
43
|
+
const options = {};
|
|
44
|
+
if (hasOwner)
|
|
45
|
+
options.owner = value('--owner');
|
|
46
|
+
if (hasOwners) {
|
|
47
|
+
const raw = value('--owners');
|
|
48
|
+
try {
|
|
49
|
+
options.owners = JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error('--owners must be a non-empty JSON array of AIDs');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const error = validateAgentOwnerOptions(options);
|
|
56
|
+
if (error)
|
|
57
|
+
throw new Error(error);
|
|
58
|
+
if (options.owners)
|
|
59
|
+
options.owners = [...new Set(options.owners)];
|
|
60
|
+
return options;
|
|
61
|
+
}
|
|
62
|
+
function withStaticOwners(config, ownersInput) {
|
|
63
|
+
const values = (ownersInput ?? []).map(value => value.trim()).filter(Boolean);
|
|
64
|
+
if (values.length === 0)
|
|
65
|
+
return config;
|
|
66
|
+
const owners = config.owners ?? [];
|
|
67
|
+
return { ...config, owners: [...new Set([...owners, ...values])] };
|
|
68
|
+
}
|
|
69
|
+
function withStaticOwner(config, owner) {
|
|
70
|
+
return withStaticOwners(config, owner ? [owner] : undefined);
|
|
71
|
+
}
|
|
72
|
+
function pickDefaultBaseagent(available) {
|
|
73
|
+
return AUTO_BASEAGENT_CANDIDATES.find(candidate => available.includes(candidate)) ?? null;
|
|
74
|
+
}
|
|
75
|
+
function readProjectMetadata(project) {
|
|
76
|
+
try {
|
|
77
|
+
const packageJsonPath = path.join(project, 'package.json');
|
|
78
|
+
if (!fs.existsSync(packageJsonPath))
|
|
79
|
+
return {};
|
|
80
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
81
|
+
return {
|
|
82
|
+
name: typeof parsed.name === 'string' ? parsed.name.trim() : undefined,
|
|
83
|
+
description: typeof parsed.description === 'string' ? parsed.description.trim() : undefined,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function projectSlug(project) {
|
|
91
|
+
const basename = path.basename(project) || 'agent';
|
|
92
|
+
const slug = basename
|
|
93
|
+
.normalize('NFKD')
|
|
94
|
+
.replace(/[^a-zA-Z0-9-]+/g, '-')
|
|
95
|
+
.replace(/^-+|-+$/g, '')
|
|
96
|
+
.toLowerCase()
|
|
97
|
+
.slice(0, 63)
|
|
98
|
+
.replace(/-+$/g, '');
|
|
99
|
+
return slug || 'agent';
|
|
100
|
+
}
|
|
101
|
+
export function resolveAgentCreatePlan(options) {
|
|
102
|
+
const aid = options.aid.trim();
|
|
103
|
+
const project = path.resolve(options.project || process.cwd());
|
|
104
|
+
const metadata = readProjectMetadata(project);
|
|
105
|
+
const defaults = loadDefaults();
|
|
106
|
+
const daemon = loadDaemonConfig();
|
|
107
|
+
const configuredBaseagent = defaults?.active_baseagent;
|
|
108
|
+
const available = detectAvailableBaseagents();
|
|
109
|
+
const defaultBaseagent = configuredBaseagent && available.includes(configuredBaseagent)
|
|
110
|
+
? configuredBaseagent
|
|
111
|
+
: pickDefaultBaseagent(available) ?? undefined;
|
|
112
|
+
const baseagent = options.baseagent || defaultBaseagent;
|
|
113
|
+
const baseagentSource = options.baseagent
|
|
114
|
+
? 'argument'
|
|
115
|
+
: configuredBaseagent && baseagent === configuredBaseagent ? 'defaults' : 'detected';
|
|
116
|
+
const owners = options.owners;
|
|
117
|
+
const owner = options.owner?.trim() || (owners === undefined ? daemon.owners?.[0] : undefined);
|
|
118
|
+
const ownerSource = owners !== undefined || options.owner?.trim() ? 'argument' : owner ? 'daemon' : 'none';
|
|
119
|
+
const name = options.name?.trim() || metadata.name || projectSlug(project);
|
|
120
|
+
const description = options.description?.trim() || metadata.description || DEFAULT_AGENT_DESCRIPTION;
|
|
121
|
+
const configExists = isValidAid(aid) && fs.existsSync(path.join(resolvePaths().agentsDir, aid, 'config.json'));
|
|
122
|
+
return {
|
|
123
|
+
aid, project, baseagent, owner, owners, handoff: options.handoff, name, description,
|
|
124
|
+
force: options.force === true, configExists, baseagentSource, ownerSource,
|
|
125
|
+
nameSource: options.name?.trim() ? 'argument' : metadata.name ? 'package' : 'directory',
|
|
126
|
+
descriptionSource: options.description?.trim() ? 'argument' : metadata.description ? 'package' : 'default',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export function validateAgentCreatePlan(plan) {
|
|
130
|
+
const ownerError = validateAgentOwnerOptions(plan);
|
|
131
|
+
if (ownerError)
|
|
132
|
+
return ownerError;
|
|
133
|
+
if (!isValidAid(plan.aid))
|
|
134
|
+
return `Invalid AID "${plan.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`;
|
|
135
|
+
if (plan.configExists && !plan.force)
|
|
136
|
+
return `Agent "${plan.aid}" already exists (use --force to overwrite)`;
|
|
137
|
+
if (!plan.baseagent)
|
|
138
|
+
return 'No usable baseagent detected. Pass --baseagent or configure a baseagent in agents/defaults.json.';
|
|
139
|
+
if (!BASEAGENT_CANDIDATES.includes(plan.baseagent)) {
|
|
140
|
+
return `Invalid baseagent: ${plan.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`;
|
|
141
|
+
}
|
|
142
|
+
if (!detectAvailableBaseagents().includes(plan.baseagent)) {
|
|
143
|
+
return `${plan.baseagent} is not available in the current environment`;
|
|
144
|
+
}
|
|
145
|
+
if (plan.baseagent === 'ecagent') {
|
|
146
|
+
const reason = ecagentUnavailableReason();
|
|
147
|
+
if (reason)
|
|
148
|
+
return reason;
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function ecagentUnavailableReason() {
|
|
153
|
+
try {
|
|
154
|
+
resolveEcagentConfig({ agents: { ecagent: {} } });
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return error instanceof Error ? error.message : String(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function saveInitialBehavior(aid, baseagent) {
|
|
162
|
+
const behavior = {
|
|
163
|
+
active_baseagent: baseagent,
|
|
164
|
+
baseagents: { [baseagent]: {} },
|
|
165
|
+
chatmode: { private: 'interactive', group: 'proactive', nothuman: 'proactive' },
|
|
166
|
+
};
|
|
167
|
+
const sel = { self: aid };
|
|
168
|
+
const existingBehavior = cfgRead(ConfigTarget.Agent, sel) || {};
|
|
169
|
+
cfgEnsure(ConfigTarget.Agent, sel);
|
|
170
|
+
cfgWrite(ConfigTarget.Agent, { ...existingBehavior, ...behavior }, sel);
|
|
171
|
+
}
|
|
172
|
+
function toPosix(value) {
|
|
173
|
+
return value ? value.replace(/\\/g, '/') : null;
|
|
174
|
+
}
|
|
175
|
+
export async function agentCreateInteractive(opts = {}) {
|
|
176
|
+
const p = resolvePaths();
|
|
177
|
+
const ownRl = !opts.rl;
|
|
178
|
+
const rl = opts.rl ?? readline.createInterface({ input: opts.stdin || process.stdin, output: opts.stdout || process.stdout });
|
|
179
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
180
|
+
try {
|
|
181
|
+
const { aidCreate, buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
|
|
182
|
+
const aidPrompt = opts.suggestedName ? `AID [${opts.suggestedName}]: ` : 'AID (e.g. mybot.agentid.pub): ';
|
|
183
|
+
let aid = '';
|
|
184
|
+
while (!aid) {
|
|
185
|
+
const aidInput = (await ask(aidPrompt)).trim();
|
|
186
|
+
const candidate = aidInput || opts.suggestedName;
|
|
187
|
+
if (!candidate) {
|
|
188
|
+
console.log(' ⚠ AID is required.');
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!isValidAid(candidate)) {
|
|
192
|
+
console.log(` ⚠ Invalid AID "${candidate}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
aid = candidate;
|
|
196
|
+
}
|
|
197
|
+
const agentDirPath = path.join(p.agentsDir, aid);
|
|
198
|
+
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
199
|
+
if (configExists) {
|
|
200
|
+
const ans = (await ask(`Agent "${aid}" already exists. Overwrite config.json? (AID 与 agent.md 保留) [y/N]: `)).trim().toLowerCase();
|
|
201
|
+
if (ans !== 'y' && ans !== 'yes')
|
|
202
|
+
return { ok: false, error: 'Aborted.' };
|
|
203
|
+
}
|
|
204
|
+
console.log(`\nCreating agent: ${aid}\n`);
|
|
205
|
+
let aidCreated = false;
|
|
206
|
+
try {
|
|
207
|
+
const result = await aidCreate(aid);
|
|
208
|
+
try {
|
|
209
|
+
await result.client.close();
|
|
210
|
+
}
|
|
211
|
+
catch { /* ignore */ }
|
|
212
|
+
aidCreated = !result.alreadyExisted;
|
|
213
|
+
console.log(` ✓ AID ${result.alreadyExisted ? 'reused' : 'created'}: ${aid}`);
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
return { ok: false, error: `AID creation failed: ${e?.message || e}` };
|
|
217
|
+
}
|
|
218
|
+
let suggestedProjectPath = '';
|
|
219
|
+
try {
|
|
220
|
+
suggestedProjectPath = deriveAgentProjectPath(agentProjectRootFromDefaults(loadDefaults(), p.root), aid);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
suggestedProjectPath = deriveAgentProjectPath(agentProjectRootFromDefaults(null, p.root), aid);
|
|
224
|
+
}
|
|
225
|
+
const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
|
|
226
|
+
const projectPath = projectInput || suggestedProjectPath;
|
|
227
|
+
if (!path.isAbsolute(projectPath))
|
|
228
|
+
return { ok: false, error: 'Project path must be an absolute path.' };
|
|
229
|
+
if (!fs.existsSync(projectPath)) {
|
|
230
|
+
const create = (await ask(`Project path does not exist. Create? [Y/n]: `)).trim().toLowerCase();
|
|
231
|
+
if (create === '' || create === 'y' || create === 'yes') {
|
|
232
|
+
try {
|
|
233
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
return { ok: false, error: `Failed to create project path "${projectPath}": ${e?.message || e}` };
|
|
237
|
+
}
|
|
238
|
+
console.log(` ✓ Created ${projectPath}`);
|
|
239
|
+
}
|
|
240
|
+
else
|
|
241
|
+
return { ok: false, error: 'Aborted.' };
|
|
242
|
+
}
|
|
243
|
+
const available = detectAvailableBaseagents();
|
|
244
|
+
if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason())) {
|
|
245
|
+
return { ok: false, error: 'No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server, or configure bundled ecagent.' };
|
|
246
|
+
}
|
|
247
|
+
const defaultBa = pickDefaultBaseagent(available);
|
|
248
|
+
let baseagent;
|
|
249
|
+
if (available.length === 1 && defaultBa) {
|
|
250
|
+
console.log(` Baseagent: ${defaultBa}`);
|
|
251
|
+
baseagent = defaultBa;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
let chosen = null;
|
|
255
|
+
while (chosen === null) {
|
|
256
|
+
const defaultHint = defaultBa ? ` [${defaultBa}]` : '';
|
|
257
|
+
const input = (await ask(`Baseagent (${available.join('/')})${defaultHint}: `)).trim() || defaultBa;
|
|
258
|
+
if (!input) {
|
|
259
|
+
console.log(' ecagent is optional; enter ecagent to select it explicitly.');
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (!BASEAGENT_CANDIDATES.includes(input)) {
|
|
263
|
+
console.log(` Invalid choice. Options: ${BASEAGENT_CANDIDATES.join('/')}`);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (!available.includes(input)) {
|
|
267
|
+
console.log(` ${input} is not available in the current environment. Available: ${available.join('/')}`);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (input === 'ecagent') {
|
|
271
|
+
const reason = ecagentUnavailableReason();
|
|
272
|
+
if (reason) {
|
|
273
|
+
console.log(` ecagent is unavailable: ${reason}`);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
chosen = input;
|
|
278
|
+
}
|
|
279
|
+
baseagent = chosen;
|
|
280
|
+
}
|
|
281
|
+
const daemonCfg = loadDaemonConfig();
|
|
282
|
+
const daemonHasOwner = !!daemonCfg.owners?.length;
|
|
283
|
+
let owner;
|
|
284
|
+
let skipQrBinding = false;
|
|
285
|
+
if (daemonHasOwner) {
|
|
286
|
+
const defaultOwner = daemonCfg.owners[0];
|
|
287
|
+
while (true) {
|
|
288
|
+
const ownerInput = (await ask(`Owner AID [${defaultOwner}]: `)).trim();
|
|
289
|
+
if (!ownerInput) {
|
|
290
|
+
owner = defaultOwner;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
if (ownerInput.toLowerCase() === 'new' || ownerInput.toLowerCase() === 'qr') {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
if (!isValidAid(ownerInput)) {
|
|
297
|
+
console.log(` ⚠ Invalid Owner AID "${ownerInput}"`);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
owner = ownerInput;
|
|
301
|
+
skipQrBinding = true;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const defaultName = aid.split('.')[0];
|
|
306
|
+
const agentName = (await ask(`Display name [${defaultName}]: `)).trim() || defaultName;
|
|
307
|
+
const agentDescription = (await ask('Description (optional): ')).trim() || '';
|
|
308
|
+
if (ownRl)
|
|
309
|
+
rl.close();
|
|
310
|
+
saveAgent(withStaticOwner({ $schema_version: CONFIG_SCHEMA_VERSION, aid, enabled: true, lifecycle: 'created', channels: [], projects: { defaultPath: projectPath } }, owner));
|
|
311
|
+
ensureInitialRoleRegistry(aid);
|
|
312
|
+
saveInitialBehavior(aid, baseagent);
|
|
313
|
+
ensureAgentDirSkeleton(aid);
|
|
314
|
+
let agentmdUploaded = false;
|
|
315
|
+
try {
|
|
316
|
+
const content = buildInitialAgentMd({ aid, name: agentName, description: agentDescription });
|
|
317
|
+
const aunPath = process.env.AUN_HOME || defaultAunPath();
|
|
318
|
+
let lastError;
|
|
319
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
320
|
+
try {
|
|
321
|
+
if (attempt > 1)
|
|
322
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
323
|
+
await agentmdPut(content, { aid, aunPath });
|
|
324
|
+
agentmdUploaded = true;
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
catch (e) {
|
|
328
|
+
lastError = e;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!agentmdUploaded)
|
|
332
|
+
console.warn(` ⚠ agent.md upload failed: ${lastError?.message || lastError}`);
|
|
333
|
+
await new Promise(r => setTimeout(r, 0));
|
|
334
|
+
}
|
|
335
|
+
catch (e) {
|
|
336
|
+
console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
|
|
337
|
+
}
|
|
338
|
+
let hotLoaded = false;
|
|
339
|
+
let hotLoadError;
|
|
340
|
+
try {
|
|
341
|
+
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid }, 30_000);
|
|
342
|
+
if (ipcResult?.ok)
|
|
343
|
+
hotLoaded = true;
|
|
344
|
+
else if (ipcResult)
|
|
345
|
+
hotLoadError = ipcResult.error;
|
|
346
|
+
}
|
|
347
|
+
catch { /* daemon not running */ }
|
|
348
|
+
let ownerBoundAid;
|
|
349
|
+
let ownerBindSkipped = false;
|
|
350
|
+
let ownerBindInterrupted = false;
|
|
351
|
+
if (!owner && !skipQrBinding && process.stdin.isTTY && daemonHasOwner) {
|
|
352
|
+
try {
|
|
353
|
+
const { runAgentOwnerQrBindFlow } = await import('./init-channel.js');
|
|
354
|
+
const bound = await runAgentOwnerQrBindFlow(aid, agentName);
|
|
355
|
+
if (bound.action === 'bound')
|
|
356
|
+
ownerBoundAid = bound.boundAid;
|
|
357
|
+
else if (bound.action === 'manual') {
|
|
358
|
+
ownerBindSkipped = true;
|
|
359
|
+
ownerBoundAid = await promptAgentOwnerManually(aid);
|
|
360
|
+
}
|
|
361
|
+
else
|
|
362
|
+
ownerBindInterrupted = true;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
ownerBindSkipped = true;
|
|
366
|
+
ownerBoundAid = await promptAgentOwnerManually(aid);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return { ok: true, aid, configPath: toPosix(path.join(agentDirPath, 'config.json')), aidCreated, agentmdUploaded, hotLoaded, hotLoadError, ownerBoundAid, ownerBindSkipped, ownerBindInterrupted };
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
if (ownRl) {
|
|
373
|
+
try {
|
|
374
|
+
rl.close();
|
|
375
|
+
}
|
|
376
|
+
catch { /* ignore */ }
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function promptAgentOwnerManually(aid) {
|
|
381
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
382
|
+
try {
|
|
383
|
+
while (true) {
|
|
384
|
+
const owner = (await new Promise(resolve => rl.question('\n扫码未完成。手动输入 agent owner AID(留空跳过): ', resolve))).trim();
|
|
385
|
+
if (!owner)
|
|
386
|
+
return undefined;
|
|
387
|
+
if (!isValidAid(owner)) {
|
|
388
|
+
console.log(' ⚠ Owner AID 格式无效');
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const agent = loadAgent(aid);
|
|
392
|
+
if (!agent)
|
|
393
|
+
return undefined;
|
|
394
|
+
saveAgent(withStaticOwner(agent, owner));
|
|
395
|
+
try {
|
|
396
|
+
await ipcQuery(resolvePaths().socket, { type: 'evolagent.reload', name: aid }, 30_000);
|
|
397
|
+
}
|
|
398
|
+
catch { /* daemon not running */ }
|
|
399
|
+
return owner;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
rl.close();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
export async function agentCreateNonInteractive(opts, runtime = {}) {
|
|
407
|
+
const p = resolvePaths();
|
|
408
|
+
const { aidCreate, buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
|
|
409
|
+
opts.onPhase?.('validating', 'begin');
|
|
410
|
+
const failValidating = (error) => { opts.onPhase?.('validating', 'failed', error); return { ok: false, error }; };
|
|
411
|
+
const ownerError = validateAgentOwnerOptions(opts);
|
|
412
|
+
if (ownerError)
|
|
413
|
+
return failValidating(ownerError);
|
|
414
|
+
if (!isValidAid(opts.aid))
|
|
415
|
+
return failValidating(`Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
|
|
416
|
+
const agentDirPath = path.join(p.agentsDir, opts.aid);
|
|
417
|
+
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
418
|
+
if (configExists && !opts.force)
|
|
419
|
+
return failValidating(`Agent "${opts.aid}" already exists: ${agentDirPath}/config.json (use --force to overwrite)`);
|
|
420
|
+
const available = detectAvailableBaseagents();
|
|
421
|
+
if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason()))
|
|
422
|
+
return failValidating('No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server, or configure bundled ecagent.');
|
|
423
|
+
let baseagent;
|
|
424
|
+
if (opts.baseagent) {
|
|
425
|
+
if (!BASEAGENT_CANDIDATES.includes(opts.baseagent))
|
|
426
|
+
return failValidating(`Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`);
|
|
427
|
+
if (!available.includes(opts.baseagent))
|
|
428
|
+
return failValidating(opts.baseagent === 'codex' ? getCodexAppServerAvailability().reason || `${opts.baseagent} is not available in the current environment` : `${opts.baseagent} is not available in the current environment (available: ${available.join('/')})`);
|
|
429
|
+
if (opts.baseagent === 'ecagent') {
|
|
430
|
+
const reason = ecagentUnavailableReason();
|
|
431
|
+
if (reason)
|
|
432
|
+
return failValidating(reason);
|
|
433
|
+
}
|
|
434
|
+
baseagent = opts.baseagent;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const defaultBaseagent = pickDefaultBaseagent(available);
|
|
438
|
+
if (!defaultBaseagent)
|
|
439
|
+
return failValidating('ecagent is optional; pass --baseagent ecagent after configuring its API key');
|
|
440
|
+
baseagent = defaultBaseagent;
|
|
441
|
+
}
|
|
442
|
+
if (!path.isAbsolute(opts.project))
|
|
443
|
+
return failValidating(`--project must be absolute: ${opts.project}`);
|
|
444
|
+
if (!fs.existsSync(opts.project)) {
|
|
445
|
+
try {
|
|
446
|
+
fs.mkdirSync(opts.project, { recursive: true });
|
|
447
|
+
}
|
|
448
|
+
catch (e) {
|
|
449
|
+
return failValidating(`Failed to create ${opts.project}: ${e?.message || e}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
opts.onPhase?.('validating', 'done');
|
|
453
|
+
opts.onPhase?.('registering_aid', 'begin');
|
|
454
|
+
let aidCreated = false;
|
|
455
|
+
try {
|
|
456
|
+
const result = await aidCreate(opts.aid);
|
|
457
|
+
try {
|
|
458
|
+
await result.client.close();
|
|
459
|
+
}
|
|
460
|
+
catch { /* ignore */ }
|
|
461
|
+
aidCreated = !result.alreadyExisted;
|
|
462
|
+
opts.onPhase?.('registering_aid', 'done', aidCreated ? 'created' : 'existed');
|
|
463
|
+
}
|
|
464
|
+
catch (e) {
|
|
465
|
+
const error = `AID creation failed: ${e?.message || e}`;
|
|
466
|
+
opts.onPhase?.('registering_aid', 'failed', error);
|
|
467
|
+
return { ok: false, error };
|
|
468
|
+
}
|
|
469
|
+
let preservedLifecycle = 'created';
|
|
470
|
+
if (configExists) {
|
|
471
|
+
try {
|
|
472
|
+
const existing = loadAgent(opts.aid);
|
|
473
|
+
if (existing?.lifecycle)
|
|
474
|
+
preservedLifecycle = existing.lifecycle;
|
|
475
|
+
}
|
|
476
|
+
catch { /* ignore */ }
|
|
477
|
+
}
|
|
478
|
+
opts.onPhase?.('config_saved', 'begin');
|
|
479
|
+
saveAgent(withStaticOwners({ $schema_version: CONFIG_SCHEMA_VERSION, aid: opts.aid, enabled: true, lifecycle: preservedLifecycle, channels: [], projects: { defaultPath: opts.project } }, opts.owners ?? (opts.owner ? [opts.owner] : undefined)));
|
|
480
|
+
ensureInitialRoleRegistry(opts.aid);
|
|
481
|
+
saveInitialBehavior(opts.aid, baseagent);
|
|
482
|
+
ensureAgentDirSkeleton(opts.aid);
|
|
483
|
+
opts.onPhase?.('config_saved', 'done');
|
|
484
|
+
opts.onPhase?.('uploading_agentmd', 'begin');
|
|
485
|
+
let agentmdUploaded = false;
|
|
486
|
+
try {
|
|
487
|
+
const content = buildInitialAgentMd({ aid: opts.aid, name: opts.name, description: opts.description });
|
|
488
|
+
const aunPath = process.env.AUN_HOME || defaultAunPath();
|
|
489
|
+
let lastError;
|
|
490
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
491
|
+
try {
|
|
492
|
+
if (attempt > 1)
|
|
493
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
494
|
+
await agentmdPut(content, { aid: opts.aid, aunPath });
|
|
495
|
+
agentmdUploaded = true;
|
|
496
|
+
opts.onPhase?.('uploading_agentmd', 'done');
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
catch (e) {
|
|
500
|
+
lastError = e;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (!agentmdUploaded)
|
|
504
|
+
opts.onPhase?.('uploading_agentmd', 'warn', `upload failed: ${lastError?.message || lastError}`);
|
|
505
|
+
await new Promise(r => setTimeout(r, 0));
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
opts.onPhase?.('uploading_agentmd', 'warn', `generation failed: ${e?.message || e}`);
|
|
509
|
+
}
|
|
510
|
+
let hotLoaded = false;
|
|
511
|
+
let hotLoadError;
|
|
512
|
+
opts.onPhase?.('hot_loading', 'begin');
|
|
513
|
+
try {
|
|
514
|
+
if (runtime.hotLoadAgent) {
|
|
515
|
+
await runtime.hotLoadAgent(opts.aid);
|
|
516
|
+
hotLoaded = true;
|
|
517
|
+
opts.onPhase?.('hot_loading', 'done');
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid }, 30_000);
|
|
521
|
+
if (ipcResult?.ok) {
|
|
522
|
+
hotLoaded = true;
|
|
523
|
+
opts.onPhase?.('hot_loading', 'done');
|
|
524
|
+
}
|
|
525
|
+
else if (ipcResult) {
|
|
526
|
+
hotLoadError = ipcResult.error;
|
|
527
|
+
opts.onPhase?.('hot_loading', 'warn', hotLoadError);
|
|
528
|
+
}
|
|
529
|
+
else
|
|
530
|
+
opts.onPhase?.('hot_loading', 'warn', 'daemon not running');
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
hotLoadError = error instanceof Error ? error.message : String(error);
|
|
535
|
+
opts.onPhase?.('hot_loading', 'warn', hotLoadError);
|
|
536
|
+
}
|
|
537
|
+
return { ok: true, aid: opts.aid, ownerAid: (opts.owners ?? (opts.owner ? [opts.owner] : undefined))?.[0], configPath: toPosix(path.join(agentDirPath, 'config.json')), aidCreated, agentmdUploaded, hotLoaded, hotLoadError };
|
|
538
|
+
}
|