evolcore 0.0.31 → 0.0.32

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 (56) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/agents/codex-runner.js +28 -5
  3. package/dist/agents/ecagent-runner.js +4 -0
  4. package/dist/agents/gemini-runner.js +4 -0
  5. package/dist/aun/aid/store.js +0 -81
  6. package/dist/aun/group/fs-download.js +1 -10
  7. package/dist/aun/msg/managed-operation.js +130 -32
  8. package/dist/aun/service-proxy.js +12 -18
  9. package/dist/aun/storage/index.js +29 -0
  10. package/dist/channels/aun.js +10 -135
  11. package/dist/cli/agent-command.js +6 -7
  12. package/dist/cli/agent-create.js +43 -24
  13. package/dist/cli/agent.js +15 -69
  14. package/dist/cli/aun-commands.js +149 -5
  15. package/dist/cli/ctl-command.js +1 -2
  16. package/dist/cli/daemon-commands.js +1 -1
  17. package/dist/cli/diag.js +2 -2
  18. package/dist/cli/handoff-command.js +15 -5
  19. package/dist/cli/memory-command.js +85 -11
  20. package/dist/cli/model.js +14 -138
  21. package/dist/cli/queue-command.js +46 -0
  22. package/dist/cli/response.js +5 -54
  23. package/dist/cli/skill-command.js +9 -3
  24. package/dist/cli/stats.js +10 -4
  25. package/dist/cli/task-command.js +1 -0
  26. package/dist/cli/task-context.js +15 -0
  27. package/dist/cli/trigger-command.js +73 -49
  28. package/dist/config/builtin-roles.js +8 -7
  29. package/dist/core/agent-ipc-registration.js +65 -22
  30. package/dist/core/auth/agent-delegation.js +17 -1
  31. package/dist/core/auth/delegation-authorization.js +1 -0
  32. package/dist/core/auth/operation-catalog.js +11 -3
  33. package/dist/core/command/command-handler.js +164 -21
  34. package/dist/core/command/diag-service.js +5 -1
  35. package/dist/core/handoff/ipc-registration.js +38 -0
  36. package/dist/core/ipc-registration.js +71 -7
  37. package/dist/core/message/aun-ipc-sender.js +48 -4
  38. package/dist/core/message/message-queue.js +131 -3
  39. package/dist/core/message/response-engine.js +30 -2
  40. package/dist/core/permission/sandbox-runtime.js +14 -1
  41. package/dist/core/task/task-recovery-service.js +2 -0
  42. package/dist/core/task/task-resume-operation.js +11 -0
  43. package/dist/index.js +2 -2
  44. package/dist/ipc.js +146 -8
  45. package/dist/trigger/authorization.js +2 -1
  46. package/dist/trigger/ipc-service.js +49 -35
  47. package/dist/trigger/script-sandbox.js +58 -77
  48. package/kits/docs/AUTHORING.md +1 -1
  49. package/kits/docs/channels/aun.md +8 -0
  50. package/kits/docs/evolcore/INDEX.md +2 -2
  51. package/kits/docs/evolcore/task.md +2 -0
  52. package/kits/docs/evolcore/trigger.md +2 -2
  53. package/kits/rules/01-overview.md +0 -9
  54. package/kits/rules/06-channel.md +7 -1
  55. package/kits/templates/system-fragments/commands.md +1 -1
  56. package/package.json +1 -1
@@ -5,7 +5,6 @@ import { performance } from 'node:perf_hooks';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import os from 'os';
8
- import { fileURLToPath, pathToFileURL } from 'url';
9
8
  import { logger, localTimestamp } from '../utils/logger.js';
10
9
  import { LogWriter } from '../utils/log-writer.js';
