@cortexkit/aft-opencode 0.30.0 → 0.30.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.
Files changed (38) hide show
  1. package/dist/bg-notifications.d.ts +2 -0
  2. package/dist/bg-notifications.d.ts.map +1 -1
  3. package/dist/hooks/auto-update-checker/cache.d.ts +5 -16
  4. package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
  5. package/dist/hooks/auto-update-checker/index.d.ts +1 -1
  6. package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1005 -632
  9. package/dist/notifications.d.ts +8 -5
  10. package/dist/notifications.d.ts.map +1 -1
  11. package/dist/shared/last-assistant-model.d.ts +5 -6
  12. package/dist/shared/last-assistant-model.d.ts.map +1 -1
  13. package/dist/shared/live-server-client.d.ts +24 -22
  14. package/dist/shared/live-server-client.d.ts.map +1 -1
  15. package/dist/shared/rpc-client.d.ts +10 -3
  16. package/dist/shared/rpc-client.d.ts.map +1 -1
  17. package/dist/shared/storage-paths.d.ts +2 -0
  18. package/dist/shared/storage-paths.d.ts.map +1 -0
  19. package/dist/tools/_shared.d.ts +39 -0
  20. package/dist/tools/_shared.d.ts.map +1 -1
  21. package/dist/tools/ast.d.ts.map +1 -1
  22. package/dist/tools/bash.d.ts.map +1 -1
  23. package/dist/tools/bash_watch.d.ts +4 -0
  24. package/dist/tools/bash_watch.d.ts.map +1 -1
  25. package/dist/tools/hoisted.d.ts.map +1 -1
  26. package/dist/tools/lsp.d.ts.map +1 -1
  27. package/dist/tools/navigation.d.ts.map +1 -1
  28. package/dist/tools/reading.d.ts.map +1 -1
  29. package/dist/tools/search.d.ts.map +1 -1
  30. package/dist/tools/semantic.d.ts.map +1 -1
  31. package/dist/tui.js +222 -123
  32. package/package.json +8 -8
  33. package/src/shared/last-assistant-model.ts +9 -27
  34. package/src/shared/live-server-client.ts +80 -48
  35. package/src/shared/rpc-client.ts +132 -28
  36. package/src/shared/storage-paths.ts +22 -0
  37. package/src/tui/index.tsx +39 -17
  38. package/src/tui/sidebar.tsx +94 -27
@@ -12,6 +12,7 @@ import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
12
12
 
13
13
  import { AftRpcClient } from "../shared/rpc-client";
14
14
  import { type AftStatusSnapshot, coerceAftStatus, type StatusCompression } from "../shared/status";
15
+ import { resolveCortexKitStorageRoot } from "../shared/storage-paths";
15
16
 
16
17
  const SINGLE_BORDER = { type: "single" } as any;
17
18
  const REFRESH_DEBOUNCE_MS = 200;
