botmux 3.0.0 → 3.1.0
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/dist/adapters/backend/herdr-backend.d.ts +9 -7
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +63 -53
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/riff-backend.d.ts +6 -1
- package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
- package/dist/adapters/backend/riff-backend.js +7 -2
- package/dist/adapters/backend/riff-backend.js.map +1 -1
- package/dist/adapters/backend/sandbox.d.ts +20 -16
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +192 -65
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +39 -2
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +55 -5
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +2 -2
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +9 -3
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts +25 -0
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +79 -9
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts +2 -1
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +2 -2
- package/dist/adapters/hook-installer.d.ts +11 -0
- package/dist/adapters/hook-installer.d.ts.map +1 -1
- package/dist/adapters/hook-installer.js +72 -11
- package/dist/adapters/hook-installer.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +211 -57
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +12 -5
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts +5 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +2 -0
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/doc-comment-prompt.d.ts +2 -0
- package/dist/core/doc-comment-prompt.d.ts.map +1 -1
- package/dist/core/doc-comment-prompt.js +30 -0
- package/dist/core/doc-comment-prompt.js.map +1 -1
- package/dist/core/github-auth.d.ts +8 -0
- package/dist/core/github-auth.d.ts.map +1 -0
- package/dist/core/github-auth.js +44 -0
- package/dist/core/github-auth.js.map +1 -0
- package/dist/core/managed-origin-capability.d.ts +7 -0
- package/dist/core/managed-origin-capability.d.ts.map +1 -1
- package/dist/core/managed-origin-capability.js +12 -0
- package/dist/core/managed-origin-capability.js.map +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +1 -1
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/restart-report.d.ts +7 -1
- package/dist/core/restart-report.d.ts.map +1 -1
- package/dist/core/restart-report.js +19 -5
- package/dist/core/restart-report.js.map +1 -1
- package/dist/core/session-activity.d.ts +8 -0
- package/dist/core/session-activity.d.ts.map +1 -1
- package/dist/core/session-activity.js +16 -0
- package/dist/core/session-activity.js.map +1 -1
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +4 -2
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/update-check.d.ts +11 -0
- package/dist/core/update-check.d.ts.map +1 -1
- package/dist/core/update-check.js +6 -1
- package/dist/core/update-check.js.map +1 -1
- package/dist/core/vc-meeting-prepare-command.d.ts +22 -0
- package/dist/core/vc-meeting-prepare-command.d.ts.map +1 -0
- package/dist/core/vc-meeting-prepare-command.js +50 -0
- package/dist/core/vc-meeting-prepare-command.js.map +1 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +11 -2
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +15 -5
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +555 -41
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +6 -1
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +34 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/preferences.d.ts +1 -1
- package/dist/dashboard/web/preferences.d.ts.map +1 -1
- package/dist/dashboard/web/preferences.js +1 -1
- package/dist/dashboard/web/preferences.js.map +1 -1
- package/dist/dashboard/web/sessions-page.d.ts +19 -0
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +66 -7
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +31 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +82 -0
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/{bot-defaults-page-MBORWWP7.js → bot-defaults-page-KLDCZNLP.js} +5 -5
- package/dist/dashboard-web/chunks/{chunk-G43RUPUN.js → chunk-EZAW2EPM.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-EIO5AWDA.js → chunk-FMJNOATE.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-4QTBR7UF.js → chunk-KEAOTUL3.js} +7 -7
- package/dist/dashboard-web/chunks/{chunk-7STGZ5BX.js → chunk-MLNI5BKE.js} +1 -1
- package/dist/dashboard-web/chunks/{connectors-page-LKDT4WBG.js → connectors-page-O32LFAL5.js} +1 -1
- package/dist/dashboard-web/chunks/{groups-page-XKIWJ2TP.js → groups-page-PSAFIZ6M.js} +1 -1
- package/dist/dashboard-web/chunks/{insights-page-CBEZCYKV.js → insights-page-V23LLATX.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-AQTHAWXA.js → monitor-room-7X5AKYOF.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-2R4NCNBQ.js → monitoring-page-CBA3CQYL.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-5M6MQIIL.js → office-page-RL6QSEM7.js} +1 -1
- package/dist/dashboard-web/chunks/{overview-page-N22HDZ2V.js → overview-page-XQIPO6KH.js} +1 -1
- package/dist/dashboard-web/chunks/{roles-page-J6K2HWFX.js → roles-page-ACIWV7QS.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-CGAQCB4J.js → schedules-page-KJDUKOJV.js} +1 -1
- package/dist/dashboard-web/chunks/sessions-page-2WDWHSP4.js +3 -0
- package/dist/dashboard-web/chunks/{settings-page-GLRWV5KU.js → settings-page-6VX4OUGG.js} +1 -1
- package/dist/dashboard-web/chunks/{skills-page-KQVW42QU.js → skills-page-WXE3UYLT.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-5PYUOV5M.js → team-federation-page-PYTF4FLM.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-DXPFILRZ.js → v3-page-G6CD6O3I.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-NUH6VZ4I.js → whiteboards-page-6SG33GP4.js} +1 -1
- package/dist/dashboard-web/style.css +167 -2
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +18 -3
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +18 -3
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +11 -2
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +4 -0
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +249 -5
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/forward-followup-buffer.d.ts +26 -0
- package/dist/im/lark/forward-followup-buffer.d.ts.map +1 -0
- package/dist/im/lark/forward-followup-buffer.js +48 -0
- package/dist/im/lark/forward-followup-buffer.js.map +1 -0
- package/dist/im/lark/forward-followup-content.d.ts +6 -0
- package/dist/im/lark/forward-followup-content.d.ts.map +1 -0
- package/dist/im/lark/forward-followup-content.js +46 -0
- package/dist/im/lark/forward-followup-content.js.map +1 -0
- package/dist/im/lark/forward-followup-store.d.ts +9 -0
- package/dist/im/lark/forward-followup-store.d.ts.map +1 -0
- package/dist/im/lark/forward-followup-store.js +48 -0
- package/dist/im/lark/forward-followup-store.js.map +1 -0
- package/dist/im/lark/message-parser.d.ts +1 -1
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +1 -2
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/platform/tunnel-client.js +3 -3
- package/dist/platform/tunnel-client.js.map +1 -1
- package/dist/services/local-cli-opener.d.ts +7 -0
- package/dist/services/local-cli-opener.d.ts.map +1 -1
- package/dist/services/local-cli-opener.js +111 -3
- package/dist/services/local-cli-opener.js.map +1 -1
- package/dist/services/skill-registry-store.d.ts.map +1 -1
- package/dist/services/skill-registry-store.js +15 -2
- package/dist/services/skill-registry-store.js.map +1 -1
- package/dist/services/vc-meeting-consumer-isolation.d.ts +7 -5
- package/dist/services/vc-meeting-consumer-isolation.d.ts.map +1 -1
- package/dist/services/vc-meeting-consumer-isolation.js +7 -5
- package/dist/services/vc-meeting-consumer-isolation.js.map +1 -1
- package/dist/services/vc-meeting-preparations-store.d.ts +22 -0
- package/dist/services/vc-meeting-preparations-store.d.ts.map +1 -0
- package/dist/services/vc-meeting-preparations-store.js +131 -0
- package/dist/services/vc-meeting-preparations-store.js.map +1 -0
- package/dist/services/vc-meeting-runtime-store.d.ts +9 -0
- package/dist/services/vc-meeting-runtime-store.d.ts.map +1 -1
- package/dist/services/vc-meeting-runtime-store.js +16 -0
- package/dist/services/vc-meeting-runtime-store.js.map +1 -1
- package/dist/setup/ensure-tmux.d.ts.map +1 -1
- package/dist/setup/ensure-tmux.js +9 -11
- package/dist/setup/ensure-tmux.js.map +1 -1
- package/dist/utils/child-env.d.ts +16 -8
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +30 -8
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/daemon-discovery.d.ts +8 -0
- package/dist/utils/daemon-discovery.d.ts.map +1 -1
- package/dist/utils/daemon-discovery.js +19 -0
- package/dist/utils/daemon-discovery.js.map +1 -1
- package/dist/utils/ready-gate.d.ts +9 -3
- package/dist/utils/ready-gate.d.ts.map +1 -1
- package/dist/utils/ready-gate.js +12 -4
- package/dist/utils/ready-gate.js.map +1 -1
- package/dist/worker.js +131 -28
- package/dist/worker.js.map +1 -1
- package/dist/workflows/ops-projection.d.ts.map +1 -1
- package/dist/workflows/ops-projection.js +27 -5
- package/dist/workflows/ops-projection.js.map +1 -1
- package/dist/workflows/v3/distillation-runner.d.ts +4 -0
- package/dist/workflows/v3/distillation-runner.d.ts.map +1 -1
- package/dist/workflows/v3/distillation-runner.js +4 -2
- package/dist/workflows/v3/distillation-runner.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/sessions-page-WJ2QYTIE.js +0 -3
|
@@ -34,6 +34,8 @@ import { buildGrantCard } from './card-builder.js';
|
|
|
34
34
|
import { openPending, isThrottled, clearPending } from './grant-pending.js';
|
|
35
35
|
import { localeForBot, t } from '../../i18n/index.js';
|
|
36
36
|
import { chatQuotaKey, globalQuotaKey } from '../../services/grant-store.js';
|
|
37
|
+
import { ForwardFollowupBuffer } from './forward-followup-buffer.js';
|
|
38
|
+
import { listForwardFollowups, putForwardFollowup, removeForwardFollowup } from './forward-followup-store.js';
|
|
37
39
|
import { claimMessageOnce, _resetCacheForTest as _resetSeenMessagesForTest } from '../../services/seen-message-store.js';
|
|
38
40
|
import { ensureDefaultOncallBound } from '../../services/oncall-store.js';
|
|
39
41
|
import { resolveRegularGroupMode, resolveGroupMentionMode } from '../../services/chat-reply-mode-store.js';
|
|
@@ -491,7 +493,7 @@ function claimEventOnce(key) {
|
|
|
491
493
|
function scheduleAckSafeEvent(key, work, label, claim = () => claimEventOnce(key)) {
|
|
492
494
|
if (!claim()) {
|
|
493
495
|
logger.info(`[event-dedupe] duplicate ${label} ignored: ${key}`);
|
|
494
|
-
return;
|
|
496
|
+
return false;
|
|
495
497
|
}
|
|
496
498
|
// INVARIANT: the claim above + this setImmediate scheduling must stay fully
|
|
497
499
|
// synchronous (no await before we get here). WS events arrive in order and
|
|
@@ -501,6 +503,7 @@ function scheduleAckSafeEvent(key, work, label, claim = () => claimEventOnce(key
|
|
|
501
503
|
setImmediate(() => {
|
|
502
504
|
void work().catch(err => logger.error(`Error handling ${label}: ${err}`));
|
|
503
505
|
});
|
|
506
|
+
return true;
|
|
504
507
|
}
|
|
505
508
|
// Fallback for an event that carries no stable id at all. Dedupe must never
|
|
506
509
|
// silently DROP a real message, so we hand back a unique key (process it, accept
|
|
@@ -1228,6 +1231,28 @@ export async function checkGroupMessageAccess(larkAppId, message, chatId, sender
|
|
|
1228
1231
|
}
|
|
1229
1232
|
return 'ignore';
|
|
1230
1233
|
}
|
|
1234
|
+
function usesForwardFollowupDelay(mentionMode) {
|
|
1235
|
+
return mentionMode === 'never' || mentionMode === 'ambient';
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Per-app barriers that resolve once `restoreActiveSessions()` completes.
|
|
1239
|
+
*
|
|
1240
|
+
* Persisted forward-followup seeds are loaded into the in-memory buffer at
|
|
1241
|
+
* dispatcher startup (so root-linked clarifications can still pair with them
|
|
1242
|
+
* while the WS is already receiving events), but any flush — timer expiry or
|
|
1243
|
+
* immediate dispatch — waits for this barrier. Without it, `isSessionOwner`
|
|
1244
|
+
* would always return false before sessions are rehydrated, routing thread
|
|
1245
|
+
* replies through `handleNewTopic` and potentially duplicating sessions.
|
|
1246
|
+
*/
|
|
1247
|
+
const sessionsReadyBarriers = new Map();
|
|
1248
|
+
const sessionsReadyApps = new Set();
|
|
1249
|
+
/** Mark a bot's active sessions as restored so pending forward-followup seeds can flush. */
|
|
1250
|
+
export function markForwardFollowupsSessionsReady(larkAppId) {
|
|
1251
|
+
sessionsReadyApps.add(larkAppId);
|
|
1252
|
+
const barrier = sessionsReadyBarriers.get(larkAppId);
|
|
1253
|
+
if (barrier)
|
|
1254
|
+
barrier.resolve();
|
|
1255
|
+
}
|
|
1231
1256
|
/**
|
|
1232
1257
|
* Best-effort plain-text extraction from a Lark message for routing-level
|
|
1233
1258
|
* decisions (currently: `/t` / `/topic` detection). Handles the two common
|
|
@@ -1664,6 +1689,80 @@ function createLarkWsAgent() {
|
|
|
1664
1689
|
* Returns the WSClient instance for lifecycle management.
|
|
1665
1690
|
*/
|
|
1666
1691
|
export function startLarkEventDispatcher(larkAppId, larkAppSecret, handlers, brand = 'feishu') {
|
|
1692
|
+
const forwardFollowups = new ForwardFollowupBuffer(config.daemon.forwardFollowupWaitMs, err => logger.error(`Error flushing delayed topic seed: ${err}`));
|
|
1693
|
+
// Barrier that resolves once restoreActiveSessions() completes. Flushes wait
|
|
1694
|
+
// on it so isSessionOwner reads the populated activeSessions map. If the app
|
|
1695
|
+
// was already marked ready (e.g. tests or a restart within the same process),
|
|
1696
|
+
// resolve immediately.
|
|
1697
|
+
let resolveSessionsReady;
|
|
1698
|
+
const sessionsReady = new Promise(resolve => { resolveSessionsReady = resolve; });
|
|
1699
|
+
if (sessionsReadyApps.has(larkAppId))
|
|
1700
|
+
resolveSessionsReady();
|
|
1701
|
+
sessionsReadyBarriers.set(larkAppId, { promise: sessionsReady, resolve: resolveSessionsReady });
|
|
1702
|
+
const dispatchHumanMessage = async (payload) => {
|
|
1703
|
+
await serializeByAnchor(payload.ctx.anchor, () => {
|
|
1704
|
+
const ownsSession = handlers.isSessionOwner?.(payload.ctx.anchor, larkAppId) ?? payload.ownsSession;
|
|
1705
|
+
return ownsSession
|
|
1706
|
+
? handlers.handleThreadReply(payload.data, payload.ctx)
|
|
1707
|
+
: handlers.handleNewTopic(payload.data, payload.ctx);
|
|
1708
|
+
});
|
|
1709
|
+
};
|
|
1710
|
+
const dispatchPersistedForwardFollowup = async (seedMessageId, payload) => {
|
|
1711
|
+
// Wait for activeSessions to be rehydrated before checking ownership, so a
|
|
1712
|
+
// thread reply isn't misrouted to handleNewTopic during daemon startup.
|
|
1713
|
+
await sessionsReady;
|
|
1714
|
+
await dispatchHumanMessage(payload);
|
|
1715
|
+
removeForwardFollowup(larkAppId, seedMessageId);
|
|
1716
|
+
};
|
|
1717
|
+
// Load persisted seeds into the buffer immediately so root-linked
|
|
1718
|
+
// clarifications can pair with them while the WS is already receiving
|
|
1719
|
+
// events. Flushes (timer expiry or immediate) wait on sessionsReady above.
|
|
1720
|
+
for (const record of listForwardFollowups(larkAppId)) {
|
|
1721
|
+
const senderOpenId = record.payload?.data?.sender?.sender_id?.open_id;
|
|
1722
|
+
const chatId = record.payload?.ctx?.chatId;
|
|
1723
|
+
if (!senderOpenId || !chatId || !record.payload?.ctx?.anchor) {
|
|
1724
|
+
removeForwardFollowup(larkAppId, record.messageId);
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
const flush = (payload) => dispatchPersistedForwardFollowup(record.messageId, payload);
|
|
1728
|
+
const remainingMs = record.dueAt - Date.now();
|
|
1729
|
+
const isUnpairedSeed = !record.payload.ctx.forwardSeedData;
|
|
1730
|
+
const delayStillEnabled = usesForwardFollowupDelay(resolveGroupMentionMode(larkAppId));
|
|
1731
|
+
if (isUnpairedSeed && delayStillEnabled && remainingMs > 0 && forwardFollowups.hold({
|
|
1732
|
+
larkAppId,
|
|
1733
|
+
chatId,
|
|
1734
|
+
senderOpenId,
|
|
1735
|
+
messageId: record.messageId,
|
|
1736
|
+
payload: record.payload,
|
|
1737
|
+
flush,
|
|
1738
|
+
}, remainingMs)) {
|
|
1739
|
+
logger.info(`[forward-followup] restored pending seed=${record.messageId.substring(0, 12)} remaining=${remainingMs}ms`);
|
|
1740
|
+
}
|
|
1741
|
+
else {
|
|
1742
|
+
void flush(record.payload).catch(err => logger.error(`Error restoring delayed topic seed: ${err}`));
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
const seedRoutingGates = new Map();
|
|
1746
|
+
const registerSeedRoutingGate = (messageId) => {
|
|
1747
|
+
let resolveReady;
|
|
1748
|
+
const ready = new Promise(resolve => { resolveReady = resolve; });
|
|
1749
|
+
let completed = false;
|
|
1750
|
+
const gate = {
|
|
1751
|
+
ready,
|
|
1752
|
+
complete: () => {
|
|
1753
|
+
if (completed)
|
|
1754
|
+
return;
|
|
1755
|
+
completed = true;
|
|
1756
|
+
resolveReady();
|
|
1757
|
+
setTimeout(() => {
|
|
1758
|
+
if (seedRoutingGates.get(messageId) === gate)
|
|
1759
|
+
seedRoutingGates.delete(messageId);
|
|
1760
|
+
}, config.daemon.forwardFollowupWaitMs);
|
|
1761
|
+
},
|
|
1762
|
+
};
|
|
1763
|
+
seedRoutingGates.set(messageId, gate);
|
|
1764
|
+
return gate;
|
|
1765
|
+
};
|
|
1667
1766
|
const eventDispatcher = new Lark.EventDispatcher({}).register({
|
|
1668
1767
|
// 主动开工 — 场景①: the bot was added to a chat. Hand off to the daemon,
|
|
1669
1768
|
// which gates on the autoStartOnGroupJoin toggle + allowedUser membership.
|
|
@@ -1711,7 +1810,8 @@ export function startLarkEventDispatcher(larkAppId, larkAppSecret, handlers, bra
|
|
|
1711
1810
|
const claim = messageIdForKey
|
|
1712
1811
|
? () => claimMessageOnce(larkAppId, messageIdForKey)
|
|
1713
1812
|
: () => claimEventOnce(eventKey);
|
|
1714
|
-
|
|
1813
|
+
let seedRoutingGate;
|
|
1814
|
+
const scheduled = scheduleAckSafeEvent(eventKey, async () => {
|
|
1715
1815
|
try {
|
|
1716
1816
|
const message = data.message;
|
|
1717
1817
|
const sender = data.sender;
|
|
@@ -2144,6 +2244,83 @@ export function startLarkEventDispatcher(larkAppId, larkAppSecret, handlers, bra
|
|
|
2144
2244
|
senderOpenId,
|
|
2145
2245
|
});
|
|
2146
2246
|
const summaryCommandTriggered = !!summaryCommandMatch && isAllowed;
|
|
2247
|
+
const routingText = extractMessageTextForRouting(message);
|
|
2248
|
+
const strippedRoutingText = routingText
|
|
2249
|
+
? stripLeadingMentions(routingText.trim(), message?.mentions ?? []).trim()
|
|
2250
|
+
: '';
|
|
2251
|
+
const isControlCommand = strippedRoutingText.startsWith('/');
|
|
2252
|
+
let pairedForwardSeed;
|
|
2253
|
+
// Require isAllowed before pairing: a root-linked clarification from a
|
|
2254
|
+
// sender who was /revoked within the grace window must not consume the
|
|
2255
|
+
// seed from the buffer or overwrite the durable paired record. The seed
|
|
2256
|
+
// stays in the buffer and flushes on its original timer. Paired seeds
|
|
2257
|
+
// only arise under never/ambient modes, where a legitimate merge always
|
|
2258
|
+
// requires isAllowed anyway.
|
|
2259
|
+
if (senderOpenId && isAllowed && message.root_id && !isControlCommand) {
|
|
2260
|
+
await seedRoutingGates.get(message.root_id)?.ready;
|
|
2261
|
+
const pairingInput = {
|
|
2262
|
+
larkAppId,
|
|
2263
|
+
chatId,
|
|
2264
|
+
senderOpenId,
|
|
2265
|
+
rootId: message.root_id,
|
|
2266
|
+
};
|
|
2267
|
+
const pairingMentionMode = resolveGroupMentionMode(larkAppId);
|
|
2268
|
+
const ambientRedirect = pairingMentionMode === 'ambient'
|
|
2269
|
+
&& !explicitlyMentionedThisBot
|
|
2270
|
+
&& mentionsAnotherMember(larkAppId, message);
|
|
2271
|
+
const pairingDelayEnabled = usesForwardFollowupDelay(pairingMentionMode);
|
|
2272
|
+
if (pairingDelayEnabled && !ambientRedirect) {
|
|
2273
|
+
pairedForwardSeed = forwardFollowups.take(pairingInput);
|
|
2274
|
+
}
|
|
2275
|
+
else if (!pairingDelayEnabled) {
|
|
2276
|
+
const stalePendingSeed = forwardFollowups.take(pairingInput);
|
|
2277
|
+
if (stalePendingSeed) {
|
|
2278
|
+
try {
|
|
2279
|
+
await dispatchPersistedForwardFollowup(stalePendingSeed.messageId, stalePendingSeed.payload);
|
|
2280
|
+
}
|
|
2281
|
+
catch (err) {
|
|
2282
|
+
logger.warn(`[forward-followup] failed to flush stale seed=${stalePendingSeed.messageId.substring(0, 12)}; ` +
|
|
2283
|
+
`continuing current msg=${messageId.substring(0, 12)}: ${err}`);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
if (pairedForwardSeed) {
|
|
2289
|
+
// The clarification becomes the visible Lark topic root. The earlier
|
|
2290
|
+
// forwarded seed is retained only as prompt context, so the bot never
|
|
2291
|
+
// emits a reply under the forwarding bubble itself.
|
|
2292
|
+
routing.scope = 'thread';
|
|
2293
|
+
routing.anchor = messageId;
|
|
2294
|
+
routingSource = 'topic-chat';
|
|
2295
|
+
replyRootId = undefined;
|
|
2296
|
+
ownsSession = false;
|
|
2297
|
+
try {
|
|
2298
|
+
putForwardFollowup(larkAppId, {
|
|
2299
|
+
messageId: pairedForwardSeed.messageId,
|
|
2300
|
+
dueAt: Date.now(),
|
|
2301
|
+
payload: {
|
|
2302
|
+
data,
|
|
2303
|
+
ctx: {
|
|
2304
|
+
...pairedForwardSeed.payload.ctx,
|
|
2305
|
+
chatId,
|
|
2306
|
+
messageId,
|
|
2307
|
+
chatType,
|
|
2308
|
+
larkAppId,
|
|
2309
|
+
scope: 'thread',
|
|
2310
|
+
anchor: messageId,
|
|
2311
|
+
replyRootId: undefined,
|
|
2312
|
+
forwardSeedData: pairedForwardSeed.payload.data,
|
|
2313
|
+
},
|
|
2314
|
+
ownsSession: false,
|
|
2315
|
+
},
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
catch (err) {
|
|
2319
|
+
logger.warn(`[forward-followup] failed to persist provisional paired payload: ${err}`);
|
|
2320
|
+
}
|
|
2321
|
+
logger.info(`[forward-followup] merged seed=${pairedForwardSeed.messageId.substring(0, 12)} ` +
|
|
2322
|
+
`into msg=${messageId.substring(0, 12)} chat=${chatId.substring(0, 12)}`);
|
|
2323
|
+
}
|
|
2147
2324
|
// Permission gating — same shape as before, just keyed on
|
|
2148
2325
|
// `ownsSession` (anchor-aware) instead of "rootId presence":
|
|
2149
2326
|
//
|
|
@@ -2186,6 +2363,9 @@ export function startLarkEventDispatcher(larkAppId, larkAppSecret, handlers, bra
|
|
|
2186
2363
|
// 默认 'always' 在多人群里必须 @,想要话题内免@续话就把 mentionMode 配成
|
|
2187
2364
|
// 'topic'(下方条款已同时覆盖话题群 thread 与普通群 shared topic),
|
|
2188
2365
|
// 'never'/'ambient' 亦按各自语义生效。1人1bot 的 solo 群仍走末条放行。
|
|
2366
|
+
// 注:pairedForwardSeed 仅在 never/ambient 模式下产生,且 ambient redirect
|
|
2367
|
+
// 已在配对前排除,故 isAllowed=true 时下方 never/ambient 条款必然放行;
|
|
2368
|
+
// 不在此单独加 clause,以免 isAllowed=false 时绕过权限检查。
|
|
2189
2369
|
const relax = (!!replyRootId && isAllowed)
|
|
2190
2370
|
|| (!!substituteTrigger && isAllowed)
|
|
2191
2371
|
|| (isAllowed && mentionMode === 'never')
|
|
@@ -2248,6 +2428,7 @@ export function startLarkEventDispatcher(larkAppId, larkAppSecret, handlers, bra
|
|
|
2248
2428
|
? { name: 'summary-command', chatKind: summaryCommandMatch.chatKind }
|
|
2249
2429
|
: undefined,
|
|
2250
2430
|
substituteTrigger,
|
|
2431
|
+
forwardSeedData: pairedForwardSeed?.payload.data,
|
|
2251
2432
|
};
|
|
2252
2433
|
if (explicitlyMentionedThisBot) {
|
|
2253
2434
|
const before = await handlers.beforeSessionTurn?.(data, ctx, { senderOpenId, explicitlyMentionedThisBot });
|
|
@@ -2257,19 +2438,82 @@ export function startLarkEventDispatcher(larkAppId, larkAppSecret, handlers, bra
|
|
|
2257
2438
|
ctx.anchor = before.anchorOverride;
|
|
2258
2439
|
ownsSession = handlers.isSessionOwner?.(ctx.anchor, larkAppId) ?? ownsSession;
|
|
2259
2440
|
}
|
|
2441
|
+
const payload = { data, ctx, ownsSession };
|
|
2442
|
+
const groupMentionMode = resolveGroupMentionMode(larkAppId);
|
|
2443
|
+
const shouldDelayTopicSeed = usesForwardFollowupDelay(groupMentionMode)
|
|
2444
|
+
&& !pairedForwardSeed
|
|
2445
|
+
&& !isControlCommand
|
|
2446
|
+
&& !!senderOpenId
|
|
2447
|
+
&& routingSource === 'topic-chat'
|
|
2448
|
+
&& ctx.scope === 'thread'
|
|
2449
|
+
&& ctx.anchor === messageId
|
|
2450
|
+
&& !ownsSession;
|
|
2451
|
+
if (shouldDelayTopicSeed) {
|
|
2452
|
+
try {
|
|
2453
|
+
putForwardFollowup(larkAppId, {
|
|
2454
|
+
messageId,
|
|
2455
|
+
dueAt: Date.now() + config.daemon.forwardFollowupWaitMs,
|
|
2456
|
+
payload,
|
|
2457
|
+
});
|
|
2458
|
+
if (forwardFollowups.hold({
|
|
2459
|
+
larkAppId,
|
|
2460
|
+
chatId,
|
|
2461
|
+
senderOpenId,
|
|
2462
|
+
messageId,
|
|
2463
|
+
payload,
|
|
2464
|
+
flush: delayedPayload => dispatchPersistedForwardFollowup(messageId, delayedPayload),
|
|
2465
|
+
})) {
|
|
2466
|
+
logger.debug(`[forward-followup] holding topic seed msg=${messageId.substring(0, 12)} ` +
|
|
2467
|
+
`for ${config.daemon.forwardFollowupWaitMs}ms`);
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
removeForwardFollowup(larkAppId, messageId);
|
|
2471
|
+
}
|
|
2472
|
+
catch (err) {
|
|
2473
|
+
logger.warn(`[forward-followup] persistence unavailable, dispatching immediately: ${err}`);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
if (pairedForwardSeed) {
|
|
2477
|
+
try {
|
|
2478
|
+
putForwardFollowup(larkAppId, {
|
|
2479
|
+
messageId: pairedForwardSeed.messageId,
|
|
2480
|
+
dueAt: Date.now(),
|
|
2481
|
+
payload,
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2484
|
+
catch (err) {
|
|
2485
|
+
logger.warn(`[forward-followup] failed to persist paired payload before dispatch: ${err}`);
|
|
2486
|
+
}
|
|
2487
|
+
await dispatchPersistedForwardFollowup(pairedForwardSeed.messageId, payload)
|
|
2488
|
+
.catch(err => logger.error(`Error handling paired message event: ${err}`));
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2260
2491
|
// Serialize per anchor so two messages to the same thread/chat are
|
|
2261
2492
|
// processed in arrival order — never concurrently. Without this a fast
|
|
2262
2493
|
// second message interleaves with the first's async session-spawn and is
|
|
2263
2494
|
// dropped (worker-not-ready → re-fork branch). See anchor-serializer.ts.
|
|
2264
|
-
await
|
|
2265
|
-
? handlers.handleThreadReply(data, ctx)
|
|
2266
|
-
: handlers.handleNewTopic(data, ctx))
|
|
2495
|
+
await dispatchHumanMessage(payload)
|
|
2267
2496
|
.catch(err => logger.error(`Error handling message event: ${err}`));
|
|
2268
2497
|
}
|
|
2269
2498
|
catch (err) {
|
|
2270
2499
|
logger.error(`Error handling message event: ${err}`);
|
|
2271
2500
|
}
|
|
2501
|
+
finally {
|
|
2502
|
+
seedRoutingGate?.complete();
|
|
2503
|
+
}
|
|
2272
2504
|
}, 'message event', claim);
|
|
2505
|
+
const rawMessage = data?.message;
|
|
2506
|
+
const rawSenderType = data?.sender?.sender_type;
|
|
2507
|
+
if (scheduled
|
|
2508
|
+
&& config.daemon.forwardFollowupWaitMs > 0
|
|
2509
|
+
&& rawMessage?.message_id
|
|
2510
|
+
&& rawMessage.chat_type !== 'p2p'
|
|
2511
|
+
&& !rawMessage.root_id
|
|
2512
|
+
&& !rawMessage.thread_id
|
|
2513
|
+
&& rawSenderType !== 'app'
|
|
2514
|
+
&& rawSenderType !== 'bot') {
|
|
2515
|
+
seedRoutingGate = registerSeedRoutingGate(rawMessage.message_id);
|
|
2516
|
+
}
|
|
2273
2517
|
},
|
|
2274
2518
|
});
|
|
2275
2519
|
// 诊断:包一层 invoke,记录长连接收到的**每一个**事件类型(含未注册的)。
|