evolcore 0.0.31 → 0.0.33

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 (179) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/bin/install-codex-managed-hooks.mjs +124 -37
  3. package/dist/agents/claude-runner.js +29 -2
  4. package/dist/agents/codex/app-server-client.js +3 -0
  5. package/dist/agents/codex/diagnostics.js +76 -44
  6. package/dist/agents/codex-runner.js +51 -24
  7. package/dist/agents/ecagent-runner.js +9 -13
  8. package/dist/agents/gemini-runner.js +4 -0
  9. package/dist/aun/aid/store.js +0 -81
  10. package/dist/aun/group/fs-download.js +1 -10
  11. package/dist/aun/group/operations.js +0 -1
  12. package/dist/aun/msg/history.js +1 -0
  13. package/dist/aun/msg/managed-operation.js +130 -32
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/aun/service-proxy.js +12 -18
  17. package/dist/aun/storage/index.js +29 -0
  18. package/dist/channels/aun.js +38 -137
  19. package/dist/channels/daemon.js +257 -2
  20. package/dist/cli/agent-command.js +64 -31
  21. package/dist/cli/agent-create.js +43 -24
  22. package/dist/cli/agent.js +16 -70
  23. package/dist/cli/aun-commands.js +512 -32
  24. package/dist/cli/contact.js +8 -2
  25. package/dist/cli/ctl-command.js +1 -2
  26. package/dist/cli/daemon-commands.js +82 -13
  27. package/dist/cli/debug-mode.js +154 -0
  28. package/dist/cli/diag.js +2 -2
  29. package/dist/cli/handoff-command.js +18 -5
  30. package/dist/cli/index.js +81 -64
  31. package/dist/cli/init.js +2 -2
  32. package/dist/cli/managed-command-guard.js +5 -0
  33. package/dist/cli/memory-command.js +93 -12
  34. package/dist/cli/model.js +14 -138
  35. package/dist/cli/queue-command.js +51 -0
  36. package/dist/cli/response.js +5 -54
  37. package/dist/cli/session-command.js +194 -0
  38. package/dist/cli/skill-command.js +85 -3
  39. package/dist/cli/stats.js +10 -4
  40. package/dist/cli/task-command.js +1 -0
  41. package/dist/cli/task-context.js +16 -0
  42. package/dist/cli/thread-command.js +620 -0
  43. package/dist/cli/trigger-command.js +76 -50
  44. package/dist/config/builtin-roles.js +9 -7
  45. package/dist/config/config-field-policy.js +2 -3
  46. package/dist/config/config-manager.js +7 -2
  47. package/dist/config/resolved-config-op.js +3 -0
  48. package/dist/config-store.js +15 -0
  49. package/dist/core/agent-ipc-registration.js +65 -22
  50. package/dist/core/auth/agent-delegation.js +42 -2
  51. package/dist/core/auth/debug-context.js +139 -0
  52. package/dist/core/auth/delegation-authorization.js +1 -0
  53. package/dist/core/auth/operation-authorizer.js +24 -1
  54. package/dist/core/auth/operation-catalog.js +44 -3
  55. package/dist/core/background-session/session-cleanup.js +212 -0
  56. package/dist/core/background-session/types.js +10 -0
  57. package/dist/core/bootstrap-messages.js +1 -1
  58. package/dist/core/bootstrap-service.js +6 -3
  59. package/dist/core/command/command-handler.js +224 -45
  60. package/dist/core/command/diag-service.js +5 -1
  61. package/dist/core/command/memory-slash.js +6 -2
  62. package/dist/core/command/menu-catalog.js +48 -1
  63. package/dist/core/command/menu-handler.js +92 -16
  64. package/dist/core/command/menu-protocol.js +24 -1
  65. package/dist/core/command/skill-slash.js +322 -0
  66. package/dist/core/command/slash-gate.js +6 -4
  67. package/dist/core/command/slash-handler.js +193 -13
  68. package/dist/core/command/topic-slash.js +528 -0
  69. package/dist/core/evolagent-registry.js +20 -5
  70. package/dist/core/handoff/ipc-registration.js +38 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +210 -9
  74. package/dist/core/memory/consolidation-context.js +49 -16
  75. package/dist/core/memory/consolidation-ops-records.js +35 -0
  76. package/dist/core/memory/consolidation-queue-format.js +21 -0
  77. package/dist/core/memory/consolidation.js +174 -180
  78. package/dist/core/message/aun-ipc-sender.js +207 -8
  79. package/dist/core/message/im-renderer.js +149 -6
  80. package/dist/core/message/message-bridge.js +13 -3
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +131 -3
  83. package/dist/core/message/peer-mode.js +6 -1
  84. package/dist/core/message/response-engine.js +379 -69
  85. package/dist/core/message/subtask-tool-projection.js +140 -0
  86. package/dist/core/model/catalog-service.js +17 -12
  87. package/dist/core/permission/ec-command-parser.js +126 -7
  88. package/dist/core/permission/sandbox-runtime.js +14 -1
  89. package/dist/core/session/session-idle-monitor.js +249 -22
  90. package/dist/core/session/session-manager.js +54 -9
  91. package/dist/core/skill/command-parser.js +13 -0
  92. package/dist/core/skill/index-generator.js +4 -1
  93. package/dist/core/skill/startup.js +41 -0
  94. package/dist/core/skill/switch-resolver.js +55 -29
  95. package/dist/core/task/task-recovery-service.js +2 -0
  96. package/dist/core/task/task-resume-operation.js +11 -0
  97. package/dist/core/thread/command-parser.js +125 -0
  98. package/dist/core/thread/index.js +1 -0
  99. package/dist/core/thread/ipc-operation.js +95 -0
  100. package/dist/core/thread/thread-context-storage.js +131 -0
  101. package/dist/core/thread/thread-operation.js +258 -0
  102. package/dist/core/thread/thread-route.js +33 -0
  103. package/dist/core/thread/thread-service.js +777 -0
  104. package/dist/core/thread/types.js +1 -0
  105. package/dist/eck/message-renderer.js +35 -0
  106. package/dist/index.js +10 -7
  107. package/dist/ipc.js +234 -17
  108. package/dist/paths.js +23 -0
  109. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  110. package/dist/trigger/authorization.js +59 -29
  111. package/dist/trigger/feedback.js +7 -1
  112. package/dist/trigger/ipc-service.js +49 -35
  113. package/dist/trigger/parser.js +63 -1
  114. package/dist/trigger/script-sandbox.js +58 -77
  115. package/dist/trigger/validation.js +14 -0
  116. package/dist/utils/error-utils.js +22 -1
  117. package/kits/docs/AUTHORING.md +1 -1
  118. package/kits/docs/channels/aun.md +8 -0
  119. package/kits/docs/context-assembly.md +1 -0
  120. package/kits/docs/evolcore/INDEX.md +3 -2
  121. package/kits/docs/evolcore/agent-create.md +39 -28
  122. package/kits/docs/evolcore/agent.md +19 -4
  123. package/kits/docs/evolcore/ec-capabilities.md +4 -0
  124. package/kits/docs/evolcore/group-collaboration.md +3 -5
  125. package/kits/docs/evolcore/group.md +3 -4
  126. package/kits/docs/evolcore/memory-consolidation.md +1 -1
  127. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  128. package/kits/docs/evolcore/menu-protocol.md +7 -0
  129. package/kits/docs/evolcore/msg.md +5 -8
  130. package/kits/docs/evolcore/task.md +2 -0
  131. package/kits/docs/evolcore/thread-chain.md +267 -0
  132. package/kits/docs/evolcore/trigger.md +2 -2
  133. package/kits/docs/prompt-loading-architecture.md +1 -0
  134. package/kits/eck_manifest.json +61 -26
  135. package/kits/eck_message_manifest.json +169 -10
  136. package/kits/rules/01-overview.md +0 -9
  137. package/kits/rules/05-venue.md +1 -1
  138. package/kits/rules/06-channel.md +7 -1
  139. package/kits/rules/07-memory-and-thread.md +202 -0
  140. package/kits/schemas/relation-config.schema.9.json +18 -3
  141. package/kits/schemas/role-config.schema.1.json +6 -6
  142. package/kits/schemas/single-session.schema.1.json +6 -6
  143. package/kits/schemas/single-session.schema.2.json +6 -6
  144. package/kits/schemas/single-session.schema.3.json +6 -6
  145. package/kits/skills/.switches.json +12 -1
  146. package/kits/skills/ec-group/SKILL.md +234 -0
  147. package/kits/skills/ec-skill/SKILL.md +10 -1
  148. package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
  149. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  150. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  151. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  152. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  153. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  154. package/kits/skills/thread-ops/SKILL.md +492 -0
  155. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  156. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  157. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  158. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  159. package/kits/templates/bootstrap-welcome.md +7 -7
  160. package/kits/templates/memory-consolidation/forget.md +5 -0
  161. package/kits/templates/memory-consolidation/generalize.md +5 -0
  162. package/kits/templates/memory-consolidation/profile.md +5 -0
  163. package/kits/templates/memory-consolidation/promote.md +5 -0
  164. package/kits/templates/message-fragments/item.md +2 -1
  165. package/kits/templates/message-fragments/thread-context.md +6 -0
  166. package/kits/templates/message-fragments/trigger-session.md +2 -0
  167. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  168. package/kits/templates/roles/member.json +2 -0
  169. package/kits/templates/roles/visitor.json +2 -0
  170. package/kits/templates/system-fragments/bootstrap.md +66 -72
  171. package/kits/templates/system-fragments/channel.md +3 -6
  172. package/kits/templates/system-fragments/commands.md +2 -1
  173. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  174. package/kits/templates/system-fragments/reply.md +3 -2
  175. package/kits/templates/system-fragments/session.md +5 -14
  176. package/package.json +3 -1
  177. package/kits/docs/evolcore/group-fs.md +0 -17
  178. package/kits/docs/venues/aun-group.md +0 -45
  179. package/kits/docs/venues/group.md +0 -43
