evolcore 0.0.29 → 0.0.30
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 +7 -0
- package/dist/agents/codex-runner.js +61 -14
- package/dist/aun/aid/agentmd.js +224 -19
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +70 -4
- package/dist/aun/group/index.js +1 -0
- package/dist/{core → aun}/group/invite-service.js +4 -4
- package/dist/channels/daemon.js +1 -1
- package/dist/cli/agent-command.js +7 -7
- package/dist/cli/aun-commands.js +97 -37
- package/dist/cli/data-command.js +2 -1
- package/dist/cli/managed-command-guard.js +2 -2
- package/dist/cli/model.js +1 -1
- package/dist/cli/response.js +1 -1
- package/dist/cli/trigger-command.js +52 -29
- package/dist/config/boot-log.js +95 -1
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/peer-role-resolver.js +21 -0
- package/dist/core/agent-ipc-registration.js +409 -0
- package/dist/core/agent-runtime.js +435 -0
- package/dist/core/auth/delegation-authorization.js +58 -0
- package/dist/core/auth/operation-catalog.js +8 -0
- package/dist/core/bootstrap-service.js +65 -1
- package/dist/core/channel-runtime.js +435 -0
- package/dist/core/command/command-handler.js +4 -1
- package/dist/core/daemon-bootstrap.js +151 -0
- package/dist/core/daemon-control.js +205 -0
- package/dist/core/daemon-lifecycle.js +225 -0
- package/dist/core/data-migration.js +4 -1
- package/dist/core/handoff/ipc-registration.js +156 -0
- package/dist/core/ipc-registration.js +457 -0
- package/dist/core/memory/consolidation-trigger.js +8 -0
- package/dist/core/memory/consolidation.js +838 -0
- package/dist/core/message/aun-ipc-sender.js +226 -0
- package/dist/core/message/outbound.js +165 -0
- package/dist/core/permission/approval-gateway.js +13 -0
- package/dist/core/permission/ec-command-parser.js +80 -35
- package/dist/core/permission/sandbox-runtime.js +23 -2
- package/dist/core/permission/tool-error-code.js +1 -0
- package/dist/core/permission/tool-policy.js +61 -24
- package/dist/core/session/session-idle-monitor.js +17 -0
- package/dist/index.js +352 -4841
- package/dist/ipc.js +2 -1
- package/dist/stats/db.js +24 -0
- package/dist/stats/index.js +1 -1
- package/dist/stats/writer.js +25 -1
- package/dist/trigger/application-service.js +157 -0
- package/dist/trigger/authorization.js +371 -0
- package/dist/trigger/ipc-service.js +406 -0
- package/dist/trigger/local-files.js +133 -0
- package/dist/trigger/manager.js +235 -22
- package/dist/trigger/scheduler.js +30 -5
- package/dist/trigger/script-executor.js +176 -39
- package/dist/trigger/script-sandbox.js +458 -0
- package/dist/trigger/system-definitions.js +188 -0
- package/dist/trigger/validation.js +0 -3
- package/dist/utils/aid-bind.js +1 -1
- package/dist/utils/evolcore-version.js +74 -0
- package/dist/utils/logger.js +16 -0
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/model.md +3 -2
- package/kits/docs/evolcore/rpc.md +4 -0
- package/kits/docs/evolcore/trigger.md +15 -1
- package/kits/schemas/relation-config.schema.9.json +1 -1
- package/kits/templates/system-fragments/commands.md +0 -1
- package/package.json +1 -1
- package/dist/core/memory/launch-consolidation.js +0 -395
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.0.30 (2026-10-10)
|
|
9
|
+
|
|
10
|
+
- 重构 Agent 运行时、渠道连接与 daemon 生命周期编排,拆分 IPC 注册和受管操作职责,提升启动、重载与数据迁移的可维护性和诊断能力。
|
|
11
|
+
- 统一 AUN 出站消息发送链路,迁移群邀请服务与记忆整理入口,并完善 AID 名片和受管操作。
|
|
12
|
+
- 强化 Trigger 授权、脚本沙箱与 Agent 作用域边界,收紧委托操作和权限校验。
|
|
13
|
+
- 改进 Codex 压缩完成状态、启动版本诊断与迁移后空目录清理,提升运行稳定性。
|
|
14
|
+
|
|
8
15
|
## 0.0.29 (2026-10-08)
|
|
9
16
|
|
|
10
17
|
- 完成记忆系统 v3:支持 note 标签、三作用域工作记忆、自动整理触发、整理队列与签名索引,并完善 `ec memory`、`/memory` 命令和 ECK 注入。
|
|
@@ -87,6 +87,8 @@ class AsyncEventQueue {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
function fallbackErrorCodeForToolDenial(decisionSource, policyCode) {
|
|
90
|
+
if (decisionSource === 'policy' && policyCode === 'apply_patch_invalid_argument')
|
|
91
|
+
return 'INVALID_ARGUMENT';
|
|
90
92
|
if (decisionSource === 'policy')
|
|
91
93
|
return 'POLICY_DENIED';
|
|
92
94
|
if (decisionSource === 'approval')
|
|
@@ -2427,13 +2429,15 @@ export class CodexRunner {
|
|
|
2427
2429
|
}
|
|
2428
2430
|
async startAndWaitForCompact(appServer, threadId) {
|
|
2429
2431
|
const completion = this.waitForThreadCompacted(appServer, threadId, Date.now());
|
|
2432
|
+
const compactStart = appServer.threadCompactStart(threadId);
|
|
2430
2433
|
try {
|
|
2431
|
-
await
|
|
2434
|
+
await Promise.race([compactStart, completion.promise]);
|
|
2432
2435
|
await completion.promise;
|
|
2433
2436
|
return true;
|
|
2434
2437
|
}
|
|
2435
2438
|
finally {
|
|
2436
2439
|
completion.dispose();
|
|
2440
|
+
void compactStart.catch(() => undefined);
|
|
2437
2441
|
}
|
|
2438
2442
|
}
|
|
2439
2443
|
waitForThreadCompacted(appServer, threadId, startedAtMs) {
|
|
@@ -2450,7 +2454,17 @@ export class CodexRunner {
|
|
|
2450
2454
|
unsubscribe?.();
|
|
2451
2455
|
resolve();
|
|
2452
2456
|
};
|
|
2453
|
-
const
|
|
2457
|
+
const fail = (reject, message, source) => {
|
|
2458
|
+
if (settled)
|
|
2459
|
+
return;
|
|
2460
|
+
settled = true;
|
|
2461
|
+
if (pollTimer)
|
|
2462
|
+
clearInterval(pollTimer);
|
|
2463
|
+
logger.warn(`[CodexRunner] Compact failed for thread: ${threadId} (${source}): ${message}`);
|
|
2464
|
+
unsubscribe?.();
|
|
2465
|
+
reject(new Error(message));
|
|
2466
|
+
};
|
|
2467
|
+
const promise = new Promise((resolve, reject) => {
|
|
2454
2468
|
unsubscribe = appServer.onNotification(notification => {
|
|
2455
2469
|
if (notification.method !== 'thread/compacted')
|
|
2456
2470
|
return;
|
|
@@ -2460,11 +2474,17 @@ export class CodexRunner {
|
|
|
2460
2474
|
return;
|
|
2461
2475
|
settle(resolve, 'notification');
|
|
2462
2476
|
});
|
|
2463
|
-
|
|
2464
|
-
|
|
2477
|
+
const checkPersistedCompletion = () => {
|
|
2478
|
+
const completion = this.readPersistedCompactCompletion(threadId, startedAtMs);
|
|
2479
|
+
if (completion?.kind === 'error') {
|
|
2480
|
+
fail(reject, completion.message, 'session-log');
|
|
2481
|
+
}
|
|
2482
|
+
else if (completion?.kind === 'complete') {
|
|
2465
2483
|
settle(resolve, 'session-log');
|
|
2466
2484
|
}
|
|
2467
|
-
}
|
|
2485
|
+
};
|
|
2486
|
+
checkPersistedCompletion();
|
|
2487
|
+
pollTimer = setInterval(checkPersistedCompletion, 1000);
|
|
2468
2488
|
pollTimer.unref?.();
|
|
2469
2489
|
});
|
|
2470
2490
|
return {
|
|
@@ -2477,16 +2497,16 @@ export class CodexRunner {
|
|
|
2477
2497
|
},
|
|
2478
2498
|
};
|
|
2479
2499
|
}
|
|
2480
|
-
|
|
2500
|
+
readPersistedCompactCompletion(threadId, startedAtMs) {
|
|
2481
2501
|
const sessionFile = this.findCodexSessionFile(threadId);
|
|
2482
2502
|
if (!sessionFile)
|
|
2483
|
-
return
|
|
2503
|
+
return undefined;
|
|
2484
2504
|
let text = '';
|
|
2485
2505
|
try {
|
|
2486
2506
|
text = fs.readFileSync(sessionFile, 'utf8');
|
|
2487
2507
|
}
|
|
2488
2508
|
catch {
|
|
2489
|
-
return
|
|
2509
|
+
return undefined;
|
|
2490
2510
|
}
|
|
2491
2511
|
const threshold = startedAtMs - 1000;
|
|
2492
2512
|
for (const line of text.trimEnd().split('\n').reverse()) {
|
|
@@ -2499,16 +2519,43 @@ export class CodexRunner {
|
|
|
2499
2519
|
catch {
|
|
2500
2520
|
continue;
|
|
2501
2521
|
}
|
|
2502
|
-
const
|
|
2522
|
+
const payload = entry.payload && typeof entry.payload === 'object' ? entry.payload : undefined;
|
|
2523
|
+
const timestampMs = typeof entry.timestamp === 'string' ? Date.parse(entry.timestamp) : NaN;
|
|
2524
|
+
const completedAtMs = typeof payload?.completed_at_ms === 'number'
|
|
2525
|
+
? payload.completed_at_ms
|
|
2526
|
+
: typeof entry.completed_at_ms === 'number'
|
|
2527
|
+
? entry.completed_at_ms
|
|
2528
|
+
: typeof payload?.completed_at === 'number'
|
|
2529
|
+
? payload.completed_at * 1000
|
|
2530
|
+
: typeof entry.completed_at === 'number'
|
|
2531
|
+
? entry.completed_at * 1000
|
|
2532
|
+
: NaN;
|
|
2533
|
+
const ts = Number.isFinite(timestampMs) ? timestampMs : completedAtMs;
|
|
2503
2534
|
if (!Number.isFinite(ts))
|
|
2504
2535
|
continue;
|
|
2505
2536
|
if (ts < threshold)
|
|
2506
2537
|
break;
|
|
2507
|
-
const payloadType =
|
|
2508
|
-
if (entry.type === 'compacted'
|
|
2509
|
-
|
|
2538
|
+
const payloadType = payload?.type;
|
|
2539
|
+
if (entry.type === 'compacted'
|
|
2540
|
+
|| payloadType === 'context_compacted'
|
|
2541
|
+
|| (payloadType === 'item_completed' && payload?.item?.type === 'ContextCompaction')) {
|
|
2542
|
+
return { kind: 'complete' };
|
|
2543
|
+
}
|
|
2544
|
+
const taskComplete = entry.type === 'task_complete'
|
|
2545
|
+
? entry
|
|
2546
|
+
: payloadType === 'task_complete'
|
|
2547
|
+
? payload
|
|
2548
|
+
: undefined;
|
|
2549
|
+
const taskError = taskComplete?.error;
|
|
2550
|
+
const message = typeof taskError === 'string'
|
|
2551
|
+
? taskError
|
|
2552
|
+
: typeof taskError?.message === 'string'
|
|
2553
|
+
? taskError.message
|
|
2554
|
+
: undefined;
|
|
2555
|
+
if (message?.trim())
|
|
2556
|
+
return { kind: 'error', message };
|
|
2510
2557
|
}
|
|
2511
|
-
return
|
|
2558
|
+
return undefined;
|
|
2512
2559
|
}
|
|
2513
2560
|
codexHomeDir() {
|
|
2514
2561
|
return process.env.CODEX_HOME || path.join(process.env.HOME || os.homedir(), '.codex');
|
|
@@ -4910,7 +4957,7 @@ export class CodexRunner {
|
|
|
4910
4957
|
? {
|
|
4911
4958
|
isError: true,
|
|
4912
4959
|
error: managedHookDenial.reason,
|
|
4913
|
-
errorCode: '
|
|
4960
|
+
errorCode: errorCodeForToolDenial('policy', managedHookDenial.policyCode),
|
|
4914
4961
|
...(errorDetailCodeForToolDenial('policy', managedHookDenial.policyCode, managedHookDenial.errorDetailCode)
|
|
4915
4962
|
? { errorDetailCode: errorDetailCodeForToolDenial('policy', managedHookDenial.policyCode, managedHookDenial.errorDetailCode) }
|
|
4916
4963
|
: {}),
|
package/dist/aun/aid/agentmd.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { parseDocument } from 'yaml';
|
|
@@ -89,6 +90,80 @@ export function validateAgentTags(tags, options = {}) {
|
|
|
89
90
|
function resolveAgentMdAunPath(opts) {
|
|
90
91
|
return opts?.aunPath ?? opts?.store?.aunPath ?? resolveRoot();
|
|
91
92
|
}
|
|
93
|
+
function agentMdMetadataPath(aid, aunPath) {
|
|
94
|
+
return path.join(aidLocalDir(aid, aunPath), 'agentmd.json');
|
|
95
|
+
}
|
|
96
|
+
function readAgentMdMetadata(aid, aunPath) {
|
|
97
|
+
try {
|
|
98
|
+
const value = JSON.parse(fs.readFileSync(agentMdMetadataPath(aid, aunPath), 'utf-8'));
|
|
99
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function normalizeRemoteVersion(value) {
|
|
106
|
+
const exists = typeof value.exists === 'boolean' ? value.exists : undefined;
|
|
107
|
+
const etag = String(value.etag ?? '').trim();
|
|
108
|
+
const lastModified = String(value.last_modified ?? value.lastModified ?? '').trim();
|
|
109
|
+
if (exists === undefined && !etag && !lastModified)
|
|
110
|
+
return undefined;
|
|
111
|
+
return {
|
|
112
|
+
...(exists !== undefined ? { exists } : {}),
|
|
113
|
+
...(etag ? { etag } : {}),
|
|
114
|
+
...(lastModified ? { lastModified } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function remoteVersionFromMetadata(metadata) {
|
|
118
|
+
return normalizeRemoteVersion({
|
|
119
|
+
exists: metadata.remote_status === 'missing' ? false : metadata.remote_status === 'found' ? true : undefined,
|
|
120
|
+
etag: metadata.remote_etag ?? metadata.etag,
|
|
121
|
+
last_modified: metadata.last_modified,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function remoteVersionsEqual(expected, actual) {
|
|
125
|
+
if (expected.exists !== undefined && expected.exists !== (actual?.exists ?? false))
|
|
126
|
+
return false;
|
|
127
|
+
if (expected.etag !== undefined && expected.etag !== (actual?.etag ?? ''))
|
|
128
|
+
return false;
|
|
129
|
+
if (expected.lastModified !== undefined && expected.lastModified !== (actual?.lastModified ?? ''))
|
|
130
|
+
return false;
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
export class AgentMdVersionConflictError extends Error {
|
|
134
|
+
expected;
|
|
135
|
+
actual;
|
|
136
|
+
code = 'REMOTE_VERSION_CONFLICT';
|
|
137
|
+
constructor(expected, actual) {
|
|
138
|
+
super(`远端 agent.md 版本已变化,请重新 get 后再修改(expected=${JSON.stringify(expected)}, actual=${JSON.stringify(actual ?? {})})`);
|
|
139
|
+
this.expected = expected;
|
|
140
|
+
this.actual = actual;
|
|
141
|
+
this.name = 'AgentMdVersionConflictError';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export class AgentMdVersionUnavailableError extends Error {
|
|
145
|
+
code = 'REMOTE_VERSION_UNAVAILABLE';
|
|
146
|
+
constructor(message) {
|
|
147
|
+
super(message);
|
|
148
|
+
this.name = 'AgentMdVersionUnavailableError';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const agentMdWriteLocks = new Map();
|
|
152
|
+
async function withAgentMdWriteLock(key, operation) {
|
|
153
|
+
const previous = agentMdWriteLocks.get(key) ?? Promise.resolve();
|
|
154
|
+
let release;
|
|
155
|
+
const current = new Promise(resolve => { release = resolve; });
|
|
156
|
+
agentMdWriteLocks.set(key, current);
|
|
157
|
+
await previous;
|
|
158
|
+
try {
|
|
159
|
+
return await operation();
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
release();
|
|
163
|
+
if (agentMdWriteLocks.get(key) === current)
|
|
164
|
+
agentMdWriteLocks.delete(key);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
92
167
|
// A process can host agents that deliberately use different AUN keystore
|
|
93
168
|
// roots. Keep their display-name state separate so one root cannot shadow the
|
|
94
169
|
// other with a same-named AID.
|
|
@@ -359,11 +434,79 @@ export function updateAgentMdFrontmatterAvatar(content, avatarUrl) {
|
|
|
359
434
|
export function updateAgentMdFrontmatterName(content, name) {
|
|
360
435
|
return updateAgentMdFrontmatterField(content, 'name', name, 'aid');
|
|
361
436
|
}
|
|
437
|
+
function updateAgentMdFrontmatterTags(content, tags) {
|
|
438
|
+
const original = content;
|
|
439
|
+
const payload = stripAgentMdSignature(content);
|
|
440
|
+
const lines = payload.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
441
|
+
const openLine = lines[0];
|
|
442
|
+
if (!openLine || lineValue(openLine) !== '---')
|
|
443
|
+
throw new Error('agent.md has no YAML frontmatter');
|
|
444
|
+
const closeIndex = lines.findIndex((line, index) => index > 0 && lineValue(line) === '---');
|
|
445
|
+
if (closeIndex < 0)
|
|
446
|
+
throw new Error('agent.md frontmatter is not closed');
|
|
447
|
+
const defaultEol = lineEnding(openLine, '\n');
|
|
448
|
+
const fmLines = lines.slice(1, closeIndex);
|
|
449
|
+
const fieldPattern = /^tags\s*:/;
|
|
450
|
+
const fieldIndex = fmLines.findIndex(line => fieldPattern.test(lineValue(line)));
|
|
451
|
+
const replacement = [
|
|
452
|
+
`tags:${defaultEol}`,
|
|
453
|
+
...tags.map(tag => ` - ${yamlDoubleQuote(tag)}${defaultEol}`),
|
|
454
|
+
];
|
|
455
|
+
if (fieldIndex >= 0) {
|
|
456
|
+
let endIndex = fieldIndex + 1;
|
|
457
|
+
while (endIndex < fmLines.length && /^\s*-\s+/.test(lineValue(fmLines[endIndex] ?? '')))
|
|
458
|
+
endIndex++;
|
|
459
|
+
fmLines.splice(fieldIndex, endIndex - fieldIndex, ...replacement);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
const versionIndex = fmLines.findIndex(line => /^version\s*:/.test(lineValue(line)));
|
|
463
|
+
const insertIndex = versionIndex >= 0 ? versionIndex + 1 : fmLines.length;
|
|
464
|
+
fmLines.splice(insertIndex, 0, ...replacement);
|
|
465
|
+
}
|
|
466
|
+
const updated = ensureTrailingNewline([
|
|
467
|
+
lines[0],
|
|
468
|
+
...fmLines,
|
|
469
|
+
...lines.slice(closeIndex),
|
|
470
|
+
].join(''));
|
|
471
|
+
return { content: updated, changed: updated !== original };
|
|
472
|
+
}
|
|
473
|
+
export function updateAgentMdFrontmatterFieldValue(content, field, value) {
|
|
474
|
+
if (field === 'tags') {
|
|
475
|
+
if (!Array.isArray(value))
|
|
476
|
+
throw new Error('agent.md tags must be an array of strings');
|
|
477
|
+
const errors = validateAgentTags(value);
|
|
478
|
+
if (errors.length > 0)
|
|
479
|
+
throw new Error(`agent.md tags ${errors.join('; ')}`);
|
|
480
|
+
const tags = [...new Set(value.map(tag => tag.trim()))];
|
|
481
|
+
return updateAgentMdFrontmatterTags(content, tags);
|
|
482
|
+
}
|
|
483
|
+
if (typeof value !== 'string')
|
|
484
|
+
throw new Error(`agent.md ${field} must be a string`);
|
|
485
|
+
const normalized = value.trim();
|
|
486
|
+
if (field === 'name' && !normalized)
|
|
487
|
+
throw new Error('agent.md name must be a non-empty string');
|
|
488
|
+
if (field === 'type') {
|
|
489
|
+
const error = validateAgentType(normalized);
|
|
490
|
+
if (error)
|
|
491
|
+
throw new Error(`agent.md type ${error}`);
|
|
492
|
+
return updateAgentMdFrontmatterField(content, field, normalizeAgentType(normalized), 'aid');
|
|
493
|
+
}
|
|
494
|
+
if (field === 'version' && !/^\d+\.\d+\.\d+$/.test(normalized)) {
|
|
495
|
+
throw new Error('agent.md version must use major.minor.patch format');
|
|
496
|
+
}
|
|
497
|
+
if (field === 'description' && Array.from(value).length > AGENT_DESCRIPTION_MAX_LENGTH) {
|
|
498
|
+
throw new Error(`agent.md description must be at most ${AGENT_DESCRIPTION_MAX_LENGTH} characters`);
|
|
499
|
+
}
|
|
500
|
+
return updateAgentMdFrontmatterField(content, field, field === 'description' ? value : normalized, field === 'name' ? 'aid' : undefined);
|
|
501
|
+
}
|
|
362
502
|
/** Normalize an SDK verification result to the local union type. */
|
|
363
503
|
function normalizeVerification(v) {
|
|
364
504
|
const status = v.status === 'verified' ? 'verified' : v.status === 'unsigned' ? 'unsigned' : 'invalid';
|
|
365
505
|
return { status, ...(v.reason ? { reason: String(v.reason) } : {}) };
|
|
366
506
|
}
|
|
507
|
+
function localStateFromVerification(verification) {
|
|
508
|
+
return verification.status === 'verified' ? 'signed' : verification.status;
|
|
509
|
+
}
|
|
367
510
|
/**
|
|
368
511
|
* Verify locally-cached agent.md content offline via the AID value object.
|
|
369
512
|
* Loads the peer cert through the store (no network, no private key required).
|
|
@@ -392,10 +535,13 @@ export async function agentmdGet(aid, opts) {
|
|
|
392
535
|
try {
|
|
393
536
|
let content;
|
|
394
537
|
let verification;
|
|
538
|
+
let source = 'remote';
|
|
539
|
+
let remoteVersion;
|
|
395
540
|
const r = await store.downloadAgentMd(aid);
|
|
396
541
|
if (r.ok) {
|
|
397
542
|
content = r.data.content;
|
|
398
543
|
verification = normalizeVerification(r.data.verification);
|
|
544
|
+
remoteVersion = normalizeRemoteVersion({ exists: true, etag: r.data.etag, last_modified: r.data.last_modified });
|
|
399
545
|
}
|
|
400
546
|
else {
|
|
401
547
|
if (opts?.remoteOnly) {
|
|
@@ -411,13 +557,23 @@ export async function agentmdGet(aid, opts) {
|
|
|
411
557
|
throw new Error(`fetch agent.md failed for ${aid}: ${r.error.message}`);
|
|
412
558
|
}
|
|
413
559
|
content = fs.readFileSync(localPath, 'utf-8');
|
|
560
|
+
source = 'local-cache';
|
|
561
|
+
remoteVersion = remoteVersionFromMetadata(readAgentMdMetadata(aid, aunPath));
|
|
414
562
|
if (opts?.withVerification) {
|
|
415
563
|
verification = verifyLocal(store, aid, content);
|
|
416
564
|
}
|
|
417
565
|
}
|
|
418
566
|
if (!opts?.withVerification)
|
|
419
567
|
return content;
|
|
420
|
-
|
|
568
|
+
const finalVerification = verification ?? { status: 'unsigned' };
|
|
569
|
+
return {
|
|
570
|
+
content,
|
|
571
|
+
verification: finalVerification,
|
|
572
|
+
source,
|
|
573
|
+
contentSha256: crypto.createHash('sha256').update(content, 'utf8').digest('hex'),
|
|
574
|
+
localState: localStateFromVerification(finalVerification),
|
|
575
|
+
...(remoteVersion ? { remoteVersion } : {}),
|
|
576
|
+
};
|
|
421
577
|
}
|
|
422
578
|
finally {
|
|
423
579
|
if (ownStore)
|
|
@@ -428,39 +584,63 @@ export async function agentmdGet(aid, opts) {
|
|
|
428
584
|
}
|
|
429
585
|
}
|
|
430
586
|
/**
|
|
431
|
-
*
|
|
587
|
+
* Validate and upload agent.md. The SDK signs the payload and persists its
|
|
588
|
+
* signed local cache only after the remote upload succeeds.
|
|
432
589
|
*
|
|
433
590
|
* fastaun 0.4.7: uploadAgentMd moved from AUNClient to AIDStore; the store builds
|
|
434
591
|
* its own LocalTokenStore + AuthFlow internally, so no AUNClient/authenticate needed.
|
|
435
592
|
*/
|
|
436
|
-
|
|
593
|
+
async function publishAgentMd(content, opts) {
|
|
437
594
|
const validation = validateAgentMdFrontmatter(content, opts.aid);
|
|
438
595
|
if (!validation.valid) {
|
|
439
596
|
throw new Error(`invalid agent.md frontmatter: ${validation.errors.join('; ')}`);
|
|
440
597
|
}
|
|
441
598
|
const aunPath = resolveAgentMdAunPath(opts);
|
|
442
|
-
const store = opts.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
443
|
-
const ownStore = !opts.store;
|
|
444
599
|
const dir = aidLocalDir(opts.aid, aunPath);
|
|
445
600
|
const filePath = path.join(dir, 'agent.md');
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
449
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
601
|
+
const store = opts.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
602
|
+
const ownStore = !opts.store;
|
|
450
603
|
try {
|
|
604
|
+
const metadataVersion = remoteVersionFromMetadata(readAgentMdMetadata(opts.aid, aunPath));
|
|
605
|
+
const expectedVersion = opts.expectedRemoteVersion ?? metadataVersion;
|
|
606
|
+
let versionChecked = false;
|
|
607
|
+
let currentVersion;
|
|
608
|
+
const candidate = store;
|
|
609
|
+
if (typeof candidate.checkAgentMd !== 'function') {
|
|
610
|
+
if (expectedVersion) {
|
|
611
|
+
throw new AgentMdVersionUnavailableError('当前 AUN SDK 不支持 agent.md 远端版本检查,已拒绝覆盖发布');
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
const checked = await candidate.checkAgentMd(opts.aid, 0);
|
|
616
|
+
if (!checked.ok) {
|
|
617
|
+
if (expectedVersion) {
|
|
618
|
+
throw new AgentMdVersionUnavailableError(`无法确认远端 agent.md 版本,已拒绝发布: ${checked.error?.message ?? 'unknown error'}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
versionChecked = true;
|
|
623
|
+
currentVersion = normalizeRemoteVersion({
|
|
624
|
+
exists: checked.data?.remote_found,
|
|
625
|
+
etag: checked.data?.remote_etag,
|
|
626
|
+
last_modified: checked.data?.last_modified,
|
|
627
|
+
});
|
|
628
|
+
if (expectedVersion && !remoteVersionsEqual(expectedVersion, currentVersion)) {
|
|
629
|
+
throw new AgentMdVersionConflictError(expectedVersion, currentVersion);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
451
633
|
const r = await store.uploadAgentMd(opts.aid, content);
|
|
452
634
|
if (!r.ok)
|
|
453
635
|
throw new Error(`upload agent.md failed for ${opts.aid}: ${r.error.message}`);
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
catch { /* ignore */ }
|
|
463
|
-
throw e;
|
|
636
|
+
invalidateAgentDisplayName(opts.aid, aunPath);
|
|
637
|
+
const remoteVersion = normalizeRemoteVersion({ exists: true, etag: r.data?.etag, last_modified: r.data?.last_modified ?? r.data?.lastModified });
|
|
638
|
+
return {
|
|
639
|
+
path: filePath,
|
|
640
|
+
...(remoteVersion ? { remoteVersion } : {}),
|
|
641
|
+
versionChecked,
|
|
642
|
+
atomicVersionCheck: false,
|
|
643
|
+
};
|
|
464
644
|
}
|
|
465
645
|
finally {
|
|
466
646
|
if (ownStore)
|
|
@@ -470,6 +650,31 @@ export async function agentmdPut(content, opts) {
|
|
|
470
650
|
catch { /* ignore */ }
|
|
471
651
|
}
|
|
472
652
|
}
|
|
653
|
+
export async function agentmdPut(content, opts) {
|
|
654
|
+
const aunPath = resolveAgentMdAunPath(opts);
|
|
655
|
+
const lockKey = `${path.resolve(aunPath)}\u0000${opts.aid.toLowerCase()}`;
|
|
656
|
+
return withAgentMdWriteLock(lockKey, () => publishAgentMd(content, opts));
|
|
657
|
+
}
|
|
658
|
+
export async function agentmdSet(aid, field, value, opts = {}) {
|
|
659
|
+
const aunPath = resolveAgentMdAunPath(opts);
|
|
660
|
+
const lockKey = `${path.resolve(aunPath)}\u0000${aid.toLowerCase()}`;
|
|
661
|
+
return withAgentMdWriteLock(lockKey, async () => {
|
|
662
|
+
const localPath = agentMdPath(aid, aunPath);
|
|
663
|
+
if (!fs.existsSync(localPath)) {
|
|
664
|
+
const error = new Error(`local agent.md not found: ${aid}`);
|
|
665
|
+
error.code = 'LOCAL_AGENTMD_MISSING';
|
|
666
|
+
throw error;
|
|
667
|
+
}
|
|
668
|
+
const current = fs.readFileSync(localPath, 'utf-8');
|
|
669
|
+
const updated = updateAgentMdFrontmatterFieldValue(current, field, value);
|
|
670
|
+
const result = await publishAgentMd(updated.content, {
|
|
671
|
+
...opts,
|
|
672
|
+
aid,
|
|
673
|
+
expectedRemoteVersion: remoteVersionFromMetadata(readAgentMdMetadata(aid, aunPath)),
|
|
674
|
+
});
|
|
675
|
+
return { ...result, field };
|
|
676
|
+
});
|
|
677
|
+
}
|
|
473
678
|
/**
|
|
474
679
|
* Check if agent.md is up-to-date (30-day TTL), fetch if changed.
|
|
475
680
|
* Returns changed=true + content when a new version was downloaded.
|
package/dist/aun/aid/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { AgentMdVersionUnavailableError, AgentMdVersionConflictError } from './agentmd.js';
|
|
1
2
|
export { AGENT_TAG_MIN_COUNT, AGENT_TAG_MAX_COUNT, AGENT_TAG_MAX_LENGTH, validateAgentTag, validateAgentTags, } from './agentmd.js';
|
|
2
3
|
export { isValidAid, aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, verifySignAbility, probePkiRecoverability, appendAidLifecycle, readAidLifecycle } from './identity.js';
|
|
3
|
-
export { AGENT_TYPES, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_TYPE, normalizeAgentType, validateAgentType, buildInitialAgentMd, validateAgentMdFrontmatter, agentmdGet, agentmdPut, agentmdSync, invalidateAgentDisplayName, parseAgentDisplayName, refreshAgentDisplayName, resolveAgentDisplayName, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterName, } from './agentmd.js';
|
|
4
|
+
export { AGENT_TYPES, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_TYPE, normalizeAgentType, validateAgentType, buildInitialAgentMd, validateAgentMdFrontmatter, agentmdGet, agentmdPut, agentmdSet, agentmdSync, invalidateAgentDisplayName, parseAgentDisplayName, refreshAgentDisplayName, resolveAgentDisplayName, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterFieldValue, updateAgentMdFrontmatterName, } from './agentmd.js';
|
|
4
5
|
export { MIN_AUN_CORE_SDK, AUN_CORE_SDK_PKG, isAunSdkVersionOk, resolveAunCoreSdkPkg, ensureAunSdk, isAunSdkReady, downloadCaRoot, suppressSdkLogs, } from './client.js';
|
|
5
6
|
export { getAidStore, loadClient, loadAid, AidLoadError, SLOT } from './store.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { resolveRoot } from '../../paths.js';
|
|
4
|
-
import { agentmdGet, agentmdPut } from './agentmd.js';
|
|
4
|
+
import { agentmdGet, agentmdPut, agentmdSet, } from './agentmd.js';
|
|
5
5
|
import { uploadAvatarBuffer, uploadAvatarUrl } from '../../utils/avatar-upload.js';
|
|
6
6
|
import { aidLookup, aidShow } from './identity.js';
|
|
7
7
|
import { isValidAid } from './validation.js';
|
|
@@ -55,6 +55,53 @@ function parseAvatar(args) {
|
|
|
55
55
|
},
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
+
const AGENTMD_EDITABLE_FIELDS = new Set(['name', 'type', 'description', 'version', 'tags']);
|
|
59
|
+
function parseAgentmdSet(args) {
|
|
60
|
+
const aidResult = parseAid(args[2]);
|
|
61
|
+
if (!aidResult.ok)
|
|
62
|
+
return aidResult;
|
|
63
|
+
const field = args[3];
|
|
64
|
+
if (!field || !AGENTMD_EDITABLE_FIELDS.has(field)) {
|
|
65
|
+
return { ok: false, code: 'INVALID_ARGUMENT', reason: `agentmd set field must be one of ${[...AGENTMD_EDITABLE_FIELDS].join(', ')}` };
|
|
66
|
+
}
|
|
67
|
+
if (args[4] === undefined)
|
|
68
|
+
return { ok: false, code: 'INVALID_ARGUMENT', reason: 'agentmd set requires a value' };
|
|
69
|
+
if (args.length !== 5 && !(args.length === 7 && args[5] === '--format' && args[6] === 'json')) {
|
|
70
|
+
return { ok: false, code: 'NOT_ALLOWED', reason: 'agentmd set only supports an optional --format json suffix' };
|
|
71
|
+
}
|
|
72
|
+
let value = args[4];
|
|
73
|
+
if (field === 'tags') {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(args[4]);
|
|
76
|
+
if (!Array.isArray(parsed) || parsed.some(item => typeof item !== 'string'))
|
|
77
|
+
throw new Error('not an array of strings');
|
|
78
|
+
value = parsed;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return { ok: false, code: 'INVALID_ARGUMENT', reason: 'agentmd set tags value must be a JSON array of strings' };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
command: {
|
|
87
|
+
kind: 'agentmd.set',
|
|
88
|
+
aid: aidResult.aid,
|
|
89
|
+
field,
|
|
90
|
+
value,
|
|
91
|
+
operationId: 'ec.aid.agentmd.set',
|
|
92
|
+
canonicalArgv: ['aid', 'agentmd', 'set', aidResult.aid, field, args[4]],
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function rethrowAgentmdError(error) {
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
99
|
+
? String(error.code || '')
|
|
100
|
+
: '';
|
|
101
|
+
if (code)
|
|
102
|
+
throw new ManagedAidOperationError(code, message);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
58
105
|
/**
|
|
59
106
|
* The daemon accepts only the narrowly scoped AID commands exposed to managed
|
|
60
107
|
* tasks. Public lookup/get may name any valid AID; local show/publish remain
|
|
@@ -94,10 +141,12 @@ export function resolveManagedAidOperation(argv) {
|
|
|
94
141
|
},
|
|
95
142
|
};
|
|
96
143
|
}
|
|
144
|
+
if (subcommand === 'agentmd' && args[1] === 'set')
|
|
145
|
+
return parseAgentmdSet(args);
|
|
97
146
|
return {
|
|
98
147
|
ok: false,
|
|
99
148
|
code: 'NOT_ALLOWED',
|
|
100
|
-
reason: 'Managed AID operations only allow exact show, lookup, agentmd get, agentmd put, or avatar commands with supported options',
|
|
149
|
+
reason: 'Managed AID operations only allow exact show, lookup, agentmd get, agentmd put, agentmd set, or avatar commands with supported options',
|
|
101
150
|
};
|
|
102
151
|
}
|
|
103
152
|
export function managedAidShowViewForRole(role) {
|
|
@@ -120,6 +169,7 @@ const defaultDependencies = () => ({
|
|
|
120
169
|
aidLookup,
|
|
121
170
|
agentmdGet,
|
|
122
171
|
agentmdPut,
|
|
172
|
+
agentmdSet,
|
|
123
173
|
existsSync: fs.existsSync,
|
|
124
174
|
readFileSync: fs.readFileSync,
|
|
125
175
|
uploadAvatarBuffer,
|
|
@@ -153,8 +203,24 @@ export async function executeManagedAidOperation(command, options = {}) {
|
|
|
153
203
|
throw new ManagedAidOperationError('LOCAL_AGENTMD_MISSING', `local agent.md not found: ${command.aid}`);
|
|
154
204
|
}
|
|
155
205
|
const content = deps.readFileSync(localPath, 'utf-8');
|
|
156
|
-
|
|
157
|
-
|
|
206
|
+
let result;
|
|
207
|
+
try {
|
|
208
|
+
result = await deps.agentmdPut(content, { aid: command.aid, aunPath });
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
rethrowAgentmdError(error);
|
|
212
|
+
}
|
|
213
|
+
return { kind: command.kind, aid: command.aid, ...result };
|
|
214
|
+
}
|
|
215
|
+
case 'agentmd.set': {
|
|
216
|
+
let result;
|
|
217
|
+
try {
|
|
218
|
+
result = await deps.agentmdSet(command.aid, command.field, command.value, { aunPath });
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
rethrowAgentmdError(error);
|
|
222
|
+
}
|
|
223
|
+
return { kind: command.kind, aid: command.aid, ...result };
|
|
158
224
|
}
|
|
159
225
|
case 'avatar': {
|
|
160
226
|
const result = command.url
|
package/dist/aun/group/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { atomicReadJson, atomicWriteJson } from '../../utils/atomic-write.js';
|
|
3
3
|
import { agentGroupInviteReviewsPath } from '../../paths.js';
|
|
4
|
-
import { normalizeGroupAid } from '
|
|
5
|
-
import { isValidAid } from '
|
|
4
|
+
import { normalizeGroupAid } from './identity.js';
|
|
5
|
+
import { isValidAid } from '../aid/validation.js';
|
|
6
6
|
import { listStaticAgentOwners } from '../../config/peer-role-resolver.js';
|
|
7
7
|
import { read, ConfigTarget } from '../../config/config-manager.js';
|
|
8
8
|
import { resolveAgentLifecycle } from '../../config/lifecycle.js';
|
|
9
9
|
import { logger } from '../../utils/logger.js';
|
|
10
|
-
import { buildEnvelope, sendInteractionPayload, withEnvelopeReplyContext } from '
|
|
11
|
-
import { renderActionAsText } from '
|
|
10
|
+
import { buildEnvelope, sendInteractionPayload, withEnvelopeReplyContext } from '../../core/message/message-utils.js';
|
|
11
|
+
import { renderActionAsText } from '../../core/interaction-router.js';
|
|
12
12
|
export const OPEN_INVITE_STATES = ['pending', 'processing', 'reconciling'];
|
|
13
13
|
export function recordValue(value) {
|
|
14
14
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
package/dist/channels/daemon.js
CHANGED
|
@@ -225,7 +225,7 @@ export class DaemonChannel {
|
|
|
225
225
|
// 记忆整理会话:把会话原型与整理模式写进 metadata,供渲染层选 manifest / 手册,
|
|
226
226
|
// 供 ipc 侧校验写命令的 --scope。
|
|
227
227
|
const { memoryMode, memoryScope, consolidationBatchDir, consolidationChatDir, consolidationMemoryDir } = trigger.execution;
|
|
228
|
-
// 反馈目标与触发方式(
|
|
228
|
+
// 反馈目标与触发方式(consolidation.ts 在 normalize 后补挂的字段):
|
|
229
229
|
// 收尾时据此投递反馈,并判断是否该反馈(定时触发不反馈)。
|
|
230
230
|
const { feedbackOrigin, consolidationTriggeredBy } = trigger.execution;
|
|
231
231
|
return await this.sessionManager.getOrCreateSession(this.channelName, channelId, projectPath, threadId, {
|
|
@@ -689,18 +689,18 @@ Options:
|
|
|
689
689
|
return;
|
|
690
690
|
}
|
|
691
691
|
if (sub === 'welcome') {
|
|
692
|
-
if (args.length === 2 && isHelpFlag(args[1])) {
|
|
693
|
-
console.log(`用法: ${MAIN_CLI_BIN} agent welcome show [--format json]
|
|
694
|
-
${MAIN_CLI_BIN} agent welcome --text <text> [--format json]
|
|
695
|
-
|
|
696
|
-
读取或保存当前 Agent 在 Bootstrap 中经 Owner 确认的 welcome 文案。文案固定保存到 personal/welcome.md。`);
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
692
|
let text;
|
|
700
693
|
try {
|
|
701
694
|
({ text } = parseAgentWelcomeArgs(args.slice(1)));
|
|
702
695
|
}
|
|
703
696
|
catch (error) {
|
|
697
|
+
if (wantsHelp(args)) {
|
|
698
|
+
console.log(`用法: ${MAIN_CLI_BIN} agent welcome show [--format json]
|
|
699
|
+
${MAIN_CLI_BIN} agent welcome --text <text> [--format json]
|
|
700
|
+
|
|
701
|
+
读取或保存当前 Agent 在 Bootstrap 中经 Owner 确认的 welcome 文案。文案固定保存到 personal/welcome.md。`);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
704
|
const result = { ok: false, code: 'INVALID_ARGS', error: error.message };
|
|
705
705
|
if (formatJson)
|
|
706
706
|
console.log(JSON.stringify(result));
|