@cortexkit/aft-opencode 0.62.0 → 0.63.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bash-wait-detach.d.ts +1 -0
- package/dist/bash-wait-detach.d.ts.map +1 -1
- package/dist/bg-notifications.d.ts +30 -11
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/bridge-bootstrap.d.ts +1 -1
- package/dist/bridge-bootstrap.d.ts.map +1 -1
- package/dist/config-live-reload.d.ts +14 -2
- package/dist/config-live-reload.d.ts.map +1 -1
- package/dist/config.d.ts +37 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/entry/server.js +4015 -1382
- package/dist/hooks/auto-update-checker/index.d.ts +5 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3294 -1243
- package/dist/logger.d.ts +6 -10
- package/dist/logger.d.ts.map +1 -1
- package/dist/lsp-auto-install.d.ts.map +1 -1
- package/dist/lsp.d.ts +1 -0
- package/dist/lsp.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +2 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/session-cleanup.d.ts +6 -0
- package/dist/shared/session-cleanup.d.ts.map +1 -0
- package/dist/shared/session-directory.d.ts +24 -0
- package/dist/shared/session-directory.d.ts.map +1 -1
- package/dist/shared/status.d.ts +12 -1
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/shared/subagent-detect.d.ts +24 -8
- package/dist/shared/subagent-detect.d.ts.map +1 -1
- package/dist/shared/wake-admission.d.ts +68 -0
- package/dist/shared/wake-admission.d.ts.map +1 -0
- package/dist/shutdown-hooks.d.ts +2 -0
- package/dist/shutdown-hooks.d.ts.map +1 -1
- package/dist/subc-tool-schemas.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts +5 -1
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash_watch.d.ts +4 -0
- package/dist/tools/bash_watch.d.ts.map +1 -1
- package/dist/tools/definitions/v2.d.ts +2 -1
- package/dist/tools/definitions/v2.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/inspect.d.ts.map +1 -1
- package/dist/tools/metadata.d.ts +7 -0
- package/dist/tools/metadata.d.ts.map +1 -0
- package/dist/tools/outside-permissions.d.ts +35 -0
- package/dist/tools/outside-permissions.d.ts.map +1 -0
- package/dist/tools/permissions.d.ts +9 -11
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/tui/notification-socket.d.ts.map +1 -1
- package/dist/tui/preferences.d.ts +11 -0
- package/dist/tui/preferences.d.ts.map +1 -1
- package/dist/v2-session-lifecycle.d.ts +7 -0
- package/dist/v2-session-lifecycle.d.ts.map +1 -1
- package/dist/v2-tool-hooks.d.ts.map +1 -1
- package/dist/wakes/runtime-consumer.d.ts +9 -3
- package/dist/wakes/runtime-consumer.d.ts.map +1 -1
- package/dist/wakes/session-delivery.d.ts +22 -4
- package/dist/wakes/session-delivery.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/entry/server-runtime.mjs +53 -18
- package/src/logger.ts +12 -5
- package/src/shared/rpc-client.ts +35 -28
- package/src/shared/rpc-notifications.ts +5 -3
- package/src/shared/rpc-server.ts +65 -7
- package/src/shared/session-cleanup.ts +13 -0
- package/src/shared/session-directory.ts +59 -3
- package/src/shared/status.ts +32 -2
- package/src/shared/subagent-detect.ts +53 -25
- package/src/shared/wake-admission.ts +340 -0
- package/src/tui/notification-socket.ts +30 -4
- package/src/tui/preferences.ts +66 -29
- package/src/tui/sidebar-view.tsx +1 -0
- package/src/tui/v2.tsx +49 -1
- package/src/tui-compiled/notification-socket.ts +30 -4
- package/src/tui-compiled/preferences.ts +66 -29
- package/src/tui-compiled/sidebar-view.tsx +2 -1
- package/src/tui-compiled/v2.tsx +54 -1
|
@@ -49,6 +49,14 @@ const WEB_SOCKET_OPEN = 1;
|
|
|
49
49
|
const clients = new Map<string, AftRpcClient>();
|
|
50
50
|
const statusInvalidationListeners = new Set<(event: StatusInvalidation) => void>();
|
|
51
51
|
const lastHandledIdBySession = new Map<string, number>();
|
|
52
|
+
const socketEndpoints = new WeakMap<WebSocketLike, AftRpcEndpoint>();
|
|
53
|
+
const CURSOR_CACHE_LIMIT = 200;
|
|
54
|
+
|
|
55
|
+
function cursorKey(ws: WebSocketLike, sessionId: string): string | null {
|
|
56
|
+
const endpoint = socketEndpoints.get(ws);
|
|
57
|
+
// The authentication token distinguishes server instances, even when a new instance reuses the port.
|
|
58
|
+
return endpoint?.token ? `${endpoint.token}\0${sessionId}` : null;
|
|
59
|
+
}
|
|
52
60
|
|
|
53
61
|
function defaultWebSocketCtor(): WebSocketFactory | null {
|
|
54
62
|
const ctor = (globalThis as typeof globalThis & { WebSocket?: WebSocketFactory }).WebSocket;
|
|
@@ -128,6 +136,7 @@ export function stopAftTuiSocket(): void {
|
|
|
128
136
|
closeCurrentSocket(false);
|
|
129
137
|
for (const client of clients.values()) client.reset();
|
|
130
138
|
clients.clear();
|
|
139
|
+
lastHandledIdBySession.clear();
|
|
131
140
|
reconnectAttempt = 0;
|
|
132
141
|
}
|
|
133
142
|
|
|
@@ -250,6 +259,7 @@ async function connect(scope: SocketScope, connectGeneration: number): Promise<v
|
|
|
250
259
|
}
|
|
251
260
|
|
|
252
261
|
socket = ws;
|
|
262
|
+
socketEndpoints.set(ws, endpoint);
|
|
253
263
|
socketScope = scope;
|
|
254
264
|
helloedScope = null;
|
|
255
265
|
|
|
@@ -335,13 +345,21 @@ async function sendHelloWithFreshToken(
|
|
|
335
345
|
) {
|
|
336
346
|
return;
|
|
337
347
|
}
|
|
338
|
-
|
|
348
|
+
const connected = socketEndpoints.get(ws);
|
|
349
|
+
if (!endpoint || endpoint.port !== connected?.port || endpoint.token !== connected.token) {
|
|
350
|
+
generation += 1;
|
|
351
|
+
closeCurrentSocket(false);
|
|
352
|
+
void connect(scope, generation);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
sendHello(ws, scope, connected.token);
|
|
339
356
|
}
|
|
340
357
|
|
|
341
358
|
function sendHello(ws: WebSocketLike, scope: SocketScope, token: string | null): void {
|
|
342
359
|
helloedScope = scope;
|
|
343
360
|
if (socket === ws) socketScope = scope;
|
|
344
|
-
const
|
|
361
|
+
const key = cursorKey(ws, scope.sessionId);
|
|
362
|
+
const lastReceivedId = key ? (lastHandledIdBySession.get(key) ?? 0) : 0;
|
|
345
363
|
ws.send(
|
|
346
364
|
JSON.stringify({
|
|
347
365
|
type: "hello",
|
|
@@ -400,8 +418,16 @@ async function handleSocketMessage(
|
|
|
400
418
|
) {
|
|
401
419
|
return;
|
|
402
420
|
}
|
|
403
|
-
|
|
404
|
-
|
|
421
|
+
const key = cursorKey(ws, active);
|
|
422
|
+
if (consumed && notification.id > (key ? (lastHandledIdBySession.get(key) ?? 0) : 0)) {
|
|
423
|
+
if (key) {
|
|
424
|
+
lastHandledIdBySession.delete(key);
|
|
425
|
+
lastHandledIdBySession.set(key, notification.id);
|
|
426
|
+
if (lastHandledIdBySession.size > CURSOR_CACHE_LIMIT) {
|
|
427
|
+
const oldest = lastHandledIdBySession.keys().next().value;
|
|
428
|
+
if (oldest !== undefined) lastHandledIdBySession.delete(oldest);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
405
431
|
try {
|
|
406
432
|
ws.send(JSON.stringify({ type: "ack", lastReceivedId: notification.id }));
|
|
407
433
|
} catch {
|
|
@@ -39,6 +39,12 @@ export interface AftTuiPrefs {
|
|
|
39
39
|
startCollapsed: boolean;
|
|
40
40
|
rememberCollapsed: boolean;
|
|
41
41
|
collapsed: boolean | null;
|
|
42
|
+
/**
|
|
43
|
+
* Whether OpenCode 2 shows AFT's health line (`AFT E0 W0 | D0 U0 C0 | T0`)
|
|
44
|
+
* in the prompt footer. Off hides it entirely; the sidebar and the status
|
|
45
|
+
* dialog are unaffected.
|
|
46
|
+
*/
|
|
47
|
+
footer: boolean;
|
|
42
48
|
header: {
|
|
43
49
|
label: string;
|
|
44
50
|
showVersion: boolean;
|
|
@@ -57,6 +63,7 @@ export const DEFAULT_PREFS: AftTuiPrefs = {
|
|
|
57
63
|
startCollapsed: false,
|
|
58
64
|
rememberCollapsed: true,
|
|
59
65
|
collapsed: null,
|
|
66
|
+
footer: true,
|
|
60
67
|
header: { label: "AFT", showVersion: true },
|
|
61
68
|
sections: {
|
|
62
69
|
searchIndex: true,
|
|
@@ -99,6 +106,7 @@ export function resolveAftPrefs(root: Record<string, unknown>): AftTuiPrefs {
|
|
|
99
106
|
startCollapsed: bool(entry.startCollapsed, d.startCollapsed),
|
|
100
107
|
rememberCollapsed: bool(entry.rememberCollapsed, d.rememberCollapsed),
|
|
101
108
|
collapsed: typeof entry.collapsed === "boolean" ? entry.collapsed : null,
|
|
109
|
+
footer: bool(entry.footer, d.footer),
|
|
102
110
|
header: {
|
|
103
111
|
label: label(header.label, d.header.label, 20),
|
|
104
112
|
showVersion: bool(header.showVersion, d.header.showVersion),
|
|
@@ -161,18 +169,14 @@ async function writePreference(pluginKey: string, path: string[], value: JsonVal
|
|
|
161
169
|
let text: string;
|
|
162
170
|
try {
|
|
163
171
|
text = await readFile(file, "utf8");
|
|
164
|
-
} catch {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (text.trim() === "") text = TEMPLATE;
|
|
168
|
-
|
|
169
|
-
let root: Record<string, unknown>;
|
|
170
|
-
try {
|
|
171
|
-
const parsed = parse(text);
|
|
172
|
-
root = isRecord(parsed) ? (parsed as Record<string, unknown>) : {};
|
|
173
|
-
} catch {
|
|
174
|
-
root = {};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
174
|
+
text = TEMPLATE;
|
|
175
175
|
}
|
|
176
|
+
// Only a missing file can be initialized. Invalid existing bytes may belong to another plugin.
|
|
177
|
+
const parsed: unknown = parse(text);
|
|
178
|
+
if (!isRecord(parsed)) throw new Error("TUI preferences must contain an object");
|
|
179
|
+
const root = parsed;
|
|
176
180
|
|
|
177
181
|
if (!isRecord(root[pluginKey])) {
|
|
178
182
|
root[pluginKey] = {};
|
|
@@ -193,22 +197,52 @@ export function queueTuiPreferenceUpdate(
|
|
|
193
197
|
path: string[],
|
|
194
198
|
value: JsonValue,
|
|
195
199
|
): Promise<void> {
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
const update = writeChain.then(() => writePreference(pluginKey, path, value));
|
|
201
|
+
// Keep subsequent writes usable while returning the actual failure to this caller.
|
|
202
|
+
writeChain = update.catch(() => {});
|
|
203
|
+
return update;
|
|
198
204
|
}
|
|
199
205
|
|
|
200
206
|
const WATCH_DEBOUNCE_MS = 150;
|
|
201
207
|
|
|
208
|
+
/**
|
|
209
|
+
* What the watcher last saw where the preferences file should be: its text,
|
|
210
|
+
* or FILE_ABSENT once a read has confirmed the file does not exist. A symbol
|
|
211
|
+
* can never equal any file text, so deleting the file always counts as a
|
|
212
|
+
* change, even from an empty file.
|
|
213
|
+
*/
|
|
214
|
+
const FILE_ABSENT: unique symbol = Symbol("tui-preferences file absent");
|
|
215
|
+
type WatchedState = string | typeof FILE_ABSENT;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Read the file for the watcher. Returns FILE_ABSENT only for ENOENT; any
|
|
219
|
+
* other failure (a permission error, a directory at the path) returns null,
|
|
220
|
+
* meaning "unknown", so a file that is present but momentarily unreadable
|
|
221
|
+
* never makes callers fall back to defaults.
|
|
222
|
+
*/
|
|
223
|
+
async function readWatchedState(file: string): Promise<WatchedState | null> {
|
|
224
|
+
try {
|
|
225
|
+
return await readFile(file, "utf8");
|
|
226
|
+
} catch (error) {
|
|
227
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT" ? FILE_ABSENT : null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Calls `onChange` after the preferences file's content changes, including
|
|
233
|
+
* when the file is deleted (callers then read defaults) and when it is
|
|
234
|
+
* created again.
|
|
235
|
+
*/
|
|
202
236
|
export function watchTuiPreferences(onChange: () => void): () => void {
|
|
203
237
|
const file = getTuiPreferencesFile();
|
|
204
238
|
const name = basename(file);
|
|
205
239
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
240
|
+
// null until a read has succeeded or confirmed the file is missing.
|
|
241
|
+
let lastSeen: WatchedState | null = null;
|
|
242
|
+
let stopped = false;
|
|
243
|
+
void readWatchedState(file).then((state) => {
|
|
244
|
+
if (lastSeen === null && state !== null) lastSeen = state;
|
|
245
|
+
});
|
|
212
246
|
try {
|
|
213
247
|
const watcher = watch(dirname(file), (_event, filename) => {
|
|
214
248
|
const isOurs =
|
|
@@ -217,20 +251,23 @@ export function watchTuiPreferences(onChange: () => void): () => void {
|
|
|
217
251
|
if (timer) clearTimeout(timer);
|
|
218
252
|
timer = setTimeout(() => {
|
|
219
253
|
timer = null;
|
|
220
|
-
void
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
onChange();
|
|
227
|
-
});
|
|
254
|
+
void readWatchedState(file).then((state) => {
|
|
255
|
+
if (stopped || state === null) return;
|
|
256
|
+
if (state === lastSeen) return;
|
|
257
|
+
lastSeen = state;
|
|
258
|
+
onChange();
|
|
259
|
+
});
|
|
228
260
|
}, WATCH_DEBOUNCE_MS);
|
|
229
261
|
});
|
|
230
|
-
|
|
262
|
+
const stop = () => {
|
|
263
|
+
if (stopped) return;
|
|
264
|
+
stopped = true;
|
|
231
265
|
if (timer) clearTimeout(timer);
|
|
266
|
+
timer = null;
|
|
232
267
|
watcher.close();
|
|
233
268
|
};
|
|
269
|
+
watcher.on("error", stop);
|
|
270
|
+
return stop;
|
|
234
271
|
} catch {
|
|
235
272
|
return () => {};
|
|
236
273
|
}
|
|
@@ -238,6 +275,6 @@ export function watchTuiPreferences(onChange: () => void): () => void {
|
|
|
238
275
|
|
|
239
276
|
export function persistCollapsedIfEnabled(prefs: AftTuiPrefs, collapsed: boolean): void {
|
|
240
277
|
if (prefs.rememberCollapsed) {
|
|
241
|
-
void queueTuiPreferenceUpdate(PLUGIN_KEY, ["collapsed"], collapsed);
|
|
278
|
+
void queueTuiPreferenceUpdate(PLUGIN_KEY, ["collapsed"], collapsed).catch(() => {});
|
|
242
279
|
}
|
|
243
280
|
}
|
|
@@ -399,7 +399,8 @@ export const AftSidebarPanel = props => {
|
|
|
399
399
|
stage: s()?.semantic_index?.stage,
|
|
400
400
|
error: s()?.semantic_index?.error,
|
|
401
401
|
reason: s()?.semantic_index?.reason,
|
|
402
|
-
backend_url: s()?.semantic_index?.backend_url
|
|
402
|
+
backend_url: s()?.semantic_index?.backend_url,
|
|
403
|
+
pending_reason: s()?.semantic_index?.pending_reason
|
|
403
404
|
})
|
|
404
405
|
};
|
|
405
406
|
};
|
package/src/tui-compiled/v2.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
2
|
+
import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
2
3
|
import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
3
4
|
import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
4
5
|
/** @jsxImportSource @opentui/solid */
|
|
@@ -8,6 +9,7 @@ import { createEffect, createSignal, onCleanup } from "opentui:runtime-module:so
|
|
|
8
9
|
import { version as packageVersion } from "../../package.json";
|
|
9
10
|
import { coerceAftStatus, formatStatusDialogMessage } from "../shared/status";
|
|
10
11
|
import { AftRpc } from "./aft-rpc";
|
|
12
|
+
import { readTuiPreferencesFile, resolveAftPrefs, watchTuiPreferences } from "./preferences";
|
|
11
13
|
import { AftSidebarPanel, createSidebarPreferences } from "./sidebar-view";
|
|
12
14
|
import { formatAftStatusSegment } from "./v2-status";
|
|
13
15
|
|
|
@@ -163,7 +165,38 @@ function useAftStatus(context, rpc, sessionID) {
|
|
|
163
165
|
});
|
|
164
166
|
return status;
|
|
165
167
|
}
|
|
166
|
-
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The `footer` switch from the `aft` entry of `tui-preferences.jsonc`, kept
|
|
171
|
+
* current while the file is edited, as the sidebar's preferences are.
|
|
172
|
+
*
|
|
173
|
+
* It reads as hidden until the file has been read once. Starting from the
|
|
174
|
+
* default (shown) instead would flash the line on every prompt mount for
|
|
175
|
+
* someone who turned it off; the read takes milliseconds, and a missing or
|
|
176
|
+
* unreadable file resolves to the default, so the line still appears for
|
|
177
|
+
* everyone else.
|
|
178
|
+
*/
|
|
179
|
+
function createFooterVisibility() {
|
|
180
|
+
const [visible, setVisible] = createSignal(false);
|
|
181
|
+
// Reads can overlap when the file changes twice in quick succession; only
|
|
182
|
+
// the most recently started read may set the value.
|
|
183
|
+
let generation = 0;
|
|
184
|
+
const reload = async () => {
|
|
185
|
+
const current = ++generation;
|
|
186
|
+
const prefs = resolveAftPrefs(await readTuiPreferencesFile());
|
|
187
|
+
if (current === generation) setVisible(prefs.footer);
|
|
188
|
+
};
|
|
189
|
+
void reload();
|
|
190
|
+
const unwatch = watchTuiPreferences(() => {
|
|
191
|
+
void reload();
|
|
192
|
+
});
|
|
193
|
+
onCleanup(() => {
|
|
194
|
+
generation += 1;
|
|
195
|
+
unwatch();
|
|
196
|
+
});
|
|
197
|
+
return visible;
|
|
198
|
+
}
|
|
199
|
+
function FooterSegment(props) {
|
|
167
200
|
const status = useAftStatus(props.context, props.rpc, () => props.sessionID);
|
|
168
201
|
return (() => {
|
|
169
202
|
var _el$ = _$createElement("text");
|
|
@@ -171,6 +204,26 @@ function FooterStatus(props) {
|
|
|
171
204
|
return _el$;
|
|
172
205
|
})();
|
|
173
206
|
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The prompt footer claim. With `footer: false` it renders no element at all,
|
|
210
|
+
* so the line takes no space, and the status subscription behind it is torn
|
|
211
|
+
* down until the switch is turned back on.
|
|
212
|
+
*/
|
|
213
|
+
function FooterStatus(props) {
|
|
214
|
+
const visible = createFooterVisibility();
|
|
215
|
+
return _$memo(() => _$memo(() => !!visible())() ? _$createComponent(FooterSegment, {
|
|
216
|
+
get context() {
|
|
217
|
+
return props.context;
|
|
218
|
+
},
|
|
219
|
+
get rpc() {
|
|
220
|
+
return props.rpc;
|
|
221
|
+
},
|
|
222
|
+
get sessionID() {
|
|
223
|
+
return props.sessionID;
|
|
224
|
+
}
|
|
225
|
+
}) : null);
|
|
226
|
+
}
|
|
174
227
|
function SidebarStatus(props) {
|
|
175
228
|
const status = useAftStatus(props.context, props.rpc, () => props.sessionID);
|
|
176
229
|
const preferences = createSidebarPreferences();
|