@cortexkit/aft-opencode 0.23.0 → 0.24.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/dist/bg-notifications.d.ts +0 -1
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -10
- package/dist/shared/status.d.ts +9 -0
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/tui.js +385 -119
- package/package.json +7 -7
- package/src/shared/status.ts +15 -1
- package/src/tui/index.tsx +349 -68
|
@@ -39,7 +39,6 @@ interface DrainContext {
|
|
|
39
39
|
ctx: PluginContext;
|
|
40
40
|
directory: string;
|
|
41
41
|
sessionID: string;
|
|
42
|
-
isActive?: () => boolean;
|
|
43
42
|
}
|
|
44
43
|
export declare function trackBgTask(sessionID: string | undefined, taskId: string): void;
|
|
45
44
|
export declare function ingestBgCompletions(sessionID: string | undefined, completions: unknown): BgCompletion[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bg-notifications.d.ts","sourceRoot":"","sources":["../src/bg-notifications.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,KAAK,cAAc,GAAG;IACpB,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,kBAAkB,EAAE,YAAY,EAAE,CAAC;IACnC,kBAAkB,EAAE,qBAAqB,EAAE,CAAC;IAC5C,aAAa,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IACrC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kBAAkB,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,YAAY,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAa,CAAC;AAGtE,eAAO,MAAM,4BAA4B,QAAiB,CAAC;AAQ3D,UAAU,YAAY;IACpB,GAAG,EAAE,aAAa,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"bg-notifications.d.ts","sourceRoot":"","sources":["../src/bg-notifications.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,KAAK,cAAc,GAAG;IACpB,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,kBAAkB,EAAE,YAAY,EAAE,CAAC;IACnC,kBAAkB,EAAE,qBAAqB,EAAE,CAAC;IAC5C,aAAa,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IACrC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kBAAkB,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,YAAY,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAa,CAAC;AAGtE,eAAO,MAAM,4BAA4B,QAAiB,CAAC;AAQ3D,UAAU,YAAY;IACpB,GAAG,EAAE,aAAa,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AASD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAgB/E;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,OAAO,GACnB,YAAY,EAAE,CAoBhB;AAED,wBAAsB,wBAAwB,CAC5C,YAAY,EAAE,YAAY,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,EAChD,UAAU,EAAE,OAAO,GAClB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,YAAY,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,EAChD,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACtC,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,YAAY,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAC/C,OAAO,CAAC,IAAI,CAAC,CAEf;AAiED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CASjF;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,SAAS,qBAAqB,EAAE,GAAG,MAAM,CAQ7F;AAWD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAcnE;AAED,wBAAgB,kCAAkC,IAAI,IAAI,CAKzD"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA8JlD;;;;;;;;;;;;;;;;;;GAkBG;AAQH,QAAA,MAAM,MAAM,EAAE,MAA6D,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA8JlD;;;;;;;;;;;;;;;;;;GAkBG;AAQH,QAAA,MAAM,MAAM,EAAE,MAA6D,CAAC;AAovB5E,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7953,6 +7953,8 @@ class BinaryBridge {
|
|
|
7953
7953
|
onConfigureWarnings;
|
|
7954
7954
|
onBashCompletion;
|
|
7955
7955
|
onBashLongRunning;
|
|
7956
|
+
cachedStatus = null;
|
|
7957
|
+
statusListeners = new Set;
|
|
7956
7958
|
configureWarningClients = new Map;
|
|
7957
7959
|
restartResetTimer = null;
|
|
7958
7960
|
errorPrefix;
|
|
@@ -8001,6 +8003,21 @@ class BinaryBridge {
|
|
|
8001
8003
|
hasPendingRequests() {
|
|
8002
8004
|
return this.pending.size > 0;
|
|
8003
8005
|
}
|
|
8006
|
+
getCachedStatus() {
|
|
8007
|
+
return this.cachedStatus;
|
|
8008
|
+
}
|
|
8009
|
+
subscribeStatus(listener) {
|
|
8010
|
+
this.statusListeners.add(listener);
|
|
8011
|
+
if (this.cachedStatus !== null) {
|
|
8012
|
+
this.deliverStatusSnapshot(listener, this.cachedStatus);
|
|
8013
|
+
}
|
|
8014
|
+
return () => {
|
|
8015
|
+
this.statusListeners.delete(listener);
|
|
8016
|
+
};
|
|
8017
|
+
}
|
|
8018
|
+
cacheStatusSnapshot(snapshot) {
|
|
8019
|
+
this.cachedStatus = snapshot;
|
|
8020
|
+
}
|
|
8004
8021
|
async send(command, params = {}, options) {
|
|
8005
8022
|
if (this._shuttingDown) {
|
|
8006
8023
|
throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
|
|
@@ -8137,6 +8154,23 @@ class BinaryBridge {
|
|
|
8137
8154
|
}
|
|
8138
8155
|
}
|
|
8139
8156
|
}
|
|
8157
|
+
handleStatusChangedFrame(frame) {
|
|
8158
|
+
const snapshot = frame.snapshot;
|
|
8159
|
+
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
|
|
8160
|
+
return;
|
|
8161
|
+
this.cachedStatus = snapshot;
|
|
8162
|
+
log("Received status_changed push frame; cached AFT status snapshot");
|
|
8163
|
+
for (const listener of this.statusListeners) {
|
|
8164
|
+
this.deliverStatusSnapshot(listener, this.cachedStatus);
|
|
8165
|
+
}
|
|
8166
|
+
}
|
|
8167
|
+
deliverStatusSnapshot(listener, snapshot) {
|
|
8168
|
+
try {
|
|
8169
|
+
listener(snapshot);
|
|
8170
|
+
} catch (err) {
|
|
8171
|
+
warn(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
8172
|
+
}
|
|
8173
|
+
}
|
|
8140
8174
|
async shutdown() {
|
|
8141
8175
|
this._shuttingDown = true;
|
|
8142
8176
|
this.clearRestartResetTimer();
|
|
@@ -8341,6 +8375,10 @@ class BinaryBridge {
|
|
|
8341
8375
|
});
|
|
8342
8376
|
continue;
|
|
8343
8377
|
}
|
|
8378
|
+
if (response.type === "status_changed") {
|
|
8379
|
+
this.handleStatusChangedFrame(response);
|
|
8380
|
+
continue;
|
|
8381
|
+
}
|
|
8344
8382
|
const id = response.id;
|
|
8345
8383
|
if (id && this.pending.has(id)) {
|
|
8346
8384
|
const entry = this.pending.get(id);
|
|
@@ -10055,8 +10093,6 @@ async function handleIdleBgCompletions(drainContext) {
|
|
|
10055
10093
|
}
|
|
10056
10094
|
async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
10057
10095
|
const state = stateFor(drainContext.sessionID);
|
|
10058
|
-
if (drainContext.isActive?.())
|
|
10059
|
-
return;
|
|
10060
10096
|
if (!skipDrain && state.outstandingTaskIds.size > 0) {
|
|
10061
10097
|
await drainCompletions(drainContext);
|
|
10062
10098
|
}
|
|
@@ -27096,6 +27132,8 @@ function coerceAftStatus(response) {
|
|
|
27096
27132
|
return {
|
|
27097
27133
|
version: readString(response.version, "unknown"),
|
|
27098
27134
|
project_root: readNullableString(response.project_root),
|
|
27135
|
+
canonical_root: readNullableString(response.canonical_root),
|
|
27136
|
+
cache_role: readString(response.cache_role, "not_initialized"),
|
|
27099
27137
|
features: {
|
|
27100
27138
|
format_on_edit: readBoolean(features.format_on_edit),
|
|
27101
27139
|
validate_on_edit: readString(features.validate_on_edit, "off"),
|
|
@@ -27145,6 +27183,8 @@ function formatStatusMarkdown(status) {
|
|
|
27145
27183
|
"",
|
|
27146
27184
|
`- **Version:** \`${status.version}\``,
|
|
27147
27185
|
`- **Project root:** \`${status.project_root ?? "(not configured)"}\``,
|
|
27186
|
+
`- **Canonical root:** \`${status.canonical_root ?? "(not configured)"}\``,
|
|
27187
|
+
`- **Cache role:** \`${status.cache_role}\``,
|
|
27148
27188
|
"",
|
|
27149
27189
|
"### Enabled features",
|
|
27150
27190
|
`- \`format_on_edit\`: ${formatFlag(status.features.format_on_edit)}`,
|
|
@@ -30810,24 +30850,22 @@ ${lines}
|
|
|
30810
30850
|
pluginVersion: PLUGIN_VERSION
|
|
30811
30851
|
});
|
|
30812
30852
|
},
|
|
30813
|
-
onBashCompletion: (completion
|
|
30853
|
+
onBashCompletion: (completion) => {
|
|
30814
30854
|
const sessionDir = getSessionDirectoryCached(completion.session_id) ?? input.directory;
|
|
30815
30855
|
handlePushedBgCompletion({
|
|
30816
30856
|
ctx,
|
|
30817
30857
|
directory: sessionDir,
|
|
30818
30858
|
sessionID: completion.session_id,
|
|
30819
|
-
client: input.client
|
|
30820
|
-
isActive: () => bridge.hasPendingRequests()
|
|
30859
|
+
client: input.client
|
|
30821
30860
|
}, completion);
|
|
30822
30861
|
},
|
|
30823
|
-
onBashLongRunning: (reminder
|
|
30862
|
+
onBashLongRunning: (reminder) => {
|
|
30824
30863
|
const sessionDir = getSessionDirectoryCached(reminder.session_id) ?? input.directory;
|
|
30825
30864
|
handlePushedBgLongRunning({
|
|
30826
30865
|
ctx,
|
|
30827
30866
|
directory: sessionDir,
|
|
30828
30867
|
sessionID: reminder.session_id,
|
|
30829
|
-
client: input.client
|
|
30830
|
-
isActive: () => bridge.hasPendingRequests()
|
|
30868
|
+
client: input.client
|
|
30831
30869
|
}, reminder);
|
|
30832
30870
|
}
|
|
30833
30871
|
};
|
|
@@ -30898,7 +30936,15 @@ ${lines}
|
|
|
30898
30936
|
message: "AFT bridge has not been initialized for this project yet. Status will populate after the first tool call."
|
|
30899
30937
|
};
|
|
30900
30938
|
}
|
|
30901
|
-
|
|
30939
|
+
const cached2 = bridge.getCachedStatus();
|
|
30940
|
+
if (cached2 !== null) {
|
|
30941
|
+
return { success: true, ...cached2 };
|
|
30942
|
+
}
|
|
30943
|
+
const response = await bridge.send("status", { session_id: sessionID });
|
|
30944
|
+
if (response.success !== false) {
|
|
30945
|
+
bridge.cacheStatusSnapshot(response);
|
|
30946
|
+
}
|
|
30947
|
+
return response;
|
|
30902
30948
|
});
|
|
30903
30949
|
const storageDir = configOverrides.storage_dir;
|
|
30904
30950
|
rpcServer.handle("get-announcement", async () => {
|
|
@@ -31061,7 +31107,11 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
31061
31107
|
}
|
|
31062
31108
|
const sessionDir = await getSessionDirectory(input.client, commandInput.sessionID, input.directory) ?? input.directory;
|
|
31063
31109
|
const bridge = ctx.pool.getActiveBridgeForRoot(sessionDir) ?? ctx.pool.getBridge(sessionDir);
|
|
31064
|
-
const
|
|
31110
|
+
const cached2 = bridge.getCachedStatus();
|
|
31111
|
+
const response = cached2 ? { success: true, ...cached2 } : await bridge.send("status", { session_id: commandInput.sessionID });
|
|
31112
|
+
if (!cached2 && response.success !== false) {
|
|
31113
|
+
bridge.cacheStatusSnapshot(response);
|
|
31114
|
+
}
|
|
31065
31115
|
if (response.success === false) {
|
|
31066
31116
|
throw new Error(response.message || "status failed");
|
|
31067
31117
|
}
|
package/dist/shared/status.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface AftStatusSnapshot {
|
|
2
2
|
version: string;
|
|
3
3
|
project_root: string | null;
|
|
4
|
+
canonical_root: string | null;
|
|
5
|
+
cache_role: string;
|
|
4
6
|
features: {
|
|
5
7
|
format_on_edit: boolean;
|
|
6
8
|
validate_on_edit: string;
|
|
@@ -47,6 +49,13 @@ export interface AftStatusSnapshot {
|
|
|
47
49
|
}
|
|
48
50
|
export declare function formatBytes(bytes: number): string;
|
|
49
51
|
export declare function coerceAftStatus(response: Record<string, unknown>): AftStatusSnapshot;
|
|
52
|
+
/**
|
|
53
|
+
* Plain-text status renderer used by the Desktop `sendIgnoredMessage` path,
|
|
54
|
+
* which can only show a plain string. The TUI dialog uses a custom JSX
|
|
55
|
+
* component in `tui/index.tsx` (see `StatusDialog`) so it can render with
|
|
56
|
+
* themed colors, proper flex columns, and right-aligned values instead of
|
|
57
|
+
* monospace padding.
|
|
58
|
+
*/
|
|
50
59
|
export declare function formatStatusDialogMessage(status: AftStatusSnapshot): string;
|
|
51
60
|
export declare function formatStatusMarkdown(status: AftStatusSnapshot): string;
|
|
52
61
|
//# sourceMappingURL=status.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE;QACR,cAAc,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,wBAAwB,EAAE,OAAO,CAAC;QAClC,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,cAAc,EAAE;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAkCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE;QACR,cAAc,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,wBAAwB,EAAE,OAAO,CAAC;QAClC,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,cAAc,EAAE;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAkCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CA6DpF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAuE3E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA0EtE"}
|
package/dist/tui.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// src/tui/index.tsx
|
|
3
|
+
import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
|
|
2
4
|
// package.json
|
|
3
5
|
var package_default = {
|
|
4
6
|
name: "@cortexkit/aft-opencode",
|
|
5
|
-
version: "0.
|
|
7
|
+
version: "0.24.0",
|
|
6
8
|
type: "module",
|
|
7
9
|
description: "OpenCode plugin for Agent File Tools (AFT) \u2014 tree-sitter and lsp powered code analysis",
|
|
8
10
|
main: "dist/index.js",
|
|
@@ -30,7 +32,7 @@ var package_default = {
|
|
|
30
32
|
},
|
|
31
33
|
dependencies: {
|
|
32
34
|
"@clack/prompts": "^1.2.0",
|
|
33
|
-
"@cortexkit/aft-bridge": "0.
|
|
35
|
+
"@cortexkit/aft-bridge": "0.24.0",
|
|
34
36
|
"@opencode-ai/plugin": "^1.14.39",
|
|
35
37
|
"@opencode-ai/sdk": "^1.14.39",
|
|
36
38
|
"comment-json": "^4.6.2",
|
|
@@ -38,11 +40,11 @@ var package_default = {
|
|
|
38
40
|
zod: "^4.1.8"
|
|
39
41
|
},
|
|
40
42
|
optionalDependencies: {
|
|
41
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
42
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
43
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
44
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
45
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
43
|
+
"@cortexkit/aft-darwin-arm64": "0.24.0",
|
|
44
|
+
"@cortexkit/aft-darwin-x64": "0.24.0",
|
|
45
|
+
"@cortexkit/aft-linux-arm64": "0.24.0",
|
|
46
|
+
"@cortexkit/aft-linux-x64": "0.24.0",
|
|
47
|
+
"@cortexkit/aft-win32-x64": "0.24.0"
|
|
46
48
|
},
|
|
47
49
|
devDependencies: {
|
|
48
50
|
"@types/node": "^22.0.0",
|
|
@@ -303,12 +305,6 @@ function readNumber(value, fallback = 0) {
|
|
|
303
305
|
function readOptionalNumber(value) {
|
|
304
306
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
305
307
|
}
|
|
306
|
-
function formatFlag(enabled) {
|
|
307
|
-
return enabled ? "enabled" : "disabled";
|
|
308
|
-
}
|
|
309
|
-
function formatCount(value) {
|
|
310
|
-
return value == null ? "\u2014" : value.toLocaleString("en-US");
|
|
311
|
-
}
|
|
312
308
|
function formatBytes(bytes) {
|
|
313
309
|
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
314
310
|
return "0 B";
|
|
@@ -336,6 +332,8 @@ function coerceAftStatus(response) {
|
|
|
336
332
|
return {
|
|
337
333
|
version: readString(response.version, "unknown"),
|
|
338
334
|
project_root: readNullableString(response.project_root),
|
|
335
|
+
canonical_root: readNullableString(response.canonical_root),
|
|
336
|
+
cache_role: readString(response.cache_role, "not_initialized"),
|
|
339
337
|
features: {
|
|
340
338
|
format_on_edit: readBoolean(features.format_on_edit),
|
|
341
339
|
validate_on_edit: readString(features.validate_on_edit, "off"),
|
|
@@ -379,54 +377,6 @@ function coerceAftStatus(response) {
|
|
|
379
377
|
}
|
|
380
378
|
};
|
|
381
379
|
}
|
|
382
|
-
function formatStatusDialogMessage(status) {
|
|
383
|
-
const lines = [
|
|
384
|
-
`AFT version: ${status.version}`,
|
|
385
|
-
`Project root: ${status.project_root ?? "(not configured)"}`,
|
|
386
|
-
"",
|
|
387
|
-
"Enabled features",
|
|
388
|
-
`- format_on_edit: ${formatFlag(status.features.format_on_edit)}`,
|
|
389
|
-
`- search_index: ${formatFlag(status.features.search_index)}`,
|
|
390
|
-
`- semantic_search: ${formatFlag(status.features.semantic_search)}`,
|
|
391
|
-
"",
|
|
392
|
-
"Search index",
|
|
393
|
-
`- status: ${status.search_index.status}`,
|
|
394
|
-
`- files: ${formatCount(status.search_index.files)}`,
|
|
395
|
-
`- trigrams: ${formatCount(status.search_index.trigrams)}`,
|
|
396
|
-
"",
|
|
397
|
-
"Semantic index",
|
|
398
|
-
`- status: ${status.semantic_index.status}`,
|
|
399
|
-
`- entries: ${formatCount(status.semantic_index.entries)}`
|
|
400
|
-
];
|
|
401
|
-
if (status.semantic_index.backend) {
|
|
402
|
-
lines.push(`- backend: ${status.semantic_index.backend}`);
|
|
403
|
-
}
|
|
404
|
-
if (status.semantic_index.model) {
|
|
405
|
-
lines.push(`- model: ${status.semantic_index.model}`);
|
|
406
|
-
}
|
|
407
|
-
if (status.semantic_index.dimension != null) {
|
|
408
|
-
lines.push(`- dimension: ${formatCount(status.semantic_index.dimension)}`);
|
|
409
|
-
}
|
|
410
|
-
lines.push("", "Disk usage", `- trigram index: ${formatBytes(status.disk.trigram_disk_bytes)}`, `- semantic index: ${formatBytes(status.disk.semantic_disk_bytes)}`, "", "Runtime", `- LSP servers: ${formatCount(status.lsp_servers)}`, `- symbol cache: ${formatCount(status.symbol_cache.local_entries)} local / ${formatCount(status.symbol_cache.warm_entries)} warm`);
|
|
411
|
-
if (status.storage_dir ?? status.disk.storage_dir) {
|
|
412
|
-
lines.push(`- storage dir: ${status.storage_dir ?? status.disk.storage_dir}`);
|
|
413
|
-
}
|
|
414
|
-
lines.push("", "Current session", `- id: ${status.session.id}`, `- tracked files: ${formatCount(status.session.tracked_files)}`, `- checkpoints: ${formatCount(status.session.checkpoints)}`, `- project checkpoints (all sessions): ${formatCount(status.checkpoints_total)}`);
|
|
415
|
-
if (status.semantic_index.stage) {
|
|
416
|
-
lines.push("", "Semantic stage", status.semantic_index.stage);
|
|
417
|
-
}
|
|
418
|
-
if (status.semantic_index.files != null) {
|
|
419
|
-
lines.push(`- semantic files: ${formatCount(status.semantic_index.files)}`);
|
|
420
|
-
}
|
|
421
|
-
if (status.semantic_index.entries_done != null || status.semantic_index.entries_total != null) {
|
|
422
|
-
lines.push(`- semantic progress: ${formatCount(status.semantic_index.entries_done ?? null)} / ${formatCount(status.semantic_index.entries_total ?? null)}`);
|
|
423
|
-
}
|
|
424
|
-
if (status.semantic_index.error) {
|
|
425
|
-
lines.push("", "Semantic error", status.semantic_index.error);
|
|
426
|
-
}
|
|
427
|
-
return lines.join(`
|
|
428
|
-
`);
|
|
429
|
-
}
|
|
430
380
|
|
|
431
381
|
// src/tui/sidebar.tsx
|
|
432
382
|
import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js";
|
|
@@ -445,7 +395,7 @@ function formatBytes2(n) {
|
|
|
445
395
|
return `${Math.round(n / 1024)} KB`;
|
|
446
396
|
return `${n} B`;
|
|
447
397
|
}
|
|
448
|
-
function
|
|
398
|
+
function formatCount(n) {
|
|
449
399
|
if (n == null || !Number.isFinite(n))
|
|
450
400
|
return "\u2014";
|
|
451
401
|
if (n >= 1e6)
|
|
@@ -658,7 +608,7 @@ var SidebarContent = (props) => {
|
|
|
658
608
|
(s()?.search_index?.files ?? null) != null && /* @__PURE__ */ jsxDEV(StatRow, {
|
|
659
609
|
theme: props.theme,
|
|
660
610
|
label: "Files",
|
|
661
|
-
value:
|
|
611
|
+
value: formatCount(s().search_index.files),
|
|
662
612
|
tone: "muted"
|
|
663
613
|
}, undefined, false, undefined, this),
|
|
664
614
|
/* @__PURE__ */ jsxDEV(StatRow, {
|
|
@@ -680,13 +630,13 @@ var SidebarContent = (props) => {
|
|
|
680
630
|
s()?.semantic_index?.status === "loading" && s()?.semantic_index?.entries_total != null && s().semantic_index.entries_total > 0 && /* @__PURE__ */ jsxDEV(StatRow, {
|
|
681
631
|
theme: props.theme,
|
|
682
632
|
label: "Progress",
|
|
683
|
-
value: `${
|
|
633
|
+
value: `${formatCount(s().semantic_index.entries_done)} / ${formatCount(s().semantic_index.entries_total)}`,
|
|
684
634
|
tone: "warn"
|
|
685
635
|
}, undefined, false, undefined, this),
|
|
686
636
|
(s()?.semantic_index?.entries ?? null) != null && /* @__PURE__ */ jsxDEV(StatRow, {
|
|
687
637
|
theme: props.theme,
|
|
688
638
|
label: "Entries",
|
|
689
|
-
value:
|
|
639
|
+
value: formatCount(s().semantic_index.entries),
|
|
690
640
|
tone: "muted"
|
|
691
641
|
}, undefined, false, undefined, this),
|
|
692
642
|
/* @__PURE__ */ jsxDEV(StatRow, {
|
|
@@ -750,6 +700,361 @@ function getSessionId(api) {
|
|
|
750
700
|
} catch {}
|
|
751
701
|
return null;
|
|
752
702
|
}
|
|
703
|
+
var POLL_INTERVAL_MS2 = 1500;
|
|
704
|
+
function formatCountShort(value) {
|
|
705
|
+
if (value == null || !Number.isFinite(value))
|
|
706
|
+
return "\u2014";
|
|
707
|
+
if (value >= 1e6)
|
|
708
|
+
return `${(value / 1e6).toFixed(1)}M`;
|
|
709
|
+
if (value >= 1000)
|
|
710
|
+
return `${Math.round(value / 1000)}K`;
|
|
711
|
+
return String(value);
|
|
712
|
+
}
|
|
713
|
+
function statusTone(status) {
|
|
714
|
+
switch (status) {
|
|
715
|
+
case "ready":
|
|
716
|
+
return "ok";
|
|
717
|
+
case "loading":
|
|
718
|
+
case "building":
|
|
719
|
+
return "warn";
|
|
720
|
+
case "failed":
|
|
721
|
+
case "error":
|
|
722
|
+
return "err";
|
|
723
|
+
default:
|
|
724
|
+
return "muted";
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function pickToneColor(theme, tone) {
|
|
728
|
+
switch (tone) {
|
|
729
|
+
case "ok":
|
|
730
|
+
return theme.success ?? theme.accent;
|
|
731
|
+
case "warn":
|
|
732
|
+
return theme.warning;
|
|
733
|
+
case "err":
|
|
734
|
+
return theme.error;
|
|
735
|
+
case "muted":
|
|
736
|
+
return theme.textMuted;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
var R = (props) => {
|
|
740
|
+
const fg = createMemo2(() => {
|
|
741
|
+
if (!props.tone)
|
|
742
|
+
return props.theme.text;
|
|
743
|
+
if (props.tone === "accent")
|
|
744
|
+
return props.theme.accent;
|
|
745
|
+
return pickToneColor(props.theme, props.tone);
|
|
746
|
+
});
|
|
747
|
+
return /* @__PURE__ */ jsxDEV2("box", {
|
|
748
|
+
flexDirection: "row",
|
|
749
|
+
width: "100%",
|
|
750
|
+
justifyContent: "space-between",
|
|
751
|
+
children: [
|
|
752
|
+
/* @__PURE__ */ jsxDEV2("text", {
|
|
753
|
+
fg: props.theme.textMuted,
|
|
754
|
+
children: props.label
|
|
755
|
+
}, undefined, false, undefined, this),
|
|
756
|
+
/* @__PURE__ */ jsxDEV2("text", {
|
|
757
|
+
fg: fg(),
|
|
758
|
+
children: props.value
|
|
759
|
+
}, undefined, false, undefined, this)
|
|
760
|
+
]
|
|
761
|
+
}, undefined, true, undefined, this);
|
|
762
|
+
};
|
|
763
|
+
var StatusDialog = (props) => {
|
|
764
|
+
const theme = createMemo2(() => props.api.theme.current);
|
|
765
|
+
const t = () => theme();
|
|
766
|
+
const [status, setStatus] = createSignal2(props.initial);
|
|
767
|
+
const [error, setError] = createSignal2(props.initialError);
|
|
768
|
+
const timer = setInterval(async () => {
|
|
769
|
+
try {
|
|
770
|
+
const response = await props.client.call("status", { sessionID: props.sessionID });
|
|
771
|
+
if (response.success !== false) {
|
|
772
|
+
setStatus(coerceAftStatus(response));
|
|
773
|
+
setError(null);
|
|
774
|
+
}
|
|
775
|
+
} catch {}
|
|
776
|
+
}, POLL_INTERVAL_MS2);
|
|
777
|
+
onCleanup2(() => clearInterval(timer));
|
|
778
|
+
const cacheRoleTone = (role) => role === "main" ? "accent" : role === "worktree" ? "warn" : "muted";
|
|
779
|
+
return /* @__PURE__ */ jsxDEV2("box", {
|
|
780
|
+
flexDirection: "column",
|
|
781
|
+
width: "100%",
|
|
782
|
+
paddingLeft: 2,
|
|
783
|
+
paddingRight: 2,
|
|
784
|
+
paddingTop: 1,
|
|
785
|
+
paddingBottom: 1,
|
|
786
|
+
children: [
|
|
787
|
+
/* @__PURE__ */ jsxDEV2("box", {
|
|
788
|
+
justifyContent: "center",
|
|
789
|
+
width: "100%",
|
|
790
|
+
marginBottom: 1,
|
|
791
|
+
flexDirection: "row",
|
|
792
|
+
gap: 2,
|
|
793
|
+
children: [
|
|
794
|
+
/* @__PURE__ */ jsxDEV2("text", {
|
|
795
|
+
fg: t().accent,
|
|
796
|
+
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
797
|
+
children: "\u26A1 AFT Status"
|
|
798
|
+
}, undefined, false, undefined, this)
|
|
799
|
+
}, undefined, false, undefined, this),
|
|
800
|
+
/* @__PURE__ */ jsxDEV2("text", {
|
|
801
|
+
fg: t().textMuted,
|
|
802
|
+
children: [
|
|
803
|
+
"v",
|
|
804
|
+
status()?.version ?? package_default.version
|
|
805
|
+
]
|
|
806
|
+
}, undefined, true, undefined, this)
|
|
807
|
+
]
|
|
808
|
+
}, undefined, true, undefined, this),
|
|
809
|
+
error() ? /* @__PURE__ */ jsxDEV2("box", {
|
|
810
|
+
width: "100%",
|
|
811
|
+
marginBottom: 1,
|
|
812
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
813
|
+
fg: t().warning,
|
|
814
|
+
children: error()
|
|
815
|
+
}, undefined, false, undefined, this)
|
|
816
|
+
}, undefined, false, undefined, this) : null,
|
|
817
|
+
status() ? /* @__PURE__ */ jsxDEV2("box", {
|
|
818
|
+
flexDirection: "column",
|
|
819
|
+
width: "100%",
|
|
820
|
+
marginBottom: 1,
|
|
821
|
+
children: [
|
|
822
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
823
|
+
theme: t(),
|
|
824
|
+
label: "Project root",
|
|
825
|
+
value: status().project_root ?? "(not configured)"
|
|
826
|
+
}, undefined, false, undefined, this),
|
|
827
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
828
|
+
theme: t(),
|
|
829
|
+
label: "Canonical root",
|
|
830
|
+
value: status().canonical_root ?? "(not configured)"
|
|
831
|
+
}, undefined, false, undefined, this),
|
|
832
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
833
|
+
theme: t(),
|
|
834
|
+
label: "Cache role",
|
|
835
|
+
value: status().cache_role,
|
|
836
|
+
tone: cacheRoleTone(status().cache_role)
|
|
837
|
+
}, undefined, false, undefined, this)
|
|
838
|
+
]
|
|
839
|
+
}, undefined, true, undefined, this) : null,
|
|
840
|
+
status() ? /* @__PURE__ */ jsxDEV2("box", {
|
|
841
|
+
flexDirection: "row",
|
|
842
|
+
width: "100%",
|
|
843
|
+
gap: 4,
|
|
844
|
+
children: [
|
|
845
|
+
/* @__PURE__ */ jsxDEV2("box", {
|
|
846
|
+
flexDirection: "column",
|
|
847
|
+
flexGrow: 1,
|
|
848
|
+
flexBasis: 0,
|
|
849
|
+
children: [
|
|
850
|
+
/* @__PURE__ */ jsxDEV2("text", {
|
|
851
|
+
fg: t().text,
|
|
852
|
+
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
853
|
+
children: "Search index"
|
|
854
|
+
}, undefined, false, undefined, this)
|
|
855
|
+
}, undefined, false, undefined, this),
|
|
856
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
857
|
+
theme: t(),
|
|
858
|
+
label: "Status",
|
|
859
|
+
value: status().search_index.status,
|
|
860
|
+
tone: statusTone(status().search_index.status)
|
|
861
|
+
}, undefined, false, undefined, this),
|
|
862
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
863
|
+
theme: t(),
|
|
864
|
+
label: "Files",
|
|
865
|
+
value: formatCountShort(status().search_index.files)
|
|
866
|
+
}, undefined, false, undefined, this),
|
|
867
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
868
|
+
theme: t(),
|
|
869
|
+
label: "Trigrams",
|
|
870
|
+
value: formatCountShort(status().search_index.trigrams)
|
|
871
|
+
}, undefined, false, undefined, this),
|
|
872
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
873
|
+
theme: t(),
|
|
874
|
+
label: "Disk",
|
|
875
|
+
value: formatBytes(status().disk.trigram_disk_bytes),
|
|
876
|
+
tone: "muted"
|
|
877
|
+
}, undefined, false, undefined, this),
|
|
878
|
+
/* @__PURE__ */ jsxDEV2("box", {
|
|
879
|
+
marginTop: 1,
|
|
880
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
881
|
+
fg: t().text,
|
|
882
|
+
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
883
|
+
children: "Runtime"
|
|
884
|
+
}, undefined, false, undefined, this)
|
|
885
|
+
}, undefined, false, undefined, this)
|
|
886
|
+
}, undefined, false, undefined, this),
|
|
887
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
888
|
+
theme: t(),
|
|
889
|
+
label: "LSP servers",
|
|
890
|
+
value: String(status().lsp_servers)
|
|
891
|
+
}, undefined, false, undefined, this),
|
|
892
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
893
|
+
theme: t(),
|
|
894
|
+
label: "Symbol cache (local)",
|
|
895
|
+
value: formatCountShort(status().symbol_cache.local_entries)
|
|
896
|
+
}, undefined, false, undefined, this),
|
|
897
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
898
|
+
theme: t(),
|
|
899
|
+
label: "Symbol cache (warm)",
|
|
900
|
+
value: formatCountShort(status().symbol_cache.warm_entries),
|
|
901
|
+
tone: "muted"
|
|
902
|
+
}, undefined, false, undefined, this),
|
|
903
|
+
/* @__PURE__ */ jsxDEV2("box", {
|
|
904
|
+
marginTop: 1,
|
|
905
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
906
|
+
fg: t().text,
|
|
907
|
+
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
908
|
+
children: "Features"
|
|
909
|
+
}, undefined, false, undefined, this)
|
|
910
|
+
}, undefined, false, undefined, this)
|
|
911
|
+
}, undefined, false, undefined, this),
|
|
912
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
913
|
+
theme: t(),
|
|
914
|
+
label: "format_on_edit",
|
|
915
|
+
value: status().features.format_on_edit ? "on" : "off",
|
|
916
|
+
tone: status().features.format_on_edit ? "ok" : "muted"
|
|
917
|
+
}, undefined, false, undefined, this),
|
|
918
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
919
|
+
theme: t(),
|
|
920
|
+
label: "search_index",
|
|
921
|
+
value: status().features.search_index ? "on" : "off",
|
|
922
|
+
tone: status().features.search_index ? "ok" : "muted"
|
|
923
|
+
}, undefined, false, undefined, this),
|
|
924
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
925
|
+
theme: t(),
|
|
926
|
+
label: "semantic_search",
|
|
927
|
+
value: status().features.semantic_search ? "on" : "off",
|
|
928
|
+
tone: status().features.semantic_search ? "ok" : "muted"
|
|
929
|
+
}, undefined, false, undefined, this)
|
|
930
|
+
]
|
|
931
|
+
}, undefined, true, undefined, this),
|
|
932
|
+
/* @__PURE__ */ jsxDEV2("box", {
|
|
933
|
+
flexDirection: "column",
|
|
934
|
+
flexGrow: 1,
|
|
935
|
+
flexBasis: 0,
|
|
936
|
+
children: [
|
|
937
|
+
/* @__PURE__ */ jsxDEV2("text", {
|
|
938
|
+
fg: t().text,
|
|
939
|
+
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
940
|
+
children: "Semantic index"
|
|
941
|
+
}, undefined, false, undefined, this)
|
|
942
|
+
}, undefined, false, undefined, this),
|
|
943
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
944
|
+
theme: t(),
|
|
945
|
+
label: "Status",
|
|
946
|
+
value: status().semantic_index.status,
|
|
947
|
+
tone: statusTone(status().semantic_index.status)
|
|
948
|
+
}, undefined, false, undefined, this),
|
|
949
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
950
|
+
theme: t(),
|
|
951
|
+
label: "Entries",
|
|
952
|
+
value: formatCountShort(status().semantic_index.entries)
|
|
953
|
+
}, undefined, false, undefined, this),
|
|
954
|
+
status().semantic_index.backend ? /* @__PURE__ */ jsxDEV2(R, {
|
|
955
|
+
theme: t(),
|
|
956
|
+
label: "Backend",
|
|
957
|
+
value: status().semantic_index.backend,
|
|
958
|
+
tone: "muted"
|
|
959
|
+
}, undefined, false, undefined, this) : null,
|
|
960
|
+
status().semantic_index.model ? /* @__PURE__ */ jsxDEV2(R, {
|
|
961
|
+
theme: t(),
|
|
962
|
+
label: "Model",
|
|
963
|
+
value: status().semantic_index.model,
|
|
964
|
+
tone: "muted"
|
|
965
|
+
}, undefined, false, undefined, this) : null,
|
|
966
|
+
status().semantic_index.dimension != null ? /* @__PURE__ */ jsxDEV2(R, {
|
|
967
|
+
theme: t(),
|
|
968
|
+
label: "Dimension",
|
|
969
|
+
value: String(status().semantic_index.dimension),
|
|
970
|
+
tone: "muted"
|
|
971
|
+
}, undefined, false, undefined, this) : null,
|
|
972
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
973
|
+
theme: t(),
|
|
974
|
+
label: "Disk",
|
|
975
|
+
value: formatBytes(status().disk.semantic_disk_bytes),
|
|
976
|
+
tone: "muted"
|
|
977
|
+
}, undefined, false, undefined, this),
|
|
978
|
+
/* @__PURE__ */ jsxDEV2("box", {
|
|
979
|
+
marginTop: 1,
|
|
980
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
981
|
+
fg: t().text,
|
|
982
|
+
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
983
|
+
children: "Current session"
|
|
984
|
+
}, undefined, false, undefined, this)
|
|
985
|
+
}, undefined, false, undefined, this)
|
|
986
|
+
}, undefined, false, undefined, this),
|
|
987
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
988
|
+
theme: t(),
|
|
989
|
+
label: "Tracked files",
|
|
990
|
+
value: String(status().session.tracked_files)
|
|
991
|
+
}, undefined, false, undefined, this),
|
|
992
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
993
|
+
theme: t(),
|
|
994
|
+
label: "Checkpoints",
|
|
995
|
+
value: String(status().session.checkpoints)
|
|
996
|
+
}, undefined, false, undefined, this),
|
|
997
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
998
|
+
theme: t(),
|
|
999
|
+
label: "All-session checkpoints",
|
|
1000
|
+
value: String(status().checkpoints_total),
|
|
1001
|
+
tone: "muted"
|
|
1002
|
+
}, undefined, false, undefined, this)
|
|
1003
|
+
]
|
|
1004
|
+
}, undefined, true, undefined, this)
|
|
1005
|
+
]
|
|
1006
|
+
}, undefined, true, undefined, this) : null,
|
|
1007
|
+
status()?.semantic_index.stage ? /* @__PURE__ */ jsxDEV2("box", {
|
|
1008
|
+
flexDirection: "column",
|
|
1009
|
+
width: "100%",
|
|
1010
|
+
marginTop: 1,
|
|
1011
|
+
children: [
|
|
1012
|
+
/* @__PURE__ */ jsxDEV2("text", {
|
|
1013
|
+
fg: t().text,
|
|
1014
|
+
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
1015
|
+
children: "Semantic build progress"
|
|
1016
|
+
}, undefined, false, undefined, this)
|
|
1017
|
+
}, undefined, false, undefined, this),
|
|
1018
|
+
/* @__PURE__ */ jsxDEV2(R, {
|
|
1019
|
+
theme: t(),
|
|
1020
|
+
label: "Stage",
|
|
1021
|
+
value: status().semantic_index.stage
|
|
1022
|
+
}, undefined, false, undefined, this),
|
|
1023
|
+
status().semantic_index.files != null ? /* @__PURE__ */ jsxDEV2(R, {
|
|
1024
|
+
theme: t(),
|
|
1025
|
+
label: "Files seen",
|
|
1026
|
+
value: formatCountShort(status().semantic_index.files)
|
|
1027
|
+
}, undefined, false, undefined, this) : null,
|
|
1028
|
+
status().semantic_index.entries_done != null || status().semantic_index.entries_total != null ? /* @__PURE__ */ jsxDEV2(R, {
|
|
1029
|
+
theme: t(),
|
|
1030
|
+
label: "Progress",
|
|
1031
|
+
value: `${formatCountShort(status().semantic_index.entries_done ?? null)} / ${formatCountShort(status().semantic_index.entries_total ?? null)}`
|
|
1032
|
+
}, undefined, false, undefined, this) : null
|
|
1033
|
+
]
|
|
1034
|
+
}, undefined, true, undefined, this) : null,
|
|
1035
|
+
status()?.semantic_index.error ? /* @__PURE__ */ jsxDEV2("box", {
|
|
1036
|
+
marginTop: 1,
|
|
1037
|
+
width: "100%",
|
|
1038
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
1039
|
+
fg: t().error,
|
|
1040
|
+
children: [
|
|
1041
|
+
"\u26A0 ",
|
|
1042
|
+
status().semantic_index.error
|
|
1043
|
+
]
|
|
1044
|
+
}, undefined, true, undefined, this)
|
|
1045
|
+
}, undefined, false, undefined, this) : null,
|
|
1046
|
+
/* @__PURE__ */ jsxDEV2("box", {
|
|
1047
|
+
marginTop: 1,
|
|
1048
|
+
justifyContent: "flex-end",
|
|
1049
|
+
width: "100%",
|
|
1050
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
1051
|
+
fg: t().textMuted,
|
|
1052
|
+
children: "Enter or Esc to close"
|
|
1053
|
+
}, undefined, false, undefined, this)
|
|
1054
|
+
}, undefined, false, undefined, this)
|
|
1055
|
+
]
|
|
1056
|
+
}, undefined, true, undefined, this);
|
|
1057
|
+
};
|
|
753
1058
|
async function showStatusDialog(api) {
|
|
754
1059
|
const sessionID = getSessionId(api);
|
|
755
1060
|
if (!sessionID) {
|
|
@@ -762,68 +1067,29 @@ async function showStatusDialog(api) {
|
|
|
762
1067
|
return;
|
|
763
1068
|
}
|
|
764
1069
|
const client = getRpcClient(directory);
|
|
765
|
-
let
|
|
1070
|
+
let initial = null;
|
|
1071
|
+
let initialError = null;
|
|
766
1072
|
try {
|
|
767
1073
|
const response = await client.call("status", { sessionID });
|
|
768
1074
|
if (response.success !== false) {
|
|
769
|
-
|
|
770
|
-
|
|
1075
|
+
initial = coerceAftStatus(response);
|
|
1076
|
+
} else {
|
|
1077
|
+
initialError = "AFT bridge returned an error response.";
|
|
771
1078
|
}
|
|
772
1079
|
} catch {
|
|
773
|
-
|
|
1080
|
+
initialError = "AFT is starting up. Status will refresh automatically...";
|
|
774
1081
|
}
|
|
775
|
-
let dialogOpen = true;
|
|
776
|
-
let pollTimer = null;
|
|
777
1082
|
api.ui.dialog.setSize("large");
|
|
778
|
-
api.ui.dialog.replace(() => {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
try {
|
|
787
|
-
const response = await client.call("status", { sessionID });
|
|
788
|
-
if (response.success !== false) {
|
|
789
|
-
const status = coerceAftStatus(response);
|
|
790
|
-
const newMessage = formatStatusDialogMessage(status);
|
|
791
|
-
if (newMessage !== currentMessage) {
|
|
792
|
-
currentMessage = newMessage;
|
|
793
|
-
api.ui.dialog.replace(() => /* @__PURE__ */ jsxDEV2(api.ui.DialogAlert, {
|
|
794
|
-
title: "AFT Status",
|
|
795
|
-
message: currentMessage,
|
|
796
|
-
onConfirm: () => {
|
|
797
|
-
dialogOpen = false;
|
|
798
|
-
if (pollTimer)
|
|
799
|
-
clearInterval(pollTimer);
|
|
800
|
-
api.ui.dialog.setSize("medium");
|
|
801
|
-
}
|
|
802
|
-
}, undefined, false, undefined, this), () => {
|
|
803
|
-
dialogOpen = false;
|
|
804
|
-
if (pollTimer)
|
|
805
|
-
clearInterval(pollTimer);
|
|
806
|
-
api.ui.dialog.setSize("medium");
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
} catch {}
|
|
811
|
-
}, 1500);
|
|
1083
|
+
api.ui.dialog.replace(() => /* @__PURE__ */ jsxDEV2(StatusDialog, {
|
|
1084
|
+
api,
|
|
1085
|
+
client,
|
|
1086
|
+
sessionID,
|
|
1087
|
+
initial,
|
|
1088
|
+
initialError,
|
|
1089
|
+
onClose: () => {
|
|
1090
|
+
api.ui.dialog.setSize("medium");
|
|
812
1091
|
}
|
|
813
|
-
|
|
814
|
-
title: "AFT Status",
|
|
815
|
-
message: currentMessage,
|
|
816
|
-
onConfirm: () => {
|
|
817
|
-
dialogOpen = false;
|
|
818
|
-
if (pollTimer)
|
|
819
|
-
clearInterval(pollTimer);
|
|
820
|
-
api.ui.dialog.setSize("medium");
|
|
821
|
-
}
|
|
822
|
-
}, undefined, false, undefined, this);
|
|
823
|
-
}, () => {
|
|
824
|
-
dialogOpen = false;
|
|
825
|
-
if (pollTimer)
|
|
826
|
-
clearInterval(pollTimer);
|
|
1092
|
+
}, undefined, false, undefined, this), () => {
|
|
827
1093
|
api.ui.dialog.setSize("medium");
|
|
828
1094
|
});
|
|
829
1095
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@clack/prompts": "^1.2.0",
|
|
31
|
-
"@cortexkit/aft-bridge": "0.
|
|
31
|
+
"@cortexkit/aft-bridge": "0.24.0",
|
|
32
32
|
"@opencode-ai/plugin": "^1.14.39",
|
|
33
33
|
"@opencode-ai/sdk": "^1.14.39",
|
|
34
34
|
"comment-json": "^4.6.2",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"zod": "^4.1.8"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
40
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
41
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
42
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
43
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
39
|
+
"@cortexkit/aft-darwin-arm64": "0.24.0",
|
|
40
|
+
"@cortexkit/aft-darwin-x64": "0.24.0",
|
|
41
|
+
"@cortexkit/aft-linux-arm64": "0.24.0",
|
|
42
|
+
"@cortexkit/aft-linux-x64": "0.24.0",
|
|
43
|
+
"@cortexkit/aft-win32-x64": "0.24.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^22.0.0",
|
package/src/shared/status.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface AftStatusSnapshot {
|
|
2
2
|
version: string;
|
|
3
3
|
project_root: string | null;
|
|
4
|
+
canonical_root: string | null;
|
|
5
|
+
cache_role: string;
|
|
4
6
|
features: {
|
|
5
7
|
format_on_edit: boolean;
|
|
6
8
|
validate_on_edit: string;
|
|
@@ -108,6 +110,8 @@ export function coerceAftStatus(response: Record<string, unknown>): AftStatusSna
|
|
|
108
110
|
return {
|
|
109
111
|
version: readString(response.version, "unknown"),
|
|
110
112
|
project_root: readNullableString(response.project_root),
|
|
113
|
+
canonical_root: readNullableString(response.canonical_root),
|
|
114
|
+
cache_role: readString(response.cache_role, "not_initialized"),
|
|
111
115
|
features: {
|
|
112
116
|
format_on_edit: readBoolean(features.format_on_edit),
|
|
113
117
|
validate_on_edit: readString(features.validate_on_edit, "off"),
|
|
@@ -154,10 +158,19 @@ export function coerceAftStatus(response: Record<string, unknown>): AftStatusSna
|
|
|
154
158
|
};
|
|
155
159
|
}
|
|
156
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Plain-text status renderer used by the Desktop `sendIgnoredMessage` path,
|
|
163
|
+
* which can only show a plain string. The TUI dialog uses a custom JSX
|
|
164
|
+
* component in `tui/index.tsx` (see `StatusDialog`) so it can render with
|
|
165
|
+
* themed colors, proper flex columns, and right-aligned values instead of
|
|
166
|
+
* monospace padding.
|
|
167
|
+
*/
|
|
157
168
|
export function formatStatusDialogMessage(status: AftStatusSnapshot): string {
|
|
158
169
|
const lines = [
|
|
159
170
|
`AFT version: ${status.version}`,
|
|
160
171
|
`Project root: ${status.project_root ?? "(not configured)"}`,
|
|
172
|
+
`Canonical root: ${status.canonical_root ?? "(not configured)"}`,
|
|
173
|
+
`Cache role: ${status.cache_role}`,
|
|
161
174
|
"",
|
|
162
175
|
"Enabled features",
|
|
163
176
|
`- format_on_edit: ${formatFlag(status.features.format_on_edit)}`,
|
|
@@ -179,7 +192,6 @@ export function formatStatusDialogMessage(status: AftStatusSnapshot): string {
|
|
|
179
192
|
if (status.semantic_index.model) {
|
|
180
193
|
lines.push(`- model: ${status.semantic_index.model}`);
|
|
181
194
|
}
|
|
182
|
-
|
|
183
195
|
if (status.semantic_index.dimension != null) {
|
|
184
196
|
lines.push(`- dimension: ${formatCount(status.semantic_index.dimension)}`);
|
|
185
197
|
}
|
|
@@ -232,6 +244,8 @@ export function formatStatusMarkdown(status: AftStatusSnapshot): string {
|
|
|
232
244
|
"",
|
|
233
245
|
`- **Version:** \`${status.version}\``,
|
|
234
246
|
`- **Project root:** \`${status.project_root ?? "(not configured)"}\``,
|
|
247
|
+
`- **Canonical root:** \`${status.canonical_root ?? "(not configured)"}\``,
|
|
248
|
+
`- **Cache role:** \`${status.cache_role}\``,
|
|
235
249
|
"",
|
|
236
250
|
"### Enabled features",
|
|
237
251
|
`- \`format_on_edit\`: ${formatFlag(status.features.format_on_edit)}`,
|
package/src/tui/index.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
|
|
4
|
-
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui";
|
|
4
|
+
import type { TuiPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui";
|
|
5
|
+
import { createMemo, createSignal, onCleanup } from "solid-js";
|
|
5
6
|
import packageJson from "../../package.json";
|
|
6
7
|
import { AftRpcClient } from "../shared/rpc-client";
|
|
7
|
-
import { coerceAftStatus,
|
|
8
|
+
import { type AftStatusSnapshot, coerceAftStatus, formatBytes } from "../shared/status";
|
|
8
9
|
import { createAftSidebarSlot } from "./sidebar";
|
|
9
10
|
|
|
10
11
|
// The TUI talks to the server plugin via AftRpcClient. The client reads the
|
|
@@ -42,6 +43,332 @@ function getSessionId(api: TuiPluginApi): string | null {
|
|
|
42
43
|
return null;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// StatusDialog — themed, two-column JSX dialog. Modeled on the magic-context
|
|
48
|
+
// /ctx-status pattern (packages/plugin/src/tui/index.tsx in that repo):
|
|
49
|
+
// custom JSX rendered via `api.ui.dialog.replace(() => <StatusDialog .../>)`
|
|
50
|
+
// instead of feeding a padded monospace string into DialogAlert. The
|
|
51
|
+
// difference matters because OpenCode renders DialogAlert text in a
|
|
52
|
+
// proportional font with no column alignment; only TUI flex primitives
|
|
53
|
+
// (<box flexDirection="row" flexBasis={0}>) actually produce visible
|
|
54
|
+
// columns. This component owns its own RPC polling so it can re-render
|
|
55
|
+
// reactively as the status snapshot changes, with no parent re-mount.
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
const POLL_INTERVAL_MS = 1500;
|
|
59
|
+
|
|
60
|
+
function formatCountShort(value: number | null | undefined): string {
|
|
61
|
+
if (value == null || !Number.isFinite(value)) return "—";
|
|
62
|
+
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
63
|
+
if (value >= 1_000) return `${Math.round(value / 1_000)}K`;
|
|
64
|
+
return String(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function statusTone(status: string): "ok" | "warn" | "err" | "muted" {
|
|
68
|
+
switch (status) {
|
|
69
|
+
case "ready":
|
|
70
|
+
return "ok";
|
|
71
|
+
case "loading":
|
|
72
|
+
case "building":
|
|
73
|
+
return "warn";
|
|
74
|
+
case "failed":
|
|
75
|
+
case "error":
|
|
76
|
+
return "err";
|
|
77
|
+
default:
|
|
78
|
+
return "muted";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function pickToneColor(theme: TuiThemeCurrent, tone: "ok" | "warn" | "err" | "muted"): string {
|
|
83
|
+
switch (tone) {
|
|
84
|
+
case "ok":
|
|
85
|
+
return (theme as any).success ?? theme.accent;
|
|
86
|
+
case "warn":
|
|
87
|
+
return theme.warning;
|
|
88
|
+
case "err":
|
|
89
|
+
return theme.error;
|
|
90
|
+
case "muted":
|
|
91
|
+
return theme.textMuted;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Label/value row. Label is left-aligned and muted; value is right-aligned
|
|
97
|
+
* and themed. flexDirection="row" + justifyContent="space-between" replaces
|
|
98
|
+
* the monospace `padEnd(40)` hack from the previous string formatter.
|
|
99
|
+
*/
|
|
100
|
+
const R = (props: {
|
|
101
|
+
theme: TuiThemeCurrent;
|
|
102
|
+
label: string;
|
|
103
|
+
value: string;
|
|
104
|
+
tone?: "ok" | "warn" | "err" | "muted" | "accent";
|
|
105
|
+
}) => {
|
|
106
|
+
const fg = createMemo(() => {
|
|
107
|
+
if (!props.tone) return props.theme.text;
|
|
108
|
+
if (props.tone === "accent") return props.theme.accent;
|
|
109
|
+
return pickToneColor(props.theme, props.tone);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<box flexDirection="row" width="100%" justifyContent="space-between">
|
|
114
|
+
<text fg={props.theme.textMuted}>{props.label}</text>
|
|
115
|
+
<text fg={fg()}>{props.value}</text>
|
|
116
|
+
</box>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
interface StatusDialogProps {
|
|
121
|
+
api: TuiPluginApi;
|
|
122
|
+
client: AftRpcClient;
|
|
123
|
+
sessionID: string;
|
|
124
|
+
initial: AftStatusSnapshot | null;
|
|
125
|
+
initialError: string | null;
|
|
126
|
+
onClose: () => void;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const StatusDialog = (props: StatusDialogProps) => {
|
|
130
|
+
const theme = createMemo(() => (props.api as any).theme.current as TuiThemeCurrent);
|
|
131
|
+
const t = () => theme();
|
|
132
|
+
|
|
133
|
+
// Reactive status signal — the dialog re-renders on every status
|
|
134
|
+
// transition without remounting. The RPC polling is local to the dialog
|
|
135
|
+
// and stops when it unmounts.
|
|
136
|
+
const [status, setStatus] = createSignal<AftStatusSnapshot | null>(props.initial);
|
|
137
|
+
const [error, setError] = createSignal<string | null>(props.initialError);
|
|
138
|
+
|
|
139
|
+
const timer = setInterval(async () => {
|
|
140
|
+
try {
|
|
141
|
+
const response = await props.client.call("status", { sessionID: props.sessionID });
|
|
142
|
+
if ((response as Record<string, unknown>).success !== false) {
|
|
143
|
+
setStatus(coerceAftStatus(response as Record<string, unknown>));
|
|
144
|
+
setError(null);
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// transient — keep showing last good snapshot
|
|
148
|
+
}
|
|
149
|
+
}, POLL_INTERVAL_MS);
|
|
150
|
+
onCleanup(() => clearInterval(timer));
|
|
151
|
+
|
|
152
|
+
// Visual cache-role badge: main is accent, worktree is warning,
|
|
153
|
+
// not_initialized is muted. Matches the sidebar convention.
|
|
154
|
+
const cacheRoleTone = (role: string): "accent" | "warn" | "muted" =>
|
|
155
|
+
role === "main" ? "accent" : role === "worktree" ? "warn" : "muted";
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<box
|
|
159
|
+
flexDirection="column"
|
|
160
|
+
width="100%"
|
|
161
|
+
paddingLeft={2}
|
|
162
|
+
paddingRight={2}
|
|
163
|
+
paddingTop={1}
|
|
164
|
+
paddingBottom={1}
|
|
165
|
+
>
|
|
166
|
+
{/* Title */}
|
|
167
|
+
<box justifyContent="center" width="100%" marginBottom={1} flexDirection="row" gap={2}>
|
|
168
|
+
<text fg={t().accent}>
|
|
169
|
+
<b>⚡ AFT Status</b>
|
|
170
|
+
</text>
|
|
171
|
+
<text fg={t().textMuted}>v{status()?.version ?? packageJson.version}</text>
|
|
172
|
+
</box>
|
|
173
|
+
|
|
174
|
+
{/* Error / not-yet-ready state */}
|
|
175
|
+
{error() ? (
|
|
176
|
+
<box width="100%" marginBottom={1}>
|
|
177
|
+
<text fg={t().warning}>{error()}</text>
|
|
178
|
+
</box>
|
|
179
|
+
) : null}
|
|
180
|
+
|
|
181
|
+
{/* Header rows — paths span full width since they can be long */}
|
|
182
|
+
{status() ? (
|
|
183
|
+
<box flexDirection="column" width="100%" marginBottom={1}>
|
|
184
|
+
<R
|
|
185
|
+
theme={t()}
|
|
186
|
+
label="Project root"
|
|
187
|
+
value={status()!.project_root ?? "(not configured)"}
|
|
188
|
+
/>
|
|
189
|
+
<R
|
|
190
|
+
theme={t()}
|
|
191
|
+
label="Canonical root"
|
|
192
|
+
value={status()!.canonical_root ?? "(not configured)"}
|
|
193
|
+
/>
|
|
194
|
+
<R
|
|
195
|
+
theme={t()}
|
|
196
|
+
label="Cache role"
|
|
197
|
+
value={status()!.cache_role}
|
|
198
|
+
tone={cacheRoleTone(status()!.cache_role)}
|
|
199
|
+
/>
|
|
200
|
+
</box>
|
|
201
|
+
) : null}
|
|
202
|
+
|
|
203
|
+
{/* 2-column body */}
|
|
204
|
+
{status() ? (
|
|
205
|
+
<box flexDirection="row" width="100%" gap={4}>
|
|
206
|
+
{/* Left column */}
|
|
207
|
+
<box flexDirection="column" flexGrow={1} flexBasis={0}>
|
|
208
|
+
<text fg={t().text}>
|
|
209
|
+
<b>Search index</b>
|
|
210
|
+
</text>
|
|
211
|
+
<R
|
|
212
|
+
theme={t()}
|
|
213
|
+
label="Status"
|
|
214
|
+
value={status()!.search_index.status}
|
|
215
|
+
tone={statusTone(status()!.search_index.status)}
|
|
216
|
+
/>
|
|
217
|
+
<R theme={t()} label="Files" value={formatCountShort(status()!.search_index.files)} />
|
|
218
|
+
<R
|
|
219
|
+
theme={t()}
|
|
220
|
+
label="Trigrams"
|
|
221
|
+
value={formatCountShort(status()!.search_index.trigrams)}
|
|
222
|
+
/>
|
|
223
|
+
<R
|
|
224
|
+
theme={t()}
|
|
225
|
+
label="Disk"
|
|
226
|
+
value={formatBytes(status()!.disk.trigram_disk_bytes)}
|
|
227
|
+
tone="muted"
|
|
228
|
+
/>
|
|
229
|
+
|
|
230
|
+
<box marginTop={1}>
|
|
231
|
+
<text fg={t().text}>
|
|
232
|
+
<b>Runtime</b>
|
|
233
|
+
</text>
|
|
234
|
+
</box>
|
|
235
|
+
<R theme={t()} label="LSP servers" value={String(status()!.lsp_servers)} />
|
|
236
|
+
<R
|
|
237
|
+
theme={t()}
|
|
238
|
+
label="Symbol cache (local)"
|
|
239
|
+
value={formatCountShort(status()!.symbol_cache.local_entries)}
|
|
240
|
+
/>
|
|
241
|
+
<R
|
|
242
|
+
theme={t()}
|
|
243
|
+
label="Symbol cache (warm)"
|
|
244
|
+
value={formatCountShort(status()!.symbol_cache.warm_entries)}
|
|
245
|
+
tone="muted"
|
|
246
|
+
/>
|
|
247
|
+
|
|
248
|
+
<box marginTop={1}>
|
|
249
|
+
<text fg={t().text}>
|
|
250
|
+
<b>Features</b>
|
|
251
|
+
</text>
|
|
252
|
+
</box>
|
|
253
|
+
<R
|
|
254
|
+
theme={t()}
|
|
255
|
+
label="format_on_edit"
|
|
256
|
+
value={status()!.features.format_on_edit ? "on" : "off"}
|
|
257
|
+
tone={status()!.features.format_on_edit ? "ok" : "muted"}
|
|
258
|
+
/>
|
|
259
|
+
<R
|
|
260
|
+
theme={t()}
|
|
261
|
+
label="search_index"
|
|
262
|
+
value={status()!.features.search_index ? "on" : "off"}
|
|
263
|
+
tone={status()!.features.search_index ? "ok" : "muted"}
|
|
264
|
+
/>
|
|
265
|
+
<R
|
|
266
|
+
theme={t()}
|
|
267
|
+
label="semantic_search"
|
|
268
|
+
value={status()!.features.semantic_search ? "on" : "off"}
|
|
269
|
+
tone={status()!.features.semantic_search ? "ok" : "muted"}
|
|
270
|
+
/>
|
|
271
|
+
</box>
|
|
272
|
+
|
|
273
|
+
{/* Right column */}
|
|
274
|
+
<box flexDirection="column" flexGrow={1} flexBasis={0}>
|
|
275
|
+
<text fg={t().text}>
|
|
276
|
+
<b>Semantic index</b>
|
|
277
|
+
</text>
|
|
278
|
+
<R
|
|
279
|
+
theme={t()}
|
|
280
|
+
label="Status"
|
|
281
|
+
value={status()!.semantic_index.status}
|
|
282
|
+
tone={statusTone(status()!.semantic_index.status)}
|
|
283
|
+
/>
|
|
284
|
+
<R
|
|
285
|
+
theme={t()}
|
|
286
|
+
label="Entries"
|
|
287
|
+
value={formatCountShort(status()!.semantic_index.entries)}
|
|
288
|
+
/>
|
|
289
|
+
{status()!.semantic_index.backend ? (
|
|
290
|
+
<R
|
|
291
|
+
theme={t()}
|
|
292
|
+
label="Backend"
|
|
293
|
+
value={status()!.semantic_index.backend!}
|
|
294
|
+
tone="muted"
|
|
295
|
+
/>
|
|
296
|
+
) : null}
|
|
297
|
+
{status()!.semantic_index.model ? (
|
|
298
|
+
<R theme={t()} label="Model" value={status()!.semantic_index.model!} tone="muted" />
|
|
299
|
+
) : null}
|
|
300
|
+
{status()!.semantic_index.dimension != null ? (
|
|
301
|
+
<R
|
|
302
|
+
theme={t()}
|
|
303
|
+
label="Dimension"
|
|
304
|
+
value={String(status()!.semantic_index.dimension)}
|
|
305
|
+
tone="muted"
|
|
306
|
+
/>
|
|
307
|
+
) : null}
|
|
308
|
+
<R
|
|
309
|
+
theme={t()}
|
|
310
|
+
label="Disk"
|
|
311
|
+
value={formatBytes(status()!.disk.semantic_disk_bytes)}
|
|
312
|
+
tone="muted"
|
|
313
|
+
/>
|
|
314
|
+
|
|
315
|
+
<box marginTop={1}>
|
|
316
|
+
<text fg={t().text}>
|
|
317
|
+
<b>Current session</b>
|
|
318
|
+
</text>
|
|
319
|
+
</box>
|
|
320
|
+
<R theme={t()} label="Tracked files" value={String(status()!.session.tracked_files)} />
|
|
321
|
+
<R theme={t()} label="Checkpoints" value={String(status()!.session.checkpoints)} />
|
|
322
|
+
<R
|
|
323
|
+
theme={t()}
|
|
324
|
+
label="All-session checkpoints"
|
|
325
|
+
value={String(status()!.checkpoints_total)}
|
|
326
|
+
tone="muted"
|
|
327
|
+
/>
|
|
328
|
+
</box>
|
|
329
|
+
</box>
|
|
330
|
+
) : null}
|
|
331
|
+
|
|
332
|
+
{/* Optional semantic build progress — full-width below the columns */}
|
|
333
|
+
{status()?.semantic_index.stage ? (
|
|
334
|
+
<box flexDirection="column" width="100%" marginTop={1}>
|
|
335
|
+
<text fg={t().text}>
|
|
336
|
+
<b>Semantic build progress</b>
|
|
337
|
+
</text>
|
|
338
|
+
<R theme={t()} label="Stage" value={status()!.semantic_index.stage!} />
|
|
339
|
+
{status()!.semantic_index.files != null ? (
|
|
340
|
+
<R
|
|
341
|
+
theme={t()}
|
|
342
|
+
label="Files seen"
|
|
343
|
+
value={formatCountShort(status()!.semantic_index.files)}
|
|
344
|
+
/>
|
|
345
|
+
) : null}
|
|
346
|
+
{status()!.semantic_index.entries_done != null ||
|
|
347
|
+
status()!.semantic_index.entries_total != null ? (
|
|
348
|
+
<R
|
|
349
|
+
theme={t()}
|
|
350
|
+
label="Progress"
|
|
351
|
+
value={`${formatCountShort(status()!.semantic_index.entries_done ?? null)} / ${formatCountShort(status()!.semantic_index.entries_total ?? null)}`}
|
|
352
|
+
/>
|
|
353
|
+
) : null}
|
|
354
|
+
</box>
|
|
355
|
+
) : null}
|
|
356
|
+
|
|
357
|
+
{/* Semantic error — full-width, themed error color */}
|
|
358
|
+
{status()?.semantic_index.error ? (
|
|
359
|
+
<box marginTop={1} width="100%">
|
|
360
|
+
<text fg={t().error}>⚠ {status()!.semantic_index.error}</text>
|
|
361
|
+
</box>
|
|
362
|
+
) : null}
|
|
363
|
+
|
|
364
|
+
{/* Footer */}
|
|
365
|
+
<box marginTop={1} justifyContent="flex-end" width="100%">
|
|
366
|
+
<text fg={t().textMuted}>Enter or Esc to close</text>
|
|
367
|
+
</box>
|
|
368
|
+
</box>
|
|
369
|
+
);
|
|
370
|
+
};
|
|
371
|
+
|
|
45
372
|
async function showStatusDialog(api: TuiPluginApi): Promise<void> {
|
|
46
373
|
const sessionID = getSessionId(api);
|
|
47
374
|
if (!sessionID) {
|
|
@@ -57,82 +384,36 @@ async function showStatusDialog(api: TuiPluginApi): Promise<void> {
|
|
|
57
384
|
|
|
58
385
|
const client = getRpcClient(directory);
|
|
59
386
|
|
|
60
|
-
//
|
|
61
|
-
|
|
387
|
+
// Prime the dialog with one initial fetch so we don't show a blank
|
|
388
|
+
// skeleton — the component then takes over polling.
|
|
389
|
+
let initial: AftStatusSnapshot | null = null;
|
|
390
|
+
let initialError: string | null = null;
|
|
62
391
|
try {
|
|
63
392
|
const response = await client.call("status", { sessionID });
|
|
64
393
|
if ((response as Record<string, unknown>).success !== false) {
|
|
65
|
-
|
|
66
|
-
|
|
394
|
+
initial = coerceAftStatus(response as Record<string, unknown>);
|
|
395
|
+
} else {
|
|
396
|
+
initialError = "AFT bridge returned an error response.";
|
|
67
397
|
}
|
|
68
398
|
} catch {
|
|
69
|
-
|
|
399
|
+
initialError = "AFT is starting up. Status will refresh automatically...";
|
|
70
400
|
}
|
|
71
401
|
|
|
72
|
-
// Track whether dialog is still open for polling cleanup
|
|
73
|
-
let dialogOpen = true;
|
|
74
|
-
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
75
|
-
|
|
76
|
-
// Show dialog with initial data
|
|
77
402
|
api.ui.dialog.setSize("large");
|
|
78
403
|
api.ui.dialog.replace(
|
|
404
|
+
() => (
|
|
405
|
+
<StatusDialog
|
|
406
|
+
api={api}
|
|
407
|
+
client={client}
|
|
408
|
+
sessionID={sessionID}
|
|
409
|
+
initial={initial}
|
|
410
|
+
initialError={initialError}
|
|
411
|
+
onClose={() => {
|
|
412
|
+
api.ui.dialog.setSize("medium");
|
|
413
|
+
}}
|
|
414
|
+
/>
|
|
415
|
+
),
|
|
79
416
|
() => {
|
|
80
|
-
// Start polling after dialog renders
|
|
81
|
-
if (!pollTimer) {
|
|
82
|
-
pollTimer = setInterval(async () => {
|
|
83
|
-
if (!dialogOpen) {
|
|
84
|
-
if (pollTimer) clearInterval(pollTimer);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
const response = await client.call("status", { sessionID });
|
|
89
|
-
if ((response as Record<string, unknown>).success !== false) {
|
|
90
|
-
const status = coerceAftStatus(response as Record<string, unknown>);
|
|
91
|
-
const newMessage = formatStatusDialogMessage(status);
|
|
92
|
-
if (newMessage !== currentMessage) {
|
|
93
|
-
currentMessage = newMessage;
|
|
94
|
-
// Re-render dialog with updated status
|
|
95
|
-
api.ui.dialog.replace(
|
|
96
|
-
() => (
|
|
97
|
-
<api.ui.DialogAlert
|
|
98
|
-
title="AFT Status"
|
|
99
|
-
message={currentMessage}
|
|
100
|
-
onConfirm={() => {
|
|
101
|
-
dialogOpen = false;
|
|
102
|
-
if (pollTimer) clearInterval(pollTimer);
|
|
103
|
-
api.ui.dialog.setSize("medium");
|
|
104
|
-
}}
|
|
105
|
-
/>
|
|
106
|
-
),
|
|
107
|
-
() => {
|
|
108
|
-
dialogOpen = false;
|
|
109
|
-
if (pollTimer) clearInterval(pollTimer);
|
|
110
|
-
api.ui.dialog.setSize("medium");
|
|
111
|
-
},
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
} catch {
|
|
116
|
-
// Polling failure is non-fatal — just skip this tick
|
|
117
|
-
}
|
|
118
|
-
}, 1500);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<api.ui.DialogAlert
|
|
123
|
-
title="AFT Status"
|
|
124
|
-
message={currentMessage}
|
|
125
|
-
onConfirm={() => {
|
|
126
|
-
dialogOpen = false;
|
|
127
|
-
if (pollTimer) clearInterval(pollTimer);
|
|
128
|
-
api.ui.dialog.setSize("medium");
|
|
129
|
-
}}
|
|
130
|
-
/>
|
|
131
|
-
);
|
|
132
|
-
},
|
|
133
|
-
() => {
|
|
134
|
-
dialogOpen = false;
|
|
135
|
-
if (pollTimer) clearInterval(pollTimer);
|
|
136
417
|
api.ui.dialog.setSize("medium");
|
|
137
418
|
},
|
|
138
419
|
);
|