evolcore 0.0.22 → 0.0.24
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 +74 -14
- package/README.md +4 -0
- package/bin/codex-managed-acl.ps1 +160 -0
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +384 -95
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +30 -2
- package/dist/agents/claude-runner.js +179 -240
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +90 -20
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +369 -0
- package/dist/agents/codex-runner.js +839 -192
- package/dist/agents/ecagent-runner.js +27 -34
- package/dist/agents/gemini-runner.js +62 -8
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +43 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +57 -5
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +7 -1
- package/dist/channels/aun.js +178 -77
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wechat.js +81 -10
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +108 -24
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +289 -61
- package/dist/cli/index.js +22 -2
- package/dist/cli/init.js +2 -10
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/cli/task-context.js +12 -2
- package/dist/cli/trigger-command.js +9 -1
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +10 -7
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +75 -18
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +14 -2
- package/dist/core/auth/remote-access-store.js +136 -0
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +7 -2
- package/dist/core/command/command-handler.js +128 -30
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +6 -1
- package/dist/core/command/menu-handler.js +410 -98
- package/dist/core/command/menu-protocol.js +123 -0
- package/dist/core/command/slash-gate.js +4 -6
- package/dist/core/command/slash-handler.js +199 -70
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +3 -0
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-queue.js +20 -15
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +147 -94
- package/dist/core/model/catalog-service.js +310 -0
- package/dist/core/model/model-catalog.js +134 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +5 -2
- package/dist/core/permission/approval-gateway.js +183 -32
- package/dist/core/permission/protected-paths.js +63 -4
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-policy.js +257 -31
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +214 -57
- package/dist/ipc.js +191 -102
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +77 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +16 -2
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +70 -15
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +13 -0
- package/kits/docs/evolcore/group-collaboration.md +7 -2
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/templates/roles/admin.json +2 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +8 -1
- package/package.json +16 -4
- package/dist/utils/codex-cli.js +0 -43
package/dist/channels/aun.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createSendFileMarkerPattern } from '../core/message/file-markers.js';
|
|
|
16
16
|
import { chatDirPath } from '../core/session/session-fs-store.js';
|
|
17
17
|
import { appendHintAdd, appendHintRemove, parseInjectRequest } from '../core/message/pending-hints.js';
|
|
18
18
|
import { appendAidLifecycle } from '../aun/aid/identity.js';
|
|
19
|
-
import { enableFullGroupPullPagination, getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
|
|
19
|
+
import { enableFullGroupPullPagination, enableReliableMessageDelivery, getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
|
|
20
20
|
import { MAX_AUN_ATTACHMENT_SIZE, uploadBufferAndBuildPayload, uploadFileAndBuildPayload } from '../aun/msg/upload.js';
|
|
21
21
|
import { loadAgent } from '../config-store.js';
|
|
22
22
|
import { resolveAgentLifecycle } from '../config/lifecycle.js';
|
|
@@ -27,7 +27,6 @@ import { getProcessStartTime } from '../utils/process-introspect.js';
|
|
|
27
27
|
import * as outbox from '../aun/outbox.js';
|
|
28
28
|
import { guessMime, formatSize } from '../utils/media-cache.js';
|
|
29
29
|
import { formatPeerKey, PeerIdentityCache } from '../core/relation/peer-identity.js';
|
|
30
|
-
import { getFirstStaticAgentOwner } from '../config/peer-role-resolver.js';
|
|
31
30
|
import { isHClassPath } from '../core/permission/protected-paths.js';
|
|
32
31
|
import { consumeAunCausation, registerAunCausation } from '../core/causation/aun-association.js';
|
|
33
32
|
import { deriveCausation, normalizeCausation } from '../core/causation/context.js';
|
|
@@ -35,7 +34,7 @@ import { recordCausationSpan } from '../core/causation/audit.js';
|
|
|
35
34
|
import { refreshAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
|
|
36
35
|
import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
|
|
37
36
|
import { toolInputForDisplay } from '../utils/tool-summary.js';
|
|
38
|
-
import { bindPostBootstrapWelcomeOutboxSession, hasPendingPostBootstrapWelcomeOutbox, bootstrapInitialMessageOperationPrefix, postBootstrapWelcomeOperationId, preparePostBootstrapWelcomeOutbox, } from '../core/bootstrap-messages.js';
|
|
37
|
+
import { bindPostBootstrapWelcomeOutboxSession, hasPendingPostBootstrapWelcomeOutbox, pendingPostBootstrapWelcomeOwners, isPostBootstrapWelcomeOperationId, findPostBootstrapWelcomeOutbox, bootstrapInitialMessageOperationPrefix, postBootstrapWelcomeOperationId, preparePostBootstrapWelcomeOutbox, } from '../core/bootstrap-messages.js';
|
|
39
38
|
import { hasMentionAll, mentionEntryAids, mentionEntryTargets, } from '../core/message/mention-schema.js';
|
|
40
39
|
import { normalizeAunMentionEntries, } from '../aun/msg/mention-schema.js';
|
|
41
40
|
import { isDeliveryTarget, sameDeliveryTarget } from '../core/message/message-utils.js';
|
|
@@ -484,7 +483,8 @@ function redactMenuTokensForTrace(value, seen = new WeakSet()) {
|
|
|
484
483
|
return value.map(item => redactMenuTokensForTrace(item, seen));
|
|
485
484
|
const redacted = {};
|
|
486
485
|
for (const [key, item] of Object.entries(value)) {
|
|
487
|
-
|
|
486
|
+
const normalizedKey = key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/-/g, '_').toLowerCase();
|
|
487
|
+
redacted[key] = /^(?:menu|ecweb)_token$/.test(normalizedKey)
|
|
488
488
|
? '[REDACTED]'
|
|
489
489
|
: redactMenuTokensForTrace(item, seen);
|
|
490
490
|
}
|
|
@@ -728,7 +728,10 @@ export class AUNChannel {
|
|
|
728
728
|
const actionValue = isComposeReply ? '_custom_input'
|
|
729
729
|
: typeof obj.value === 'string' ? obj.value
|
|
730
730
|
: typeof obj.action_value === 'string' ? obj.action_value : text;
|
|
731
|
-
|
|
731
|
+
// Prefer the trusted context persisted with the card. A callback's
|
|
732
|
+
// thread_id is client-controlled and may be absent or stale.
|
|
733
|
+
const threadId = cardInfo?.replyContext?.threadId
|
|
734
|
+
?? (typeof obj.thread_id === 'string' ? obj.thread_id : undefined);
|
|
732
735
|
// 卡片点击者身份:只信认证信封(senderAid 参数,由调用方从 msg.from / msg.sender_aid 提取)。
|
|
733
736
|
// payload 自报字段(from / sender_aid / user_id)不可信,可被客户端伪造,不读取。
|
|
734
737
|
// 两类卡片共用:CommandCard → 伪入站消息的 peerId,ActionInteraction → operatorId。
|
|
@@ -770,6 +773,10 @@ export class AUNChannel {
|
|
|
770
773
|
peerId: cardClickerAid,
|
|
771
774
|
peerName: typeof obj.label === 'string' ? obj.label : typeof obj.action_label === 'string' ? obj.action_label : undefined,
|
|
772
775
|
messageId: `card-trigger-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
776
|
+
threadId,
|
|
777
|
+
replyContext: cardInfo?.replyContext ?? ((threadId || cardDelivery)
|
|
778
|
+
? { ...(threadId ? { threadId } : {}), ...(cardDelivery ? { delivery: cardDelivery } : {}) }
|
|
779
|
+
: undefined),
|
|
773
780
|
source: 'card-trigger',
|
|
774
781
|
})).catch((err) => {
|
|
775
782
|
logger.error(`${this.logPrefix()} CommandCard handler failed: action=${actionValue} mid=${cardMsgId}`, err);
|
|
@@ -1465,12 +1472,13 @@ export class AUNChannel {
|
|
|
1465
1472
|
this.store = store;
|
|
1466
1473
|
const client = await loadClient(store, aidName);
|
|
1467
1474
|
enableFullGroupPullPagination(client);
|
|
1475
|
+
enableReliableMessageDelivery(client);
|
|
1468
1476
|
this.client = client;
|
|
1469
1477
|
// A reconnect replaces the SDK client. Late events from the retired
|
|
1470
1478
|
// instance must not mutate the new instance's health or message state.
|
|
1471
1479
|
const isCurrentClient = () => this.client === client;
|
|
1472
1480
|
// Register event handlers before connecting
|
|
1473
|
-
client.on('message.received', (data) => {
|
|
1481
|
+
client.on('message.received', async (data) => {
|
|
1474
1482
|
if (!isCurrentClient())
|
|
1475
1483
|
return;
|
|
1476
1484
|
this.aidState.lastInboundAt = Date.now();
|
|
@@ -1479,11 +1487,11 @@ export class AUNChannel {
|
|
|
1479
1487
|
const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
|
|
1480
1488
|
const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
|
|
1481
1489
|
logger.debug(`${this.logPrefix()}[DIAG] message.received: kind=${kind} keys=${keys}`);
|
|
1482
|
-
this.handleIncomingPrivateMessage(data);
|
|
1490
|
+
await this.handleIncomingPrivateMessage(data);
|
|
1483
1491
|
});
|
|
1484
1492
|
// pureIdentity(控制 AID):协议层不接群消息,不注册 group 创建监听
|
|
1485
1493
|
if (!this.config.pureIdentity) {
|
|
1486
|
-
client.on('group.message_created', (data) => {
|
|
1494
|
+
client.on('group.message_created', async (data) => {
|
|
1487
1495
|
if (!isCurrentClient())
|
|
1488
1496
|
return;
|
|
1489
1497
|
this.aidState.lastInboundAt = Date.now();
|
|
@@ -1493,7 +1501,7 @@ export class AUNChannel {
|
|
|
1493
1501
|
const gid = env.group_id ?? '';
|
|
1494
1502
|
const sender = env.from ?? '';
|
|
1495
1503
|
logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
|
|
1496
|
-
this.handleIncomingGroupMessage(data);
|
|
1504
|
+
await this.handleIncomingGroupMessage(data);
|
|
1497
1505
|
});
|
|
1498
1506
|
client.on('group.changed', (data) => {
|
|
1499
1507
|
if (!isCurrentClient())
|
|
@@ -1504,6 +1512,35 @@ export class AUNChannel {
|
|
|
1504
1512
|
: {};
|
|
1505
1513
|
this.invalidateObserverGroupMembers(envelope.group_id ?? record.group_id);
|
|
1506
1514
|
});
|
|
1515
|
+
// fastaun 0.5.12+ exposes targeted invitations as a local, aggregated
|
|
1516
|
+
// event. It is a control signal, not a user message, so keep it out of
|
|
1517
|
+
// the Agent routing path while retaining it in diagnostics.
|
|
1518
|
+
client.on('group.invite_received', (data) => {
|
|
1519
|
+
if (!isCurrentClient())
|
|
1520
|
+
return;
|
|
1521
|
+
const record = data && typeof data === 'object' ? data : {};
|
|
1522
|
+
const groupId = record.group_id ?? record.group_aid ?? 'unknown';
|
|
1523
|
+
const inviteIds = Array.isArray(record.invite_ids) ? record.invite_ids : [];
|
|
1524
|
+
this.trace('IN', 'group.invite_received', data);
|
|
1525
|
+
logger.info(`${this.logPrefix()} Group invitation received: group=${groupId} pending=${inviteIds.length || 1}`);
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
// 0.5.10/0.5.11 local lifecycle events summarize SDK delivery and Pull
|
|
1529
|
+
// progress. They must never be interpreted as inbound chat messages.
|
|
1530
|
+
for (const eventName of ['delivery.changed', 'sync.started', 'sync.progress', 'sync.stopped']) {
|
|
1531
|
+
client.on(eventName, (data) => {
|
|
1532
|
+
if (!isCurrentClient())
|
|
1533
|
+
return;
|
|
1534
|
+
this.trace('IN', eventName, data);
|
|
1535
|
+
if (eventName === 'sync.progress') {
|
|
1536
|
+
const record = data && typeof data === 'object' ? data : {};
|
|
1537
|
+
logger.debug(`${this.logPrefix()} AUN sync progress: namespace=${record.namespace ?? 'unknown'} page=${record.page ?? '?'} pulled=${record.page_pulled_count ?? record.pulled_count ?? 0}`);
|
|
1538
|
+
}
|
|
1539
|
+
else if (eventName === 'sync.stopped') {
|
|
1540
|
+
const record = data && typeof data === 'object' ? data : {};
|
|
1541
|
+
logger.info(`${this.logPrefix()} AUN sync stopped: reason=${record.reason ?? 'unknown'}`);
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1507
1544
|
}
|
|
1508
1545
|
const handleSdkState = (source) => (data) => {
|
|
1509
1546
|
if (!isCurrentClient())
|
|
@@ -1629,6 +1666,14 @@ export class AUNChannel {
|
|
|
1629
1666
|
this.connected = true;
|
|
1630
1667
|
this.connectedAt = Date.now();
|
|
1631
1668
|
this.setAidStatus('connected', { lastConnectedAt: Date.now(), lastError: undefined, gatewayUrl: this.gatewayUrl });
|
|
1669
|
+
// AUN seq_tracker is transport-scoped, while this channel's durable
|
|
1670
|
+
// dedup log is application-scoped. A diagnostic consumer can advance
|
|
1671
|
+
// the former without the application having seen those messages. On a
|
|
1672
|
+
// reconnect, repair only the in-memory P2P cursor to the last message
|
|
1673
|
+
// the application actually claimed, then force a Forward pull. The SDK
|
|
1674
|
+
// persists cursors monotonically, so this does not rewrite SQLite and
|
|
1675
|
+
// the normal pull will restore the high watermark after delivery.
|
|
1676
|
+
this.scheduleInboundP2PCursorRepair(client, isCurrentClient);
|
|
1632
1677
|
// Workaround: SDK e2ee uses _identity.cert for sender_cert_fingerprint;
|
|
1633
1678
|
// if cert is missing, it falls back to public key SPKI fingerprint which
|
|
1634
1679
|
// causes peer cert lookup failures. Backfill from keystore if needed.
|
|
@@ -1661,51 +1706,50 @@ export class AUNChannel {
|
|
|
1661
1706
|
logger.warn(`${this.logPrefix()} agent config not found for ${aidName}, skipping welcome message`);
|
|
1662
1707
|
return false;
|
|
1663
1708
|
}
|
|
1664
|
-
const
|
|
1665
|
-
if (
|
|
1709
|
+
const owners = Array.from(new Set(agentConfig.owners ?? []));
|
|
1710
|
+
if (owners.length === 0) {
|
|
1666
1711
|
logger.info(`${this.logPrefix()} No owner configured, skipping post-bootstrap welcome message`);
|
|
1667
1712
|
return false;
|
|
1668
1713
|
}
|
|
1669
|
-
|
|
1670
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aidName);
|
|
1671
|
-
if (hasMessageLogOperation(chatDir, operationId))
|
|
1672
|
-
return true;
|
|
1673
|
-
const prepared = outbox.findByDedupeKey(aidName, operationId);
|
|
1714
|
+
this.repairBootstrapRoutes();
|
|
1674
1715
|
// Bootstrap welcomes target the configured personal Owner AID. They are
|
|
1675
1716
|
// not replies to an inbound group message, so their route is always
|
|
1676
1717
|
// private and must not be inferred with group.get_info.
|
|
1677
1718
|
const lifecycle = resolveAgentLifecycle(agentConfig);
|
|
1678
1719
|
const delivery = { chatType: 'private' };
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1720
|
+
for (const owner of owners) {
|
|
1721
|
+
const operationId = postBootstrapWelcomeOperationId(aidName, owner);
|
|
1722
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aidName);
|
|
1723
|
+
if (hasMessageLogOperation(chatDir, operationId)
|
|
1724
|
+
|| hasMessageLogOperation(chatDir, postBootstrapWelcomeOperationId(aidName)))
|
|
1725
|
+
continue;
|
|
1726
|
+
const prepared = outbox.findByDedupeKey(aidName, operationId);
|
|
1727
|
+
if (prepared && prepared.channelId === owner && isDeliveryTarget(prepared.delivery) && sameDeliveryTarget(prepared.delivery, delivery))
|
|
1728
|
+
continue;
|
|
1729
|
+
const ownerAidClean = owner.startsWith('@') ? owner.slice(1) : owner;
|
|
1730
|
+
let ownerDisplayName = ownerAidClean.split('.')[0].slice(0, 12);
|
|
1731
|
+
// Keep lifecycle=bootstrapping preparation entirely local. Resolving an
|
|
1732
|
+
// uncached peer may download agent.md, and welcome display metadata must
|
|
1733
|
+
// never delay the durable write-ahead record or the lifecycle commit.
|
|
1734
|
+
const cachedOwnerInfo = this.peerInfoCached(owner);
|
|
1735
|
+
try {
|
|
1736
|
+
const ownerInfo = cachedOwnerInfo
|
|
1737
|
+
?? (lifecycle === 'active' ? await this.fetchPeerInfo(owner) : undefined);
|
|
1738
|
+
if (!ownerInfo) {
|
|
1739
|
+
preparePostBootstrapWelcomeOutbox(aidName, owner, ownerDisplayName, delivery);
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
ownerDisplayName = (ownerInfo.name || ownerDisplayName).slice(0, 12);
|
|
1743
|
+
if (ownerInfo.type !== null && ownerInfo.type !== 'human') {
|
|
1744
|
+
logger.warn(`${this.logPrefix()} Owner ${owner} type is "${ownerInfo.type}" (not human).`);
|
|
1745
|
+
}
|
|
1698
1746
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
logger.warn(`${this.logPrefix()} Owner ${owner} type is "${ownerInfo.type}" (not human).`);
|
|
1747
|
+
catch (e) {
|
|
1748
|
+
logger.warn(`${this.logPrefix()} Failed to resolve owner display name; using AID label: ${e}`);
|
|
1702
1749
|
}
|
|
1750
|
+
preparePostBootstrapWelcomeOutbox(aidName, owner, ownerDisplayName, delivery);
|
|
1703
1751
|
}
|
|
1704
|
-
|
|
1705
|
-
logger.warn(`${this.logPrefix()} Failed to resolve owner display name; using AID label: ${e}`);
|
|
1706
|
-
}
|
|
1707
|
-
preparePostBootstrapWelcomeOutbox(aidName, owner, ownerDisplayName, delivery);
|
|
1708
|
-
logger.info(`${this.logPrefix()} Post-bootstrap welcome durably prepared for owner: ${owner}`);
|
|
1752
|
+
logger.info(`${this.logPrefix()} Post-bootstrap welcomes durably prepared for ${owners.length} owner(s)`);
|
|
1709
1753
|
return true;
|
|
1710
1754
|
}
|
|
1711
1755
|
catch (e) {
|
|
@@ -1723,9 +1767,48 @@ export class AUNChannel {
|
|
|
1723
1767
|
const aid = this.config.aid.replace(/^@/, '');
|
|
1724
1768
|
return hasPendingPostBootstrapWelcomeOutbox(aid);
|
|
1725
1769
|
}
|
|
1726
|
-
async bindPostBootstrapWelcomeToSession(sessionId) {
|
|
1770
|
+
async bindPostBootstrapWelcomeToSession(sessionId, owner) {
|
|
1727
1771
|
const aid = this.config.aid.replace(/^@/, '');
|
|
1728
|
-
return bindPostBootstrapWelcomeOutboxSession(aid, sessionId);
|
|
1772
|
+
return bindPostBootstrapWelcomeOutboxSession(aid, sessionId, owner);
|
|
1773
|
+
}
|
|
1774
|
+
scheduleInboundP2PCursorRepair(client, isCurrentClient) {
|
|
1775
|
+
const tracker = client?._seqTracker;
|
|
1776
|
+
const repair = tracker?.repairContiguousSeq;
|
|
1777
|
+
const pull = client?._pullV2;
|
|
1778
|
+
if (typeof repair !== 'function' || typeof pull !== 'function')
|
|
1779
|
+
return;
|
|
1780
|
+
const ns = `p2p:${this.config.aid}`;
|
|
1781
|
+
let appSeq = 0;
|
|
1782
|
+
for (const [key, value] of this.inboundSeenMessages) {
|
|
1783
|
+
// Group sequence spaces are independent from the P2P space.
|
|
1784
|
+
if (!key.includes(':aun:private'))
|
|
1785
|
+
continue;
|
|
1786
|
+
const seq = Number(value.seq);
|
|
1787
|
+
if (Number.isSafeInteger(seq) && seq > appSeq)
|
|
1788
|
+
appSeq = seq;
|
|
1789
|
+
}
|
|
1790
|
+
if (appSeq <= 0)
|
|
1791
|
+
return;
|
|
1792
|
+
setTimeout(() => {
|
|
1793
|
+
if (!isCurrentClient() || !this.connected)
|
|
1794
|
+
return;
|
|
1795
|
+
const transportSeq = Number(tracker.getContiguousSeq?.(ns) ?? 0);
|
|
1796
|
+
if (!Number.isSafeInteger(transportSeq) || transportSeq <= appSeq + 1)
|
|
1797
|
+
return;
|
|
1798
|
+
try {
|
|
1799
|
+
repair.call(tracker, ns, appSeq);
|
|
1800
|
+
logger.warn(`${this.logPrefix()} Rewinding in-memory P2P cursor for application recovery: transport=${transportSeq}, app=${appSeq}`);
|
|
1801
|
+
Promise.resolve(pull.call(client, appSeq, 50, { force: true }))
|
|
1802
|
+
.then(() => logger.info(`${this.logPrefix()} Replayed P2P messages after cursor mismatch: after_seq=${appSeq}`))
|
|
1803
|
+
.catch((error) => logger.warn(`${this.logPrefix()} P2P cursor recovery pull failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1804
|
+
}
|
|
1805
|
+
catch (error) {
|
|
1806
|
+
logger.warn(`${this.logPrefix()} P2P cursor repair failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1807
|
+
}
|
|
1808
|
+
}, 250);
|
|
1809
|
+
}
|
|
1810
|
+
pendingPostBootstrapWelcomeOwners() {
|
|
1811
|
+
return pendingPostBootstrapWelcomeOwners(this.config.aid);
|
|
1729
1812
|
}
|
|
1730
1813
|
/**
|
|
1731
1814
|
* Resume delivery of an already prepared completion welcome.
|
|
@@ -1738,20 +1821,9 @@ export class AUNChannel {
|
|
|
1738
1821
|
this.repairBootstrapRoutes();
|
|
1739
1822
|
const configuredAid = this.config.aid;
|
|
1740
1823
|
const aid = configuredAid.startsWith('@') ? configuredAid.slice(1) : configuredAid;
|
|
1741
|
-
const
|
|
1742
|
-
|
|
1743
|
-
if (!entry) {
|
|
1744
|
-
const owner = getFirstStaticAgentOwner(aid);
|
|
1745
|
-
if (!owner)
|
|
1746
|
-
return false;
|
|
1747
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aid);
|
|
1748
|
-
return hasMessageLogOperation(chatDir, operationId);
|
|
1749
|
-
}
|
|
1750
|
-
if (!outbox.isDeliveryForChannel(entry.delivery, entry.channelId)) {
|
|
1751
|
-
logger.warn(`${this.logPrefix()} Discarding unrouted legacy post-bootstrap welcome: id=${entry.id}`);
|
|
1752
|
-
outbox.remove(aid, entry.id);
|
|
1824
|
+
const entries = findPostBootstrapWelcomeOutbox(aid);
|
|
1825
|
+
if (entries.length === 0)
|
|
1753
1826
|
return false;
|
|
1754
|
-
}
|
|
1755
1827
|
const agentConfig = loadAgent(aid);
|
|
1756
1828
|
if (!agentConfig || resolveAgentLifecycle(agentConfig) !== 'active') {
|
|
1757
1829
|
logger.info(`${this.logPrefix()} Post-bootstrap welcome prepared; waiting for lifecycle=active`);
|
|
@@ -1759,11 +1831,15 @@ export class AUNChannel {
|
|
|
1759
1831
|
}
|
|
1760
1832
|
if (!this.connected || !this.client)
|
|
1761
1833
|
return true;
|
|
1762
|
-
const
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1834
|
+
for (const entry of entries) {
|
|
1835
|
+
if (!outbox.isDeliveryForChannel(entry.delivery, entry.channelId))
|
|
1836
|
+
continue;
|
|
1837
|
+
const sent = await this.withOutboxInFlight(entry, () => this.deliverTextEntry(entry), { status: 'retry', error: 'outbox send is already in flight' });
|
|
1838
|
+
if (sent.status === 'sent')
|
|
1839
|
+
this.removeDeliveredOutboxEntry(entry);
|
|
1840
|
+
else if (sent.status === 'permanent')
|
|
1841
|
+
this.markPermanentOutboxFailure(entry, sent);
|
|
1842
|
+
}
|
|
1767
1843
|
return true;
|
|
1768
1844
|
}
|
|
1769
1845
|
// ── Event handlers ──────────────────────────────────────────
|
|
@@ -3523,7 +3599,7 @@ export class AUNChannel {
|
|
|
3523
3599
|
if (ids && ids.size === 0)
|
|
3524
3600
|
this.interactionCardMessageIds.delete(interactionId);
|
|
3525
3601
|
}
|
|
3526
|
-
registerCardPostSend(messageId, action, channelId, delivery) {
|
|
3602
|
+
registerCardPostSend(messageId, action, channelId, delivery, replyContext) {
|
|
3527
3603
|
if (action.type !== 'register_interaction_card')
|
|
3528
3604
|
return;
|
|
3529
3605
|
this.cardMessageIdMap.set(messageId, {
|
|
@@ -3532,6 +3608,7 @@ export class AUNChannel {
|
|
|
3532
3608
|
initiatorAid: action.initiatorAid,
|
|
3533
3609
|
channelId,
|
|
3534
3610
|
delivery: delivery ?? action.delivery,
|
|
3611
|
+
replyContext,
|
|
3535
3612
|
});
|
|
3536
3613
|
this.ownedCardMsgIds.add(messageId);
|
|
3537
3614
|
let messageIds = this.interactionCardMessageIds.get(action.requestId);
|
|
@@ -3564,7 +3641,7 @@ export class AUNChannel {
|
|
|
3564
3641
|
if (!entry.postSend)
|
|
3565
3642
|
return;
|
|
3566
3643
|
if (entry.postSend.type === 'register_interaction_card') {
|
|
3567
|
-
this.registerCardPostSend(messageId, entry.postSend, entry.channelId, entry.delivery);
|
|
3644
|
+
this.registerCardPostSend(messageId, entry.postSend, entry.channelId, entry.delivery, entry.context);
|
|
3568
3645
|
}
|
|
3569
3646
|
}
|
|
3570
3647
|
async sendAunPayload(channelId, payload, context, label) {
|
|
@@ -3893,6 +3970,9 @@ export class AUNChannel {
|
|
|
3893
3970
|
}
|
|
3894
3971
|
async sendReliableStructured(channelId, payload, context, logText) {
|
|
3895
3972
|
const result = await this.sendContentPayload(channelId, payload, {
|
|
3973
|
+
// Activity items use the durable activity queue so reconnect draining
|
|
3974
|
+
// preserves their ordering relative to other lifecycle events.
|
|
3975
|
+
queue: payload.type === 'activity' ? 'activity' : undefined,
|
|
3896
3976
|
contentKind: 'custom',
|
|
3897
3977
|
context,
|
|
3898
3978
|
logText: logText ?? this.payloadLogText(payload, 'custom'),
|
|
@@ -3903,6 +3983,7 @@ export class AUNChannel {
|
|
|
3903
3983
|
outboxId: result.outboxId,
|
|
3904
3984
|
});
|
|
3905
3985
|
}
|
|
3986
|
+
return result;
|
|
3906
3987
|
}
|
|
3907
3988
|
async sendMessage(channelId, text, context) {
|
|
3908
3989
|
if (!text?.trim()) {
|
|
@@ -4314,7 +4395,7 @@ export class AUNChannel {
|
|
|
4314
4395
|
logger.info(`${this.logPrefix()} Durable operation already logged; skipping duplicate send: ${operationId}`);
|
|
4315
4396
|
return sentOutboxResult(completedMessageId);
|
|
4316
4397
|
}
|
|
4317
|
-
if (operationId
|
|
4398
|
+
if (isPostBootstrapWelcomeOperationId(operationId, this.config.aid)) {
|
|
4318
4399
|
const agentConfig = loadAgent(this.config.aid);
|
|
4319
4400
|
if (!agentConfig || resolveAgentLifecycle(agentConfig) !== 'active') {
|
|
4320
4401
|
return { status: 'retry', error: 'post-bootstrap welcome is waiting for active lifecycle', code: 'BOOTSTRAP_NOT_ACTIVE' };
|
|
@@ -5287,15 +5368,19 @@ export class AUNChannel {
|
|
|
5287
5368
|
/** Repair bootstrap outbox entries to the configured personal Owner route. */
|
|
5288
5369
|
repairBootstrapRoutes() {
|
|
5289
5370
|
const aid = this.config.aid.replace(/^@/, '');
|
|
5290
|
-
const
|
|
5291
|
-
if (!owner)
|
|
5292
|
-
return;
|
|
5293
|
-
const completionEntry = outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid), { includeTerminal: true });
|
|
5371
|
+
const owners = Array.from(new Set(loadAgent(aid)?.owners ?? []));
|
|
5294
5372
|
const entries = [
|
|
5295
5373
|
...outbox.findByDedupePrefix(aid, bootstrapInitialMessageOperationPrefix(aid), { includeTerminal: true }),
|
|
5296
|
-
...(
|
|
5374
|
+
...findPostBootstrapWelcomeOutbox(aid, { includeTerminal: true }),
|
|
5297
5375
|
].filter((entry) => !!entry?.channelId);
|
|
5298
5376
|
for (const entry of entries) {
|
|
5377
|
+
const isInitial = entry.dedupeKey?.startsWith(bootstrapInitialMessageOperationPrefix(aid));
|
|
5378
|
+
const isOwnerSpecific = entry.dedupeKey?.startsWith(`bootstrap-complete:v2:${aid}:`);
|
|
5379
|
+
const owner = isInitial ? owners[0]
|
|
5380
|
+
: isOwnerSpecific ? owners.find(candidate => postBootstrapWelcomeOperationId(aid, candidate) === entry.dedupeKey)
|
|
5381
|
+
: (owners.includes(entry.channelId) ? entry.channelId : owners[0]);
|
|
5382
|
+
if (!owner)
|
|
5383
|
+
continue;
|
|
5299
5384
|
const delivery = { chatType: 'private' };
|
|
5300
5385
|
const nestedDelivery = entry.context?.delivery;
|
|
5301
5386
|
const routeAlreadyPrivate = entry.channelId === owner
|
|
@@ -5357,6 +5442,7 @@ export class AUNChannel {
|
|
|
5357
5442
|
timeout: 'timeout',
|
|
5358
5443
|
queued: 'queued',
|
|
5359
5444
|
progress: 'progress',
|
|
5445
|
+
notice: 'notice',
|
|
5360
5446
|
requires_action: 'requires_action',
|
|
5361
5447
|
};
|
|
5362
5448
|
const statusPayload = {
|
|
@@ -5416,6 +5502,16 @@ export class AUNChannel {
|
|
|
5416
5502
|
logger.info(logTaskStatus);
|
|
5417
5503
|
}
|
|
5418
5504
|
async sendCustomPayload(channelId, payload, context) {
|
|
5505
|
+
// Validate the route even when disconnected; callers must never be able
|
|
5506
|
+
// to bypass the fail-closed AUN routing contract. This compatibility API
|
|
5507
|
+
// is intentionally best-effort while offline, so a valid route simply
|
|
5508
|
+
// returns until the channel reconnects.
|
|
5509
|
+
const delivery = this.requireDelivery(channelId, context);
|
|
5510
|
+
context = this.withDelivery(context, delivery);
|
|
5511
|
+
if (!this.connected || !this.client) {
|
|
5512
|
+
logger.debug(`${this.logPrefix()} Custom payload skipped while disconnected: channel=${channelId}`);
|
|
5513
|
+
return;
|
|
5514
|
+
}
|
|
5419
5515
|
// SDK 0.3.0 E2EE requires payload to be an object
|
|
5420
5516
|
let payloadObj;
|
|
5421
5517
|
try {
|
|
@@ -5806,12 +5902,14 @@ export class AUNChannelPlugin {
|
|
|
5806
5902
|
await channel.sendThought(channelId, envelope.taskId, aunPayload, replyCtx);
|
|
5807
5903
|
}
|
|
5808
5904
|
else {
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5905
|
+
// Interactive activities are reliable structured payloads;
|
|
5906
|
+
// sendReliableStructured internally selects the activity
|
|
5907
|
+
// outbox queue while preserving the common delivery contract.
|
|
5908
|
+
const result = await channel.sendReliableStructured(channelId, aunPayload, replyCtx, channel.activityLogText(item));
|
|
5909
|
+
// Test and legacy adapters may stub this method without a
|
|
5910
|
+
// structured result; the call itself still proves acceptance.
|
|
5911
|
+
if (!result)
|
|
5912
|
+
continue;
|
|
5815
5913
|
if (result.status === 'permanent' || result.status === 'failed')
|
|
5816
5914
|
failedResult ??= result;
|
|
5817
5915
|
else if (result.status === 'retry' || result.status === 'queued' || result.queued)
|
|
@@ -5855,6 +5953,9 @@ export class AUNChannelPlugin {
|
|
|
5855
5953
|
case 'status.progress':
|
|
5856
5954
|
channel.sendProcessingStatus(channelId, 'progress', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
5857
5955
|
return;
|
|
5956
|
+
case 'status.notice':
|
|
5957
|
+
channel.sendProcessingStatus(channelId, 'notice', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
5958
|
+
return;
|
|
5858
5959
|
case 'status.requires_action':
|
|
5859
5960
|
channel.sendProcessingStatus(channelId, 'requires_action', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
5860
5961
|
return;
|
package/dist/channels/daemon.js
CHANGED
|
@@ -133,8 +133,12 @@ export class DaemonChannel {
|
|
|
133
133
|
const slot = this.pending.get(runId);
|
|
134
134
|
if (!slot || slot.terminalClaim)
|
|
135
135
|
return;
|
|
136
|
-
|
|
136
|
+
// A task notice reports an observation (for example, idle detection); it
|
|
137
|
+
// is not evidence that the runner made progress and must not renew the
|
|
138
|
+
// outer trigger watchdog.
|
|
139
|
+
if (payload.kind !== 'status.requires_action' && payload.kind !== 'status.notice') {
|
|
137
140
|
this.resetWatchdog(runId, slot);
|
|
141
|
+
}
|
|
138
142
|
switch (payload.kind) {
|
|
139
143
|
case 'activity.batch':
|
|
140
144
|
for (const item of payload.items) {
|
|
@@ -171,6 +175,8 @@ export class DaemonChannel {
|
|
|
171
175
|
if (payload.metadata?.activityType === 'tool_call')
|
|
172
176
|
this.recordToolCall(slot, payload.metadata.callId);
|
|
173
177
|
break;
|
|
178
|
+
case 'status.notice':
|
|
179
|
+
break;
|
|
174
180
|
case 'status.requires_action':
|
|
175
181
|
slot.waitingForAction = true;
|
|
176
182
|
if (slot.watchdogTimer)
|
|
@@ -1319,6 +1319,7 @@ export class DingtalkChannelPlugin {
|
|
|
1319
1319
|
case 'status.started':
|
|
1320
1320
|
case 'status.queued':
|
|
1321
1321
|
case 'status.progress':
|
|
1322
|
+
case 'status.notice':
|
|
1322
1323
|
return suppressedReceipt(envelope, 'reaction_lifecycle');
|
|
1323
1324
|
case 'status.completed':
|
|
1324
1325
|
await channel.completeAckReaction(envelope.taskId, true);
|
package/dist/channels/feishu.js
CHANGED
|
@@ -393,6 +393,7 @@ export class FeishuChannel {
|
|
|
393
393
|
const chatId = data.context?.open_chat_id || data.open_chat_id;
|
|
394
394
|
const cardMessageId = data.open_message_id || data.context?.open_message_id;
|
|
395
395
|
const formValues = action.form_value || {};
|
|
396
|
+
const entry = this.cardMetaStore.get(value._id);
|
|
396
397
|
const decision = routeCardAction({ value, formValues, operatorId }, this.cardMetaStore);
|
|
397
398
|
switch (decision.kind) {
|
|
398
399
|
case 'ignore':
|
|
@@ -413,6 +414,9 @@ export class FeishuChannel {
|
|
|
413
414
|
content: decision.command,
|
|
414
415
|
peerId: operatorId,
|
|
415
416
|
messageId: `card-trigger-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
417
|
+
threadId: entry?.replyContext?.threadId,
|
|
418
|
+
chatType: entry?.replyContext?.delivery?.chatType,
|
|
419
|
+
replyContext: entry?.replyContext,
|
|
416
420
|
source: 'card-trigger',
|
|
417
421
|
});
|
|
418
422
|
}
|
|
@@ -1197,6 +1201,7 @@ export class FeishuChannel {
|
|
|
1197
1201
|
const invalidationReason = this.invalidatedInteractions.get(interaction.id);
|
|
1198
1202
|
this.cardMetaStore.set(interaction.id, {
|
|
1199
1203
|
interaction, chatId, messageId, resolved: false,
|
|
1204
|
+
replyContext: options,
|
|
1200
1205
|
invalidated: !!invalidationReason,
|
|
1201
1206
|
invalidatedReason: invalidationReason,
|
|
1202
1207
|
});
|
|
@@ -1750,6 +1755,7 @@ export class FeishuChannelPlugin {
|
|
|
1750
1755
|
case 'status.started':
|
|
1751
1756
|
case 'status.queued':
|
|
1752
1757
|
case 'status.progress':
|
|
1758
|
+
case 'status.notice':
|
|
1753
1759
|
return suppressedReceipt(envelope, 'reaction_lifecycle');
|
|
1754
1760
|
case 'status.completed':
|
|
1755
1761
|
await channel.finalizeAckReaction(envelope.taskId, 'CheckMark');
|
|
@@ -1804,16 +1810,16 @@ export class FeishuChannelPlugin {
|
|
|
1804
1810
|
disconnect: () => channel.disconnect(),
|
|
1805
1811
|
onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
|
|
1806
1812
|
registerBridge(bridge, channelType) {
|
|
1807
|
-
bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, mentionAids, threadId, rootId, chatType, source }) => {
|
|
1808
|
-
const replyContext = peerId || threadId
|
|
1813
|
+
bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, mentionAids, threadId, rootId, chatType, replyContext: inboundReplyContext, source }) => {
|
|
1814
|
+
const replyContext = inboundReplyContext ?? (peerId || threadId
|
|
1809
1815
|
? {
|
|
1810
1816
|
...(peerId ? { peerId } : {}),
|
|
1811
1817
|
...(threadId ? { threadId, replyToMessageId: rootId ?? threadId, replyInThread: true } : {}),
|
|
1812
1818
|
}
|
|
1813
|
-
: undefined;
|
|
1819
|
+
: undefined);
|
|
1814
1820
|
await handler({
|
|
1815
1821
|
channel: adapter.channelName, channelType, channelId: chatId, content, images,
|
|
1816
|
-
selfAID: ctx.agentName, chatType: chatType || 'private',
|
|
1822
|
+
selfAID: ctx.agentName, chatType: chatType || replyContext?.delivery?.chatType || 'private',
|
|
1817
1823
|
peerId: peerId || '', peerName, messageId, mentions, mentionAids, threadId,
|
|
1818
1824
|
replyContext,
|
|
1819
1825
|
source,
|