@@ -64,6 +64,24 @@ function isExpired(entry) {
64
64
  return false;
65
65
  return Date.now() - entry.ts > entry.ttl;
66
66
  }
67
+ /**
68
+ * A process-scoped entry whose enqueuing process is gone. Replaying it would
69
+ * deliver stale live feedback — typically the "restarting, back in ~5s" notice
70
+ * that a successful restart has already superseded with its own confirmation.
71
+ *
72
+ * A stale entry is not `isExpired()`: it needs `drainOnce` to retire it with a
73
+ * reason of its own, otherwise the send log would report a semantic drop as a
74
+ * timeout.
75
+ */
76
+ export function isStaleProcessScoped(entry) {
77
+ if (!entry.processScoped)
78
+ return false;
79
+ // A record without a pid cannot prove same-process ownership. Treat it as
80
+ // replayable so the ordinary route/expiry rules stay in charge.
81
+ if (typeof entry.enqueuedByPid !== 'number')
82
+ return false;
83
+ return entry.enqueuedByPid !== process.pid;
84
+ }
67
85
  export function isDeliveryForChannel(value, channelId) {
68
86
  return isDeliveryTargetForChannel(value, channelId);
69
87
  }
@@ -129,6 +147,7 @@ export function enqueue(aid, opts) {
129
147
  delivery,
130
148
  dedupeKey: opts.dedupeKey,
131
149
  critical: opts.critical,
150
+ ...(opts.processScoped ? { processScoped: true, enqueuedByPid: process.pid } : {}),
132
151
  type: opts.type,
133
152
  contentKind: opts.contentKind,
134
153
  payload: opts.payload,
@@ -273,6 +292,19 @@ function markExpired(entry) {
273
292
  code: entry.lastErrorCode,
274
293
  };
275
294
  }
