evolcore 0.0.35 → 0.0.37
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 +42 -0
- package/README.md +6 -0
- package/bin/install-codex-managed-hooks.mjs +20 -3
- package/dist/agents/baseagent-availability.js +1 -1
- package/dist/agents/baseagent.js +88 -0
- package/dist/agents/claude-runner.js +59 -12
- package/dist/agents/codex/app-server-client.js +18 -1
- package/dist/agents/codex-runner.js +262 -21
- package/dist/agents/ecagent-runner.js +47 -11
- package/dist/agents/gemini-runner.js +36 -12
- package/dist/aun/aid/client.js +4 -3
- package/dist/aun/aid/control-aid.js +270 -8
- package/dist/aun/aid/identity.js +6 -3
- package/dist/aun/aid/ownership-operation.js +58 -0
- package/dist/aun/aid/store.js +1 -0
- package/dist/aun/group/fs-download.js +67 -2
- package/dist/aun/group/history.js +20 -5
- package/dist/aun/group/operations.js +8 -2
- package/dist/aun/menu/client.js +39 -2
- package/dist/aun/menu/transport.js +1 -1
- package/dist/aun/msg/history.js +43 -4
- package/dist/aun/msg/managed-operation.js +30 -53
- package/dist/aun/msg/p2p.js +51 -15
- package/dist/aun/outbox.js +4 -0
- package/dist/channels/aun.js +260 -73
- package/dist/channels/daemon.js +6 -4
- package/dist/channels/feishu.js +3 -0
- package/dist/cli/agent-command.js +161 -223
- package/dist/cli/agent-create.js +190 -223
- package/dist/cli/agent.js +3 -1
- package/dist/cli/aid-bind-command.js +53 -0
- package/dist/cli/aun-commands.js +384 -91
- package/dist/cli/bench.js +1 -1
- package/dist/cli/cli-argv.js +74 -0
- package/dist/cli/daemon-commands.js +105 -74
- package/dist/cli/diag.js +1 -1
- package/dist/cli/fs-command.js +18 -19
- package/dist/cli/getmeta-command.js +159 -6
- package/dist/cli/index.js +6 -12
- package/dist/cli/init-channel.js +8 -55
- package/dist/cli/init.js +125 -218
- package/dist/cli/queue-command.js +11 -8
- package/dist/cli/restart-monitor.js +177 -85
- package/dist/cli/session-command.js +1 -1
- package/dist/cli/stats.js +7 -5
- package/dist/cli/thread-command.js +103 -30
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-msg-diag.js +1 -0
- package/dist/config/access-policy.js +2 -2
- package/dist/config/boot-log.js +14 -11
- package/dist/config/config-field-policy.js +9 -1
- package/dist/config/config-manager.js +176 -90
- package/dist/config/config-operation-service.js +7 -14
- package/dist/config/contact-book-store.js +142 -26
- package/dist/config/contact-book.js +4 -2
- package/dist/config/lifecycle.js +1 -1
- package/dist/config/owner-config.js +96 -0
- package/dist/config/peer-role-resolver.js +12 -18
- package/dist/config/providers-config.js +382 -0
- package/dist/config/providers-import.js +74 -0
- package/dist/config/providers-management.js +328 -0
- package/dist/config/providers.js +22 -0
- package/dist/config/resolved-config-op.js +17 -14
- package/dist/config/snapshot.js +28 -16
- package/dist/config-store.js +18 -14
- package/dist/core/agent-ipc-registration.js +21 -63
- package/dist/core/agent-runtime.js +16 -5
- package/dist/core/auth/agent-delegation.js +199 -25
- package/dist/core/auth/auth-gateway.js +44 -2
- package/dist/core/auth/authorization-denial.js +1 -1
- package/dist/core/auth/debug-context.js +2 -1
- package/dist/core/auth/delegation-authorization.js +2 -2
- package/dist/core/auth/delegation-diagnostics.js +4 -1
- package/dist/core/auth/operation-authorizer.js +10 -1
- package/dist/core/auth/operation-catalog.js +19 -1
- package/dist/core/bootstrap-service.js +43 -20
- package/dist/core/channel-loader.js +0 -1
- package/dist/core/channel-runtime.js +30 -4
- package/dist/core/command/agent-control.js +196 -37
- package/dist/core/command/cli-intent-parser.js +60 -4
- package/dist/core/command/command-handler.js +212 -60
- package/dist/core/command/connect-menu.js +1 -0
- package/dist/core/command/diag-service.js +5 -4
- package/dist/core/command/file-search.js +368 -0
- package/dist/core/command/menu-catalog.js +2 -1
- package/dist/core/command/menu-handler.js +292 -107
- package/dist/core/command/menu-protocol.js +23 -0
- package/dist/core/command/role-menu.js +7 -19
- package/dist/core/command/skill-slash.js +130 -25
- package/dist/core/command/slash-handler.js +101 -53
- package/dist/core/command/topic-slash.js +16 -5
- package/dist/core/daemon-bootstrap.js +7 -1
- package/dist/core/daemon-control.js +19 -1
- package/dist/core/daemon-lifecycle.js +2 -1
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/evolagent-registry.js +13 -2
- package/dist/core/evolagent.js +3 -6
- package/dist/core/handoff/ipc-registration.js +2 -2
- package/dist/core/handoff/runtime.js +40 -6
- package/dist/core/handoff/store.js +1 -0
- package/dist/core/inference/text-inference.js +95 -6
- package/dist/core/ipc-registration.js +43 -24
- package/dist/core/memory/consolidation-context.js +4 -1
- package/dist/core/memory/consolidation.js +25 -14
- package/dist/core/message/aun-ipc-sender.js +296 -88
- package/dist/core/message/aun-request-registry.js +226 -0
- package/dist/core/message/create-status.js +33 -4
- package/dist/core/message/message-bridge.js +62 -13
- package/dist/core/message/message-log.js +0 -1
- package/dist/core/message/message-queue.js +4 -1
- package/dist/core/message/message-utils.js +8 -1
- package/dist/core/message/peer-mode.js +17 -9
- package/dist/core/message/response-engine.js +266 -124
- package/dist/core/message/retry-scheduler.js +11 -8
- package/dist/core/model/catalog-service.js +60 -18
- package/dist/core/model/model-catalog.js +112 -9
- package/dist/core/model/model-fallback.js +94 -26
- package/dist/core/ownership/owner-resolver.js +687 -0
- package/dist/core/permission/approval-gateway.js +196 -62
- package/dist/core/permission/ec-command-parser.js +138 -25
- package/dist/core/permission/filesystem-approval-scope.js +141 -0
- package/dist/core/permission/protected-paths.js +14 -1
- package/dist/core/permission/sandbox-runtime.js +171 -18
- package/dist/core/permission/tool-policy.js +13 -2
- package/dist/core/role/runtime-policy.js +3 -4
- package/dist/core/session/session-idle-monitor.js +10 -8
- package/dist/core/session/session-manager.js +72 -45
- package/dist/core/session/session-mapper.js +18 -7
- package/dist/core/session/session-renew.js +5 -4
- package/dist/core/session/session-title.js +2 -1
- package/dist/core/session/session-turn-coordinator.js +4 -2
- package/dist/core/skill/index-generator.js +2 -16
- package/dist/core/task/task-recovery-service.js +15 -2
- package/dist/core/thread/command-parser.js +28 -13
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +11 -8
- package/dist/core/thread/thread-operation.js +79 -44
- package/dist/core/thread/thread-route.js +54 -6
- package/dist/core/thread/thread-service.js +236 -45
- package/dist/core/thread/topic-id.js +248 -0
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/eck/message-renderer.js +12 -2
- package/dist/index.js +50 -27
- package/dist/ipc.js +59 -6
- package/dist/paths.js +1 -0
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +10 -3
- package/dist/stats/db.js +8 -0
- package/dist/stats/writer.js +26 -4
- package/dist/trigger/authorization.js +48 -3
- package/dist/trigger/feedback.js +28 -10
- package/dist/trigger/ipc-service.js +86 -10
- package/dist/trigger/local-files.js +55 -23
- package/dist/trigger/scheduler.js +57 -11
- package/dist/trigger/system-definitions.js +1 -0
- package/dist/trigger/validation.js +44 -15
- package/dist/utils/aid-bind.js +17 -297
- package/dist/utils/avatar-upload.js +16 -4
- package/dist/utils/cross-platform.js +115 -36
- package/dist/utils/ecweb-supervisor.js +1 -1
- package/dist/utils/ecweb-utils.js +8 -1
- package/dist/utils/error-utils.js +44 -6
- package/dist/utils/instance-registry.js +12 -2
- package/dist/utils/npm-ops.js +102 -27
- package/dist/utils/package-version-info.js +72 -27
- package/dist/utils/restart-safety.js +50 -0
- package/dist/utils/upgrade-jobs.js +304 -0
- package/dist/utils/windows-autostart.js +17 -10
- package/kits/docs/INDEX.md +5 -0
- package/kits/docs/aun/ABILITY_CALLING.md +239 -0
- package/kits/docs/evolcore/INDEX.md +14 -3
- package/kits/docs/evolcore/agent-create.md +55 -141
- package/kits/docs/evolcore/agent.md +38 -16
- package/kits/docs/evolcore/aid.md +34 -0
- package/kits/docs/evolcore/config.md +20 -1
- package/kits/docs/evolcore/event.md +2 -2
- package/kits/docs/evolcore/fs.md +18 -2
- package/kits/docs/evolcore/group-collaboration.md +13 -246
- package/kits/docs/evolcore/group.md +2 -1
- package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +65 -1
- package/kits/docs/evolcore/msg.md +64 -2
- package/kits/docs/evolcore/skill.md +86 -0
- package/kits/docs/evolcore/stats.md +6 -0
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +145 -51
- package/kits/docs/evolcore/trigger.md +48 -3
- package/kits/eck_manifest.json +21 -0
- package/kits/eck_message_manifest.json +4 -4
- package/kits/migrations/migrate-role-config-v5.mjs +20 -7
- package/kits/rules/01-overview.md +2 -1
- package/kits/rules/03-identity.md +3 -1
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/07-memory-and-thread.md +1 -1
- package/kits/schemas/_meta.json +16 -1
- package/kits/schemas/agent-config.schema.13.json +44 -2
- package/kits/schemas/contact-book.schema.3.json +5 -0
- package/kits/schemas/daemon.schema.6.json +3 -1
- package/kits/schemas/daemon.schema.8.json +329 -0
- package/kits/schemas/defaults.schema.5.json +39 -3
- package/kits/schemas/providers.schema.1.json +76 -0
- package/kits/schemas/relation-config.schema.9.json +5 -1
- package/kits/skills/.switches.json +28 -0
- package/kits/skills/aid-ability/SKILL.md +214 -0
- package/kits/skills/ec-agent-create/SKILL.md +162 -0
- package/kits/skills/ec-group/SKILL.md +2 -1
- package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
- package/kits/skills/ec-identity/SKILL.md +143 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
- package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
- package/kits/skills/ec-trigger/SKILL.md +167 -0
- package/kits/skills/ec-trigger/references/examples.md +153 -0
- package/kits/skills/thread-ops/SKILL.md +108 -420
- package/kits/templates/message-fragments/thread-context.md +1 -2
- package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
- package/kits/templates/roles/member.json +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +1 -0
- package/kits/templates/system-fragments/channel.md +2 -1
- package/kits/templates/system-fragments/commands.md +5 -4
- package/kits/templates/system-fragments/identity.md +2 -0
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -3
package/dist/utils/aid-bind.js
CHANGED
|
@@ -1,308 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { readInstalledEvolcoreVersion as readEvolcoreVersion } from './evolcore-version.js';
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
const BIND_TTL_MS = 10 * 60 * 1000;
|
|
3
|
+
const deferredMessage = 'Evol App ownership binding is deferred; use ec aid bind <ownerAid> [--subject <subjectAid>]';
|
|
5
4
|
export class BindService {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
constructor(opts) {
|
|
11
|
-
this.opts = opts;
|
|
12
|
-
}
|
|
13
|
-
startCleanup() {
|
|
14
|
-
if (this.cleanupTimer)
|
|
15
|
-
return;
|
|
16
|
-
this.cleanupTimer = setInterval(() => this.cleanup(), 60_000);
|
|
17
|
-
this.cleanupTimer.unref?.();
|
|
18
|
-
}
|
|
19
|
-
stopCleanup() {
|
|
20
|
-
if (!this.cleanupTimer)
|
|
21
|
-
return;
|
|
22
|
-
clearInterval(this.cleanupTimer);
|
|
23
|
-
this.cleanupTimer = null;
|
|
24
|
-
}
|
|
25
|
-
begin(req) {
|
|
26
|
-
if (req.bindType !== 'daemon' && req.bindType !== 'agent') {
|
|
27
|
-
return { ok: false, code: 'INVALID_BIND_TYPE', error: 'invalid bindType' };
|
|
28
|
-
}
|
|
29
|
-
const targetAid = req.bindType === 'daemon' ? (req.targetAid ?? this.opts.receiverAid) : req.targetAid;
|
|
30
|
-
if (!targetAid || typeof targetAid !== 'string') {
|
|
31
|
-
return { ok: false, code: 'MISSING_TARGET', error: 'missing targetAid' };
|
|
32
|
-
}
|
|
33
|
-
if (req.bindType === 'daemon' && targetAid !== this.opts.receiverAid) {
|
|
34
|
-
return { ok: false, code: 'TARGET_MISMATCH', error: 'daemon targetAid must equal control AID' };
|
|
35
|
-
}
|
|
36
|
-
if (req.bindType === 'agent' && !loadAgent(targetAid)) {
|
|
37
|
-
return { ok: false, code: 'TARGET_NOT_FOUND', error: `agent not found: ${targetAid}` };
|
|
38
|
-
}
|
|
39
|
-
const taskId = crypto.randomUUID();
|
|
40
|
-
let token = generatePublicToken();
|
|
41
|
-
let tokenHash = hashToken(token);
|
|
42
|
-
while (this.taskIdsByTokenHash.has(tokenHash)) {
|
|
43
|
-
token = generatePublicToken();
|
|
44
|
-
tokenHash = hashToken(token);
|
|
45
|
-
}
|
|
46
|
-
const ttlMs = clampTtl(req.ttlMs);
|
|
47
|
-
const expiresAt = Date.now() + ttlMs;
|
|
48
|
-
const task = {
|
|
49
|
-
taskId,
|
|
50
|
-
bindType: req.bindType,
|
|
51
|
-
receiverAid: this.opts.receiverAid,
|
|
52
|
-
targetAid,
|
|
53
|
-
agentName: req.agentName,
|
|
54
|
-
tokenHash,
|
|
55
|
-
createdAt: Date.now(),
|
|
56
|
-
expiresAt,
|
|
57
|
-
status: 'pending',
|
|
58
|
-
};
|
|
59
|
-
this.tasks.set(taskId, task);
|
|
60
|
-
this.taskIdsByTokenHash.set(tokenHash, taskId);
|
|
61
|
-
this.cleanup();
|
|
5
|
+
constructor(_options) { }
|
|
6
|
+
startCleanup() { }
|
|
7
|
+
stopCleanup() { }
|
|
8
|
+
begin(_request) {
|
|
62
9
|
return {
|
|
63
10
|
ok: true,
|
|
64
|
-
taskId,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
type: 'evolcore.bind',
|
|
68
|
-
version: readEvolcoreVersion(),
|
|
69
|
-
bindType: req.bindType,
|
|
70
|
-
daemonAid: this.opts.receiverAid,
|
|
71
|
-
...(req.bindType === 'daemon' ? { platform: getPlatform() } : {}),
|
|
72
|
-
...(req.bindType === 'agent' ? { agentAid: targetAid, agentName: req.agentName ?? null } : {}),
|
|
73
|
-
token,
|
|
74
|
-
expiresAt,
|
|
75
|
-
},
|
|
11
|
+
taskId: randomUUID(),
|
|
12
|
+
qrData: {},
|
|
13
|
+
expiresAt: Date.now() + BIND_TTL_MS,
|
|
76
14
|
};
|
|
77
15
|
}
|
|
78
|
-
status(
|
|
79
|
-
|
|
80
|
-
return { ok: false, code: 'MISSING_TASK', error: 'missing taskId' };
|
|
81
|
-
const task = this.tasks.get(taskId);
|
|
82
|
-
if (!task)
|
|
83
|
-
return { ok: false, code: 'TASK_NOT_FOUND', error: 'binding task not found' };
|
|
84
|
-
if (task.status === 'pending' && Date.now() > task.expiresAt) {
|
|
85
|
-
task.status = 'expired';
|
|
86
|
-
task.finishedAt ??= task.expiresAt;
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
ok: true,
|
|
90
|
-
taskId: task.taskId,
|
|
91
|
-
status: task.status,
|
|
92
|
-
boundAid: task.boundAid,
|
|
93
|
-
boundName: task.boundName,
|
|
94
|
-
};
|
|
16
|
+
status(_taskId) {
|
|
17
|
+
return { ok: false, code: 'OWNERSHIP_APP_DEFERRED', error: deferredMessage };
|
|
95
18
|
}
|
|
96
|
-
cancel(
|
|
97
|
-
|
|
98
|
-
return { ok: false, code: 'MISSING_TASK', error: 'missing taskId' };
|
|
99
|
-
const task = this.tasks.get(taskId);
|
|
100
|
-
if (!task)
|
|
101
|
-
return { ok: false, code: 'TASK_NOT_FOUND', error: 'binding task not found' };
|
|
102
|
-
if (task.status === 'pending') {
|
|
103
|
-
task.status = 'cancelled';
|
|
104
|
-
task.finishedAt = Date.now();
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
ok: true,
|
|
108
|
-
taskId: task.taskId,
|
|
109
|
-
status: task.status,
|
|
110
|
-
boundAid: task.boundAid,
|
|
111
|
-
boundName: task.boundName,
|
|
112
|
-
};
|
|
19
|
+
cancel(_taskId) {
|
|
20
|
+
return { ok: false, code: 'OWNERSHIP_APP_DEFERRED', error: deferredMessage };
|
|
113
21
|
}
|
|
114
|
-
async handleRequest(payload,
|
|
22
|
+
async handleRequest(payload, _fromAid) {
|
|
115
23
|
if (payload?.type !== 'bind.request')
|
|
116
24
|
return null;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
type: 'bind.response',
|
|
120
|
-
bindType,
|
|
121
|
-
success: false,
|
|
122
|
-
error: { code, message },
|
|
123
|
-
});
|
|
124
|
-
if (!payload.token)
|
|
125
|
-
return fail('INVALID_TOKEN', 'invalid token');
|
|
126
|
-
const taskId = this.taskIdsByTokenHash.get(hashToken(payload.token));
|
|
127
|
-
if (!taskId)
|
|
128
|
-
return fail('TASK_NOT_FOUND', 'binding task not found');
|
|
129
|
-
const task = this.tasks.get(taskId);
|
|
130
|
-
if (!task)
|
|
131
|
-
return fail('TASK_NOT_FOUND', 'binding task not found');
|
|
132
|
-
if (payload.bindType !== task.bindType)
|
|
133
|
-
return fail('BIND_TYPE_MISMATCH', 'bindType mismatch');
|
|
134
|
-
if (!fromAid)
|
|
135
|
-
return fail('INTERNAL_ERROR', 'missing AUN sender');
|
|
136
|
-
if (task.status === 'pending' && Date.now() > task.expiresAt) {
|
|
137
|
-
task.status = 'expired';
|
|
138
|
-
task.finishedAt ??= task.expiresAt;
|
|
139
|
-
return fail('TASK_EXPIRED', 'binding task expired');
|
|
140
|
-
}
|
|
141
|
-
if (task.status === 'bound') {
|
|
142
|
-
// The owner write and task transition necessarily happen before the caller
|
|
143
|
-
// can send bind.response. If that send fails, the App retries the same
|
|
144
|
-
// authenticated request. Replay the original success only to the AID that
|
|
145
|
-
// completed the bind; other senders must not receive daemonInfo.
|
|
146
|
-
if (task.boundAid === fromAid && task.boundResponse)
|
|
147
|
-
return task.boundResponse;
|
|
148
|
-
return fail('ALREADY_BOUND', 'binding task already completed');
|
|
149
|
-
}
|
|
150
|
-
if (task.status !== 'pending')
|
|
151
|
-
return fail('TASK_NOT_FOUND', 'binding task is not pending');
|
|
152
|
-
if (!timingSafeTokenEqual(payload.token, task.tokenHash)) {
|
|
153
|
-
return fail('INVALID_TOKEN', 'invalid token');
|
|
154
|
-
}
|
|
155
|
-
try {
|
|
156
|
-
// Build and retain the exact response before committing the bind so a
|
|
157
|
-
// post-commit transport failure can be recovered by an idempotent retry.
|
|
158
|
-
const response = this.buildSuccessResponse(task);
|
|
159
|
-
this.applyOwnerBinding(task, fromAid);
|
|
160
|
-
task.status = 'bound';
|
|
161
|
-
task.boundAid = fromAid;
|
|
162
|
-
task.boundName = payload.clientName;
|
|
163
|
-
task.boundResponse = response;
|
|
164
|
-
task.finishedAt = Date.now();
|
|
165
|
-
return response;
|
|
166
|
-
}
|
|
167
|
-
catch (e) {
|
|
168
|
-
return fail('INTERNAL_ERROR', e?.message || String(e));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
buildSuccessResponse(task) {
|
|
172
|
-
const details = task.bindType === 'agent' ? this.buildAgentBindDetails(task) : null;
|
|
173
|
-
return {
|
|
174
|
-
type: 'bind.response',
|
|
175
|
-
bindType: task.bindType,
|
|
176
|
-
success: true,
|
|
177
|
-
daemonInfo: {
|
|
178
|
-
daemonAid: task.receiverAid,
|
|
179
|
-
...(task.bindType === 'agent' ? { agentAid: task.targetAid } : {}),
|
|
180
|
-
bindType: task.bindType,
|
|
181
|
-
...(task.bindType === 'daemon'
|
|
182
|
-
? {
|
|
183
|
-
version: readEvolcoreVersion(),
|
|
184
|
-
platform: getPlatform(),
|
|
185
|
-
uptime: this.opts.getUptimeSeconds(),
|
|
186
|
-
baseagents: this.opts.getAvailableBaseagents(),
|
|
187
|
-
}
|
|
188
|
-
: {
|
|
189
|
-
active_baseagent: details.baseagent.active_baseagent,
|
|
190
|
-
model: details.baseagent.model,
|
|
191
|
-
effort: details.baseagent.effort,
|
|
192
|
-
channels: details.channels,
|
|
193
|
-
agentName: task.agentName,
|
|
194
|
-
}),
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
buildAgentBindDetails(task) {
|
|
199
|
-
const agent = loadAgent(task.targetAid);
|
|
200
|
-
const config = resolveEffective({ self: task.targetAid }, { expand: true });
|
|
201
|
-
return {
|
|
202
|
-
baseagent: resolveBaseagentInfo(config),
|
|
203
|
-
channels: summarizeChannels(agent?.channels ?? []),
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
applyOwnerBinding(task, ownerAid) {
|
|
207
|
-
if (task.bindType === 'daemon') {
|
|
208
|
-
const cfg = loadDaemonConfig();
|
|
209
|
-
const current = cfg.owners ?? [];
|
|
210
|
-
const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
211
|
-
saveDaemonConfig({ ...cfg, $schema_version: cfg.$schema_version ?? 1, owners });
|
|
212
|
-
this.opts.onDaemonOwnersUpdated?.(owners);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
const agent = loadAgent(task.targetAid);
|
|
216
|
-
if (!agent)
|
|
217
|
-
throw new Error(`agent not found: ${task.targetAid}`);
|
|
218
|
-
const current = agent.owners ?? [];
|
|
219
|
-
const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
220
|
-
saveAgent({ ...agent, owners });
|
|
221
|
-
this.opts.onAgentOwnerUpdated?.(task.targetAid, ownerAid);
|
|
222
|
-
}
|
|
223
|
-
cleanup() {
|
|
224
|
-
const now = Date.now();
|
|
225
|
-
for (const [id, task] of this.tasks) {
|
|
226
|
-
if (task.status === 'pending' && now > task.expiresAt) {
|
|
227
|
-
task.status = 'expired';
|
|
228
|
-
task.finishedAt ??= task.expiresAt;
|
|
229
|
-
}
|
|
230
|
-
if (task.status === 'cancelled') {
|
|
231
|
-
this.deleteTask(id);
|
|
232
|
-
}
|
|
233
|
-
else if (task.status === 'expired' && now - (task.finishedAt ?? task.expiresAt) > 10 * 60_000) {
|
|
234
|
-
this.deleteTask(id);
|
|
235
|
-
}
|
|
236
|
-
else if (task.status === 'bound' && task.finishedAt && now - task.finishedAt > 10 * 60_000) {
|
|
237
|
-
this.deleteTask(id);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (this.tasks.size <= 128)
|
|
241
|
-
return;
|
|
242
|
-
const sorted = [...this.tasks.values()].sort((a, b) => a.createdAt - b.createdAt);
|
|
243
|
-
for (const task of sorted) {
|
|
244
|
-
if (this.tasks.size <= 128)
|
|
245
|
-
break;
|
|
246
|
-
if (task.status !== 'pending')
|
|
247
|
-
this.deleteTask(task.taskId);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
deleteTask(taskId) {
|
|
251
|
-
const task = this.tasks.get(taskId);
|
|
252
|
-
if (!task)
|
|
253
|
-
return;
|
|
254
|
-
this.tasks.delete(taskId);
|
|
255
|
-
this.taskIdsByTokenHash.delete(task.tokenHash);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
function clampTtl(ttlMs) {
|
|
259
|
-
if (!ttlMs || !Number.isFinite(ttlMs))
|
|
260
|
-
return 10 * 60_000;
|
|
261
|
-
return Math.max(60_000, Math.min(ttlMs, 10 * 60_000));
|
|
262
|
-
}
|
|
263
|
-
function hashToken(token) {
|
|
264
|
-
return crypto.createHash('sha256').update(token, 'utf8').digest('hex');
|
|
265
|
-
}
|
|
266
|
-
function generatePublicToken() {
|
|
267
|
-
return crypto.randomBytes(16).toString('base64url'); // 128 bits for stronger security
|
|
268
|
-
}
|
|
269
|
-
function timingSafeTokenEqual(token, expectedHash) {
|
|
270
|
-
const actual = Buffer.from(hashToken(token), 'hex');
|
|
271
|
-
const expected = Buffer.from(expectedHash, 'hex');
|
|
272
|
-
if (actual.length !== expected.length)
|
|
273
|
-
return false;
|
|
274
|
-
return crypto.timingSafeEqual(actual, expected);
|
|
275
|
-
}
|
|
276
|
-
function resolveBaseagentInfo(config) {
|
|
277
|
-
const baseagents = (config?.baseagents ?? {});
|
|
278
|
-
const active_baseagent = ('active_baseagent' in (config ?? {}) && typeof config?.active_baseagent === 'string')
|
|
279
|
-
? config.active_baseagent
|
|
280
|
-
: pickFirstBaseagent(baseagents);
|
|
281
|
-
const block = active_baseagent ? baseagents[active_baseagent] : undefined;
|
|
282
|
-
const model = typeof block?.model === 'string' ? block.model : undefined;
|
|
283
|
-
const effort = typeof block?.effort === 'string'
|
|
284
|
-
? block.effort
|
|
285
|
-
: (typeof block?.reasoning === 'string' ? block.reasoning : undefined);
|
|
286
|
-
return { active_baseagent, model, effort };
|
|
287
|
-
}
|
|
288
|
-
function pickFirstBaseagent(baseagents) {
|
|
289
|
-
for (const name of ['claude', 'codex', 'gemini', 'ecagent']) {
|
|
290
|
-
if (baseagents[name] !== undefined)
|
|
291
|
-
return name;
|
|
25
|
+
return { type: 'bind.response', bindType: payload.bindType === 'agent' ? 'agent' : 'daemon', success: false,
|
|
26
|
+
error: { code: 'OWNERSHIP_APP_DEFERRED', message: deferredMessage } };
|
|
292
27
|
}
|
|
293
|
-
return 'claude';
|
|
294
|
-
}
|
|
295
|
-
function summarizeChannels(channels) {
|
|
296
|
-
return channels.map(ch => ({
|
|
297
|
-
type: ch.type,
|
|
298
|
-
name: ch.name,
|
|
299
|
-
enabled: ch.enabled !== false,
|
|
300
|
-
}));
|
|
301
|
-
}
|
|
302
|
-
function getPlatform() {
|
|
303
|
-
if (process.platform === 'win32')
|
|
304
|
-
return 'windows';
|
|
305
|
-
if (process.platform === 'darwin')
|
|
306
|
-
return 'macos';
|
|
307
|
-
return 'linux';
|
|
308
28
|
}
|
|
@@ -16,6 +16,7 @@ const MAX_AVATAR_RATIO = 1.25;
|
|
|
16
16
|
const MAX_AVATAR_REDIRECTS = 3;
|
|
17
17
|
const AVATAR_DOWNLOAD_TIMEOUT_MS = 10_000;
|
|
18
18
|
const AVATAR_DNS_TIMEOUT_MS = 2_000;
|
|
19
|
+
const ALLOWED_AUN_STORAGE_PORTS = new Set(['9470', '9471', '39470']);
|
|
19
20
|
const ALLOWED_AVATAR_MIMES = new Set(['image/png', 'image/jpeg', 'image/webp']);
|
|
20
21
|
const MIME_EXT = {
|
|
21
22
|
'image/png': 'png',
|
|
@@ -210,7 +211,7 @@ function isPrivateAddress(address) {
|
|
|
210
211
|
}
|
|
211
212
|
return false;
|
|
212
213
|
}
|
|
213
|
-
async function validateAvatarUrl(url) {
|
|
214
|
+
async function validateAvatarUrl(url, aunStorageHost) {
|
|
214
215
|
let parsed;
|
|
215
216
|
try {
|
|
216
217
|
parsed = new URL(url);
|
|
@@ -224,8 +225,12 @@ async function validateAvatarUrl(url) {
|
|
|
224
225
|
return { ok: false, reason: '头像 URL 缺少主机名' };
|
|
225
226
|
if (parsed.username || parsed.password)
|
|
226
227
|
return { ok: false, reason: '头像 URL 不允许携带认证信息' };
|
|
227
|
-
if (parsed.port && parsed.port !== '443')
|
|
228
|
-
|
|
228
|
+
if (parsed.port && parsed.port !== '443') {
|
|
229
|
+
const isAunStorageRedirect = parsed.hostname === aunStorageHost
|
|
230
|
+
&& ALLOWED_AUN_STORAGE_PORTS.has(parsed.port);
|
|
231
|
+
if (!isAunStorageRedirect)
|
|
232
|
+
return { ok: false, reason: '头像 URL 仅支持 HTTPS 默认端口' };
|
|
233
|
+
}
|
|
229
234
|
const hostname = parsed.hostname.replace(/^\[|\]$/g, '');
|
|
230
235
|
if (isPrivateAddress(hostname))
|
|
231
236
|
return { ok: false, reason: '头像 URL 指向受限地址' };
|
|
@@ -250,10 +255,17 @@ async function validateAvatarUrl(url) {
|
|
|
250
255
|
}
|
|
251
256
|
async function downloadAvatarBuffer(sourceUrl) {
|
|
252
257
|
let current = sourceUrl;
|
|
258
|
+
let aunStorageHost;
|
|
253
259
|
for (let redirect = 0; redirect <= MAX_AVATAR_REDIRECTS; redirect++) {
|
|
254
|
-
const checked = await validateAvatarUrl(current);
|
|
260
|
+
const checked = await validateAvatarUrl(current, aunStorageHost);
|
|
255
261
|
if (!checked.ok)
|
|
256
262
|
return fail(checked.reason, 'INVALID_ARGS');
|
|
263
|
+
if (redirect === 0) {
|
|
264
|
+
// Canonical AID URLs redirect to their domain's Storage service on a public service port.
|
|
265
|
+
const aidDomain = checked.url.hostname.split('.').slice(1);
|
|
266
|
+
if (aidDomain.length >= 2)
|
|
267
|
+
aunStorageHost = `storage.${aidDomain.join('.')}`;
|
|
268
|
+
}
|
|
257
269
|
const controller = new AbortController();
|
|
258
270
|
const timeout = setTimeout(() => controller.abort(), AVATAR_DOWNLOAD_TIMEOUT_MS);
|
|
259
271
|
let response;
|
|
@@ -286,55 +286,120 @@ export function spawnDetachedNode(args, env = {}) {
|
|
|
286
286
|
* In a checkout the monitor is executed through tsx, where a syntax error
|
|
287
287
|
* anywhere in its import graph is fatal at load time — and the usual
|
|
288
288
|
* `stdio: 'ignore'` would throw that diagnostic away. So stderr is captured,
|
|
289
|
-
* and success requires the child to
|
|
290
|
-
*
|
|
289
|
+
* and success requires the child to register as a monitor, not merely to have
|
|
290
|
+
* been spawned. A live but unregistered child at timeout is reported as
|
|
291
|
+
* pending, since it may still take ownership after the caller returns.
|
|
291
292
|
*
|
|
292
293
|
* `isAliveInRegistry` lets the caller define proof-of-life without dragging the
|
|
293
294
|
* instance registry into this module.
|
|
294
295
|
*/
|
|
295
|
-
export async function launchMonitorAndConfirm(args, env, isAliveInRegistry, hasOtherLiveMonitor,
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
296
|
+
export async function launchMonitorAndConfirm(args, env, isAliveInRegistry, hasOtherLiveMonitor, options = {}) {
|
|
297
|
+
const startedAt = Date.now();
|
|
298
|
+
const diagnostic = (message) => {
|
|
299
|
+
if (!options.logFilePath)
|
|
300
|
+
return;
|
|
301
|
+
try {
|
|
302
|
+
fs.appendFileSync(options.logFilePath, `[${new Date().toISOString()}] [requestId=${options.requestId ?? 'unknown'}] ${message}\n`);
|
|
303
|
+
}
|
|
304
|
+
catch { }
|
|
305
|
+
};
|
|
306
|
+
let logFd;
|
|
307
|
+
let child;
|
|
308
|
+
try {
|
|
309
|
+
if (options.logFilePath) {
|
|
310
|
+
fs.mkdirSync(path.dirname(options.logFilePath), { recursive: true });
|
|
311
|
+
logFd = fs.openSync(options.logFilePath, 'a');
|
|
312
|
+
}
|
|
313
|
+
child = spawn(process.execPath, [...args], {
|
|
314
|
+
detached: true,
|
|
315
|
+
stdio: logFd === undefined ? ['ignore', 'pipe', 'pipe'] : ['ignore', logFd, logFd],
|
|
316
|
+
windowsHide: isWindows,
|
|
317
|
+
env: { ...process.env, ...env },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
diagnostic(`Launch failed: ${error.message}`);
|
|
322
|
+
return { ok: false, reason: error.message };
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
if (logFd !== undefined)
|
|
326
|
+
fs.closeSync(logFd);
|
|
327
|
+
}
|
|
302
328
|
let stderrTail = '';
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
329
|
+
const finish = (result) => {
|
|
330
|
+
diagnostic(`Launch result: pid=${child.pid ?? 'unknown'} ok=${result.ok} pending=${result.pending ?? false} elapsedMs=${Date.now() - startedAt} reason=${result.reason ?? 'accepted'}`);
|
|
331
|
+
if (!result.ok && options.logFilePath) {
|
|
332
|
+
let tailFd;
|
|
333
|
+
try {
|
|
334
|
+
tailFd = fs.openSync(options.logFilePath, 'r');
|
|
335
|
+
const size = fs.fstatSync(tailFd).size;
|
|
336
|
+
const buffer = Buffer.alloc(Math.min(size, 2000));
|
|
337
|
+
fs.readSync(tailFd, buffer, 0, buffer.length, Math.max(0, size - buffer.length));
|
|
338
|
+
stderrTail = buffer.toString('utf8');
|
|
339
|
+
}
|
|
340
|
+
catch { }
|
|
341
|
+
finally {
|
|
342
|
+
if (tailFd !== undefined)
|
|
343
|
+
fs.closeSync(tailFd);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return { ...result, ...(!result.ok ? { stderrTail } : {}) };
|
|
347
|
+
};
|
|
348
|
+
child.stdout?.on('data', () => { });
|
|
349
|
+
child.stderr?.on('data', (chunk) => { stderrTail = (stderrTail + chunk.toString()).slice(-2000); });
|
|
306
350
|
// Capturing stderr must not defeat detached-process semantics. A referenced
|
|
307
351
|
// pipe keeps the caller's event loop alive until the monitor exits, which
|
|
308
352
|
// makes restart commands wait for the monitor's full lifetime.
|
|
353
|
+
child.stdout?.unref?.();
|
|
309
354
|
child.stderr?.unref?.();
|
|
310
355
|
let exitCode = null;
|
|
356
|
+
let exited = false;
|
|
357
|
+
let exitSignal = null;
|
|
311
358
|
let spawnError = null;
|
|
312
|
-
child.on('exit', code => {
|
|
313
|
-
|
|
359
|
+
child.on('exit', (code, signal) => {
|
|
360
|
+
exited = true;
|
|
361
|
+
exitCode = code;
|
|
362
|
+
exitSignal = signal;
|
|
363
|
+
diagnostic(`Child exited: pid=${child.pid ?? 'unknown'} exit=${code} signal=${signal ?? 'none'} elapsedMs=${Date.now() - startedAt}`);
|
|
364
|
+
});
|
|
365
|
+
child.on('error', err => {
|
|
366
|
+
spawnError = err.message;
|
|
367
|
+
diagnostic(`Child error: ${err.message}`);
|
|
368
|
+
});
|
|
314
369
|
const childPid = child.pid;
|
|
315
370
|
child.unref();
|
|
371
|
+
diagnostic(`Child spawned: pid=${childPid ?? 'unknown'}`);
|
|
316
372
|
if (!childPid)
|
|
317
|
-
return { ok: false, reason: 'spawn 未返回 PID' };
|
|
318
|
-
|
|
373
|
+
return finish({ ok: false, reason: 'spawn 未返回 PID' });
|
|
374
|
+
try {
|
|
375
|
+
options.onSpawn?.(childPid);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
diagnostic(`Spawn callback failed: ${error.message}`);
|
|
379
|
+
}
|
|
380
|
+
const accepted = () => isAliveInRegistry(childPid) || hasOtherLiveMonitor(childPid);
|
|
381
|
+
const deadline = Date.now() + (options.timeoutMs ?? 15_000);
|
|
319
382
|
while (Date.now() < deadline) {
|
|
320
383
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
384
|
+
if (accepted())
|
|
385
|
+
return finish({ ok: true, pid: childPid });
|
|
321
386
|
if (spawnError)
|
|
322
|
-
return { ok: false, reason: spawnError
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
// restart (single-instance election) — that one is alive and in charge.
|
|
326
|
-
if (exitCode === 0 && hasOtherLiveMonitor(childPid))
|
|
327
|
-
return { ok: true };
|
|
328
|
-
return { ok: false, reason: `进程已退出,exit=${exitCode}`, stderrTail };
|
|
387
|
+
return finish({ ok: false, pid: childPid, reason: spawnError });
|
|
388
|
+
if (exited && !options.isPendingInRegistry?.()) {
|
|
389
|
+
return finish({ ok: false, pid: childPid, reason: `进程已退出,exit=${exitCode}, signal=${exitSignal ?? 'none'}` });
|
|
329
390
|
}
|
|
330
|
-
// Registering means the module graph loaded and the monitor started work.
|
|
331
|
-
if (isAliveInRegistry(childPid))
|
|
332
|
-
return { ok: true };
|
|
333
391
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
392
|
+
if (accepted())
|
|
393
|
+
return finish({ ok: true, pid: childPid });
|
|
394
|
+
if ((!exited && isProcessRunning(childPid)) || options.isPendingInRegistry?.()) {
|
|
395
|
+
return finish({
|
|
396
|
+
ok: false,
|
|
397
|
+
pid: childPid,
|
|
398
|
+
pending: true,
|
|
399
|
+
reason: '进程仍在运行但尚未登记为 restart-monitor',
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return finish({ ok: false, pid: childPid, reason: '进程未登记且已消失' });
|
|
338
403
|
}
|
|
339
404
|
/**
|
|
340
405
|
* Copy the monitor out of the package directory so an upgrade cannot replace it
|
|
@@ -344,9 +409,10 @@ export async function launchMonitorAndConfirm(args, env, isAliveInRegistry, hasO
|
|
|
344
409
|
* in a checkout: `npm install -g` swaps the whole package directory, and the
|
|
345
410
|
* monitor has ~170 lazy `await import()` sites, so a module it has not loaded
|
|
346
411
|
* yet would come from the new version while the rest is still the old one.
|
|
347
|
-
* Staging `dist/` and `
|
|
412
|
+
* Staging `dist/`, `bin/` and `kits/` keeps package-owned code and runtime
|
|
413
|
+
* schemas on one version.
|
|
348
414
|
*
|
|
349
|
-
*
|
|
415
|
+
* `dist/`, `bin/`, `kits/` and `package.json` are copied. `node_modules` is
|
|
350
416
|
* linked, because copying it is hundreds of MB. The link degrades in steps so
|
|
351
417
|
* that no platform is left without a path forward:
|
|
352
418
|
*
|
|
@@ -382,6 +448,9 @@ export function prepareMonitorStage(packageRoot, stageParent) {
|
|
|
382
448
|
const binSource = path.join(packageRoot, 'bin');
|
|
383
449
|
if (fs.existsSync(binSource))
|
|
384
450
|
fs.cpSync(binSource, path.join(stageDir, 'bin'), { recursive: true });
|
|
451
|
+
const kitsSource = path.join(packageRoot, 'kits');
|
|
452
|
+
if (fs.existsSync(kitsSource))
|
|
453
|
+
fs.cpSync(kitsSource, path.join(stageDir, 'kits'), { recursive: true });
|
|
385
454
|
const pkgJson = path.join(packageRoot, 'package.json');
|
|
386
455
|
if (fs.existsSync(pkgJson))
|
|
387
456
|
fs.copyFileSync(pkgJson, path.join(stageDir, 'package.json'));
|
|
@@ -867,10 +936,10 @@ export function commandExists(cmd) {
|
|
|
867
936
|
* 因此优先选 PATHEXT 可执行后缀(.cmd/.exe/.bat/.com),都没有才退回首行。
|
|
868
937
|
* 失败返回 null。不缓存——刚安装的命令需要重新探测。
|
|
869
938
|
*/
|
|
870
|
-
export function resolveCommandPath(cmd) {
|
|
939
|
+
export function resolveCommandPath(cmd, env = process.env) {
|
|
871
940
|
try {
|
|
872
941
|
if (isWindows) {
|
|
873
|
-
const r = spawnSync('where.exe', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
|
|
942
|
+
const r = spawnSync('where.exe', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true, env });
|
|
874
943
|
if (r.status !== 0 || !r.stdout)
|
|
875
944
|
return null;
|
|
876
945
|
const candidates = r.stdout.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
@@ -881,8 +950,18 @@ export function resolveCommandPath(cmd) {
|
|
|
881
950
|
return runnable || candidates[0];
|
|
882
951
|
}
|
|
883
952
|
else {
|
|
884
|
-
const
|
|
885
|
-
|
|
953
|
+
const candidates = cmd.includes(path.sep)
|
|
954
|
+
? [path.resolve(cmd)]
|
|
955
|
+
: (env.PATH ?? '').split(path.delimiter).map(directory => path.resolve(directory || '.', cmd));
|
|
956
|
+
for (const candidate of candidates) {
|
|
957
|
+
try {
|
|
958
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
959
|
+
if (fs.statSync(candidate).isFile())
|
|
960
|
+
return candidate;
|
|
961
|
+
}
|
|
962
|
+
catch { }
|
|
963
|
+
}
|
|
964
|
+
return null;
|
|
886
965
|
}
|
|
887
966
|
}
|
|
888
967
|
catch {
|
|
@@ -196,7 +196,7 @@ export function createEcwebSupervisor(options) {
|
|
|
196
196
|
catch { }
|
|
197
197
|
}
|
|
198
198
|
if (!launch) {
|
|
199
|
-
options.logger.warn(
|
|
199
|
+
options.logger.warn('ECWeb 启动入口不存在,请重新安装包含匹配 ECWeb 的 EvolCore 版本。');
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
202
|
const versionError = ecwebVersionRequirementError(version);
|
|
@@ -5,6 +5,13 @@ import { isWindows as platformIsWindows, resolveCommandPath } from './cross-plat
|
|
|
5
5
|
import { MIN_ECWEB_VERSION, WEB_CLI_BIN, WEB_PACKAGE_NAME } from '../product.js';
|
|
6
6
|
import { resolvePaths } from '../paths.js';
|
|
7
7
|
import { ipcQuery } from '../ipc.js';
|
|
8
|
+
import { ecwebService } from '../config/daemon-services.js';
|
|
9
|
+
export function resolveEcwebExternalUrl(config) {
|
|
10
|
+
const aid = config.aid?.trim();
|
|
11
|
+
if (!aid || ecwebService(config.services)?.enabled !== true)
|
|
12
|
+
return null;
|
|
13
|
+
return `https://${aid}/proxy/ecweb`;
|
|
14
|
+
}
|
|
8
15
|
/** EC Web owns the session-log reader, so older builds cannot safely serve a newer daemon. */
|
|
9
16
|
export function isEcwebVersionSupported(version) {
|
|
10
17
|
const normalized = version?.trim();
|
|
@@ -16,7 +23,7 @@ export function ecwebVersionRequirementError(version) {
|
|
|
16
23
|
if (isEcwebVersionSupported(version))
|
|
17
24
|
return null;
|
|
18
25
|
const detected = version?.trim() || 'unknown';
|
|
19
|
-
return `EC Web ${detected} 低于最低要求 ${MIN_ECWEB_VERSION}
|
|
26
|
+
return `EC Web ${detected} 低于最低要求 ${MIN_ECWEB_VERSION},拒绝启动。请升级 EvolCore 到包含匹配 EC Web 的版本。`;
|
|
20
27
|
}
|
|
21
28
|
function readEcwebPackageEntry(pkgJsonPath) {
|
|
22
29
|
try {
|