evolcore 0.0.20 → 0.0.22
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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- 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 +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- 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 +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- 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 +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- 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 +311 -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 +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- 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 +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- 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 +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- 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 +30 -6
- 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/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -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/message-fragments/item.md +1 -1
- 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/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
package/dist/cli/watch-logs.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
const WATCH_LOG_FILE = /\.(?:log|jsonl|out)(?:\..+)?$/;
|
|
3
|
+
/** Whether a file is a supported input for the interactive log watcher. */
|
|
4
|
+
export function isWatchLogFile(file) {
|
|
5
|
+
return WATCH_LOG_FILE.test(path.basename(file));
|
|
6
|
+
}
|
|
2
7
|
/** 去掉轮转后缀(按小时、按日及旧版带连字符日期)。入参可为文件名或绝对路径。 */
|
|
3
8
|
export function shortLogName(file) {
|
|
4
|
-
|
|
9
|
+
const baseName = path.basename(file);
|
|
10
|
+
const stem = baseName.replace(WATCH_LOG_FILE, '');
|
|
11
|
+
return stem
|
|
5
12
|
.replace(/-\d{8}(?:-\d{2})?$/, '') // -YYYYMMDD[-HH](按日/小时轮转)
|
|
6
13
|
.replace(/-\d{4}-\d{2}-\d{2}$/, ''); // -YYYY-MM-DD(按日轮转,如 ts-sdk)
|
|
7
14
|
}
|
|
8
|
-
/**
|
|
15
|
+
/** 从支持的日志文件名列表推导去重、字母序的类型列表。 */
|
|
9
16
|
export function deriveLogTypes(files) {
|
|
10
17
|
const set = new Set();
|
|
11
18
|
for (const f of files) {
|
|
12
|
-
if (!f
|
|
19
|
+
if (!isWatchLogFile(f))
|
|
13
20
|
continue;
|
|
14
21
|
set.add(shortLogName(f));
|
|
15
22
|
}
|
|
@@ -32,6 +32,7 @@ export function getBuiltinRolesConfig() {
|
|
|
32
32
|
export function getManagementCommandPermissions(role) {
|
|
33
33
|
return {
|
|
34
34
|
'contact.read': { allow: true, scopes: ['agent'] },
|
|
35
|
+
'contact.add': { allow: true, scopes: ['relation'], constraints: { ownPeerOnly: true, privateOnly: true } },
|
|
35
36
|
'contact.block': { allow: true, scopes: ['agent'], constraints: { requireAgentAdmin: true } },
|
|
36
37
|
'connect.write': { allow: true, scopes: ['agent'], constraints: { requireAgentAdmin: true } },
|
|
37
38
|
'connect.access.write': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true } },
|
|
@@ -2,6 +2,7 @@ const BEHAVIOR_TOP_FIELDS = new Set([
|
|
|
2
2
|
'active_baseagent',
|
|
3
3
|
'chatmode',
|
|
4
4
|
'mentionMode',
|
|
5
|
+
'groupSlashRequireMentions',
|
|
5
6
|
'responseMode',
|
|
6
7
|
'responseModeParams',
|
|
7
8
|
'flush_delay',
|
|
@@ -17,6 +18,8 @@ const BEHAVIOR_TOP_FIELDS = new Set([
|
|
|
17
18
|
const BASEAGENT_BEHAVIOR_FIELDS = new Set([
|
|
18
19
|
'model',
|
|
19
20
|
'effort',
|
|
21
|
+
'auxiliaryModel',
|
|
22
|
+
'auxiliaryEffort',
|
|
20
23
|
'reasoning',
|
|
21
24
|
'agentProgressSummaries',
|
|
22
25
|
'excludeDynamicSections',
|
|
@@ -25,7 +28,7 @@ const BASEAGENT_BEHAVIOR_FIELDS = new Set([
|
|
|
25
28
|
'mode',
|
|
26
29
|
'useVertex',
|
|
27
30
|
]);
|
|
28
|
-
const SUPPORTED_BASEAGENTS = new Set(['claude', 'codex', 'gemini', 'ecagent'
|
|
31
|
+
const SUPPORTED_BASEAGENTS = new Set(['claude', 'codex', 'gemini', 'ecagent']);
|
|
29
32
|
const SENSITIVE_TOP_FIELDS = new Set([
|
|
30
33
|
'$schema_version',
|
|
31
34
|
'aid',
|
|
@@ -75,10 +78,17 @@ export function resolveConfigFieldRule(fieldPath) {
|
|
|
75
78
|
const [, baseagent, leaf] = parts;
|
|
76
79
|
if (!SUPPORTED_BASEAGENTS.has(baseagent))
|
|
77
80
|
return { class: 'unknown' };
|
|
78
|
-
const
|
|
79
|
-
|
|
81
|
+
const normalizedLeaf = leaf === 'reasoning'
|
|
82
|
+
? 'effort'
|
|
83
|
+
: leaf === 'auxiliaryModel'
|
|
84
|
+
? 'model'
|
|
85
|
+
: leaf === 'auxiliaryEffort'
|
|
86
|
+
? 'effort'
|
|
87
|
+
: leaf;
|
|
88
|
+
const permissionKey = `baseagents.${baseagent}.${normalizedLeaf}`;
|
|
89
|
+
if ((leaf === 'model' || leaf === 'auxiliaryModel') && parts.length === 3)
|
|
80
90
|
return scalar(permissionKey, 'string');
|
|
81
|
-
if ((leaf === 'effort' || leaf === 'reasoning') && parts.length === 3)
|
|
91
|
+
if ((leaf === 'effort' || leaf === 'reasoning' || leaf === 'auxiliaryEffort') && parts.length === 3)
|
|
82
92
|
return scalar(permissionKey, 'effort');
|
|
83
93
|
if (baseagent === 'claude' && (leaf === 'agentProgressSummaries' || leaf === 'excludeDynamicSections') && parts.length === 3) {
|
|
84
94
|
return scalar(permissionKey, 'boolean');
|
|
@@ -101,6 +111,8 @@ export function resolveConfigFieldRule(fieldPath) {
|
|
|
101
111
|
return scalar(field, 'nonnegative-number');
|
|
102
112
|
if (field === 'mentionMode')
|
|
103
113
|
return scalar(field, 'mention-mode');
|
|
114
|
+
if (field === 'groupSlashRequireMentions')
|
|
115
|
+
return scalar(field, 'boolean');
|
|
104
116
|
if (field === 'show_activities')
|
|
105
117
|
return scalar(field, 'show-activities');
|
|
106
118
|
if (field === 'enable_rich_content')
|
|
@@ -199,7 +211,9 @@ export function relationFieldWriteOperation(fieldPath) {
|
|
|
199
211
|
return 'mentionmode.update';
|
|
200
212
|
if (/^baseagents\.[^.]+\.model$/.test(field))
|
|
201
213
|
return 'model.use';
|
|
202
|
-
if (/^baseagents\.[^.]+\.
|
|
214
|
+
if (/^baseagents\.[^.]+\.auxiliaryModel$/.test(field))
|
|
215
|
+
return 'model.use';
|
|
216
|
+
if (/^baseagents\.[^.]+\.(?:effort|reasoning|auxiliaryEffort)$/.test(field)) {
|
|
203
217
|
return 'model.effort';
|
|
204
218
|
}
|
|
205
219
|
return undefined;
|
|
@@ -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);
|
|
@@ -892,7 +933,10 @@ function acquireConfigMutationLock(file) {
|
|
|
892
933
|
return () => releaseConfigMutationLock(lockDir, ownerFile, token);
|
|
893
934
|
}
|
|
894
935
|
catch (error) {
|
|
895
|
-
|
|
936
|
+
const lockMayAlreadyExist = error?.code === 'EEXIST'
|
|
937
|
+
|| error?.code === 'ENOTEMPTY'
|
|
938
|
+
|| (process.platform === 'win32' && error?.code === 'EPERM' && fs.existsSync(lockDir));
|
|
939
|
+
if (!lockMayAlreadyExist)
|
|
896
940
|
throw error;
|
|
897
941
|
if (reclaimStaleConfigMutationLock(lockDir, ownerFile))
|
|
898
942
|
continue;
|
|
@@ -916,10 +960,10 @@ function reclaimStaleConfigMutationLock(lockDir, ownerFile) {
|
|
|
916
960
|
pid = Number(JSON.parse(raw).pid);
|
|
917
961
|
}
|
|
918
962
|
catch {
|
|
919
|
-
return
|
|
963
|
+
return reclaimCorruptConfigMutationLock(lockDir);
|
|
920
964
|
}
|
|
921
965
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
922
|
-
return
|
|
966
|
+
return reclaimCorruptConfigMutationLock(lockDir);
|
|
923
967
|
try {
|
|
924
968
|
process.kill(pid, 0);
|
|
925
969
|
return false;
|
|
@@ -938,6 +982,24 @@ function reclaimStaleConfigMutationLock(lockDir, ownerFile) {
|
|
|
938
982
|
}
|
|
939
983
|
}
|
|
940
984
|
}
|
|
985
|
+
function reclaimCorruptConfigMutationLock(lockDir) {
|
|
986
|
+
try {
|
|
987
|
+
const stat = fs.statSync(lockDir);
|
|
988
|
+
if (Date.now() - stat.mtimeMs < CONFIG_MUTATION_LOCK_WAIT_MS)
|
|
989
|
+
return false;
|
|
990
|
+
const reclaimed = `${lockDir}.reclaimed-corrupt-${crypto.randomBytes(8).toString('hex')}`;
|
|
991
|
+
fs.renameSync(lockDir, reclaimed);
|
|
992
|
+
fs.rmSync(reclaimed, { recursive: true, force: true });
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
if (error?.code === 'ENOENT')
|
|
997
|
+
return true;
|
|
998
|
+
if (error?.code === 'EEXIST' || error?.code === 'ENOTEMPTY' || error?.code === 'EPERM')
|
|
999
|
+
return false;
|
|
1000
|
+
throw error;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
941
1003
|
function releaseConfigMutationLock(lockDir, ownerFile, token) {
|
|
942
1004
|
try {
|
|
943
1005
|
const owner = JSON.parse(fs.readFileSync(ownerFile, 'utf8'));
|
|
@@ -985,7 +1047,11 @@ function ensureSchemaVersion(value, version) {
|
|
|
985
1047
|
}
|
|
986
1048
|
function validateOrThrow(schema, value, target) {
|
|
987
1049
|
const ok = schema.validate(value);
|
|
988
|
-
const scopeErrors = [
|
|
1050
|
+
const scopeErrors = [
|
|
1051
|
+
...validateProcessOnlyFields(target, value),
|
|
1052
|
+
...validateProcessAidDomain(target, value),
|
|
1053
|
+
...validateProcessServiceNames(target, value),
|
|
1054
|
+
];
|
|
989
1055
|
if (!ok || scopeErrors.length > 0) {
|
|
990
1056
|
const errs = [
|
|
991
1057
|
...scopeErrors,
|
|
@@ -1026,6 +1092,24 @@ function validateProcessAidDomain(target, value) {
|
|
|
1026
1092
|
return [`/aun/defaultAidDomain ${error instanceof Error ? error.message : String(error)}`];
|
|
1027
1093
|
}
|
|
1028
1094
|
}
|
|
1095
|
+
function validateProcessServiceNames(target, value) {
|
|
1096
|
+
if (target !== ConfigTarget.Process || !value || typeof value !== 'object')
|
|
1097
|
+
return [];
|
|
1098
|
+
const services = value.services;
|
|
1099
|
+
if (!Array.isArray(services))
|
|
1100
|
+
return [];
|
|
1101
|
+
const seen = new Set();
|
|
1102
|
+
const errors = [];
|
|
1103
|
+
services.forEach((service, index) => {
|
|
1104
|
+
if (!service || typeof service !== 'object' || typeof service.name !== 'string')
|
|
1105
|
+
return;
|
|
1106
|
+
if (seen.has(service.name))
|
|
1107
|
+
errors.push(`/services/${index}/name duplicates service name ${JSON.stringify(service.name)}`);
|
|
1108
|
+
else
|
|
1109
|
+
seen.add(service.name);
|
|
1110
|
+
});
|
|
1111
|
+
return errors;
|
|
1112
|
+
}
|
|
1029
1113
|
/**
|
|
1030
1114
|
* responseModeParams 桶专项校验。
|
|
1031
1115
|
*
|
|
@@ -1076,8 +1160,28 @@ export function validateConfig(target, value) {
|
|
|
1076
1160
|
? loadSchema('contact-book', 2)
|
|
1077
1161
|
: loadSchema(TARGET_SCHEMA[target]);
|
|
1078
1162
|
const withVer = ensureSchemaVersion(value, schema.version);
|
|
1079
|
-
|
|
1080
|
-
|
|
1163
|
+
let normalized = withVer;
|
|
1164
|
+
try {
|
|
1165
|
+
if (target === ConfigTarget.Process)
|
|
1166
|
+
normalized = normalizeProcessConfigCompat(normalizeDaemonServices(withVer));
|
|
1167
|
+
}
|
|
1168
|
+
catch (error) {
|
|
1169
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
1170
|
+
}
|
|
1171
|
+
const ok = schema.validate(normalized);
|
|
1172
|
+
const errors = [
|
|
1173
|
+
...validateProcessOnlyFields(target, normalized),
|
|
1174
|
+
...validateProcessAidDomain(target, normalized),
|
|
1175
|
+
...validateProcessServiceNames(target, normalized),
|
|
1176
|
+
];
|
|
1177
|
+
if (target === ConfigTarget.Defaults || target === ConfigTarget.Agent || target === ConfigTarget.Relation) {
|
|
1178
|
+
try {
|
|
1179
|
+
validateResponseModeParams(normalized?.responseModeParams, target);
|
|
1180
|
+
}
|
|
1181
|
+
catch (error) {
|
|
1182
|
+
errors.push(error instanceof ConfigError ? error.message : String(error));
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1081
1185
|
if (ok && errors.length === 0)
|
|
1082
1186
|
return [];
|
|
1083
1187
|
return [
|
|
@@ -1097,7 +1201,15 @@ export function validateConfigFile(target, sel) {
|
|
|
1097
1201
|
return { exists: false, errors: [] };
|
|
1098
1202
|
// Persisted pre-removal files remain readable: validate the same sanitized
|
|
1099
1203
|
// view used by runtime reads, while new writes are still rejected by write().
|
|
1100
|
-
|
|
1204
|
+
let sanitized;
|
|
1205
|
+
try {
|
|
1206
|
+
sanitized = target === ConfigTarget.Process
|
|
1207
|
+
? normalizeDaemonServices(stripLegacyAunGatewayConfig(value, file, message => configWarn(message)))
|
|
1208
|
+
: stripLegacyAunGatewayConfig(value, file, message => configWarn(message));
|
|
1209
|
+
}
|
|
1210
|
+
catch (error) {
|
|
1211
|
+
return { exists: true, errors: [error instanceof Error ? error.message : String(error)] };
|
|
1212
|
+
}
|
|
1101
1213
|
return { exists: true, errors: validateConfig(target, sanitized) };
|
|
1102
1214
|
}
|
|
1103
1215
|
function fileCacheAvailable() {
|
|
@@ -1159,21 +1271,52 @@ export function resolveAgentConfig(sel, opts = {}) {
|
|
|
1159
1271
|
const agentConfig = sel.self ? read(ConfigTarget.Agent, sel, opts) : null;
|
|
1160
1272
|
const relationConfig = (sel.self && sel.peerKey) ? read(ConfigTarget.Relation, sel, opts) : null;
|
|
1161
1273
|
const layers = [
|
|
1162
|
-
stripStaticOwnerField(read(ConfigTarget.Defaults, undefined, opts)),
|
|
1274
|
+
stripStaticOwnerField(normalizeDefaultsBehaviorCompat(read(ConfigTarget.Defaults, undefined, opts))),
|
|
1163
1275
|
agentConfig,
|
|
1164
1276
|
stripRelationRoleData(stripStaticOwnerField(relationConfig)),
|
|
1165
1277
|
];
|
|
1166
1278
|
const merged = mergeLayers(layers, fields);
|
|
1167
1279
|
return merged;
|
|
1168
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* defaults.schema keeps the historical `config` object as the process-wide
|
|
1283
|
+
* response behavior compatibility surface. Make those values participate in
|
|
1284
|
+
* the normal agent/relation merge chain without leaking the compatibility
|
|
1285
|
+
* container itself into EffectiveAgentConfig.
|
|
1286
|
+
*
|
|
1287
|
+
* Explicit canonical fields (if a future schema adds them) always win. A
|
|
1288
|
+
* single default chat mode applies to the three peer categories because this
|
|
1289
|
+
* compatibility field predates the per-category `chatmode` dictionary.
|
|
1290
|
+
*/
|
|
1291
|
+
export function normalizeDefaultsBehaviorCompat(defaults) {
|
|
1292
|
+
if (!defaults || !defaults.config || typeof defaults.config !== 'object' || Array.isArray(defaults.config))
|
|
1293
|
+
return defaults;
|
|
1294
|
+
const { config, ...rest } = defaults;
|
|
1295
|
+
const normalized = { ...rest };
|
|
1296
|
+
const chatMode = config.chatMode;
|
|
1297
|
+
if (!normalized.chatmode && (chatMode === 'interactive' || chatMode === 'proactive')) {
|
|
1298
|
+
normalized.chatmode = { private: chatMode, group: chatMode, nothuman: chatMode };
|
|
1299
|
+
}
|
|
1300
|
+
if (normalized.mentionMode === undefined && (config.mentionMode === 'disabled' || config.mentionMode === 'mention-only')) {
|
|
1301
|
+
normalized.mentionMode = config.mentionMode;
|
|
1302
|
+
}
|
|
1303
|
+
if (normalized.show_activities === undefined && ['all', 'text', 'none'].includes(config.show_activities)) {
|
|
1304
|
+
normalized.show_activities = config.show_activities;
|
|
1305
|
+
}
|
|
1306
|
+
return normalized;
|
|
1307
|
+
}
|
|
1169
1308
|
function stripStaticOwnerField(config) {
|
|
1170
1309
|
if (!config)
|
|
1171
1310
|
return config;
|
|
1172
1311
|
if (!Object.prototype.hasOwnProperty.call(config, 'owners')
|
|
1173
1312
|
&& !Object.prototype.hasOwnProperty.call(config, 'admins')
|
|
1174
|
-
&& !Object.prototype.hasOwnProperty.call(config, 'access')
|
|
1313
|
+
&& !Object.prototype.hasOwnProperty.call(config, 'access')
|
|
1314
|
+
&& !Object.prototype.hasOwnProperty.call(config, 'groupSlashRequireMentions'))
|
|
1175
1315
|
return config;
|
|
1176
|
-
|
|
1316
|
+
// groupSlashRequireMentions is intentionally Agent-only. Relation/defaults
|
|
1317
|
+
// files allow forward-compatible unknown properties, so strip it before the
|
|
1318
|
+
// generic merge chain can treat a manually written value as an override.
|
|
1319
|
+
const { owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, ...rest } = config;
|
|
1177
1320
|
return rest;
|
|
1178
1321
|
}
|
|
1179
1322
|
function stripRelationRoleData(config) {
|
|
@@ -1212,6 +1355,7 @@ export function resolveEffective(sel, opts = {}) {
|
|
|
1212
1355
|
baseagents: config.baseagents,
|
|
1213
1356
|
chatmode: config.chatmode,
|
|
1214
1357
|
mentionMode: config.mentionMode,
|
|
1358
|
+
groupSlashRequireMentions: config.groupSlashRequireMentions ?? true,
|
|
1215
1359
|
model: config.model,
|
|
1216
1360
|
responseMode: config.responseMode,
|
|
1217
1361
|
responseModeParams: config.responseModeParams,
|
|
@@ -1243,6 +1387,7 @@ const EFFECTIVE_BEHAVIOR_FIELDS = [
|
|
|
1243
1387
|
'baseagents',
|
|
1244
1388
|
'chatmode',
|
|
1245
1389
|
'mentionMode',
|
|
1390
|
+
'groupSlashRequireMentions',
|
|
1246
1391
|
'flush_delay',
|
|
1247
1392
|
'debounce',
|
|
1248
1393
|
'show_activities',
|
|
@@ -14,6 +14,7 @@ const LOCK_NAME = '.contact-book-mutation.lock';
|
|
|
14
14
|
const JOURNAL_NAME = '.contact-book-mutation.json';
|
|
15
15
|
const LOCK_WAIT_MS = 5_000;
|
|
16
16
|
const LOCK_RETRY_MS = 20;
|
|
17
|
+
const CORRUPT_LOCK_STALE_MS = LOCK_WAIT_MS;
|
|
17
18
|
export const EMPTY_CONTACT_REVISION = crypto.createHash('sha256').update('').digest('hex');
|
|
18
19
|
export class ContactMutationError extends Error {
|
|
19
20
|
code;
|
|
@@ -454,7 +455,10 @@ function acquireAgentLock(selfAid) {
|
|
|
454
455
|
return () => releaseAgentLock(lockDir, ownerFile, lockToken);
|
|
455
456
|
}
|
|
456
457
|
catch (error) {
|
|
457
|
-
|
|
458
|
+
const lockMayAlreadyExist = error?.code === 'EEXIST'
|
|
459
|
+
|| error?.code === 'ENOTEMPTY'
|
|
460
|
+
|| (process.platform === 'win32' && error?.code === 'EPERM' && fs.existsSync(lockDir));
|
|
461
|
+
if (!lockMayAlreadyExist)
|
|
458
462
|
throw error;
|
|
459
463
|
if (removeStaleLock(lockDir, ownerFile))
|
|
460
464
|
continue;
|
|
@@ -478,10 +482,10 @@ function removeStaleLock(lockDir, ownerFile) {
|
|
|
478
482
|
pid = Number(JSON.parse(rawOwner).pid);
|
|
479
483
|
}
|
|
480
484
|
catch {
|
|
481
|
-
return
|
|
485
|
+
return reclaimCorruptLock(lockDir);
|
|
482
486
|
}
|
|
483
487
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
484
|
-
return
|
|
488
|
+
return reclaimCorruptLock(lockDir);
|
|
485
489
|
try {
|
|
486
490
|
process.kill(pid, 0);
|
|
487
491
|
return false;
|
|
@@ -503,6 +507,24 @@ function removeStaleLock(lockDir, ownerFile) {
|
|
|
503
507
|
}
|
|
504
508
|
}
|
|
505
509
|
}
|
|
510
|
+
function reclaimCorruptLock(lockDir) {
|
|
511
|
+
try {
|
|
512
|
+
const stat = fs.statSync(lockDir);
|
|
513
|
+
if (Date.now() - stat.mtimeMs < CORRUPT_LOCK_STALE_MS)
|
|
514
|
+
return false;
|
|
515
|
+
const staleClaim = `${lockDir}.reclaimed-corrupt-${crypto.randomBytes(8).toString('hex')}`;
|
|
516
|
+
fs.renameSync(lockDir, staleClaim);
|
|
517
|
+
fs.rmSync(staleClaim, { recursive: true, force: true });
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
if (error?.code === 'ENOENT')
|
|
522
|
+
return true;
|
|
523
|
+
if (error?.code === 'EEXIST' || error?.code === 'ENOTEMPTY' || error?.code === 'EPERM')
|
|
524
|
+
return false;
|
|
525
|
+
throw error;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
506
528
|
function releaseAgentLock(lockDir, ownerFile, lockToken) {
|
|
507
529
|
try {
|
|
508
530
|
const owner = JSON.parse(fs.readFileSync(ownerFile, 'utf8'));
|
|
@@ -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);
|