@cortexkit/opencode-magic-context 0.32.0 → 0.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +3 -0
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +82 -2
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/config/transform-mode.d.ts +15 -0
- package/dist/config/transform-mode.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/cron.d.ts +2 -2
- package/dist/features/magic-context/dreamer/cron.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-provider.d.ts +6 -0
- package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-synapse.d.ts +98 -0
- package/dist/features/magic-context/memory/embedding-synapse.d.ts.map +1 -0
- package/dist/features/magic-context/memory/embedding.d.ts +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts +4 -1
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +7 -4
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts +21 -16
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/plugin-messages.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +27 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/search-measurement.d.ts +14 -0
- package/dist/features/magic-context/search-measurement.d.ts.map +1 -0
- package/dist/features/magic-context/search.d.ts +11 -1
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/session-project-backfill.d.ts +6 -3
- package/dist/features/magic-context/session-project-backfill.d.ts.map +1 -1
- package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/runner.d.ts +1 -0
- package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +8 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +10 -0
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/storage.d.ts +16 -1
- package/dist/features/magic-context/smart-notes/storage.d.ts.map +1 -1
- package/dist/features/magic-context/storage-clone.d.ts +4 -0
- package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +7 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-embedding-measurements.d.ts +60 -0
- package/dist/features/magic-context/storage-embedding-measurements.d.ts.map +1 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts +10 -5
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-schema-helpers.d.ts +3 -4
- package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -0
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/tool-owner-backfill.d.ts +2 -0
- package/dist/features/magic-context/tool-owner-backfill.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-runner.d.ts +7 -1
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts +2 -0
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +5 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts +4 -0
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts +12 -0
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts.map +1 -0
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/decay-render.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +1 -0
- package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts +7 -0
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -0
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +29 -0
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts +6 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +19 -3
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/index.d.ts +1 -1
- package/dist/hooks/magic-context/index.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +1 -0
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-replay.d.ts +51 -0
- package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-slot.d.ts +25 -0
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -0
- package/dist/hooks/magic-context/module-state-sync.d.ts +169 -0
- package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -0
- package/dist/hooks/magic-context/module-wire.d.ts +73 -0
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -0
- package/dist/hooks/magic-context/pass-outcome.d.ts +15 -0
- package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts +17 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts +30 -2
- package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +64 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -0
- package/dist/hooks/magic-context/shadow-sender.d.ts +84 -49
- package/dist/hooks/magic-context/shadow-sender.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +2 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +56 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +9 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17355 -10510
- package/dist/plugin/boot-quiet.d.ts +12 -0
- package/dist/plugin/boot-quiet.d.ts.map +1 -0
- package/dist/plugin/dream-timer.d.ts +6 -2
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/embedding-bootstrap-helpers.d.ts.map +1 -1
- package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
- package/dist/plugin/embedding-routing.d.ts +32 -0
- package/dist/plugin/embedding-routing.d.ts.map +1 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts +54 -2
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts +4 -3
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +19 -2
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/rust-tool-backends.d.ts +10 -0
- package/dist/plugin/rust-tool-backends.d.ts.map +1 -0
- package/dist/plugin/tool-registry.d.ts +2 -0
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts +5 -0
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +3 -1
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +11 -6
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +2 -2
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +3 -0
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/tui-config.d.ts +5 -3
- package/dist/shared/tui-config.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-memory/types.d.ts +2 -0
- package/dist/tools/ctx-memory/types.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts +2 -0
- package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +9 -2
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui/data/notification-socket.d.ts +4 -5
- package/dist/tui/data/notification-socket.d.ts.map +1 -1
- package/dist/tui-compiled/data/context-db.d.ts +9 -2
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
- package/dist/tui-compiled/data/notification-socket.d.ts +4 -5
- package/dist/tui-compiled/data/notification-socket.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/shared/models-dev-cache.test.ts +5 -0
- package/src/shared/models-dev-cache.ts +44 -6
- package/src/shared/opencode-compaction-detector.test.ts +11 -2
- package/src/shared/rpc-client.test.ts +213 -9
- package/src/shared/rpc-client.ts +48 -39
- package/src/shared/rpc-notifications.test.ts +26 -5
- package/src/shared/rpc-notifications.ts +44 -35
- package/src/shared/rpc-server.ts +69 -33
- package/src/shared/rpc-types.ts +3 -0
- package/src/shared/sqlite.ts +22 -3
- package/src/shared/tui-config.test.ts +4 -12
- package/src/shared/tui-config.ts +6 -6
- package/src/tui/data/context-db.test.ts +113 -0
- package/src/tui/data/context-db.ts +26 -19
- package/src/tui/data/notification-socket.test.ts +277 -11
- package/src/tui/data/notification-socket.ts +257 -86
- package/src/tui/index.tsx +42 -25
- package/src/tui/slots/sidebar-content.tsx +62 -24
- package/src/tui-compiled/data/context-db.ts +26 -19
- package/src/tui-compiled/data/notification-socket.ts +257 -86
- package/src/tui-compiled/index.tsx +83 -30
- package/src/tui-compiled/slots/sidebar-content.tsx +45 -23
|
@@ -30,8 +30,8 @@ export interface SocketNotification {
|
|
|
30
30
|
interface NotificationSocketOptions {
|
|
31
31
|
/** Current active session id (re-read cheaply to follow session switches). */
|
|
32
32
|
getSessionId: () => string | null;
|
|
33
|
-
/** Handle one delivered notification. Returns true
|
|
34
|
-
*
|
|
33
|
+
/** Handle one delivered notification. Returns true only after it is fully
|
|
34
|
+
* consumed and can be acknowledged. Async because dialog handlers await. */
|
|
35
35
|
onNotification: (notification: SocketNotification) => boolean | Promise<boolean>;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -48,40 +48,51 @@ let reconnectAttempt = 0;
|
|
|
48
48
|
let closed = false;
|
|
49
49
|
let helloedSession: string | null = null;
|
|
50
50
|
let opts: NotificationSocketOptions | null = null;
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
let activeToken: string | null = null;
|
|
52
|
+
/** Generation of the rpc client used by the active socket. */
|
|
53
53
|
let connectGeneration = 0;
|
|
54
|
+
/** Exactly one endpoint lookup may be active. A monotonically increasing id lets
|
|
55
|
+
* stop/restart invalidate a late lookup before it can publish a socket. */
|
|
56
|
+
let nextAttemptId = 0;
|
|
57
|
+
let inFlightAttemptId: number | null = null;
|
|
54
58
|
|
|
55
59
|
const GLOBAL_CURSOR_KEY = "global";
|
|
56
60
|
const SESSION_CURSOR_PREFIX = "session:";
|
|
57
61
|
const MAX_DEDUPED_NOTIFICATION_IDS = 500;
|
|
62
|
+
const LEGACY_INSTANCE_ID = "legacy";
|
|
63
|
+
type NotificationProtocolMode = "legacy" | "v2";
|
|
58
64
|
|
|
59
65
|
/**
|
|
60
|
-
* Notification ids
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* backlog. The id set de-dupes at-least-once re-delivery when a reconnect sends
|
|
64
|
-
* a session cursor that is intentionally lower than the global cursor.
|
|
66
|
+
* Notification ids restart with every server instance. Epoch-prefixing cursor keys
|
|
67
|
+
* and deduplication ids prevents a surviving TUI from applying a replaced server's
|
|
68
|
+
* high watermark or remembered ids to the replacement's fresh queue.
|
|
65
69
|
*/
|
|
70
|
+
let activeInstanceId: string | null = null;
|
|
71
|
+
let notificationProtocolMode: NotificationProtocolMode | null = null;
|
|
72
|
+
const bufferedNotifications: SocketNotification[] = [];
|
|
66
73
|
const lastHandledIdByCursor = new Map<string, number>();
|
|
67
|
-
const handledNotificationIds = new Set<
|
|
68
|
-
const handledNotificationIdOrder:
|
|
74
|
+
const handledNotificationIds = new Set<string>();
|
|
75
|
+
const handledNotificationIdOrder: string[] = [];
|
|
76
|
+
const legacyUnconsumedIdsByCursor = new Map<string, Set<number>>();
|
|
77
|
+
const legacyConsumedIdsByCursor = new Map<string, Set<number>>();
|
|
78
|
+
/** Dialog actions share UI state, so notification handlers must never overlap. */
|
|
79
|
+
let notificationHandlingChain: Promise<void> = Promise.resolve();
|
|
69
80
|
|
|
70
|
-
/** Open the persistent notification socket.
|
|
71
|
-
* is a no-op. Reconnects on its own after any drop. */
|
|
81
|
+
/** Open the persistent notification socket. Reconnects on its own after a drop. */
|
|
72
82
|
export function startNotificationSocket(options: NotificationSocketOptions): void {
|
|
73
83
|
opts = options;
|
|
74
84
|
closed = false;
|
|
75
|
-
|
|
76
|
-
connect();
|
|
85
|
+
if (!socket && inFlightAttemptId === null) void connect();
|
|
77
86
|
if (!sessionWatchTimer) {
|
|
78
87
|
sessionWatchTimer = setInterval(watchSession, SESSION_WATCH_MS);
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
/** Close the socket and
|
|
91
|
+
/** Close the socket and release all state owned by this TUI initialization. */
|
|
83
92
|
export function stopNotificationSocket(): void {
|
|
84
93
|
closed = true;
|
|
94
|
+
nextAttemptId += 1;
|
|
95
|
+
inFlightAttemptId = null;
|
|
85
96
|
if (reconnectTimer) {
|
|
86
97
|
clearTimeout(reconnectTimer);
|
|
87
98
|
reconnectTimer = undefined;
|
|
@@ -96,8 +107,19 @@ export function stopNotificationSocket(): void {
|
|
|
96
107
|
// best-effort
|
|
97
108
|
}
|
|
98
109
|
socket = null;
|
|
110
|
+
opts = null;
|
|
111
|
+
activeToken = null;
|
|
99
112
|
helloedSession = null;
|
|
100
113
|
reconnectAttempt = 0;
|
|
114
|
+
activeInstanceId = null;
|
|
115
|
+
notificationProtocolMode = null;
|
|
116
|
+
bufferedNotifications.length = 0;
|
|
117
|
+
lastHandledIdByCursor.clear();
|
|
118
|
+
handledNotificationIds.clear();
|
|
119
|
+
handledNotificationIdOrder.length = 0;
|
|
120
|
+
legacyUnconsumedIdsByCursor.clear();
|
|
121
|
+
legacyConsumedIdsByCursor.clear();
|
|
122
|
+
notificationHandlingChain = Promise.resolve();
|
|
101
123
|
}
|
|
102
124
|
|
|
103
125
|
function scheduleReconnect(): void {
|
|
@@ -112,17 +134,22 @@ function scheduleReconnect(): void {
|
|
|
112
134
|
}
|
|
113
135
|
|
|
114
136
|
async function connect(): Promise<void> {
|
|
115
|
-
if (closed) return;
|
|
116
|
-
if (socket) return; // already connected/connecting
|
|
137
|
+
if (closed || socket || inFlightAttemptId !== null) return;
|
|
117
138
|
|
|
118
139
|
const client = getRpcClient();
|
|
119
140
|
if (!client) {
|
|
120
141
|
scheduleReconnect();
|
|
121
142
|
return;
|
|
122
143
|
}
|
|
144
|
+
|
|
145
|
+
const attemptId = ++nextAttemptId;
|
|
146
|
+
const rpcGeneration = getRpcGeneration();
|
|
147
|
+
inFlightAttemptId = attemptId;
|
|
123
148
|
const endpoint = await client.resolveEndpoint();
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
if (closed || inFlightAttemptId !== attemptId || getRpcGeneration() !== rpcGeneration) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
inFlightAttemptId = null;
|
|
126
153
|
if (!endpoint) {
|
|
127
154
|
scheduleReconnect();
|
|
128
155
|
return;
|
|
@@ -130,32 +157,48 @@ async function connect(): Promise<void> {
|
|
|
130
157
|
|
|
131
158
|
let ws: WebSocket;
|
|
132
159
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
160
|
+
ws = new WebSocket(`ws://127.0.0.1:${endpoint.port}/ws`, {
|
|
161
|
+
headers: endpoint.token ? { Authorization: `Bearer ${endpoint.token}` } : {},
|
|
162
|
+
});
|
|
135
163
|
} catch {
|
|
136
164
|
client.reset();
|
|
137
165
|
scheduleReconnect();
|
|
138
166
|
return;
|
|
139
167
|
}
|
|
168
|
+
|
|
169
|
+
if (closed || getRpcGeneration() !== rpcGeneration || socket) {
|
|
170
|
+
ws.close();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
connectGeneration = rpcGeneration;
|
|
174
|
+
activeToken = endpoint.token;
|
|
175
|
+
notificationProtocolMode = null;
|
|
176
|
+
bufferedNotifications.length = 0;
|
|
177
|
+
switchNotificationEpoch(endpoint.instanceId ?? LEGACY_INSTANCE_ID);
|
|
140
178
|
socket = ws;
|
|
141
179
|
|
|
142
180
|
ws.addEventListener("open", () => {
|
|
143
|
-
if (socket !== ws)
|
|
181
|
+
if (socket !== ws || getRpcGeneration() !== connectGeneration) {
|
|
182
|
+
ws.close();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
144
185
|
reconnectAttempt = 0;
|
|
145
186
|
sendHello(ws, endpoint.token);
|
|
146
187
|
});
|
|
147
188
|
|
|
148
189
|
ws.addEventListener("message", (event) => {
|
|
149
190
|
if (socket !== ws) return;
|
|
150
|
-
|
|
191
|
+
handleSocketMessage(ws, String((event as MessageEvent).data), endpoint.token);
|
|
151
192
|
});
|
|
152
193
|
|
|
153
194
|
const onDown = () => {
|
|
195
|
+
if (socket !== ws) return;
|
|
154
196
|
client.reset();
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
197
|
+
socket = null;
|
|
198
|
+
activeToken = null;
|
|
199
|
+
helloedSession = null;
|
|
200
|
+
notificationProtocolMode = null;
|
|
201
|
+
bufferedNotifications.length = 0;
|
|
159
202
|
scheduleReconnect();
|
|
160
203
|
};
|
|
161
204
|
ws.addEventListener("close", onDown);
|
|
@@ -168,54 +211,55 @@ function sendHello(ws: WebSocket, token: string | null): void {
|
|
|
168
211
|
ws.send(
|
|
169
212
|
JSON.stringify({
|
|
170
213
|
type: "hello",
|
|
214
|
+
protocol: 2,
|
|
215
|
+
instanceId: activeInstanceId,
|
|
171
216
|
token: token ?? "",
|
|
172
217
|
sessionId,
|
|
218
|
+
// Older servers still read these scoped cursors. Protocol 2 servers
|
|
219
|
+
// rely only on exact acknowledgements and do not prune from them.
|
|
173
220
|
lastReceivedId: cursorForKey(cursorKeyForSession(sessionId)),
|
|
174
|
-
globalLastReceivedId: cursorForKey(
|
|
221
|
+
globalLastReceivedId: cursorForKey(cursorKeyForSession(undefined)),
|
|
175
222
|
}),
|
|
176
223
|
);
|
|
177
224
|
}
|
|
178
225
|
|
|
179
|
-
|
|
180
|
-
let msg: {
|
|
226
|
+
function handleSocketMessage(ws: WebSocket, raw: string, token: string | null): void {
|
|
227
|
+
let msg: {
|
|
228
|
+
type?: string;
|
|
229
|
+
notification?: SocketNotification;
|
|
230
|
+
error?: string;
|
|
231
|
+
instanceId?: string;
|
|
232
|
+
};
|
|
181
233
|
try {
|
|
182
234
|
msg = JSON.parse(raw);
|
|
183
235
|
} catch {
|
|
184
236
|
return;
|
|
185
237
|
}
|
|
186
238
|
|
|
187
|
-
if (msg.type === "
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return;
|
|
239
|
+
if (msg.type === "hello-ack") {
|
|
240
|
+
if (typeof msg.instanceId === "string") {
|
|
241
|
+
notificationProtocolMode = "v2";
|
|
242
|
+
if (msg.instanceId !== activeInstanceId) {
|
|
243
|
+
switchNotificationEpoch(msg.instanceId);
|
|
244
|
+
// The server does not prune protocol 2 backlog from hello cursors, so a
|
|
245
|
+
// corrected hello safely establishes fresh epoch-scoped state.
|
|
246
|
+
sendHello(ws, token);
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
// A hello-ack without an instance id is the frozen v0.32 shape. Its
|
|
250
|
+
// server ignores exact-id acks, so cursors must remain gap-safe.
|
|
251
|
+
notificationProtocolMode = "legacy";
|
|
252
|
+
switchNotificationEpoch(LEGACY_INSTANCE_ID);
|
|
202
253
|
}
|
|
254
|
+
flushBufferedNotifications(ws);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
203
257
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
// A dispose/reinit during an awaited dialog handler invalidates this socket.
|
|
211
|
-
if (socket !== ws || getRpcGeneration() !== connectGeneration) return;
|
|
212
|
-
if (consumed) {
|
|
213
|
-
rememberHandledId(notification.id);
|
|
214
|
-
advanceCursor(notificationCursorKey(notification), notification.id);
|
|
215
|
-
// Ack only the notification's own cursor scope. A dropped ack is safe:
|
|
216
|
-
// the next hello sends the same per-scope cursors and duplicates are
|
|
217
|
-
// ignored locally.
|
|
218
|
-
sendAck(ws, notification);
|
|
258
|
+
if (msg.type === "notification" && msg.notification) {
|
|
259
|
+
if (notificationProtocolMode === null) {
|
|
260
|
+
bufferedNotifications.push(msg.notification);
|
|
261
|
+
} else {
|
|
262
|
+
queueNotification(ws, msg.notification);
|
|
219
263
|
}
|
|
220
264
|
return;
|
|
221
265
|
}
|
|
@@ -231,14 +275,82 @@ async function handleSocketMessage(ws: WebSocket, raw: string): Promise<void> {
|
|
|
231
275
|
}
|
|
232
276
|
}
|
|
233
277
|
|
|
278
|
+
function flushBufferedNotifications(ws: WebSocket): void {
|
|
279
|
+
const pending = bufferedNotifications.splice(0);
|
|
280
|
+
for (const notification of pending) queueNotification(ws, notification);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function queueNotification(ws: WebSocket, notification: SocketNotification): void {
|
|
284
|
+
const deliveryInstanceId = activeInstanceId ?? LEGACY_INSTANCE_ID;
|
|
285
|
+
const deliveryMode = notificationProtocolMode;
|
|
286
|
+
if (deliveryMode === null) return;
|
|
287
|
+
// A single promise chain prevents two dialog actions from replacing each
|
|
288
|
+
// other's UI while either handler is still awaiting user input.
|
|
289
|
+
notificationHandlingChain = notificationHandlingChain
|
|
290
|
+
.then(() => handleNotification(ws, notification, deliveryInstanceId, deliveryMode))
|
|
291
|
+
.catch(() => {});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function handleNotification(
|
|
295
|
+
ws: WebSocket,
|
|
296
|
+
notification: SocketNotification,
|
|
297
|
+
deliveryInstanceId: string,
|
|
298
|
+
deliveryMode: NotificationProtocolMode,
|
|
299
|
+
): Promise<void> {
|
|
300
|
+
if (
|
|
301
|
+
socket !== ws ||
|
|
302
|
+
getRpcGeneration() !== connectGeneration ||
|
|
303
|
+
activeInstanceId !== deliveryInstanceId ||
|
|
304
|
+
notificationProtocolMode !== deliveryMode
|
|
305
|
+
) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// Client-side session filtering follows session switches that happen between
|
|
309
|
+
// queueing and delivery. Global notifications always apply.
|
|
310
|
+
const active = opts?.getSessionId() ?? null;
|
|
311
|
+
if (notification.sessionId !== undefined && notification.sessionId !== active) return;
|
|
312
|
+
|
|
313
|
+
if (deliveryMode === "legacy") markLegacyUnconsumed(notification);
|
|
314
|
+
if (handledNotificationIds.has(notificationDedupKey(notification.id, deliveryInstanceId))) {
|
|
315
|
+
if (deliveryMode === "legacy") markLegacyConsumed(notification);
|
|
316
|
+
sendAck(ws, notification, deliveryMode);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let consumed = false;
|
|
321
|
+
try {
|
|
322
|
+
consumed = await Promise.resolve(opts?.onNotification(notification) ?? false);
|
|
323
|
+
} catch {
|
|
324
|
+
consumed = false;
|
|
325
|
+
}
|
|
326
|
+
// A dispose, reconnect, or epoch correction during an awaited dialog invalidates
|
|
327
|
+
// the delivery. The server retains it for the current socket to redeliver.
|
|
328
|
+
if (
|
|
329
|
+
socket !== ws ||
|
|
330
|
+
getRpcGeneration() !== connectGeneration ||
|
|
331
|
+
activeInstanceId !== deliveryInstanceId ||
|
|
332
|
+
notificationProtocolMode !== deliveryMode
|
|
333
|
+
) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (consumed) {
|
|
337
|
+
rememberHandledId(notification.id, deliveryInstanceId);
|
|
338
|
+
if (deliveryMode === "legacy") {
|
|
339
|
+
markLegacyConsumed(notification);
|
|
340
|
+
} else {
|
|
341
|
+
advanceCursor(notificationCursorKey(notification), notification.id);
|
|
342
|
+
}
|
|
343
|
+
sendAck(ws, notification, deliveryMode);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
234
347
|
function cursorKeyForSession(sessionId: string | null | undefined): string {
|
|
235
|
-
|
|
348
|
+
const scope = sessionId ? `${SESSION_CURSOR_PREFIX}${sessionId}` : GLOBAL_CURSOR_KEY;
|
|
349
|
+
return `${activeInstanceId ?? LEGACY_INSTANCE_ID}:${scope}`;
|
|
236
350
|
}
|
|
237
351
|
|
|
238
352
|
function notificationCursorKey(notification: SocketNotification): string {
|
|
239
|
-
return notification.sessionId
|
|
240
|
-
? `${SESSION_CURSOR_PREFIX}${notification.sessionId}`
|
|
241
|
-
: GLOBAL_CURSOR_KEY;
|
|
353
|
+
return cursorKeyForSession(notification.sessionId);
|
|
242
354
|
}
|
|
243
355
|
|
|
244
356
|
function cursorForKey(key: string): number {
|
|
@@ -249,34 +361,96 @@ function advanceCursor(key: string, id: number): void {
|
|
|
249
361
|
if (id > cursorForKey(key)) lastHandledIdByCursor.set(key, id);
|
|
250
362
|
}
|
|
251
363
|
|
|
252
|
-
function
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
364
|
+
function idsForCursor(map: Map<string, Set<number>>, key: string): Set<number> {
|
|
365
|
+
let ids = map.get(key);
|
|
366
|
+
if (!ids) {
|
|
367
|
+
ids = new Set<number>();
|
|
368
|
+
map.set(key, ids);
|
|
369
|
+
}
|
|
370
|
+
return ids;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function markLegacyUnconsumed(notification: SocketNotification): void {
|
|
374
|
+
const key = notificationCursorKey(notification);
|
|
375
|
+
if (idsForCursor(legacyConsumedIdsByCursor, key).has(notification.id)) return;
|
|
376
|
+
idsForCursor(legacyUnconsumedIdsByCursor, key).add(notification.id);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function markLegacyConsumed(notification: SocketNotification): void {
|
|
380
|
+
const key = notificationCursorKey(notification);
|
|
381
|
+
idsForCursor(legacyUnconsumedIdsByCursor, key).delete(notification.id);
|
|
382
|
+
const consumedIds = idsForCursor(legacyConsumedIdsByCursor, key);
|
|
383
|
+
consumedIds.add(notification.id);
|
|
384
|
+
|
|
385
|
+
let safeCursor = Math.max(cursorForKey(key), ...consumedIds);
|
|
386
|
+
const unconsumedIds = legacyUnconsumedIdsByCursor.get(key);
|
|
387
|
+
if (unconsumedIds && unconsumedIds.size > 0) {
|
|
388
|
+
safeCursor = Math.min(safeCursor, Math.min(...unconsumedIds) - 1);
|
|
389
|
+
}
|
|
390
|
+
advanceCursor(key, safeCursor);
|
|
391
|
+
for (const id of consumedIds) {
|
|
392
|
+
if (id <= cursorForKey(key)) consumedIds.delete(id);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function notificationDedupKey(
|
|
397
|
+
id: number,
|
|
398
|
+
instanceId = activeInstanceId ?? LEGACY_INSTANCE_ID,
|
|
399
|
+
): string {
|
|
400
|
+
return `${instanceId}:${id}`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function rememberHandledId(id: number, instanceId: string): void {
|
|
404
|
+
const key = notificationDedupKey(id, instanceId);
|
|
405
|
+
if (handledNotificationIds.has(key)) return;
|
|
406
|
+
handledNotificationIds.add(key);
|
|
407
|
+
handledNotificationIdOrder.push(key);
|
|
256
408
|
while (handledNotificationIdOrder.length > MAX_DEDUPED_NOTIFICATION_IDS) {
|
|
257
409
|
const evicted = handledNotificationIdOrder.shift();
|
|
258
410
|
if (evicted !== undefined) handledNotificationIds.delete(evicted);
|
|
259
411
|
}
|
|
260
412
|
}
|
|
261
413
|
|
|
262
|
-
function
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
414
|
+
function switchNotificationEpoch(instanceId: string): void {
|
|
415
|
+
if (activeInstanceId === instanceId) return;
|
|
416
|
+
activeInstanceId = instanceId;
|
|
417
|
+
lastHandledIdByCursor.clear();
|
|
418
|
+
handledNotificationIds.clear();
|
|
419
|
+
handledNotificationIdOrder.length = 0;
|
|
420
|
+
legacyUnconsumedIdsByCursor.clear();
|
|
421
|
+
legacyConsumedIdsByCursor.clear();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function sendAck(
|
|
425
|
+
ws: WebSocket,
|
|
426
|
+
notification: SocketNotification,
|
|
427
|
+
mode: NotificationProtocolMode,
|
|
428
|
+
): void {
|
|
267
429
|
try {
|
|
268
|
-
|
|
430
|
+
if (mode === "legacy") {
|
|
431
|
+
const cursor = cursorForKey(notificationCursorKey(notification));
|
|
432
|
+
ws.send(
|
|
433
|
+
JSON.stringify({
|
|
434
|
+
type: "ack",
|
|
435
|
+
cursor,
|
|
436
|
+
...(notification.sessionId
|
|
437
|
+
? { sessionId: notification.sessionId }
|
|
438
|
+
: { ackScope: "global" }),
|
|
439
|
+
}),
|
|
440
|
+
);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// Exact ids avoid deleting an earlier notification whose handler failed
|
|
444
|
+
// while a later notification was consumed successfully.
|
|
445
|
+
ws.send(JSON.stringify({ type: "ack", ids: [notification.id] }));
|
|
269
446
|
} catch {
|
|
270
|
-
//
|
|
447
|
+
// Best-effort: an unacknowledged row is safely deduplicated and re-acked
|
|
448
|
+
// when the server delivers it again after reconnecting.
|
|
271
449
|
}
|
|
272
450
|
}
|
|
273
451
|
|
|
274
452
|
export function _resetNotificationSocketStateForTesting(): void {
|
|
275
453
|
stopNotificationSocket();
|
|
276
|
-
opts = null;
|
|
277
|
-
lastHandledIdByCursor.clear();
|
|
278
|
-
handledNotificationIds.clear();
|
|
279
|
-
handledNotificationIdOrder.length = 0;
|
|
280
454
|
}
|
|
281
455
|
|
|
282
456
|
/** Cheap session-change watcher: re-scope the socket only when the active session
|
|
@@ -285,10 +459,7 @@ function watchSession(): void {
|
|
|
285
459
|
if (closed || !socket || socket.readyState !== WebSocket.OPEN) return;
|
|
286
460
|
const current = opts?.getSessionId() ?? null;
|
|
287
461
|
if (current === helloedSession) return;
|
|
288
|
-
// Re-hello with the
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (!socket || socket.readyState !== WebSocket.OPEN) return;
|
|
292
|
-
sendHello(socket, endpoint?.token ?? null);
|
|
293
|
-
});
|
|
462
|
+
// Re-hello with the token authenticated by this socket; no rediscovery or
|
|
463
|
+
// network request is needed for a local route change.
|
|
464
|
+
sendHello(socket, activeToken);
|
|
294
465
|
}
|
|
@@ -676,6 +676,42 @@ const StatusDialog = props => {
|
|
|
676
676
|
_$setProp(_el$45, "marginTop", 1);
|
|
677
677
|
_$insertNode(_el$46, _el$47);
|
|
678
678
|
_$insertNode(_el$47, _$createTextNode(`History Compression`));
|
|
679
|
+
_$insert(_el$37, (() => {
|
|
680
|
+
var _c$3 = _$memo(() => typeof s().boundaryPresent === "boolean");
|
|
681
|
+
return () => _c$3() && _$createComponent(R, {
|
|
682
|
+
get t() {
|
|
683
|
+
return t();
|
|
684
|
+
},
|
|
685
|
+
l: "Boundary",
|
|
686
|
+
get v() {
|
|
687
|
+
return s().boundaryPresent ? "present" : "absent";
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
})(), null);
|
|
691
|
+
_$insert(_el$37, (() => {
|
|
692
|
+
var _c$4 = _$memo(() => s().coverageOrdinal !== undefined);
|
|
693
|
+
return () => _c$4() && _$createComponent(R, {
|
|
694
|
+
get t() {
|
|
695
|
+
return t();
|
|
696
|
+
},
|
|
697
|
+
l: "Coverage ordinal",
|
|
698
|
+
get v() {
|
|
699
|
+
return _$memo(() => s().coverageOrdinal == null)() ? "none" : String(s().coverageOrdinal);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
})(), null);
|
|
703
|
+
_$insert(_el$37, (() => {
|
|
704
|
+
var _c$5 = _$memo(() => typeof s().boundaryPresent === "boolean");
|
|
705
|
+
return () => _c$5() && _$createComponent(R, {
|
|
706
|
+
get t() {
|
|
707
|
+
return t();
|
|
708
|
+
},
|
|
709
|
+
l: "Compartments",
|
|
710
|
+
get v() {
|
|
711
|
+
return String(s().compartmentCount);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
})(), null);
|
|
679
715
|
_$insert(_el$37, _$createComponent(R, {
|
|
680
716
|
get t() {
|
|
681
717
|
return t();
|
|
@@ -686,8 +722,8 @@ const StatusDialog = props => {
|
|
|
686
722
|
}
|
|
687
723
|
}), null);
|
|
688
724
|
_$insert(_el$37, (() => {
|
|
689
|
-
var _c$
|
|
690
|
-
return () => _c$
|
|
725
|
+
var _c$6 = _$memo(() => s().compressionBudget != null);
|
|
726
|
+
return () => _c$6() && _$createComponent(R, {
|
|
691
727
|
get t() {
|
|
692
728
|
return t();
|
|
693
729
|
},
|
|
@@ -698,8 +734,8 @@ const StatusDialog = props => {
|
|
|
698
734
|
});
|
|
699
735
|
})(), null);
|
|
700
736
|
_$insert(_el$37, (() => {
|
|
701
|
-
var _c$
|
|
702
|
-
return () => _c$
|
|
737
|
+
var _c$7 = _$memo(() => !!s().lastDreamerRunAt);
|
|
738
|
+
return () => _c$7() && _$createComponent(R, {
|
|
703
739
|
get t() {
|
|
704
740
|
return t();
|
|
705
741
|
},
|
|
@@ -713,8 +749,8 @@ const StatusDialog = props => {
|
|
|
713
749
|
});
|
|
714
750
|
})(), null);
|
|
715
751
|
_$insert(_el$4, (() => {
|
|
716
|
-
var _c$
|
|
717
|
-
return () => _c$
|
|
752
|
+
var _c$8 = _$memo(() => !!s().lastTransformError);
|
|
753
|
+
return () => _c$8() && (() => {
|
|
718
754
|
var _el$62 = _$createElement("box"),
|
|
719
755
|
_el$63 = _$createElement("text"),
|
|
720
756
|
_el$64 = _$createTextNode(`⚠ `);
|
|
@@ -807,17 +843,32 @@ async function showRecompDialog(api, targetSessionId = getSessionId(api)) {
|
|
|
807
843
|
});
|
|
808
844
|
return false;
|
|
809
845
|
}
|
|
810
|
-
const
|
|
846
|
+
const countResult = await getCompartmentCount(sessionId);
|
|
811
847
|
// Ack only after the dialog is actually shown for the same active session;
|
|
812
848
|
// route switches while the RPC detail load is in flight must leave it pending.
|
|
813
849
|
if (getSessionId(api) !== sessionId) return false;
|
|
850
|
+
if (!countResult.ok) {
|
|
851
|
+
showToast(api, {
|
|
852
|
+
message: "Unable to load recomp details",
|
|
853
|
+
variant: "error"
|
|
854
|
+
});
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
const count = countResult.count;
|
|
814
858
|
api.ui.dialog.replace(() => _$createComponent(api.ui.DialogConfirm, {
|
|
815
859
|
title: "\u26A0\uFE0F Recomp Confirmation",
|
|
816
860
|
get message() {
|
|
817
861
|
return [count === 0 ? "This session has no compartments yet — recomp will build them from raw history." : `You have ${count} compartments.`, "", "Recomp will regenerate all compartments and facts from raw history.", "This may take a long time and consume significant tokens.", "", "Proceed?"].join("\n");
|
|
818
862
|
},
|
|
819
|
-
onConfirm: () => {
|
|
820
|
-
|
|
863
|
+
onConfirm: async () => {
|
|
864
|
+
const requested = await requestRecomp(sessionId);
|
|
865
|
+
if (!requested) {
|
|
866
|
+
showToast(api, {
|
|
867
|
+
message: "Recomp request failed",
|
|
868
|
+
variant: "error"
|
|
869
|
+
});
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
821
872
|
kickRecompProgressRefresh();
|
|
822
873
|
showToast(api, {
|
|
823
874
|
message: "Recomp requested — historian will start shortly",
|
|
@@ -848,24 +899,23 @@ function showUpgradeDialog(api, resume, targetSessionId = getSessionId(api)) {
|
|
|
848
899
|
api.ui.dialog.replace(() => _$createComponent(api.ui.DialogConfirm, {
|
|
849
900
|
title: title,
|
|
850
901
|
message: message,
|
|
851
|
-
onConfirm: () => {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
902
|
+
onConfirm: async () => {
|
|
903
|
+
const started = await requestUpgrade(sessionId);
|
|
904
|
+
if (!started) {
|
|
905
|
+
showToast(api, {
|
|
906
|
+
message: "Session upgrade request failed",
|
|
907
|
+
variant: "error"
|
|
908
|
+
});
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
// The RPC call fires no message event, so start the sidebar's
|
|
912
|
+
// progress poll only after the server accepts the request.
|
|
855
913
|
kickRecompProgressRefresh();
|
|
856
914
|
showToast(api, {
|
|
857
915
|
message: resume ? "Resuming session upgrade — running in the background" : "Session upgrade started — running in the background",
|
|
858
916
|
variant: "info"
|
|
859
917
|
});
|
|
860
|
-
|
|
861
|
-
// actually started. If requestUpgrade() returns false (RPC /
|
|
862
|
-
// server / db / auth failure, restart race), the session stays
|
|
863
|
-
// legacy — dismissing first would set upgradeRemindedAt and
|
|
864
|
-
// suppress all future reminders for a session that never
|
|
865
|
-
// upgraded. (Resume prompts are staging-driven and unaffected.)
|
|
866
|
-
void requestUpgrade(sessionId).then(started => {
|
|
867
|
-
if (started) void dismissUpgradeReminder(sessionId);
|
|
868
|
-
});
|
|
918
|
+
void dismissUpgradeReminder(sessionId);
|
|
869
919
|
},
|
|
870
920
|
onCancel: () => {
|
|
871
921
|
// Explicit decline → set the durable stamp so we don't re-prompt
|
|
@@ -1089,13 +1139,20 @@ async function showStartupAnnouncement(api) {
|
|
|
1089
1139
|
}
|
|
1090
1140
|
}
|
|
1091
1141
|
const tui = async (api, _options, meta) => {
|
|
1092
|
-
// Initialize RPC client for server communication
|
|
1093
1142
|
const directory = api.state.path.directory ?? "";
|
|
1143
|
+
// A conflicted installation intentionally has no server. Gate before RPC
|
|
1144
|
+
// discovery or socket startup so disabled installs perform no idle work.
|
|
1145
|
+
const conflictResult = detectConflicts(directory);
|
|
1146
|
+
if (conflictResult.hasConflict) {
|
|
1147
|
+
showConflictDialog(api, directory, conflictResult.reasons, conflictResult.conflicts);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1094
1150
|
initRpcClient(directory);
|
|
1095
1151
|
await refreshToastDurationMs();
|
|
1096
1152
|
|
|
1097
1153
|
// Register sidebar slot
|
|
1098
|
-
|
|
1154
|
+
const sidebarSlot = createSidebarContentSlot(api);
|
|
1155
|
+
api.slots.register(sidebarSlot);
|
|
1099
1156
|
|
|
1100
1157
|
// Register TUI command palette entries (no slash field — slash commands
|
|
1101
1158
|
// are registered server-side so there's only one /ctx-* registration).
|
|
@@ -1124,7 +1181,7 @@ const tui = async (api, _options, meta) => {
|
|
|
1124
1181
|
// session; global (session-less) ones always apply. Returning false leaves
|
|
1125
1182
|
// it unacked so a TUI on the right session (or a later switch back) still
|
|
1126
1183
|
// gets it.
|
|
1127
|
-
if (n.sessionId
|
|
1184
|
+
if (n.sessionId !== undefined && n.sessionId !== requestedSessionId) {
|
|
1128
1185
|
return false;
|
|
1129
1186
|
}
|
|
1130
1187
|
if (n.type === "toast") {
|
|
@@ -1182,14 +1239,10 @@ const tui = async (api, _options, meta) => {
|
|
|
1182
1239
|
|
|
1183
1240
|
// Clean up on dispose
|
|
1184
1241
|
api.lifecycle.onDispose(() => {
|
|
1242
|
+
sidebarSlot.dispose();
|
|
1185
1243
|
stopNotificationSocket();
|
|
1186
1244
|
closeRpc();
|
|
1187
1245
|
});
|
|
1188
|
-
const conflictResult = detectConflicts(directory);
|
|
1189
|
-
if (conflictResult.hasConflict) {
|
|
1190
|
-
showConflictDialog(api, directory, conflictResult.reasons, conflictResult.conflicts);
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
1246
|
|
|
1194
1247
|
// Show one-shot release announcement after conflict gate.
|
|
1195
1248
|
// Fire-and-forget: if the server isn't ready or RPC fails, the next TUI
|