botmux 2.64.0 → 2.65.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/README.en.md +1 -1
- package/README.md +3 -3
- package/dist/adapters/backend/herdr-backend.d.ts +8 -1
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +15 -2
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +17 -1
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +25 -4
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +14 -0
- package/dist/adapters/backend/types.d.ts.map +1 -1
- package/dist/adapters/backend/types.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.d.ts +12 -1
- package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +25 -8
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/bot-registry.d.ts +39 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +30 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/send-dispatch.d.ts +23 -0
- package/dist/cli/send-dispatch.d.ts.map +1 -0
- package/dist/cli/send-dispatch.js +23 -0
- package/dist/cli/send-dispatch.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +141 -58
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +8 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -6
- package/dist/config.js.map +1 -1
- package/dist/core/ask-broker.d.ts +33 -0
- package/dist/core/ask-broker.d.ts.map +1 -1
- package/dist/core/ask-broker.js +58 -0
- package/dist/core/ask-broker.js.map +1 -1
- package/dist/core/ask-hook/claude-code.d.ts.map +1 -1
- package/dist/core/ask-hook/claude-code.js +15 -9
- package/dist/core/ask-hook/claude-code.js.map +1 -1
- package/dist/core/ask-hook/codex.d.ts.map +1 -1
- package/dist/core/ask-hook/codex.js +2 -1
- package/dist/core/ask-hook/codex.js.map +1 -1
- package/dist/core/ask-hook/opencode.d.ts.map +1 -1
- package/dist/core/ask-hook/opencode.js +9 -6
- package/dist/core/ask-hook/opencode.js.map +1 -1
- package/dist/core/ask-hook/types.d.ts +3 -1
- package/dist/core/ask-hook/types.d.ts.map +1 -1
- package/dist/core/ask-types.d.ts +13 -6
- package/dist/core/ask-types.d.ts.map +1 -1
- package/dist/core/ask-types.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +255 -4
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/daemon-heartbeat.d.ts +15 -0
- package/dist/core/daemon-heartbeat.d.ts.map +1 -0
- package/dist/core/daemon-heartbeat.js +83 -0
- package/dist/core/daemon-heartbeat.js.map +1 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +80 -33
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dispatch.d.ts +1 -23
- package/dist/core/dispatch.d.ts.map +1 -1
- package/dist/core/dispatch.js +1 -17
- package/dist/core/dispatch.js.map +1 -1
- package/dist/core/idle-worker-sweeper.d.ts +13 -0
- package/dist/core/idle-worker-sweeper.d.ts.map +1 -0
- package/dist/core/idle-worker-sweeper.js +42 -0
- package/dist/core/idle-worker-sweeper.js.map +1 -0
- package/dist/core/maintenance-schedule.d.ts +34 -0
- package/dist/core/maintenance-schedule.d.ts.map +1 -0
- package/dist/core/maintenance-schedule.js +72 -0
- package/dist/core/maintenance-schedule.js.map +1 -0
- package/dist/core/maintenance.d.ts +43 -0
- package/dist/core/maintenance.d.ts.map +1 -0
- package/dist/core/maintenance.js +160 -0
- package/dist/core/maintenance.js.map +1 -0
- package/dist/core/reply-target.d.ts +23 -0
- package/dist/core/reply-target.d.ts.map +1 -0
- package/dist/core/reply-target.js +47 -0
- package/dist/core/reply-target.js.map +1 -0
- package/dist/core/restart-report.d.ts +49 -0
- package/dist/core/restart-report.d.ts.map +1 -0
- package/dist/core/restart-report.js +98 -0
- package/dist/core/restart-report.js.map +1 -0
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +20 -0
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/session-manager.d.ts +26 -10
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +104 -26
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-marker.d.ts +13 -0
- package/dist/core/session-marker.d.ts.map +1 -0
- package/dist/core/session-marker.js +55 -0
- package/dist/core/session-marker.js.map +1 -0
- package/dist/core/types.d.ts +20 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-budget.d.ts +19 -0
- package/dist/core/worker-budget.d.ts.map +1 -0
- package/dist/core/worker-budget.js +50 -0
- package/dist/core/worker-budget.js.map +1 -0
- package/dist/core/worker-pool.d.ts +5 -2
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +105 -12
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +243 -38
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +118 -0
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +44 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/settings.d.ts.map +1 -1
- package/dist/dashboard/web/settings.js +84 -13
- package/dist/dashboard/web/settings.js.map +1 -1
- package/dist/dashboard-web/app.js +568 -503
- package/dist/dashboard.js +87 -7
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +46 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +115 -0
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +72 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +72 -1
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/ask-card.d.ts.map +1 -1
- package/dist/im/lark/ask-card.js +15 -1
- package/dist/im/lark/ask-card.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +17 -0
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +146 -0
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +119 -4
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +3 -2
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +28 -2
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +7 -17
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +170 -50
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts +1 -0
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +1 -0
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/im/lark/reply-mode-command.d.ts +2 -0
- package/dist/im/lark/reply-mode-command.d.ts.map +1 -0
- package/dist/im/lark/reply-mode-command.js +102 -0
- package/dist/im/lark/reply-mode-command.js.map +1 -0
- package/dist/services/bot-config-store.d.ts +144 -0
- package/dist/services/bot-config-store.d.ts.map +1 -0
- package/dist/services/bot-config-store.js +241 -0
- package/dist/services/bot-config-store.js.map +1 -0
- package/dist/services/card-prefs-store.d.ts +5 -0
- package/dist/services/card-prefs-store.d.ts.map +1 -1
- package/dist/services/card-prefs-store.js +47 -0
- package/dist/services/card-prefs-store.js.map +1 -1
- package/dist/services/chat-reply-mode-store.d.ts +28 -0
- package/dist/services/chat-reply-mode-store.d.ts.map +1 -0
- package/dist/services/chat-reply-mode-store.js +115 -0
- package/dist/services/chat-reply-mode-store.js.map +1 -0
- package/dist/services/hook-runner.d.ts +43 -0
- package/dist/services/hook-runner.d.ts.map +1 -0
- package/dist/services/hook-runner.js +394 -0
- package/dist/services/hook-runner.js.map +1 -0
- package/dist/services/restart-intent-store.d.ts +26 -0
- package/dist/services/restart-intent-store.d.ts.map +1 -0
- package/dist/services/restart-intent-store.js +84 -0
- package/dist/services/restart-intent-store.js.map +1 -0
- package/dist/services/session-lifecycle-hooks.d.ts +10 -0
- package/dist/services/session-lifecycle-hooks.d.ts.map +1 -0
- package/dist/services/session-lifecycle-hooks.js +66 -0
- package/dist/services/session-lifecycle-hooks.js.map +1 -0
- package/dist/services/session-store.d.ts +6 -0
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +25 -0
- package/dist/services/session-store.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +28 -3
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/install-info.d.ts +13 -0
- package/dist/utils/install-info.d.ts.map +1 -0
- package/dist/utils/install-info.js +56 -0
- package/dist/utils/install-info.js.map +1 -0
- package/dist/utils/listen-with-probe.d.ts +26 -0
- package/dist/utils/listen-with-probe.d.ts.map +1 -0
- package/dist/utils/listen-with-probe.js +64 -0
- package/dist/utils/listen-with-probe.js.map +1 -0
- package/dist/utils/web-terminal-listen.d.ts +30 -0
- package/dist/utils/web-terminal-listen.d.ts.map +1 -0
- package/dist/utils/web-terminal-listen.js +81 -0
- package/dist/utils/web-terminal-listen.js.map +1 -0
- package/dist/worker.js +71 -44
- package/dist/worker.js.map +1 -1
- package/dist/workflows/definition.d.ts +30 -30
- package/dist/workflows/events/payloads.d.ts +4 -4
- package/dist/workflows/events/schema.d.ts +156 -156
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -31,6 +31,7 @@ import { baselineJsonlCursor } from './services/jsonl-cursor.js';
|
|
|
31
31
|
import { dirname } from 'node:path';
|
|
32
32
|
import { createServer as createHttpServer } from 'node:http';
|
|
33
33
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
34
|
+
import { listenWebTerminalWithFallback } from './utils/web-terminal-listen.js';
|
|
34
35
|
import { TerminalRenderer } from './utils/terminal-renderer.js';
|
|
35
36
|
import { DEFAULT_RENDER_COLS, DEFAULT_RENDER_ROWS, MAX_RENDER_COLS, MAX_RENDER_ROWS, MIN_RENDER_COLS, MIN_RENDER_ROWS, clamp, resolveRenderDimensions, } from './utils/render-dimensions.js';
|
|
36
37
|
import { createCliAdapterSync, locateOnPath } from './adapters/cli/registry.js';
|
|
@@ -107,6 +108,17 @@ const pendingMessages = [];
|
|
|
107
108
|
/** Alternate submit-confirmation signals. Some CLIs can consume PTY input and
|
|
108
109
|
* start work before their history/transcript submit marker is observable. */
|
|
109
110
|
let lastPtyActivityAtMs = 0;
|
|
111
|
+
let currentBotmuxTurnId;
|
|
112
|
+
function writeCliPidMarker() {
|
|
113
|
+
if (!cliPidMarker || !sessionId)
|
|
114
|
+
return;
|
|
115
|
+
try {
|
|
116
|
+
writeFileSync(cliPidMarker, JSON.stringify({ sessionId, turnId: currentBotmuxTurnId ?? null }));
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
log(`Failed to update CLI PID marker: ${err?.message ?? err}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
110
122
|
let lastStructuredBridgeActivityAtMs = 0;
|
|
111
123
|
// Per-turn usage-limit state machine. Owns the turn counter plus the
|
|
112
124
|
// "did this turn hit a limit" / "suppress a stale retry-ready banner" flags, so
|
|
@@ -389,6 +401,7 @@ function maybeEmitAdoptPreamble(events) {
|
|
|
389
401
|
bridgePreambleSent = true;
|
|
390
402
|
send({
|
|
391
403
|
type: 'adopt_preamble',
|
|
404
|
+
turnId: currentBotmuxTurnId,
|
|
392
405
|
userText: truncatePreambleText(turn.userText, PREAMBLE_USER_MAX),
|
|
393
406
|
assistantText: truncatePreambleText(turn.assistantText, PREAMBLE_ASSISTANT_MAX),
|
|
394
407
|
});
|
|
@@ -413,6 +426,7 @@ function maybeEmitCodexAdoptPreamble(history) {
|
|
|
413
426
|
codexBridgePreambleSent = true;
|
|
414
427
|
send({
|
|
415
428
|
type: 'adopt_preamble',
|
|
429
|
+
turnId: currentBotmuxTurnId,
|
|
416
430
|
userText: truncatePreambleText(turn.userText, PREAMBLE_USER_MAX),
|
|
417
431
|
assistantText: truncatePreambleText(turn.assistantText, PREAMBLE_ASSISTANT_MAX),
|
|
418
432
|
});
|
|
@@ -1298,7 +1312,7 @@ function stopBridgeWatcher() {
|
|
|
1298
1312
|
* no jsonl line will ever match, and `maybeSwitchBridgeJsonl` burns 99%
|
|
1299
1313
|
* CPU scanning all sibling jsonls for it on every poll tick.
|
|
1300
1314
|
*/
|
|
1301
|
-
function bridgeMarkPendingTurn(messageText) {
|
|
1315
|
+
function bridgeMarkPendingTurn(messageText, preferredTurnId) {
|
|
1302
1316
|
if (!bridgeJsonlPath)
|
|
1303
1317
|
return undefined;
|
|
1304
1318
|
if (!bridgeBaselineDone) {
|
|
@@ -1314,7 +1328,7 @@ function bridgeMarkPendingTurn(messageText) {
|
|
|
1314
1328
|
// pane to false-match than the 30-char substring fingerprint.
|
|
1315
1329
|
const normalised = normaliseForFingerprint(messageText);
|
|
1316
1330
|
const contentNormalized = normalised.length > 0 ? normalised : undefined;
|
|
1317
|
-
const turnId = randomBytes(8).toString('hex');
|
|
1331
|
+
const turnId = preferredTurnId ?? randomBytes(8).toString('hex');
|
|
1318
1332
|
bridgeQueue.mark(turnId, fingerprint, Date.now(), contentNormalized);
|
|
1319
1333
|
return turnId;
|
|
1320
1334
|
}
|
|
@@ -1853,10 +1867,10 @@ function codexBridgeIngest() {
|
|
|
1853
1867
|
/** Mark a pending Lark turn for Codex. Crucially this works even before a
|
|
1854
1868
|
* rollout path is known — the queue is path-agnostic, and ingest after
|
|
1855
1869
|
* late-attach picks up the user_message and matches the fingerprint. */
|
|
1856
|
-
function codexBridgeMarkPendingTurn(messageText) {
|
|
1870
|
+
function codexBridgeMarkPendingTurn(messageText, preferredTurnId) {
|
|
1857
1871
|
if (!codexBridgeFallbackActive())
|
|
1858
1872
|
return false;
|
|
1859
|
-
const turnId = `codex-${randomBytes(8).toString('hex')}`;
|
|
1873
|
+
const turnId = preferredTurnId ?? `codex-${randomBytes(8).toString('hex')}`;
|
|
1860
1874
|
codexBridgeQueue.mark(turnId, messageText);
|
|
1861
1875
|
return true;
|
|
1862
1876
|
}
|
|
@@ -2115,7 +2129,7 @@ function maybeEmitWorkflowTranscriptOutput() {
|
|
|
2115
2129
|
type: 'final_output',
|
|
2116
2130
|
content: workflowTranscript,
|
|
2117
2131
|
lastUuid: `workflow-pty-${Date.now()}`,
|
|
2118
|
-
turnId: `workflow-pty-${sessionId || 'unknown'}`,
|
|
2132
|
+
turnId: currentBotmuxTurnId ?? `workflow-pty-${sessionId || 'unknown'}`,
|
|
2119
2133
|
});
|
|
2120
2134
|
log('Workflow PTY transcript final_output emitted');
|
|
2121
2135
|
}
|
|
@@ -2145,7 +2159,7 @@ function startScreenAnalyzer() {
|
|
|
2145
2159
|
onAnalyzing: () => { },
|
|
2146
2160
|
onTuiPrompt: (description, options, multiSelect) => {
|
|
2147
2161
|
tuiPromptBlocking = true;
|
|
2148
|
-
send({ type: 'tui_prompt', description, options, multiSelect });
|
|
2162
|
+
send({ type: 'tui_prompt', description, options, multiSelect, turnId: currentBotmuxTurnId });
|
|
2149
2163
|
},
|
|
2150
2164
|
onTuiPromptResolved: (selectedText) => {
|
|
2151
2165
|
tuiPromptBlocking = false;
|
|
@@ -2513,7 +2527,7 @@ function handleCodexAppMarker(body) {
|
|
|
2513
2527
|
return;
|
|
2514
2528
|
}
|
|
2515
2529
|
}
|
|
2516
|
-
const turnId = typeof payload.turnId === 'string' ? payload.turnId : `${lastInitConfig?.cliId ?? 'app'}-${Date.now()}
|
|
2530
|
+
const turnId = typeof payload.turnId === 'string' ? payload.turnId : (currentBotmuxTurnId ?? `${lastInitConfig?.cliId ?? 'app'}-${Date.now()}`);
|
|
2517
2531
|
send({
|
|
2518
2532
|
type: 'final_output',
|
|
2519
2533
|
content: payload.content,
|
|
@@ -2637,7 +2651,7 @@ function markPromptReady() {
|
|
|
2637
2651
|
// (where the initial prompt is queued before the CLI becomes idle).
|
|
2638
2652
|
if (renderer && pendingMessages.length === 0 && !isFlushing) {
|
|
2639
2653
|
const { content } = renderer.snapshot();
|
|
2640
|
-
send({ type: 'screen_update', content, ...usageLimitTracker.classify(content, 'idle') });
|
|
2654
|
+
send({ type: 'screen_update', content, ...usageLimitTracker.classify(content, 'idle'), turnId: currentBotmuxTurnId });
|
|
2641
2655
|
}
|
|
2642
2656
|
flushPending();
|
|
2643
2657
|
}
|
|
@@ -2700,6 +2714,7 @@ function scheduleSubmitFailureNotify(msg, recheck, transcriptLabel, bridgeTurnId
|
|
|
2700
2714
|
log(`writeInput: submit impossible — notifying user immediately. reason="${reason}" preview="${preview}"`);
|
|
2701
2715
|
send({
|
|
2702
2716
|
type: 'user_notify',
|
|
2717
|
+
turnId: currentBotmuxTurnId,
|
|
2703
2718
|
message: `⚠️ 刚才那条消息没有写入 ${cliName()},因为当前按键配置无法从终端自动提交。\n原因:${reason}\n请调整 Claude Code Chat keybinding 后重发。\n开头:${preview}`,
|
|
2704
2719
|
});
|
|
2705
2720
|
return;
|
|
@@ -2754,6 +2769,7 @@ function scheduleSubmitFailureNotify(msg, recheck, transcriptLabel, bridgeTurnId
|
|
|
2754
2769
|
log(`Deferred recheck still missing — notifying user. preview="${preview}"`);
|
|
2755
2770
|
send({
|
|
2756
2771
|
type: 'user_notify',
|
|
2772
|
+
turnId: currentBotmuxTurnId,
|
|
2757
2773
|
message: `⚠️ 刚才那条消息发给 ${cliName()} 后没能确认提交(重试 Enter 后等了 ${Math.round(SUBMIT_DEFERRED_RECHECK_MS / 1000)}s 仍未在${transcriptLabel}里看到新记录)。可能卡在输入框里——请去 Web 终端看一下,手动按 Enter 或重发。\n开头:${preview}`,
|
|
2758
2774
|
});
|
|
2759
2775
|
}, SUBMIT_DEFERRED_RECHECK_MS);
|
|
@@ -2807,7 +2823,10 @@ async function flushPending() {
|
|
|
2807
2823
|
}
|
|
2808
2824
|
try {
|
|
2809
2825
|
while (pendingMessages.length > 0 && backend && cliAdapter) {
|
|
2810
|
-
const
|
|
2826
|
+
const item = pendingMessages.shift();
|
|
2827
|
+
const msg = item.content;
|
|
2828
|
+
currentBotmuxTurnId = item.turnId;
|
|
2829
|
+
writeCliPidMarker();
|
|
2811
2830
|
const turnSeq = usageLimitTracker.beginTurn(currentUsageLimitSnapshot());
|
|
2812
2831
|
// Bridge fallback: mark immediately before writeInput. Doing it here
|
|
2813
2832
|
// (instead of at enqueue time) means markTimeMs anchors to the
|
|
@@ -2822,14 +2841,14 @@ async function flushPending() {
|
|
|
2822
2841
|
bridgeIngest();
|
|
2823
2842
|
}
|
|
2824
2843
|
catch { /* best-effort */ }
|
|
2825
|
-
bridgeTurnId = bridgeMarkPendingTurn(msg);
|
|
2844
|
+
bridgeTurnId = bridgeMarkPendingTurn(msg, item.turnId);
|
|
2826
2845
|
}
|
|
2827
2846
|
else if (codexBridgeActive) {
|
|
2828
2847
|
// Codex mark works even before the rollout path is known: the
|
|
2829
2848
|
// queue is path-agnostic, and the late-attach below will start
|
|
2830
2849
|
// ingest from offset 0 so the user_message that lands shortly
|
|
2831
2850
|
// after still fingerprint-matches this turn.
|
|
2832
|
-
codexBridgeMarkPendingTurn(msg);
|
|
2851
|
+
codexBridgeMarkPendingTurn(msg, item.turnId);
|
|
2833
2852
|
}
|
|
2834
2853
|
log(`Writing to PTY (flush): "${msg.substring(0, 80)}"`);
|
|
2835
2854
|
// Defense in depth: TmuxPipeBackend's send methods no longer throw on a
|
|
@@ -2884,10 +2903,10 @@ async function flushPending() {
|
|
|
2884
2903
|
isFlushing = false;
|
|
2885
2904
|
}
|
|
2886
2905
|
}
|
|
2887
|
-
function sendToPty(content) {
|
|
2906
|
+
function sendToPty(content, turnId) {
|
|
2888
2907
|
if (!backend || !cliAdapter)
|
|
2889
2908
|
return;
|
|
2890
|
-
pendingMessages.push(content);
|
|
2909
|
+
pendingMessages.push({ content, turnId });
|
|
2891
2910
|
// User-override semantics: a fresh Lark message while a TUI prompt is "active"
|
|
2892
2911
|
// takes precedence over the AI-detected prompt. The screen analyzer can be
|
|
2893
2912
|
// wrong (false positive on a question that has no rendered options) and a
|
|
@@ -2966,7 +2985,7 @@ function startScreenUpdates() {
|
|
|
2966
2985
|
const usageAware = usageLimitTracker.classify(content, status);
|
|
2967
2986
|
if (changed || usageAware.status !== lastSentStatus) {
|
|
2968
2987
|
lastSentStatus = usageAware.status;
|
|
2969
|
-
send({ type: 'screen_update', content, ...usageAware });
|
|
2988
|
+
send({ type: 'screen_update', content, ...usageAware, turnId: currentBotmuxTurnId });
|
|
2970
2989
|
}
|
|
2971
2990
|
})();
|
|
2972
2991
|
}, SCREEN_UPDATE_INTERVAL_MS);
|
|
@@ -3360,6 +3379,7 @@ function spawnCli(cfg) {
|
|
|
3360
3379
|
const probe = isAbsolute(wantBin) ? `ls -l ${wantBin}` : `which ${wantBin}`;
|
|
3361
3380
|
send({
|
|
3362
3381
|
type: 'user_notify',
|
|
3382
|
+
turnId: currentBotmuxTurnId,
|
|
3363
3383
|
message: `无法启动 ${cliName()}:找不到可执行文件「${wantBin}」。\n` +
|
|
3364
3384
|
`请在运行 botmux daemon 的这台机器上确认它已安装并在 PATH 中(自查:${probe}),然后重发消息重试。\n` +
|
|
3365
3385
|
`当前 daemon PATH=${process.env.PATH ?? '(空)'}`,
|
|
@@ -3385,6 +3405,9 @@ function spawnCli(cfg) {
|
|
|
3385
3405
|
childEnv.BOTMUX_CHAT_ID = cfg.chatId;
|
|
3386
3406
|
childEnv.BOTMUX_LARK_APP_ID = cfg.larkAppId;
|
|
3387
3407
|
childEnv.BOTMUX_ROOT_MESSAGE_ID = cfg.rootMessageId;
|
|
3408
|
+
// Initial value only; long-lived panes get the latest turn via the JSON pid marker.
|
|
3409
|
+
if (cfg.turnId)
|
|
3410
|
+
childEnv.BOTMUX_TURN_ID = cfg.turnId;
|
|
3388
3411
|
if (injectClaudeSandbox)
|
|
3389
3412
|
childEnv.IS_SANDBOX = '1';
|
|
3390
3413
|
if (claudeResumeTokenThreshold)
|
|
@@ -3410,7 +3433,7 @@ function spawnCli(cfg) {
|
|
|
3410
3433
|
try {
|
|
3411
3434
|
mkdirSync(markersDir, { recursive: true });
|
|
3412
3435
|
cliPidMarker = join(markersDir, String(cliPid));
|
|
3413
|
-
|
|
3436
|
+
writeCliPidMarker();
|
|
3414
3437
|
log(`CLI PID marker written: ${cliPid}`);
|
|
3415
3438
|
}
|
|
3416
3439
|
catch (err) {
|
|
@@ -3448,7 +3471,7 @@ function spawnCli(cfg) {
|
|
|
3448
3471
|
const markersDir = join(process.env.SESSION_DATA_DIR, '.botmux-cli-pids');
|
|
3449
3472
|
mkdirSync(markersDir, { recursive: true });
|
|
3450
3473
|
cliPidMarker = join(markersDir, String(pid));
|
|
3451
|
-
|
|
3474
|
+
writeCliPidMarker();
|
|
3452
3475
|
log(`CLI PID marker written (async): ${pid}`);
|
|
3453
3476
|
}
|
|
3454
3477
|
catch (err) {
|
|
@@ -3882,28 +3905,13 @@ function startWebServer(host, preferredPort) {
|
|
|
3882
3905
|
});
|
|
3883
3906
|
}
|
|
3884
3907
|
});
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
httpServer.on('error', (err) => {
|
|
3893
|
-
if (preferredPort && err.code === 'EADDRINUSE') {
|
|
3894
|
-
// Preferred port in use — fall back to random
|
|
3895
|
-
log(`Preferred port ${preferredPort} in use, falling back to random`);
|
|
3896
|
-
httpServer.listen(0, host, () => {
|
|
3897
|
-
const addr = httpServer.address();
|
|
3898
|
-
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
3899
|
-
log(`HTTP listening on ${host}:${port} (fallback)`);
|
|
3900
|
-
resolve(port);
|
|
3901
|
-
});
|
|
3902
|
-
}
|
|
3903
|
-
else {
|
|
3904
|
-
reject(err);
|
|
3905
|
-
}
|
|
3906
|
-
});
|
|
3908
|
+
// Bind + EADDRINUSE→random-port fallback live in a shared helper that also
|
|
3909
|
+
// attaches the load-bearing wss 'error' listener: `new WebSocketServer({
|
|
3910
|
+
// server })` makes ws proxy the http server's 'error' onto the wss, so a
|
|
3911
|
+
// busy port would otherwise emit an UNHANDLED 'error' on the wss and crash
|
|
3912
|
+
// the worker before this fallback can run. See web-terminal-listen.ts.
|
|
3913
|
+
listenWebTerminalWithFallback({ httpServer: httpServer, wss: wss, host, preferredPort, log })
|
|
3914
|
+
.then(resolve, reject);
|
|
3907
3915
|
});
|
|
3908
3916
|
}
|
|
3909
3917
|
function getTerminalHtml(hasWrite) {
|
|
@@ -4194,6 +4202,10 @@ process.on('message', async (raw) => {
|
|
|
4194
4202
|
renderRows = dims.rows;
|
|
4195
4203
|
log(`Init: session=${sessionId}, cwd=${msg.workingDir}, render=${renderCols}x${renderRows}${msg.adoptMode ? ' (adopt-pane)' : ''}`);
|
|
4196
4204
|
try {
|
|
4205
|
+
if (msg.turnId) {
|
|
4206
|
+
currentBotmuxTurnId = msg.turnId;
|
|
4207
|
+
writeCliPidMarker();
|
|
4208
|
+
}
|
|
4197
4209
|
let port = 0;
|
|
4198
4210
|
if (!isWorkflowWorker()) {
|
|
4199
4211
|
port = await startWebServer(config.web.workerHost, msg.webPort);
|
|
@@ -4215,9 +4227,9 @@ process.on('message', async (raw) => {
|
|
|
4215
4227
|
// Bridge mark is deferred to flushPending — see flushPending
|
|
4216
4228
|
// comment for why marking at enqueue is wrong.
|
|
4217
4229
|
if (msg.prompt && !cliAdapter?.passesInitialPromptViaArgs) {
|
|
4218
|
-
pendingMessages.push(msg.prompt);
|
|
4230
|
+
pendingMessages.push({ content: msg.prompt, turnId: msg.turnId });
|
|
4219
4231
|
}
|
|
4220
|
-
send({ type: 'ready', port, token: writeToken });
|
|
4232
|
+
send({ type: 'ready', port, token: writeToken, turnId: currentBotmuxTurnId });
|
|
4221
4233
|
}
|
|
4222
4234
|
catch (err) {
|
|
4223
4235
|
send({ type: 'error', message: `init failed: ${err.message}` });
|
|
@@ -4233,6 +4245,8 @@ process.on('message', async (raw) => {
|
|
|
4233
4245
|
if (tmuxScrolledHalfPages > 0)
|
|
4234
4246
|
exitTmuxScrollMode();
|
|
4235
4247
|
const content = msg.content;
|
|
4248
|
+
currentBotmuxTurnId = msg.turnId;
|
|
4249
|
+
writeCliPidMarker();
|
|
4236
4250
|
if (lastInitConfig?.adoptMode) {
|
|
4237
4251
|
// Bridge mode: capture transcript baseline BEFORE writing to the pane,
|
|
4238
4252
|
// so any assistant uuids appended after this point are attributed to
|
|
@@ -4244,7 +4258,7 @@ process.on('message', async (raw) => {
|
|
|
4244
4258
|
bridgeIngest();
|
|
4245
4259
|
}
|
|
4246
4260
|
catch { /* best effort */ }
|
|
4247
|
-
bridgeMarkPendingTurn(content);
|
|
4261
|
+
bridgeMarkPendingTurn(content, msg.turnId);
|
|
4248
4262
|
}
|
|
4249
4263
|
else if (codexBridgeFallbackActive()) {
|
|
4250
4264
|
// Codex adopt: same idea, different bridge. ingest first so any
|
|
@@ -4256,7 +4270,7 @@ process.on('message', async (raw) => {
|
|
|
4256
4270
|
// before this IPC message is handled. Mark first so that preexisting
|
|
4257
4271
|
// current-line can still fingerprint-match instead of being marked
|
|
4258
4272
|
// seen as an unmatched event.
|
|
4259
|
-
codexBridgeMarkPendingTurn(content);
|
|
4273
|
+
codexBridgeMarkPendingTurn(content, msg.turnId);
|
|
4260
4274
|
try {
|
|
4261
4275
|
codexBridgeIngest();
|
|
4262
4276
|
}
|
|
@@ -4267,7 +4281,7 @@ process.on('message', async (raw) => {
|
|
|
4267
4281
|
codexBridgeIngest();
|
|
4268
4282
|
}
|
|
4269
4283
|
catch { /* best effort */ }
|
|
4270
|
-
codexBridgeMarkPendingTurn(content);
|
|
4284
|
+
codexBridgeMarkPendingTurn(content, msg.turnId);
|
|
4271
4285
|
}
|
|
4272
4286
|
}
|
|
4273
4287
|
// Adopt mode write:
|
|
@@ -4326,7 +4340,7 @@ process.on('message', async (raw) => {
|
|
|
4326
4340
|
// arrival. Marking now would race with a still-running previous
|
|
4327
4341
|
// turn whose `botmux send` could sneak its sentAtMs past this
|
|
4328
4342
|
// turn's markTimeMs and falsely suppress its fallback.
|
|
4329
|
-
sendToPty(content);
|
|
4343
|
+
sendToPty(content, msg.turnId);
|
|
4330
4344
|
}
|
|
4331
4345
|
break;
|
|
4332
4346
|
}
|
|
@@ -4430,6 +4444,19 @@ process.on('message', async (raw) => {
|
|
|
4430
4444
|
cleanup();
|
|
4431
4445
|
process.exit(0);
|
|
4432
4446
|
}
|
|
4447
|
+
case 'suspend': {
|
|
4448
|
+
log('Suspend requested');
|
|
4449
|
+
stopScreenshotLoop();
|
|
4450
|
+
stopBridgeWatcher();
|
|
4451
|
+
try {
|
|
4452
|
+
backend?.kill();
|
|
4453
|
+
}
|
|
4454
|
+
catch { /* detach best-effort */ }
|
|
4455
|
+
backend = null;
|
|
4456
|
+
isPromptReady = false;
|
|
4457
|
+
cleanup();
|
|
4458
|
+
process.exit(0);
|
|
4459
|
+
}
|
|
4433
4460
|
}
|
|
4434
4461
|
});
|
|
4435
4462
|
// ─── Cleanup ─────────────────────────────────────────────────────────────────
|