mstro-app 0.4.51 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/bin/mstro.js +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.js +7 -2
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +1 -1
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/runner.d.ts.map +1 -1
- package/dist/server/cli/headless/runner.js +63 -67
- package/dist/server/cli/headless/runner.js.map +1 -1
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +9 -4
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/improvisation-history-store.d.ts +16 -0
- package/dist/server/cli/improvisation-history-store.d.ts.map +1 -0
- package/dist/server/cli/improvisation-history-store.js +52 -0
- package/dist/server/cli/improvisation-history-store.js.map +1 -0
- package/dist/server/cli/improvisation-movements.d.ts +31 -0
- package/dist/server/cli/improvisation-movements.d.ts.map +1 -0
- package/dist/server/cli/improvisation-movements.js +93 -0
- package/dist/server/cli/improvisation-movements.js.map +1 -0
- package/dist/server/cli/improvisation-output-queue.d.ts +13 -0
- package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -0
- package/dist/server/cli/improvisation-output-queue.js +40 -0
- package/dist/server/cli/improvisation-output-queue.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +21 -51
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.js +18 -433
- package/dist/server/cli/improvisation-retry.js.map +1 -1
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -8
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +53 -148
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/retry/retry-best-result.d.ts +4 -0
- package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-best-result.js +61 -0
- package/dist/server/cli/retry/retry-best-result.js.map +1 -0
- package/dist/server/cli/retry/retry-context-loss.d.ts +6 -0
- package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-context-loss.js +68 -0
- package/dist/server/cli/retry/retry-context-loss.js.map +1 -0
- package/dist/server/cli/retry/retry-premature-completion.d.ts +5 -0
- package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-premature-completion.js +81 -0
- package/dist/server/cli/retry/retry-premature-completion.js.map +1 -0
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts +13 -0
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js +166 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -0
- package/dist/server/cli/retry/retry-resume-strategy.d.ts +12 -0
- package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-resume-strategy.js +22 -0
- package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -0
- package/dist/server/cli/retry/retry-runner-factory.d.ts +11 -0
- package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-runner-factory.js +60 -0
- package/dist/server/cli/retry/retry-runner-factory.js.map +1 -0
- package/dist/server/cli/retry/retry-tool-results.d.ts +9 -0
- package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-tool-results.js +24 -0
- package/dist/server/cli/retry/retry-tool-results.js.map +1 -0
- package/dist/server/cli/retry/retry-types.d.ts +30 -0
- package/dist/server/cli/retry/retry-types.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-types.js +4 -0
- package/dist/server/cli/retry/retry-types.js.map +1 -0
- package/dist/server/index.js +21 -109
- package/dist/server/index.js.map +1 -1
- package/dist/server/server-setup.d.ts +16 -1
- package/dist/server/server-setup.d.ts.map +1 -1
- package/dist/server/server-setup.js +107 -0
- package/dist/server/server-setup.js.map +1 -1
- package/dist/server/services/plan/board-config.d.ts +21 -0
- package/dist/server/services/plan/board-config.d.ts.map +1 -0
- package/dist/server/services/plan/board-config.js +112 -0
- package/dist/server/services/plan/board-config.js.map +1 -0
- package/dist/server/services/plan/composer.d.ts +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +7 -5
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +48 -48
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +157 -455
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/issue-loader.d.ts +16 -0
- package/dist/server/services/plan/issue-loader.d.ts.map +1 -0
- package/dist/server/services/plan/issue-loader.js +46 -0
- package/dist/server/services/plan/issue-loader.js.map +1 -0
- package/dist/server/services/plan/issue-writer.d.ts +34 -0
- package/dist/server/services/plan/issue-writer.d.ts.map +1 -0
- package/dist/server/services/plan/issue-writer.js +110 -0
- package/dist/server/services/plan/issue-writer.js.map +1 -0
- package/dist/server/services/plan/output-manager.d.ts.map +1 -1
- package/dist/server/services/plan/output-manager.js +2 -1
- package/dist/server/services/plan/output-manager.js.map +1 -1
- package/dist/server/services/plan/progress-log.d.ts +11 -0
- package/dist/server/services/plan/progress-log.d.ts.map +1 -0
- package/dist/server/services/plan/progress-log.js +81 -0
- package/dist/server/services/plan/progress-log.js.map +1 -0
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/prompt-builder.js +48 -31
- package/dist/server/services/plan/prompt-builder.js.map +1 -1
- package/dist/server/services/plan/readiness-planner.d.ts +15 -0
- package/dist/server/services/plan/readiness-planner.d.ts.map +1 -0
- package/dist/server/services/plan/readiness-planner.js +41 -0
- package/dist/server/services/plan/readiness-planner.js.map +1 -0
- package/dist/server/services/plan/review-gate.d.ts +31 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +52 -2
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/platform.d.ts +56 -0
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +154 -52
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/websocket/file-download-handler.d.ts +17 -0
- package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -0
- package/dist/server/services/websocket/file-download-handler.js +165 -0
- package/dist/server/services/websocket/file-download-handler.js.map +1 -0
- package/dist/server/services/websocket/git-branch-handlers.d.ts +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.js +21 -1
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +1 -1
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.d.ts +2 -0
- package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.js +30 -4
- package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
- package/dist/server/services/websocket/handler-context.d.ts +15 -0
- package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.d.ts +7 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +73 -11
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/msg-id-tracker.d.ts +21 -0
- package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -0
- package/dist/server/services/websocket/msg-id-tracker.js +77 -0
- package/dist/server/services/websocket/msg-id-tracker.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.js +15 -3
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +2 -2
- package/dist/server/services/websocket/session-handlers.d.ts +48 -2
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +204 -65
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-initialization.d.ts +2 -2
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
- package/dist/server/services/websocket/session-initialization.js +75 -17
- package/dist/server/services/websocket/session-initialization.js.map +1 -1
- package/dist/server/services/websocket/session-registry.d.ts +29 -1
- package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
- package/dist/server/services/websocket/session-registry.js +53 -4
- package/dist/server/services/websocket/session-registry.js.map +1 -1
- package/dist/server/services/websocket/tab-broadcast.d.ts +24 -0
- package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-broadcast.js +13 -0
- package/dist/server/services/websocket/tab-broadcast.js.map +1 -0
- package/dist/server/services/websocket/tab-event-buffer.d.ts +103 -0
- package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-event-buffer.js +107 -0
- package/dist/server/services/websocket/tab-event-buffer.js.map +1 -0
- package/dist/server/services/websocket/tab-event-replay.d.ts +20 -0
- package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-event-replay.js +21 -0
- package/dist/server/services/websocket/tab-event-replay.js.map +1 -0
- package/dist/server/services/websocket/tab-handlers.d.ts +0 -1
- package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-handlers.js +2 -9
- package/dist/server/services/websocket/tab-handlers.js.map +1 -1
- package/dist/server/services/websocket/types.d.ts +15 -6
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/services/websocket/types.js +6 -4
- package/dist/server/services/websocket/types.js.map +1 -1
- package/package.json +1 -1
- package/server/README.md +1 -1
- package/server/cli/headless/claude-invoker-stall.ts +7 -2
- package/server/cli/headless/claude-invoker.ts +1 -1
- package/server/cli/headless/runner.ts +67 -72
- package/server/cli/headless/stall-assessor.ts +9 -4
- package/server/cli/headless/types.ts +1 -1
- package/server/cli/improvisation-history-store.ts +62 -0
- package/server/cli/improvisation-movements.ts +120 -0
- package/server/cli/improvisation-output-queue.ts +42 -0
- package/server/cli/improvisation-retry.ts +25 -600
- package/server/cli/improvisation-session-manager.ts +74 -160
- package/server/cli/retry/retry-best-result.ts +70 -0
- package/server/cli/retry/retry-context-loss.ts +87 -0
- package/server/cli/retry/retry-premature-completion.ts +113 -0
- package/server/cli/retry/retry-recovery-strategies.ts +247 -0
- package/server/cli/retry/retry-resume-strategy.ts +33 -0
- package/server/cli/retry/retry-runner-factory.ts +70 -0
- package/server/cli/retry/retry-tool-results.ts +31 -0
- package/server/cli/retry/retry-types.ts +32 -0
- package/server/index.ts +37 -123
- package/server/server-setup.ts +126 -1
- package/server/services/plan/agents/assess-stall.md +11 -4
- package/server/services/plan/board-config.ts +122 -0
- package/server/services/plan/composer.ts +7 -5
- package/server/services/plan/executor.ts +214 -467
- package/server/services/plan/issue-loader.ts +64 -0
- package/server/services/plan/issue-writer.ts +137 -0
- package/server/services/plan/output-manager.ts +2 -1
- package/server/services/plan/progress-log.ts +92 -0
- package/server/services/plan/prompt-builder.ts +73 -35
- package/server/services/plan/readiness-planner.ts +50 -0
- package/server/services/plan/review-gate.ts +102 -2
- package/server/services/platform.ts +163 -58
- package/server/services/websocket/file-download-handler.ts +191 -0
- package/server/services/websocket/git-branch-handlers.ts +28 -1
- package/server/services/websocket/git-handlers.ts +1 -1
- package/server/services/websocket/git-worktree-handlers.ts +31 -4
- package/server/services/websocket/handler-context.ts +15 -0
- package/server/services/websocket/handler.ts +76 -12
- package/server/services/websocket/msg-id-tracker.ts +84 -0
- package/server/services/websocket/quality-handlers.ts +16 -3
- package/server/services/websocket/quality-review-agent.ts +2 -2
- package/server/services/websocket/session-handlers.ts +213 -68
- package/server/services/websocket/session-initialization.ts +83 -19
- package/server/services/websocket/session-registry.ts +61 -4
- package/server/services/websocket/tab-broadcast.ts +38 -0
- package/server/services/websocket/tab-event-buffer.ts +159 -0
- package/server/services/websocket/tab-event-replay.ts +42 -0
- package/server/services/websocket/tab-handlers.ts +2 -9
- package/server/services/websocket/types.ts +17 -4
|
@@ -3,7 +3,22 @@
|
|
|
3
3
|
import { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
|
|
4
4
|
import { getEffortLevel, getModel } from '../settings.js';
|
|
5
5
|
import { buildOutputHistory, setupSessionListeners } from './session-handlers.js';
|
|
6
|
-
|
|
6
|
+
import { replayTabEventsSince } from './tab-event-replay.js';
|
|
7
|
+
/**
|
|
8
|
+
* Extract `lastSeenSeq` from an initTab/resumeSession data payload.
|
|
9
|
+
*
|
|
10
|
+
* Keeps the narrow-typing scoped to the initialization module instead of
|
|
11
|
+
* leaking into the broader `HandlerContext`. Returns `undefined` for first
|
|
12
|
+
* init (no replay needed) or malformed payloads (treated as first init —
|
|
13
|
+
* safer than surfacing an error the user can't act on).
|
|
14
|
+
*/
|
|
15
|
+
function extractLastSeenSeq(data) {
|
|
16
|
+
if (!data || typeof data !== 'object')
|
|
17
|
+
return undefined;
|
|
18
|
+
const candidate = data.lastSeenSeq;
|
|
19
|
+
return typeof candidate === 'number' && Number.isFinite(candidate) ? candidate : undefined;
|
|
20
|
+
}
|
|
21
|
+
function tryResumeFromDisk(ctx, ws, tabId, workingDir, registrySessionId, tabMap, registry, lastSeenSeq) {
|
|
7
22
|
try {
|
|
8
23
|
const diskSession = ImprovisationSessionManager.resumeFromHistory(workingDir, registrySessionId);
|
|
9
24
|
setupSessionListeners(ctx, diskSession, ws, tabId);
|
|
@@ -12,6 +27,7 @@ function tryResumeFromDisk(ctx, ws, tabId, workingDir, registrySessionId, tabMap
|
|
|
12
27
|
if (tabMap)
|
|
13
28
|
tabMap.set(tabId, diskSessionId);
|
|
14
29
|
registry.touchTab(tabId);
|
|
30
|
+
registry.markTabPersisted(tabId);
|
|
15
31
|
// Restore worktree state from registry
|
|
16
32
|
const regTab = registry.getTab(tabId);
|
|
17
33
|
if (regTab?.worktreePath && !ctx.gitDirectories.has(tabId)) {
|
|
@@ -21,12 +37,17 @@ function tryResumeFromDisk(ctx, ws, tabId, workingDir, registrySessionId, tabMap
|
|
|
21
37
|
}
|
|
22
38
|
const worktreePath = ctx.gitDirectories.get(tabId);
|
|
23
39
|
const worktreeBranch = ctx.gitBranches.get(tabId);
|
|
40
|
+
// Replay any tab-scoped events the web missed during the transport gap
|
|
41
|
+
// BEFORE tabInitialized so they arrive in the right order. Web-side
|
|
42
|
+
// handlers append; `tabInitialized` does NOT reset when `resumedFromSeq`
|
|
43
|
+
// is set, preserving the replayed additions.
|
|
44
|
+
replayTabEventsSince(ctx, ws, tabId, lastSeenSeq);
|
|
24
45
|
ctx.send(ws, {
|
|
25
46
|
type: 'tabInitialized',
|
|
26
47
|
tabId,
|
|
27
48
|
data: {
|
|
28
49
|
...diskSession.getSessionInfo(),
|
|
29
|
-
outputHistory: buildOutputHistory(diskSession),
|
|
50
|
+
...(lastSeenSeq === undefined ? { outputHistory: buildOutputHistory(diskSession) } : { resumedFromSeq: true }),
|
|
30
51
|
...(worktreePath ? { worktreePath, worktreeBranch } : {}),
|
|
31
52
|
}
|
|
32
53
|
});
|
|
@@ -36,15 +57,16 @@ function tryResumeFromDisk(ctx, ws, tabId, workingDir, registrySessionId, tabMap
|
|
|
36
57
|
return false;
|
|
37
58
|
}
|
|
38
59
|
}
|
|
39
|
-
export async function initializeTab(ctx, ws, tabId, workingDir, tabName) {
|
|
60
|
+
export async function initializeTab(ctx, ws, tabId, workingDir, tabName, rawData) {
|
|
40
61
|
const tabMap = ctx.connections.get(ws);
|
|
41
62
|
const registry = ctx.getRegistry(workingDir);
|
|
63
|
+
const lastSeenSeq = extractLastSeenSeq(rawData);
|
|
42
64
|
// 1. Check per-connection map (same WS reconnect)
|
|
43
65
|
const existingSessionId = tabMap?.get(tabId);
|
|
44
66
|
if (existingSessionId) {
|
|
45
67
|
const existingSession = ctx.sessions.get(existingSessionId);
|
|
46
68
|
if (existingSession) {
|
|
47
|
-
reattachSession(ctx, existingSession, ws, tabId, registry);
|
|
69
|
+
reattachSession(ctx, existingSession, ws, tabId, registry, lastSeenSeq);
|
|
48
70
|
return;
|
|
49
71
|
}
|
|
50
72
|
}
|
|
@@ -53,41 +75,56 @@ export async function initializeTab(ctx, ws, tabId, workingDir, tabName) {
|
|
|
53
75
|
if (registrySessionId) {
|
|
54
76
|
const inMemorySession = ctx.sessions.get(registrySessionId);
|
|
55
77
|
if (inMemorySession) {
|
|
56
|
-
reattachSession(ctx, inMemorySession, ws, tabId, registry);
|
|
78
|
+
reattachSession(ctx, inMemorySession, ws, tabId, registry, lastSeenSeq);
|
|
57
79
|
return;
|
|
58
80
|
}
|
|
59
|
-
if (tryResumeFromDisk(ctx, ws, tabId, workingDir, registrySessionId, tabMap, registry)) {
|
|
81
|
+
if (tryResumeFromDisk(ctx, ws, tabId, workingDir, registrySessionId, tabMap, registry, lastSeenSeq)) {
|
|
60
82
|
return;
|
|
61
83
|
}
|
|
62
84
|
}
|
|
63
|
-
// 3. Create new session
|
|
64
|
-
|
|
85
|
+
// 3. Create new session. If the tab is already registered (no file on
|
|
86
|
+
// disk — tab is pending first prompt or file was deleted), reuse its
|
|
87
|
+
// sessionId so the tab keeps its identity across restarts.
|
|
88
|
+
const existingTab = registry.getTab(tabId);
|
|
89
|
+
const session = new ImprovisationSessionManager({
|
|
90
|
+
workingDir,
|
|
91
|
+
...(registrySessionId ? { sessionId: registrySessionId } : {}),
|
|
92
|
+
model: getModel(),
|
|
93
|
+
effortLevel: getEffortLevel(),
|
|
94
|
+
});
|
|
65
95
|
setupSessionListeners(ctx, session, ws, tabId);
|
|
66
96
|
const sessionId = session.getSessionInfo().sessionId;
|
|
67
97
|
ctx.sessions.set(sessionId, session);
|
|
68
98
|
if (tabMap) {
|
|
69
99
|
tabMap.set(tabId, sessionId);
|
|
70
100
|
}
|
|
71
|
-
registry.registerTab(tabId, sessionId, tabName);
|
|
101
|
+
registry.registerTab(tabId, sessionId, tabName || existingTab?.tabName);
|
|
72
102
|
const registeredTab = registry.getTab(tabId);
|
|
73
103
|
ctx.broadcastToAll({
|
|
74
104
|
type: 'tabCreated',
|
|
75
105
|
data: { tabId, tabName: registeredTab?.tabName || 'Chat', createdAt: registeredTab?.createdAt, order: registeredTab?.order, sessionInfo: session.getSessionInfo() }
|
|
76
106
|
});
|
|
107
|
+
// Fresh session (no disk/memory predecessor) has nothing to replay,
|
|
108
|
+
// but we still pass lastSeenSeq through so the web flag is consistent.
|
|
109
|
+
replayTabEventsSince(ctx, ws, tabId, lastSeenSeq);
|
|
77
110
|
ctx.send(ws, {
|
|
78
111
|
type: 'tabInitialized',
|
|
79
112
|
tabId,
|
|
80
|
-
data:
|
|
113
|
+
data: {
|
|
114
|
+
...session.getSessionInfo(),
|
|
115
|
+
...(lastSeenSeq !== undefined ? { resumedFromSeq: true } : {}),
|
|
116
|
+
}
|
|
81
117
|
});
|
|
82
118
|
}
|
|
83
|
-
export async function resumeHistoricalSession(ctx, ws, tabId, workingDir, historicalSessionId) {
|
|
119
|
+
export async function resumeHistoricalSession(ctx, ws, tabId, workingDir, historicalSessionId, rawData) {
|
|
84
120
|
const tabMap = ctx.connections.get(ws);
|
|
85
121
|
const registry = ctx.getRegistry(workingDir);
|
|
122
|
+
const lastSeenSeq = extractLastSeenSeq(rawData);
|
|
86
123
|
const existingSessionId = tabMap?.get(tabId);
|
|
87
124
|
if (existingSessionId) {
|
|
88
125
|
const existingSession = ctx.sessions.get(existingSessionId);
|
|
89
126
|
if (existingSession) {
|
|
90
|
-
reattachSession(ctx, existingSession, ws, tabId, registry);
|
|
127
|
+
reattachSession(ctx, existingSession, ws, tabId, registry, lastSeenSeq);
|
|
91
128
|
return;
|
|
92
129
|
}
|
|
93
130
|
}
|
|
@@ -95,7 +132,7 @@ export async function resumeHistoricalSession(ctx, ws, tabId, workingDir, histor
|
|
|
95
132
|
if (registrySessionId) {
|
|
96
133
|
const inMemorySession = ctx.sessions.get(registrySessionId);
|
|
97
134
|
if (inMemorySession) {
|
|
98
|
-
reattachSession(ctx, inMemorySession, ws, tabId, registry);
|
|
135
|
+
reattachSession(ctx, inMemorySession, ws, tabId, registry, lastSeenSeq);
|
|
99
136
|
return;
|
|
100
137
|
}
|
|
101
138
|
}
|
|
@@ -116,18 +153,19 @@ export async function resumeHistoricalSession(ctx, ws, tabId, workingDir, histor
|
|
|
116
153
|
tabMap.set(tabId, sessionId);
|
|
117
154
|
}
|
|
118
155
|
registry.registerTab(tabId, sessionId);
|
|
156
|
+
replayTabEventsSince(ctx, ws, tabId, lastSeenSeq);
|
|
119
157
|
ctx.send(ws, {
|
|
120
158
|
type: 'tabInitialized',
|
|
121
159
|
tabId,
|
|
122
160
|
data: {
|
|
123
161
|
...session.getSessionInfo(),
|
|
124
|
-
outputHistory: buildOutputHistory(session),
|
|
162
|
+
...(lastSeenSeq === undefined ? { outputHistory: buildOutputHistory(session) } : { resumedFromSeq: true }),
|
|
125
163
|
resumeFailed: isNewSession,
|
|
126
164
|
originalSessionId: isNewSession ? historicalSessionId : undefined
|
|
127
165
|
}
|
|
128
166
|
});
|
|
129
167
|
}
|
|
130
|
-
function reattachSession(ctx, session, ws, tabId, registry) {
|
|
168
|
+
function reattachSession(ctx, session, ws, tabId, registry, lastSeenSeq) {
|
|
131
169
|
setupSessionListeners(ctx, session, ws, tabId);
|
|
132
170
|
const tabMap = ctx.connections.get(ws);
|
|
133
171
|
const sessionId = session.getSessionInfo().sessionId;
|
|
@@ -141,12 +179,32 @@ function reattachSession(ctx, session, ws, tabId, registry) {
|
|
|
141
179
|
if (regTab.worktreeBranch)
|
|
142
180
|
ctx.gitBranches.set(tabId, regTab.worktreeBranch);
|
|
143
181
|
}
|
|
182
|
+
const worktreePath = ctx.gitDirectories.get(tabId);
|
|
183
|
+
const worktreeBranch = ctx.gitBranches.get(tabId);
|
|
184
|
+
// Fast path: the web already has local state (via Zustand), so just replay
|
|
185
|
+
// anything newer than `lastSeenSeq` and tell the client to skip the
|
|
186
|
+
// destructive reset in its tabInitialized handler.
|
|
187
|
+
if (lastSeenSeq !== undefined) {
|
|
188
|
+
replayTabEventsSince(ctx, ws, tabId, lastSeenSeq);
|
|
189
|
+
ctx.send(ws, {
|
|
190
|
+
type: 'tabInitialized',
|
|
191
|
+
tabId,
|
|
192
|
+
data: {
|
|
193
|
+
...session.getSessionInfo(),
|
|
194
|
+
resumedFromSeq: true,
|
|
195
|
+
isExecuting: session.isExecuting,
|
|
196
|
+
...(session.isExecuting && session.executionStartTimestamp ? { executionStartTimestamp: session.executionStartTimestamp } : {}),
|
|
197
|
+
...(worktreePath ? { worktreePath, worktreeBranch } : {}),
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Cold-start reattach (no prior seq): send the full snapshot so the web
|
|
203
|
+
// can rebuild from scratch.
|
|
144
204
|
const outputHistory = buildOutputHistory(session);
|
|
145
205
|
const executionEvents = session.isExecuting
|
|
146
206
|
? session.getExecutionEventLog()
|
|
147
207
|
: undefined;
|
|
148
|
-
const worktreePath = ctx.gitDirectories.get(tabId);
|
|
149
|
-
const worktreeBranch = ctx.gitBranches.get(tabId);
|
|
150
208
|
ctx.send(ws, {
|
|
151
209
|
type: 'tabInitialized',
|
|
152
210
|
tabId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-initialization.js","sourceRoot":"","sources":["../../../../server/services/websocket/session-initialization.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,gEAAgE;AAEhE,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"session-initialization.js","sourceRoot":"","sources":["../../../../server/services/websocket/session-initialization.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,14 +5,36 @@ export interface RegisteredTab {
|
|
|
5
5
|
lastActivityAt: string;
|
|
6
6
|
order: number;
|
|
7
7
|
hasUnviewedCompletion: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* True once the session's history file has existed on disk (first prompt
|
|
10
|
+
* ran, or the tab was resumed from an existing file). Guards `sweepGhostTabs`
|
|
11
|
+
* so brand-new tabs that haven't had a first prompt are not confused with
|
|
12
|
+
* tabs whose history file was deleted.
|
|
13
|
+
*/
|
|
14
|
+
hasPersistedHistory?: boolean;
|
|
8
15
|
worktreePath?: string;
|
|
9
16
|
worktreeBranch?: string;
|
|
10
17
|
}
|
|
11
18
|
export declare class SessionRegistry {
|
|
12
19
|
private registryPath;
|
|
20
|
+
private historyDir;
|
|
13
21
|
private data;
|
|
14
22
|
constructor(workingDir: string);
|
|
15
23
|
private load;
|
|
24
|
+
/**
|
|
25
|
+
* Drop registry entries whose backing history file no longer exists.
|
|
26
|
+
*
|
|
27
|
+
* The history file may have been deleted via `clearHistory`, pruned by the
|
|
28
|
+
* user, or lost on disk. Without a sweep, `getActiveTabs` returns the tab
|
|
29
|
+
* to the web, the web tries to `initTab` it, `resumeFromHistory` throws,
|
|
30
|
+
* and the tab re-creates as an empty new session — confusing the user with
|
|
31
|
+
* a "restored" tab that lost its content.
|
|
32
|
+
*
|
|
33
|
+
* Only sweeps tabs that were previously persisted (`hasPersistedHistory`).
|
|
34
|
+
* Tabs that have never had a first prompt have no file on disk by design;
|
|
35
|
+
* removing them here would wipe a freshly opened tab after a CLI restart.
|
|
36
|
+
*/
|
|
37
|
+
private sweepGhostTabs;
|
|
16
38
|
private save;
|
|
17
39
|
private getNextOrder;
|
|
18
40
|
registerTab(tabId: string, sessionId: string, tabName?: string): void;
|
|
@@ -27,9 +49,15 @@ export declare class SessionRegistry {
|
|
|
27
49
|
updateTabName(tabId: string, name: string): void;
|
|
28
50
|
touchTab(tabId: string): void;
|
|
29
51
|
/**
|
|
30
|
-
* Update session ID for a tab (e.g., when "new session" is started)
|
|
52
|
+
* Update session ID for a tab (e.g., when "new session" is started).
|
|
53
|
+
* Resets `hasPersistedHistory` since the new session has no file on disk yet.
|
|
31
54
|
*/
|
|
32
55
|
updateTabSession(tabId: string, sessionId: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Mark a tab as having persisted its history to disk. Called after the
|
|
58
|
+
* first `persistHistory` or when an existing file is resumed.
|
|
59
|
+
*/
|
|
60
|
+
markTabPersisted(tabId: string): void;
|
|
33
61
|
markTabViewed(tabId: string): void;
|
|
34
62
|
markTabUnviewed(tabId: string): void;
|
|
35
63
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/session-registry.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,qBAAqB,EAAE,OAAO,CAAA;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAMD,qBAAa,eAAe;IAC1B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,IAAI,CAAc;gBAEd,UAAU,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/session-registry.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,qBAAqB,EAAE,OAAO,CAAA;IAC9B;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAMD,qBAAa,eAAe;IAC1B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,IAAI,CAAc;gBAEd,UAAU,EAAE,MAAM;IAW9B,OAAO,CAAC,IAAI;IA0BZ;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,YAAY;IAQpB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAgBrE;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAazB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKlC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIhD,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC;IAI3C,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAOhD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAO7B;;;OAGG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IASxD;;;OAGG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQrC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOlC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOpC;;OAEG;IACH,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAalG;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI;CAOtC"}
|
|
@@ -13,6 +13,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
export class SessionRegistry {
|
|
15
15
|
registryPath;
|
|
16
|
+
historyDir;
|
|
16
17
|
data;
|
|
17
18
|
constructor(workingDir) {
|
|
18
19
|
const mstroDir = join(workingDir, '.mstro');
|
|
@@ -20,7 +21,9 @@ export class SessionRegistry {
|
|
|
20
21
|
mkdirSync(mstroDir, { recursive: true });
|
|
21
22
|
}
|
|
22
23
|
this.registryPath = join(mstroDir, 'session-registry.json');
|
|
24
|
+
this.historyDir = join(mstroDir, 'history');
|
|
23
25
|
this.data = this.load();
|
|
26
|
+
this.sweepGhostTabs();
|
|
24
27
|
}
|
|
25
28
|
load() {
|
|
26
29
|
try {
|
|
@@ -48,6 +51,36 @@ export class SessionRegistry {
|
|
|
48
51
|
}
|
|
49
52
|
return { tabs: {} };
|
|
50
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Drop registry entries whose backing history file no longer exists.
|
|
56
|
+
*
|
|
57
|
+
* The history file may have been deleted via `clearHistory`, pruned by the
|
|
58
|
+
* user, or lost on disk. Without a sweep, `getActiveTabs` returns the tab
|
|
59
|
+
* to the web, the web tries to `initTab` it, `resumeFromHistory` throws,
|
|
60
|
+
* and the tab re-creates as an empty new session — confusing the user with
|
|
61
|
+
* a "restored" tab that lost its content.
|
|
62
|
+
*
|
|
63
|
+
* Only sweeps tabs that were previously persisted (`hasPersistedHistory`).
|
|
64
|
+
* Tabs that have never had a first prompt have no file on disk by design;
|
|
65
|
+
* removing them here would wipe a freshly opened tab after a CLI restart.
|
|
66
|
+
*/
|
|
67
|
+
sweepGhostTabs() {
|
|
68
|
+
const removed = [];
|
|
69
|
+
for (const [tabId, tab] of Object.entries(this.data.tabs)) {
|
|
70
|
+
if (tab.hasPersistedHistory !== true)
|
|
71
|
+
continue;
|
|
72
|
+
const timestamp = tab.sessionId.replace('improv-', '');
|
|
73
|
+
const historyPath = join(this.historyDir, `${timestamp}.json`);
|
|
74
|
+
if (!existsSync(historyPath)) {
|
|
75
|
+
removed.push(tabId);
|
|
76
|
+
delete this.data.tabs[tabId];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (removed.length > 0) {
|
|
80
|
+
console.log(`[SessionRegistry] Swept ${removed.length} ghost tab(s) whose history file was missing`);
|
|
81
|
+
this.save();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
51
84
|
save() {
|
|
52
85
|
try {
|
|
53
86
|
writeFileSync(this.registryPath, JSON.stringify(this.data, null, 2));
|
|
@@ -66,13 +99,16 @@ export class SessionRegistry {
|
|
|
66
99
|
}
|
|
67
100
|
registerTab(tabId, sessionId, tabName) {
|
|
68
101
|
const now = new Date().toISOString();
|
|
102
|
+
const existing = this.data.tabs[tabId];
|
|
103
|
+
const sessionChanged = existing?.sessionId !== sessionId;
|
|
69
104
|
this.data.tabs[tabId] = {
|
|
70
105
|
sessionId,
|
|
71
106
|
tabName: tabName || `Chat ${this.getNextChatNumber()}`,
|
|
72
|
-
createdAt:
|
|
107
|
+
createdAt: existing?.createdAt || now,
|
|
73
108
|
lastActivityAt: now,
|
|
74
|
-
order:
|
|
75
|
-
hasUnviewedCompletion:
|
|
109
|
+
order: existing?.order ?? this.getNextOrder(),
|
|
110
|
+
hasUnviewedCompletion: existing?.hasUnviewedCompletion ?? false,
|
|
111
|
+
hasPersistedHistory: sessionChanged ? false : existing?.hasPersistedHistory,
|
|
76
112
|
};
|
|
77
113
|
this.save();
|
|
78
114
|
}
|
|
@@ -118,12 +154,25 @@ export class SessionRegistry {
|
|
|
118
154
|
}
|
|
119
155
|
}
|
|
120
156
|
/**
|
|
121
|
-
* Update session ID for a tab (e.g., when "new session" is started)
|
|
157
|
+
* Update session ID for a tab (e.g., when "new session" is started).
|
|
158
|
+
* Resets `hasPersistedHistory` since the new session has no file on disk yet.
|
|
122
159
|
*/
|
|
123
160
|
updateTabSession(tabId, sessionId) {
|
|
124
161
|
if (this.data.tabs[tabId]) {
|
|
125
162
|
this.data.tabs[tabId].sessionId = sessionId;
|
|
126
163
|
this.data.tabs[tabId].lastActivityAt = new Date().toISOString();
|
|
164
|
+
this.data.tabs[tabId].hasPersistedHistory = false;
|
|
165
|
+
this.save();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Mark a tab as having persisted its history to disk. Called after the
|
|
170
|
+
* first `persistHistory` or when an existing file is resumed.
|
|
171
|
+
*/
|
|
172
|
+
markTabPersisted(tabId) {
|
|
173
|
+
const tab = this.data.tabs[tabId];
|
|
174
|
+
if (tab && !tab.hasPersistedHistory) {
|
|
175
|
+
tab.hasPersistedHistory = true;
|
|
127
176
|
this.save();
|
|
128
177
|
}
|
|
129
178
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../../../server/services/websocket/session-registry.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,gEAAgE;AAEhE;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../../../server/services/websocket/session-registry.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,gEAAgE;AAEhE;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAwBhC,MAAM,OAAO,eAAe;IAClB,YAAY,CAAQ;IACpB,UAAU,CAAQ;IAClB,IAAI,CAAc;IAE1B,YAAY,UAAkB;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QAC3C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1C,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAA;QAC3D,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;QAC3C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QACvB,IAAI,CAAC,cAAc,EAAE,CAAA;IACvB,CAAC;IAEO,IAAI;QACV,IAAI,CAAC;YACH,IAAI,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;gBAClC,MAAM,GAAG,GAAiB,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;gBAC9E,iDAAiD;gBACjD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAA;gBAC7E,IAAI,UAAU,EAAE,CAAC;oBACf,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAC5D,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CACrD,CAAA;oBACD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;wBAC5B,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS;4BAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAA;oBAC5C,CAAC,CAAC,CAAA;gBACJ,CAAC;gBACD,mDAAmD;gBACnD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC1C,IAAI,GAAG,CAAC,qBAAqB,KAAK,SAAS;wBAAE,GAAG,CAAC,qBAAqB,GAAG,KAAK,CAAA;gBAChF,CAAC;gBACD,OAAO,GAAG,CAAA;YACZ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAA;QACnE,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAA;IACrB,CAAC;IAED;;;;;;;;;;;;OAYG;IACK,cAAc;QACpB,MAAM,OAAO,GAAa,EAAE,CAAA;QAC5B,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,IAAI,GAAG,CAAC,mBAAmB,KAAK,IAAI;gBAAE,SAAQ;YAC9C,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YACtD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,OAAO,CAAC,CAAA;YAC9D,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC7B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBACnB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAC9B,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,2BAA2B,OAAO,CAAC,MAAM,8CAA8C,CAAC,CAAA;YACpG,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAEO,IAAI;QACV,IAAI,CAAC;YACH,aAAa,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,KAAK,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;IAEO,YAAY;QAClB,IAAI,GAAG,GAAG,CAAC,CAAC,CAAA;QACZ,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,GAAG,CAAC,KAAK,GAAG,GAAG;gBAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAA;QACtC,CAAC;QACD,OAAO,GAAG,GAAG,CAAC,CAAA;IAChB,CAAC;IAED,WAAW,CAAC,KAAa,EAAE,SAAiB,EAAE,OAAgB;QAC5D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACtC,MAAM,cAAc,GAAG,QAAQ,EAAE,SAAS,KAAK,SAAS,CAAA;QACxD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG;YACtB,SAAS;YACT,OAAO,EAAE,OAAO,IAAI,QAAQ,IAAI,CAAC,iBAAiB,EAAE,EAAE;YACtD,SAAS,EAAE,QAAQ,EAAE,SAAS,IAAI,GAAG;YACrC,cAAc,EAAE,GAAG;YACnB,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,IAAI,CAAC,YAAY,EAAE;YAC7C,qBAAqB,EAAE,QAAQ,EAAE,qBAAqB,IAAI,KAAK;YAC/D,mBAAmB,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,mBAAmB;SAC5E,CAAA;QACD,IAAI,CAAC,IAAI,EAAE,CAAA;IACb,CAAC;IAED;;OAEG;IACK,iBAAiB;QACvB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAA;QACrC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;YAC/C,IAAI,KAAK,EAAE,CAAC;gBACV,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;YACzC,CAAC;QACH,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,CAAA;QACT,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,CAAC,EAAE,CAAA;QAC9B,OAAO,CAAC,CAAA;IACV,CAAC;IAED,aAAa,CAAC,KAAa;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAA;IACb,CAAC;IAED,aAAa,CAAC,KAAa;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IACzC,CAAC;IAED,MAAM,CAAC,KAAa;QAClB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC9B,CAAC;IAED,UAAU;QACR,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IAC9B,CAAC;IAED,aAAa,CAAC,KAAa,EAAE,IAAY;QACvC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,IAAI,CAAA;YACpC,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,KAAa;QACpB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAC/D,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,gBAAgB,CAAC,KAAa,EAAE,SAAiB;QAC/C,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,SAAS,CAAA;YAC3C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAC/D,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,mBAAmB,GAAG,KAAK,CAAA;YACjD,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,gBAAgB,CAAC,KAAa;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;YACpC,GAAG,CAAC,mBAAmB,GAAG,IAAI,CAAA;YAC9B,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAED,aAAa,CAAC,KAAa;QACzB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,qBAAqB,GAAG,KAAK,CAAA;YACnD,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAED,eAAe,CAAC,KAAa;QAC3B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,qBAAqB,GAAG,IAAI,CAAA;YAClD,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAED;;OAEG;IACH,iBAAiB,CAAC,KAAa,EAAE,YAA2B,EAAE,cAA6B;QACzF,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,YAAY,EAAE,CAAC;gBACjB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,YAAY,CAAA;gBACjD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,cAAc,GAAG,cAAc,IAAI,SAAS,CAAA;YACpE,CAAC;iBAAM,CAAC;gBACN,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,CAAA;gBACzC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,cAAc,CAAA;YAC7C,CAAC;YACD,IAAI,CAAC,IAAI,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,QAAkB;QAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;YACvC,IAAI,GAAG;gBAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAA;QACxB,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAA;IACb,CAAC;CACF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab-scoped broadcast helper.
|
|
3
|
+
*
|
|
4
|
+
* Session and plan handlers emit streaming events that (a) fan out to all
|
|
5
|
+
* paired web clients and (b) need to survive transport reconnects. Instead of
|
|
6
|
+
* every call site knowing about both concerns, route through
|
|
7
|
+
* `broadcastTabEvent`: it assigns the next monotonic `seq` via the tab's
|
|
8
|
+
* event buffer and broadcasts the wire message with that seq attached.
|
|
9
|
+
*
|
|
10
|
+
* Receiving webs record the seq and ask for replay starting from their
|
|
11
|
+
* highest-seen seq the next time they `initTab` / `resumeSession`. See
|
|
12
|
+
* `tab-event-buffer.ts` for the buffer itself and
|
|
13
|
+
* `session-initialization.ts` for the replay path.
|
|
14
|
+
*/
|
|
15
|
+
import type { HandlerContext } from './handler-context.js';
|
|
16
|
+
import type { WebSocketResponse } from './types.js';
|
|
17
|
+
type TabScopedEventType = WebSocketResponse['type'];
|
|
18
|
+
/**
|
|
19
|
+
* Record + broadcast a tab-scoped event in one call. Returns the assigned
|
|
20
|
+
* sequence number purely for logging/tests — callers rarely need it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function broadcastTabEvent(ctx: HandlerContext, tabId: string, type: TabScopedEventType, data: unknown): number;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=tab-broadcast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-broadcast.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/tab-broadcast.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,KAAK,kBAAkB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAA;AAEnD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,OAAO,GACZ,MAAM,CAKR"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
/**
|
|
4
|
+
* Record + broadcast a tab-scoped event in one call. Returns the assigned
|
|
5
|
+
* sequence number purely for logging/tests — callers rarely need it.
|
|
6
|
+
*/
|
|
7
|
+
export function broadcastTabEvent(ctx, tabId, type, data) {
|
|
8
|
+
const buffer = ctx.tabEventBuffers.getOrCreate(tabId);
|
|
9
|
+
const seq = buffer.record(type, data);
|
|
10
|
+
ctx.broadcastToAll({ type, tabId, data, seq });
|
|
11
|
+
return seq;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=tab-broadcast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-broadcast.js","sourceRoot":"","sources":["../../../../server/services/websocket/tab-broadcast.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,gEAAgE;AAsBhE;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAmB,EACnB,KAAa,EACb,IAAwB,EACxB,IAAa;IAEb,MAAM,MAAM,GAAG,GAAG,CAAC,eAAe,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IACrD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACrC,GAAG,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;IAC9C,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab event buffer — monotonic, bounded replay log for tab-scoped broadcasts.
|
|
3
|
+
*
|
|
4
|
+
* ## Why it exists
|
|
5
|
+
*
|
|
6
|
+
* The platform relay only fans out broadcasts to webs currently paired to the
|
|
7
|
+
* CLI's connection key. During a CLI-side platform reconnect the CLI's key
|
|
8
|
+
* rotates (new `connectionId`) and any web whose transport was pointing at
|
|
9
|
+
* the old key sees no events until it completes its own reconnect handshake.
|
|
10
|
+
* The `executionEventLog` on the session manager covers in-flight execution,
|
|
11
|
+
* but smaller lifecycle events — `movementStart`, `movementComplete`,
|
|
12
|
+
* `tabStateChanged`, `sessionUpdate`, approve/reject acknowledgements — can
|
|
13
|
+
* land in that dark window and be lost.
|
|
14
|
+
*
|
|
15
|
+
* This buffer records every tab-scoped broadcast with a monotonic per-tab
|
|
16
|
+
* `seq`. When the web sends `initTab` / `resumeSession` with its
|
|
17
|
+
* `lastSeenSeq`, we replay anything newer before emitting `tabInitialized`.
|
|
18
|
+
*
|
|
19
|
+
* ## Design choices
|
|
20
|
+
*
|
|
21
|
+
* - **Bounded by both count and age** so a long-idle tab doesn't keep ancient
|
|
22
|
+
* events forever, and a chatty session doesn't balloon memory. The limits
|
|
23
|
+
* are intentionally generous: 1000 events + 15 minutes covers a typical
|
|
24
|
+
* reconnect window by orders of magnitude, and events are small objects.
|
|
25
|
+
* - **Per-tab registry, not per-session** because the web identifies replay
|
|
26
|
+
* targets by `tabId`, which is stable across `new` (sessionId rotates, tab
|
|
27
|
+
* doesn't).
|
|
28
|
+
* - **No sequencing gaps**: `nextSeq` strictly increments, even when the
|
|
29
|
+
* buffer drops old events. The web compares `seq > lastSeenSeq`, so stale
|
|
30
|
+
* numbering below the window is fine — everything the web hasn't seen yet
|
|
31
|
+
* has a larger seq.
|
|
32
|
+
*/
|
|
33
|
+
export interface BufferedEvent {
|
|
34
|
+
/** Monotonic per-tab sequence (1-based). */
|
|
35
|
+
seq: number;
|
|
36
|
+
/** Wire message type, e.g. `output`, `thinking`, `movementComplete`. */
|
|
37
|
+
type: string;
|
|
38
|
+
/** Opaque payload for the wire message. */
|
|
39
|
+
data: unknown;
|
|
40
|
+
/** `Date.now()` at record time. Used for age-based eviction. */
|
|
41
|
+
timestamp: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Bounded replay log for a single tab.
|
|
45
|
+
*
|
|
46
|
+
* Size/age limits are parameterised for testability but defaulted to values
|
|
47
|
+
* that comfortably cover real-world reconnect windows.
|
|
48
|
+
*/
|
|
49
|
+
export declare class TabEventBuffer {
|
|
50
|
+
private readonly maxEvents;
|
|
51
|
+
private readonly maxAgeMs;
|
|
52
|
+
private readonly now;
|
|
53
|
+
private readonly events;
|
|
54
|
+
private nextSeq;
|
|
55
|
+
constructor(maxEvents?: number, maxAgeMs?: number, now?: () => number);
|
|
56
|
+
/**
|
|
57
|
+
* Append an event and return its assigned sequence number.
|
|
58
|
+
*
|
|
59
|
+
* Callers include the returned `seq` on the outgoing wire message so the
|
|
60
|
+
* web can record it and ask for replay starting after that seq on a
|
|
61
|
+
* subsequent reconnect.
|
|
62
|
+
*/
|
|
63
|
+
record(type: string, data: unknown): number;
|
|
64
|
+
/**
|
|
65
|
+
* Return all still-buffered events with `seq > afterSeq`, in original
|
|
66
|
+
* order. Returns an empty array if nothing newer is buffered (either the
|
|
67
|
+
* web is caught up or the window has rolled past).
|
|
68
|
+
*/
|
|
69
|
+
getSince(afterSeq: number): BufferedEvent[];
|
|
70
|
+
/** Current highest assigned seq (monotonic; not reset by eviction). */
|
|
71
|
+
currentSeq(): number;
|
|
72
|
+
/** Events currently held in memory. For tests. */
|
|
73
|
+
size(): number;
|
|
74
|
+
/**
|
|
75
|
+
* Drop events older than `maxAgeMs` from the front, then enforce
|
|
76
|
+
* `maxEvents` by trimming the front further if needed. Eviction keeps the
|
|
77
|
+
* newest events — they're the ones the web is most likely to still need.
|
|
78
|
+
*/
|
|
79
|
+
private evict;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Registry of per-tab buffers. Kept as a thin collection so `HandlerContext`
|
|
83
|
+
* can expose one instance and every broadcast site looks up (or lazily
|
|
84
|
+
* creates) the tab's buffer with a single call.
|
|
85
|
+
*/
|
|
86
|
+
export declare class TabEventBufferRegistry {
|
|
87
|
+
private readonly bufferFactory;
|
|
88
|
+
private readonly buffers;
|
|
89
|
+
constructor(bufferFactory?: () => TabEventBuffer);
|
|
90
|
+
/** Get the buffer for `tabId`, creating it on first touch. */
|
|
91
|
+
getOrCreate(tabId: string): TabEventBuffer;
|
|
92
|
+
/** Get the buffer for `tabId` without creating it. */
|
|
93
|
+
get(tabId: string): TabEventBuffer | undefined;
|
|
94
|
+
/** Forget `tabId` entirely — called on `tabRemoved`. */
|
|
95
|
+
delete(tabId: string): void;
|
|
96
|
+
/** Drop all bookkeeping. Used for tests; no production caller expected. */
|
|
97
|
+
clear(): void;
|
|
98
|
+
}
|
|
99
|
+
/** 1000 events per tab covers typical reconnect windows comfortably. */
|
|
100
|
+
export declare const DEFAULT_MAX_EVENTS = 1000;
|
|
101
|
+
/** 15 minutes of history is more than enough for the longest plausible web reconnect. */
|
|
102
|
+
export declare const DEFAULT_MAX_AGE_MS: number;
|
|
103
|
+
//# sourceMappingURL=tab-event-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-event-buffer.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-buffer.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,2CAA2C;IAC3C,IAAI,EAAE,OAAO,CAAA;IACb,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,qBAAa,cAAc;IAKvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,OAAO,CAAI;gBAGA,SAAS,GAAE,MAA2B,EACtC,QAAQ,GAAE,MAA2B,EACrC,GAAG,GAAE,MAAM,MAAiB;IAG/C;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM;IAO3C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE;IAS3C,uEAAuE;IACvE,UAAU,IAAI,MAAM;IAIpB,kDAAkD;IAClD,IAAI,IAAI,MAAM;IAId;;;;OAIG;IACH,OAAO,CAAC,KAAK;CASd;AAED;;;;GAIG;AACH,qBAAa,sBAAsB;IAI/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAHhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;gBAGzC,aAAa,GAAE,MAAM,cAA2C;IAGnF,8DAA8D;IAC9D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAS1C,sDAAsD;IACtD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI9C,wDAAwD;IACxD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B,2EAA2E;IAC3E,KAAK,IAAI,IAAI;CAGd;AAED,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,OAAO,CAAA;AACtC,yFAAyF;AACzF,eAAO,MAAM,kBAAkB,QAAiB,CAAA"}
|