11
10
  import { middleOutputModePolicy, resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
@@ -19,7 +18,7 @@ import { chatDirPath } from '../core/session/session-fs-store.js';
19
18
  import { readTopicLabel, readTopicStatus } from '../core/session/session-mapper.js';
20
19
  import { appendHintAddDetailed, appendHintRemoveDetailed, appendHintPromoteDetailed, parseInjectRequest, peekHints, withHintTargetLock } from '../core/message/pending-hints.js';
21
20
  import { appendAidLifecycle } from '../aun/aid/identity.js';
22
- import { enableFullGroupPullPagination, enableReliableMessageDelivery, getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
21
+ import { getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
23
22
  import { MAX_AUN_ATTACHMENT_SIZE, uploadBufferAndBuildPayload, uploadFileAndBuildPayload } from '../aun/msg/upload.js';
24
23
  import { loadAgent } from '../config-store.js';
25
24
  import { resolveAgentLifecycle } from '../config/lifecycle.js';
@@ -653,7 +652,6 @@ export class AUNChannel {
653
652
  return this.managedMenuClient.request(options);
654
653
  }
655
654
  client = null;
656
- localSenderInstallation;
657
655
  store = null;
658
656
  /** 实际连接的网关 URL(来自 authenticate() 返回值 / connection.state 事件),替代旧 (client as any)._gatewayUrl。 */
659
657
  gatewayUrl = '';
@@ -1343,7 +1341,7 @@ export class AUNChannel {
1343
1341
  // boundary while the command handler uses /mention internally.
1344
1342
  return stripped.replace(/^\/mentionmode(?=\s|$)/, '/mention');
1345
1343
  }
1346
- parsePayloadMentionsOrReject(payload, context, messageId, seq) {
1344
+ parsePayloadMentionsOrReject(payload, context, messageId) {
1347
1345
  const raw = payload && typeof payload === 'object' && !Array.isArray(payload)
1348
1346
  ? payload.mentions
1349
1347
  : undefined;
@@ -1351,7 +1349,6 @@ export class AUNChannel {
1351
1349
  return normalizeAunMentionEntries(raw);
1352
1350
  }
1353
1351
  catch (error) {
1354
- this.acknowledgeImmediately(messageId, seq);
1355
1352
  const detail = error instanceof Error ? error.message : String(error);
1356
1353
  const code = error?.code ?? 'INVALID_MENTION_SCHEMA';
1357
1354
  logger.error(`${this.logPrefix()} Dropped ${context}: invalid payload.mentions (${detail}) code=${code} mid=${messageId}`);
@@ -1452,12 +1449,9 @@ export class AUNChannel {
1452
1449
  else {
1453
1450
  logger.debug(`${this.logPrefix()} Duplicate inbound message dropped: namespace=${namespace} mid=${messageId} seq=${seq ?? 'unknown'}`);
1454
1451
  }
1455
- this.acknowledgeImmediately(messageId, seq);
1456
1452
  return false;
1457
1453
  }
1458
1454
  this.inboundSeenMessages.set(dedupeKey, { seenAt: Date.now(), seq });
1459
- if (seq !== undefined)
1460
- this.messageSeqMap.set(messageId, seq);
1461
1455
  this.appendInboundDedupe(dedupeKey, messageId, kind, seq);
1462
1456
  setTimeout(() => this.inboundSeenMessages.delete(dedupeKey), AUN_INBOUND_DEDUP_TTL_MS);
1463
1457
  return true;
@@ -1524,12 +1518,6 @@ export class AUNChannel {
1524
1518
  logger.warn(`${this.logPrefix()} Failed to compact inbound dedup: ${error instanceof Error ? error.message : String(error)}`);
1525
1519
  }
1526
1520
  }
1527
- acknowledgeImmediately(messageId, _seq) {
1528
- // SDK internally manages seq tracking and ack — do not call message.ack RPC directly,
1529
- // as it corrupts the SDK's seqTracker state and breaks V2 e2ee message pull.
1530
- if (messageId)
1531
- this.messageSeqMap.delete(messageId);
1532
- }
1533
1521
  shouldEncrypt(_peerId) {
1534
1522
  return this.config.defaultEncrypt ?? true;
1535
1523
  }
@@ -1543,7 +1531,7 @@ export class AUNChannel {
1543
1531
  /**
1544
1532
  * Inbound application dedupe. The AUN SDK may redeliver the same message
1545
1533
  * with a different transport sequence, so message_id is the identity and
1546
- * seq is only retained for ACK/conflict diagnostics. Claiming happens at
1534
+ * seq is only retained for ordering and conflict diagnostics. Claiming happens at
1547
1535
  * the event handler boundary, before payload/slash/mention parsing.
1548
1536
  */
1549
1537
  inboundSeenMessages = new Map();
@@ -1551,7 +1539,6 @@ export class AUNChannel {
1551
1539
  groupNameCache = new Map(); // groupId → 群显示名(进程内缓存,群名极少变)
1552
1540
  peerInfoCache = new Map();
1553
1541
  peerInfoRevision = 0;
1554
- messageSeqMap = new Map(); // messageId → seq (for ack/diagnostics)
1555
1542
  sentCount = new Map(); // "channelId\0threadId" → 本轮已发消息计数(seqInTurn/turnMessageCount 用)
1556
1543
  peerE2ee = new Map();
1557
1544
  static E2EE_PROBE_TTL = 10 * 60 * 1000; // 10min
@@ -1737,27 +1724,6 @@ export class AUNChannel {
1737
1724
  this.initInProgress = false;
1738
1725
  }
1739
1726
  }
1740
- /** Optional local compatibility code is never required by a clean checkout or npm install. */
1741
- async loadLocalSenderInstallation(client) {
1742
- this.localSenderInstallation = undefined;
1743
- const currentFile = fileURLToPath(import.meta.url);
1744
- const modulePath = path.join(path.dirname(currentFile), '..', 'aun', `sender-installation${path.extname(currentFile)}`);
1745
- if (!fs.existsSync(modulePath))
1746
- return;
1747
- try {
1748
- // Use a runtime URL so TypeScript can build when the local file is absent.
1749
- const extension = await import(pathToFileURL(modulePath).href);
1750
- if (typeof extension.enableAunSenderInstallation !== 'function'
1751
- || typeof extension.aunSenderInstallation !== 'function') {
1752
- throw new Error('Invalid local sender installation module');
1753
- }
1754
- extension.enableAunSenderInstallation(client);
1755
- this.localSenderInstallation = extension;
1756
- }
1757
- catch (error) {
1758
- logger.warn(`${this.logPrefix()} Optional local sender installation disabled: ${error instanceof Error ? error.message : String(error)}`);
1759
- }
1760
- }
1761
1727
  async _initClientInner() {
1762
1728
  this.observerGroupMembers.clear();
1763
1729
  for (const groupId of new Set([
@@ -1805,9 +1771,6 @@ export class AUNChannel {
1805
1771
  });
1806
1772
  this.store = store;
1807
1773
  const client = await loadClient(store, aidName);
1808
- enableFullGroupPullPagination(client);
1809
- enableReliableMessageDelivery(client);
1810
- await this.loadLocalSenderInstallation(client);
1811
1774
  this.client = client;
1812
1775
  // A reconnect replaces the SDK client. Late events from the retired
1813
1776
  // instance must not mutate the new instance's health or message state.
@@ -1939,7 +1902,7 @@ export class AUNChannel {
1939
1902
  client.on('message.recalled', (data) => {
1940
1903
  if (!isCurrentClient())
1941
1904
  return;
1942
- this.handleMessageRecalled(data, client, isCurrentClient);
1905
+ this.handleMessageRecalled(data);
1943
1906
  });
1944
1907
  client.on('message.undecryptable', (data) => {
1945
1908
  if (!isCurrentClient())
@@ -2017,13 +1980,14 @@ export class AUNChannel {
2017
1980
  // max_attempts=0 = 无限重试(与 Go/Python 对齐),交由 SDK 自己跑指数退避
2018
1981
  // initial_delay=1s,max_delay=300s(5min 封顶)
2019
1982
  auto_reconnect: true,
1983
+ background_sync: true,
2020
1984
  retry_max_attempts: 0,
2021
1985
  retry_initial_delay: 1.0,
2022
1986
  retry_max_delay: 300.0,
2023
1987
  });
2024
1988
  this.trace('OUT', 'client.connect.ok', { aid: client.aid });
2025
1989
  this._aid = this.client.aid ?? undefined;
2026
- const deviceId = this.client._device_id ?? '';
1990
+ const deviceId = client.currentAid?.deviceId ?? '';
2027
1991
  this._chatId = this._aid ? `${this._aid}:${deviceId}:` : '';
2028
1992
  // 控制 AID 也有标准 host agent.md。发布在后台执行,短暂的 Name
2029
1993
  // Service/Gateway 故障不能把控制通道判定为连接失败。
@@ -2056,25 +2020,6 @@ export class AUNChannel {
2056
2020
  logger.warn(`${this.logPrefix()} Group invitation recovery failed: ${error instanceof Error ? error.message : String(error)}`);
2057
2021
  });
2058
2022
  }
2059
- // AUN seq_tracker is transport-scoped, while this channel's durable
2060
- // dedup log is application-scoped. A diagnostic consumer can advance
2061
- // the former without the application having seen those messages. On a
2062
- // reconnect, repair only the in-memory P2P cursor to the last message
2063
- // the application actually claimed, then force a Forward pull. The SDK
2064
- // persists cursors monotonically, so this does not rewrite SQLite and
2065
- // the normal pull will restore the high watermark after delivery.
2066
- this.scheduleInboundP2PCursorRepair(client, isCurrentClient);
2067
- // Workaround: SDK e2ee uses _identity.cert for sender_cert_fingerprint;
2068
- // if cert is missing, it falls back to public key SPKI fingerprint which
2069
- // causes peer cert lookup failures. Backfill from keystore if needed.
2070
- const clientAny = this.client;
2071
- if (clientAny._identity && !clientAny._identity.cert) {
2072
- const cert = clientAny._keystore?.loadCert?.(aidName);
2073
- if (cert) {
2074
- clientAny._identity.cert = cert;
2075
- logger.info(`${this.logPrefix()} Backfilled identity.cert from keystore for e2ee fingerprint`);
2076
- }
2077
- }
2078
2023
  logger.info(`${this.logPrefix()} Connected as ${this._aid}`);
2079
2024
  appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'connected', aid: this.config.aid, gateway: this.gatewayUrl });
2080
2025
  appendAidLifecycle({ ts: Date.now(), iso: new Date().toISOString(), event: 'connected', aid: this.config.aid, gateway: this.gatewayUrl });
@@ -2161,47 +2106,7 @@ export class AUNChannel {
2161
2106
  const aid = this.config.aid.replace(/^@/, '');
2162
2107
  return bindPostBootstrapWelcomeOutboxSession(aid, sessionId, owner);
2163
2108
  }
2164
- scheduleInboundP2PCursorRepair(client, isCurrentClient) {
2165
- const tracker = client?._seqTracker;
2166
- const repair = tracker?.repairContiguousSeq;
2167
- const pull = client?._pullV2;
2168
- if (typeof repair !== 'function' || typeof pull !== 'function')
2169
- return;
2170
- if (this.inboundP2PCursorRepairTimer)
2171
- clearTimeout(this.inboundP2PCursorRepairTimer);
2172
- const ns = `p2p:${this.config.aid}`;
2173
- let appSeq = 0;
2174
- for (const [key, value] of this.inboundSeenMessages) {
2175
- // Group sequence spaces are independent from the P2P space.
2176
- if (!key.includes(':aun:private'))
2177
- continue;
2178
- const seq = Number(value.seq);
2179
- if (Number.isSafeInteger(seq) && seq > appSeq)
2180
- appSeq = seq;
2181
- }
2182
- if (appSeq <= 0)
2183
- return;
2184
- this.inboundP2PCursorRepairTimer = setTimeout(() => {
2185
- this.inboundP2PCursorRepairTimer = null;
2186
- if (!isCurrentClient() || !this.connected)
2187
- return;
2188
- const transportSeq = Number(tracker.getContiguousSeq?.(ns) ?? 0);
2189
- if (!Number.isSafeInteger(transportSeq) || transportSeq <= appSeq + 1)
2190
- return;
2191
- try {
2192
- repair.call(tracker, ns, appSeq);
2193
- logger.warn(`${this.logPrefix()} Rewinding in-memory P2P cursor for application recovery: transport=${transportSeq}, app=${appSeq}`);
2194
- Promise.resolve(pull.call(client, appSeq, 50, { force: true }))
2195
- .then(() => logger.info(`${this.logPrefix()} Replayed P2P messages after cursor mismatch: after_seq=${appSeq}`))
2196
- .catch((error) => logger.warn(`${this.logPrefix()} P2P cursor recovery pull failed: ${error instanceof Error ? error.message : String(error)}`));
2197
- }
2198
- catch (error) {
2199
- logger.warn(`${this.logPrefix()} P2P cursor repair failed: ${error instanceof Error ? error.message : String(error)}`);
2200
- }
2201
- }, 250);
2202
- }
2203
- inboundP2PCursorRepairTimer = null;
2204
- handleMessageRecalled(data, client, isCurrentClient) {
2109
+ handleMessageRecalled(data) {
2205
2110
  this.trace('IN', 'message.recalled', data);
2206
2111
  if (data && typeof data === 'object') {
2207
2112
  const ids = data.message_ids;
@@ -2214,9 +2119,6 @@ export class AUNChannel {
2214
2119
  }
2215
2120
  }
2216
2121
  }
2217
- // A recall tombstone may arrive at a high transport sequence and advance
2218
- // the SDK cursor past messages that were never delivered to the app.
2219
- this.scheduleInboundP2PCursorRepair(client, isCurrentClient);
2220
2122
  }
2221
2123
  pendingPostBootstrapWelcomeOwners() {
2222
2124
  return pendingPostBootstrapWelcomeOwners(this.config.aid);
@@ -2525,7 +2427,6 @@ export class AUNChannel {
2525
2427
  const env = (msg.envelope && typeof msg.envelope === 'object') ? msg.envelope : {};
2526
2428
  const fromAid = typeof env.from === 'string' ? env.from.trim() : '';
2527
2429
  if (!messageId || !fromAid) {
2528
- this.acknowledgeImmediately(messageId, seq);
2529
2430
  const now = Date.now();
2530
2431
  if (now - this.invalidInboundEnvelopeLastLogAt >= 60_000) {
2531
2432
  this.invalidInboundEnvelopeLastLogAt = now;
@@ -2541,15 +2442,13 @@ export class AUNChannel {
2541
2442
  const protectedHeaders = verifiedProtectedHeaders(msg, env);
2542
2443
  const payload = msg.payload ?? '';
2543
2444
  if (this.managedMenuClient.accept(fromAid, payload, !!env.encrypted)) {
2544
- this.acknowledgeImmediately(messageId, seq);
2545
2445
  return;
2546
2446
  }
2547
2447
  if (payloadTypeForObserver(msg) === 'observer.forward') {
2548
- this.acknowledgeImmediately(messageId, seq);
2549
2448
  logger.debug(`${this.logPrefix()} P2P dropped observer.forward control envelope: mid=${messageId}`);
2550
2449
  return;
2551
2450
  }
2552
- const mentions = this.parsePayloadMentionsOrReject(payload, 'p2p.inbound', messageId, seq);
2451
+ const mentions = this.parsePayloadMentionsOrReject(payload, 'p2p.inbound', messageId);
2553
2452
  if (!mentions)
2554
2453
  return;
2555
2454
  const causationRef = payload && typeof payload === 'object' && typeof payload._evolcore_causation_ref === 'string'
@@ -2570,7 +2469,6 @@ export class AUNChannel {
2570
2469
  const inboundType = (payload && typeof payload === 'object') ? payload.type : undefined;
2571
2470
  // 终态错误只作为展示信号,不能进入 Agent,也不能被 observer 再次传播。
2572
2471
  if (inboundType === 'error') {
2573
- this.acknowledgeImmediately(messageId, seq);
2574
2472
  logger.info(`${this.logPrefix()} P2P dropped terminal error: from=${fromAid || 'unknown'} mid=${messageId}`);
2575
2473
  return;
2576
2474
  }
@@ -2582,14 +2480,12 @@ export class AUNChannel {
2582
2480
  void this.forwardInbound(msg);
2583
2481
  }
2584
2482
  if (inboundType === 'observer.inject.status' || inboundType === 'observer.inject.ack') {
2585
- this.acknowledgeImmediately(messageId, seq);
2586
2483
  return;
2587
2484
  }
2588
2485
  // 回声过滤:自己发出的消息会被 gateway fanout 回来,
2589
2486
  // 只有 from_aid == self 且 chat_id 不匹配时才丢弃(说明是其它实例发的)
2590
2487
  const msgChatId = typeof payload === 'object' && payload !== null && payload.chat_id;
2591
2488
  if (this._aid && fromAid === this._aid && (!msgChatId || !this._chatId || msgChatId !== this._chatId)) {
2592
- this.acknowledgeImmediately(messageId, seq);
2593
2489
  logger.debug(`${this.logPrefix()} P2P dropped: echo from self (from=${fromAid} mid=${messageId})`);
2594
2490
  return;
2595
2491
  }
@@ -2632,18 +2528,14 @@ export class AUNChannel {
2632
2528
  if (p2pPayloadType.startsWith('menu.')
2633
2529
  || p2pPayloadType === AUNChannel.CONTACT_REQUEST_QUERY_TYPE
2634
2530
  || p2pPayloadType === AUNChannel.CONTACT_REQUEST_SUBMIT_TYPE) {
2635
- const installation = p2pPayloadType.startsWith('menu.')
2636
- ? this.localSenderInstallation?.aunSenderInstallation(msg) : undefined;
2637
2531
  const peerDeviceId = p2pPayloadType.startsWith('menu.')
2638
2532
  ? menuSenderDeviceId(msg.sender_device_id) : undefined;
2639
- this.acknowledgeImmediately(messageId, seq);
2640
2533
  this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
2641
2534
  this.dispatchMessage({
2642
2535
  channelId: chatId, userId: fromAid,
2643
2536
  text: JSON.stringify(payload),
2644
2537
  chatType: 'private', messageId, seq, receivedAt, receivedAtMono, gatewaySeq: seq,
2645
2538
  peerDeviceId,
2646
- peerSlotId: installation?.slotId,
2647
2539
  threadId,
2648
2540
  peerName: displayName || undefined,
2649
2541
  peerType: peerIdentity.type,
@@ -2669,7 +2561,6 @@ export class AUNChannel {
2669
2561
  // observer.inject:owner 插话。鉴权 from∈owners 后,以 target.channel_id 选 agent↔对端 会话,
2670
2562
  // observer 插话(v0.3):只落盘到 pending-hints,不进 Agent、不回 owner。详见 docs/observer-insert-design.md。
2671
2563
  if (p2pPayloadType === AUNChannel.INJECT_REQUEST_TYPE) {
2672
- this.acknowledgeImmediately(messageId, seq);
2673
2564
  void Promise.resolve(this.handleObserverInject(fromAid, payload, displayName, peerIdentity.type)).catch(error => {
2674
2565
  logger.error(`${this.logPrefix()} observer.inject failed: ${error instanceof Error ? error.message : String(error)}`);
2675
2566
  });
@@ -2679,7 +2570,6 @@ export class AUNChannel {
2679
2570
  // 由控制 AID 的 onMessage 处理器(BindService.handleRequest)解析并回 bind.response。
2680
2571
  // 透传 encrypted:bind.response 须与请求的加密/明文对称(见 onMessage 处理)。
2681
2572
  if (p2pPayloadType === AUNChannel.BIND_REQUEST_TYPE) {
2682
- this.acknowledgeImmediately(messageId, seq);
2683
2573
  this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
2684
2574
  this.dispatchMessage({
2685
2575
  channelId: chatId, userId: fromAid,
@@ -2700,7 +2590,6 @@ export class AUNChannel {
2700
2590
  }
2701
2591
  // payload 类型白名单:信号类消息(status / event / thought 等)不进 Agent
2702
2592
  if (p2pPayloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(p2pPayloadType)) {
2703
- this.acknowledgeImmediately(messageId, seq);
2704
2593
  this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
2705
2594
  logger.info(`${this.logPrefix()} P2P dropped (type deny): type=${p2pPayloadType} from=${shortAid}(${displayName}) mid=${messageId}`);
2706
2595
  return;
@@ -2758,11 +2647,10 @@ export class AUNChannel {
2758
2647
  const payload = msg.payload ?? '';
2759
2648
  const inboundType = (payload && typeof payload === 'object') ? payload.type : undefined;
2760
2649
  if (inboundType === 'observer.forward' || (typeof inboundType === 'string' && inboundType.startsWith('observer.inject'))) {
2761
- this.acknowledgeImmediately(messageId, seq);
2762
2650
  logger.debug(`${this.logPrefix()} Group dropped observer control envelope: mid=${messageId}`);
2763
2651
  return;
2764
2652
  }
2765
- const payloadMentionEntries = this.parsePayloadMentionsOrReject(payload, 'group.inbound', messageId, seq);
2653
+ const payloadMentionEntries = this.parsePayloadMentionsOrReject(payload, 'group.inbound', messageId);
2766
2654
  if (!payloadMentionEntries)
2767
2655
  return;
2768
2656
  const payloadMentionAids = mentionEntryAids(payloadMentionEntries);
@@ -2774,7 +2662,6 @@ export class AUNChannel {
2774
2662
  : undefined;
2775
2663
  // 终态错误只作为展示信号,不能进入 Agent,也不能被 observer 再次传播。
2776
2664
  if (inboundType === 'error') {
2777
- this.acknowledgeImmediately(messageId, seq);
2778
2665
  logger.info(`${this.logPrefix()} Group dropped terminal error: group=${groupId || 'unknown'} sender=${senderAid || 'unknown'} mid=${messageId}`);
2779
2666
  return;
2780
2667
  }
@@ -2783,7 +2670,6 @@ export class AUNChannel {
2783
2670
  void this.forwardInbound(msg);
2784
2671
  logger.debug(`${this.logPrefix()}[DIAG-GRP] full_msg=${JSON.stringify(msg).substring(0, 500)}`);
2785
2672
  if (!groupId || !senderAid) {
2786
- this.acknowledgeImmediately(messageId, seq);
2787
2673
  logger.debug(`${this.logPrefix()} Group dropped: missing groupId or senderAid (mid=${messageId})`);
2788
2674
  return;
2789
2675
  }
@@ -2795,7 +2681,6 @@ export class AUNChannel {
2795
2681
  && typeof handoffMarker === 'string'
2796
2682
  && /^h-[A-Za-z0-9._-]+$/.test(handoffMarker);
2797
2683
  if (this._aid && senderAid === this._aid && !isOwnHandoff) {
2798
- this.acknowledgeImmediately(messageId, seq);
2799
2684
  logger.debug(`${this.logPrefix()} Group dropped: own message (group=${groupId} mid=${messageId})`);
2800
2685
  return;
2801
2686
  }
@@ -2804,7 +2689,6 @@ export class AUNChannel {
2804
2689
  const firstLineFast = text.split('\n')[0] || '';
2805
2690
  const hasEvolCoreTrace = /\[EvolCore\.(receive|reply|agent)\]/.test(text);
2806
2691
  if (/echo/i.test(firstLineFast) && firstLineFast.trim().length <= 10 && !hasEvolCoreTrace) {
2807
- this.acknowledgeImmediately(messageId, seq);
2808
2692
  const msgEncryptedFast = !!env.encrypted;
2809
2693
  const msgChatmodeFast = (payload && typeof payload === 'object') ? payload.chatmode : undefined;
2810
2694
  const peerInfo = this.peerInfoCached(senderAid);
@@ -2843,12 +2727,10 @@ export class AUNChannel {
2843
2727
  const payloadType = typeof payloadObj?.type === 'string' ? payloadObj.type : '';
2844
2728
  // Menu control requests are private-only, including token issuance.
2845
2729
  if (payloadType.startsWith('menu.')) {
2846
- this.acknowledgeImmediately(messageId, seq);
2847
2730
  logger.debug(`${this.logPrefix()} Group Menu dropped: code=MENU_PRIVATE_ONLY mid=${messageId}`);
2848
2731
  return;
2849
2732
  }
2850
2733
  if (payloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(payloadType)) {
2851
- this.acknowledgeImmediately(messageId, seq);
2852
2734
  this.appendInboundPayloadJsonl(groupId, senderAid, messageId, !!env.encrypted, true, logDescriptor, threadId, receivedAt, seq);
2853
2735
  logger.info(`${this.logPrefix()} Group dropped (type deny): type=${payloadType} group=${groupId} sender=${senderAid} mid=${messageId}`);
2854
2736
  return;
@@ -2906,7 +2788,6 @@ export class AUNChannel {
2906
2788
  }
2907
2789
  else if (/echo/i.test(firstLineGroup) && hasEvolCoreTraceGroup) {
2908
2790
  // 回声炸弹:已被任何 EvolCore 节点 trace 过的 echo,直接丢弃
2909
- this.acknowledgeImmediately(messageId, seq);
2910
2791
  logger.info(`${this.logPrefix()} Group dropped: echo bomb (already-traced group=${groupId} sender=${senderAid} mid=${messageId})`);
2911
2792
  return;
2912
2793
  }
@@ -2934,7 +2815,6 @@ export class AUNChannel {
2934
2815
  logger.warn(`${this.logPrefix()} Deferred group message was not recorded; awaiting redelivery: ${error}`);
2935
2816
  return;
2936
2817
  }
2937
- this.acknowledgeImmediately(messageId, seq);
2938
2818
  const dropReason = commandTargetsAnotherAgent ? 'command-targeted-other-agent' : 'unmentioned';
2939
2819
  logger.info(`${this.logPrefix()} Group dropped: ${dropReason} (group=${groupId} sender=${senderAid} mid=${messageId} mentionMode=${mentionMode} slashRequireMentions=${groupSlashRequireMentions} isCommand=${isCommandMsg} commandMentionAids=${JSON.stringify(commandMentionAids)} textPreview=${JSON.stringify(text.slice(0, 80))})`);
2940
2820
  return;
@@ -2950,7 +2830,6 @@ export class AUNChannel {
2950
2830
  const hasAttachments = rawAttachments.length > 0;
2951
2831
  // Allow through if there's text OR attachments; both-empty messages are silently dropped
2952
2832
  if (!strippedText && !hasAttachments) {
2953
- this.acknowledgeImmediately(messageId, seq);
2954
2833
  logger.debug(`${this.logPrefix()} Group dropped: empty text and no attachments (group=${groupId} sender=${senderAid} mid=${messageId})`);
2955
2834
  return;
2956
2835
  }
@@ -6022,11 +5901,7 @@ export class AUNChannel {
6022
5901
  logger.warn(`${this.logPrefix()} Repaired bootstrap outbox route to private Owner delivery: entry=${entry.id} owner=${owner}`);
6023
5902
  }
6024
5903
  }
6025
- acknowledge(messageId) {
6026
- // Gateway auto-delivery-ack is sufficient; skip explicit message.ack RPC
6027
- // to avoid duplicate "已送达" at the sender CLI
6028
- this.messageSeqMap.delete(messageId);
6029
- }
5904
+ acknowledge(_messageId) { }
6030
5905
  sendProcessingStatus(channelId, status, sessionId, taskId, context, extraMeta) {
6031
5906
  let delivery;
6032
5907
  try {
@@ -148,8 +148,7 @@ Options:
148
148
  process.exitCode = 1;
149
149
  return;
150
150
  }
151
- const managedArgs = commands.has(sub) ? args : ['show', ...args];
152
- const result = await runManagedAgentOperation(managedArgs);
151
+ const result = await runManagedAgentOperation(args);
153
152
  if (!result.ok) {
154
153
  if (formatJson)
155
154
  console.log(JSON.stringify(result));
@@ -671,7 +670,7 @@ Options:
671
670
  process.exitCode = 1;
672
671
  return;
673
672
  }
674
- const result = await agentPersona(aid, { text, file });
673
+ const result = await agentPersona(aid, { text, file, argv: args });
675
674
  if (!result.ok) {
676
675
  if (formatJson)
677
676
  console.log(JSON.stringify(result));
@@ -720,7 +719,7 @@ Options:
720
719
  process.exitCode = 1;
721
720
  return;
722
721
  }
723
- const result = await agentWelcome(aid, { text });
722
+ const result = await agentWelcome(aid, { text, argv: args });
724
723
  if (!result.ok) {
725
724
  if (formatJson)
726
725
  console.log(JSON.stringify(result));
@@ -756,7 +755,7 @@ Options:
756
755
  console.error('用法: ec agent get <aid> <key>');
757
756
  process.exit(1);
758
757
  }
759
- const result = await agentGet(aid, key);
758
+ const result = await agentGet(aid, key, { argv: args });
760
759
  if (!result.ok) {
761
760
  if (formatJson) {
762
761
  console.log(JSON.stringify(result));
@@ -797,7 +796,7 @@ Options:
797
796
  console.error('用法: ec agent set <aid> <key> <value>');
798
797
  process.exit(1);
799
798
  }
800
- const result = await agentSet(aid, key, val);
799
+ const result = await agentSet(aid, key, val, { argv: args });
801
800
  if (!result.ok) {
802
801
  if (formatJson) {
803
802
  console.log(JSON.stringify(result));
@@ -828,7 +827,7 @@ Options:
828
827
  console.error('用法: ec agent ready <aid>');
829
828
  process.exit(1);
830
829
  }
831
- const result = await agentReady(aid);
830
+ const result = await agentReady(aid, args);
832
831
  if (!result.ok) {
833
832
  if (formatJson) {
834
833
  console.log(JSON.stringify(result));
@@ -20,6 +20,7 @@ import { atomicWriteJson } from '../utils/atomic-write.js';
20
20
  import { isHClassPath, isLClassPath, isSameOrDescendant } from '../core/permission/protected-paths.js';
21
21
  import { MAIN_CLI_BIN } from '../product.js';
22
22
  import { uploadAvatarFile, uploadAvatarUrl } from '../utils/avatar-upload.js';
23
+ import { readTaskRuntimeContextFromEnv } from './task-context.js';
23
24
  /** Parse the current Agent's Bootstrap welcome subcommand. */
24
25
  export function parseAgentWelcomeArgs(args) {
25
26
  const tail = args.at(-2) === '--format' && args.at(-1) === 'json' ? args.slice(0, -2) : args;
@@ -190,9 +191,9 @@ function projectSlug(project) {
190
191
  .replace(/-+$/g, '');
191
192
  return slug || 'agent';
192
193
  }
193
- export function resolveAgentCreatePlan(options) {
194
+ export function resolveAgentCreatePlan(options, projectCwd = process.cwd()) {
194
195
  const aid = options.aid.trim();
195
- const project = path.resolve(options.project || process.cwd());
196
+ const project = path.resolve(projectCwd, options.project || '.');
196
197
  const metadata = readProjectMetadata(project);
197
198
  const defaults = loadDefaults();
198
199
  const daemon = loadDaemonConfig();
@@ -940,6 +941,20 @@ export class AgentInitializationService {
940
941
  await this.running.get(aid);
941
942
  }
942
943
  async settle(aid) { await this.running.get(aid); }
944
+ async wait(aid, seconds) {
945
+ const work = this.running.get(aid);
946
+ if (work && seconds > 0) {
947
+ let timer;
948
+ try {
949
+ await Promise.race([work, new Promise(resolve => { timer = setTimeout(resolve, seconds * 1000); })]);
950
+ }
951
+ finally {
952
+ if (timer)
953
+ clearTimeout(timer);
954
+ }
955
+ }
956
+ return this.status(aid);
957
+ }
943
958
  launch(r) {
944
959
  const aid = r.plan.aid;
945
960
  const work = Promise.resolve().then(() => this.run(r)).catch(error => {
@@ -1167,33 +1182,37 @@ export function parseInitializationArgs(args) {
1167
1182
  throw new Error('--wait must be an integer from 0 to 300');
1168
1183
  return { aid: args[1], resume, dryRun: seen.has('--dry-run'), wait, values };
1169
1184
  }
1170
- export function resolveInitializationPlan(args) {
1185
+ export function resolveInitializationPlan(args, context) {
1171
1186
  const parsed = parseInitializationArgs(args);
1172
1187
  if (parsed.resume)
1173
1188
  throw new Error('A resume request cannot supply a new plan');
1174
1189
  const v = parsed.values;
1175
1190
  if (v['--avatar-file'] !== undefined && v['--avatar-url'] !== undefined)
1176
1191
  throw new Error('--avatar-file and --avatar-url are mutually exclusive');
1177
- if (v['--avatar-file']?.startsWith('attachment:') && !process.env.EVOLCORE_SESSION_ID) {
1192
+ if (v['--avatar-file']?.startsWith('attachment:') && !context && !process.env.EVOLCORE_SESSION_ID) {
1178
1193
  throw new Error('attachment:<id> avatar sources require a managed Agent task');
1179
1194
  }
1180
- if (v['--persona-file'] !== undefined)
1181
- throw new Error('CLI must submit persona file contents as text');
1195
+ const persona = v['--persona-file'] !== undefined
1196
+ ? readInitializationPersona(v['--persona-file'], !!context, context)
1197
+ : v['--persona-text'];
1182
1198
  const p = resolveAgentCreatePlan({
1183
1199
  aid: parsed.aid, project: v['--project'], baseagent: v['--baseagent'], ...parseAgentOwnerArgs(args),
1184
1200
  avatarFile: v['--avatar-file'], avatarUrl: v['--avatar-url'],
1185
1201
  name: v['--name'], description: v['--description'], tags: parseAgentTags(v['--tags']), type: v['--type'],
1186
- });
1202
+ }, context?.projectCwd);
1187
1203
  return {
1188
1204
  aid: p.aid, project: p.project, baseagent: p.baseagent ?? '', owners: p.owners ?? (p.owner ? [p.owner] : []),
1189
1205
  name: p.name, description: p.description, tags: p.tags, type: p.type,
1190
- ...(v['--persona-text'] !== undefined ? { persona: v['--persona-text'] } : {}),
1206
+ ...(persona !== undefined ? { persona } : {}),
1191
1207
  ...(v['--avatar-file'] !== undefined ? { avatarFile: v['--avatar-file'] } : {}),
1192
1208
  ...(v['--avatar-url'] !== undefined ? { avatarUrl: v['--avatar-url'] } : {}),
1193
1209
  };
1194
1210
  }
1195
- export function readInitializationPersona(file, managed) {
1196
- const resolved = path.resolve(file);
1211
+ export function readInitializationPersona(file, managed, context = {
1212
+ projectCwd: managed ? readTaskRuntimeContextFromEnv()?.projectPath ?? process.cwd() : process.cwd(),
1213
+ tempDir: process.env.TMPDIR,
1214
+ }) {
1215
+ const resolved = path.resolve(context.projectCwd, file);
1197
1216
  if (!/\.(md|txt)$/i.test(resolved))
1198
1217
  throw new Error('persona file must be .md or .txt');
1199
1218
  const stat = fs.lstatSync(resolved);
@@ -1201,8 +1220,8 @@ export function readInitializationPersona(file, managed) {
1201
1220
  throw new Error('persona must be a regular text file of at most 64 KiB');
1202
1221
  if (managed) {
1203
1222
  const real = fs.realpathSync(resolved);
1204
- const roots = [process.cwd(), process.env.TMPDIR].filter((r) => !!r && path.isAbsolute(r));
1205
- const allowed = roots.some(root => isSameOrDescendant(real, fs.realpathSync(root)));
1223
+ const roots = [context.projectCwd, context.tempDir].filter((root) => !!root && path.isAbsolute(root));
1224
+ const allowed = roots.some(root => fs.existsSync(root) && isSameOrDescendant(real, fs.realpathSync(root)));
1206
1225
  if (!allowed || isHClassPath(real) || isLClassPath(real))
1207
1226
  throw new Error('persona file must be inside the project or current TMPDIR');
1208
1227
  }
@@ -1216,27 +1235,24 @@ export async function runInitializationCommand(args) {
1216
1235
  const parsed = parseInitializationArgs(args);
1217
1236
  const managed = !!process.env.EVOLCORE_SESSION_ID;
1218
1237
  const argv = [...args];
1219
- if (parsed.values['--persona-file'] !== undefined) {
1238
+ if (parsed.values['--persona-file'] !== undefined && !managed) {
1239
+ const persona = readInitializationPersona(parsed.values['--persona-file'], managed);
1220
1240
  const at = argv.indexOf('--persona-file');
1221
- argv.splice(at, 2, '--persona-text', readInitializationPersona(parsed.values['--persona-file'], managed));
1241
+ argv.splice(at, 2, '--persona-text', persona);
1222
1242
  }
1223
- if (!parsed.resume) {
1243
+ if (!managed && !parsed.resume) {
1224
1244
  const at = argv.indexOf('--project');
1225
1245
  if (at < 0)
1226
1246
  argv.push('--project', process.cwd());
1227
1247
  else
1228
1248
  argv[at + 1] = path.resolve(argv[at + 1]);
1229
1249
  }
1250
+ if (managed) {
1251
+ const { runManagedAgentOperation } = await import('./agent.js');
1252
+ const result = await runManagedAgentOperation(argv, (parsed.wait + 10) * 1000);
1253
+ return initializationCommandResult(result);
1254
+ }
1230
1255
  const request = async (poll) => {
1231
- if (managed) {
1232
- const { runManagedAgentOperation } = await import('./agent.js');
1233
- const r = await runManagedAgentOperation(poll ? ['show', parsed.aid, '--format', 'json'] : argv);
1234
- if (!poll || !r.ok || !r.initialization)
1235
- return r;
1236
- const init = r.initialization;
1237
- return { ...init, ok: !['failed', 'cancelled'].includes(init.status), aid: parsed.aid,
1238
- accepted: true, lifecycle: r.lifecycle, usable: init.complete && r.connection?.status === 'connected' };
1239
- }
1240
1256
  return await ipcQuery(resolvePaths().socket, {
1241
1257
  type: 'evolagent.initialize', argv, poll,
1242
1258
  }, 10_000) ?? { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'Initialization requires a running daemon' };
@@ -1249,6 +1265,9 @@ export async function runInitializationCommand(args) {
1249
1265
  await new Promise(resolve => setTimeout(resolve, 500));
1250
1266
  result = await request(true);
1251
1267
  }
1268
+ return initializationCommandResult(result);
1269
+ }
1270
+ function initializationCommandResult(result) {
1252
1271
  if (result.ok && !result.complete && result.accepted)
1253
1272
  return { ...result, ok: false, code: 'INITIALIZE_PENDING', error: 'Initialization is still running; inspect agent show' };
1254
1273
  if (result.complete && !result.usable)