evolcore 0.0.35 → 0.0.36
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 +31 -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 +134 -48
- 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 +109 -34
- 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
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { posix } from 'node:path';
|
|
3
|
+
import { resolvePaths } from '../paths.js';
|
|
4
|
+
import { ConfigTarget, mutateConfig } from './config-manager.js';
|
|
5
|
+
import { readProvidersFile, validateProvidersFile } from './providers.js';
|
|
6
|
+
import { providerEnvironmentVariable, providerNameFromBaseUrlIfStatic, resolveProviderService, resolveProviderServiceForBaseagent } from './providers-config.js';
|
|
7
|
+
export const PROVIDER_SECRET_MASK = '<redacted>';
|
|
8
|
+
function revisionValue(value) {
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return value.map(revisionValue);
|
|
11
|
+
if (!value || typeof value !== 'object')
|
|
12
|
+
return value;
|
|
13
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right))
|
|
14
|
+
.map(([key, entry]) => [key, revisionValue(entry)]));
|
|
15
|
+
}
|
|
16
|
+
export function providersRevision(value) {
|
|
17
|
+
return createHash('sha256').update(JSON.stringify(revisionValue({ providers: value.providers, ...(value.nextId !== undefined ? { nextId: value.nextId } : {}) }))).digest('hex');
|
|
18
|
+
}
|
|
19
|
+
export function readProvidersSnapshot(file = resolvePaths().providersConfig) {
|
|
20
|
+
return providersSnapshot(readProvidersFile(file));
|
|
21
|
+
}
|
|
22
|
+
function providersSnapshot(value) {
|
|
23
|
+
const providers = structuredClone(value.providers);
|
|
24
|
+
for (const provider of Object.values(providers)) {
|
|
25
|
+
if (provider.common.apiKey && !providerEnvironmentVariable(provider.common.apiKey))
|
|
26
|
+
provider.common.apiKey = PROVIDER_SECRET_MASK;
|
|
27
|
+
for (const field of ['headers', 'queryParams']) {
|
|
28
|
+
const values = provider.common[field];
|
|
29
|
+
if (values)
|
|
30
|
+
for (const key of Object.keys(values))
|
|
31
|
+
values[key] = PROVIDER_SECRET_MASK;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
...(value.$schema_version !== undefined ? { $schema_version: value.$schema_version } : {}),
|
|
36
|
+
...(value.nextId !== undefined ? { nextId: value.nextId } : {}),
|
|
37
|
+
providers,
|
|
38
|
+
revision: providersRevision(value),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function retainSecrets(value, previous) {
|
|
42
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
43
|
+
return value;
|
|
44
|
+
const provider = structuredClone(value);
|
|
45
|
+
if (!provider.common || typeof provider.common !== 'object' || Array.isArray(provider.common))
|
|
46
|
+
return provider;
|
|
47
|
+
const common = provider.common;
|
|
48
|
+
if (common.apiKey === PROVIDER_SECRET_MASK) {
|
|
49
|
+
if (!previous?.common.apiKey)
|
|
50
|
+
throw new Error('A replacement API key is required');
|
|
51
|
+
common.apiKey = previous.common.apiKey;
|
|
52
|
+
}
|
|
53
|
+
for (const field of ['headers', 'queryParams']) {
|
|
54
|
+
if (!common[field] || typeof common[field] !== 'object' || Array.isArray(common[field]))
|
|
55
|
+
continue;
|
|
56
|
+
for (const key of Object.keys(common[field])) {
|
|
57
|
+
if (common[field][key] !== PROVIDER_SECRET_MASK)
|
|
58
|
+
continue;
|
|
59
|
+
const existing = previous?.common[field]?.[key];
|
|
60
|
+
if (existing === undefined)
|
|
61
|
+
throw new Error(`A replacement ${field} value is required`);
|
|
62
|
+
common[field][key] = existing;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return provider;
|
|
66
|
+
}
|
|
67
|
+
export function resolveProviderDraftService(input, file = resolvePaths().providersConfig) {
|
|
68
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(input.providerId) || ['__proto__', 'constructor', 'prototype'].includes(input.providerId)) {
|
|
69
|
+
throw new Error('Invalid provider identifier');
|
|
70
|
+
}
|
|
71
|
+
const current = readProvidersFile(file);
|
|
72
|
+
const previous = Object.hasOwn(current.providers, input.providerId) ? current.providers[input.providerId] : undefined;
|
|
73
|
+
const provider = resolveDraftProvider(input.value, previous, input.imports);
|
|
74
|
+
if (input.baseagent) {
|
|
75
|
+
return resolveProviderServiceForBaseagent(input.providerId, input.baseagent, input.serviceType, {
|
|
76
|
+
providers: { [input.providerId]: provider },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return resolveProviderService(input.providerId, input.serviceType, { providers: { [input.providerId]: provider } });
|
|
80
|
+
}
|
|
81
|
+
export async function updateProvider(input, file = resolvePaths().providersConfig) {
|
|
82
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(input.providerId) || ['__proto__', 'constructor', 'prototype'].includes(input.providerId)) {
|
|
83
|
+
throw new Error('Invalid provider identifier');
|
|
84
|
+
}
|
|
85
|
+
const result = mutateConfig(ConfigTarget.Providers, { providerFile: file }, config => {
|
|
86
|
+
const current = config;
|
|
87
|
+
if (providersRevision(current) !== input.revision) {
|
|
88
|
+
throw Object.assign(new Error('Providers changed after loading; reload before saving'), { statusCode: 409 });
|
|
89
|
+
}
|
|
90
|
+
if (input.remove) {
|
|
91
|
+
current.nextId = nextProviderSequence(current);
|
|
92
|
+
delete current.providers[input.providerId];
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const value = retainSecrets(input.value, current.providers[input.providerId]);
|
|
96
|
+
const normalized = validateProvidersFile({ providers: { [input.providerId]: value } });
|
|
97
|
+
current.providers[input.providerId] = normalized.providers[input.providerId];
|
|
98
|
+
}
|
|
99
|
+
return current;
|
|
100
|
+
});
|
|
101
|
+
return providersSnapshot(result.value);
|
|
102
|
+
}
|
|
103
|
+
function rejectMaskedImportSecrets(providers) {
|
|
104
|
+
for (const [providerId, provider] of Object.entries(providers)) {
|
|
105
|
+
if (provider.common.apiKey === PROVIDER_SECRET_MASK) {
|
|
106
|
+
throw new Error(`Provider ${providerId} has a redacted API key; import a file with the original secret`);
|
|
107
|
+
}
|
|
108
|
+
for (const field of ['headers', 'queryParams']) {
|
|
109
|
+
for (const [key, value] of Object.entries(provider.common[field] ?? {})) {
|
|
110
|
+
if (value === PROVIDER_SECRET_MASK) {
|
|
111
|
+
throw new Error(`Provider ${providerId} ${field}.${key} is redacted; import a file with the original secret`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export function parseProviderImport(text) {
|
|
118
|
+
if (typeof text !== 'string' || !text.trim())
|
|
119
|
+
throw new Error('Paste one Provider configuration');
|
|
120
|
+
if (Buffer.byteLength(text, 'utf8') > 1024 * 1024) {
|
|
121
|
+
throw Object.assign(new Error('Provider configuration exceeds 1 MiB'), { statusCode: 413 });
|
|
122
|
+
}
|
|
123
|
+
let source = text.replace(/^\uFEFF/u, '').trim();
|
|
124
|
+
const fenced = source.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/iu);
|
|
125
|
+
if (fenced)
|
|
126
|
+
source = fenced[1];
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = JSON.parse(source);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
throw new Error('Provider configuration must be valid JSON');
|
|
133
|
+
}
|
|
134
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
135
|
+
throw new Error('Provider configuration must be an object');
|
|
136
|
+
const value = parsed;
|
|
137
|
+
const isProviderMap = Object.values(value).every(provider => provider && typeof provider === 'object'
|
|
138
|
+
&& !Array.isArray(provider) && Object.hasOwn(provider, 'common'));
|
|
139
|
+
const isSingleProvider = !isProviderMap && ['name', 'common', 'services'].some(field => Object.hasOwn(value, field));
|
|
140
|
+
if (!Object.hasOwn(value, 'providers') && isSingleProvider && !Object.hasOwn(value, 'common')) {
|
|
141
|
+
throw new Error('Provider configuration is missing "common"; check the field spelling');
|
|
142
|
+
}
|
|
143
|
+
const normalized = validateProvidersFile(Object.hasOwn(value, 'providers') ? value
|
|
144
|
+
: { providers: isSingleProvider ? { imported: value } : value });
|
|
145
|
+
const providers = Object.values(normalized.providers);
|
|
146
|
+
if (providers.length !== 1)
|
|
147
|
+
throw new Error('Import exactly one Provider at a time');
|
|
148
|
+
rejectMaskedImportSecrets(normalized.providers);
|
|
149
|
+
const provider = providers[0];
|
|
150
|
+
provider.name ||= providerNameFromBaseUrlIfStatic(provider.common.baseUrl);
|
|
151
|
+
return provider;
|
|
152
|
+
}
|
|
153
|
+
export function previewProviderImport(text) {
|
|
154
|
+
return previewProviderValue(parseProviderImport(text));
|
|
155
|
+
}
|
|
156
|
+
export function previewProviderValue(value) {
|
|
157
|
+
const normalized = validateProvidersFile({ providers: { preview: value } });
|
|
158
|
+
return { provider: providersSnapshot(normalized).providers.preview };
|
|
159
|
+
}
|
|
160
|
+
function nextProviderSequence(config) {
|
|
161
|
+
let nextId = config.nextId ?? 1;
|
|
162
|
+
for (const providerId of Object.keys(config.providers)) {
|
|
163
|
+
const match = /^provider-(\d+)$/u.exec(providerId);
|
|
164
|
+
if (match)
|
|
165
|
+
nextId = Math.max(nextId, Number(match[1]) + 1);
|
|
166
|
+
}
|
|
167
|
+
if (!Number.isSafeInteger(nextId) || nextId >= Number.MAX_SAFE_INTEGER)
|
|
168
|
+
throw new Error('Provider ID sequence is exhausted');
|
|
169
|
+
return nextId;
|
|
170
|
+
}
|
|
171
|
+
export async function createProvider(input, file = resolvePaths().providersConfig) {
|
|
172
|
+
if (!/^[a-f0-9]{64}$/u.test(input.revision))
|
|
173
|
+
throw new Error('A valid providers revision is required');
|
|
174
|
+
const normalized = validateProvidersFile({ providers: { imported: input.value } });
|
|
175
|
+
rejectMaskedImportSecrets(normalized.providers);
|
|
176
|
+
const provider = normalized.providers.imported;
|
|
177
|
+
provider.name ||= providerNameFromBaseUrlIfStatic(provider.common.baseUrl);
|
|
178
|
+
const result = mutateConfig(ConfigTarget.Providers, { providerFile: file }, config => {
|
|
179
|
+
const current = config;
|
|
180
|
+
if (providersRevision(current) !== input.revision) {
|
|
181
|
+
throw Object.assign(new Error('Providers changed after loading; reload before saving'), { statusCode: 409 });
|
|
182
|
+
}
|
|
183
|
+
const sequence = nextProviderSequence(current);
|
|
184
|
+
const providerId = `provider-${sequence}`;
|
|
185
|
+
current.providers[providerId] = provider;
|
|
186
|
+
current.nextId = sequence + 1;
|
|
187
|
+
return { current, providerId };
|
|
188
|
+
});
|
|
189
|
+
return { ...providersSnapshot(result.value.current), providerId: result.value.providerId };
|
|
190
|
+
}
|
|
191
|
+
function providerHostname(baseUrl) {
|
|
192
|
+
if (providerEnvironmentVariable(baseUrl))
|
|
193
|
+
return undefined;
|
|
194
|
+
return new URL(baseUrl).hostname.toLowerCase().replace(/\.$/u, '');
|
|
195
|
+
}
|
|
196
|
+
function mergeImportedProvider(existing, imported) {
|
|
197
|
+
const services = structuredClone(existing.services || {});
|
|
198
|
+
const existingVariable = providerEnvironmentVariable(existing.common.baseUrl);
|
|
199
|
+
const importedVariable = providerEnvironmentVariable(imported.common.baseUrl);
|
|
200
|
+
if (existingVariable || importedVariable) {
|
|
201
|
+
if (existingVariable !== importedVariable) {
|
|
202
|
+
throw new Error('Cannot merge Providers with different environment-variable base URLs');
|
|
203
|
+
}
|
|
204
|
+
for (const [serviceType, service] of Object.entries(imported.services || {})) {
|
|
205
|
+
if (!Object.hasOwn(services, serviceType))
|
|
206
|
+
services[serviceType] = structuredClone(service);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
...existing,
|
|
210
|
+
common: {
|
|
211
|
+
...existing.common,
|
|
212
|
+
...(!existing.common.apiKey?.trim() && imported.common.apiKey ? { apiKey: imported.common.apiKey } : {}),
|
|
213
|
+
},
|
|
214
|
+
services,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const baseUrl = new URL(existing.common.baseUrl);
|
|
218
|
+
for (const [serviceType, service] of Object.entries(imported.services || {})) {
|
|
219
|
+
if (Object.hasOwn(services, serviceType))
|
|
220
|
+
continue;
|
|
221
|
+
const endpoint = new URL(resolveProviderService('imported', serviceType, { providers: { imported } }).baseUrl);
|
|
222
|
+
if (baseUrl.protocol !== endpoint.protocol || baseUrl.port !== endpoint.port || providerHostname(baseUrl.href) !== providerHostname(endpoint.href)) {
|
|
223
|
+
throw new Error('Cannot merge this domain: the source uses a different protocol or port');
|
|
224
|
+
}
|
|
225
|
+
const relativePath = posix.relative(baseUrl.pathname.replace(/\/+$/u, '') || '/', endpoint.pathname.replace(/\/+$/u, '') || '/');
|
|
226
|
+
services[serviceType] = { ...service, path: relativePath ? `./${relativePath}` : '' };
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
...existing,
|
|
230
|
+
common: {
|
|
231
|
+
...existing.common,
|
|
232
|
+
...(!existing.common.apiKey?.trim() && imported.common.apiKey ? { apiKey: imported.common.apiKey } : {}),
|
|
233
|
+
},
|
|
234
|
+
services,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function resolveDraftProvider(value, previous, imports = []) {
|
|
238
|
+
if (!Array.isArray(imports))
|
|
239
|
+
throw new Error('Provider imports must be an array');
|
|
240
|
+
let secretSource = previous;
|
|
241
|
+
for (const item of imports) {
|
|
242
|
+
const imported = validateProvidersFile({ providers: { imported: item } }).providers.imported;
|
|
243
|
+
rejectMaskedImportSecrets({ imported });
|
|
244
|
+
const secretSourceHost = secretSource ? providerHostname(secretSource.common.baseUrl) : undefined;
|
|
245
|
+
const importedHost = providerHostname(imported.common.baseUrl);
|
|
246
|
+
if (secretSource && secretSourceHost && importedHost && secretSourceHost !== importedHost) {
|
|
247
|
+
throw new Error('Imported Provider domain does not match the target Provider');
|
|
248
|
+
}
|
|
249
|
+
secretSource = secretSource ? mergeImportedProvider(secretSource, imported) : imported;
|
|
250
|
+
}
|
|
251
|
+
return validateProvidersFile({ providers: { draft: retainSecrets(value, secretSource) } }).providers.draft;
|
|
252
|
+
}
|
|
253
|
+
export async function saveProviderChanges(input, file = resolvePaths().providersConfig) {
|
|
254
|
+
if (!Array.isArray(input.changes) || !input.changes.length)
|
|
255
|
+
throw new Error('Provider changes are required');
|
|
256
|
+
const identifiers = new Set();
|
|
257
|
+
for (const change of input.changes) {
|
|
258
|
+
if (!change || typeof change.providerId !== 'string' || !/^[A-Za-z0-9_-]+$/u.test(change.providerId)
|
|
259
|
+
|| ['__proto__', 'constructor', 'prototype'].includes(change.providerId) || identifiers.has(change.providerId)) {
|
|
260
|
+
throw new Error('Invalid or duplicate provider identifier');
|
|
261
|
+
}
|
|
262
|
+
if ((change.create !== undefined && typeof change.create !== 'boolean')
|
|
263
|
+
|| (change.remove !== undefined && typeof change.remove !== 'boolean')
|
|
264
|
+
|| (change.imports !== undefined && !Array.isArray(change.imports))
|
|
265
|
+
|| (change.remove && (change.create || change.value !== undefined || change.imports?.length))) {
|
|
266
|
+
throw new Error('Invalid provider change');
|
|
267
|
+
}
|
|
268
|
+
identifiers.add(change.providerId);
|
|
269
|
+
}
|
|
270
|
+
const result = mutateConfig(ConfigTarget.Providers, { providerFile: file }, config => {
|
|
271
|
+
const current = config;
|
|
272
|
+
if (providersRevision(current) !== input.revision) {
|
|
273
|
+
throw Object.assign(new Error('Providers changed after loading; reload before saving'), { statusCode: 409 });
|
|
274
|
+
}
|
|
275
|
+
const providerIds = {};
|
|
276
|
+
const originalIds = new Set(Object.keys(current.providers));
|
|
277
|
+
if (input.changes.some(change => change.remove))
|
|
278
|
+
current.nextId = nextProviderSequence(current);
|
|
279
|
+
for (const change of input.changes) {
|
|
280
|
+
if (change.create ? originalIds.has(change.providerId) : !originalIds.has(change.providerId)) {
|
|
281
|
+
throw new Error(change.create ? 'Provider already exists' : 'Provider not found');
|
|
282
|
+
}
|
|
283
|
+
if (change.remove) {
|
|
284
|
+
delete current.providers[change.providerId];
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const previous = change.create ? undefined : current.providers[change.providerId];
|
|
288
|
+
const provider = resolveDraftProvider(change.value, previous, change.imports);
|
|
289
|
+
provider.name ||= providerNameFromBaseUrlIfStatic(provider.common.baseUrl);
|
|
290
|
+
const providerId = change.create ? `provider-${nextProviderSequence(current)}` : change.providerId;
|
|
291
|
+
if (change.create)
|
|
292
|
+
current.nextId = Number(providerId.slice('provider-'.length)) + 1;
|
|
293
|
+
current.providers[providerId] = provider;
|
|
294
|
+
providerIds[change.providerId] = providerId;
|
|
295
|
+
}
|
|
296
|
+
return { current, providerIds };
|
|
297
|
+
});
|
|
298
|
+
return { ...providersSnapshot(result.value.current), providerIds: result.value.providerIds };
|
|
299
|
+
}
|
|
300
|
+
export async function importOrMergeBaseagentProvider(input, file = resolvePaths().providersConfig) {
|
|
301
|
+
if (!/^[a-f0-9]{64}$/u.test(input.revision))
|
|
302
|
+
throw new Error('A valid providers revision is required');
|
|
303
|
+
const normalized = validateProvidersFile({ providers: { imported: input.value } });
|
|
304
|
+
rejectMaskedImportSecrets(normalized.providers);
|
|
305
|
+
const imported = normalized.providers.imported;
|
|
306
|
+
imported.name ||= providerNameFromBaseUrlIfStatic(imported.common.baseUrl);
|
|
307
|
+
const result = mutateConfig(ConfigTarget.Providers, { providerFile: file }, config => {
|
|
308
|
+
const current = config;
|
|
309
|
+
if (providersRevision(current) !== input.revision) {
|
|
310
|
+
throw Object.assign(new Error('Providers changed after loading; reload before saving'), { statusCode: 409 });
|
|
311
|
+
}
|
|
312
|
+
const importedHost = providerHostname(imported.common.baseUrl);
|
|
313
|
+
const match = importedHost
|
|
314
|
+
? Object.entries(current.providers).find(([, provider]) => providerHostname(provider.common.baseUrl) === importedHost)
|
|
315
|
+
: undefined;
|
|
316
|
+
if (match) {
|
|
317
|
+
const [providerId, existing] = match;
|
|
318
|
+
current.providers[providerId] = validateProvidersFile({ providers: { [providerId]: mergeImportedProvider(existing, imported) } }).providers[providerId];
|
|
319
|
+
return { current, providerId, merged: true };
|
|
320
|
+
}
|
|
321
|
+
const sequence = nextProviderSequence(current);
|
|
322
|
+
const providerId = `provider-${sequence}`;
|
|
323
|
+
current.providers[providerId] = imported;
|
|
324
|
+
current.nextId = sequence + 1;
|
|
325
|
+
return { current, providerId, merged: false };
|
|
326
|
+
});
|
|
327
|
+
return { ...providersSnapshot(result.value.current), providerId: result.value.providerId, merged: result.value.merged };
|
|
328
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { resolvePaths } from '../paths.js';
|
|
2
|
+
import { ConfigTarget, read, write } from './config-manager.js';
|
|
3
|
+
import { assertProviderSelectionWrite as validateSelectionWrite, resolveProviderModelOverrides as resolveModelOverrides, resolveProviderService as resolveService, resolveProviderServiceForBaseagent as resolveServiceForBaseagent, resolveServiceSelection as resolveSelection, } from './providers-config.js';
|
|
4
|
+
export { providerIsConfigured, providerServiceTypeForBaseagent, providerServiceTypesForBaseagent, providerServiceTypeForPurpose, validateProvidersFile, } from './providers-config.js';
|
|
5
|
+
export function readProvidersFile(file = resolvePaths().providersConfig) {
|
|
6
|
+
return read(ConfigTarget.Providers, { providerFile: file }) ?? { providers: {} };
|
|
7
|
+
}
|
|
8
|
+
export function writeProvidersFile(value, file = resolvePaths().providersConfig) {
|
|
9
|
+
write(ConfigTarget.Providers, value, { providerFile: file });
|
|
10
|
+
}
|
|
11
|
+
export function resolveProviderService(providerId, serviceType, file = resolvePaths().providersConfig) {
|
|
12
|
+
return resolveService(providerId, serviceType, readProvidersFile(file));
|
|
13
|
+
}
|
|
14
|
+
export function resolveProviderServiceForBaseagent(providerId, baseagent, preferredServiceType, file = resolvePaths().providersConfig) {
|
|
15
|
+
return resolveServiceForBaseagent(providerId, baseagent, preferredServiceType, readProvidersFile(file));
|
|
16
|
+
}
|
|
17
|
+
export function resolveServiceSelection(selections, serviceType) {
|
|
18
|
+
return resolveSelection(selections, serviceType, resolveProviderService);
|
|
19
|
+
}
|
|
20
|
+
export function assertProviderSelectionWrite(next, previous = {}, inherited = {}) {
|
|
21
|
+
validateSelectionWrite(next, previous, inherited, (providerId, serviceType) => resolveModelOverrides(providerId, serviceType, readProvidersFile()));
|
|
22
|
+
}
|
|
@@ -26,7 +26,7 @@ export function resolveConfigCommand(inputArgv, options = {}) {
|
|
|
26
26
|
return { ok: false, code: 'MISSING_ARG', reason: 'A config subcommand is required' };
|
|
27
27
|
}
|
|
28
28
|
if (options.defaultRelation?.self && options.defaultRelation.peerKey
|
|
29
|
-
&& isScopedConfigSubcommand(subcommand)
|
|
29
|
+
&& (isScopedConfigSubcommand(subcommand) || subcommand === 'get' || subcommand === 'set' || subcommand === 'unset')
|
|
30
30
|
&& !hasExplicitConfigSelector(argv)) {
|
|
31
31
|
argv = [...argv, '--self', options.defaultRelation.self, '--peer', options.defaultRelation.peerKey];
|
|
32
32
|
}
|
|
@@ -118,14 +118,14 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
118
118
|
reason: 'Agent role policies are managed through the role policy editor',
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
-
const
|
|
121
|
+
const canContainFallback = field === 'baseagents'
|
|
122
122
|
|| /^baseagents\.[^.]+$/.test(field)
|
|
123
|
-
|| /^baseagents\.[^.]+\.
|
|
124
|
-
if (subcommand !== 'get' &&
|
|
123
|
+
|| /^baseagents\.[^.]+\.fallback(?:\.|$)/.test(field);
|
|
124
|
+
if (subcommand !== 'get' && canContainFallback) {
|
|
125
125
|
return {
|
|
126
126
|
ok: false,
|
|
127
127
|
code: 'DEFAULTS_FILE_ONLY',
|
|
128
|
-
reason: '
|
|
128
|
+
reason: 'Baseagent fallback is configured only by editing agents/defaults.json',
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
const fieldRule = resolveConfigFieldRule(field, selector.scope);
|
|
@@ -176,7 +176,13 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
176
176
|
const rawValue = subcommand === 'set' ? business.values[1] : undefined;
|
|
177
177
|
let value;
|
|
178
178
|
if (subcommand === 'set') {
|
|
179
|
-
if (field === '
|
|
179
|
+
if (field === 'owner') {
|
|
180
|
+
const owner = rawValue.trim();
|
|
181
|
+
if (!isValidAid(owner))
|
|
182
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: `Invalid owner AID: ${JSON.stringify(rawValue)}` };
|
|
183
|
+
value = owner;
|
|
184
|
+
}
|
|
185
|
+
else if (field === 'owners') {
|
|
180
186
|
const parsedOwners = parseOwnersValue(rawValue);
|
|
181
187
|
if (!parsedOwners.ok)
|
|
182
188
|
return parsedOwners;
|
|
@@ -556,14 +562,11 @@ function parseOwnersValue(raw) {
|
|
|
556
562
|
if (!Array.isArray(values) || values.length === 0) {
|
|
557
563
|
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'owners must contain at least one AID' };
|
|
558
564
|
}
|
|
559
|
-
const owners = [
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
const owner = value.trim();
|
|
565
|
-
if (!owners.includes(owner))
|
|
566
|
-
owners.push(owner);
|
|
565
|
+
const owners = [...new Set(values
|
|
566
|
+
.filter((value) => typeof value === 'string' && !!value.trim())
|
|
567
|
+
.map(value => value.trim()))];
|
|
568
|
+
if (owners.length === 0 || owners.some(owner => !isValidAid(owner))) {
|
|
569
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: `Invalid owner AID: ${JSON.stringify(owners)}` };
|
|
567
570
|
}
|
|
568
571
|
return { ok: true, value: owners };
|
|
569
572
|
}
|
package/dist/config/snapshot.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* snapshot —— 配置快照与回滚。
|
|
3
3
|
*
|
|
4
|
-
* v3 设计快照范围:daemon.json + agents/defaults.json + 每 agent 的 config.json
|
|
5
|
-
* + 每关系的 config.json + extra_backup 声明文件。**不含 .env
|
|
4
|
+
* v3 设计快照范围:daemon.json + providers.json + agents/defaults.json + 每 agent 的 config.json
|
|
5
|
+
* + 每关系的 config.json + extra_backup 声明文件。**不含 .env**。
|
|
6
6
|
*
|
|
7
7
|
* 版本号:全量百位递增(v100/v200…),增量嵌套其下(v101…v199,每全量 ≤99 个增量)。
|
|
8
8
|
* 目录:{root}/backups/config/v100/{meta.json, snapshot/...} + v101/{meta.json, delta/...}
|
|
@@ -16,7 +16,7 @@ import fs from 'fs';
|
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import { resolvePaths } from '../paths.js';
|
|
18
18
|
import { logger } from '../utils/logger.js';
|
|
19
|
-
import { ConfigTarget, read } from './config-manager.js';
|
|
19
|
+
import { ConfigTarget, notifyConfigWrite, read } from './config-manager.js';
|
|
20
20
|
const RETAIN_FULL = 10;
|
|
21
21
|
const RETAIN_DELTA = 20;
|
|
22
22
|
/** Process-level master switch. Missing config remains enabled for compatibility. */
|
|
@@ -56,6 +56,7 @@ export function collectConfigFiles(root) {
|
|
|
56
56
|
const pushIf = (abs) => { if (fs.existsSync(abs))
|
|
57
57
|
out.push(rel(abs)); };
|
|
58
58
|
pushIf(path.join(root, 'daemon.json'));
|
|
59
|
+
pushIf(path.join(root, 'providers.json'));
|
|
59
60
|
const agentsRoot = path.join(root, 'agents');
|
|
60
61
|
pushIf(path.join(agentsRoot, 'defaults.json'));
|
|
61
62
|
if (fs.existsSync(agentsRoot)) {
|
|
@@ -342,9 +343,7 @@ function createFull(root, work, trigger, description) {
|
|
|
342
343
|
const verDir = path.join(backupsDir(), version);
|
|
343
344
|
const snapDir = path.join(verDir, 'snapshot');
|
|
344
345
|
for (const [rel, content] of work) {
|
|
345
|
-
|
|
346
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
347
|
-
fs.writeFileSync(dest, content, 'utf-8');
|
|
346
|
+
writeSnapshotConfigFile(snapDir, rel, content);
|
|
348
347
|
}
|
|
349
348
|
const meta = {
|
|
350
349
|
version, type: 'full', createdAt: nowIso(), trigger, description,
|
|
@@ -388,9 +387,25 @@ function createDelta(root, work, baseFull, baseMap, trigger, description) {
|
|
|
388
387
|
return { created: true, version, type: 'delta' };
|
|
389
388
|
}
|
|
390
389
|
function writeDeltaFile(filesDir, rel, content) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
writeSnapshotConfigFile(filesDir, rel, content);
|
|
391
|
+
}
|
|
392
|
+
export function writeSnapshotConfigFile(root, rel, content) {
|
|
393
|
+
const dest = path.join(root, rel);
|
|
394
|
+
const protectedProvider = rel === 'providers.json';
|
|
395
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true, ...(protectedProvider ? { mode: 0o700 } : {}) });
|
|
396
|
+
fs.writeFileSync(dest, content, { encoding: 'utf8', ...(protectedProvider ? { mode: 0o600 } : {}) });
|
|
397
|
+
if (protectedProvider && process.platform !== 'win32')
|
|
398
|
+
fs.chmodSync(dest, 0o600);
|
|
399
|
+
}
|
|
400
|
+
export function removeSnapshotConfigFile(root, rel) {
|
|
401
|
+
const dest = path.join(root, rel);
|
|
402
|
+
const files = rel === 'providers.json' ? [dest, `${dest}_`, `${dest}__`] : [dest];
|
|
403
|
+
for (const file of files) {
|
|
404
|
+
try {
|
|
405
|
+
fs.unlinkSync(file);
|
|
406
|
+
}
|
|
407
|
+
catch { }
|
|
408
|
+
}
|
|
394
409
|
}
|
|
395
410
|
function nowIso() {
|
|
396
411
|
// Date 在 workflow 脚本里被禁;此处是运行时模块,正常可用。
|
|
@@ -422,20 +437,17 @@ export function restore(version) {
|
|
|
422
437
|
// 删除工作目录中目标版本没有的配置文件
|
|
423
438
|
for (const rel of current.keys()) {
|
|
424
439
|
if (!target.has(rel)) {
|
|
425
|
-
|
|
426
|
-
fs.unlinkSync(path.join(root, rel));
|
|
427
|
-
}
|
|
428
|
-
catch { /* best effort */ }
|
|
440
|
+
removeSnapshotConfigFile(root, rel);
|
|
429
441
|
}
|
|
430
442
|
}
|
|
431
443
|
// 写入目标版本所有文件
|
|
432
444
|
let applied = 0;
|
|
433
445
|
for (const [rel, content] of target) {
|
|
434
|
-
|
|
435
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
436
|
-
fs.writeFileSync(dest, content, 'utf-8');
|
|
446
|
+
writeSnapshotConfigFile(root, rel, content);
|
|
437
447
|
applied++;
|
|
438
448
|
}
|
|
449
|
+
if (current.has('providers.json') || target.has('providers.json'))
|
|
450
|
+
notifyConfigWrite(ConfigTarget.Providers);
|
|
439
451
|
writeCurrent({ full: loc.full, delta: loc.delta });
|
|
440
452
|
writeWVersion({ full: loc.full, delta: loc.delta });
|
|
441
453
|
logger.info(`[snapshot] restored ${version} (${applied} files)`);
|
package/dist/config-store.js
CHANGED
|
@@ -32,6 +32,7 @@ import { parseStableSemver } from './utils/stable-semver.js';
|
|
|
32
32
|
import { isSupportedBaseagent } from './agents/baseagent.js';
|
|
33
33
|
import { normalizeDaemonServices } from './config/daemon-services.js';
|
|
34
34
|
import { currentVersion } from './config/schema-registry.js';
|
|
35
|
+
import { assertOwnerConfigRecord } from './config/owner-config.js';
|
|
35
36
|
const SERVICE_METADATA_SENSITIVE_KEY = /(?:^|[_-])(endpoint|url|uri|token|access[_-]?token|authorization|cookie|secret|password|private[_-]?key|key|cert|certificate)(?:$|[_-])/i;
|
|
36
37
|
function assertLocalServiceEndpoint(value, fieldPath) {
|
|
37
38
|
if (typeof value !== 'string' || value.trim() === '' || value.length > 2048) {
|
|
@@ -56,7 +57,8 @@ function assertLocalServiceEndpoint(value, fieldPath) {
|
|
|
56
57
|
export function loadDaemonConfig() {
|
|
57
58
|
const raw = atomicReadJson(resolvePaths().daemonConfig);
|
|
58
59
|
const sanitized = stripLegacyAunGatewayConfig(raw ?? {}, resolvePaths().daemonConfig, message => logger.warn(message));
|
|
59
|
-
|
|
60
|
+
const normalized = validateDaemonConfig(normalizeProcessConfigCompat(normalizeDaemonServices(sanitized)), false);
|
|
61
|
+
return normalized;
|
|
60
62
|
}
|
|
61
63
|
export function isRestartRecoveryEnabled(config = loadDaemonConfig()) {
|
|
62
64
|
return config.restartRecovery?.enabled === true;
|
|
@@ -111,6 +113,15 @@ export function saveDaemonConfig(value) {
|
|
|
111
113
|
catch { /* best-effort */ }
|
|
112
114
|
}
|
|
113
115
|
function validateDaemonConfig(value, validateAidDomain) {
|
|
116
|
+
assertOwnerConfigRecord(value, 'daemon.json', isValidAid);
|
|
117
|
+
if (value.upgrade !== undefined) {
|
|
118
|
+
if (!value.upgrade || typeof value.upgrade !== 'object' || Array.isArray(value.upgrade))
|
|
119
|
+
throw new Error('daemon.json.upgrade must be an object');
|
|
120
|
+
for (const [field, enabled] of Object.entries(value.upgrade)) {
|
|
121
|
+
if (!['autoOnStart', 'testPackages'].includes(field) || typeof enabled !== 'boolean')
|
|
122
|
+
throw new Error(`daemon.json.upgrade.${field} must be a supported boolean setting`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
114
125
|
if (value.restartRecovery !== undefined) {
|
|
115
126
|
if (!value.restartRecovery || typeof value.restartRecovery !== 'object' || Array.isArray(value.restartRecovery)) {
|
|
116
127
|
throw new Error('daemon.json.restartRecovery must be an object');
|
|
@@ -498,24 +509,17 @@ export function validateAgentConfig(cfg) {
|
|
|
498
509
|
errs.push('channels must be an array');
|
|
499
510
|
return errs;
|
|
500
511
|
}
|
|
512
|
+
if (cfg.owner !== undefined && (typeof cfg.owner !== 'string' || !isValidAid(cfg.owner))) {
|
|
513
|
+
errs.push(`invalid owner: ${cfg.owner}`);
|
|
514
|
+
}
|
|
501
515
|
if (cfg.owners !== undefined) {
|
|
502
|
-
if (!Array.isArray(cfg.owners))
|
|
516
|
+
if (!Array.isArray(cfg.owners))
|
|
503
517
|
errs.push('owners must be an array');
|
|
504
|
-
|
|
505
|
-
else {
|
|
506
|
-
const seenOwners = new Set();
|
|
518
|
+
else
|
|
507
519
|
cfg.owners.forEach((owner, i) => {
|
|
508
|
-
if (typeof owner !== 'string' || !isValidAid(owner))
|
|
520
|
+
if (typeof owner !== 'string' || !isValidAid(owner))
|
|
509
521
|
errs.push(`owners[${i}] invalid aid: ${owner}`);
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
if (seenOwners.has(owner)) {
|
|
513
|
-
errs.push(`owners[${i}] duplicate aid: ${owner}`);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
seenOwners.add(owner);
|
|
517
522
|
});
|
|
518
|
-
}
|
|
519
523
|
}
|
|
520
524
|
let aunCount = 0;
|
|
521
525
|
const seenNamesByType = new Map();
|