295
+ /**
296
+ * Drop a process-scoped entry left behind by a previous process. The terminal
297
+ * record is retained so the send log still shows why it never went out.
298
+ */
299
+ function markSupersededProcessScoped(entry) {
300
+ entry.lastError = 'process-scoped delivery superseded by a new daemon process';
301
+ entry.lastErrorCode = 'OUTBOX_PROCESS_SUPERSEDED';
302
+ entry.terminal = {
303
+ at: Date.now(),
304
+ error: entry.lastError,
305
+ code: entry.lastErrorCode,
306
+ };
307
+ }
276
308
  /** Mark a durable entry terminal while keeping its reason for diagnostics. */
277
309
  export function markTerminal(aid, id, failure, submitted, queue = submitted ? entryQueue(submitted) : 'default') {
278
310
  const entries = readEntries(aid, queue);
@@ -336,6 +368,7 @@ async function drainOnce(aid, sender, queue) {
336
368
  let expired = 0;
337
369
  let failed = 0;
338
370
  let permanent = 0;
371
+ let superseded = 0;
339
372
  const remaining = [];
340
373
  for (const entry of entries) {
341
374
  // Cancellation or another sender may have changed an
@@ -350,6 +383,16 @@ async function drainOnce(aid, sender, queue) {
350
383
  }
351
384
  Object.assign(entry, currentEntry);
352
385
  }
386
+ if (isStaleProcessScoped(entry)) {
387
+ // Deliberately not counted as `expired`: this is a semantic drop, not a
388
+ // timeout, and the drained-line diagnostic should not conflate them.
389
+ // The terminal record also retires the entry from `hasPending()`, so a
390
+ // later drain does not rediscover it.
391
+ superseded++;
392
+ markSupersededProcessScoped(entry);
393
+ remaining.push(entry);
394
+ continue;
395
+ }
353
396
  if (isExpired(entry)) {
354
397
  expired++;
355
398
  markExpired(entry);
@@ -457,7 +500,13 @@ async function drainOnce(aid, sender, queue) {
457
500
  }];
458
501
  });
