botmux 2.11.1 → 2.12.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/pty-backend.d.ts +6 -0
- package/dist/adapters/backend/pty-backend.d.ts.map +1 -1
- package/dist/adapters/backend/pty-backend.js +6 -0
- package/dist/adapters/backend/pty-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +16 -2
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +40 -10
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +59 -0
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -0
- package/dist/adapters/backend/tmux-pipe-backend.js +288 -0
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -0
- package/dist/adapters/cli/claude-code.d.ts +15 -0
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +205 -24
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/codex.d.ts.map +1 -1
- package/dist/adapters/cli/codex.js +78 -16
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +10 -0
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/cli.js +63 -8
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts +10 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +29 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/scheduler.d.ts +3 -0
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +3 -0
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/session-manager.d.ts +17 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +51 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +13 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +115 -4
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +59 -120
- package/dist/daemon.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +2 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +23 -9
- 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 +26 -93
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/merge-forward.d.ts +32 -0
- package/dist/im/lark/merge-forward.d.ts.map +1 -0
- package/dist/im/lark/merge-forward.js +99 -0
- package/dist/im/lark/merge-forward.js.map +1 -0
- package/dist/services/bridge-fallback-gate.d.ts +42 -0
- package/dist/services/bridge-fallback-gate.d.ts.map +1 -0
- package/dist/services/bridge-fallback-gate.js +12 -0
- package/dist/services/bridge-fallback-gate.js.map +1 -0
- package/dist/services/bridge-turn-queue.d.ts +111 -0
- package/dist/services/bridge-turn-queue.d.ts.map +1 -0
- package/dist/services/bridge-turn-queue.js +213 -0
- package/dist/services/bridge-turn-queue.js.map +1 -0
- package/dist/services/claude-transcript.d.ts +168 -0
- package/dist/services/claude-transcript.d.ts.map +1 -0
- package/dist/services/claude-transcript.js +524 -0
- package/dist/services/claude-transcript.js.map +1 -0
- package/dist/services/schedule-store.d.ts +3 -0
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +6 -0
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/services/session-store.d.ts +10 -0
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +40 -0
- package/dist/services/session-store.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +2 -1
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/render-dimensions.d.ts +48 -0
- package/dist/utils/render-dimensions.d.ts.map +1 -0
- package/dist/utils/render-dimensions.js +55 -0
- package/dist/utils/render-dimensions.js.map +1 -0
- package/dist/utils/terminal-renderer.d.ts.map +1 -1
- package/dist/utils/terminal-renderer.js +5 -2
- package/dist/utils/terminal-renderer.js.map +1 -1
- package/dist/worker.js +1068 -37
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createImgNumberer, type MessageResource } from './message-parser.js';
|
|
2
|
+
import { type ForwardedNode } from './forwarded-renderer.js';
|
|
3
|
+
import type { LarkMessage } from '../../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extract a useful one-liner from an AxiosError / generic error.
|
|
6
|
+
* Lark's SDK rethrows AxiosError whose default toString just says
|
|
7
|
+
* "Request failed with status code 400" — hiding the actual server reason.
|
|
8
|
+
*/
|
|
9
|
+
export declare function describeAxiosErr(err: any): string;
|
|
10
|
+
/**
|
|
11
|
+
* Build a tree of ForwardedNode + collect attachment resources from a
|
|
12
|
+
* merge_forward message. Lark's im.v1.message.get returns ALL descendants
|
|
13
|
+
* in one shot via the `items` array, each with `upper_message_id` pointing
|
|
14
|
+
* to its parent — so we make exactly one API call per top-level expand and
|
|
15
|
+
* walk the tree purely in memory. Recursing per nested merge_forward via
|
|
16
|
+
* separate API calls fails (Lark 230002 "Bot/User can NOT be out of the chat")
|
|
17
|
+
* because the bot may be in the outer chat but not in the original chat the
|
|
18
|
+
* nested merge_forward came from. The single-call design sidesteps that.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildForwardedTree(larkAppId: string, rootMessageId: string, numberer: ReturnType<typeof createImgNumberer>, maxDepth: number): Promise<{
|
|
21
|
+
nodes: ForwardedNode[];
|
|
22
|
+
extraResources: MessageResource[];
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Expand a merge_forward message by fetching sub-messages via Lark API.
|
|
26
|
+
* Replaces parsed.content with an XML-rendered forward tree (deduplicated
|
|
27
|
+
* participants, alias-referenced messages) and collects additional resources.
|
|
28
|
+
*/
|
|
29
|
+
export declare function expandMergeForward(larkAppId: string, messageId: string, parsed: LarkMessage, numberer?: import("./message-parser.js").ImgNumberer): Promise<{
|
|
30
|
+
extraResources: MessageResource[];
|
|
31
|
+
}>;
|
|
32
|
+
//# sourceMappingURL=merge-forward.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-forward.d.ts","sourceRoot":"","sources":["../../../src/im/lark/merge-forward.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,iBAAiB,EAEjB,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAGjD;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,EAC9C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IAAC,cAAc,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC,CA2DxE;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EACzD,QAAQ,4CAAsB,GAC7B,OAAO,CAAC;IAAE,cAAc,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC,CAYhD"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { getMessageDetail } from './client.js';
|
|
2
|
+
import { parseApiMessage, extractResources, createImgNumberer, unwrapUserDslContent, } from './message-parser.js';
|
|
3
|
+
import { renderForwardedXml } from './forwarded-renderer.js';
|
|
4
|
+
import { logger } from '../../utils/logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* Extract a useful one-liner from an AxiosError / generic error.
|
|
7
|
+
* Lark's SDK rethrows AxiosError whose default toString just says
|
|
8
|
+
* "Request failed with status code 400" — hiding the actual server reason.
|
|
9
|
+
*/
|
|
10
|
+
export function describeAxiosErr(err) {
|
|
11
|
+
const detail = err?.response?.data ?? err?.message ?? String(err);
|
|
12
|
+
return typeof detail === 'string' ? detail : JSON.stringify(detail);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build a tree of ForwardedNode + collect attachment resources from a
|
|
16
|
+
* merge_forward message. Lark's im.v1.message.get returns ALL descendants
|
|
17
|
+
* in one shot via the `items` array, each with `upper_message_id` pointing
|
|
18
|
+
* to its parent — so we make exactly one API call per top-level expand and
|
|
19
|
+
* walk the tree purely in memory. Recursing per nested merge_forward via
|
|
20
|
+
* separate API calls fails (Lark 230002 "Bot/User can NOT be out of the chat")
|
|
21
|
+
* because the bot may be in the outer chat but not in the original chat the
|
|
22
|
+
* nested merge_forward came from. The single-call design sidesteps that.
|
|
23
|
+
*/
|
|
24
|
+
export async function buildForwardedTree(larkAppId, rootMessageId, numberer, maxDepth) {
|
|
25
|
+
// user_card_content combined with merge_forward triggers HTTP 500 — keep
|
|
26
|
+
// it off. Sub-messages come back in the simplified "Format A" card shape
|
|
27
|
+
// which extractCardContent already handles.
|
|
28
|
+
const detail = await getMessageDetail(larkAppId, rootMessageId, { userCardContent: false });
|
|
29
|
+
const allItems = detail?.items ?? [];
|
|
30
|
+
const extraResources = [];
|
|
31
|
+
// Index children by upper_message_id for O(1) lookup during the walk.
|
|
32
|
+
const childrenByParent = new Map();
|
|
33
|
+
for (const msg of allItems) {
|
|
34
|
+
const parent = msg.upper_message_id ?? '';
|
|
35
|
+
if (!parent)
|
|
36
|
+
continue;
|
|
37
|
+
const list = childrenByParent.get(parent) ?? [];
|
|
38
|
+
list.push(msg);
|
|
39
|
+
childrenByParent.set(parent, list);
|
|
40
|
+
}
|
|
41
|
+
function walk(parentId, depth) {
|
|
42
|
+
const children = childrenByParent.get(parentId) ?? [];
|
|
43
|
+
const nodes = [];
|
|
44
|
+
for (const msg of children) {
|
|
45
|
+
const senderType = msg.sender?.sender_type === 'app' ? 'app'
|
|
46
|
+
: msg.sender?.sender_type === 'user' ? 'user'
|
|
47
|
+
: 'unknown';
|
|
48
|
+
const senderOpenId = msg.sender?.id ?? '';
|
|
49
|
+
// Interactive sub-messages arrive via REST as a simplified fallback.
|
|
50
|
+
// Lark's im.message.get never returns user_dsl (even for the bot's own
|
|
51
|
+
// messages), so we can only unwrap when a user_dsl somehow got through.
|
|
52
|
+
// For third-party cards whose simplified form is the "请升级至最新版本"
|
|
53
|
+
// fallback, the real body is unrecoverable from REST.
|
|
54
|
+
if (msg.msg_type === 'interactive') {
|
|
55
|
+
const unwrapped = unwrapUserDslContent(msg.body?.content ?? '');
|
|
56
|
+
if (unwrapped !== null) {
|
|
57
|
+
msg.body = { ...(msg.body ?? {}), content: unwrapped };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Resources first so the numberer assigns [图片 N] in attachment order;
|
|
61
|
+
// text extraction below reuses those numbers. Do NOT override messageId —
|
|
62
|
+
// Lark requires the parent merge_forward's message_id to download
|
|
63
|
+
// resources (error 234003 if sub-message ID is used).
|
|
64
|
+
const subResources = extractResources(msg.msg_type ?? 'text', msg.body?.content ?? '', numberer);
|
|
65
|
+
extraResources.push(...subResources);
|
|
66
|
+
if (msg.msg_type === 'merge_forward' && depth < maxDepth) {
|
|
67
|
+
const inner = walk(msg.message_id, depth + 1);
|
|
68
|
+
nodes.push({ senderOpenId, senderType, children: inner });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const sub = parseApiMessage(msg, numberer);
|
|
72
|
+
nodes.push({ senderOpenId, senderType, content: sub.content });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return nodes;
|
|
76
|
+
}
|
|
77
|
+
return { nodes: walk(rootMessageId, 0), extraResources };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Expand a merge_forward message by fetching sub-messages via Lark API.
|
|
81
|
+
* Replaces parsed.content with an XML-rendered forward tree (deduplicated
|
|
82
|
+
* participants, alias-referenced messages) and collects additional resources.
|
|
83
|
+
*/
|
|
84
|
+
export async function expandMergeForward(larkAppId, messageId, parsed, numberer = createImgNumberer()) {
|
|
85
|
+
const MAX_DEPTH = 5;
|
|
86
|
+
try {
|
|
87
|
+
const { nodes, extraResources } = await buildForwardedTree(larkAppId, messageId, numberer, MAX_DEPTH);
|
|
88
|
+
if (nodes.length === 0)
|
|
89
|
+
return { extraResources };
|
|
90
|
+
parsed.content = renderForwardedXml(nodes);
|
|
91
|
+
parsed.msgType = 'merge_forward_expanded';
|
|
92
|
+
return { extraResources };
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
logger.warn(`Failed to expand merge_forward ${messageId}: ${describeAxiosErr(err)}`);
|
|
96
|
+
return { extraResources: [] };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=merge-forward.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-forward.js","sourceRoot":"","sources":["../../../src/im/lark/merge-forward.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision logic for "should the worker suppress its transcript-driven
|
|
3
|
+
* fallback emit for this Lark turn?"
|
|
4
|
+
*
|
|
5
|
+
* Pure function with no I/O — kept separate from worker.ts so the rules
|
|
6
|
+
* (including the type-ahead window and the adopt-vs-non-adopt branching)
|
|
7
|
+
* can be tested deterministically. The worker reads marker entries from
|
|
8
|
+
* disk and threads them through here.
|
|
9
|
+
*
|
|
10
|
+
* Rules:
|
|
11
|
+
* - Adopt mode never suppresses: in /adopt the model in the adopted
|
|
12
|
+
* session is unaware of botmux, so transcript drain is the ONLY
|
|
13
|
+
* channel from model to Lark. There's no `botmux send` to compete
|
|
14
|
+
* with, hence no marker to gate on.
|
|
15
|
+
* - Non-adopt + isLocal: suppress. A local-typing turn means the
|
|
16
|
+
* attribution queue saw a user event whose content didn't match any
|
|
17
|
+
* pending Lark fingerprint. In a worker-spawned CLI that's a Web
|
|
18
|
+
* terminal hand-typed input — the user is already looking at it, no
|
|
19
|
+
* reason to push it back to the Lark thread.
|
|
20
|
+
* - Non-adopt + send observed in window: suppress. The window is
|
|
21
|
+
* [turn.markTimeMs, nextBoundaryMs); any `botmux send` whose
|
|
22
|
+
* sentAtMs falls inside means the model already delivered this turn
|
|
23
|
+
* to Lark itself. Boundary handling intentionally also considers
|
|
24
|
+
* queue items that haven't reached "ready" yet (passed in via
|
|
25
|
+
* nextBoundaryMs) — without that, a model that's still mid-tool-use
|
|
26
|
+
* for turn N+1 could leak a send credit into turn N's window.
|
|
27
|
+
*/
|
|
28
|
+
export interface BridgeSendMarker {
|
|
29
|
+
sentAtMs: number;
|
|
30
|
+
messageId?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface BridgeGateInput {
|
|
33
|
+
/** When the user message was queued — defines the lower bound of the
|
|
34
|
+
* send window. Undefined for legacy turns; the gate degrades to
|
|
35
|
+
* "never suppress" in that case. */
|
|
36
|
+
markTimeMs: number | undefined;
|
|
37
|
+
/** Whether the queue synthesised this turn from a local-terminal event
|
|
38
|
+
* (no fingerprint match for a Lark message). */
|
|
39
|
+
isLocal: boolean | undefined;
|
|
40
|
+
}
|
|
41
|
+
export declare function shouldSuppressBridgeEmit(turn: BridgeGateInput, nextBoundaryMs: number | undefined, markers: readonly BridgeSendMarker[], adoptMode: boolean): boolean;
|
|
42
|
+
//# sourceMappingURL=bridge-fallback-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-fallback-gate.d.ts","sourceRoot":"","sources":["../../src/services/bridge-fallback-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B;;yCAEqC;IACrC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B;qDACiD;IACjD,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,eAAe,EACrB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,SAAS,EAAE,OAAO,GACjB,OAAO,CAOT"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function shouldSuppressBridgeEmit(turn, nextBoundaryMs, markers, adoptMode) {
|
|
2
|
+
if (adoptMode)
|
|
3
|
+
return false;
|
|
4
|
+
if (turn.isLocal)
|
|
5
|
+
return true;
|
|
6
|
+
if (turn.markTimeMs === undefined)
|
|
7
|
+
return false;
|
|
8
|
+
const lower = turn.markTimeMs;
|
|
9
|
+
const upper = nextBoundaryMs ?? Number.POSITIVE_INFINITY;
|
|
10
|
+
return markers.some(m => m.sentAtMs >= lower && m.sentAtMs < upper);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=bridge-fallback-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-fallback-gate.js","sourceRoot":"","sources":["../../src/services/bridge-fallback-gate.ts"],"names":[],"mappings":"AA0CA,MAAM,UAAU,wBAAwB,CACtC,IAAqB,EACrB,cAAkC,EAClC,OAAoC,EACpC,SAAkB;IAElB,IAAI,SAAS;QAAE,OAAO,KAAK,CAAC;IAC5B,IAAI,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;IAC9B,MAAM,KAAK,GAAG,cAAc,IAAI,MAAM,CAAC,iBAAiB,CAAC;IACzD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adopt-bridge turn attribution state machine.
|
|
3
|
+
*
|
|
4
|
+
* Pure (no fs / IPC / timers) so the worker can wrap it with watchers and
|
|
5
|
+
* tests can drive it deterministically. The worker feeds it transcript
|
|
6
|
+
* events (already drained from JSONL) and Lark-message markers; this class
|
|
7
|
+
* decides which assistant uuids belong to which Lark turn.
|
|
8
|
+
*
|
|
9
|
+
* Attribution rule:
|
|
10
|
+
* - mark() — pushes a new pending turn entry (state: not started)
|
|
11
|
+
* - ingest(events) — for each new user/assistant event:
|
|
12
|
+
* * user event → the earliest unstarted pending turn whose fingerprint
|
|
13
|
+
* matches becomes 'started' (its assistantUuids will collect from
|
|
14
|
+
* now on). A user event that does NOT match any pending fingerprint
|
|
15
|
+
* (or arrives with no pending Lark turn at all) is treated as
|
|
16
|
+
* **local terminal input**: a synthetic local turn is created on
|
|
17
|
+
* the spot, started immediately, and inserted ahead of any
|
|
18
|
+
* still-unstarted Lark turns so emit ordering reflects when the
|
|
19
|
+
* user event actually landed in the transcript. The local turn is
|
|
20
|
+
* emitted with `isLocal: true` so the worker can format it with a
|
|
21
|
+
* "user typed in the terminal" marker for the Lark thread.
|
|
22
|
+
* * assistant text event (non-sidechain) → appended to the
|
|
23
|
+
* currently-collecting turn (Lark or local), if any.
|
|
24
|
+
* - drainEmittable() — pops any leading turn that has been started AND has
|
|
25
|
+
* accumulated at least one visible assistant-text uuid. Started turns with no text
|
|
26
|
+
* yet (Claude is mid-tool-use) stay queued for the next idle.
|
|
27
|
+
*
|
|
28
|
+
* Baseline (`absorb()`) takes a batch of historical events and registers
|
|
29
|
+
* their uuids as already-seen so future ingest doesn't double-attribute.
|
|
30
|
+
*/
|
|
31
|
+
import { normaliseForFingerprint, type TranscriptEvent } from './claude-transcript.js';
|
|
32
|
+
export { normaliseForFingerprint };
|
|
33
|
+
export interface BridgePendingTurn {
|
|
34
|
+
turnId: string;
|
|
35
|
+
started: boolean;
|
|
36
|
+
assistantUuids: string[];
|
|
37
|
+
/** Set when this turn was synthesised from a local-terminal user event
|
|
38
|
+
* (no matching Lark fingerprint). Causes the worker emit path to format
|
|
39
|
+
* the Lark message with both user text and assistant text under a
|
|
40
|
+
* "🖥️ 终端本地对话" header — otherwise the user would see an orphan
|
|
41
|
+
* reply with no prompt for context. Lark-driven turns keep this unset. */
|
|
42
|
+
isLocal?: boolean;
|
|
43
|
+
/** Transcript uuid of the user event that started this turn. Stored for
|
|
44
|
+
* local turns so emit can fetch the user-typed content from the source
|
|
45
|
+
* jsonl alongside the assistant uuids. Lark turns don't need it because
|
|
46
|
+
* the user content is already known on the daemon side. */
|
|
47
|
+
userUuid?: string;
|
|
48
|
+
/** A short substring of the Lark message that we expect to find inside
|
|
49
|
+
* the next matching `user` event's content. When set, only a user event
|
|
50
|
+
* whose stringified content contains this fingerprint is allowed to
|
|
51
|
+
* start the turn. Local-terminal input (whose content won't contain
|
|
52
|
+
* the Lark fingerprint) leaves the turn unstarted. */
|
|
53
|
+
contentFingerprint?: string;
|
|
54
|
+
/** JSONL file the turn's user event was first seen in. Stamped by ingest()
|
|
55
|
+
* when the turn transitions to started. Lets the emit step re-read text
|
|
56
|
+
* from the original transcript even after a sessionId rotation has
|
|
57
|
+
* pointed bridgeJsonlPath at a *different* file — without this stamp,
|
|
58
|
+
* uuid → text resolution would fail and the reply would be silently
|
|
59
|
+
* dropped. */
|
|
60
|
+
sourceJsonlPath?: string;
|
|
61
|
+
/** Wall-clock millis when mark() was called. Lets the fingerprint-based
|
|
62
|
+
* rotation fallback bound its scan to events written after we marked
|
|
63
|
+
* the turn — short fingerprints ("hello", "test") would otherwise risk
|
|
64
|
+
* matching pre-existing user lines in unrelated sibling jsonls. */
|
|
65
|
+
markTimeMs?: number;
|
|
66
|
+
}
|
|
67
|
+
/** Trim a Lark message into a stable fingerprint. Keeps a leading window
|
|
68
|
+
* of non-whitespace-collapsed content; long enough to disambiguate, short
|
|
69
|
+
* enough that minor formatting differences (newlines, attachment hints
|
|
70
|
+
* appended below) don't break the match. */
|
|
71
|
+
export declare function makeFingerprint(message: string, len?: number): string | undefined;
|
|
72
|
+
export declare class BridgeTurnQueue {
|
|
73
|
+
private seen;
|
|
74
|
+
private queue;
|
|
75
|
+
private collecting;
|
|
76
|
+
/** Register events as historical — their uuids are now considered seen
|
|
77
|
+
* but no attribution happens. Used at attach time to baseline. */
|
|
78
|
+
absorb(events: TranscriptEvent[]): void;
|
|
79
|
+
/** Push a new pending turn for the next Lark message. `contentFingerprint`
|
|
80
|
+
* (when set) restricts which user event can start this turn — only a
|
|
81
|
+
* user event whose content contains the fingerprint qualifies. Pass
|
|
82
|
+
* `undefined` to start on the next user event regardless (legacy).
|
|
83
|
+
*
|
|
84
|
+
* `markTimeMs` is captured here so the rotation fallback can bound its
|
|
85
|
+
* fingerprint scan to events written after this point — protects short
|
|
86
|
+
* fingerprints from matching old history in unrelated sibling jsonls. */
|
|
87
|
+
mark(turnId: string, contentFingerprint?: string, markTimeMs?: number): void;
|
|
88
|
+
/** Drop all pending turns. Used when the worker discovers it can't
|
|
89
|
+
* reliably attribute future events (e.g. baseline raced with a turn
|
|
90
|
+
* already in flight) and wants to clear the slate. */
|
|
91
|
+
clearPending(): BridgePendingTurn[];
|
|
92
|
+
/** Process newly-appended events. Idempotent on uuid: events with seen
|
|
93
|
+
* uuids are skipped, so callers can safely replay.
|
|
94
|
+
*
|
|
95
|
+
* `sourceJsonlPath` (when provided) is stamped onto a turn at the moment
|
|
96
|
+
* it transitions from "pending" to "started" — so that emit-time text
|
|
97
|
+
* resolution reads the same transcript file the user/assistant uuids
|
|
98
|
+
* were originally observed in. Without this, a sessionId rotation
|
|
99
|
+
* between ingest and emit would silently drop the reply, since the
|
|
100
|
+
* global current jsonl path would no longer contain those uuids. */
|
|
101
|
+
ingest(events: TranscriptEvent[], sourceJsonlPath?: string): void;
|
|
102
|
+
/** Pop FIFO any leading turn that's started AND has assistant text.
|
|
103
|
+
* Returns the popped turns in order; the caller is responsible for
|
|
104
|
+
* rebuilding the text payload from the assistant uuids. */
|
|
105
|
+
drainEmittable(): BridgePendingTurn[];
|
|
106
|
+
/** Number of queued (not-yet-emitted) Lark turns. */
|
|
107
|
+
size(): number;
|
|
108
|
+
/** Test helper — peek the queue without mutating. */
|
|
109
|
+
peek(): readonly BridgePendingTurn[];
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=bridge-turn-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-turn-queue.d.ts","sourceRoot":"","sources":["../../src/services/bridge-turn-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAwB,uBAAuB,EAAyB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAIpI,OAAO,EAAE,uBAAuB,EAAE,CAAC;AAEnC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;;;;+EAI2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;gEAG4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;2DAIuD;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;mBAKe;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;wEAGoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD;;;6CAG6C;AAC7C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,MAAM,GAAG,SAAS,CAK7E;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,UAAU,CAAkC;IAEpD;uEACmE;IACnE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI;IAMvC;;;;;;;8EAO0E;IAC1E,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,EAAE,UAAU,GAAE,MAAmB,GAAG,IAAI;IAIxF;;2DAEuD;IACvD,YAAY,IAAI,iBAAiB,EAAE;IAMnC;;;;;;;;yEAQqE;IACrE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI;IAoFjE;;gEAE4D;IAC5D,cAAc,IAAI,iBAAiB,EAAE;IAYrC,qDAAqD;IACrD,IAAI,IAAI,MAAM;IAId,qDAAqD;IACrD,IAAI,IAAI,SAAS,iBAAiB,EAAE;CAGrC"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adopt-bridge turn attribution state machine.
|
|
3
|
+
*
|
|
4
|
+
* Pure (no fs / IPC / timers) so the worker can wrap it with watchers and
|
|
5
|
+
* tests can drive it deterministically. The worker feeds it transcript
|
|
6
|
+
* events (already drained from JSONL) and Lark-message markers; this class
|
|
7
|
+
* decides which assistant uuids belong to which Lark turn.
|
|
8
|
+
*
|
|
9
|
+
* Attribution rule:
|
|
10
|
+
* - mark() — pushes a new pending turn entry (state: not started)
|
|
11
|
+
* - ingest(events) — for each new user/assistant event:
|
|
12
|
+
* * user event → the earliest unstarted pending turn whose fingerprint
|
|
13
|
+
* matches becomes 'started' (its assistantUuids will collect from
|
|
14
|
+
* now on). A user event that does NOT match any pending fingerprint
|
|
15
|
+
* (or arrives with no pending Lark turn at all) is treated as
|
|
16
|
+
* **local terminal input**: a synthetic local turn is created on
|
|
17
|
+
* the spot, started immediately, and inserted ahead of any
|
|
18
|
+
* still-unstarted Lark turns so emit ordering reflects when the
|
|
19
|
+
* user event actually landed in the transcript. The local turn is
|
|
20
|
+
* emitted with `isLocal: true` so the worker can format it with a
|
|
21
|
+
* "user typed in the terminal" marker for the Lark thread.
|
|
22
|
+
* * assistant text event (non-sidechain) → appended to the
|
|
23
|
+
* currently-collecting turn (Lark or local), if any.
|
|
24
|
+
* - drainEmittable() — pops any leading turn that has been started AND has
|
|
25
|
+
* accumulated at least one visible assistant-text uuid. Started turns with no text
|
|
26
|
+
* yet (Claude is mid-tool-use) stay queued for the next idle.
|
|
27
|
+
*
|
|
28
|
+
* Baseline (`absorb()`) takes a batch of historical events and registers
|
|
29
|
+
* their uuids as already-seen so future ingest doesn't double-attribute.
|
|
30
|
+
*/
|
|
31
|
+
import { stringifyUserContent, normaliseForFingerprint, isMeaningfulUserEvent } from './claude-transcript.js';
|
|
32
|
+
// Re-export so existing callers (worker.ts, tests) don't need to change
|
|
33
|
+
// their import path now that these helpers live in claude-transcript.ts.
|
|
34
|
+
export { normaliseForFingerprint };
|
|
35
|
+
function assistantHasVisibleText(content) {
|
|
36
|
+
if (typeof content === 'string')
|
|
37
|
+
return content.length > 0;
|
|
38
|
+
if (!Array.isArray(content))
|
|
39
|
+
return false;
|
|
40
|
+
return content.some((block) => block?.type === 'text' && typeof block.text === 'string' && block.text.length > 0);
|
|
41
|
+
}
|
|
42
|
+
/** Trim a Lark message into a stable fingerprint. Keeps a leading window
|
|
43
|
+
* of non-whitespace-collapsed content; long enough to disambiguate, short
|
|
44
|
+
* enough that minor formatting differences (newlines, attachment hints
|
|
45
|
+
* appended below) don't break the match. */
|
|
46
|
+
export function makeFingerprint(message, len = 30) {
|
|
47
|
+
if (typeof message !== 'string')
|
|
48
|
+
return undefined;
|
|
49
|
+
const collapsed = normaliseForFingerprint(message);
|
|
50
|
+
if (collapsed.length === 0)
|
|
51
|
+
return undefined;
|
|
52
|
+
return collapsed.substring(0, len);
|
|
53
|
+
}
|
|
54
|
+
export class BridgeTurnQueue {
|
|
55
|
+
seen = new Set();
|
|
56
|
+
queue = [];
|
|
57
|
+
collecting = null;
|
|
58
|
+
/** Register events as historical — their uuids are now considered seen
|
|
59
|
+
* but no attribution happens. Used at attach time to baseline. */
|
|
60
|
+
absorb(events) {
|
|
61
|
+
for (const ev of events) {
|
|
62
|
+
if (ev.uuid)
|
|
63
|
+
this.seen.add(ev.uuid);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Push a new pending turn for the next Lark message. `contentFingerprint`
|
|
67
|
+
* (when set) restricts which user event can start this turn — only a
|
|
68
|
+
* user event whose content contains the fingerprint qualifies. Pass
|
|
69
|
+
* `undefined` to start on the next user event regardless (legacy).
|
|
70
|
+
*
|
|
71
|
+
* `markTimeMs` is captured here so the rotation fallback can bound its
|
|
72
|
+
* fingerprint scan to events written after this point — protects short
|
|
73
|
+
* fingerprints from matching old history in unrelated sibling jsonls. */
|
|
74
|
+
mark(turnId, contentFingerprint, markTimeMs = Date.now()) {
|
|
75
|
+
this.queue.push({ turnId, started: false, assistantUuids: [], contentFingerprint, markTimeMs });
|
|
76
|
+
}
|
|
77
|
+
/** Drop all pending turns. Used when the worker discovers it can't
|
|
78
|
+
* reliably attribute future events (e.g. baseline raced with a turn
|
|
79
|
+
* already in flight) and wants to clear the slate. */
|
|
80
|
+
clearPending() {
|
|
81
|
+
const dropped = this.queue.splice(0);
|
|
82
|
+
if (this.collecting && dropped.includes(this.collecting))
|
|
83
|
+
this.collecting = null;
|
|
84
|
+
return dropped;
|
|
85
|
+
}
|
|
86
|
+
/** Process newly-appended events. Idempotent on uuid: events with seen
|
|
87
|
+
* uuids are skipped, so callers can safely replay.
|
|
88
|
+
*
|
|
89
|
+
* `sourceJsonlPath` (when provided) is stamped onto a turn at the moment
|
|
90
|
+
* it transitions from "pending" to "started" — so that emit-time text
|
|
91
|
+
* resolution reads the same transcript file the user/assistant uuids
|
|
92
|
+
* were originally observed in. Without this, a sessionId rotation
|
|
93
|
+
* between ingest and emit would silently drop the reply, since the
|
|
94
|
+
* global current jsonl path would no longer contain those uuids. */
|
|
95
|
+
ingest(events, sourceJsonlPath) {
|
|
96
|
+
for (const ev of events) {
|
|
97
|
+
const uuid = ev.uuid;
|
|
98
|
+
if (!uuid || this.seen.has(uuid))
|
|
99
|
+
continue;
|
|
100
|
+
this.seen.add(uuid);
|
|
101
|
+
const role = ev.message?.role ?? ev.type;
|
|
102
|
+
if (role === 'user') {
|
|
103
|
+
// Skip ALL non-meaningful user events: tool_result (intra-turn
|
|
104
|
+
// machinery), `<command-name>/clear</command-name>` and other
|
|
105
|
+
// slash-command wrappers (Claude rewrites them after /clear /
|
|
106
|
+
// /resume — same in-process rotation that broke bridge tracking
|
|
107
|
+
// before), isMeta / isCompactSummary markers, sidechain spawns,
|
|
108
|
+
// empty content. These are NOT real user input; treating them as
|
|
109
|
+
// turn boundaries would (a) drop `collecting` mid-stream and lose
|
|
110
|
+
// assistant text after them, and (b) let a synthetic line that
|
|
111
|
+
// accidentally contains the fingerprint substring start the
|
|
112
|
+
// wrong turn.
|
|
113
|
+
if (!isMeaningfulUserEvent(ev))
|
|
114
|
+
continue;
|
|
115
|
+
// Defensive: if the previous local turn never accumulated any
|
|
116
|
+
// assistant text (Claude crashed / was killed / user cancelled
|
|
117
|
+
// mid-response), drop it now so its empty `assistantUuids` doesn't
|
|
118
|
+
// head-of-line block every subsequent emit. Lark turns are NOT
|
|
119
|
+
// dropped here — they may still be in tool-use legitimately, and
|
|
120
|
+
// Lark-side timeout is tracked separately by the daemon.
|
|
121
|
+
if (this.collecting && this.collecting.isLocal && this.collecting.assistantUuids.length === 0) {
|
|
122
|
+
const idx = this.queue.indexOf(this.collecting);
|
|
123
|
+
if (idx >= 0)
|
|
124
|
+
this.queue.splice(idx, 1);
|
|
125
|
+
this.collecting = null;
|
|
126
|
+
}
|
|
127
|
+
const next = this.queue.find(t => !t.started);
|
|
128
|
+
let consumedNext = false;
|
|
129
|
+
if (next) {
|
|
130
|
+
// If this turn has a fingerprint, gate on a content match. Both
|
|
131
|
+
// sides are normalised (whitespace-collapsed + trimmed) before
|
|
132
|
+
// the substring check so a transcript line that preserved
|
|
133
|
+
// newlines still matches a fingerprint built from the same text.
|
|
134
|
+
if (next.contentFingerprint) {
|
|
135
|
+
const userText = normaliseForFingerprint(stringifyUserContent(ev.message?.content));
|
|
136
|
+
if (userText.includes(next.contentFingerprint)) {
|
|
137
|
+
next.started = true;
|
|
138
|
+
if (!next.sourceJsonlPath)
|
|
139
|
+
next.sourceJsonlPath = sourceJsonlPath;
|
|
140
|
+
this.collecting = next;
|
|
141
|
+
consumedNext = true;
|
|
142
|
+
}
|
|
143
|
+
// Mismatch falls through to the local-turn branch below.
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Legacy mark() with no fingerprint — start on the next user.
|
|
147
|
+
next.started = true;
|
|
148
|
+
if (!next.sourceJsonlPath)
|
|
149
|
+
next.sourceJsonlPath = sourceJsonlPath;
|
|
150
|
+
this.collecting = next;
|
|
151
|
+
consumedNext = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!consumedNext) {
|
|
155
|
+
// Local-terminal input. Synthesise a started turn so the
|
|
156
|
+
// assistant text that follows is captured (and pushed to the
|
|
157
|
+
// Lark thread) instead of being silently dropped — that's the
|
|
158
|
+
// /adopt symptom users hit when typing directly in the iterm
|
|
159
|
+
// pane. Insert AHEAD of any still-unstarted Lark turn so
|
|
160
|
+
// chronological order is preserved: this user event landed in
|
|
161
|
+
// the transcript before the next Lark turn's user event will.
|
|
162
|
+
const localTurn = {
|
|
163
|
+
turnId: `local-${uuid}`,
|
|
164
|
+
started: true,
|
|
165
|
+
isLocal: true,
|
|
166
|
+
userUuid: uuid,
|
|
167
|
+
assistantUuids: [],
|
|
168
|
+
sourceJsonlPath,
|
|
169
|
+
markTimeMs: Date.now(),
|
|
170
|
+
};
|
|
171
|
+
const insertAt = this.queue.findIndex(t => !t.started);
|
|
172
|
+
if (insertAt === -1)
|
|
173
|
+
this.queue.push(localTurn);
|
|
174
|
+
else
|
|
175
|
+
this.queue.splice(insertAt, 0, localTurn);
|
|
176
|
+
this.collecting = localTurn;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (role === 'assistant') {
|
|
180
|
+
if (ev.isSidechain === true)
|
|
181
|
+
continue;
|
|
182
|
+
if (this.collecting && assistantHasVisibleText(ev.message?.content)) {
|
|
183
|
+
this.collecting.assistantUuids.push(uuid);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Pop FIFO any leading turn that's started AND has assistant text.
|
|
189
|
+
* Returns the popped turns in order; the caller is responsible for
|
|
190
|
+
* rebuilding the text payload from the assistant uuids. */
|
|
191
|
+
drainEmittable() {
|
|
192
|
+
const out = [];
|
|
193
|
+
while (this.queue.length > 0) {
|
|
194
|
+
const head = this.queue[0];
|
|
195
|
+
if (!head.started || head.assistantUuids.length === 0)
|
|
196
|
+
break;
|
|
197
|
+
this.queue.shift();
|
|
198
|
+
if (this.collecting === head)
|
|
199
|
+
this.collecting = null;
|
|
200
|
+
out.push(head);
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
/** Number of queued (not-yet-emitted) Lark turns. */
|
|
205
|
+
size() {
|
|
206
|
+
return this.queue.length;
|
|
207
|
+
}
|
|
208
|
+
/** Test helper — peek the queue without mutating. */
|
|
209
|
+
peek() {
|
|
210
|
+
return this.queue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=bridge-turn-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-turn-queue.js","sourceRoot":"","sources":["../../src/services/bridge-turn-queue.ts"],"names":[],"mappings":"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"}
|