botmux 2.56.0 → 2.57.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/README.en.md +10 -10
- package/README.md +10 -10
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +10 -4
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +11 -5
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/core/session-manager.d.ts +42 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +82 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/terminal-proxy.d.ts +7 -0
- package/dist/core/terminal-proxy.d.ts.map +1 -1
- package/dist/core/terminal-proxy.js +84 -63
- package/dist/core/terminal-proxy.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +53 -7
- package/dist/daemon.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/package.json +1 -1
|
@@ -11,6 +11,13 @@ export interface TerminalProxyOptions {
|
|
|
11
11
|
host?: string;
|
|
12
12
|
/** Resolve a sessionId to its live worker HTTP port (undefined if not running). */
|
|
13
13
|
resolvePort: (sessionId: string) => number | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Optional on-demand wake: when `resolvePort` finds no live worker, re-fork it
|
|
16
|
+
* (re-attaching the surviving tmux/zellij pane) and resolve once its port is
|
|
17
|
+
* up. Lets terminals open after a quiet restart without first messaging the
|
|
18
|
+
* session. Returns undefined when there's nothing to wake. Slow path only.
|
|
19
|
+
*/
|
|
20
|
+
ensureWorkerPort?: (sessionId: string) => Promise<number | undefined>;
|
|
14
21
|
/** Max upward port probes when `port` is taken (EADDRINUSE). Default 20; 0 disables. */
|
|
15
22
|
maxProbe?: number;
|
|
16
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-proxy.d.ts","sourceRoot":"","sources":["../../src/core/terminal-proxy.ts"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AAEH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACvD,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAUtF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"terminal-proxy.d.ts","sourceRoot":"","sources":["../../src/core/terminal-proxy.ts"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AAEH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACvD;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtE,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAUtF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAkI3F"}
|
|
@@ -22,6 +22,21 @@ export function parseTarget(rawUrl) {
|
|
|
22
22
|
}
|
|
23
23
|
export function startTerminalProxy(opts) {
|
|
24
24
|
const host = opts.host ?? '0.0.0.0';
|
|
25
|
+
// Fast sync lookup; fall back to the on-demand wake (slow path) only when no
|
|
26
|
+
// live worker is registered. Errors in the wake collapse to "not serveable".
|
|
27
|
+
const resolvePortMaybeWake = async (sessionId) => {
|
|
28
|
+
const live = opts.resolvePort(sessionId);
|
|
29
|
+
if (live)
|
|
30
|
+
return live;
|
|
31
|
+
if (!opts.ensureWorkerPort)
|
|
32
|
+
return undefined;
|
|
33
|
+
try {
|
|
34
|
+
return await opts.ensureWorkerPort(sessionId);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
25
40
|
const server = createServer((req, res) => {
|
|
26
41
|
const parsed = parseTarget(req.url ?? '');
|
|
27
42
|
if (!parsed) {
|
|
@@ -29,78 +44,84 @@ export function startTerminalProxy(opts) {
|
|
|
29
44
|
res.end('not found');
|
|
30
45
|
return;
|
|
31
46
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
resolvePortMaybeWake(parsed.sessionId).then((port) => {
|
|
48
|
+
if (!port) {
|
|
49
|
+
res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });
|
|
50
|
+
res.end('session not running');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const upstream = httpRequest({ host: '127.0.0.1', port, method: req.method, path: parsed.rest, headers: req.headers }, (up) => {
|
|
54
|
+
res.writeHead(up.statusCode ?? 502, up.headers);
|
|
55
|
+
up.pipe(res);
|
|
56
|
+
});
|
|
57
|
+
upstream.on('error', () => {
|
|
58
|
+
if (!res.headersSent)
|
|
59
|
+
res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });
|
|
60
|
+
res.end('proxy error');
|
|
61
|
+
});
|
|
62
|
+
req.pipe(upstream);
|
|
63
|
+
}).catch(() => {
|
|
43
64
|
if (!res.headersSent)
|
|
44
65
|
res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });
|
|
45
66
|
res.end('proxy error');
|
|
46
67
|
});
|
|
47
|
-
req.pipe(upstream);
|
|
48
68
|
});
|
|
49
69
|
server.on('upgrade', (req, clientSocket, head) => {
|
|
50
70
|
const parsed = parseTarget(req.url ?? '');
|
|
51
71
|
if (!parsed)
|
|
52
72
|
return clientSocket.destroy();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
73
|
+
resolvePortMaybeWake(parsed.sessionId).then((port) => {
|
|
74
|
+
if (!port)
|
|
75
|
+
return clientSocket.destroy();
|
|
76
|
+
const upstream = httpRequest({
|
|
77
|
+
host: '127.0.0.1',
|
|
78
|
+
port,
|
|
79
|
+
method: req.method,
|
|
80
|
+
path: parsed.rest,
|
|
81
|
+
headers: req.headers,
|
|
82
|
+
});
|
|
83
|
+
upstream.on('upgrade', (upRes, upstreamSocket, upstreamHead) => {
|
|
84
|
+
// rawHeaders is a flat [k, v, k, v, ...] list — preserves duplicates/casing.
|
|
85
|
+
const lines = [`HTTP/1.1 ${upRes.statusCode} ${upRes.statusMessage}`];
|
|
86
|
+
const rh = upRes.rawHeaders;
|
|
87
|
+
for (let i = 0; i + 1 < rh.length; i += 2)
|
|
88
|
+
lines.push(`${rh[i]}: ${rh[i + 1]}`);
|
|
89
|
+
lines.push('', '');
|
|
90
|
+
clientSocket.write(lines.join('\r\n'));
|
|
91
|
+
if (upstreamHead?.length)
|
|
92
|
+
clientSocket.write(upstreamHead);
|
|
93
|
+
if (head?.length)
|
|
94
|
+
upstreamSocket.write(head);
|
|
95
|
+
upstreamSocket.pipe(clientSocket);
|
|
96
|
+
clientSocket.pipe(upstreamSocket);
|
|
97
|
+
const cleanup = () => { upstreamSocket.destroy(); clientSocket.destroy(); };
|
|
98
|
+
upstreamSocket.on('error', cleanup);
|
|
99
|
+
clientSocket.on('error', cleanup);
|
|
100
|
+
upstreamSocket.on('close', () => clientSocket.destroy());
|
|
101
|
+
clientSocket.on('close', () => upstreamSocket.destroy());
|
|
102
|
+
});
|
|
103
|
+
// Upstream answered without upgrading (e.g. worker rejected the handshake).
|
|
104
|
+
// Relay the response and close the client socket so it doesn't hang. The
|
|
105
|
+
// body arrives already de-chunked, so drop framing headers and let the
|
|
106
|
+
// socket close delimit the response (HTTP/1.1 connection-close framing).
|
|
107
|
+
upstream.on('response', (upRes) => {
|
|
108
|
+
const lines = [`HTTP/1.1 ${upRes.statusCode} ${upRes.statusMessage}`, 'connection: close'];
|
|
109
|
+
const rh = upRes.rawHeaders;
|
|
110
|
+
for (let i = 0; i + 1 < rh.length; i += 2) {
|
|
111
|
+
const name = rh[i].toLowerCase();
|
|
112
|
+
if (name === 'transfer-encoding' || name === 'content-length' || name === 'connection')
|
|
113
|
+
continue;
|
|
114
|
+
lines.push(`${rh[i]}: ${rh[i + 1]}`);
|
|
115
|
+
}
|
|
116
|
+
lines.push('', '');
|
|
117
|
+
clientSocket.write(lines.join('\r\n'));
|
|
118
|
+
upRes.on('data', (chunk) => clientSocket.write(chunk));
|
|
119
|
+
upRes.on('end', () => { clientSocket.end(); upRes.socket?.destroy(); });
|
|
120
|
+
upRes.on('error', () => clientSocket.destroy());
|
|
121
|
+
});
|
|
122
|
+
upstream.on('error', () => clientSocket.destroy());
|
|
123
|
+
upstream.end();
|
|
124
|
+
}).catch(() => clientSocket.destroy());
|
|
104
125
|
});
|
|
105
126
|
// When the preferred port is taken, probe upward to the next free port so the
|
|
106
127
|
// proxy always comes up on a single stable-ish port (the daemon advertises the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-proxy.js","sourceRoot":"","sources":["../../src/core/terminal-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,IAAI,WAAW,GAIvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"terminal-proxy.js","sourceRoot":"","sources":["../../src/core/terminal-proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AA0BA,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AA0BA,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAyErD,OAAO,EAAE,oBAAoB,EAA6B,MAAM,uBAAuB,CAAC;AACxF,OAAO,KAAK,EAAE,sBAAsB,EAAiB,MAAM,wBAAwB,CAAC;AAiTpF,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CA4ClB;AAED,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAIpB;AAED,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAGpB;AAyJD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,sBAAsB,GAAG,oBAAoB,CA0C5G;AA2sED,wBAAsB,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqUlE"}
|
package/dist/daemon.js
CHANGED
|
@@ -40,7 +40,7 @@ import { findInheritablePeer } from './core/inherit-peer.js';
|
|
|
40
40
|
import { isCallbackUrl, handleCallbackUrl } from './utils/user-token.js';
|
|
41
41
|
import { consumeQuota, removeChatGrant, removeGlobalGrant } from './services/grant-store.js';
|
|
42
42
|
import { abortCharge, commitCharge, beginCharge } from './services/quota-dedup.js';
|
|
43
|
-
import { getSessionWorkingDir, getProjectScanDirs, expandHome, downloadResources, formatAttachmentsHint, buildNewTopicPrompt, buildFollowUpContent, buildBridgeInputContent, buildReforkPrompt, getAvailableBots, restoreActiveSessions, executeScheduledTask, persistStreamCardState, rememberLastCliInput, } from './core/session-manager.js';
|
|
43
|
+
import { getSessionWorkingDir, getProjectScanDirs, expandHome, downloadResources, formatAttachmentsHint, buildNewTopicPrompt, buildFollowUpContent, buildBridgeInputContent, buildReforkPrompt, getAvailableBots, restoreActiveSessions, executeScheduledTask, persistStreamCardState, rememberLastCliInput, ensureTerminalWorkerPort, } from './core/session-manager.js';
|
|
44
44
|
import { handleCardAction } from './im/lark/card-handler.js';
|
|
45
45
|
import { executeWorkflowCommand, parseWorkflowCommand, resolveBotSnapshot, } from './im/lark/workflow-slash-command.js';
|
|
46
46
|
import { workflowRunDetailUrl } from './im/lark/workflow-cards.js';
|
|
@@ -49,7 +49,7 @@ import { EventLog as WorkflowEventLog } from './workflows/events/append.js';
|
|
|
49
49
|
import { replay as replayWorkflow } from './workflows/events/replay.js';
|
|
50
50
|
import { isBotMentioned, probeBotOpenId, startLarkEventDispatcher, writeBotInfoFile, canOperate, evaluateTalk, grantCommandRestriction, isKnownPeerBot, checkRequiredScopes } from './im/lark/event-dispatcher.js';
|
|
51
51
|
import { learnFromMentions, resolveSender, flushIdentityCacheSync } from './im/lark/identity-cache.js';
|
|
52
|
-
import {
|
|
52
|
+
import { renderBufferedSenderBlock } from './core/session-manager.js';
|
|
53
53
|
import { markSessionActivity } from './core/session-activity.js';
|
|
54
54
|
import { WorkflowEventWatcher, handleWorkflowFanoutEvent } from './workflows/fanout.js';
|
|
55
55
|
import { runLoop } from './workflows/loop.js';
|
|
@@ -1113,6 +1113,29 @@ const commandDeps = {
|
|
|
1113
1113
|
getActiveCount,
|
|
1114
1114
|
lastRepoScan,
|
|
1115
1115
|
};
|
|
1116
|
+
/**
|
|
1117
|
+
* Fire a session-less daemon command (`/group`, `/g`) WITHOUT blocking the Lark
|
|
1118
|
+
* event ACK on its slow work — the fast-ACK path.
|
|
1119
|
+
*
|
|
1120
|
+
* The Lark WS SDK sends the event response frame (a content-free `{code:200}`)
|
|
1121
|
+
* only AFTER our `im.message.receive_v1` handler resolves: node-sdk's
|
|
1122
|
+
* `handleEventData` does `await eventDispatcher.invoke(...)` and only then sends
|
|
1123
|
+
* the ack. `/group` runs several seconds of serial Lark API calls (create chat →
|
|
1124
|
+
* add bots → transfer owner → fetch share link → reply); awaiting that blocks the
|
|
1125
|
+
* ack past Feishu's redelivery window, so Feishu redelivers the same message_id.
|
|
1126
|
+
* Because these commands are session-less (SESSIONLESS_DAEMON_COMMANDS), no
|
|
1127
|
+
* session record exists to dedupe the redelivery against — so it builds a SECOND
|
|
1128
|
+
* group. (Observed: one `/g` created two identical groups, the duplicate ~19s
|
|
1129
|
+
* after the first.)
|
|
1130
|
+
*
|
|
1131
|
+
* The ack needs nothing from the command's output, so run it detached and let the
|
|
1132
|
+
* handler return immediately → the ack fires now → no redelivery. handleCommand
|
|
1133
|
+
* wraps its whole body in try/catch and replies failures to the chat; the `.catch`
|
|
1134
|
+
* here is only a last-resort backstop for anything that somehow escapes.
|
|
1135
|
+
*/
|
|
1136
|
+
function fireSessionlessCommandDetached(cmd, anchor, message, larkAppId) {
|
|
1137
|
+
void handleCommand(cmd, anchor, message, commandDeps, larkAppId).catch((err) => logger.error(`[sessionless ${cmd}] ${anchor.substring(0, 12)} failed: ${err?.message ?? err}`));
|
|
1138
|
+
}
|
|
1116
1139
|
// Dependencies passed to card-handler
|
|
1117
1140
|
const cardDeps = {
|
|
1118
1141
|
activeSessions,
|
|
@@ -1581,7 +1604,10 @@ async function handleNewTopic(data, ctx) {
|
|
|
1581
1604
|
// without a session; pass chatId on the message so the handler can reach
|
|
1582
1605
|
// the chat roster (it normally reads it from the active session's ds).
|
|
1583
1606
|
if (SESSIONLESS_DAEMON_COMMANDS.has(cmd)) {
|
|
1584
|
-
|
|
1607
|
+
// Fast-ACK: run detached so the WS event ack isn't blocked on /group's
|
|
1608
|
+
// slow Lark API work → no Feishu redelivery → no duplicate group.
|
|
1609
|
+
// See fireSessionlessCommandDetached.
|
|
1610
|
+
fireSessionlessCommandDetached(cmd, anchor, { ...parsed, content: commandContent, chatId }, larkAppId);
|
|
1585
1611
|
return;
|
|
1586
1612
|
}
|
|
1587
1613
|
// Same rootMessageId reasoning as below in the main spawn path:
|
|
@@ -2148,7 +2174,13 @@ async function handleThreadReply(data, ctx) {
|
|
|
2148
2174
|
}
|
|
2149
2175
|
// Pass mention-stripped content so /command argument parsing works.
|
|
2150
2176
|
// chatId lets session-less handlers (e.g. /group) reach the chat roster.
|
|
2151
|
-
|
|
2177
|
+
const cmdMessage = { ...parsed, content: commandContent, chatId: threadChatId };
|
|
2178
|
+
if (SESSIONLESS_DAEMON_COMMANDS.has(cmd)) {
|
|
2179
|
+
// Fast-ACK for /group invoked mid-thread. See fireSessionlessCommandDetached.
|
|
2180
|
+
fireSessionlessCommandDetached(cmd, anchor, cmdMessage, larkAppId);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
await handleCommand(cmd, anchor, cmdMessage, commandDeps, larkAppId);
|
|
2152
2184
|
return;
|
|
2153
2185
|
}
|
|
2154
2186
|
}
|
|
@@ -2225,9 +2257,14 @@ async function handleThreadReply(data, ctx) {
|
|
|
2225
2257
|
// tell multi-user buffered messages apart after repo selection unlocks.
|
|
2226
2258
|
const followUpSender = await getThreadSender();
|
|
2227
2259
|
if (followUpSender?.openId && followUpSender.openId !== ds.pendingSender?.openId) {
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2260
|
+
// This buffer folds into the opening <user_message> after repo selection,
|
|
2261
|
+
// so pair the foreign sender tag with the cursor anti-echo note: without
|
|
2262
|
+
// the adjacent note a cursor session sees an inline ou_xxx:name with no
|
|
2263
|
+
// guard (the builder's own note only covers ds.pendingSender's top-level
|
|
2264
|
+
// tag, and is absent entirely when pendingSender is undefined).
|
|
2265
|
+
const followUpSenderBlock = renderBufferedSenderBlock(followUpSender, getBot(larkAppId).config.cliId, localeForBot(larkAppId));
|
|
2266
|
+
if (followUpSenderBlock)
|
|
2267
|
+
enriched = `${followUpSenderBlock}\n${enriched}`;
|
|
2231
2268
|
}
|
|
2232
2269
|
if (!ds.pendingFollowUps)
|
|
2233
2270
|
ds.pendingFollowUps = [];
|
|
@@ -2560,6 +2597,15 @@ export async function startDaemon(botIndex) {
|
|
|
2560
2597
|
}
|
|
2561
2598
|
return undefined;
|
|
2562
2599
|
},
|
|
2600
|
+
// Quiet-restart leaves sessions registered but worker-less until messaged.
|
|
2601
|
+
// Wake the worker on terminal access so links open without a manual ping.
|
|
2602
|
+
ensureWorkerPort: async (sessionId) => {
|
|
2603
|
+
for (const ds of activeSessions.values()) {
|
|
2604
|
+
if (ds.session.sessionId === sessionId)
|
|
2605
|
+
return ensureTerminalWorkerPort(ds);
|
|
2606
|
+
}
|
|
2607
|
+
return undefined;
|
|
2608
|
+
},
|
|
2563
2609
|
});
|
|
2564
2610
|
// Only mark the proxy live after a successful bind — buildTerminalUrl then
|
|
2565
2611
|
// falls back to the worker's own port so links stay reachable if the port
|