botmux 2.36.0 → 2.36.2
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/cli/cursor.d.ts.map +1 -1
- package/dist/adapters/cli/cursor.js +53 -11
- package/dist/adapters/cli/cursor.js.map +1 -1
- package/dist/cli.js +28 -27
- package/dist/cli.js.map +1 -1
- package/dist/im/lark/client.d.ts +18 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +58 -45
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/identity-cache.d.ts.map +1 -1
- package/dist/im/lark/identity-cache.js +3 -3
- package/dist/im/lark/identity-cache.js.map +1 -1
- package/dist/services/groups-store.d.ts.map +1 -1
- package/dist/services/groups-store.js +8 -12
- package/dist/services/groups-store.js.map +1 -1
- package/dist/setup/verify-permissions.d.ts.map +1 -1
- package/dist/setup/verify-permissions.js +6 -1
- package/dist/setup/verify-permissions.js.map +1 -1
- package/dist/utils/bot-routing.d.ts +31 -0
- package/dist/utils/bot-routing.d.ts.map +1 -1
- package/dist/utils/bot-routing.js +56 -0
- package/dist/utils/bot-routing.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../../src/adapters/cli/cursor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../../src/adapters/cli/cursor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,YAAY,CAAC;AAYxD,wBAAgB,mBAAmB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,UAAU,CAuFrE;AAED,eAAO,MAAM,MAAM,4BAAsB,CAAC"}
|
|
@@ -3,6 +3,11 @@ import { BOTMUX_SHELL_HINTS } from './shared-hints.js';
|
|
|
3
3
|
function delay(ms) {
|
|
4
4
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
5
5
|
}
|
|
6
|
+
/** PTYs that have already received a writeInput. The first write lands while
|
|
7
|
+
* cursor-agent's TUI is still doing its startup render, so it needs a longer
|
|
8
|
+
* settle + throttle than later writes. Tracked by identity so the warmup state
|
|
9
|
+
* is shared across adapter instances. Mirrors claude-code's first-write guard. */
|
|
10
|
+
const cursorFirstWriteSeen = new WeakSet();
|
|
6
11
|
export function createCursorAdapter(pathOverride) {
|
|
7
12
|
const bin = resolveCommand(pathOverride ?? 'cursor-agent');
|
|
8
13
|
return {
|
|
@@ -31,20 +36,57 @@ export function createCursorAdapter(pathOverride) {
|
|
|
31
36
|
return `cursor-agent --resume ${cliSessionId}`;
|
|
32
37
|
},
|
|
33
38
|
async writeInput(pty, content) {
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
// Emit line-by-line instead of writing the whole message at once.
|
|
40
|
+
// cursor-agent's paste detector folds a multi-line chunk that arrives in
|
|
41
|
+
// one burst into a `[Pasted text +N lines]` placeholder the model can't
|
|
42
|
+
// read; typing each line with a throttle between keeps it under that
|
|
43
|
+
// threshold so the text lands verbatim. Covers both backends — tmux
|
|
44
|
+
// (send-keys) and raw PTY (write only). Never use bracketed-paste markers
|
|
45
|
+
// (\x1b[200~ … \x1b[201~): they trigger the fold.
|
|
46
|
+
//
|
|
47
|
+
// Soft-newline differs per backend because the detector counts LF (0x0a)
|
|
48
|
+
// bytes arriving densely:
|
|
49
|
+
// - tmux: Ctrl+J, cursor's native soft-newline — renders cleanly and
|
|
50
|
+
// send-keys spaces the bytes out enough to never fold.
|
|
51
|
+
// - raw PTY: a fast write('\n') folds, so send `\` + CR; cursor eats the
|
|
52
|
+
// backslash-before-CR as a soft-newline (not part of the submitted
|
|
53
|
+
// text) and no LF byte hits the stream, making it fold-immune. Costs a
|
|
54
|
+
// cosmetic trailing `\` in the local TUI render only.
|
|
55
|
+
// Submit is always a bare Enter (\r). No on-disk submit verification —
|
|
56
|
+
// cursor's transcript path isn't documented, so the worker relies on
|
|
57
|
+
// idle detection + the bridge fallback timer.
|
|
58
|
+
const useKeys = !!(pty.sendText && pty.sendSpecialKeys);
|
|
59
|
+
const emitText = (s) => (useKeys ? pty.sendText(s) : pty.write(s));
|
|
60
|
+
const emitSoftNewline = () => {
|
|
61
|
+
if (useKeys) {
|
|
62
|
+
pty.sendSpecialKeys('C-j');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
pty.write('\\');
|
|
66
|
+
pty.write('\r');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const emitEnter = () => (useKeys ? pty.sendSpecialKeys('Enter') : pty.write('\r'));
|
|
70
|
+
const isFirstWrite = !cursorFirstWriteSeen.has(pty);
|
|
71
|
+
if (isFirstWrite) {
|
|
72
|
+
cursorFirstWriteSeen.add(pty);
|
|
40
73
|
await delay(200);
|
|
41
|
-
pty.sendSpecialKeys('Enter');
|
|
42
74
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
75
|
+
const throttleMs = isFirstWrite ? 80 : 30;
|
|
76
|
+
const tick = () => delay(throttleMs);
|
|
77
|
+
const lines = content.split('\n');
|
|
78
|
+
for (let i = 0; i < lines.length; i++) {
|
|
79
|
+
if (lines[i].length > 0) {
|
|
80
|
+
emitText(lines[i]);
|
|
81
|
+
await tick();
|
|
82
|
+
}
|
|
83
|
+
if (i < lines.length - 1) {
|
|
84
|
+
emitSoftNewline();
|
|
85
|
+
await tick();
|
|
86
|
+
}
|
|
47
87
|
}
|
|
88
|
+
await delay(200);
|
|
89
|
+
emitEnter();
|
|
48
90
|
},
|
|
49
91
|
completionPattern: undefined,
|
|
50
92
|
skillsDir: '~/.cursor/skills',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../../src/adapters/cli/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGvD,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,YAAqB;IACvD,MAAM,GAAG,GAAG,cAAc,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;IAC3D,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,WAAW,EAAE,GAAG;QAEhB,SAAS,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE;YACnC,wEAAwE;YACxE,yEAAyE;YACzE,0EAA0E;YAC1E,kCAAkC;YAClC,MAAM,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;YACzB,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YACzB,IAAI,eAAe;gBAAE,OAAO,CAAC,GAAG,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;YACnE,qEAAqE;YACrE,wEAAwE;YACxE,OAAO,CAAC,GAAG,IAAI,EAAE,YAAY,CAAC,CAAC;QACjC,CAAC;QAED,kBAAkB,CAAC,EAAE,YAAY,EAAE;YACjC,wEAAwE;YACxE,4EAA4E;YAC5E,sCAAsC;YACtC,IAAI,CAAC,YAAY;gBAAE,OAAO,IAAI,CAAC;YAC/B,OAAO,yBAAyB,YAAY,EAAE,CAAC;QACjD,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,GAAc,EAAE,OAAe;YAC9C,oEAAoE;YACpE,
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../../src/adapters/cli/cursor.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,7 @@ import { logger } from './utils/logger.js';
|
|
|
33
33
|
import { invalidWorkingDirs } from './utils/working-dir.js';
|
|
34
34
|
import { firstPositional } from './cli/arg-utils.js';
|
|
35
35
|
import { formatBotInfoEntriesForCli, formatChatBotsForCli, } from './cli/bots-list-output.js';
|
|
36
|
+
import { buildFooterAddressing, hasKnownBotMention, knownBotOpenIdsFromCrossRef, } from './utils/bot-routing.js';
|
|
36
37
|
import { isLocale, setDefaultLocale, SUPPORTED_LOCALES } from './i18n/index.js';
|
|
37
38
|
import { readGlobalConfig, setGlobalLocale, globalConfigPath } from './global-config.js';
|
|
38
39
|
// Resolve the CLI's UI locale once from the global config file, so subsequent
|
|
@@ -2456,20 +2457,6 @@ function argValues(args, ...flags) {
|
|
|
2456
2457
|
// daemon's bridge fallback path can produce identical cards. cmdSend
|
|
2457
2458
|
// keeps using `buildCardBodyElements` and `hasMarkdown` from there.
|
|
2458
2459
|
import { buildCardBodyElements, hasMarkdown } from './im/lark/md-card.js';
|
|
2459
|
-
/**
|
|
2460
|
-
* Decide who the reply card should @ in its footer.
|
|
2461
|
-
*
|
|
2462
|
-
* Non-oncall chats: `发送给: @<owner>`.
|
|
2463
|
-
* Oncall chats: `发送给: @<last caller>` (falls back to owner if unknown) —
|
|
2464
|
-
* permission is governed by allowedUsers, so there's no per-chat list to cc.
|
|
2465
|
-
*/
|
|
2466
|
-
function buildFooterAddressing(s, oncall) {
|
|
2467
|
-
const owner = s.ownerOpenId;
|
|
2468
|
-
const caller = s.lastCallerOpenId ?? owner;
|
|
2469
|
-
if (!oncall)
|
|
2470
|
-
return { sendTo: owner, cc: [] };
|
|
2471
|
-
return { sendTo: caller, cc: [] };
|
|
2472
|
-
}
|
|
2473
2460
|
async function cmdSend(rest) {
|
|
2474
2461
|
// Safety gate: a CLI agent running inside a workflow subagent (Slice F)
|
|
2475
2462
|
// must not chat-post directly — chat-facing side effects are reserved
|
|
@@ -2616,12 +2603,14 @@ async function cmdSend(rest) {
|
|
|
2616
2603
|
// "获取群组中其他机器人和用户@当前机器人的消息"权限),不再走任何本地
|
|
2617
2604
|
// 转发——botmux 历史上为绕过 Lark 不投递跨 bot 事件搞过 signal-file,
|
|
2618
2605
|
// 那套已经在该权限上线后整体下线。
|
|
2606
|
+
let botEntries = [];
|
|
2607
|
+
let crossRef = {};
|
|
2619
2608
|
try {
|
|
2620
2609
|
const dataDir = resolveDataDir();
|
|
2621
2610
|
const botInfoPath = join(dataDir, 'bots-info.json');
|
|
2622
|
-
|
|
2611
|
+
botEntries = existsSync(botInfoPath) ? JSON.parse(readFileSync(botInfoPath, 'utf-8')) : [];
|
|
2623
2612
|
const crossRefPath = join(dataDir, `bot-openids-${appId}.json`);
|
|
2624
|
-
|
|
2613
|
+
crossRef = existsSync(crossRefPath)
|
|
2625
2614
|
? JSON.parse(readFileSync(crossRefPath, 'utf-8'))
|
|
2626
2615
|
: {};
|
|
2627
2616
|
const alreadyMentioned = new Set(mentions.map(m => m.open_id));
|
|
@@ -2629,10 +2618,16 @@ async function cmdSend(rest) {
|
|
|
2629
2618
|
// prefix ("Claude") when both could match — break-on-first-hit otherwise
|
|
2630
2619
|
// routes "@Claude分身" to Claude.
|
|
2631
2620
|
const sortedEntries = [...botEntries].sort((a, b) => (b.botName?.length ?? 0) - (a.botName?.length ?? 0));
|
|
2621
|
+
const selfAliases = new Set(botEntries
|
|
2622
|
+
.filter(entry => entry.larkAppId === appId)
|
|
2623
|
+
.flatMap(entry => [entry.botName, entry.cliId])
|
|
2624
|
+
.filter((name) => !!name)
|
|
2625
|
+
.map(name => name.toLowerCase()));
|
|
2632
2626
|
for (const entry of sortedEntries) {
|
|
2633
2627
|
if (!entry.botName || entry.larkAppId === appId)
|
|
2634
2628
|
continue;
|
|
2635
|
-
const names = [entry.botName, entry.cliId]
|
|
2629
|
+
const names = [entry.botName, entry.cliId]
|
|
2630
|
+
.filter((name) => !!name && !selfAliases.has(name.toLowerCase()));
|
|
2636
2631
|
for (const name of names) {
|
|
2637
2632
|
const escName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2638
2633
|
// Boundary: lookbehind blocks only ASCII word chars (so `user@Claude`
|
|
@@ -2661,6 +2656,15 @@ async function cmdSend(rest) {
|
|
|
2661
2656
|
}
|
|
2662
2657
|
}
|
|
2663
2658
|
catch { /* best-effort */ }
|
|
2659
|
+
const explicitKnownBotMention = hasKnownBotMention(text, mentions, botEntries, crossRef, appId);
|
|
2660
|
+
const knownBotOpenIds = knownBotOpenIdsFromCrossRef(crossRef, botEntries, appId);
|
|
2661
|
+
const footerAddressing = sendTopLevel
|
|
2662
|
+
? { sendTo: undefined, cc: [] }
|
|
2663
|
+
: buildFooterAddressing(s, {
|
|
2664
|
+
isOncall: !!oncallEntry,
|
|
2665
|
+
hasExplicitBotMention: explicitKnownBotMention,
|
|
2666
|
+
knownBotOpenIds,
|
|
2667
|
+
});
|
|
2664
2668
|
// Decide: interactive card (renders markdown) vs. post (plain text).
|
|
2665
2669
|
// Explicit --card / --text wins; otherwise auto-detect markdown syntax.
|
|
2666
2670
|
const useCard = forceCard || (!forceText && hasMarkdown(text));
|
|
@@ -2724,14 +2728,13 @@ async function cmdSend(rest) {
|
|
|
2724
2728
|
const elements = mdWithImages ? buildCardBodyElements(mdWithImages) : [];
|
|
2725
2729
|
// Footer: de-emphasized markdown (v2 dropped the `note` tag). Use small
|
|
2726
2730
|
// text size + grey font tag so it reads like a footnote below the hr.
|
|
2727
|
-
// Oncall groups
|
|
2728
|
-
//
|
|
2731
|
+
// Oncall groups usually address whoever triggered this turn (may not be
|
|
2732
|
+
// the session owner). Bot recipients are filtered out so footer chrome
|
|
2733
|
+
// cannot accidentally wake a sibling bot.
|
|
2729
2734
|
const footerParts = ['[botmux](https://github.com/deepcoldy/botmux)'];
|
|
2730
2735
|
// Top-level publish has no specific recipient — drop "发送给/cc" addressing
|
|
2731
2736
|
// so the message doesn't @ the session owner who isn't even in the target chat.
|
|
2732
|
-
const addressing =
|
|
2733
|
-
? { sendTo: undefined, cc: [] }
|
|
2734
|
-
: buildFooterAddressing(s, oncallEntry);
|
|
2737
|
+
const addressing = footerAddressing;
|
|
2735
2738
|
if (addressing.sendTo)
|
|
2736
2739
|
footerParts.push(`发送给:<at id=${addressing.sendTo}></at>`);
|
|
2737
2740
|
if (addressing.cc.length > 0) {
|
|
@@ -2787,11 +2790,9 @@ async function cmdSend(rest) {
|
|
|
2787
2790
|
}
|
|
2788
2791
|
}
|
|
2789
2792
|
// Footer: mirror the card layout — a blank paragraph separates the body
|
|
2790
|
-
// from the addressing line(s).
|
|
2791
|
-
//
|
|
2792
|
-
const addressing =
|
|
2793
|
-
? { sendTo: undefined, cc: [] }
|
|
2794
|
-
: buildFooterAddressing(s, oncallEntry);
|
|
2793
|
+
// from the addressing line(s). Top-level publish has no specific
|
|
2794
|
+
// recipient; bot recipients are filtered out by footerAddressing.
|
|
2795
|
+
const addressing = footerAddressing;
|
|
2795
2796
|
if (addressing.sendTo || addressing.cc.length > 0) {
|
|
2796
2797
|
if (postContent.length > 0)
|
|
2797
2798
|
postContent.push([{ tag: 'text', text: '' }]);
|