evolcore 0.0.20 → 0.0.21
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 +22 -0
- package/README.md +58 -9
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +379 -108
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +402 -135
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +182 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +409 -88
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +69 -115
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +46 -0
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +16 -5
- package/dist/config/config-manager.js +135 -17
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +150 -2
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-service.js +6 -2
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +290 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +279 -80
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +350 -32
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +10 -0
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +83 -84
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +124 -10
- package/dist/core/message/message-log.js +14 -7
- package/dist/core/message/message-queue.js +206 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +486 -68
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +410 -54
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-policy.js +575 -21
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +299 -30
- package/dist/core/session/session-renew.js +19 -12
- package/dist/core/session/session-turn-coordinator.js +11 -4
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +253 -46
- package/dist/ipc.js +374 -24
- package/dist/paths.js +64 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +56 -5
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +3 -2
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +21 -2
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/schemas/_meta.json +4 -2
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -29,6 +29,7 @@ import { clearRoleStoreCache, isRoleName } from './role-store.js';
|
|
|
29
29
|
import { writeRoleDefinition, writeRoleRegistry } from './role-service.js';
|
|
30
30
|
import { mutateContactBookTransactionSync } from './contact-book-store.js';
|
|
31
31
|
import { DAEMON_SCHEMA_NAME } from '../product.js';
|
|
32
|
+
import { normalizeDaemonServices } from './daemon-services.js';
|
|
32
33
|
// AUN gateway discovery belongs to fastaun. Keep the retired gatewayUrl
|
|
33
34
|
// compatibility handling beside the rest of the configuration read/write
|
|
34
35
|
// policy so all config callers share one implementation.
|
|
@@ -595,11 +596,13 @@ export function read(target, sel, opts = {}) {
|
|
|
595
596
|
raw = stripLegacyAunGatewayConfig(raw, file, message => configWarn(message));
|
|
596
597
|
// schema 版本迁移(read 时若 $schema_version < current)
|
|
597
598
|
const migrated = migrateIfNeeded(target, raw, file);
|
|
598
|
-
const normalized = target === ConfigTarget.
|
|
599
|
-
?
|
|
600
|
-
: target === ConfigTarget.
|
|
601
|
-
?
|
|
602
|
-
:
|
|
599
|
+
const normalized = target === ConfigTarget.Process
|
|
600
|
+
? normalizeProcessConfigCompat(normalizeDaemonServices(migrated))
|
|
601
|
+
: target === ConfigTarget.Agent
|
|
602
|
+
? normalizeAgentConfigForRead(migrated, sel?.self)
|
|
603
|
+
: target === ConfigTarget.Relation
|
|
604
|
+
? foldLegacyProactiveBlock(migrated)
|
|
605
|
+
: migrated;
|
|
603
606
|
// Agent read compatibility may move the legacy top-level `agents` block to
|
|
604
607
|
// `baseagents`, so canonicalize effort after that structural migration.
|
|
605
608
|
const canonicalized = normalizeBaseagentEffortCompat(normalized);
|
|
@@ -680,12 +683,11 @@ function normalizeAgentConfigForWrite(value) {
|
|
|
680
683
|
if (mutable.aid && !isValidAid(mutable.aid)) {
|
|
681
684
|
throw new ConfigError('VALIDATION_ERROR', `Invalid aid "${mutable.aid}" (must be a valid multi-level domain like mybot.agentid.pub)`);
|
|
682
685
|
}
|
|
683
|
-
// 规范化 projects
|
|
686
|
+
// 规范化 projects 字段:只保留唯一公开项目路径 defaultPath。
|
|
687
|
+
// defaults.projects.rootPath 已废弃,不能继续成为有效运行时参数。
|
|
684
688
|
if (mutable.projects && typeof mutable.projects === 'object') {
|
|
685
689
|
const projects = mutable.projects;
|
|
686
690
|
const normalized = {};
|
|
687
|
-
if (typeof projects.rootPath === 'string')
|
|
688
|
-
normalized.rootPath = projects.rootPath;
|
|
689
691
|
if (typeof projects.defaultPath === 'string')
|
|
690
692
|
normalized.defaultPath = projects.defaultPath;
|
|
691
693
|
if (Object.keys(normalized).length > 0) {
|
|
@@ -698,19 +700,58 @@ function normalizeAgentConfigForWrite(value) {
|
|
|
698
700
|
return mutable;
|
|
699
701
|
}
|
|
700
702
|
function normalizeProcessConfigForWrite(value) {
|
|
701
|
-
|
|
703
|
+
let normalized;
|
|
704
|
+
try {
|
|
705
|
+
normalized = normalizeProcessConfigCompat(normalizeDaemonServices(value));
|
|
706
|
+
}
|
|
707
|
+
catch (error) {
|
|
708
|
+
throw new ConfigError('DAEMON_SERVICES_MIGRATION_REQUIRED', error instanceof Error ? error.message : String(error));
|
|
709
|
+
}
|
|
710
|
+
const configuredAidDomain = normalized.aun?.defaultAidDomain;
|
|
702
711
|
if (configuredAidDomain === undefined)
|
|
703
|
-
return
|
|
712
|
+
return normalized;
|
|
704
713
|
try {
|
|
705
714
|
return {
|
|
706
|
-
...
|
|
707
|
-
aun: { ...
|
|
715
|
+
...normalized,
|
|
716
|
+
aun: { ...normalized.aun, defaultAidDomain: normalizeAidDomain(configuredAidDomain, 'aun.defaultAidDomain') },
|
|
708
717
|
};
|
|
709
718
|
}
|
|
710
719
|
catch (error) {
|
|
711
720
|
throw new ConfigError('VALIDATION_ERROR', error instanceof Error ? error.message : String(error));
|
|
712
721
|
}
|
|
713
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* Normalize renamed process-level settings while keeping old daemon.json files
|
|
725
|
+
* readable after an upgrade. New values take precedence when both keys exist.
|
|
726
|
+
*/
|
|
727
|
+
export function normalizeProcessConfigCompat(value) {
|
|
728
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
729
|
+
return value;
|
|
730
|
+
const idleMonitor = value.idleMonitor;
|
|
731
|
+
const debug = value.debug;
|
|
732
|
+
let changed = false;
|
|
733
|
+
let normalized = value;
|
|
734
|
+
if (idleMonitor && typeof idleMonitor === 'object' && !Array.isArray(idleMonitor)) {
|
|
735
|
+
const nextIdleMonitor = { ...idleMonitor };
|
|
736
|
+
if (nextIdleMonitor.apiRetryTimeout === undefined && nextIdleMonitor.retryAttemptTimeout !== undefined) {
|
|
737
|
+
nextIdleMonitor.apiRetryTimeout = nextIdleMonitor.retryAttemptTimeout;
|
|
738
|
+
changed = true;
|
|
739
|
+
}
|
|
740
|
+
if (Object.prototype.hasOwnProperty.call(nextIdleMonitor, 'retryAttemptTimeout')) {
|
|
741
|
+
delete nextIdleMonitor.retryAttemptTimeout;
|
|
742
|
+
changed = true;
|
|
743
|
+
}
|
|
744
|
+
if (changed)
|
|
745
|
+
normalized = { ...normalized, idleMonitor: nextIdleMonitor };
|
|
746
|
+
}
|
|
747
|
+
if (debug && typeof debug === 'object' && !Array.isArray(debug)
|
|
748
|
+
&& Object.prototype.hasOwnProperty.call(debug, 'flusherDiag')) {
|
|
749
|
+
const nextDebug = { ...debug };
|
|
750
|
+
delete nextDebug.flusherDiag;
|
|
751
|
+
normalized = { ...normalized, debug: nextDebug };
|
|
752
|
+
}
|
|
753
|
+
return normalized;
|
|
754
|
+
}
|
|
714
755
|
function normalizeRelationConfigForWrite(value) {
|
|
715
756
|
const mutable = normalizeBaseagentEffortCompat({ ...value });
|
|
716
757
|
normalizeShowActivitiesCompat(mutable);
|
|
@@ -985,7 +1026,11 @@ function ensureSchemaVersion(value, version) {
|
|
|
985
1026
|
}
|
|
986
1027
|
function validateOrThrow(schema, value, target) {
|
|
987
1028
|
const ok = schema.validate(value);
|
|
988
|
-
const scopeErrors = [
|
|
1029
|
+
const scopeErrors = [
|
|
1030
|
+
...validateProcessOnlyFields(target, value),
|
|
1031
|
+
...validateProcessAidDomain(target, value),
|
|
1032
|
+
...validateProcessServiceNames(target, value),
|
|
1033
|
+
];
|
|
989
1034
|
if (!ok || scopeErrors.length > 0) {
|
|
990
1035
|
const errs = [
|
|
991
1036
|
...scopeErrors,
|
|
@@ -1026,6 +1071,24 @@ function validateProcessAidDomain(target, value) {
|
|
|
1026
1071
|
return [`/aun/defaultAidDomain ${error instanceof Error ? error.message : String(error)}`];
|
|
1027
1072
|
}
|
|
1028
1073
|
}
|
|
1074
|
+
function validateProcessServiceNames(target, value) {
|
|
1075
|
+
if (target !== ConfigTarget.Process || !value || typeof value !== 'object')
|
|
1076
|
+
return [];
|
|
1077
|
+
const services = value.services;
|
|
1078
|
+
if (!Array.isArray(services))
|
|
1079
|
+
return [];
|
|
1080
|
+
const seen = new Set();
|
|
1081
|
+
const errors = [];
|
|
1082
|
+
services.forEach((service, index) => {
|
|
1083
|
+
if (!service || typeof service !== 'object' || typeof service.name !== 'string')
|
|
1084
|
+
return;
|
|
1085
|
+
if (seen.has(service.name))
|
|
1086
|
+
errors.push(`/services/${index}/name duplicates service name ${JSON.stringify(service.name)}`);
|
|
1087
|
+
else
|
|
1088
|
+
seen.add(service.name);
|
|
1089
|
+
});
|
|
1090
|
+
return errors;
|
|
1091
|
+
}
|
|
1029
1092
|
/**
|
|
1030
1093
|
* responseModeParams 桶专项校验。
|
|
1031
1094
|
*
|
|
@@ -1076,8 +1139,28 @@ export function validateConfig(target, value) {
|
|
|
1076
1139
|
? loadSchema('contact-book', 2)
|
|
1077
1140
|
: loadSchema(TARGET_SCHEMA[target]);
|
|
1078
1141
|
const withVer = ensureSchemaVersion(value, schema.version);
|
|
1079
|
-
|
|
1080
|
-
|
|
1142
|
+
let normalized = withVer;
|
|
1143
|
+
try {
|
|
1144
|
+
if (target === ConfigTarget.Process)
|
|
1145
|
+
normalized = normalizeProcessConfigCompat(normalizeDaemonServices(withVer));
|
|
1146
|
+
}
|
|
1147
|
+
catch (error) {
|
|
1148
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
1149
|
+
}
|
|
1150
|
+
const ok = schema.validate(normalized);
|
|
1151
|
+
const errors = [
|
|
1152
|
+
...validateProcessOnlyFields(target, normalized),
|
|
1153
|
+
...validateProcessAidDomain(target, normalized),
|
|
1154
|
+
...validateProcessServiceNames(target, normalized),
|
|
1155
|
+
];
|
|
1156
|
+
if (target === ConfigTarget.Defaults || target === ConfigTarget.Agent || target === ConfigTarget.Relation) {
|
|
1157
|
+
try {
|
|
1158
|
+
validateResponseModeParams(normalized?.responseModeParams, target);
|
|
1159
|
+
}
|
|
1160
|
+
catch (error) {
|
|
1161
|
+
errors.push(error instanceof ConfigError ? error.message : String(error));
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1081
1164
|
if (ok && errors.length === 0)
|
|
1082
1165
|
return [];
|
|
1083
1166
|
return [
|
|
@@ -1097,7 +1180,15 @@ export function validateConfigFile(target, sel) {
|
|
|
1097
1180
|
return { exists: false, errors: [] };
|
|
1098
1181
|
// Persisted pre-removal files remain readable: validate the same sanitized
|
|
1099
1182
|
// view used by runtime reads, while new writes are still rejected by write().
|
|
1100
|
-
|
|
1183
|
+
let sanitized;
|
|
1184
|
+
try {
|
|
1185
|
+
sanitized = target === ConfigTarget.Process
|
|
1186
|
+
? normalizeDaemonServices(stripLegacyAunGatewayConfig(value, file, message => configWarn(message)))
|
|
1187
|
+
: stripLegacyAunGatewayConfig(value, file, message => configWarn(message));
|
|
1188
|
+
}
|
|
1189
|
+
catch (error) {
|
|
1190
|
+
return { exists: true, errors: [error instanceof Error ? error.message : String(error)] };
|
|
1191
|
+
}
|
|
1101
1192
|
return { exists: true, errors: validateConfig(target, sanitized) };
|
|
1102
1193
|
}
|
|
1103
1194
|
function fileCacheAvailable() {
|
|
@@ -1159,13 +1250,40 @@ export function resolveAgentConfig(sel, opts = {}) {
|
|
|
1159
1250
|
const agentConfig = sel.self ? read(ConfigTarget.Agent, sel, opts) : null;
|
|
1160
1251
|
const relationConfig = (sel.self && sel.peerKey) ? read(ConfigTarget.Relation, sel, opts) : null;
|
|
1161
1252
|
const layers = [
|
|
1162
|
-
stripStaticOwnerField(read(ConfigTarget.Defaults, undefined, opts)),
|
|
1253
|
+
stripStaticOwnerField(normalizeDefaultsBehaviorCompat(read(ConfigTarget.Defaults, undefined, opts))),
|
|
1163
1254
|
agentConfig,
|
|
1164
1255
|
stripRelationRoleData(stripStaticOwnerField(relationConfig)),
|
|
1165
1256
|
];
|
|
1166
1257
|
const merged = mergeLayers(layers, fields);
|
|
1167
1258
|
return merged;
|
|
1168
1259
|
}
|
|
1260
|
+
/**
|
|
1261
|
+
* defaults.schema keeps the historical `config` object as the process-wide
|
|
1262
|
+
* response behavior compatibility surface. Make those values participate in
|
|
1263
|
+
* the normal agent/relation merge chain without leaking the compatibility
|
|
1264
|
+
* container itself into EffectiveAgentConfig.
|
|
1265
|
+
*
|
|
1266
|
+
* Explicit canonical fields (if a future schema adds them) always win. A
|
|
1267
|
+
* single default chat mode applies to the three peer categories because this
|
|
1268
|
+
* compatibility field predates the per-category `chatmode` dictionary.
|
|
1269
|
+
*/
|
|
1270
|
+
export function normalizeDefaultsBehaviorCompat(defaults) {
|
|
1271
|
+
if (!defaults || !defaults.config || typeof defaults.config !== 'object' || Array.isArray(defaults.config))
|
|
1272
|
+
return defaults;
|
|
1273
|
+
const { config, ...rest } = defaults;
|
|
1274
|
+
const normalized = { ...rest };
|
|
1275
|
+
const chatMode = config.chatMode;
|
|
1276
|
+
if (!normalized.chatmode && (chatMode === 'interactive' || chatMode === 'proactive')) {
|
|
1277
|
+
normalized.chatmode = { private: chatMode, group: chatMode, nothuman: chatMode };
|
|
1278
|
+
}
|
|
1279
|
+
if (normalized.mentionMode === undefined && (config.mentionMode === 'disabled' || config.mentionMode === 'mention-only')) {
|
|
1280
|
+
normalized.mentionMode = config.mentionMode;
|
|
1281
|
+
}
|
|
1282
|
+
if (normalized.show_activities === undefined && ['all', 'text', 'none'].includes(config.show_activities)) {
|
|
1283
|
+
normalized.show_activities = config.show_activities;
|
|
1284
|
+
}
|
|
1285
|
+
return normalized;
|
|
1286
|
+
}
|
|
1169
1287
|
function stripStaticOwnerField(config) {
|
|
1170
1288
|
if (!config)
|
|
1171
1289
|
return config;
|
|
@@ -4,7 +4,7 @@ import { isExplicitGroupId } from '../aun/group-identity.js';
|
|
|
4
4
|
import { agentConfig } from '../paths.js';
|
|
5
5
|
import { getContactSnapshot, resolveContactView } from './contact-book.js';
|
|
6
6
|
import { ContactMutationError } from './contact-book-store.js';
|
|
7
|
-
import { mutateContactWithOperation } from './contact-request-service.js';
|
|
7
|
+
import { addContact, mutateContactWithOperation } from './contact-request-service.js';
|
|
8
8
|
export function resolveContactCommand(argv) {
|
|
9
9
|
const args = argv[0] === 'contact' ? argv.slice(1) : [...argv];
|
|
10
10
|
const selfAid = String(argValue(args, '--self') || '').trim();
|
|
@@ -39,6 +39,21 @@ export function resolveContactCommand(argv) {
|
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
+
if (command === 'add') {
|
|
43
|
+
if (!isValidAid(subcommand || '') || isExplicitGroupId(subcommand || '')) {
|
|
44
|
+
return invalid('INVALID_AID', 'primaryId must be a valid individual AID');
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
command: {
|
|
49
|
+
kind: 'add',
|
|
50
|
+
operationId: 'contact.add',
|
|
51
|
+
selfAid,
|
|
52
|
+
primaryId: subcommand,
|
|
53
|
+
canonicalArgv: canonicalArgv(argv),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
42
57
|
return invalid('UNKNOWN_COMMAND', `Unknown contact command: ${positional.join(' ')}`);
|
|
43
58
|
}
|
|
44
59
|
export async function executeResolvedContactCommand(command, actor = 'local-cli') {
|
|
@@ -81,6 +96,22 @@ export async function executeResolvedContactCommand(command, actor = 'local-cli'
|
|
|
81
96
|
contactRevision: result.contactRevision,
|
|
82
97
|
};
|
|
83
98
|
}
|
|
99
|
+
if (command.kind === 'add') {
|
|
100
|
+
const result = await addContact({
|
|
101
|
+
selfAid: command.selfAid,
|
|
102
|
+
primaryId: command.primaryId,
|
|
103
|
+
actorId: actor,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
ok: true,
|
|
107
|
+
operation: 'add',
|
|
108
|
+
selfAid: command.selfAid,
|
|
109
|
+
primaryId: command.primaryId,
|
|
110
|
+
status: 'active',
|
|
111
|
+
changed: result.changed,
|
|
112
|
+
contactRevision: result.contactRevision,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
84
115
|
return { ok: false, code: 'CONTACT_OPERATION_FAILED', error: 'Unsupported contact operation' };
|
|
85
116
|
}
|
|
86
117
|
catch (error) {
|
|
@@ -38,6 +38,50 @@ export async function mutateContactWithOperation(input) {
|
|
|
38
38
|
const { value: _value, ...mutationResult } = result;
|
|
39
39
|
return mutationResult;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Add a canonical AUN contact as active through the Contact Book transaction.
|
|
43
|
+
* Existing pending/declined entries are intentionally promoted here, while a
|
|
44
|
+
* blocked entry remains a hard stop and must be explicitly unblocked first.
|
|
45
|
+
*/
|
|
46
|
+
export async function addContact(input) {
|
|
47
|
+
const primaryId = String(input.primaryId || '').trim();
|
|
48
|
+
if (!isValidAid(primaryId)) {
|
|
49
|
+
throw new ContactMutationError('INVALID_AID', `Invalid contact AID: ${primaryId}`);
|
|
50
|
+
}
|
|
51
|
+
const at = new Date(input.now ?? Date.now()).toISOString();
|
|
52
|
+
const logFile = agentContactOperations(input.selfAid);
|
|
53
|
+
const result = await mutateContactBookPrepared(input.selfAid, ({ contact, contactRevision }) => {
|
|
54
|
+
if (input.expectedContactRevision !== undefined && input.expectedContactRevision !== contactRevision) {
|
|
55
|
+
throw new ContactMutationError('CONFLICT', 'Contact book changed since it was read', {
|
|
56
|
+
expectedContactRevision: input.expectedContactRevision,
|
|
57
|
+
contactRevision,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (contact.contacts[primaryId]?.status === 'blocked') {
|
|
61
|
+
throw new ContactMutationError('CONTACT_BLOCKED', `Contact is blocked: ${primaryId}`);
|
|
62
|
+
}
|
|
63
|
+
const mutations = [
|
|
64
|
+
{ type: 'set-status', primaryId, status: 'active' },
|
|
65
|
+
];
|
|
66
|
+
if (input.displayName !== undefined) {
|
|
67
|
+
mutations.push({ type: 'set-display-name', primaryId, displayName: input.displayName });
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
mutation: mutations.length === 1 ? mutations[0] : { type: 'batch', mutations },
|
|
71
|
+
actor: input.actorId,
|
|
72
|
+
value: undefined,
|
|
73
|
+
beforeCommit: ({ contactRevision: nextRevision }) => operationLogChange(logFile, [operationRecord({
|
|
74
|
+
at,
|
|
75
|
+
op: 'manual-add',
|
|
76
|
+
primaryId,
|
|
77
|
+
actorId: input.actorId,
|
|
78
|
+
contactRevision: nextRevision,
|
|
79
|
+
})]),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
const { value: _value, ...mutationResult } = result;
|
|
83
|
+
return mutationResult;
|
|
84
|
+
}
|
|
41
85
|
export async function submitContactRequest(input) {
|
|
42
86
|
const applicantAid = String(input.applicantAid || '').trim();
|
|
43
87
|
const owners = listAgentOwners(input.selfAid);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const SERVICE_TYPES = new Set(['http', 'websocket', 'ssh']);
|
|
2
|
+
const SERVICE_VISIBILITIES = new Set(['public', 'private', 'aun-auth']);
|
|
3
|
+
export const DEFAULT_ECWEB_PORT = 42705;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Convert the former `ecweb` + `serviceProxy` process configuration into the
|
|
9
|
+
* service-instance model. The function is intentionally idempotent and does
|
|
10
|
+
* not mutate its input.
|
|
11
|
+
*/
|
|
12
|
+
export function normalizeDaemonServices(raw, warn) {
|
|
13
|
+
if (!isRecord(raw))
|
|
14
|
+
return raw;
|
|
15
|
+
const next = { ...raw };
|
|
16
|
+
const hasLegacyEcweb = Object.prototype.hasOwnProperty.call(raw, 'ecweb');
|
|
17
|
+
const hasLegacyProxy = Object.prototype.hasOwnProperty.call(raw, 'serviceProxy');
|
|
18
|
+
const hasCanonicalServices = Object.prototype.hasOwnProperty.call(raw, 'services');
|
|
19
|
+
const services = [];
|
|
20
|
+
if (hasCanonicalServices) {
|
|
21
|
+
if (!Array.isArray(raw.services)) {
|
|
22
|
+
throw new Error('daemon.json.services must be an array');
|
|
23
|
+
}
|
|
24
|
+
for (const [index, value] of raw.services.entries()) {
|
|
25
|
+
if (!isRecord(value)) {
|
|
26
|
+
throw new Error(`daemon.json.services[${index}] must be an object`);
|
|
27
|
+
}
|
|
28
|
+
if (typeof value.name !== 'string' || value.name.trim() === '') {
|
|
29
|
+
throw new Error(`daemon.json.services[${index}].name must be a non-empty string`);
|
|
30
|
+
}
|
|
31
|
+
// Preserve the canonical object for schema validation. In particular,
|
|
32
|
+
// do not trim/drop fields here, otherwise malformed new-format config
|
|
33
|
+
// could be silently rewritten into a different valid-looking shape.
|
|
34
|
+
services.push({ ...value });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (hasLegacyEcweb && !isRecord(raw.ecweb)) {
|
|
38
|
+
throw new Error('daemon.json.ecweb must be an object before it can be migrated');
|
|
39
|
+
}
|
|
40
|
+
if (hasLegacyProxy && !isRecord(raw.serviceProxy)) {
|
|
41
|
+
throw new Error('daemon.json.serviceProxy must be an object before it can be migrated');
|
|
42
|
+
}
|
|
43
|
+
const legacyEcweb = hasLegacyEcweb ? raw.ecweb : undefined;
|
|
44
|
+
const legacyProxy = hasLegacyProxy ? raw.serviceProxy : undefined;
|
|
45
|
+
if (legacyEcweb?.enabled !== undefined && typeof legacyEcweb.enabled !== 'boolean') {
|
|
46
|
+
throw new Error('daemon.json.ecweb.enabled must be a boolean before it can be migrated');
|
|
47
|
+
}
|
|
48
|
+
if (legacyEcweb?.port !== undefined && typeof legacyEcweb.port !== 'number') {
|
|
49
|
+
throw new Error('daemon.json.ecweb.port must be a number before it can be migrated');
|
|
50
|
+
}
|
|
51
|
+
if (legacyProxy?.enabled !== undefined && typeof legacyProxy.enabled !== 'boolean') {
|
|
52
|
+
throw new Error('daemon.json.serviceProxy.enabled must be a boolean before it can be migrated');
|
|
53
|
+
}
|
|
54
|
+
const legacyProxyServices = [];
|
|
55
|
+
if (legacyProxy?.services !== undefined) {
|
|
56
|
+
if (!Array.isArray(legacyProxy.services)) {
|
|
57
|
+
throw new Error('daemon.json.serviceProxy.services must be an array before it can be migrated');
|
|
58
|
+
}
|
|
59
|
+
for (const [index, value] of legacyProxy.services.entries()) {
|
|
60
|
+
if (!isRecord(value)) {
|
|
61
|
+
throw new Error(`daemon.json.serviceProxy.services[${index}] cannot be represented by source="instance"`);
|
|
62
|
+
}
|
|
63
|
+
if (value.enabled !== undefined && typeof value.enabled !== 'boolean') {
|
|
64
|
+
throw new Error(`daemon.json.serviceProxy.services[${index}].enabled must be a boolean before it can be migrated`);
|
|
65
|
+
}
|
|
66
|
+
if (value.visibility !== undefined && typeof value.visibility !== 'string') {
|
|
67
|
+
throw new Error(`daemon.json.serviceProxy.services[${index}].visibility must be a string before it can be migrated`);
|
|
68
|
+
}
|
|
69
|
+
legacyProxyServices.push(value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const legacyEcwebProxy = legacyProxyServices.find((value) => value.name === 'ecweb');
|
|
73
|
+
const ecwebIndex = services.findIndex((service) => service.name === 'ecweb');
|
|
74
|
+
// Convert the former flat serviceProxy entries into canonical services[].
|
|
75
|
+
// Keep every service rather than silently dropping non-ecweb entries.
|
|
76
|
+
for (const legacy of legacyProxyServices) {
|
|
77
|
+
const name = typeof legacy.name === 'string' ? legacy.name : '';
|
|
78
|
+
if (!name || name === 'ecweb')
|
|
79
|
+
continue;
|
|
80
|
+
if (services.some((service) => service.name === name))
|
|
81
|
+
continue;
|
|
82
|
+
const source = legacy.source === 'ecweb'
|
|
83
|
+
? 'instance'
|
|
84
|
+
: legacy.source === 'instance'
|
|
85
|
+
? 'instance'
|
|
86
|
+
: typeof legacy.endpoint === 'string' && legacy.endpoint.trim()
|
|
87
|
+
? 'static'
|
|
88
|
+
: 'static';
|
|
89
|
+
const proxy = {
|
|
90
|
+
enabled: legacyProxy?.enabled === true && legacy.enabled !== false,
|
|
91
|
+
source,
|
|
92
|
+
...(typeof legacy.endpoint === 'string' ? { endpoint: legacy.endpoint } : {}),
|
|
93
|
+
...(typeof legacy.serviceType === 'string' && SERVICE_TYPES.has(legacy.serviceType)
|
|
94
|
+
? { serviceType: legacy.serviceType } : {}),
|
|
95
|
+
...(typeof legacy.visibility === 'string' ? { visibility: legacy.visibility } : {}),
|
|
96
|
+
...(isRecord(legacy.metadata) ? { metadata: { ...legacy.metadata } } : {}),
|
|
97
|
+
};
|
|
98
|
+
services.push({ name, enabled: true, proxy });
|
|
99
|
+
}
|
|
100
|
+
if (legacyEcweb || legacyEcwebProxy) {
|
|
101
|
+
const service = ecwebIndex >= 0
|
|
102
|
+
? services[ecwebIndex]
|
|
103
|
+
: {
|
|
104
|
+
name: 'ecweb',
|
|
105
|
+
// The old ECWeb lifecycle switch and Service Proxy switch were
|
|
106
|
+
// independent. Preserve that distinction during migration: a
|
|
107
|
+
// proxy-only config must not start managing the local process.
|
|
108
|
+
enabled: legacyEcweb?.enabled === true,
|
|
109
|
+
};
|
|
110
|
+
if (legacyEcweb) {
|
|
111
|
+
if (service.enabled === undefined && typeof legacyEcweb.enabled === 'boolean')
|
|
112
|
+
service.enabled = legacyEcweb.enabled;
|
|
113
|
+
if (service.port === undefined && typeof legacyEcweb.port === 'number')
|
|
114
|
+
service.port = legacyEcweb.port;
|
|
115
|
+
}
|
|
116
|
+
if (service.proxy !== undefined && !isRecord(service.proxy)) {
|
|
117
|
+
throw new Error('daemon.json.services[ecweb].proxy must be an object before legacy proxy settings can be migrated');
|
|
118
|
+
}
|
|
119
|
+
const proxy = { ...(service.proxy ?? {}) };
|
|
120
|
+
if (legacyEcwebProxy) {
|
|
121
|
+
if (proxy.enabled === undefined) {
|
|
122
|
+
proxy.enabled = legacyProxy?.enabled === true && legacyEcwebProxy.enabled !== false;
|
|
123
|
+
}
|
|
124
|
+
if (proxy.source === undefined) {
|
|
125
|
+
proxy.source = legacyEcwebProxy.source === 'static'
|
|
126
|
+
|| (typeof legacyEcwebProxy.endpoint === 'string' && legacyEcwebProxy.endpoint.trim())
|
|
127
|
+
? 'static'
|
|
128
|
+
: 'instance';
|
|
129
|
+
}
|
|
130
|
+
if (proxy.endpoint === undefined && typeof legacyEcwebProxy.endpoint === 'string')
|
|
131
|
+
proxy.endpoint = legacyEcwebProxy.endpoint;
|
|
132
|
+
if (proxy.serviceType === undefined && typeof legacyEcwebProxy.serviceType === 'string'
|
|
133
|
+
&& SERVICE_TYPES.has(legacyEcwebProxy.serviceType)) {
|
|
134
|
+
proxy.serviceType = legacyEcwebProxy.serviceType;
|
|
135
|
+
}
|
|
136
|
+
if (proxy.visibility === undefined && typeof legacyEcwebProxy.visibility === 'string') {
|
|
137
|
+
proxy.visibility = legacyEcwebProxy.visibility;
|
|
138
|
+
}
|
|
139
|
+
if (proxy.metadata === undefined && isRecord(legacyEcwebProxy.metadata)) {
|
|
140
|
+
proxy.metadata = { ...legacyEcwebProxy.metadata };
|
|
141
|
+
}
|
|
142
|
+
if (proxy.enabled === true && proxy.visibility === undefined)
|
|
143
|
+
proxy.visibility = 'private';
|
|
144
|
+
}
|
|
145
|
+
if (Object.keys(proxy).length > 0)
|
|
146
|
+
service.proxy = proxy;
|
|
147
|
+
if (ecwebIndex < 0)
|
|
148
|
+
services.push(service);
|
|
149
|
+
}
|
|
150
|
+
if (hasCanonicalServices || services.length > 0)
|
|
151
|
+
next.services = services;
|
|
152
|
+
if (hasLegacyEcweb || hasLegacyProxy) {
|
|
153
|
+
warn?.('[config] daemon.json: ecweb/serviceProxy 已归一化为 services[]');
|
|
154
|
+
delete next.ecweb;
|
|
155
|
+
delete next.serviceProxy;
|
|
156
|
+
}
|
|
157
|
+
return next;
|
|
158
|
+
}
|
|
159
|
+
export function findService(services, name) {
|
|
160
|
+
return (services ?? []).find((service) => service.name === name);
|
|
161
|
+
}
|
|
162
|
+
export function ecwebService(services) {
|
|
163
|
+
return findService(services, 'ecweb');
|
|
164
|
+
}
|
|
165
|
+
export function upsertService(services, patch) {
|
|
166
|
+
const current = services ?? [];
|
|
167
|
+
const index = current.findIndex((service) => service.name === patch.name);
|
|
168
|
+
if (index < 0)
|
|
169
|
+
return [...current, patch];
|
|
170
|
+
const existing = current[index];
|
|
171
|
+
const replacement = {
|
|
172
|
+
...existing,
|
|
173
|
+
...patch,
|
|
174
|
+
...(existing.proxy || patch.proxy
|
|
175
|
+
? { proxy: { ...(existing.proxy ?? {}), ...(patch.proxy ?? {}) } }
|
|
176
|
+
: {}),
|
|
177
|
+
};
|
|
178
|
+
return current.map((service, serviceIndex) => serviceIndex === index ? replacement : service);
|
|
179
|
+
}
|
|
180
|
+
export function setEcwebEnabled(services, enabled, port = ecwebService(services)?.port ?? DEFAULT_ECWEB_PORT) {
|
|
181
|
+
return upsertService(services, {
|
|
182
|
+
name: 'ecweb',
|
|
183
|
+
enabled,
|
|
184
|
+
port,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
@@ -17,6 +17,7 @@ import { resolvePaths } from '../paths.js';
|
|
|
17
17
|
import { saveDefaultsSafe, saveAgent } from '../config-store.js';
|
|
18
18
|
import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../agents/baseagent.js';
|
|
19
19
|
import { buildModelRequestHeaders } from '../agents/request-identity.js';
|
|
20
|
+
import { appendV1RequestPath } from '../agents/request-identity.js';
|
|
20
21
|
import { resolvePriceRow } from '../stats/billing.js';
|
|
21
22
|
import { ipcQuery } from '../ipc.js';
|
|
22
23
|
import { logger } from '../utils/logger.js';
|
|
@@ -541,6 +542,7 @@ function resolveGatewayCreds(scope, type) {
|
|
|
541
542
|
baseUrl: r.baseUrl,
|
|
542
543
|
apiKey: r.apiKey,
|
|
543
544
|
headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: r.baseUrl, agentAid: scope === 'defaults' ? undefined : scope, configuredHeaders: r.headers }),
|
|
545
|
+
queryParams: r.queryParams,
|
|
544
546
|
};
|
|
545
547
|
}
|
|
546
548
|
else if (type === 'claude') {
|
|
@@ -549,6 +551,7 @@ function resolveGatewayCreds(scope, type) {
|
|
|
549
551
|
baseUrl: r.baseUrl,
|
|
550
552
|
apiKey: r.apiKey,
|
|
551
553
|
headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: r.baseUrl, agentAid: scope === 'defaults' ? undefined : scope, configuredHeaders: r.headers }),
|
|
554
|
+
queryParams: r.queryParams,
|
|
552
555
|
};
|
|
553
556
|
}
|
|
554
557
|
else if (type === 'ecagent') {
|
|
@@ -557,6 +560,7 @@ function resolveGatewayCreds(scope, type) {
|
|
|
557
560
|
baseUrl: r.baseUrl,
|
|
558
561
|
apiKey: r.apiKey,
|
|
559
562
|
headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: r.baseUrl, agentAid: scope === 'defaults' ? undefined : scope, configuredHeaders: r.headers }),
|
|
563
|
+
queryParams: r.queryParams,
|
|
560
564
|
};
|
|
561
565
|
}
|
|
562
566
|
return { error: 'gemini 暂不支持 HTTP 探测(CLI 后端)', code: 'NOT_SUPPORTED' };
|
|
@@ -574,20 +578,19 @@ export async function gatewayTest(args) {
|
|
|
574
578
|
const creds = resolveGatewayCreds(scope, type);
|
|
575
579
|
if ('error' in creds)
|
|
576
580
|
return creds;
|
|
577
|
-
const { baseUrl, apiKey, headers } = creds;
|
|
581
|
+
const { baseUrl, apiKey, headers, queryParams } = creds;
|
|
578
582
|
if (!baseUrl)
|
|
579
583
|
return { error: '未配置 baseUrl(或为官方占位地址)', code: 'INVALID_ARGS' };
|
|
580
584
|
const start = Date.now();
|
|
585
|
+
let timer;
|
|
581
586
|
try {
|
|
582
587
|
const controller = new AbortController();
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
const modelsUrl = base.endsWith('/v1') ? `${base}/models` : `${base}/v1/models`;
|
|
588
|
+
timer = setTimeout(() => controller.abort(), 5000);
|
|
589
|
+
const modelsUrl = appendV1RequestPath(baseUrl, 'models', queryParams);
|
|
586
590
|
const resp = await fetch(modelsUrl, {
|
|
587
591
|
headers: { ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), ...headers },
|
|
588
592
|
signal: controller.signal,
|
|
589
593
|
});
|
|
590
|
-
clearTimeout(timer);
|
|
591
594
|
const latency = Date.now() - start;
|
|
592
595
|
if (!resp.ok)
|
|
593
596
|
return { data: { ok: false, latency, error: `HTTP ${resp.status}` } };
|
|
@@ -600,6 +603,10 @@ export async function gatewayTest(args) {
|
|
|
600
603
|
catch (e) {
|
|
601
604
|
return { data: { ok: false, latency: Date.now() - start, error: e?.message || String(e) } };
|
|
602
605
|
}
|
|
606
|
+
finally {
|
|
607
|
+
if (timer)
|
|
608
|
+
clearTimeout(timer);
|
|
609
|
+
}
|
|
603
610
|
}
|
|
604
611
|
/** 从 model-prices.jsonl 的 PriceRecord 提取展示用价格四元组。 */
|
|
605
612
|
function priceRowToQuad(row) {
|
|
@@ -632,19 +639,19 @@ export async function gatewayModels(args) {
|
|
|
632
639
|
const creds = resolveGatewayCreds(scope, type);
|
|
633
640
|
if ('error' in creds)
|
|
634
641
|
return creds;
|
|
635
|
-
const { baseUrl, apiKey, headers } = creds;
|
|
642
|
+
const { baseUrl, apiKey, headers, queryParams } = creds;
|
|
636
643
|
if (!baseUrl)
|
|
637
644
|
return { error: '未配置 baseUrl(或为官方占位地址)', code: 'INVALID_ARGS' };
|
|
638
645
|
let arr = [];
|
|
639
646
|
let usdToCny = 7; // 默认汇率
|
|
647
|
+
let timer;
|
|
640
648
|
try {
|
|
641
649
|
const controller = new AbortController();
|
|
642
|
-
|
|
643
|
-
const resp = await fetch(
|
|
650
|
+
timer = setTimeout(() => controller.abort(), 8000);
|
|
651
|
+
const resp = await fetch(appendV1RequestPath(baseUrl, 'models', queryParams), {
|
|
644
652
|
headers: { ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), ...headers },
|
|
645
653
|
signal: controller.signal,
|
|
646
654
|
});
|
|
647
|
-
clearTimeout(timer);
|
|
648
655
|
if (!resp.ok)
|
|
649
656
|
return { error: `HTTP ${resp.status}`, code: 'EXEC_FAILED' };
|
|
650
657
|
const json = await resp.json().catch(() => null);
|
|
@@ -657,6 +664,10 @@ export async function gatewayModels(args) {
|
|
|
657
664
|
catch (e) {
|
|
658
665
|
return { error: `请求失败:${e?.message || e}`, code: 'EXEC_FAILED' };
|
|
659
666
|
}
|
|
667
|
+
finally {
|
|
668
|
+
if (timer)
|
|
669
|
+
clearTimeout(timer);
|
|
670
|
+
}
|
|
660
671
|
const evolcoreHome = resolvePaths().root;
|
|
661
672
|
const now = Date.now();
|
|
662
673
|
const models = arr.map((m) => {
|