evolcore 0.0.26 → 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 +25 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +63 -26
- package/dist/agents/codex-runner.js +316 -411
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- 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} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +12 -7
- 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 +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- 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 +522 -233
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +35 -23
- package/dist/cli/diag.js +2 -2
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +9 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +16 -2
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -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 +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- 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 +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- 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 +76 -39
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +348 -77
- package/dist/core/command/menu-protocol.js +40 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +37 -22
- 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/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +76 -15
- package/dist/core/message/message-bridge.js +94 -11
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +709 -117
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +61 -29
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +7 -7
- 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 +308 -54
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- 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/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +15 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- 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,7 +1,9 @@
|
|
|
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';
|
|
@@ -13,7 +15,20 @@ import { resolveEcagentConfig } from '../agents/baseagent.js';
|
|
|
13
15
|
import { AUTO_BASEAGENT_CANDIDATES, BASEAGENT_CANDIDATES, detectAvailableBaseagents, } from '../agents/baseagent-availability.js';
|
|
14
16
|
import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/project-path.js';
|
|
15
17
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
16
|
-
import { AGENT_DESCRIPTION_MAX_LENGTH, AGENT_TAG_MAX_COUNT, AGENT_TAG_MIN_COUNT, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_TYPE, normalizeAgentType, validateAgentTags, validateAgentType, } 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
|
+
}
|
|
17
32
|
export function parseAgentTags(raw) {
|
|
18
33
|
if (raw === undefined)
|
|
19
34
|
return undefined;
|
|
@@ -278,6 +293,8 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
278
293
|
}
|
|
279
294
|
aid = candidate;
|
|
280
295
|
}
|
|
296
|
+
if (hasSuppliedInitialization(aid))
|
|
297
|
+
return { ok: false, error: 'Use agent initialize --resume for this target' };
|
|
281
298
|
const agentDirPath = path.join(p.agentsDir, aid);
|
|
282
299
|
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
283
300
|
if (configExists) {
|
|
@@ -537,6 +554,8 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
|
|
|
537
554
|
}
|
|
538
555
|
if (!isValidAid(opts.aid))
|
|
539
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');
|
|
540
559
|
const agentDirPath = path.join(p.agentsDir, opts.aid);
|
|
541
560
|
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
542
561
|
if (configExists && !opts.force)
|
|
@@ -676,3 +695,497 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
|
|
|
676
695
|
...ownerContactSync,
|
|
677
696
|
};
|
|
678
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;
|
|
1191
|
+
}
|