@@ -135,6 +136,13 @@ const SectionHeader = (props: { theme: TuiThemeCurrent; title: string; marginTop
135
136
  </box>
136
137
  );
137
138
 
139
+ // v0.27 moved AFT storage to the CortexKit root. TUI code must use a
140
+ // lightweight local path helper rather than the shared bridge barrel, which
141
+ // also exports URL-fetch helpers unsuitable for Bun's TUI runtime.
142
+ export function resolveTuiStorageDir(): string {
143
+ return resolveCortexKitStorageRoot();
144
+ }
145
+
138
146
  // One RPC client per project directory — same pattern as the /aft-status
139
147
  // dialog handler in tui/index.tsx. Sharing the map avoids opening a second
140
148
  // connection just for the sidebar.
@@ -142,56 +150,111 @@ const sidebarClients = new Map<string, AftRpcClient>();
142
150
  function getClient(directory: string): AftRpcClient {
143
151
  let client = sidebarClients.get(directory);
144
152
  if (client) return client;
145
- const home = process.env.HOME || process.env.USERPROFILE || "";
146
- const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
147
- // v0.27 moved AFT storage to the CortexKit root. Must match the server
148
- // plugin's `resolveCortexKitStorageRoot()` or the sidebar will poll a
149
- // stale legacy port file and never connect to the live RPC server.
150
- const storageDir = `${dataHome}/cortexkit/aft`;
151
- client = new AftRpcClient(storageDir, directory);
153
+ client = new AftRpcClient(resolveTuiStorageDir(), directory);
152
154
  sidebarClients.set(directory, client);
153
155
  return client;
154
156
  }
155
157
 
158
+ export type ScopedSidebarStatus = {
159
+ directory: string;
160
+ sessionID: string;
161
+ snapshot: AftStatusSnapshot;
162
+ };
163
+
164
+ export function scopedSidebarSnapshot(
165
+ scoped: ScopedSidebarStatus | null,
166
+ directory: string,
167
+ sessionID: string,
168
+ ): AftStatusSnapshot | null {
169
+ if (!scoped) return null;
170
+ if (scoped.directory !== directory || scoped.sessionID !== sessionID) return null;
171
+ return scoped.snapshot;
172
+ }
173
+
156
174
  const SidebarContent = (props: {
157
175
  api: TuiPluginApi;
158
176
  sessionID: () => string;
159
177
  theme: TuiThemeCurrent;
160
178
  pluginVersion: string;
161
179
  }) => {
162
- const [status, setStatus] = createSignal<AftStatusSnapshot | null>(null);
163
- // Once a request is in flight, suppress any overlapping refresh so we
164
- // don't open a thundering herd of RPCs on rapid event bursts.
165
- let inflight = false;
180
+ const [status, setStatus] = createSignal<ScopedSidebarStatus | null>(null);
181
+ let inflight: {
182
+ controller: AbortController;
183
+ generation: number;
184
+ directory: string;
185
+ sessionID: string;
186
+ } | null = null;
187
+ let generation = 0;
166
188
  let debounceTimer: ReturnType<typeof setTimeout> | undefined;
167
189
  let pollTimer: ReturnType<typeof setInterval> | undefined;
168
190
 
191
+ const currentDirectory = () => props.api.state.path.directory ?? "";
192
+ const requestRender = () => {
193
+ try {
194
+ props.api.renderer.requestRender();
195
+ } catch {
196
+ // renderer may not be available during teardown; safe to ignore
197
+ }
198
+ };
199
+ const abortInflight = () => {
200
+ if (!inflight) return;
201
+ inflight.controller.abort();
202
+ inflight = null;
203
+ };
204
+ const clearStatusForContext = (directory: string, sessionID: string) => {
205
+ const current = status();
206
+ if (!current) return;
207
+ if (current.directory === directory && current.sessionID === sessionID) return;
208
+ setStatus(null);
209
+ requestRender();
210
+ };
211
+
169
212
  const refresh = async () => {
170
213
  const sid = props.sessionID();
171
- if (!sid) return;
172
- if (inflight) return;
173
- const directory = props.api.state.path.directory ?? "";
174
- if (!directory) return;
214
+ const directory = currentDirectory();
215
+ if (!sid || !directory) {
216
+ generation++;
217
+ abortInflight();
218
+ if (status()) {
219
+ setStatus(null);
220
+ requestRender();
221
+ }
222
+ return;
223
+ }
224
+
225
+ clearStatusForContext(directory, sid);
226
+
227
+ if (inflight) {
228
+ if (inflight.directory === directory && inflight.sessionID === sid) return;
229
+ generation++;
230
+ abortInflight();
231
+ }
232
+
233
+ const requestGeneration = ++generation;
234
+ const controller = new AbortController();
235
+ inflight = { controller, generation: requestGeneration, directory, sessionID: sid };
175
236
 
176
- inflight = true;
177
237
  try {
178
238
  const client = getClient(directory);
179
- const response = await client.call("status", { sessionID: sid });
239
+ const response = await client.call(
240
+ "status",
241
+ { sessionID: sid },
242
+ { signal: controller.signal },
243
+ );
244
+ if (controller.signal.aborted || requestGeneration !== generation) return;
245
+ if (currentDirectory() !== directory || props.sessionID() !== sid) return;
180
246
  if (response && (response as Record<string, unknown>).success !== false) {
181
247
  const snapshot = coerceAftStatus(response as Record<string, unknown>);
182
- setStatus(snapshot);
183
- try {
184
- props.api.renderer.requestRender();
185
- } catch {
186
- // renderer may not be available during teardown; safe to ignore
187
- }
248
+ setStatus({ directory, sessionID: sid, snapshot });
249
+ requestRender();
188
250
  }
189
251
  } catch {
252
+ if (controller.signal.aborted || requestGeneration !== generation) return;
190
253
  // RPC server may not be ready yet, or the bridge may be respawning
191
- // after a binary swap leave the previous snapshot visible rather
192
- // than blanking the sidebar.
254
+ // after a binary swap. Keep the previous snapshot only when it belongs
255
+ // to the current project/session; mismatched snapshots were cleared above.
193
256
  } finally {
194
- inflight = false;
257
+ if (inflight?.generation === requestGeneration) inflight = null;
195
258
  }
196
259
  };
197
260
 
@@ -204,6 +267,8 @@ const SidebarContent = (props: {
204
267
  };
205
268
 
206
269
  onCleanup(() => {
270
+ generation++;
271
+ abortInflight();
207
272
  if (debounceTimer) clearTimeout(debounceTimer);
208
273
  if (pollTimer) clearInterval(pollTimer);
209
274
  });
@@ -250,6 +315,8 @@ const SidebarContent = (props: {
250
315
  // best effort
251
316
  }
252
317
  }
318
+ generation++;
319
+ abortInflight();
253
320
  if (pollTimer) {
254
321
  clearInterval(pollTimer);
255
322
  pollTimer = undefined;
@@ -260,7 +327,7 @@ const SidebarContent = (props: {
260
327
  ),
261
328
  );
262
329
 
263
- const s = createMemo(() => status());
330
+ const s = () => scopedSidebarSnapshot(status(), currentDirectory(), props.sessionID());
264
331
 
265
332
  // Lazy-bridge: while AFT has no live bridge yet, the RPC server returns a
266
333
  // synthetic snapshot with `cache_role === "not_initialized"`. In that state