botmux 2.9.1 → 2.9.3
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 +140 -76
- package/README.md +134 -75
- 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 +10 -0
- package/dist/adapters/backend/pty-backend.js.map +1 -1
- package/dist/adapters/backend/session-backend-selector.d.ts +11 -0
- package/dist/adapters/backend/session-backend-selector.d.ts.map +1 -0
- package/dist/adapters/backend/session-backend-selector.js +26 -0
- package/dist/adapters/backend/session-backend-selector.js.map +1 -0
- package/dist/adapters/backend/tmux-backend.d.ts +80 -3
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +301 -49
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +100 -0
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -0
- package/dist/adapters/backend/tmux-pipe-backend.js +473 -0
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -0
- package/dist/adapters/cli/aiden.d.ts.map +1 -1
- package/dist/adapters/cli/aiden.js +5 -0
- package/dist/adapters/cli/aiden.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts +40 -1
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +470 -49
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/coco.d.ts.map +1 -1
- package/dist/adapters/cli/coco.js +191 -9
- package/dist/adapters/cli/coco.js.map +1 -1
- package/dist/adapters/cli/codex.d.ts.map +1 -1
- package/dist/adapters/cli/codex.js +94 -17
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts +2 -2
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +7 -5
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +38 -1
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/autostart.d.ts +14 -0
- package/dist/autostart.d.ts.map +1 -0
- package/dist/autostart.js +357 -0
- package/dist/autostart.js.map +1 -0
- package/dist/bot-registry.d.ts +29 -3
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +91 -12
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/arg-utils.d.ts +11 -0
- package/dist/cli/arg-utils.d.ts.map +1 -0
- package/dist/cli/arg-utils.js +25 -0
- package/dist/cli/arg-utils.js.map +1 -0
- package/dist/cli/create-group-resolver.d.ts +32 -0
- package/dist/cli/create-group-resolver.d.ts.map +1 -0
- package/dist/cli/create-group-resolver.js +70 -0
- package/dist/cli/create-group-resolver.js.map +1 -0
- package/dist/cli/quoted-render.d.ts +30 -0
- package/dist/cli/quoted-render.d.ts.map +1 -0
- package/dist/cli/quoted-render.js +29 -0
- package/dist/cli/quoted-render.js.map +1 -0
- package/dist/cli.js +916 -272
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -8
- package/dist/config.js.map +1 -1
- package/dist/core/command-handler.d.ts +43 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +167 -64
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-events.d.ts +57 -0
- package/dist/core/dashboard-events.d.ts.map +1 -0
- package/dist/core/dashboard-events.js +23 -0
- package/dist/core/dashboard-events.js.map +1 -0
- package/dist/core/dashboard-ipc-server.d.ts +43 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -0
- package/dist/core/dashboard-ipc-server.js +481 -0
- package/dist/core/dashboard-ipc-server.js.map +1 -0
- package/dist/core/dashboard-locate.d.ts +20 -0
- package/dist/core/dashboard-locate.d.ts.map +1 -0
- package/dist/core/dashboard-locate.js +26 -0
- package/dist/core/dashboard-locate.js.map +1 -0
- package/dist/core/dashboard-rows.d.ts +31 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -0
- package/dist/core/dashboard-rows.js +65 -0
- package/dist/core/dashboard-rows.js.map +1 -0
- package/dist/core/inherit-peer.d.ts +14 -0
- package/dist/core/inherit-peer.d.ts.map +1 -0
- package/dist/core/inherit-peer.js +32 -0
- package/dist/core/inherit-peer.js.map +1 -0
- package/dist/core/scheduler.d.ts +24 -0
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +93 -2
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/session-activity.d.ts +3 -0
- package/dist/core/session-activity.d.ts.map +1 -0
- package/dist/core/session-activity.js +20 -0
- package/dist/core/session-activity.js.map +1 -0
- package/dist/core/session-discovery.d.ts +39 -0
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +114 -21
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-manager.d.ts +72 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +396 -106
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/types.d.ts +27 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +14 -3
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +72 -3
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +459 -38
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +645 -314
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/aggregator.d.ts +41 -0
- package/dist/dashboard/aggregator.d.ts.map +1 -0
- package/dist/dashboard/aggregator.js +125 -0
- package/dist/dashboard/aggregator.js.map +1 -0
- package/dist/dashboard/auth.d.ts +23 -0
- package/dist/dashboard/auth.d.ts.map +1 -0
- package/dist/dashboard/auth.js +66 -0
- package/dist/dashboard/auth.js.map +1 -0
- package/dist/dashboard/operator-selector.d.ts +20 -0
- package/dist/dashboard/operator-selector.d.ts.map +1 -0
- package/dist/dashboard/operator-selector.js +39 -0
- package/dist/dashboard/operator-selector.js.map +1 -0
- package/dist/dashboard/registry.d.ts +35 -0
- package/dist/dashboard/registry.d.ts.map +1 -0
- package/dist/dashboard/registry.js +74 -0
- package/dist/dashboard/registry.js.map +1 -0
- package/dist/dashboard/web/app.d.ts +2 -0
- package/dist/dashboard/web/app.d.ts.map +1 -0
- package/dist/dashboard/web/app.js +45 -0
- package/dist/dashboard/web/app.js.map +1 -0
- package/dist/dashboard/web/bot-defaults.d.ts +2 -0
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -0
- package/dist/dashboard/web/bot-defaults.js +201 -0
- package/dist/dashboard/web/bot-defaults.js.map +1 -0
- package/dist/dashboard/web/groups.d.ts +16 -0
- package/dist/dashboard/web/groups.d.ts.map +1 -0
- package/dist/dashboard/web/groups.js +584 -0
- package/dist/dashboard/web/groups.js.map +1 -0
- package/dist/dashboard/web/schedules.d.ts +2 -0
- package/dist/dashboard/web/schedules.d.ts.map +1 -0
- package/dist/dashboard/web/schedules.js +105 -0
- package/dist/dashboard/web/schedules.js.map +1 -0
- package/dist/dashboard/web/sessions.d.ts +2 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -0
- package/dist/dashboard/web/sessions.js +374 -0
- package/dist/dashboard/web/sessions.js.map +1 -0
- package/dist/dashboard/web/store.d.ts +23 -0
- package/dist/dashboard/web/store.d.ts.map +1 -0
- package/dist/dashboard/web/store.js +82 -0
- package/dist/dashboard/web/store.js.map +1 -0
- package/dist/dashboard-web/app.js +263 -0
- package/dist/dashboard-web/index.html +23 -0
- package/dist/dashboard-web/style.css +93 -0
- package/dist/dashboard.d.ts +2 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +639 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/im/lark/card-builder.d.ts +18 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +70 -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 +123 -109
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +35 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +114 -11
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +88 -6
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +398 -62
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/forwarded-renderer.d.ts +23 -0
- package/dist/im/lark/forwarded-renderer.d.ts.map +1 -0
- package/dist/im/lark/forwarded-renderer.js +105 -0
- package/dist/im/lark/forwarded-renderer.js.map +1 -0
- package/dist/im/lark/md-card.d.ts +73 -0
- package/dist/im/lark/md-card.d.ts.map +1 -0
- package/dist/im/lark/md-card.js +332 -0
- package/dist/im/lark/md-card.js.map +1 -0
- 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 +110 -0
- package/dist/im/lark/merge-forward.js.map +1 -0
- package/dist/im/lark/message-parser.d.ts +9 -3
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +48 -13
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/im/lark/quote-hint.d.ts +18 -0
- package/dist/im/lark/quote-hint.d.ts.map +1 -0
- package/dist/im/lark/quote-hint.js +23 -0
- package/dist/im/lark/quote-hint.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-rotation-policy.d.ts +139 -0
- package/dist/services/bridge-rotation-policy.d.ts.map +1 -0
- package/dist/services/bridge-rotation-policy.js +125 -0
- package/dist/services/bridge-rotation-policy.js.map +1 -0
- package/dist/services/bridge-turn-queue.d.ts +154 -0
- package/dist/services/bridge-turn-queue.d.ts.map +1 -0
- package/dist/services/bridge-turn-queue.js +316 -0
- package/dist/services/bridge-turn-queue.js.map +1 -0
- package/dist/services/chat-first-seen-store.d.ts +27 -0
- package/dist/services/chat-first-seen-store.d.ts.map +1 -0
- package/dist/services/chat-first-seen-store.js +114 -0
- package/dist/services/chat-first-seen-store.js.map +1 -0
- package/dist/services/claude-transcript.d.ts +268 -0
- package/dist/services/claude-transcript.d.ts.map +1 -0
- package/dist/services/claude-transcript.js +798 -0
- package/dist/services/claude-transcript.js.map +1 -0
- package/dist/services/coco-transcript.d.ts +35 -0
- package/dist/services/coco-transcript.d.ts.map +1 -0
- package/dist/services/coco-transcript.js +192 -0
- package/dist/services/coco-transcript.js.map +1 -0
- package/dist/services/codex-bridge-queue.d.ts +56 -0
- package/dist/services/codex-bridge-queue.d.ts.map +1 -0
- package/dist/services/codex-bridge-queue.js +150 -0
- package/dist/services/codex-bridge-queue.js.map +1 -0
- package/dist/services/codex-transcript.d.ts +84 -0
- package/dist/services/codex-transcript.d.ts.map +1 -0
- package/dist/services/codex-transcript.js +298 -0
- package/dist/services/codex-transcript.js.map +1 -0
- package/dist/services/group-creator.d.ts +23 -0
- package/dist/services/group-creator.d.ts.map +1 -0
- package/dist/services/group-creator.js +75 -0
- package/dist/services/group-creator.js.map +1 -0
- package/dist/services/groups-store.d.ts +98 -0
- package/dist/services/groups-store.d.ts.map +1 -0
- package/dist/services/groups-store.js +213 -0
- package/dist/services/groups-store.js.map +1 -0
- package/dist/services/oncall-store.d.ts +80 -8
- package/dist/services/oncall-store.d.ts.map +1 -1
- package/dist/services/oncall-store.js +265 -55
- package/dist/services/oncall-store.js.map +1 -1
- package/dist/services/project-scanner.d.ts +1 -2
- package/dist/services/project-scanner.d.ts.map +1 -1
- package/dist/services/project-scanner.js +118 -68
- package/dist/services/project-scanner.js.map +1 -1
- package/dist/services/schedule-store.d.ts +5 -0
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +77 -1
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/services/session-store.d.ts +22 -0
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +62 -4
- package/dist/services/session-store.js.map +1 -1
- package/dist/setup/bots-store.d.ts +3 -0
- package/dist/setup/bots-store.d.ts.map +1 -0
- package/dist/setup/bots-store.js +24 -0
- package/dist/setup/bots-store.js.map +1 -0
- package/dist/setup/detect-platform.d.ts +14 -0
- package/dist/setup/detect-platform.d.ts.map +1 -0
- package/dist/setup/detect-platform.js +139 -0
- package/dist/setup/detect-platform.js.map +1 -0
- package/dist/setup/ensure-fonts.d.ts +13 -0
- package/dist/setup/ensure-fonts.d.ts.map +1 -0
- package/dist/setup/ensure-fonts.js +225 -0
- package/dist/setup/ensure-fonts.js.map +1 -0
- package/dist/setup/ensure-tmux.d.ts +60 -0
- package/dist/setup/ensure-tmux.d.ts.map +1 -0
- package/dist/setup/ensure-tmux.js +236 -0
- package/dist/setup/ensure-tmux.js.map +1 -0
- package/dist/setup/index.d.ts +9 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +46 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/setup/lark-scopes.json +301 -0
- package/dist/setup/register-app.d.ts +52 -0
- package/dist/setup/register-app.d.ts.map +1 -0
- package/dist/setup/register-app.js +91 -0
- package/dist/setup/register-app.js.map +1 -0
- package/dist/setup/verify-permissions.d.ts +115 -0
- package/dist/setup/verify-permissions.d.ts.map +1 -0
- package/dist/setup/verify-permissions.js +207 -0
- package/dist/setup/verify-permissions.js.map +1 -0
- package/dist/skills/definitions.d.ts +4 -0
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +133 -19
- package/dist/skills/definitions.js.map +1 -1
- package/dist/skills/installer.d.ts +3 -1
- package/dist/skills/installer.d.ts.map +1 -1
- package/dist/skills/installer.js +18 -3
- package/dist/skills/installer.js.map +1 -1
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/bot-routing.d.ts +6 -0
- package/dist/utils/bot-routing.d.ts.map +1 -0
- package/dist/utils/bot-routing.js +50 -0
- package/dist/utils/bot-routing.js.map +1 -0
- package/dist/utils/file-lock.d.ts +2 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +114 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/font-installer.js +1 -1
- package/dist/utils/font-installer.js.map +1 -1
- package/dist/utils/idle-detector.d.ts +6 -0
- package/dist/utils/idle-detector.d.ts.map +1 -1
- package/dist/utils/idle-detector.js +25 -4
- package/dist/utils/idle-detector.js.map +1 -1
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +60 -8
- package/dist/utils/logger.js.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/screen-analyzer.d.ts.map +1 -1
- package/dist/utils/screen-analyzer.js +24 -0
- package/dist/utils/screen-analyzer.js.map +1 -1
- package/dist/utils/screenshot-renderer.d.ts.map +1 -1
- package/dist/utils/screenshot-renderer.js +67 -23
- package/dist/utils/screenshot-renderer.js.map +1 -1
- package/dist/utils/terminal-renderer.d.ts +16 -0
- package/dist/utils/terminal-renderer.d.ts.map +1 -1
- package/dist/utils/terminal-renderer.js +40 -23
- package/dist/utils/terminal-renderer.js.map +1 -1
- package/dist/utils/transient-snapshot.d.ts +28 -0
- package/dist/utils/transient-snapshot.d.ts.map +1 -0
- package/dist/utils/transient-snapshot.js +96 -0
- package/dist/utils/transient-snapshot.js.map +1 -0
- package/dist/worker.js +2248 -83
- package/dist/worker.js.map +1 -1
- package/package.json +12 -5
|
@@ -0,0 +1,316 @@
|
|
|
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, isMeaningfulUserEvent, isMeaningfulQueuedCommand, extractTurnStartText } 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(), contentNormalized) {
|
|
75
|
+
this.queue.push({
|
|
76
|
+
turnId,
|
|
77
|
+
started: false,
|
|
78
|
+
assistantUuids: [],
|
|
79
|
+
contentFingerprint,
|
|
80
|
+
contentNormalized,
|
|
81
|
+
markTimeMs,
|
|
82
|
+
});
|
|
83
|
+
return turnId;
|
|
84
|
+
}
|
|
85
|
+
/** Drop all pending turns. Used when the worker discovers it can't
|
|
86
|
+
* reliably attribute future events (e.g. baseline raced with a turn
|
|
87
|
+
* already in flight) and wants to clear the slate. */
|
|
88
|
+
clearPending() {
|
|
89
|
+
const dropped = this.queue.splice(0);
|
|
90
|
+
if (this.collecting && dropped.includes(this.collecting))
|
|
91
|
+
this.collecting = null;
|
|
92
|
+
return dropped;
|
|
93
|
+
}
|
|
94
|
+
/** Drop a specific pending turn by turnId iff it has not yet started
|
|
95
|
+
* collecting assistant text. Returns the dropped turn or null if not
|
|
96
|
+
* found / already started. Used by the worker when a writeInput's
|
|
97
|
+
* deferred recheck conclusively fails — the user has been notified
|
|
98
|
+
* the message was lost, so keeping a fingerprint-bearing mark around
|
|
99
|
+
* only fuels the per-tick rotation-fallback scan that already
|
|
100
|
+
* spammed 99% CPU once (no jsonl line will ever match). */
|
|
101
|
+
dropPendingTurn(turnId) {
|
|
102
|
+
const idx = this.queue.findIndex(t => t.turnId === turnId && !t.started);
|
|
103
|
+
if (idx === -1)
|
|
104
|
+
return null;
|
|
105
|
+
const [dropped] = this.queue.splice(idx, 1);
|
|
106
|
+
return dropped;
|
|
107
|
+
}
|
|
108
|
+
/** Sweep pending (unstarted) turns whose mark is older than `maxAgeMs`.
|
|
109
|
+
* Returns the dropped turns for logging. Belt-and-braces backstop for
|
|
110
|
+
* any future code path that leaves an unstarted mark stranded — without
|
|
111
|
+
* it, `maybeSwitchBridgeJsonl` would keep doing full-directory jsonl
|
|
112
|
+
* scans every poll tick until the worker restarts. Started turns are
|
|
113
|
+
* never expired here: once Claude actually wrote the user line, the
|
|
114
|
+
* turn is collecting assistant text and we want to wait however long
|
|
115
|
+
* the model takes. */
|
|
116
|
+
pruneExpired(maxAgeMs, now = Date.now()) {
|
|
117
|
+
const dropped = [];
|
|
118
|
+
this.queue = this.queue.filter(t => {
|
|
119
|
+
if (t.started)
|
|
120
|
+
return true;
|
|
121
|
+
if (t.markTimeMs === undefined)
|
|
122
|
+
return true;
|
|
123
|
+
if (now - t.markTimeMs <= maxAgeMs)
|
|
124
|
+
return true;
|
|
125
|
+
dropped.push(t);
|
|
126
|
+
return false;
|
|
127
|
+
});
|
|
128
|
+
return dropped;
|
|
129
|
+
}
|
|
130
|
+
/** Process newly-appended events. Idempotent on uuid: events with seen
|
|
131
|
+
* uuids are skipped, so callers can safely replay.
|
|
132
|
+
*
|
|
133
|
+
* `sourceJsonlPath` (when provided) is stamped onto a turn at the moment
|
|
134
|
+
* it transitions from "pending" to "started" — so that emit-time text
|
|
135
|
+
* resolution reads the same transcript file the user/assistant uuids
|
|
136
|
+
* were originally observed in. Without this, a sessionId rotation
|
|
137
|
+
* between ingest and emit would silently drop the reply, since the
|
|
138
|
+
* global current jsonl path would no longer contain those uuids. */
|
|
139
|
+
ingest(events, sourceJsonlPath) {
|
|
140
|
+
for (const ev of events) {
|
|
141
|
+
const uuid = ev.uuid;
|
|
142
|
+
if (!uuid || this.seen.has(uuid))
|
|
143
|
+
continue;
|
|
144
|
+
this.seen.add(uuid);
|
|
145
|
+
const role = ev.message?.role ?? ev.type;
|
|
146
|
+
if (role === 'user') {
|
|
147
|
+
// Skip ALL non-meaningful user events: tool_result (intra-turn
|
|
148
|
+
// machinery), `<command-name>/clear</command-name>` and other
|
|
149
|
+
// slash-command wrappers (Claude rewrites them after /clear /
|
|
150
|
+
// /resume — same in-process rotation that broke bridge tracking
|
|
151
|
+
// before), isMeta / isCompactSummary markers, sidechain spawns,
|
|
152
|
+
// empty content. These are NOT real user input; treating them as
|
|
153
|
+
// turn boundaries would (a) drop `collecting` mid-stream and lose
|
|
154
|
+
// assistant text after them, and (b) let a synthetic line that
|
|
155
|
+
// accidentally contains the fingerprint substring start the
|
|
156
|
+
// wrong turn.
|
|
157
|
+
if (!isMeaningfulUserEvent(ev))
|
|
158
|
+
continue;
|
|
159
|
+
this.handleTurnStart(uuid, ev, sourceJsonlPath);
|
|
160
|
+
}
|
|
161
|
+
else if (ev.type === 'attachment' && ev.attachment?.type === 'queued_command') {
|
|
162
|
+
// Type-ahead path: Claude writes `attachment(queued_command)` the
|
|
163
|
+
// moment it dequeues a queued submit, immediately before the
|
|
164
|
+
// assistant text for that turn starts streaming. Equivalent to
|
|
165
|
+
// role:user for turn-start purposes; share the same handler so
|
|
166
|
+
// fingerprint-match / HOL-block-drop / local-turn fallback all
|
|
167
|
+
// apply identically. Without this the bridge attribution queue
|
|
168
|
+
// would skip queued_command events and the type-ahead'd turn's
|
|
169
|
+
// assistant text would either be dropped or attributed to a
|
|
170
|
+
// sibling turn.
|
|
171
|
+
if (!isMeaningfulQueuedCommand(ev))
|
|
172
|
+
continue;
|
|
173
|
+
this.handleTurnStart(uuid, ev, sourceJsonlPath);
|
|
174
|
+
}
|
|
175
|
+
else if (role === 'assistant') {
|
|
176
|
+
if (ev.isSidechain === true)
|
|
177
|
+
continue;
|
|
178
|
+
if (!assistantHasVisibleText(ev.message?.content))
|
|
179
|
+
continue;
|
|
180
|
+
if (!this.collecting) {
|
|
181
|
+
// Headless local turn: assistant text arrived without any
|
|
182
|
+
// collecting context. Typical trigger: daemon restart cut off
|
|
183
|
+
// an in-flight model stream — baseline absorbed the original
|
|
184
|
+
// user event (uuid added to `seen`) and the worker process lost
|
|
185
|
+
// its in-memory `collecting` pointer. Without this synthesis the
|
|
186
|
+
// continuation would be silently dropped (assistantHasVisibleText
|
|
187
|
+
// events with no `collecting` were just skipped before).
|
|
188
|
+
// Headless turns have no userUuid; emit-side formatting omits
|
|
189
|
+
// the user block. Inserted at the head of the unstarted region
|
|
190
|
+
// so a subsequent normal turn doesn't get reordered ahead of it.
|
|
191
|
+
const headless = {
|
|
192
|
+
turnId: `local-headless-${uuid}`,
|
|
193
|
+
started: true,
|
|
194
|
+
isLocal: true,
|
|
195
|
+
userUuid: undefined,
|
|
196
|
+
assistantUuids: [],
|
|
197
|
+
sourceJsonlPath,
|
|
198
|
+
markTimeMs: Date.now(),
|
|
199
|
+
};
|
|
200
|
+
const insertAt = this.queue.findIndex(t => !t.started);
|
|
201
|
+
if (insertAt === -1)
|
|
202
|
+
this.queue.push(headless);
|
|
203
|
+
else
|
|
204
|
+
this.queue.splice(insertAt, 0, headless);
|
|
205
|
+
this.collecting = headless;
|
|
206
|
+
}
|
|
207
|
+
this.collecting.assistantUuids.push(uuid);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Shared turn-start handler. Called for both `role:user` and
|
|
212
|
+
* `attachment(queued_command)` events once meaningfulness has been
|
|
213
|
+
* established by the caller. Encapsulates:
|
|
214
|
+
* 1. HOL-block drop of the previous collecting turn when it got no
|
|
215
|
+
* assistant text (Claude moved on).
|
|
216
|
+
* 2. Fingerprint-gated start of the earliest unstarted Lark turn,
|
|
217
|
+
* falling through to local-turn synthesis on mismatch.
|
|
218
|
+
* 3. markTimeMs override to the transcript event's own timestamp —
|
|
219
|
+
* critical for type-ahead, where the original markTimeMs (set when
|
|
220
|
+
* the worker wrote to PTY) can be many seconds earlier than the
|
|
221
|
+
* moment Claude actually dequeues and starts processing the turn.
|
|
222
|
+
* The bridge-fallback gate's [markTimeMs, nextBoundaryMs) window
|
|
223
|
+
* MUST anchor on the latter, otherwise a `botmux send` from the
|
|
224
|
+
* previous turn can leak into the next turn's window and the
|
|
225
|
+
* suppression decision flips to the wrong turn (real reply
|
|
226
|
+
* suppressed, fallback shown — exactly what the type-ahead-disable
|
|
227
|
+
* in commit b2d9791 was protecting against). */
|
|
228
|
+
handleTurnStart(uuid, ev, sourceJsonlPath) {
|
|
229
|
+
// Head-of-line block drop: previous turn never produced any visible
|
|
230
|
+
// assistant text and a new meaningful turn-start has arrived → Claude
|
|
231
|
+
// is single-threaded over the PTY, so the old turn will never get
|
|
232
|
+
// text. Applies to both Lark and local turns.
|
|
233
|
+
if (this.collecting && this.collecting.assistantUuids.length === 0) {
|
|
234
|
+
const idx = this.queue.indexOf(this.collecting);
|
|
235
|
+
if (idx >= 0)
|
|
236
|
+
this.queue.splice(idx, 1);
|
|
237
|
+
this.collecting = null;
|
|
238
|
+
}
|
|
239
|
+
const tsParsed = ev.timestamp ? Date.parse(ev.timestamp) : NaN;
|
|
240
|
+
const eventTimeMs = Number.isFinite(tsParsed) ? tsParsed : Date.now();
|
|
241
|
+
const next = this.queue.find(t => !t.started);
|
|
242
|
+
let consumedNext = false;
|
|
243
|
+
if (next) {
|
|
244
|
+
if (next.contentFingerprint) {
|
|
245
|
+
// Both sides normalised (whitespace-collapsed + trimmed) before
|
|
246
|
+
// the substring check so a transcript line that preserved newlines
|
|
247
|
+
// still matches a fingerprint built from the same text.
|
|
248
|
+
const userText = normaliseForFingerprint(extractTurnStartText(ev));
|
|
249
|
+
if (userText.includes(next.contentFingerprint)) {
|
|
250
|
+
next.started = true;
|
|
251
|
+
if (!next.sourceJsonlPath)
|
|
252
|
+
next.sourceJsonlPath = sourceJsonlPath;
|
|
253
|
+
next.markTimeMs = eventTimeMs;
|
|
254
|
+
this.collecting = next;
|
|
255
|
+
consumedNext = true;
|
|
256
|
+
}
|
|
257
|
+
// Mismatch falls through to local-turn synthesis below.
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// Legacy mark() with no fingerprint — start on the next turn-start.
|
|
261
|
+
next.started = true;
|
|
262
|
+
if (!next.sourceJsonlPath)
|
|
263
|
+
next.sourceJsonlPath = sourceJsonlPath;
|
|
264
|
+
next.markTimeMs = eventTimeMs;
|
|
265
|
+
this.collecting = next;
|
|
266
|
+
consumedNext = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!consumedNext) {
|
|
270
|
+
// Local-terminal input (or a queued_command whose prompt didn't
|
|
271
|
+
// match any pending Lark fingerprint). Synthesise a started turn
|
|
272
|
+
// ahead of any unstarted Lark turn so chronological order matches
|
|
273
|
+
// transcript order at emit time.
|
|
274
|
+
const localTurn = {
|
|
275
|
+
turnId: `local-${uuid}`,
|
|
276
|
+
started: true,
|
|
277
|
+
isLocal: true,
|
|
278
|
+
userUuid: uuid,
|
|
279
|
+
assistantUuids: [],
|
|
280
|
+
sourceJsonlPath,
|
|
281
|
+
markTimeMs: eventTimeMs,
|
|
282
|
+
};
|
|
283
|
+
const insertAt = this.queue.findIndex(t => !t.started);
|
|
284
|
+
if (insertAt === -1)
|
|
285
|
+
this.queue.push(localTurn);
|
|
286
|
+
else
|
|
287
|
+
this.queue.splice(insertAt, 0, localTurn);
|
|
288
|
+
this.collecting = localTurn;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/** Pop FIFO any leading turn that's started AND has assistant text.
|
|
292
|
+
* Returns the popped turns in order; the caller is responsible for
|
|
293
|
+
* rebuilding the text payload from the assistant uuids. */
|
|
294
|
+
drainEmittable() {
|
|
295
|
+
const out = [];
|
|
296
|
+
while (this.queue.length > 0) {
|
|
297
|
+
const head = this.queue[0];
|
|
298
|
+
if (!head.started || head.assistantUuids.length === 0)
|
|
299
|
+
break;
|
|
300
|
+
this.queue.shift();
|
|
301
|
+
if (this.collecting === head)
|
|
302
|
+
this.collecting = null;
|
|
303
|
+
out.push(head);
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
/** Number of queued (not-yet-emitted) Lark turns. */
|
|
308
|
+
size() {
|
|
309
|
+
return this.queue.length;
|
|
310
|
+
}
|
|
311
|
+
/** Test helper — peek the queue without mutating. */
|
|
312
|
+
peek() {
|
|
313
|
+
return this.queue;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
//# 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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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare function init(appId: string): void;
|
|
2
|
+
/** True iff init() has been called with an appId. Callers that may run before
|
|
3
|
+
* daemon startup (e.g. the dashboard IPC server invoked from tests) can use
|
|
4
|
+
* this to skip persistence rather than crash the request. */
|
|
5
|
+
export declare function isInitialised(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Per-chat result of a markSeen call. `preexisting=false` means we stamped
|
|
8
|
+
* this chat for the first time in *this* call — callers that need provenance
|
|
9
|
+
* (e.g. the defaultOncall auto-bind judge) must treat freshly-stamped chats
|
|
10
|
+
* as "unknown age" rather than "new", because a missed backfill manifests
|
|
11
|
+
* exactly the same way as a real first observation.
|
|
12
|
+
*/
|
|
13
|
+
export interface SeenEntry {
|
|
14
|
+
firstSeenAt: number;
|
|
15
|
+
preexisting: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Record `Date.now()` for each chatId not yet known, return per-chat
|
|
19
|
+
* (firstSeenAt, preexisting) for the requested ids. Batches the write so a
|
|
20
|
+
* fresh 44-chat listChats only hits disk once. Empty map is returned when
|
|
21
|
+
* the store hasn't been init()'d (test harnesses that spin the IPC server up
|
|
22
|
+
* without a daemon backing it).
|
|
23
|
+
*/
|
|
24
|
+
export declare function markSeenBulkDetailed(chatIds: readonly string[]): Map<string, SeenEntry>;
|
|
25
|
+
/** Back-compat shim — same semantics as before, returns only the timestamps. */
|
|
26
|
+
export declare function markSeenBulk(chatIds: readonly string[]): Map<string, number>;
|
|
27
|
+
//# sourceMappingURL=chat-first-seen-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-first-seen-store.d.ts","sourceRoot":"","sources":["../../src/services/chat-first-seen-store.ts"],"names":[],"mappings":"AAwBA,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIxC;AAOD;;8DAE8D;AAC9D,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAiCD;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IAAG,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE;AAExE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAqBvF;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAK5E"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-bot "first seen" timestamps for chats — the dashboard sorts Groups & Bots
|
|
3
|
+
* by these timestamps so newly-added chats surface at the top. Lark's chat
|
|
4
|
+
* APIs do not return chat create_time, so we approximate creation order with
|
|
5
|
+
* the moment our daemon first observed each chat in `im.v1.chat.list`.
|
|
6
|
+
*
|
|
7
|
+
* On a fresh install all existing chats get the same backfill timestamp on
|
|
8
|
+
* first enumeration, so their relative order is undefined (the dashboard
|
|
9
|
+
* tie-breaks by name). From that point on, every newly-added chat gets its
|
|
10
|
+
* own real timestamp and rises above the bunch.
|
|
11
|
+
*
|
|
12
|
+
* File layout mirrors session-store: one file per bot at
|
|
13
|
+
* `${config.session.dataDir}/chat-first-seen-${appId}.json`, written
|
|
14
|
+
* atomically via tmp + rename.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync } from 'node:fs';
|
|
17
|
+
import { join, dirname } from 'node:path';
|
|
18
|
+
import { config } from '../config.js';
|
|
19
|
+
import { logger } from '../utils/logger.js';
|
|
20
|
+
let firstSeen = new Map();
|
|
21
|
+
let loaded = false;
|
|
22
|
+
let currentAppId;
|
|
23
|
+
export function init(appId) {
|
|
24
|
+
currentAppId = appId;
|
|
25
|
+
loaded = false;
|
|
26
|
+
firstSeen = new Map();
|
|
27
|
+
}
|
|
28
|
+
function getFilePath() {
|
|
29
|
+
if (!currentAppId)
|
|
30
|
+
throw new Error('chat-first-seen-store not initialised (call init(appId) first)');
|
|
31
|
+
return join(config.session.dataDir, `chat-first-seen-${currentAppId}.json`);
|
|
32
|
+
}
|
|
33
|
+
/** True iff init() has been called with an appId. Callers that may run before
|
|
34
|
+
* daemon startup (e.g. the dashboard IPC server invoked from tests) can use
|
|
35
|
+
* this to skip persistence rather than crash the request. */
|
|
36
|
+
export function isInitialised() {
|
|
37
|
+
return !!currentAppId;
|
|
38
|
+
}
|
|
39
|
+
function ensureDir() {
|
|
40
|
+
const dir = dirname(getFilePath());
|
|
41
|
+
if (!existsSync(dir))
|
|
42
|
+
mkdirSync(dir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
function load() {
|
|
45
|
+
if (loaded)
|
|
46
|
+
return;
|
|
47
|
+
ensureDir();
|
|
48
|
+
const fp = getFilePath();
|
|
49
|
+
if (existsSync(fp)) {
|
|
50
|
+
try {
|
|
51
|
+
const data = JSON.parse(readFileSync(fp, 'utf-8'));
|
|
52
|
+
firstSeen = new Map(Object.entries(data));
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
logger.error(`[chat-first-seen] failed to load ${fp}: ${err}`);
|
|
56
|
+
firstSeen = new Map();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
loaded = true;
|
|
60
|
+
}
|
|
61
|
+
function save() {
|
|
62
|
+
ensureDir();
|
|
63
|
+
const fp = getFilePath();
|
|
64
|
+
const tmpFp = fp + '.tmp';
|
|
65
|
+
const obj = {};
|
|
66
|
+
for (const [k, v] of firstSeen)
|
|
67
|
+
obj[k] = v;
|
|
68
|
+
writeFileSync(tmpFp, JSON.stringify(obj, null, 2), 'utf-8');
|
|
69
|
+
renameSync(tmpFp, fp);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Record `Date.now()` for each chatId not yet known, return per-chat
|
|
73
|
+
* (firstSeenAt, preexisting) for the requested ids. Batches the write so a
|
|
74
|
+
* fresh 44-chat listChats only hits disk once. Empty map is returned when
|
|
75
|
+
* the store hasn't been init()'d (test harnesses that spin the IPC server up
|
|
76
|
+
* without a daemon backing it).
|
|
77
|
+
*/
|
|
78
|
+
export function markSeenBulkDetailed(chatIds) {
|
|
79
|
+
if (!currentAppId)
|
|
80
|
+
return new Map();
|
|
81
|
+
load();
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
let dirty = false;
|
|
84
|
+
const out = new Map();
|
|
85
|
+
for (const id of chatIds) {
|
|
86
|
+
const prior = firstSeen.get(id);
|
|
87
|
+
if (prior !== undefined) {
|
|
88
|
+
out.set(id, { firstSeenAt: prior, preexisting: true });
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
firstSeen.set(id, now);
|
|
92
|
+
dirty = true;
|
|
93
|
+
out.set(id, { firstSeenAt: now, preexisting: false });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (dirty) {
|
|
97
|
+
try {
|
|
98
|
+
save();
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
logger.error(`[chat-first-seen] save failed: ${err}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
/** Back-compat shim — same semantics as before, returns only the timestamps. */
|
|
107
|
+
export function markSeenBulk(chatIds) {
|
|
108
|
+
const detailed = markSeenBulkDetailed(chatIds);
|
|
109
|
+
const out = new Map();
|
|
110
|
+
for (const [id, e] of detailed)
|
|
111
|
+
out.set(id, e.firstSeenAt);
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=chat-first-seen-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-first-seen-store.js","sourceRoot":"","sources":["../../src/services/chat-first-seen-store.ts"],"names":[],"mappings":"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"}
|