evolcore 0.0.23 → 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 +50 -0
- package/README.md +3 -1
- package/bin/codex-managed-acl.ps1 +7 -2
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +17 -6
- package/dist/agents/baseagent-availability.js +4 -4
- package/dist/agents/baseagent.js +29 -1
- package/dist/agents/claude-runner.js +148 -13
- package/dist/agents/codex/app-server-client.js +18 -8
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +215 -0
- package/dist/agents/codex-runner.js +438 -59
- package/dist/agents/ecagent-runner.js +17 -3
- package/dist/agents/gemini-runner.js +60 -2
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +41 -0
- package/dist/aun/msg/managed-operation.js +6 -1
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +4 -5
- package/dist/channels/aun.js +147 -76
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- 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 +60 -20
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +148 -17
- package/dist/cli/index.js +2 -2
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/config/builtin-roles.js +2 -0
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +72 -19
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +10 -2
- package/dist/core/auth/remote-access-store.js +3 -2
- 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 +87 -25
- package/dist/core/command/menu-catalog.js +3 -1
- package/dist/core/command/menu-handler.js +274 -70
- package/dist/core/command/menu-protocol.js +6 -2
- package/dist/core/command/slash-gate.js +1 -3
- package/dist/core/command/slash-handler.js +157 -48
- 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-utils.js +6 -3
- package/dist/core/message/response-engine.js +73 -21
- package/dist/core/model/catalog-service.js +3 -1
- package/dist/core/model/model-catalog.js +10 -0
- package/dist/core/model/model-permission.js +4 -1
- package/dist/core/permission/approval-gateway.js +169 -33
- package/dist/core/permission/tool-policy.js +13 -6
- 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 +174 -28
- package/dist/ipc.js +68 -21
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/error-utils.js +11 -4
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +24 -1
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/templates/roles/admin.json +2 -0
- package/package.json +7 -3
- package/AGENTS.md +0 -62
- 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';
|
|
@@ -729,7 +728,10 @@ export class AUNChannel {
|
|
|
729
728
|
const actionValue = isComposeReply ? '_custom_input'
|
|
730
729
|
: typeof obj.value === 'string' ? obj.value
|
|
731
730
|
: typeof obj.action_value === 'string' ? obj.action_value : text;
|
|
732
|
-
|
|
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);
|
|
733
735
|
// 卡片点击者身份:只信认证信封(senderAid 参数,由调用方从 msg.from / msg.sender_aid 提取)。
|
|
734
736
|
// payload 自报字段(from / sender_aid / user_id)不可信,可被客户端伪造,不读取。
|
|
735
737
|
// 两类卡片共用:CommandCard → 伪入站消息的 peerId,ActionInteraction → operatorId。
|
|
@@ -771,6 +773,10 @@ export class AUNChannel {
|
|
|
771
773
|
peerId: cardClickerAid,
|
|
772
774
|
peerName: typeof obj.label === 'string' ? obj.label : typeof obj.action_label === 'string' ? obj.action_label : undefined,
|
|
773
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),
|
|
774
780
|
source: 'card-trigger',
|
|
775
781
|
})).catch((err) => {
|
|
776
782
|
logger.error(`${this.logPrefix()} CommandCard handler failed: action=${actionValue} mid=${cardMsgId}`, err);
|
|
@@ -1466,12 +1472,13 @@ export class AUNChannel {
|
|
|
1466
1472
|
this.store = store;
|
|
1467
1473
|
const client = await loadClient(store, aidName);
|
|
1468
1474
|
enableFullGroupPullPagination(client);
|
|
1475
|
+
enableReliableMessageDelivery(client);
|
|
1469
1476
|
this.client = client;
|
|
1470
1477
|
// A reconnect replaces the SDK client. Late events from the retired
|
|
1471
1478
|
// instance must not mutate the new instance's health or message state.
|
|
1472
1479
|
const isCurrentClient = () => this.client === client;
|
|
1473
1480
|
// Register event handlers before connecting
|
|
1474
|
-
client.on('message.received', (data) => {
|
|
1481
|
+
client.on('message.received', async (data) => {
|
|
1475
1482
|
if (!isCurrentClient())
|
|
1476
1483
|
return;
|
|
1477
1484
|
this.aidState.lastInboundAt = Date.now();
|
|
@@ -1480,11 +1487,11 @@ export class AUNChannel {
|
|
|
1480
1487
|
const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
|
|
1481
1488
|
const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
|
|
1482
1489
|
logger.debug(`${this.logPrefix()}[DIAG] message.received: kind=${kind} keys=${keys}`);
|
|
1483
|
-
this.handleIncomingPrivateMessage(data);
|
|
1490
|
+
await this.handleIncomingPrivateMessage(data);
|
|
1484
1491
|
});
|
|
1485
1492
|
// pureIdentity(控制 AID):协议层不接群消息,不注册 group 创建监听
|
|
1486
1493
|
if (!this.config.pureIdentity) {
|
|
1487
|
-
client.on('group.message_created', (data) => {
|
|
1494
|
+
client.on('group.message_created', async (data) => {
|
|
1488
1495
|
if (!isCurrentClient())
|
|
1489
1496
|
return;
|
|
1490
1497
|
this.aidState.lastInboundAt = Date.now();
|
|
@@ -1494,7 +1501,7 @@ export class AUNChannel {
|
|
|
1494
1501
|
const gid = env.group_id ?? '';
|
|
1495
1502
|
const sender = env.from ?? '';
|
|
1496
1503
|
logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
|
|
1497
|
-
this.handleIncomingGroupMessage(data);
|
|
1504
|
+
await this.handleIncomingGroupMessage(data);
|
|
1498
1505
|
});
|
|
1499
1506
|
client.on('group.changed', (data) => {
|
|
1500
1507
|
if (!isCurrentClient())
|
|
@@ -1659,6 +1666,14 @@ export class AUNChannel {
|
|
|
1659
1666
|
this.connected = true;
|
|
1660
1667
|
this.connectedAt = Date.now();
|
|
1661
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);
|
|
1662
1677
|
// Workaround: SDK e2ee uses _identity.cert for sender_cert_fingerprint;
|
|
1663
1678
|
// if cert is missing, it falls back to public key SPKI fingerprint which
|
|
1664
1679
|
// causes peer cert lookup failures. Backfill from keystore if needed.
|
|
@@ -1691,51 +1706,50 @@ export class AUNChannel {
|
|
|
1691
1706
|
logger.warn(`${this.logPrefix()} agent config not found for ${aidName}, skipping welcome message`);
|
|
1692
1707
|
return false;
|
|
1693
1708
|
}
|
|
1694
|
-
const
|
|
1695
|
-
if (
|
|
1709
|
+
const owners = Array.from(new Set(agentConfig.owners ?? []));
|
|
1710
|
+
if (owners.length === 0) {
|
|
1696
1711
|
logger.info(`${this.logPrefix()} No owner configured, skipping post-bootstrap welcome message`);
|
|
1697
1712
|
return false;
|
|
1698
1713
|
}
|
|
1699
|
-
|
|
1700
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aidName);
|
|
1701
|
-
if (hasMessageLogOperation(chatDir, operationId))
|
|
1702
|
-
return true;
|
|
1703
|
-
const prepared = outbox.findByDedupeKey(aidName, operationId);
|
|
1714
|
+
this.repairBootstrapRoutes();
|
|
1704
1715
|
// Bootstrap welcomes target the configured personal Owner AID. They are
|
|
1705
1716
|
// not replies to an inbound group message, so their route is always
|
|
1706
1717
|
// private and must not be inferred with group.get_info.
|
|
1707
1718
|
const lifecycle = resolveAgentLifecycle(agentConfig);
|
|
1708
1719
|
const delivery = { chatType: 'private' };
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
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
|
+
}
|
|
1728
1746
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
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}`);
|
|
1732
1749
|
}
|
|
1750
|
+
preparePostBootstrapWelcomeOutbox(aidName, owner, ownerDisplayName, delivery);
|
|
1733
1751
|
}
|
|
1734
|
-
|
|
1735
|
-
logger.warn(`${this.logPrefix()} Failed to resolve owner display name; using AID label: ${e}`);
|
|
1736
|
-
}
|
|
1737
|
-
preparePostBootstrapWelcomeOutbox(aidName, owner, ownerDisplayName, delivery);
|
|
1738
|
-
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)`);
|
|
1739
1753
|
return true;
|
|
1740
1754
|
}
|
|
1741
1755
|
catch (e) {
|
|
@@ -1753,9 +1767,48 @@ export class AUNChannel {
|
|
|
1753
1767
|
const aid = this.config.aid.replace(/^@/, '');
|
|
1754
1768
|
return hasPendingPostBootstrapWelcomeOutbox(aid);
|
|
1755
1769
|
}
|
|
1756
|
-
async bindPostBootstrapWelcomeToSession(sessionId) {
|
|
1770
|
+
async bindPostBootstrapWelcomeToSession(sessionId, owner) {
|
|
1757
1771
|
const aid = this.config.aid.replace(/^@/, '');
|
|
1758
|
-
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);
|
|
1759
1812
|
}
|
|
1760
1813
|
/**
|
|
1761
1814
|
* Resume delivery of an already prepared completion welcome.
|
|
@@ -1768,20 +1821,9 @@ export class AUNChannel {
|
|
|
1768
1821
|
this.repairBootstrapRoutes();
|
|
1769
1822
|
const configuredAid = this.config.aid;
|
|
1770
1823
|
const aid = configuredAid.startsWith('@') ? configuredAid.slice(1) : configuredAid;
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1773
|
-
if (!entry) {
|
|
1774
|
-
const owner = getFirstStaticAgentOwner(aid);
|
|
1775
|
-
if (!owner)
|
|
1776
|
-
return false;
|
|
1777
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aid);
|
|
1778
|
-
return hasMessageLogOperation(chatDir, operationId);
|
|
1779
|
-
}
|
|
1780
|
-
if (!outbox.isDeliveryForChannel(entry.delivery, entry.channelId)) {
|
|
1781
|
-
logger.warn(`${this.logPrefix()} Discarding unrouted legacy post-bootstrap welcome: id=${entry.id}`);
|
|
1782
|
-
outbox.remove(aid, entry.id);
|
|
1824
|
+
const entries = findPostBootstrapWelcomeOutbox(aid);
|
|
1825
|
+
if (entries.length === 0)
|
|
1783
1826
|
return false;
|
|
1784
|
-
}
|
|
1785
1827
|
const agentConfig = loadAgent(aid);
|
|
1786
1828
|
if (!agentConfig || resolveAgentLifecycle(agentConfig) !== 'active') {
|
|
1787
1829
|
logger.info(`${this.logPrefix()} Post-bootstrap welcome prepared; waiting for lifecycle=active`);
|
|
@@ -1789,11 +1831,15 @@ export class AUNChannel {
|
|
|
1789
1831
|
}
|
|
1790
1832
|
if (!this.connected || !this.client)
|
|
1791
1833
|
return true;
|
|
1792
|
-
const
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
+
}
|
|
1797
1843
|
return true;
|
|
1798
1844
|
}
|
|
1799
1845
|
// ── Event handlers ──────────────────────────────────────────
|
|
@@ -3553,7 +3599,7 @@ export class AUNChannel {
|
|
|
3553
3599
|
if (ids && ids.size === 0)
|
|
3554
3600
|
this.interactionCardMessageIds.delete(interactionId);
|
|
3555
3601
|
}
|
|
3556
|
-
registerCardPostSend(messageId, action, channelId, delivery) {
|
|
3602
|
+
registerCardPostSend(messageId, action, channelId, delivery, replyContext) {
|
|
3557
3603
|
if (action.type !== 'register_interaction_card')
|
|
3558
3604
|
return;
|
|
3559
3605
|
this.cardMessageIdMap.set(messageId, {
|
|
@@ -3562,6 +3608,7 @@ export class AUNChannel {
|
|
|
3562
3608
|
initiatorAid: action.initiatorAid,
|
|
3563
3609
|
channelId,
|
|
3564
3610
|
delivery: delivery ?? action.delivery,
|
|
3611
|
+
replyContext,
|
|
3565
3612
|
});
|
|
3566
3613
|
this.ownedCardMsgIds.add(messageId);
|
|
3567
3614
|
let messageIds = this.interactionCardMessageIds.get(action.requestId);
|
|
@@ -3594,7 +3641,7 @@ export class AUNChannel {
|
|
|
3594
3641
|
if (!entry.postSend)
|
|
3595
3642
|
return;
|
|
3596
3643
|
if (entry.postSend.type === 'register_interaction_card') {
|
|
3597
|
-
this.registerCardPostSend(messageId, entry.postSend, entry.channelId, entry.delivery);
|
|
3644
|
+
this.registerCardPostSend(messageId, entry.postSend, entry.channelId, entry.delivery, entry.context);
|
|
3598
3645
|
}
|
|
3599
3646
|
}
|
|
3600
3647
|
async sendAunPayload(channelId, payload, context, label) {
|
|
@@ -3923,6 +3970,9 @@ export class AUNChannel {
|
|
|
3923
3970
|
}
|
|
3924
3971
|
async sendReliableStructured(channelId, payload, context, logText) {
|
|
3925
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,
|
|
3926
3976
|
contentKind: 'custom',
|
|
3927
3977
|
context,
|
|
3928
3978
|
logText: logText ?? this.payloadLogText(payload, 'custom'),
|
|
@@ -3933,6 +3983,7 @@ export class AUNChannel {
|
|
|
3933
3983
|
outboxId: result.outboxId,
|
|
3934
3984
|
});
|
|
3935
3985
|
}
|
|
3986
|
+
return result;
|
|
3936
3987
|
}
|
|
3937
3988
|
async sendMessage(channelId, text, context) {
|
|
3938
3989
|
if (!text?.trim()) {
|
|
@@ -4344,7 +4395,7 @@ export class AUNChannel {
|
|
|
4344
4395
|
logger.info(`${this.logPrefix()} Durable operation already logged; skipping duplicate send: ${operationId}`);
|
|
4345
4396
|
return sentOutboxResult(completedMessageId);
|
|
4346
4397
|
}
|
|
4347
|
-
if (operationId
|
|
4398
|
+
if (isPostBootstrapWelcomeOperationId(operationId, this.config.aid)) {
|
|
4348
4399
|
const agentConfig = loadAgent(this.config.aid);
|
|
4349
4400
|
if (!agentConfig || resolveAgentLifecycle(agentConfig) !== 'active') {
|
|
4350
4401
|
return { status: 'retry', error: 'post-bootstrap welcome is waiting for active lifecycle', code: 'BOOTSTRAP_NOT_ACTIVE' };
|
|
@@ -5317,15 +5368,19 @@ export class AUNChannel {
|
|
|
5317
5368
|
/** Repair bootstrap outbox entries to the configured personal Owner route. */
|
|
5318
5369
|
repairBootstrapRoutes() {
|
|
5319
5370
|
const aid = this.config.aid.replace(/^@/, '');
|
|
5320
|
-
const
|
|
5321
|
-
if (!owner)
|
|
5322
|
-
return;
|
|
5323
|
-
const completionEntry = outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid), { includeTerminal: true });
|
|
5371
|
+
const owners = Array.from(new Set(loadAgent(aid)?.owners ?? []));
|
|
5324
5372
|
const entries = [
|
|
5325
5373
|
...outbox.findByDedupePrefix(aid, bootstrapInitialMessageOperationPrefix(aid), { includeTerminal: true }),
|
|
5326
|
-
...(
|
|
5374
|
+
...findPostBootstrapWelcomeOutbox(aid, { includeTerminal: true }),
|
|
5327
5375
|
].filter((entry) => !!entry?.channelId);
|
|
5328
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;
|
|
5329
5384
|
const delivery = { chatType: 'private' };
|
|
5330
5385
|
const nestedDelivery = entry.context?.delivery;
|
|
5331
5386
|
const routeAlreadyPrivate = entry.channelId === owner
|
|
@@ -5387,6 +5442,7 @@ export class AUNChannel {
|
|
|
5387
5442
|
timeout: 'timeout',
|
|
5388
5443
|
queued: 'queued',
|
|
5389
5444
|
progress: 'progress',
|
|
5445
|
+
notice: 'notice',
|
|
5390
5446
|
requires_action: 'requires_action',
|
|
5391
5447
|
};
|
|
5392
5448
|
const statusPayload = {
|
|
@@ -5446,6 +5502,16 @@ export class AUNChannel {
|
|
|
5446
5502
|
logger.info(logTaskStatus);
|
|
5447
5503
|
}
|
|
5448
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
|
+
}
|
|
5449
5515
|
// SDK 0.3.0 E2EE requires payload to be an object
|
|
5450
5516
|
let payloadObj;
|
|
5451
5517
|
try {
|
|
@@ -5836,12 +5902,14 @@ export class AUNChannelPlugin {
|
|
|
5836
5902
|
await channel.sendThought(channelId, envelope.taskId, aunPayload, replyCtx);
|
|
5837
5903
|
}
|
|
5838
5904
|
else {
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
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;
|
|
5845
5913
|
if (result.status === 'permanent' || result.status === 'failed')
|
|
5846
5914
|
failedResult ??= result;
|
|
5847
5915
|
else if (result.status === 'retry' || result.status === 'queued' || result.queued)
|
|
@@ -5885,6 +5953,9 @@ export class AUNChannelPlugin {
|
|
|
5885
5953
|
case 'status.progress':
|
|
5886
5954
|
channel.sendProcessingStatus(channelId, 'progress', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
5887
5955
|
return;
|
|
5956
|
+
case 'status.notice':
|
|
5957
|
+
channel.sendProcessingStatus(channelId, 'notice', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
5958
|
+
return;
|
|
5888
5959
|
case 'status.requires_action':
|
|
5889
5960
|
channel.sendProcessingStatus(channelId, 'requires_action', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
5890
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,
|
package/dist/channels/wecom.js
CHANGED
|
@@ -1461,7 +1461,8 @@ export class WecomChannelPlugin {
|
|
|
1461
1461
|
return;
|
|
1462
1462
|
case 'status.started':
|
|
1463
1463
|
case 'status.queued':
|
|
1464
|
-
case 'status.progress':
|
|
1464
|
+
case 'status.progress':
|
|
1465
|
+
case 'status.notice': return;
|
|
1465
1466
|
case 'status.requires_action':
|
|
1466
1467
|
await channel.sendMessage(channelId, '等待 owner 审批', envelope.replyContext);
|
|
1467
1468
|
return;
|
|
@@ -69,6 +69,7 @@ Options:
|
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
const { agentList, agentShow, agentCreateInteractive, agentCreateNonInteractive, agentReload, agentEnable, agentDisable, agentGet, agentSet, agentRename, agentDelete, agentReady, runManagedAgentOperation, } = await import('./agent.js');
|
|
72
|
+
const { parseAgentOwnerArgs, resolveAgentCreatePlan, validateAgentCreatePlan } = await import('./agent-create.js');
|
|
72
73
|
if (process.env.EVOLCORE_SESSION_ID && !wantsHelp(args) && !new Set(['get', 'set', 'ready']).has(sub)) {
|
|
73
74
|
if (sub === 'new' && !args.includes('--non-interactive')) {
|
|
74
75
|
const error = {
|
|
@@ -210,6 +211,8 @@ Options:
|
|
|
210
211
|
--baseagent <claude|codex|gemini|ecagent>
|
|
211
212
|
默认: defaults 配置,否则检测本机可用基座
|
|
212
213
|
--owner <aid> 默认: daemon.json 第一个 owner
|
|
214
|
+
--owners <json-array> 多个 Owner;与 --owner 互斥
|
|
215
|
+
--handoff <context> 当前任务的 Bootstrap 上下文
|
|
213
216
|
--name <display-name> 默认: package.json name,否则目录名
|
|
214
217
|
--description <text> 默认: package.json description,否则使用 EvolCore 默认描述
|
|
215
218
|
--dry-run 只预览最终参数,不注册 AID、不写文件
|
|
@@ -223,6 +226,15 @@ Options:
|
|
|
223
226
|
ec agent new mybot.agentid.pub --non-interactive --project ../project --baseagent claude`);
|
|
224
227
|
return;
|
|
225
228
|
}
|
|
229
|
+
if (args.includes('--handoff')) {
|
|
230
|
+
const result = { ok: false, code: 'MANAGED_ONLY', error: '--handoff requires a managed Agent task' };
|
|
231
|
+
if (formatJson)
|
|
232
|
+
console.log(JSON.stringify(result));
|
|
233
|
+
else
|
|
234
|
+
console.error(result.error);
|
|
235
|
+
process.exitCode = 1;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
226
238
|
const name = args[1];
|
|
227
239
|
const nonInteractive = args.includes('--non-interactive');
|
|
228
240
|
if (nonInteractive) {
|
|
@@ -234,12 +246,25 @@ Options:
|
|
|
234
246
|
const idx = args.indexOf(flag);
|
|
235
247
|
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
236
248
|
};
|
|
237
|
-
|
|
249
|
+
let ownerOptions;
|
|
250
|
+
try {
|
|
251
|
+
ownerOptions = parseAgentOwnerArgs(args);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
const result = { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
255
|
+
if (formatJson)
|
|
256
|
+
console.log(JSON.stringify(result));
|
|
257
|
+
else
|
|
258
|
+
console.error(`❌ ${result.error}`);
|
|
259
|
+
process.exitCode = 1;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
238
262
|
const plan = resolveAgentCreatePlan({
|
|
239
263
|
aid: name,
|
|
240
264
|
project: getArg('--project'),
|
|
241
265
|
baseagent: getArg('--baseagent'),
|
|
242
|
-
|
|
266
|
+
...ownerOptions,
|
|
267
|
+
handoff: getArg('--handoff'),
|
|
243
268
|
name: getArg('--name'),
|
|
244
269
|
description: getArg('--description'),
|
|
245
270
|
force: args.includes('--force'),
|
|
@@ -263,7 +288,7 @@ Options:
|
|
|
263
288
|
console.log(` AID: ${plan.aid}`);
|
|
264
289
|
console.log(` Project: ${plan.project}`);
|
|
265
290
|
console.log(` Baseagent: ${plan.baseagent} (${plan.baseagentSource})`);
|
|
266
|
-
console.log(` Owner: ${plan.owner || '未设置'} (${plan.ownerSource})`);
|
|
291
|
+
console.log(` Owner: ${plan.owners?.join(', ') || plan.owner || '未设置'} (${plan.ownerSource})`);
|
|
267
292
|
console.log(` Name: ${plan.name} (${plan.nameSource})`);
|
|
268
293
|
if (plan.description)
|
|
269
294
|
console.log(` Description: ${plan.description} (${plan.descriptionSource})`);
|
|
@@ -276,6 +301,7 @@ Options:
|
|
|
276
301
|
baseagent: plan.baseagent,
|
|
277
302
|
project: plan.project,
|
|
278
303
|
owner: plan.owner,
|
|
304
|
+
owners: plan.owners,
|
|
279
305
|
name: plan.name,
|
|
280
306
|
description: plan.description,
|
|
281
307
|
force: args.includes('--force'),
|