459
502
  writeEntries(aid, retained, queue);
460
- return { sent, expired, failed, ...(permanent > 0 ? { permanent } : {}) };
503
+ return {
504
+ sent,
505
+ expired,
506
+ failed,
507
+ ...(permanent > 0 ? { permanent } : {}),
508
+ ...(superseded > 0 ? { superseded } : {}),
509
+ };
461
510
  }
462
511
  export async function drain(aid, sender, queue = 'default') {
463
512
  const drainKey = `${queue}:${aid}`;
@@ -9,7 +9,7 @@
9
9
  * - providerAid = 控制 AID(daemon 进程身份,pureIdentity 常驻 AUN)
10
10
  * - aunClient 生命周期陷阱:AUNChannel 重连会销毁重建底层 client,
11
11
  * 所以不能把 client 引用直接交给 ServiceProxyClient;传一个动态解引用的
12
- * facade,每次 .call()/.authenticate()/._tokenStore 都读 channel 当前 client。
12
+ * facade,每次 .call()/.authenticate()/getAccessToken() 都读 channel 当前 client。
13
13
  * serveForever(persistent) 的指数退避会在 client 缺失时自动等待恢复。
14
14
  * - endpoint 发现:source='instance' 时读服务实例登记文件的 port;source='static' 时使用显式 endpoint。
15
15
  * - 失败降级:任何异常只 warn,绝不影响 daemon 主流程。
@@ -143,31 +143,25 @@ export function startServiceProxy(controlChannel, providerAid, configuredService
143
143
  },
144
144
  authenticate: () => {
145
145
  const client = controlChannel.getClient();
146
- if (!client || typeof client.authenticate !== 'function') {
146
+ if (!client) {
147
147
  return Promise.reject(new Error('AUN control client not connected'));
148
148
  }
149
+ const accessToken = client.getAccessToken();
150
+ if (accessToken)
151
+ return Promise.resolve({ access_token: accessToken });
149
152
  return client.authenticate();
150
153
  },
151
154
  on: (event, handler) => {
152
- const client = controlChannel.getClient();
153
- return client?.on?.(event, handler);
154
- },
155
- get _tokenStore() {
156
- return controlChannel.getClient()?._tokenStore;
155
+ return controlChannel.getClient()?.on(event, handler);
157
156
  },
158
- // ServiceProxyClient._resolveCachedAccessToken 依次读取 _identity / _auth /
159
- // _tokenStore + deviceId 来复用 AUNChannel 已签发的 access_token。控制 AID 在
160
- // connect 时已 authenticate(client 处于 ready 态),若这些字段不透出,
161
- // _ensureAccessToken 会回退到 authenticate() 并抛 "authenticate not allowed
162
- // in state ready",导致隧道永远连不上 proxy-server。动态解引用,规避重连换 client。
163
- get _identity() {
164
- return controlChannel.getClient()?._identity;
157
+ getAccessToken: () => {
158
+ return controlChannel.getClient()?.getAccessToken() ?? null;
165
159
  },
166
- get _auth() {
167
- return controlChannel.getClient()?._auth;
160
+ get access_token() {
161
+ return controlChannel.getClient()?.getAccessToken() ?? null;
168
162
  },
169
- get _deviceId() {
170
- return controlChannel.getClient()?._deviceId;
163
+ get currentAid() {
164
+ return controlChannel.getClient()?.currentAid ?? null;
171
165
  },
172
166
  };
173
167
  // endpoint 白名单:只允许回连本机回环(127.0.0.1 / localhost 默认放行)。
@@ -1,2 +1,31 @@
1
1
  export { storageUpload, storageUploadBuffer, storageLs, storageRm, storageQuota } from './client.js';
2
2
  export { storageDownload } from './download.js';
3
+ import { isValidAid } from '../aid/validation.js';
4
+ export function resolveStorageCommandArgv(argv) {
5
+ if (argv[0] !== 'storage')
6
+ throw new Error('Expected a storage command');
7
+ const positionals = [];
8
+ let isPublic = false;
9
+ for (let index = 1; index < argv.length; index++) {
10
+ const value = argv[index];
11
+ if (value === '--public')
12
+ isPublic = true;
13
+ else if (value === '--format' && argv[index + 1] === 'json')
14
+ index++;
15
+ else if (value.startsWith('--'))
16
+ throw new Error(`Unsupported managed storage option: ${value}`);
17
+ else
18
+ positionals.push(value);
19
+ }
20
+ const [sub, aid, source, destination] = positionals;
21
+ const operation = { upload: 'storage.upload', download: 'storage.download', ls: 'storage.list', rm: 'storage.delete', quota: 'storage.quota' }[sub];
22
+ if (!operation || !aid || !isValidAid(aid))
23
+ throw new Error('Invalid storage command or AID');
24
+ const counts = { upload: [4], download: [3, 4], ls: [2, 3], rm: [3], quota: [2] };
25
+ if (!counts[sub].includes(positionals.length) || (isPublic && sub !== 'upload'))
26
+ throw new Error('Invalid storage arguments');
27
+ return { sub, aid, operation, isPublic, localFile: sub === 'upload' ? source : undefined,
28
+ remotePath: sub === 'upload' ? destination : sub === 'rm' ? source : undefined,
29
+ url: sub === 'download' ? source : undefined, localPath: sub === 'download' ? destination : undefined,
30
+ prefix: sub === 'ls' ? source ?? '' : undefined };
31
+ }
@@ -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
  }
@@ -4534,6 +4413,9 @@ export class AUNChannel {
4534
4413
  };
4535
4414
  if (evolCommand)
4536
4415
  base.ext = { evolCommand };
4416
+ // Same diagnostic dictionary as text payloads (sessionId/agentSessionId/model).
4417
+ if (context?.debug)
4418
+ base.debug = { ...context.debug, sentAt: localTimestamp() };
4537
4419
  return withAunAttention(base);
4538
4420
  }
4539
4421
  async sendReliableStructured(channelId, payload, context, logText) {
@@ -4617,6 +4499,9 @@ export class AUNChannel {
4617
4499
  delivery,
4618
4500
  dedupeKey: operationId,
4619
4501
  critical: routedContext.metadata?.criticalDelivery === true,
4502
+ // Live feedback about the running process (for example the pre-restart
4503
+ // notice) must never be replayed by the process that replaces it.
4504
+ processScoped: routedContext.metadata?.processScopedDelivery === true,
4620
4505
  type: 'text',
4621
4506
  text: finalText,
4622
4507
  context: routedContext,
@@ -4686,6 +4571,16 @@ export class AUNChannel {
4686
4571
  };
4687
4572
  }
4688
4573
  /** Daemon-side transport for `ec msg send` running inside an agent task. */
4574
+ /**
4575
+ * Agent 经 CLI 委托发送(ec msg send / ec group send)时占用本轮序号,
4576
+ * 与回复路径共用 sentCount,保证同一轮内 seqInTurn 连续。
4577
+ */
4578
+ nextSeqInTurn(channelId, threadId) {
4579
+ const turnKey = `${channelId}\0${threadId ?? ''}`;
4580
+ const seqInTurn = (this.sentCount.get(turnKey) || 0) + 1;
4581
+ this.sentCount.set(turnKey, seqInTurn);
4582
+ return seqInTurn;
4583
+ }
4689
4584
  async sendDaemonMsg(args) {
4690
4585
  if (!args.to) {
4691
4586
  return { ok: false, error: 'aun-msg-send requires a private target', code: 'UNSUPPORTED_TARGET' };
@@ -5392,6 +5287,9 @@ export class AUNChannel {
5392
5287
  delete payloadWithChatmode.thread_id;
5393
5288
  if (context?.threadId && !payloadWithChatmode.thread_id)
5394
5289
  payloadWithChatmode.thread_id = context.threadId;
5290
+ // Same diagnostic dictionary as text payloads (sessionId/agentSessionId/model).
5291
+ if (context?.debug)
5292
+ payloadWithChatmode.debug = { ...context.debug, sentAt: localTimestamp() };
5395
5293
  const params = {
5396
5294
  context: { type: 'task', id: taskId },
5397
5295
  payload: this.limitThoughtPayload(payloadWithChatmode),
@@ -5968,8 +5866,12 @@ export class AUNChannel {
5968
5866
  }
5969
5867
  return { status: 'permanent', error: `unsupported outbox entry type: ${entry.type}`, code: 'UNSUPPORTED_OUTBOX_TYPE' };
5970
5868
  }, queue);
5971
- if (result.sent > 0 || result.expired > 0 || result.permanent) {
5972
- logger.info(`${this.logPrefix()} ${queue} outbox drained: sent=${result.sent} expired=${result.expired} failed=${result.failed} permanent=${result.permanent ?? 0}`);
5869
+ if (result.sent > 0 || result.expired > 0 || result.permanent || result.superseded) {
5870
+ const superseded = result.superseded ? ` superseded=${result.superseded}` : '';
5871
+ logger.info(`${this.logPrefix()} ${queue} outbox drained: sent=${result.sent} expired=${result.expired} failed=${result.failed} permanent=${result.permanent ?? 0}${superseded}`);
5872
+ }
5873
+ if (result.superseded) {
5874
+ logger.info(`${this.logPrefix()} ${queue} outbox: dropped ${result.superseded} process-scoped entr${result.superseded === 1 ? 'y' : 'ies'} left by a previous daemon process`);
5973
5875
  }
5974
5876
  };
5975
5877
  await drainQueue('default');
@@ -6022,11 +5924,7 @@ export class AUNChannel {
6022
5924
  logger.warn(`${this.logPrefix()} Repaired bootstrap outbox route to private Owner delivery: entry=${entry.id} owner=${owner}`);
6023
5925
  }
6024
5926
  }
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
- }
5927
+ acknowledge(_messageId) { }
6030
5928
  sendProcessingStatus(channelId, status, sessionId, taskId, context, extraMeta) {
6031
5929
  let delivery;
6032
5930
  try {
@@ -6087,6 +5985,9 @@ export class AUNChannel {
6087
5985
  statusPayload.chatmode = context.metadata.chatmode;
6088
5986
  if (context?.replyToMessageId)
6089
5987
  statusPayload.ref_message_id = context.replyToMessageId;
5988
+ // Same diagnostic dictionary as text payloads (sessionId/agentSessionId/model).
5989
+ if (context?.debug)
5990
+ statusPayload.debug = { ...context.debug, sentAt: localTimestamp() };
6090
5991
  const notifyOptions = { ttlMs: 60_000 };
6091
5992
  if (delivery.chatType === 'group')
6092
5993
  notifyOptions.groupId = delivery.groupId;