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.
Files changed (68) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/agents/codex-runner.js +61 -14
  3. package/dist/aun/aid/agentmd.js +224 -19
  4. package/dist/aun/aid/index.js +2 -1
  5. package/dist/aun/aid/managed-operation.js +70 -4
  6. package/dist/aun/group/index.js +1 -0
  7. package/dist/{core → aun}/group/invite-service.js +4 -4
  8. package/dist/channels/daemon.js +1 -1
  9. package/dist/cli/agent-command.js +7 -7
  10. package/dist/cli/aun-commands.js +97 -37
  11. package/dist/cli/data-command.js +2 -1
  12. package/dist/cli/managed-command-guard.js +2 -2
  13. package/dist/cli/model.js +1 -1
  14. package/dist/cli/response.js +1 -1
  15. package/dist/cli/trigger-command.js +52 -29
  16. package/dist/config/boot-log.js +95 -1
  17. package/dist/config/builtin-roles.js +1 -0
  18. package/dist/config/peer-role-resolver.js +21 -0
  19. package/dist/core/agent-ipc-registration.js +409 -0
  20. package/dist/core/agent-runtime.js +435 -0
  21. package/dist/core/auth/delegation-authorization.js +58 -0
  22. package/dist/core/auth/operation-catalog.js +8 -0
  23. package/dist/core/bootstrap-service.js +65 -1
  24. package/dist/core/channel-runtime.js +435 -0
  25. package/dist/core/command/command-handler.js +4 -1
  26. package/dist/core/daemon-bootstrap.js +151 -0
  27. package/dist/core/daemon-control.js +205 -0
  28. package/dist/core/daemon-lifecycle.js +225 -0
  29. package/dist/core/data-migration.js +4 -1
  30. package/dist/core/handoff/ipc-registration.js +156 -0
  31. package/dist/core/ipc-registration.js +457 -0
  32. package/dist/core/memory/consolidation-trigger.js +8 -0
  33. package/dist/core/memory/consolidation.js +838 -0
  34. package/dist/core/message/aun-ipc-sender.js +226 -0
  35. package/dist/core/message/outbound.js +165 -0
  36. package/dist/core/permission/approval-gateway.js +13 -0
  37. package/dist/core/permission/ec-command-parser.js +80 -35
  38. package/dist/core/permission/sandbox-runtime.js +23 -2
  39. package/dist/core/permission/tool-error-code.js +1 -0
  40. package/dist/core/permission/tool-policy.js +61 -24
  41. package/dist/core/session/session-idle-monitor.js +17 -0
  42. package/dist/index.js +352 -4841
  43. package/dist/ipc.js +2 -1
  44. package/dist/stats/db.js +24 -0
  45. package/dist/stats/index.js +1 -1
  46. package/dist/stats/writer.js +25 -1
  47. package/dist/trigger/application-service.js +157 -0
  48. package/dist/trigger/authorization.js +371 -0
  49. package/dist/trigger/ipc-service.js +406 -0
  50. package/dist/trigger/local-files.js +133 -0
  51. package/dist/trigger/manager.js +235 -22
  52. package/dist/trigger/scheduler.js +30 -5
  53. package/dist/trigger/script-executor.js +176 -39
  54. package/dist/trigger/script-sandbox.js +458 -0
  55. package/dist/trigger/system-definitions.js +188 -0
  56. package/dist/trigger/validation.js +0 -3
  57. package/dist/utils/aid-bind.js +1 -1
  58. package/dist/utils/evolcore-version.js +74 -0
  59. package/dist/utils/logger.js +16 -0
  60. package/kits/docs/evolcore/INDEX.md +1 -1
  61. package/kits/docs/evolcore/aid.md +9 -1
  62. package/kits/docs/evolcore/model.md +3 -2
  63. package/kits/docs/evolcore/rpc.md +4 -0
  64. package/kits/docs/evolcore/trigger.md +15 -1
  65. package/kits/schemas/relation-config.schema.9.json +1 -1
  66. package/kits/templates/system-fragments/commands.md +0 -1
  67. package/package.json +1 -1
  68. 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 appServer.threadCompactStart(threadId);
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 promise = new Promise(resolve => {
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
- pollTimer = setInterval(() => {
2464
- if (this.hasPersistedCompactCompletion(threadId, startedAtMs)) {
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
- }, 1000);
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
- hasPersistedCompactCompletion(threadId, startedAtMs) {
2500
+ readPersistedCompactCompletion(threadId, startedAtMs) {
2481
2501
  const sessionFile = this.findCodexSessionFile(threadId);
2482
2502
  if (!sessionFile)
2483
- return false;
2503
+ return undefined;
2484
2504
  let text = '';
2485
2505
  try {
2486
2506
  text = fs.readFileSync(sessionFile, 'utf8');
2487
2507
  }
2488
2508
  catch {
2489
- return false;
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 ts = typeof entry.timestamp === 'string' ? Date.parse(entry.timestamp) : NaN;
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 = entry.payload?.type;
2508
- if (entry.type === 'compacted' || payloadType === 'context_compacted')
2509
- return true;
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 false;
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: 'POLICY_DENIED',
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
  : {}),
@@ -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
- return { content, verification: verification ?? { status: 'unsigned' } };
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
- * Upload agent.md: write local file → store.uploadAgentMd (auto-auth + sign + upload).
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
- export async function agentmdPut(content, opts) {
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 existed = fs.existsSync(filePath);
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
- catch (e) {
456
- // 上传失败:仅当文件原本不存在(本次新建)时回滚,避免留下孤儿文件;
457
- // 已存在的文件保留新内容(旧语义)。
458
- if (!existed)
459
- try {
460
- fs.unlinkSync(filePath);
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.
@@ -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
- await deps.agentmdPut(content, { aid: command.aid, aunPath });
157
- return { kind: command.kind, aid: command.aid, path: localPath };
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
@@ -1,4 +1,5 @@
1
1
  export * from './operations.js';
2
+ export * from './invite-service.js';
2
3
  export * from './history.js';
3
4
  export * from './identity.js';
4
5
  export * from './index-rpc.js';
@@ -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 '../../aun/group/identity.js';
5
- import { isValidAid } from '../../aun/aid/validation.js';
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 '../message/message-utils.js';
11
- import { renderActionAsText } from '../interaction-router.js';
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);
@@ -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
- // 反馈目标与触发方式(launch-consolidation 在 normalize 后补挂的字段):
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));