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.
- package/CHANGELOG.md +35 -0
- package/bin/install-codex-managed-hooks.mjs +124 -37
- package/dist/agents/claude-runner.js +29 -2
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +76 -44
- package/dist/agents/codex-runner.js +51 -24
- package/dist/agents/ecagent-runner.js +9 -13
- package/dist/agents/gemini-runner.js +4 -0
- package/dist/aun/aid/store.js +0 -81
- package/dist/aun/group/fs-download.js +1 -10
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/managed-operation.js +130 -32
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/aun/service-proxy.js +12 -18
- package/dist/aun/storage/index.js +29 -0
- package/dist/channels/aun.js +38 -137
- package/dist/channels/daemon.js +257 -2
- package/dist/cli/agent-command.js +64 -31
- package/dist/cli/agent-create.js +43 -24
- package/dist/cli/agent.js +16 -70
- package/dist/cli/aun-commands.js +512 -32
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +1 -2
- package/dist/cli/daemon-commands.js +82 -13
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/diag.js +2 -2
- package/dist/cli/handoff-command.js +18 -5
- package/dist/cli/index.js +81 -64
- package/dist/cli/init.js +2 -2
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +93 -12
- package/dist/cli/model.js +14 -138
- package/dist/cli/queue-command.js +51 -0
- package/dist/cli/response.js +5 -54
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +85 -3
- package/dist/cli/stats.js +10 -4
- package/dist/cli/task-command.js +1 -0
- package/dist/cli/task-context.js +16 -0
- package/dist/cli/thread-command.js +620 -0
- package/dist/cli/trigger-command.js +76 -50
- package/dist/config/builtin-roles.js +9 -7
- package/dist/config/config-field-policy.js +2 -3
- package/dist/config/config-manager.js +7 -2
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +15 -0
- package/dist/core/agent-ipc-registration.js +65 -22
- package/dist/core/auth/agent-delegation.js +42 -2
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-authorization.js +1 -0
- package/dist/core/auth/operation-authorizer.js +24 -1
- package/dist/core/auth/operation-catalog.js +44 -3
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/command-handler.js +224 -45
- package/dist/core/command/diag-service.js +5 -1
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +48 -1
- package/dist/core/command/menu-handler.js +92 -16
- package/dist/core/command/menu-protocol.js +24 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +193 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/handoff/ipc-registration.js +38 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +210 -9
- package/dist/core/memory/consolidation-context.js +49 -16
- package/dist/core/memory/consolidation-ops-records.js +35 -0
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +174 -180
- package/dist/core/message/aun-ipc-sender.js +207 -8
- package/dist/core/message/im-renderer.js +149 -6
- package/dist/core/message/message-bridge.js +13 -3
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +131 -3
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +379 -69
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +126 -7
- package/dist/core/permission/sandbox-runtime.js +14 -1
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +54 -9
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +4 -1
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/task/task-recovery-service.js +2 -0
- package/dist/core/task/task-resume-operation.js +11 -0
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +777 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +10 -7
- package/dist/ipc.js +234 -17
- package/dist/paths.js +23 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/trigger/authorization.js +59 -29
- package/dist/trigger/feedback.js +7 -1
- package/dist/trigger/ipc-service.js +49 -35
- package/dist/trigger/parser.js +63 -1
- package/dist/trigger/script-sandbox.js +58 -77
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/error-utils.js +22 -1
- package/kits/docs/AUTHORING.md +1 -1
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +39 -28
- package/kits/docs/evolcore/agent.md +19 -4
- package/kits/docs/evolcore/ec-capabilities.md +4 -0
- package/kits/docs/evolcore/group-collaboration.md +3 -5
- package/kits/docs/evolcore/group.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +1 -1
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/msg.md +5 -8
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/evolcore/trigger.md +2 -2
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/eck_manifest.json +61 -26
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +0 -9
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +7 -1
- package/kits/rules/07-memory-and-thread.md +202 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +5 -0
- package/kits/templates/memory-consolidation/generalize.md +5 -0
- package/kits/templates/memory-consolidation/profile.md +5 -0
- package/kits/templates/memory-consolidation/promote.md +5 -0
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +66 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +2 -1
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/reply.md +3 -2
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +3 -1
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
package/dist/aun/outbox.js
CHANGED
|
@@ -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 {
|
|
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()
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
167
|
-
return controlChannel.getClient()?.
|
|
160
|
+
get access_token() {
|
|
161
|
+
return controlChannel.getClient()?.getAccessToken() ?? null;
|
|
168
162
|
},
|
|
169
|
-
get
|
|
170
|
-
return controlChannel.getClient()?.
|
|
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
|
+
}
|
package/dist/channels/aun.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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;
|