evolcore 0.0.25 → 0.0.27
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 +632 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +203 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +172 -33
- package/dist/agents/codex/app-server-client.js +76 -31
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +434 -446
- package/dist/agents/ecagent-runner.js +174 -34
- package/dist/agents/gemini-runner.js +8 -7
- package/dist/agents/runner-types.js +75 -19
- package/dist/aun/aid/agentmd.js +151 -36
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +4 -4
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +162 -39
- package/dist/aun/{menu-client.js → menu/client.js} +85 -6
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +85 -19
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +19 -9
- package/dist/aun/msg/upload.js +43 -80
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +842 -318
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +212 -89
- package/dist/cli/agent-create.js +626 -11
- package/dist/cli/agent.js +125 -13
- package/dist/cli/aun-commands.js +487 -337
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +55 -33
- package/dist/cli/contact.js +72 -15
- package/dist/cli/ctl-command.js +24 -24
- package/dist/cli/daemon-commands.js +140 -63
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +70 -70
- package/dist/cli/handoff-command.js +46 -14
- package/dist/cli/index.js +91 -79
- package/dist/cli/init.js +34 -1
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +5 -1
- package/dist/cli/menu-client.js +51 -32
- package/dist/cli/model.js +3 -3
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +31 -31
- package/dist/cli/response.js +32 -32
- package/dist/cli/restart-monitor.js +103 -42
- package/dist/cli/stats.js +51 -51
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +74 -64
- package/dist/config/builtin-roles.js +11 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +160 -0
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +97 -8
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +30 -7
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +7 -1
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +53 -49
- package/dist/core/auth/operation-authorizer.js +90 -7
- package/dist/core/auth/operation-catalog.js +145 -4
- package/dist/core/auth/remote-access-store.js +155 -10
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +111 -20
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +159 -65
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +46 -7
- package/dist/core/command/menu-handler.js +392 -100
- package/dist/core/command/menu-protocol.js +45 -6
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +123 -43
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +146 -34
- package/dist/core/message/message-bridge.js +163 -20
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +99 -41
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +755 -144
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +112 -41
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +108 -17
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +9 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +34 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +588 -217
- package/dist/ipc.js +193 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +61 -2
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +26 -14
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +196 -23
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/agent-harness.js +3 -0
- 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/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +60 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +69 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +379 -367
- 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 +76 -71
- package/kits/docs/evolcore/agent-create.md +202 -165
- package/kits/docs/evolcore/agent.md +99 -67
- package/kits/docs/evolcore/aid.md +68 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +241 -195
- package/kits/docs/evolcore/contact.md +75 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +244 -244
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +267 -152
- package/kits/docs/evolcore/menu-client.md +58 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +169 -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 +53 -50
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +623 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +269 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +42 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +217 -190
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +166 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- package/kits/rules/04-relation.md +76 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +65 -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 +451 -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 +215 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +92 -92
- package/kits/templates/roles/member.json +101 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +90 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +53 -34
- package/kits/templates/system-fragments/channel.md +28 -64
- package/kits/templates/system-fragments/commands.md +36 -28
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/reply.md +56 -0
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +104 -98
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
package/dist/cli/agent-create.js
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import { randomBytes, createHash } from 'crypto';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import readline from 'readline';
|
|
4
|
-
import {
|
|
5
|
+
import { parse } from 'yaml';
|
|
6
|
+
import { resolvePaths, aunPath as defaultAunPath, agentConfig, agentDataDir, agentPersonalDir, aidLocalDir, agentMdPath } from '../paths.js';
|
|
5
7
|
import { loadDefaults, loadAgent, saveAgent, ensureAgentDirSkeleton, loadDaemonConfig } from '../config-store.js';
|
|
6
8
|
import { ConfigTarget, write as cfgWrite, ensureFile as cfgEnsure, read as cfgRead } from '../config/config-manager.js';
|
|
7
9
|
import { ipcQuery } from '../ipc.js';
|
|
8
10
|
import { CONFIG_SCHEMA_VERSION } from '../types.js';
|
|
9
11
|
import { ensureInitialRoleRegistry } from '../config/role-service.js';
|
|
12
|
+
import { addContact } from '../config/contact-request-service.js';
|
|
10
13
|
import { getCodexAppServerAvailability } from '../agents/codex-runner.js';
|
|
11
14
|
import { resolveEcagentConfig } from '../agents/baseagent.js';
|
|
12
15
|
import { AUTO_BASEAGENT_CANDIDATES, BASEAGENT_CANDIDATES, detectAvailableBaseagents, } from '../agents/baseagent-availability.js';
|
|
13
16
|
import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/project-path.js';
|
|
14
17
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
15
|
-
import { AGENT_TAG_MAX_COUNT, AGENT_TAG_MIN_COUNT, DEFAULT_AGENT_DESCRIPTION, validateAgentTags, } from '../aun/aid/agentmd.js';
|
|
18
|
+
import { AGENT_DESCRIPTION_MAX_LENGTH, AGENT_TAG_MAX_COUNT, AGENT_TAG_MIN_COUNT, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_TYPE, normalizeAgentType, validateAgentTags, validateAgentType, buildInitialAgentMd, validateAgentMdFrontmatter, } from '../aun/aid/agentmd.js';
|
|
19
|
+
import { atomicWriteJson } from '../utils/atomic-write.js';
|
|
20
|
+
import { isHClassPath, isLClassPath, isSameOrDescendant } from '../core/permission/protected-paths.js';
|
|
21
|
+
import { MAIN_CLI_BIN } from '../product.js';
|
|
22
|
+
/** Parse the current Agent's Bootstrap welcome subcommand. */
|
|
23
|
+
export function parseAgentWelcomeArgs(args) {
|
|
24
|
+
const tail = args.at(-2) === '--format' && args.at(-1) === 'json' ? args.slice(0, -2) : args;
|
|
25
|
+
if (tail.length === 1 && tail[0] === 'show')
|
|
26
|
+
return { action: 'show' };
|
|
27
|
+
if (tail.length === 2 && tail[0] === '--text' && typeof tail[1] === 'string') {
|
|
28
|
+
return { action: 'set', text: tail[1] };
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`用法: ${MAIN_CLI_BIN} agent welcome show | --text <text> [--format json]`);
|
|
31
|
+
}
|
|
16
32
|
export function parseAgentTags(raw) {
|
|
17
33
|
if (raw === undefined)
|
|
18
34
|
return undefined;
|
|
@@ -89,6 +105,42 @@ function withStaticOwners(config, ownersInput) {
|
|
|
89
105
|
function withStaticOwner(config, owner) {
|
|
90
106
|
return withStaticOwners(config, owner ? [owner] : undefined);
|
|
91
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* An Agent owner must be able to receive the new Agent's Bootstrap reply.
|
|
110
|
+
* Contact admission is per local Agent, so materialize the new AID in each
|
|
111
|
+
* locally managed owner's contact book immediately after creation. Human or
|
|
112
|
+
* remote owners have no local contact book and are reported as skipped.
|
|
113
|
+
*/
|
|
114
|
+
export async function syncOwnerContactsAfterCreate(createdAid, owners) {
|
|
115
|
+
const synced = [];
|
|
116
|
+
const skipped = [];
|
|
117
|
+
const errors = [];
|
|
118
|
+
for (const ownerAid of [...new Set((owners ?? []).map(value => value.trim()).filter(Boolean))]) {
|
|
119
|
+
if (ownerAid === createdAid)
|
|
120
|
+
continue;
|
|
121
|
+
const ownerConfig = path.join(resolvePaths().agentsDir, ownerAid, 'config.json');
|
|
122
|
+
if (!fs.existsSync(ownerConfig)) {
|
|
123
|
+
skipped.push(ownerAid);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
await addContact({
|
|
128
|
+
selfAid: ownerAid,
|
|
129
|
+
primaryId: createdAid,
|
|
130
|
+
actorId: 'agent-create-owner-sync',
|
|
131
|
+
});
|
|
132
|
+
synced.push(ownerAid);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
errors.push(`${ownerAid}: ${error instanceof Error ? error.message : String(error)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
...(synced.length > 0 ? { ownerContactsSynced: synced } : {}),
|
|
140
|
+
...(skipped.length > 0 ? { ownerContactsSkipped: skipped } : {}),
|
|
141
|
+
...(errors.length > 0 ? { ownerContactSyncErrors: errors } : {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
92
144
|
function pickDefaultBaseagent(available) {
|
|
93
145
|
return AUTO_BASEAGENT_CANDIDATES.find(candidate => available.includes(candidate)) ?? null;
|
|
94
146
|
}
|
|
@@ -137,13 +189,25 @@ export function resolveAgentCreatePlan(options) {
|
|
|
137
189
|
const owner = options.owner?.trim() || (owners === undefined ? daemon.owners?.[0] : undefined);
|
|
138
190
|
const ownerSource = owners !== undefined || options.owner?.trim() ? 'argument' : owner ? 'daemon' : 'none';
|
|
139
191
|
const name = options.name?.trim() || metadata.name || projectSlug(project);
|
|
140
|
-
|
|
192
|
+
// description 是可选字段:用户显式传入的必须合规(超长会在校验阶段被拒);
|
|
193
|
+
// 从 package.json 推导出来的超长值直接弃用、回落默认短描述——用户没设过它,
|
|
194
|
+
// 不该因此让创建命令失败。
|
|
195
|
+
const explicitDescription = options.description?.trim();
|
|
196
|
+
const packageDescription = metadata.description
|
|
197
|
+
&& Array.from(metadata.description).length <= AGENT_DESCRIPTION_MAX_LENGTH
|
|
198
|
+
? metadata.description
|
|
199
|
+
: undefined;
|
|
200
|
+
const description = explicitDescription || packageDescription || DEFAULT_AGENT_DESCRIPTION;
|
|
201
|
+
// 大小写不敏感:能识别就收敛成小写;无法识别时保留原值,交给 validateAgentCreatePlan 报错。
|
|
202
|
+
const requestedType = options.type?.trim();
|
|
203
|
+
const type = requestedType ? (normalizeAgentType(requestedType) ?? requestedType) : DEFAULT_AGENT_TYPE;
|
|
141
204
|
const configExists = isValidAid(aid) && fs.existsSync(path.join(resolvePaths().agentsDir, aid, 'config.json'));
|
|
142
205
|
return {
|
|
143
|
-
aid, project, baseagent, owner, owners, handoff: options.handoff, name, description, tags: options.tags,
|
|
206
|
+
aid, project, baseagent, owner, owners, handoff: options.handoff, name, description, type, tags: options.tags,
|
|
144
207
|
force: options.force === true, configExists, baseagentSource, ownerSource,
|
|
145
208
|
nameSource: options.name?.trim() ? 'argument' : metadata.name ? 'package' : 'directory',
|
|
146
|
-
descriptionSource:
|
|
209
|
+
descriptionSource: explicitDescription ? 'argument' : packageDescription ? 'package' : 'default',
|
|
210
|
+
typeSource: options.type?.trim() ? 'argument' : 'default',
|
|
147
211
|
};
|
|
148
212
|
}
|
|
149
213
|
export function validateAgentCreatePlan(plan) {
|
|
@@ -159,6 +223,16 @@ export function validateAgentCreatePlan(plan) {
|
|
|
159
223
|
if (tagErrors.length > 0)
|
|
160
224
|
return `Invalid tags: ${tagErrors.join('; ')}`;
|
|
161
225
|
}
|
|
226
|
+
const typeError = validateAgentType(plan.type);
|
|
227
|
+
if (typeError)
|
|
228
|
+
return `Invalid --type: ${typeError}`;
|
|
229
|
+
// 只有显式传入的 --description 才会超限(package.json 的超长值在 resolve 阶段
|
|
230
|
+
// 已被弃用)。必须在这里就拒绝:否则 plan 通过、直到 agentmdPut 上传才失败,
|
|
231
|
+
// 而那处是 warn 级不中断创建,最终留下一张 SDK 兜底名片(--type/--name 全部丢失)。
|
|
232
|
+
const descriptionLength = Array.from(plan.description).length;
|
|
233
|
+
if (descriptionLength > AGENT_DESCRIPTION_MAX_LENGTH) {
|
|
234
|
+
return `Invalid --description: ${descriptionLength} characters exceeds the agent.md limit of ${AGENT_DESCRIPTION_MAX_LENGTH}. Pass a shorter --description.`;
|
|
235
|
+
}
|
|
162
236
|
if (!plan.baseagent)
|
|
163
237
|
return 'No usable baseagent detected. Pass --baseagent or configure a baseagent in agents/defaults.json.';
|
|
164
238
|
if (!BASEAGENT_CANDIDATES.includes(plan.baseagent)) {
|
|
@@ -219,6 +293,8 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
219
293
|
}
|
|
220
294
|
aid = candidate;
|
|
221
295
|
}
|
|
296
|
+
if (hasSuppliedInitialization(aid))
|
|
297
|
+
return { ok: false, error: 'Use agent initialize --resume for this target' };
|
|
222
298
|
const agentDirPath = path.join(p.agentsDir, aid);
|
|
223
299
|
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
224
300
|
if (configExists) {
|
|
@@ -344,6 +420,8 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
344
420
|
ensureInitialRoleRegistry(aid);
|
|
345
421
|
saveInitialBehavior(aid, baseagent);
|
|
346
422
|
ensureAgentDirSkeleton(aid);
|
|
423
|
+
if (owner)
|
|
424
|
+
await syncOwnerContactsAfterCreate(aid, [owner]);
|
|
347
425
|
let agentmdUploaded = false;
|
|
348
426
|
try {
|
|
349
427
|
const content = buildInitialAgentMd({ aid, name: agentName, description: agentDescription, tags: agentTags });
|
|
@@ -399,7 +477,21 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
399
477
|
ownerBoundAid = await promptAgentOwnerManually(aid);
|
|
400
478
|
}
|
|
401
479
|
}
|
|
402
|
-
|
|
480
|
+
const finalOwnerAid = owner ?? ownerBoundAid;
|
|
481
|
+
const ownerContactSync = await syncOwnerContactsAfterCreate(aid, finalOwnerAid ? [finalOwnerAid] : undefined);
|
|
482
|
+
return {
|
|
483
|
+
ok: true,
|
|
484
|
+
aid,
|
|
485
|
+
configPath: toPosix(path.join(agentDirPath, 'config.json')),
|
|
486
|
+
aidCreated,
|
|
487
|
+
agentmdUploaded,
|
|
488
|
+
hotLoaded,
|
|
489
|
+
hotLoadError,
|
|
490
|
+
ownerBoundAid,
|
|
491
|
+
ownerBindSkipped,
|
|
492
|
+
ownerBindInterrupted,
|
|
493
|
+
...ownerContactSync,
|
|
494
|
+
};
|
|
403
495
|
}
|
|
404
496
|
finally {
|
|
405
497
|
if (ownRl) {
|
|
@@ -449,8 +541,21 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
|
|
|
449
541
|
if (tagErrors.length > 0)
|
|
450
542
|
return failValidating(`Invalid tags: ${tagErrors.join('; ')}`);
|
|
451
543
|
}
|
|
544
|
+
if (opts.type !== undefined) {
|
|
545
|
+
const typeError = validateAgentType(opts.type);
|
|
546
|
+
if (typeError)
|
|
547
|
+
return failValidating(`Invalid --type: ${typeError}`);
|
|
548
|
+
}
|
|
549
|
+
if (opts.description !== undefined) {
|
|
550
|
+
const descriptionLength = Array.from(opts.description).length;
|
|
551
|
+
if (descriptionLength > AGENT_DESCRIPTION_MAX_LENGTH) {
|
|
552
|
+
return failValidating(`Invalid description: ${descriptionLength} characters exceeds the agent.md limit of ${AGENT_DESCRIPTION_MAX_LENGTH}. Pass a shorter --description.`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
452
555
|
if (!isValidAid(opts.aid))
|
|
453
556
|
return failValidating(`Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
|
|
557
|
+
if (hasSuppliedInitialization(opts.aid))
|
|
558
|
+
return failValidating('Use agent initialize --resume for this target');
|
|
454
559
|
const agentDirPath = path.join(p.agentsDir, opts.aid);
|
|
455
560
|
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
456
561
|
if (configExists && !opts.force)
|
|
@@ -518,11 +623,13 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
|
|
|
518
623
|
ensureInitialRoleRegistry(opts.aid);
|
|
519
624
|
saveInitialBehavior(opts.aid, baseagent);
|
|
520
625
|
ensureAgentDirSkeleton(opts.aid);
|
|
626
|
+
const ownerContactSync = await syncOwnerContactsAfterCreate(opts.aid, opts.owners ?? (opts.owner ? [opts.owner] : undefined));
|
|
521
627
|
opts.onPhase?.('config_saved', 'done');
|
|
522
628
|
opts.onPhase?.('uploading_agentmd', 'begin');
|
|
523
629
|
let agentmdUploaded = false;
|
|
630
|
+
let agentmdError;
|
|
524
631
|
try {
|
|
525
|
-
const content = buildInitialAgentMd({ aid: opts.aid, name: opts.name, description: opts.description, tags: opts.tags });
|
|
632
|
+
const content = buildInitialAgentMd({ aid: opts.aid, name: opts.name, description: opts.description, type: opts.type, tags: opts.tags });
|
|
526
633
|
const aunPath = process.env.AUN_HOME || defaultAunPath();
|
|
527
634
|
let lastError;
|
|
528
635
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
@@ -538,12 +645,15 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
|
|
|
538
645
|
lastError = e;
|
|
539
646
|
}
|
|
540
647
|
}
|
|
541
|
-
if (!agentmdUploaded)
|
|
542
|
-
|
|
648
|
+
if (!agentmdUploaded) {
|
|
649
|
+
agentmdError = `upload failed: ${lastError?.message || lastError}`;
|
|
650
|
+
opts.onPhase?.('uploading_agentmd', 'warn', agentmdError);
|
|
651
|
+
}
|
|
543
652
|
await new Promise(r => setTimeout(r, 0));
|
|
544
653
|
}
|
|
545
654
|
catch (e) {
|
|
546
|
-
|
|
655
|
+
agentmdError = `generation failed: ${e?.message || e}`;
|
|
656
|
+
opts.onPhase?.('uploading_agentmd', 'warn', agentmdError);
|
|
547
657
|
}
|
|
548
658
|
let hotLoaded = false;
|
|
549
659
|
let hotLoadError;
|
|
@@ -572,5 +682,510 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
|
|
|
572
682
|
hotLoadError = error instanceof Error ? error.message : String(error);
|
|
573
683
|
opts.onPhase?.('hot_loading', 'warn', hotLoadError);
|
|
574
684
|
}
|
|
575
|
-
return {
|
|
685
|
+
return {
|
|
686
|
+
ok: true,
|
|
687
|
+
aid: opts.aid,
|
|
688
|
+
ownerAid: (opts.owners ?? (opts.owner ? [opts.owner] : undefined))?.[0],
|
|
689
|
+
configPath: toPosix(path.join(agentDirPath, 'config.json')),
|
|
690
|
+
aidCreated,
|
|
691
|
+
agentmdUploaded,
|
|
692
|
+
agentmdError,
|
|
693
|
+
hotLoaded,
|
|
694
|
+
hotLoadError,
|
|
695
|
+
...ownerContactSync,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
// ---- One-shot Agent initialization (kept with Agent creation) ----
|
|
699
|
+
export const INITIALIZATION_PERSONA_MAX_BYTES = 64 * 1024;
|
|
700
|
+
export function validateInitializationPersonaContent(content) {
|
|
701
|
+
if (content.includes('\0'))
|
|
702
|
+
return 'persona 只能包含文本内容';
|
|
703
|
+
if (Buffer.byteLength(content, 'utf8') > INITIALIZATION_PERSONA_MAX_BYTES) {
|
|
704
|
+
return `persona 不能超过 ${INITIALIZATION_PERSONA_MAX_BYTES} bytes`;
|
|
705
|
+
}
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
export function initializationPath(aid) {
|
|
709
|
+
return path.join(agentDataDir(aid), 'initialization.json');
|
|
710
|
+
}
|
|
711
|
+
export function initializationFingerprint(plan) {
|
|
712
|
+
return createHash('sha256').update(JSON.stringify([
|
|
713
|
+
plan.aid, plan.project, plan.baseagent, plan.owners, plan.name,
|
|
714
|
+
plan.description, plan.type, plan.tags, plan.persona ?? null,
|
|
715
|
+
])).digest('hex');
|
|
716
|
+
}
|
|
717
|
+
export function readInitialization(aid) {
|
|
718
|
+
const file = initializationPath(aid);
|
|
719
|
+
if (!fs.existsSync(file))
|
|
720
|
+
return null;
|
|
721
|
+
const record = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
722
|
+
if (record.version !== 1 || record.plan?.aid !== aid || !/^init-[a-f0-9]{24}$/.test(record.operationId)
|
|
723
|
+
|| !['in_progress', 'failed', 'complete', 'cancelled'].includes(record.status)
|
|
724
|
+
|| initializationFingerprint(record.plan) !== record.fingerprint) {
|
|
725
|
+
throw new Error('INITIALIZE_STATE_INVALID');
|
|
726
|
+
}
|
|
727
|
+
return record;
|
|
728
|
+
}
|
|
729
|
+
export function saveInitialization(record) {
|
|
730
|
+
const file = initializationPath(record.plan.aid);
|
|
731
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
732
|
+
record.updatedAt = Date.now();
|
|
733
|
+
atomicWriteJson(file, record);
|
|
734
|
+
// The hot backup also contains the private plan snapshot.
|
|
735
|
+
for (const target of [file, `${file}_`])
|
|
736
|
+
if (fs.existsSync(target))
|
|
737
|
+
fs.chmodSync(target, 0o600);
|
|
738
|
+
}
|
|
739
|
+
/** Pure summary: never expose the persona, project path or authorization snapshot. */
|
|
740
|
+
export function initializationSummary(aid) {
|
|
741
|
+
try {
|
|
742
|
+
const r = readInitialization(aid);
|
|
743
|
+
if (!r)
|
|
744
|
+
return undefined;
|
|
745
|
+
return {
|
|
746
|
+
mode: 'supplied', operationId: r.operationId, fingerprint: r.fingerprint,
|
|
747
|
+
status: r.status, phase: r.phase, complete: r.status === 'complete',
|
|
748
|
+
code: r.code, error: r.error, retryable: r.status === 'failed',
|
|
749
|
+
nextStep: r.status === 'failed' ? `ec agent initialize ${aid} --resume` : undefined,
|
|
750
|
+
agentmd: { published: r.published === true, verified: r.verified === true },
|
|
751
|
+
persona: { provided: r.plan.persona !== undefined },
|
|
752
|
+
welcome: r.welcome, startedAt: r.startedAt, updatedAt: r.updatedAt,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
return { mode: 'supplied', status: 'failed', complete: false, code: 'INITIALIZE_STATE_INVALID' };
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
/** Persistent guard also covers a removed/tampered config marker. */
|
|
760
|
+
export function initializationBlocksTasks(config) {
|
|
761
|
+
try {
|
|
762
|
+
const r = readInitialization(config.aid);
|
|
763
|
+
if (!r && config.initialization === undefined)
|
|
764
|
+
return false;
|
|
765
|
+
return !r || config.initialization?.operationId !== r.operationId
|
|
766
|
+
|| config.initialization.mode !== 'supplied' || config.lifecycle !== 'active'
|
|
767
|
+
|| r.status !== 'complete';
|
|
768
|
+
}
|
|
769
|
+
catch {
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
export function hasSuppliedInitialization(aid) {
|
|
774
|
+
return fs.existsSync(initializationPath(aid));
|
|
775
|
+
}
|
|
776
|
+
function failure(code, error = code) {
|
|
777
|
+
throw Object.assign(new Error(error), { code });
|
|
778
|
+
}
|
|
779
|
+
export function validateInitializationPlan(plan) {
|
|
780
|
+
if (!plan.name?.trim() || !plan.description?.trim() || !Array.isArray(plan.tags) || !plan.owners?.length) {
|
|
781
|
+
failure('INITIALIZE_FIELDS_REQUIRED', 'Explicit name, description, tags and owners are required');
|
|
782
|
+
}
|
|
783
|
+
if (plan.type !== 'agent')
|
|
784
|
+
failure('INVALID_ARGS', 'Initialized targets must have type=agent');
|
|
785
|
+
const error = validateAgentCreatePlan({
|
|
786
|
+
...plan, force: false, configExists: false, baseagentSource: 'argument', ownerSource: 'argument',
|
|
787
|
+
nameSource: 'argument', descriptionSource: 'argument', typeSource: 'argument',
|
|
788
|
+
});
|
|
789
|
+
if (error)
|
|
790
|
+
failure('INVALID_ARGS', error);
|
|
791
|
+
if (!path.isAbsolute(plan.project))
|
|
792
|
+
failure('INVALID_ARGS', 'project must be an absolute path');
|
|
793
|
+
if (fs.existsSync(plan.project) && !fs.statSync(plan.project).isDirectory())
|
|
794
|
+
failure('INVALID_ARGS', 'project is not a directory');
|
|
795
|
+
const card = validateAgentMdFrontmatter(buildInitialAgentMd(plan), plan.aid);
|
|
796
|
+
if (!card.valid)
|
|
797
|
+
failure('INVALID_ARGS', card.errors.join('; '));
|
|
798
|
+
if (plan.persona !== undefined) {
|
|
799
|
+
if (typeof plan.persona !== 'string')
|
|
800
|
+
failure('INVALID_ARGS', 'persona must be text');
|
|
801
|
+
const personaError = validateInitializationPersonaContent(plan.persona);
|
|
802
|
+
if (personaError)
|
|
803
|
+
failure('INVALID_ARGS', personaError);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function cardHash(content) {
|
|
807
|
+
return createHash('sha256').update(content).digest('hex');
|
|
808
|
+
}
|
|
809
|
+
function matchesCard(content, plan) {
|
|
810
|
+
if (!validateAgentMdFrontmatter(content, plan.aid).valid)
|
|
811
|
+
return false;
|
|
812
|
+
const fields = parse(content.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? '');
|
|
813
|
+
return fields?.aid === plan.aid && fields.name === plan.name && fields.description === plan.description
|
|
814
|
+
&& fields.type === plan.type && JSON.stringify(fields.tags) === JSON.stringify(plan.tags);
|
|
815
|
+
}
|
|
816
|
+
/** Daemon-owned, durable creation. All public entry points authorize before calling this service. */
|
|
817
|
+
export class AgentInitializationService {
|
|
818
|
+
deps;
|
|
819
|
+
running = new Map();
|
|
820
|
+
constructor(deps) {
|
|
821
|
+
this.deps = deps;
|
|
822
|
+
}
|
|
823
|
+
status(aid) {
|
|
824
|
+
const summary = initializationSummary(aid);
|
|
825
|
+
if (!summary)
|
|
826
|
+
return { ok: false, code: 'NOT_FOUND', error: 'No initialization record', aid };
|
|
827
|
+
const config = loadAgent(aid);
|
|
828
|
+
const complete = summary.complete === true && config?.lifecycle === 'active';
|
|
829
|
+
return {
|
|
830
|
+
ok: summary.status !== 'failed' && summary.status !== 'cancelled', aid, accepted: true,
|
|
831
|
+
...summary, complete, lifecycle: config?.lifecycle ?? null,
|
|
832
|
+
usable: complete && this.deps.usable(aid), initialization: summary,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
start(plan, actor, dryRun = false) {
|
|
836
|
+
if (this.deps.authorize && !this.deps.authorize(actor))
|
|
837
|
+
failure('NOT_ALLOWED');
|
|
838
|
+
validateInitializationPlan(plan);
|
|
839
|
+
const fingerprint = initializationFingerprint(plan);
|
|
840
|
+
const existing = readInitialization(plan.aid);
|
|
841
|
+
if (existing) {
|
|
842
|
+
if (existing.actor !== actor || existing.fingerprint !== fingerprint)
|
|
843
|
+
failure('INITIALIZE_PLAN_CONFLICT');
|
|
844
|
+
const config = loadAgent(plan.aid);
|
|
845
|
+
if (existing.status === 'complete'
|
|
846
|
+
&& (!config || config.initialization?.operationId !== existing.operationId)) {
|
|
847
|
+
failure('INITIALIZE_STATE_INVALID');
|
|
848
|
+
}
|
|
849
|
+
return dryRun ? { ok: true, dryRun: true, aid: plan.aid, fingerprint } : this.status(plan.aid);
|
|
850
|
+
}
|
|
851
|
+
if (fs.existsSync(agentConfig(plan.aid)) || fs.existsSync(aidLocalDir(plan.aid)))
|
|
852
|
+
failure('INITIALIZE_TARGET_EXISTS');
|
|
853
|
+
if (dryRun) {
|
|
854
|
+
const { persona, ...safe } = plan;
|
|
855
|
+
return { ok: true, dryRun: true, aid: plan.aid, plan: safe, fingerprint, persona: { provided: persona !== undefined } };
|
|
856
|
+
}
|
|
857
|
+
const r = {
|
|
858
|
+
version: 1, operationId: `init-${randomBytes(12).toString('hex')}`, actor, plan, fingerprint,
|
|
859
|
+
status: 'in_progress', phase: 'planned', startedAt: Date.now(), updatedAt: Date.now(),
|
|
860
|
+
};
|
|
861
|
+
saveInitialization(r);
|
|
862
|
+
this.launch(r);
|
|
863
|
+
return this.status(plan.aid);
|
|
864
|
+
}
|
|
865
|
+
resume(aid, actor) {
|
|
866
|
+
if (this.deps.authorize && !this.deps.authorize(actor))
|
|
867
|
+
failure('NOT_ALLOWED');
|
|
868
|
+
const r = readInitialization(aid);
|
|
869
|
+
if (!r)
|
|
870
|
+
failure('NOT_FOUND');
|
|
871
|
+
if (r.actor !== actor)
|
|
872
|
+
failure('INITIALIZE_OWNER_MISMATCH');
|
|
873
|
+
if (r.status === 'cancelled')
|
|
874
|
+
failure('INITIALIZE_CANCELLED');
|
|
875
|
+
if (r.status !== 'complete' && !this.running.has(aid)) {
|
|
876
|
+
validateInitializationPlan(r.plan);
|
|
877
|
+
r.status = 'in_progress';
|
|
878
|
+
delete r.error;
|
|
879
|
+
delete r.code;
|
|
880
|
+
saveInitialization(r);
|
|
881
|
+
this.launch(r);
|
|
882
|
+
}
|
|
883
|
+
return this.status(aid);
|
|
884
|
+
}
|
|
885
|
+
async cancel(aid) {
|
|
886
|
+
const r = readInitialization(aid);
|
|
887
|
+
if (!r || r.status === 'complete')
|
|
888
|
+
return;
|
|
889
|
+
r.status = 'cancelled';
|
|
890
|
+
r.code = 'INITIALIZE_CANCELLED';
|
|
891
|
+
saveInitialization(r);
|
|
892
|
+
await this.running.get(aid);
|
|
893
|
+
}
|
|
894
|
+
async settle(aid) { await this.running.get(aid); }
|
|
895
|
+
launch(r) {
|
|
896
|
+
const aid = r.plan.aid;
|
|
897
|
+
const work = Promise.resolve().then(() => this.run(r)).catch(error => {
|
|
898
|
+
// Cancellation wins over a stale worker, including delete while network I/O was pending.
|
|
899
|
+
let current;
|
|
900
|
+
try {
|
|
901
|
+
current = readInitialization(aid);
|
|
902
|
+
}
|
|
903
|
+
catch {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (!current || current.status === 'cancelled')
|
|
907
|
+
return;
|
|
908
|
+
r.status = 'failed';
|
|
909
|
+
r.code = error.code ?? 'INITIALIZE_FAILED';
|
|
910
|
+
// External provider errors may contain credentials or payloads; expose the failing phase instead.
|
|
911
|
+
r.error = `Initialization failed at ${r.phase} (${r.code})`;
|
|
912
|
+
saveInitialization(r);
|
|
913
|
+
}).catch(() => { })
|
|
914
|
+
.finally(() => this.running.delete(aid));
|
|
915
|
+
this.running.set(aid, work);
|
|
916
|
+
}
|
|
917
|
+
check(r) {
|
|
918
|
+
if (this.deps.authorize && !this.deps.authorize(r.actor))
|
|
919
|
+
failure('NOT_ALLOWED');
|
|
920
|
+
const current = readInitialization(r.plan.aid);
|
|
921
|
+
if (!current || current.operationId !== r.operationId || current.status === 'cancelled')
|
|
922
|
+
failure('INITIALIZE_CANCELLED');
|
|
923
|
+
const config = loadAgent(r.plan.aid);
|
|
924
|
+
if (r.configSaved && (!config || config.enabled === false))
|
|
925
|
+
failure('INITIALIZE_TARGET_CHANGED');
|
|
926
|
+
if (config && (config.initialization?.operationId !== r.operationId
|
|
927
|
+
|| JSON.stringify(config.owners) !== JSON.stringify(r.plan.owners)
|
|
928
|
+
|| config.active_baseagent !== r.plan.baseagent || config.projects?.defaultPath !== r.plan.project)) {
|
|
929
|
+
failure('INITIALIZE_TARGET_CHANGED');
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
phase(r, phase) {
|
|
933
|
+
this.check(r);
|
|
934
|
+
r.phase = phase;
|
|
935
|
+
saveInitialization(r);
|
|
936
|
+
}
|
|
937
|
+
async run(r) {
|
|
938
|
+
const p = r.plan;
|
|
939
|
+
this.phase(r, 'registering_aid');
|
|
940
|
+
await this.deps.register(p.aid);
|
|
941
|
+
this.phase(r, 'config_saved');
|
|
942
|
+
fs.mkdirSync(p.project, { recursive: true });
|
|
943
|
+
if (!loadAgent(p.aid)) {
|
|
944
|
+
saveAgent({
|
|
945
|
+
$schema_version: CONFIG_SCHEMA_VERSION, aid: p.aid, enabled: true, lifecycle: 'created',
|
|
946
|
+
initialization: { mode: 'supplied', operationId: r.operationId }, channels: [], owners: p.owners,
|
|
947
|
+
projects: { defaultPath: p.project }, active_baseagent: p.baseagent,
|
|
948
|
+
baseagents: { [p.baseagent]: {} }, chatmode: { private: 'interactive', group: 'proactive', nothuman: 'proactive' },
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
r.configSaved = true;
|
|
952
|
+
this.phase(r, 'persona_saved');
|
|
953
|
+
ensureAgentDirSkeleton(p.aid);
|
|
954
|
+
ensureInitialRoleRegistry(p.aid);
|
|
955
|
+
if (p.persona !== undefined) {
|
|
956
|
+
const file = path.join(agentPersonalDir(p.aid), 'persona.md');
|
|
957
|
+
if (fs.existsSync(file) && fs.readFileSync(file, 'utf8') !== p.persona)
|
|
958
|
+
failure('INITIALIZE_PLAN_CONFLICT');
|
|
959
|
+
fs.writeFileSync(file, p.persona, { encoding: 'utf8', mode: 0o600 });
|
|
960
|
+
}
|
|
961
|
+
await syncOwnerContactsAfterCreate(p.aid, p.owners);
|
|
962
|
+
this.phase(r, 'uploading_agentmd');
|
|
963
|
+
let remote;
|
|
964
|
+
if (r.publicationStarted) {
|
|
965
|
+
// Resolve unknown publish outcomes before attempting a second write.
|
|
966
|
+
try {
|
|
967
|
+
remote = await this.deps.fetch(p.aid);
|
|
968
|
+
this.check(r);
|
|
969
|
+
if (!matchesCard(remote.content, p) || !remote.verified) {
|
|
970
|
+
if (r.published || (r.previousCardHash !== 'none' && cardHash(remote.content) !== r.previousCardHash)) {
|
|
971
|
+
failure('INITIALIZE_AGENTMD_VERIFY_FAILED');
|
|
972
|
+
}
|
|
973
|
+
remote = undefined; // The pre-publication card is still remote: retry the same frozen plan.
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
catch (error) {
|
|
977
|
+
const code = String(error.code ?? '');
|
|
978
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
979
|
+
if (code !== 'AGENTMD_NOT_FOUND' && !/not found|404|missing/i.test(message)) {
|
|
980
|
+
failure('INITIALIZE_AGENTMD_VERIFY_FAILED');
|
|
981
|
+
}
|
|
982
|
+
remote = undefined;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
try {
|
|
987
|
+
const previous = await this.deps.fetch(p.aid);
|
|
988
|
+
this.check(r);
|
|
989
|
+
r.previousCardHash = cardHash(previous.content);
|
|
990
|
+
}
|
|
991
|
+
catch (error) {
|
|
992
|
+
const code = String(error.code ?? '');
|
|
993
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
994
|
+
if (code !== 'AGENTMD_NOT_FOUND' && !/not found|404|missing/i.test(message))
|
|
995
|
+
failure('INITIALIZE_AGENTMD_VERIFY_FAILED');
|
|
996
|
+
// A newly registered AID normally has no remote card yet.
|
|
997
|
+
r.previousCardHash = 'none';
|
|
998
|
+
this.check(r);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (!remote) {
|
|
1002
|
+
r.publicationStarted = true;
|
|
1003
|
+
saveInitialization(r);
|
|
1004
|
+
await this.deps.publish(p.aid, buildInitialAgentMd(p));
|
|
1005
|
+
this.check(r);
|
|
1006
|
+
r.published = true;
|
|
1007
|
+
saveInitialization(r);
|
|
1008
|
+
remote = await this.deps.fetch(p.aid);
|
|
1009
|
+
}
|
|
1010
|
+
this.check(r);
|
|
1011
|
+
if (!remote.verified || !matchesCard(remote.content, p))
|
|
1012
|
+
failure('INITIALIZE_AGENTMD_VERIFY_FAILED');
|
|
1013
|
+
if (!fs.existsSync(agentMdPath(p.aid)) || !matchesCard(fs.readFileSync(agentMdPath(p.aid), 'utf8'), p)) {
|
|
1014
|
+
failure('INITIALIZE_AGENTMD_VERIFY_FAILED');
|
|
1015
|
+
}
|
|
1016
|
+
r.published = true;
|
|
1017
|
+
r.verified = true;
|
|
1018
|
+
this.phase(r, 'hot_loading');
|
|
1019
|
+
const config = loadAgent(p.aid);
|
|
1020
|
+
if (config.lifecycle !== 'active') {
|
|
1021
|
+
config.lifecycle = 'bootstrapping';
|
|
1022
|
+
saveAgent(config);
|
|
1023
|
+
}
|
|
1024
|
+
await this.deps.hotLoad(p.aid);
|
|
1025
|
+
this.phase(r, 'activating');
|
|
1026
|
+
if (p.persona !== undefined && fs.readFileSync(path.join(agentPersonalDir(p.aid), 'persona.md'), 'utf8') !== p.persona) {
|
|
1027
|
+
failure('INITIALIZE_PLAN_CONFLICT');
|
|
1028
|
+
}
|
|
1029
|
+
r.activationPrepared = true;
|
|
1030
|
+
saveInitialization(r);
|
|
1031
|
+
const result = await this.deps.activate(p.aid);
|
|
1032
|
+
this.check(r);
|
|
1033
|
+
if (!result.ok)
|
|
1034
|
+
failure(result.code ?? 'INITIALIZE_WELCOME_UNAVAILABLE');
|
|
1035
|
+
if (loadAgent(p.aid)?.lifecycle !== 'active')
|
|
1036
|
+
failure('INITIALIZE_STATE_INVALID');
|
|
1037
|
+
// Activation is durable even if a post-commit network delivery failed.
|
|
1038
|
+
r.welcome = result.welcome;
|
|
1039
|
+
r.phase = 'active';
|
|
1040
|
+
r.status = 'complete';
|
|
1041
|
+
saveInitialization(r);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
/** Called only after the IPC adapter has validated the local control token. */
|
|
1045
|
+
export function executeLocalInitializationCommand(service, command) {
|
|
1046
|
+
try {
|
|
1047
|
+
if (!Array.isArray(command.argv) || command.argv.some(value => typeof value !== 'string')) {
|
|
1048
|
+
return { ok: false, code: 'INVALID_ARGS', error: 'argv required' };
|
|
1049
|
+
}
|
|
1050
|
+
const argv = command.argv;
|
|
1051
|
+
const parsed = parseInitializationArgs(argv);
|
|
1052
|
+
if (command.poll === true)
|
|
1053
|
+
return service.status(parsed.aid);
|
|
1054
|
+
if (parsed.resume) {
|
|
1055
|
+
const record = readInitialization(parsed.aid);
|
|
1056
|
+
if (!record)
|
|
1057
|
+
return { ok: false, code: 'NOT_FOUND', error: 'No initialization record' };
|
|
1058
|
+
return service.resume(parsed.aid, record.actor);
|
|
1059
|
+
}
|
|
1060
|
+
return service.start(resolveInitializationPlan(argv), 'local-control', parsed.dryRun);
|
|
1061
|
+
}
|
|
1062
|
+
catch (error) {
|
|
1063
|
+
return {
|
|
1064
|
+
ok: false,
|
|
1065
|
+
code: error.code ?? 'INVALID_ARGS',
|
|
1066
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
/** Shared strict grammar for CLI, permission inspection and daemon dispatch. */
|
|
1071
|
+
export function parseInitializationArgs(args) {
|
|
1072
|
+
const resume = args[0] === 'initialize';
|
|
1073
|
+
if ((!resume && args[0] !== 'new') || !isValidAid(args[1] ?? ''))
|
|
1074
|
+
throw new Error('Invalid initialization command or AID');
|
|
1075
|
+
const booleans = new Set(resume ? ['--resume'] : ['--initialize', '--non-interactive', '--dry-run']);
|
|
1076
|
+
const strings = new Set(resume ? ['--wait', '--format'] : [
|
|
1077
|
+
'--project', '--baseagent', '--owner', '--owners', '--name', '--description', '--tags', '--type',
|
|
1078
|
+
'--persona-file', '--persona-text', '--wait', '--format',
|
|
1079
|
+
]);
|
|
1080
|
+
const seen = new Set();
|
|
1081
|
+
const values = {};
|
|
1082
|
+
for (let i = 2; i < args.length; i++) {
|
|
1083
|
+
const flag = args[i];
|
|
1084
|
+
if (seen.has(flag) || (!strings.has(flag) && !booleans.has(flag)))
|
|
1085
|
+
throw new Error(`Unsupported or repeated option: ${flag}`);
|
|
1086
|
+
seen.add(flag);
|
|
1087
|
+
if (strings.has(flag)) {
|
|
1088
|
+
const value = args[++i];
|
|
1089
|
+
if (value === undefined || value.startsWith('--'))
|
|
1090
|
+
throw new Error(`${flag} requires a value`);
|
|
1091
|
+
values[flag] = value;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
if (!seen.has(resume ? '--resume' : '--initialize'))
|
|
1095
|
+
throw new Error(resume ? '--resume required' : '--initialize required');
|
|
1096
|
+
if (values['--format'] !== undefined && values['--format'] !== 'json')
|
|
1097
|
+
throw new Error('--format must be json');
|
|
1098
|
+
if (seen.has('--persona-file') && seen.has('--persona-text'))
|
|
1099
|
+
throw new Error('persona sources are mutually exclusive');
|
|
1100
|
+
if (!resume) {
|
|
1101
|
+
for (const key of ['--name', '--description', '--tags'])
|
|
1102
|
+
if (!values[key]?.trim())
|
|
1103
|
+
throw new Error(`${key} must be explicitly supplied`);
|
|
1104
|
+
parseAgentOwnerArgs(args);
|
|
1105
|
+
parseAgentTags(values['--tags']);
|
|
1106
|
+
}
|
|
1107
|
+
const wait = values['--wait'] === undefined ? 60 : Number(values['--wait']);
|
|
1108
|
+
if (!Number.isInteger(wait) || wait < 0 || wait > 300)
|
|
1109
|
+
throw new Error('--wait must be an integer from 0 to 300');
|
|
1110
|
+
return { aid: args[1], resume, dryRun: seen.has('--dry-run'), wait, values };
|
|
1111
|
+
}
|
|
1112
|
+
export function resolveInitializationPlan(args) {
|
|
1113
|
+
const parsed = parseInitializationArgs(args);
|
|
1114
|
+
if (parsed.resume)
|
|
1115
|
+
throw new Error('A resume request cannot supply a new plan');
|
|
1116
|
+
const v = parsed.values;
|
|
1117
|
+
if (v['--persona-file'] !== undefined)
|
|
1118
|
+
throw new Error('CLI must submit persona file contents as text');
|
|
1119
|
+
const p = resolveAgentCreatePlan({
|
|
1120
|
+
aid: parsed.aid, project: v['--project'], baseagent: v['--baseagent'], ...parseAgentOwnerArgs(args),
|
|
1121
|
+
name: v['--name'], description: v['--description'], tags: parseAgentTags(v['--tags']), type: v['--type'],
|
|
1122
|
+
});
|
|
1123
|
+
return {
|
|
1124
|
+
aid: p.aid, project: p.project, baseagent: p.baseagent ?? '', owners: p.owners ?? (p.owner ? [p.owner] : []),
|
|
1125
|
+
name: p.name, description: p.description, tags: p.tags, type: p.type,
|
|
1126
|
+
...(v['--persona-text'] !== undefined ? { persona: v['--persona-text'] } : {}),
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
export function readInitializationPersona(file, managed) {
|
|
1130
|
+
const resolved = path.resolve(file);
|
|
1131
|
+
if (!/\.(md|txt)$/i.test(resolved))
|
|
1132
|
+
throw new Error('persona file must be .md or .txt');
|
|
1133
|
+
const stat = fs.lstatSync(resolved);
|
|
1134
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 65536)
|
|
1135
|
+
throw new Error('persona must be a regular text file of at most 64 KiB');
|
|
1136
|
+
if (managed) {
|
|
1137
|
+
const real = fs.realpathSync(resolved);
|
|
1138
|
+
const roots = [process.cwd(), process.env.TMPDIR].filter((r) => !!r && path.isAbsolute(r));
|
|
1139
|
+
const allowed = roots.some(root => isSameOrDescendant(real, fs.realpathSync(root)));
|
|
1140
|
+
if (!allowed || isHClassPath(real) || isLClassPath(real))
|
|
1141
|
+
throw new Error('persona file must be inside the project or current TMPDIR');
|
|
1142
|
+
}
|
|
1143
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(fs.readFileSync(resolved));
|
|
1144
|
+
const error = validateInitializationPersonaContent(text);
|
|
1145
|
+
if (error)
|
|
1146
|
+
throw new Error(error);
|
|
1147
|
+
return text;
|
|
1148
|
+
}
|
|
1149
|
+
export async function runInitializationCommand(args) {
|
|
1150
|
+
const parsed = parseInitializationArgs(args);
|
|
1151
|
+
const managed = !!process.env.EVOLCORE_SESSION_ID;
|
|
1152
|
+
const argv = [...args];
|
|
1153
|
+
if (parsed.values['--persona-file'] !== undefined) {
|
|
1154
|
+
const at = argv.indexOf('--persona-file');
|
|
1155
|
+
argv.splice(at, 2, '--persona-text', readInitializationPersona(parsed.values['--persona-file'], managed));
|
|
1156
|
+
}
|
|
1157
|
+
if (!parsed.resume) {
|
|
1158
|
+
const at = argv.indexOf('--project');
|
|
1159
|
+
if (at < 0)
|
|
1160
|
+
argv.push('--project', process.cwd());
|
|
1161
|
+
else
|
|
1162
|
+
argv[at + 1] = path.resolve(argv[at + 1]);
|
|
1163
|
+
}
|
|
1164
|
+
const request = async (poll) => {
|
|
1165
|
+
if (managed) {
|
|
1166
|
+
const { runManagedAgentOperation } = await import('./agent.js');
|
|
1167
|
+
const r = await runManagedAgentOperation(poll ? ['show', parsed.aid, '--format', 'json'] : argv);
|
|
1168
|
+
if (!poll || !r.ok || !r.initialization)
|
|
1169
|
+
return r;
|
|
1170
|
+
const init = r.initialization;
|
|
1171
|
+
return { ...init, ok: !['failed', 'cancelled'].includes(init.status), aid: parsed.aid,
|
|
1172
|
+
accepted: true, lifecycle: r.lifecycle, usable: init.complete && r.connection?.status === 'connected' };
|
|
1173
|
+
}
|
|
1174
|
+
return await ipcQuery(resolvePaths().socket, {
|
|
1175
|
+
type: 'evolagent.initialize', argv, poll,
|
|
1176
|
+
}, 10_000) ?? { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'Initialization requires a running daemon' };
|
|
1177
|
+
};
|
|
1178
|
+
let result = await request(false);
|
|
1179
|
+
if (parsed.dryRun)
|
|
1180
|
+
return result;
|
|
1181
|
+
const deadline = Date.now() + parsed.wait * 1000;
|
|
1182
|
+
while (result.ok && !result.complete && result.accepted && Date.now() < deadline) {
|
|
1183
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1184
|
+
result = await request(true);
|
|
1185
|
+
}
|
|
1186
|
+
if (result.ok && !result.complete && result.accepted)
|
|
1187
|
+
return { ...result, ok: false, code: 'INITIALIZE_PENDING', error: 'Initialization is still running; inspect agent show' };
|
|
1188
|
+
if (result.complete && !result.usable)
|
|
1189
|
+
return { ...result, ok: false, code: 'INITIALIZE_NOT_USABLE', error: 'Initialized; runtime is not currently connected' };
|
|
1190
|
+
return result;
|
|
576
1191
|
}
|