botmux 3.16.0 → 3.16.1
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/.runtime-build-id +1 -1
- package/dist/core/dashboard-url.d.ts +10 -1
- package/dist/core/dashboard-url.d.ts.map +1 -1
- package/dist/core/dashboard-url.js +12 -2
- package/dist/core/dashboard-url.js.map +1 -1
- package/dist/core/plugins/mcp/gateway.d.ts +27 -3
- package/dist/core/plugins/mcp/gateway.d.ts.map +1 -1
- package/dist/core/plugins/mcp/gateway.js +272 -26
- package/dist/core/plugins/mcp/gateway.js.map +1 -1
- package/dist/core/plugins/mcp/host.d.ts +18 -0
- package/dist/core/plugins/mcp/host.d.ts.map +1 -1
- package/dist/core/plugins/mcp/host.js +80 -16
- package/dist/core/plugins/mcp/host.js.map +1 -1
- package/dist/core/plugins/mcp/launch-record.d.ts +14 -0
- package/dist/core/plugins/mcp/launch-record.d.ts.map +1 -0
- package/dist/core/plugins/mcp/launch-record.js +63 -0
- package/dist/core/plugins/mcp/launch-record.js.map +1 -0
- package/dist/core/worker-pool.d.ts +10 -2
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +30 -3
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/dashboard/control-csrf.d.ts.map +1 -1
- package/dist/dashboard/control-csrf.js +6 -1
- package/dist/dashboard/control-csrf.js.map +1 -1
- package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -1
- package/dist/dashboard/terminal-front-proxy.js +19 -1
- package/dist/dashboard/terminal-front-proxy.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +1 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +65 -4
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/platform/binding.d.ts +27 -0
- package/dist/platform/binding.d.ts.map +1 -1
- package/dist/platform/binding.js +40 -0
- package/dist/platform/binding.js.map +1 -1
- package/dist/services/bridge-turn-journal.d.ts +63 -0
- package/dist/services/bridge-turn-journal.d.ts.map +1 -0
- package/dist/services/bridge-turn-journal.js +149 -0
- package/dist/services/bridge-turn-journal.js.map +1 -0
- package/dist/services/bridge-turn-queue.d.ts +8 -1
- package/dist/services/bridge-turn-queue.d.ts.map +1 -1
- package/dist/services/bridge-turn-queue.js +2 -1
- package/dist/services/bridge-turn-queue.js.map +1 -1
- package/dist/worker.js +270 -19
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -34,7 +34,7 @@ import { logger } from '../../utils/logger.js';
|
|
|
34
34
|
import * as sessionStore from '../../services/session-store.js';
|
|
35
35
|
import { loadFrozenCards, saveFrozenCards } from '../../services/frozen-card-store.js';
|
|
36
36
|
import { resumeStartsFresh } from '../../services/resume-fresh-policy.js';
|
|
37
|
-
import { forkWorker, sendWorkerInput, sendWorkerSessionInput, closeSession as closeWorkerPoolSession, teardownAuthoritativePersistentBackingBeforeClose, scheduleCardPatch, parkStreamCard, clearUsageLimitState, cardUsageLimit, writableTerminalLinkFor, workerHasInitialized, sessionSupportsWebTerminal, readableTerminalUrlFor, resolvePrivateCardAudience, deliverWriteLinkCard, deliverEphemeralOrReply, CARD_POSTING_SENTINEL, requestSessionRestart, isSessionTransferring, getDaemonStreamingCardUsageSnapshot, withActiveSessionKeyLock } from '../../core/worker-pool.js';
|
|
37
|
+
import { forkWorker, sendWorkerInput, sendWorkerSessionInput, closeSession as closeWorkerPoolSession, teardownAuthoritativePersistentBackingBeforeClose, scheduleCardPatch, parkStreamCard, clearUsageLimitState, cardUsageLimit, writableTerminalLinkFor, workerHasInitialized, sessionSupportsWebTerminal, readableTerminalUrlFor, resolvePrivateCardAudience, deliverWriteLinkCard, deliverEphemeralOrReply, CARD_POSTING_SENTINEL, requestSessionRestart, isSessionTransferring, getDaemonStreamingCardUsageSnapshot, withActiveSessionKeyLock, buildStreamingCardJson } from '../../core/worker-pool.js';
|
|
38
38
|
import { getSessionWorkingDir, buildNewTopicCliInput, getAvailableBots, persistStreamCardState, resumeSession, rememberLastCliInput, ensureSessionWhiteboard } from '../../core/session-manager.js';
|
|
39
39
|
import { markInitialUserTurnPending } from '../../core/initial-user-turn.js';
|
|
40
40
|
import { publishAttentionPatch, announcePendingRepoSession } from '../../core/session-activity.js';
|
|
@@ -2176,9 +2176,22 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
2176
2176
|
// Build the closed card BEFORE closeWorkerPoolSession — it reads the
|
|
2177
2177
|
// live session's identity off `current`.
|
|
2178
2178
|
const card = buildClosedSessionCard(current, localeForBot(current.larkAppId));
|
|
2179
|
+
// The clicked card IS the live streaming card in the common in-thread
|
|
2180
|
+
// (non-private) case. When so, patch it in place via the callback return
|
|
2181
|
+
// below instead of sending a separate closed card — a stray extra card
|
|
2182
|
+
// (and its now-dead buttons) is what we're avoiding. Only when the
|
|
2183
|
+
// clicked message is that streaming card and we're not in private mode.
|
|
2184
|
+
const patchClickedCardInPlace = !!cardMessageId
|
|
2185
|
+
&& cardMessageId === current.streamCardId
|
|
2186
|
+
&& value?.visibility !== 'private'
|
|
2187
|
+
&& !botCfg.privateCard;
|
|
2179
2188
|
let closeResult;
|
|
2180
2189
|
try {
|
|
2181
|
-
|
|
2190
|
+
// Don't await the worker exiting — a busy CLI only dies at the ~7s
|
|
2191
|
+
// SIGKILL backstop, which blows past Lark's ~3s card-ACK window and
|
|
2192
|
+
// surfaces the client-side "code: 300000" toast. The logical close is
|
|
2193
|
+
// synchronous; the worker is killed in the background.
|
|
2194
|
+
closeResult = await closeWorkerPoolSession(targetSessionId, { awaitWorkerExit: false });
|
|
2182
2195
|
}
|
|
2183
2196
|
catch (err) {
|
|
2184
2197
|
logger.error(`[${tag(current)}] Refused close because backing teardown was not verified: ${err}`);
|
|
@@ -2200,7 +2213,7 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
2200
2213
|
residual: closeResult.residual,
|
|
2201
2214
|
};
|
|
2202
2215
|
}
|
|
2203
|
-
return { status: 'closed', current, botCfg, card };
|
|
2216
|
+
return { status: 'closed', current, botCfg, card, patchClickedCardInPlace };
|
|
2204
2217
|
});
|
|
2205
2218
|
if (!closed) {
|
|
2206
2219
|
return { toast: { type: 'warning', content: t('card.action.session_gone', undefined, localeForBot(larkAppId)) } };
|
|
@@ -2242,7 +2255,7 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
2242
2255
|
},
|
|
2243
2256
|
};
|
|
2244
2257
|
}
|
|
2245
|
-
const { current, botCfg, card } = closed;
|
|
2258
|
+
const { current, botCfg, card, patchClickedCardInPlace } = closed;
|
|
2246
2259
|
// The closed card carries session title / CLI name / workingDir / resume
|
|
2247
2260
|
// command. In private-card mode those must not leak to the group — send the
|
|
2248
2261
|
// closed card ephemeral to the same owner audience instead. No group
|
|
@@ -2258,6 +2271,14 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
2258
2271
|
logger.info(`[${tag(current)}] Closed via card button (private close card → ${audience.length} owner(s))`);
|
|
2259
2272
|
}
|
|
2260
2273
|
else {
|
|
2274
|
+
if (patchClickedCardInPlace) {
|
|
2275
|
+
// Return the closed card as the callback response → Lark patches the
|
|
2276
|
+
// just-clicked streaming card in place (no delete, no separate send,
|
|
2277
|
+
// no "code: 300000" race). The "等待输入" card becomes the closed card
|
|
2278
|
+
// with its now-dead buttons removed.
|
|
2279
|
+
logger.info(`[${tag(current)}] Closed via card button (in-place patch)`);
|
|
2280
|
+
return JSON.parse(card);
|
|
2281
|
+
}
|
|
2261
2282
|
await deliverEphemeralOrReply(current, operatorOpenId, card, 'interactive', () => sessionReply(rootId, card, 'interactive'));
|
|
2262
2283
|
logger.info(`[${tag(current)}] Closed via card button`);
|
|
2263
2284
|
}
|
|
@@ -2279,6 +2300,46 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
2279
2300
|
const resumeMsg = resumeStartsFresh(result.ds.session)
|
|
2280
2301
|
? t('card.action.resume_success_fresh', { cliName }, localeForBot(result.ds.larkAppId))
|
|
2281
2302
|
: t('card.action.resume_success', { cliName }, localeForBot(result.ds.larkAppId));
|
|
2303
|
+
// Restore the ORIGINAL live streaming card (🖥️ header + usage line +
|
|
2304
|
+
// 显示输出/终端/操作链接/关闭会话) as a WITHDRAW-then-REPOST (old closed card
|
|
2305
|
+
// recalled, fresh card posted at the thread bottom), AND send the
|
|
2306
|
+
// "✅ 会话已恢复…" text follow-up — both are wanted.
|
|
2307
|
+
// Ordering is load-bearing for two reasons:
|
|
2308
|
+
// 1) ACK the callback FIRST (bare `return` → empty ACK), THEN
|
|
2309
|
+
// post/delete in the background — deleting the just-clicked card
|
|
2310
|
+
// inside the callback response races it and triggers client
|
|
2311
|
+
// "code: 300000".
|
|
2312
|
+
// 2) POST the fresh card BEFORE deleting the old one, so the thread
|
|
2313
|
+
// never briefly shows zero cards (same invariant as park→recall).
|
|
2314
|
+
// Skip in private-card mode (clicked card may be an ephemeral snapshot).
|
|
2315
|
+
const botCfgResume = getBot(result.ds.larkAppId).config;
|
|
2316
|
+
if (cardMessageId && value?.visibility !== 'private' && !botCfgResume.privateCard) {
|
|
2317
|
+
const staleCardId = cardMessageId;
|
|
2318
|
+
const resumedDs = result.ds;
|
|
2319
|
+
void (async () => {
|
|
2320
|
+
try {
|
|
2321
|
+
const freshCardId = await sessionReply(rootId, buildStreamingCardJson(resumedDs), 'interactive');
|
|
2322
|
+
resumedDs.streamCardId = freshCardId;
|
|
2323
|
+
persistStreamCardState(resumedDs);
|
|
2324
|
+
await deleteMessage(resumedDs.larkAppId, staleCardId).catch(() => { });
|
|
2325
|
+
// Also send the "✅ 会话已恢复…" text follow-up (the original resume
|
|
2326
|
+
// behavior). Both are wanted: the live streaming card AND the text
|
|
2327
|
+
// prompt telling the user to send a message to continue.
|
|
2328
|
+
await deliverEphemeralOrReply(resumedDs, operatorOpenId, resumeMsg, 'text', () => sessionReply(rootId, resumeMsg));
|
|
2329
|
+
logger.info(`[${targetSessionId.substring(0, 8)}] Resumed via card button (withdraw + repost streaming card + text)`);
|
|
2330
|
+
}
|
|
2331
|
+
catch (err) {
|
|
2332
|
+
logger.warn(`[${targetSessionId.substring(0, 8)}] resume card repost failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2333
|
+
}
|
|
2334
|
+
})();
|
|
2335
|
+
// Bare `return` (→ undefined) so the dispatcher's shaper emits a
|
|
2336
|
+
// genuine empty ACK `{}`. Returning `{}` here would instead be
|
|
2337
|
+
// truthy and get wrapped as `{card:{type:raw,data:{}}}` — an
|
|
2338
|
+
// in-place patch with an empty card body (invalid), racing the
|
|
2339
|
+
// background deleteMessage above. Matches every other empty-ACK in
|
|
2340
|
+
// this handler.
|
|
2341
|
+
return; // fast empty ACK; card work happens in background
|
|
2342
|
+
}
|
|
2282
2343
|
await deliverEphemeralOrReply(result.ds, operatorOpenId, resumeMsg, 'text', () => sessionReply(rootId, resumeMsg));
|
|
2283
2344
|
logger.info(`[${targetSessionId.substring(0, 8)}] Resumed via card button`);
|
|
2284
2345
|
}
|