@cortexkit/aft-opencode 0.26.4 → 0.27.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.
@@ -13,6 +13,7 @@
13
13
  * Messages are identified by markers and cleaned up on subsequent startups
14
14
  * when no longer relevant (Desktop only — TUI toasts are inherently transient).
15
15
  */
16
+ import type { BinaryBridge } from "@cortexkit/aft-bridge";
16
17
  type SdkMessage = {
17
18
  info?: {
18
19
  id?: string;
@@ -50,6 +51,7 @@ export interface ConfigureWarning {
50
51
  export interface ConfigureWarningOptions {
51
52
  client: unknown;
52
53
  sessionId: string;
54
+ bridge: Pick<BinaryBridge, "send">;
53
55
  storageDir: string;
54
56
  pluginVersion: string;
55
57
  projectRoot?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAyIH,KAAK,UAAU,GAAG;IAChB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACpE,CAAC;AAyBF,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,EAAE,CAAC,CAqBvB;AAqED,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,yBAAyB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1F;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAqCf;AA8FD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAmCf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C9E"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAmI1D,KAAK,UAAU,GAAG;IAChB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACpE,CAAC;AAyBF,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,EAAE,CAAC,CAqBvB;AAqED,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,yBAAyB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1F;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CA2Cf;AA4ED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C9E"}
@@ -1,3 +1,13 @@
1
+ export interface StatusCompressionAggregate {
2
+ events: number;
3
+ original_tokens: number;
4
+ compressed_tokens: number;
5
+ savings_tokens: number;
6
+ }
7
+ export interface StatusCompression {
8
+ project: StatusCompressionAggregate;
9
+ session: StatusCompressionAggregate;
10
+ }
1
11
  export interface AftStatusSnapshot {
2
12
  version: string;
3
13
  project_root: string | null;
@@ -57,6 +67,8 @@ export interface AftStatusSnapshot {
57
67
  tracked_files: number;
58
68
  checkpoints: number;
59
69
  };
70
+ /** Compression aggregate passthrough; rendering is added separately. */
71
+ compression?: StatusCompression;
60
72
  /**
61
73
  * Human-readable explanation for a synthetic snapshot (e.g.
62
74
  * `cache_role === "not_initialized"`). When the plugin returns a placeholder
@@ -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,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,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;IACF;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAkCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAkEpF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAuE3E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA0EtE"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,0BAA0B,CAAC;IACpC,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAED,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;;;;;;;OAOG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,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;IACF,wEAAwE;IACxE,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAqDD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAmEpF;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
@@ -4,7 +4,7 @@ import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "@cortexkit/aft-opencode",
7
- version: "0.26.4",
7
+ version: "0.27.0",
8
8
  type: "module",
9
9
  description: "OpenCode plugin for Agent File Tools (AFT) \u2014 tree-sitter and lsp powered code analysis",
10
10
  main: "dist/index.js",
@@ -32,7 +32,7 @@ var package_default = {
32
32
  },
33
33
  dependencies: {
34
34
  "@clack/prompts": "^1.2.0",
35
- "@cortexkit/aft-bridge": "0.26.4",
35
+ "@cortexkit/aft-bridge": "0.27.0",
36
36
  "@opencode-ai/plugin": "^1.15.5",
37
37
  "@opencode-ai/sdk": "^1.15.5",
38
38
  "comment-json": "^4.6.2",
@@ -40,11 +40,11 @@ var package_default = {
40
40
  zod: "^4.1.8"
41
41
  },
42
42
  optionalDependencies: {
43
- "@cortexkit/aft-darwin-arm64": "0.26.4",
44
- "@cortexkit/aft-darwin-x64": "0.26.4",
45
- "@cortexkit/aft-linux-arm64": "0.26.4",
46
- "@cortexkit/aft-linux-x64": "0.26.4",
47
- "@cortexkit/aft-win32-x64": "0.26.4"
43
+ "@cortexkit/aft-darwin-arm64": "0.27.0",
44
+ "@cortexkit/aft-darwin-x64": "0.27.0",
45
+ "@cortexkit/aft-linux-arm64": "0.27.0",
46
+ "@cortexkit/aft-linux-x64": "0.27.0",
47
+ "@cortexkit/aft-win32-x64": "0.27.0"
48
48
  },
49
49
  devDependencies: {
50
50
  "@types/node": "^22.0.0",
@@ -304,6 +304,24 @@ function readNumber(value, fallback = 0) {
304
304
  function readOptionalNumber(value) {
305
305
  return typeof value === "number" && Number.isFinite(value) ? value : null;
306
306
  }
307
+ function readCompressionAggregate(value) {
308
+ const aggregate = asRecord(value);
309
+ return {
310
+ events: readNumber(aggregate.events),
311
+ original_tokens: readNumber(aggregate.original_tokens),
312
+ compressed_tokens: readNumber(aggregate.compressed_tokens),
313
+ savings_tokens: readNumber(aggregate.savings_tokens)
314
+ };
315
+ }
316
+ function readCompression(value) {
317
+ if (typeof value !== "object" || value === null)
318
+ return;
319
+ const compression = asRecord(value);
320
+ return {
321
+ project: readCompressionAggregate(compression.project),
322
+ session: readCompressionAggregate(compression.session)
323
+ };
324
+ }
307
325
  function formatBytes(bytes) {
308
326
  if (!Number.isFinite(bytes) || bytes <= 0)
309
327
  return "0 B";
@@ -376,13 +394,14 @@ function coerceAftStatus(response) {
376
394
  tracked_files: readNumber(session.tracked_files),
377
395
  checkpoints: readNumber(session.checkpoints)
378
396
  },
397
+ compression: readCompression(response.compression),
379
398
  message: readString(response.message, "")
380
399
  };
381
400
  }
382
401
 
383
402
  // src/tui/sidebar.tsx
384
403
  import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js";
385
- import { jsxDEV } from "@opentui/solid/jsx-dev-runtime";
404
+ import { jsxDEV, Fragment } from "@opentui/solid/jsx-dev-runtime";
386
405
  var SINGLE_BORDER = { type: "single" };
387
406
  var REFRESH_DEBOUNCE_MS = 200;
388
407
  var POLL_INTERVAL_MS = 1500;
@@ -406,6 +425,23 @@ function formatCount(n) {
406
425
  return `${Math.round(n / 1000)}K`;
407
426
  return String(n);
408
427
  }
428
+ function appendScope(rows, label, scope) {
429
+ const savings = scope.savings_tokens;
430
+ const pct = scope.original_tokens > 0 ? Math.round(savings / scope.original_tokens * 100) : 0;
431
+ rows.push({ kind: "scope", label });
432
+ rows.push({ kind: "stat", label: "Tokens Saved", value: savings.toLocaleString("en-US") });
433
+ rows.push({ kind: "stat", label: "Compression Ratio", value: `${pct}%` });
434
+ }
435
+ function formatCompressionSidebarRows(compression) {
436
+ if (!compression || compression.project.events <= 0)
437
+ return [];
438
+ const rows = [];
439
+ if (compression.session.events > 0) {
440
+ appendScope(rows, "Session", compression.session);
441
+ }
442
+ appendScope(rows, "Project", compression.project);
443
+ return rows;
444
+ }
409
445
  function statusDisplay(status) {
410
446
  switch (status) {
411
447
  case "ready":
@@ -474,7 +510,7 @@ function getClient(directory) {
474
510
  return client;
475
511
  const home = process.env.HOME || process.env.USERPROFILE || "";
476
512
  const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
477
- const storageDir = `${dataHome}/opencode/storage/plugin/aft`;
513
+ const storageDir = `${dataHome}/cortexkit/aft`;
478
514
  client = new AftRpcClient(storageDir, directory);
479
515
  sidebarClients.set(directory, client);
480
516
  return client;
@@ -562,6 +598,7 @@ var SidebarContent = (props) => {
562
598
  const semanticStatus = () => statusDisplay(s()?.semantic_index?.status ?? "disabled");
563
599
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
564
600
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
601
+ const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
565
602
  const degradedReasonLabel = (reason) => {
566
603
  if (reason === "home_root") {
567
604
  return "project root is your home directory";
@@ -703,6 +740,26 @@ var SidebarContent = (props) => {
703
740
  value: formatBytes2(semanticBytes()),
704
741
  tone: "muted"
705
742
  }, undefined, false, undefined, this),
743
+ compressionRows().length > 0 && /* @__PURE__ */ jsxDEV(Fragment, {
744
+ children: [
745
+ /* @__PURE__ */ jsxDEV(SectionHeader, {
746
+ theme: props.theme,
747
+ title: "Compression"
748
+ }, undefined, false, undefined, this),
749
+ compressionRows().map((row) => row.kind === "scope" ? /* @__PURE__ */ jsxDEV("box", {
750
+ width: "100%",
751
+ children: /* @__PURE__ */ jsxDEV("text", {
752
+ fg: props.theme.text,
753
+ children: row.label
754
+ }, undefined, false, undefined, this)
755
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(StatRow, {
756
+ theme: props.theme,
757
+ label: row.label,
758
+ value: row.value,
759
+ tone: "muted"
760
+ }, undefined, false, undefined, this))
761
+ ]
762
+ }, undefined, true, undefined, this),
706
763
  s()?.semantic_index?.status === "failed" && s()?.semantic_index?.error && /* @__PURE__ */ jsxDEV("box", {
707
764
  marginTop: 1,
708
765
  width: "100%",
@@ -744,7 +801,7 @@ function getRpcClient(directory) {
744
801
  return client;
745
802
  const home = process.env.HOME || process.env.USERPROFILE || "";
746
803
  const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
747
- const storageDir = `${dataHome}/opencode/storage/plugin/aft`;
804
+ const storageDir = `${dataHome}/cortexkit/aft`;
748
805
  client = new AftRpcClient(storageDir, directory);
749
806
  rpcClients.set(directory, client);
750
807
  return client;
@@ -834,6 +891,7 @@ var StatusDialog = (props) => {
834
891
  }, POLL_INTERVAL_MS2);
835
892
  onCleanup2(() => clearInterval(timer));
836
893
  const cacheRoleTone = (role) => role === "main" ? "accent" : role === "worktree" ? "warn" : "muted";
894
+ const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
837
895
  return /* @__PURE__ */ jsxDEV2("box", {
838
896
  flexDirection: "column",
839
897
  width: "100%",
@@ -1110,6 +1168,31 @@ var StatusDialog = (props) => {
1110
1168
  ]
1111
1169
  }, undefined, true, undefined, this)
1112
1170
  }, undefined, false, undefined, this) : null,
1171
+ compressionAggregateRows().length > 0 ? /* @__PURE__ */ jsxDEV2("box", {
1172
+ flexDirection: "column",
1173
+ width: "100%",
1174
+ marginTop: 1,
1175
+ children: [
1176
+ /* @__PURE__ */ jsxDEV2("text", {
1177
+ fg: t().text,
1178
+ children: /* @__PURE__ */ jsxDEV2("b", {
1179
+ children: "Compression"
1180
+ }, undefined, false, undefined, this)
1181
+ }, undefined, false, undefined, this),
1182
+ compressionAggregateRows().map((row) => row.kind === "scope" ? /* @__PURE__ */ jsxDEV2("box", {
1183
+ width: "100%",
1184
+ children: /* @__PURE__ */ jsxDEV2("text", {
1185
+ fg: t().text,
1186
+ children: row.label
1187
+ }, undefined, false, undefined, this)
1188
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2(R, {
1189
+ theme: t(),
1190
+ label: row.label,
1191
+ value: row.value,
1192
+ tone: "muted"
1193
+ }, undefined, false, undefined, this))
1194
+ ]
1195
+ }, undefined, true, undefined, this) : null,
1113
1196
  /* @__PURE__ */ jsxDEV2("box", {
1114
1197
  marginTop: 1,
1115
1198
  justifyContent: "flex-end",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.26.4",
3
+ "version": "0.27.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.26.4",
31
+ "@cortexkit/aft-bridge": "0.27.0",
32
32
  "@opencode-ai/plugin": "^1.15.5",
33
33
  "@opencode-ai/sdk": "^1.15.5",
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.26.4",
40
- "@cortexkit/aft-darwin-x64": "0.26.4",
41
- "@cortexkit/aft-linux-arm64": "0.26.4",
42
- "@cortexkit/aft-linux-x64": "0.26.4",
43
- "@cortexkit/aft-win32-x64": "0.26.4"
39
+ "@cortexkit/aft-darwin-arm64": "0.27.0",
40
+ "@cortexkit/aft-darwin-x64": "0.27.0",
41
+ "@cortexkit/aft-linux-arm64": "0.27.0",
42
+ "@cortexkit/aft-linux-x64": "0.27.0",
43
+ "@cortexkit/aft-win32-x64": "0.27.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^22.0.0",
@@ -1,3 +1,15 @@
1
+ export interface StatusCompressionAggregate {
2
+ events: number;
3
+ original_tokens: number;
4
+ compressed_tokens: number;
5
+ savings_tokens: number;
6
+ }
7
+
8
+ export interface StatusCompression {
9
+ project: StatusCompressionAggregate;
10
+ session: StatusCompressionAggregate;
11
+ }
12
+
1
13
  export interface AftStatusSnapshot {
2
14
  version: string;
3
15
  project_root: string | null;
@@ -57,6 +69,8 @@ export interface AftStatusSnapshot {
57
69
  tracked_files: number;
58
70
  checkpoints: number;
59
71
  };
72
+ /** Compression aggregate passthrough; rendering is added separately. */
73
+ compression?: StatusCompression;
60
74
  /**
61
75
  * Human-readable explanation for a synthetic snapshot (e.g.
62
76
  * `cache_role === "not_initialized"`). When the plugin returns a placeholder
@@ -91,6 +105,25 @@ function readOptionalNumber(value: unknown): number | null {
91
105
  return typeof value === "number" && Number.isFinite(value) ? value : null;
92
106
  }
93
107
 
108
+ function readCompressionAggregate(value: unknown): StatusCompressionAggregate {
109
+ const aggregate = asRecord(value);
110
+ return {
111
+ events: readNumber(aggregate.events),
112
+ original_tokens: readNumber(aggregate.original_tokens),
113
+ compressed_tokens: readNumber(aggregate.compressed_tokens),
114
+ savings_tokens: readNumber(aggregate.savings_tokens),
115
+ };
116
+ }
117
+
118
+ function readCompression(value: unknown): StatusCompression | undefined {
119
+ if (typeof value !== "object" || value === null) return undefined;
120
+ const compression = asRecord(value);
121
+ return {
122
+ project: readCompressionAggregate(compression.project),
123
+ session: readCompressionAggregate(compression.session),
124
+ };
125
+ }
126
+
94
127
  function formatFlag(enabled: boolean): string {
95
128
  return enabled ? "enabled" : "disabled";
96
129
  }
@@ -178,6 +211,7 @@ export function coerceAftStatus(response: Record<string, unknown>): AftStatusSna
178
211
  tracked_files: readNumber(session.tracked_files),
179
212
  checkpoints: readNumber(session.checkpoints),
180
213
  },
214
+ compression: readCompression(response.compression),
181
215
  message: readString(response.message, ""),
182
216
  };
183
217
  }
package/src/tui/index.tsx CHANGED
@@ -3,10 +3,11 @@
3
3
 
4
4
  import type { TuiPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui";
5
5
  import { createMemo, createSignal, onCleanup } from "solid-js";
6
+
6
7
  import packageJson from "../../package.json";
7
8
  import { AftRpcClient } from "../shared/rpc-client";
8
9
  import { type AftStatusSnapshot, coerceAftStatus, formatBytes } from "../shared/status";
9
- import { createAftSidebarSlot } from "./sidebar";
10
+ import { createAftSidebarSlot, formatCompressionSidebarRows } from "./sidebar";
10
11
 
11
12
  // The TUI talks to the server plugin via AftRpcClient. The client reads the
12
13
  // JSON port file written by AftRpcServer ({ port, token }) and includes that
@@ -22,9 +23,14 @@ function getRpcClient(directory: string): AftRpcClient {
22
23
  let client = rpcClients.get(directory);
23
24
  if (client) return client;
24
25
 
26
+ // v0.27 moved AFT storage to the CortexKit root. The TUI plugin must use
27
+ // the same path as the server plugin (`resolveCortexKitStorageRoot()`),
28
+ // otherwise it polls stale legacy port files written by older versions
29
+ // and never picks up the live RPC server, leaving the sidebar/dialog
30
+ // stuck on the "AFT is starting up" placeholder forever.
25
31
  const home = process.env.HOME || process.env.USERPROFILE || "";
26
32
  const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
27
- const storageDir = `${dataHome}/opencode/storage/plugin/aft`;
33
+ const storageDir = `${dataHome}/cortexkit/aft`;
28
34
 
29
35
  client = new AftRpcClient(storageDir, directory);
30
36
  rpcClients.set(directory, client);
@@ -153,6 +159,11 @@ const StatusDialog = (props: StatusDialogProps) => {
153
159
  // not_initialized is muted. Matches the sidebar convention.
154
160
  const cacheRoleTone = (role: string): "accent" | "warn" | "muted" =>
155
161
  role === "main" ? "accent" : role === "worktree" ? "warn" : "muted";
162
+ // Reuse the sidebar's label/value formatter so the dialog and sidebar
163
+ // render identical text (e.g. "Session" / "-174,489 tokens, 59% reduction").
164
+ // The earlier `formatCompressionDialogRows` returned padded strings that
165
+ // looked offset against neighboring sections.
166
+ const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
156
167
 
157
168
  return (
158
169
  <box
@@ -374,6 +385,28 @@ const StatusDialog = (props: StatusDialogProps) => {
374
385
  </box>
375
386
  ) : null}
376
387
 
388
+ {/* Compression aggregates — tabular layout matching Search/Semantic
389
+ Index. Each scope ("Session", "Project") renders as a subheader
390
+ followed by two <R> rows (Tokens Saved, Compression Ratio) so the
391
+ numbers stay right-aligned under the value column instead of
392
+ crowding the label. */}
393
+ {compressionAggregateRows().length > 0 ? (
394
+ <box flexDirection="column" width="100%" marginTop={1}>
395
+ <text fg={t().text}>
396
+ <b>Compression</b>
397
+ </text>
398
+ {compressionAggregateRows().map((row) =>
399
+ row.kind === "scope" ? (
400
+ <box width="100%">
401
+ <text fg={t().text}>{row.label}</text>
402
+ </box>
403
+ ) : (
404
+ <R theme={t()} label={row.label} value={row.value} tone="muted" />
405
+ ),
406
+ )}
407
+ </box>
408
+ ) : null}
409
+
377
410
  {/* Footer */}
378
411
  <box marginTop={1} justifyContent="flex-end" width="100%">
379
412
  <text fg={t().textMuted}>Enter or Esc to close</text>
@@ -11,7 +11,7 @@ import type { TuiPluginApi, TuiSlotPlugin, TuiThemeCurrent } from "@opencode-ai/
11
11
  import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js";
12
12
 
13
13
  import { AftRpcClient } from "../shared/rpc-client";
14
- import { type AftStatusSnapshot, coerceAftStatus } from "../shared/status";
14
+ import { type AftStatusSnapshot, coerceAftStatus, type StatusCompression } from "../shared/status";
15
15
 
16
16
  const SINGLE_BORDER = { type: "single" } as any;
17
17
  const REFRESH_DEBOUNCE_MS = 200;
@@ -35,6 +35,46 @@ function formatCount(n: number | null | undefined): string {
35
35
  return String(n);
36
36
  }
37
37
 
38
+ /** Tagged rows for the Compression section. Each scope (Session / Project)
39
+ * emits a "scope" header followed by two "stat" rows — Tokens Saved and
40
+ * Compression Ratio — so the renderer can use the same StatRow layout as
41
+ * Search Index / Semantic Index above. Pi's monospace overlay and the
42
+ * OpenCode TUI dialog/sidebar all consume this same shape. */
43
+ export type CompressionRow =
44
+ | { kind: "scope"; label: string }
45
+ | { kind: "stat"; label: string; value: string };
46
+
47
+ function appendScope(
48
+ rows: CompressionRow[],
49
+ label: string,
50
+ scope: {
51
+ events: number;
52
+ original_tokens: number;
53
+ compressed_tokens: number;
54
+ savings_tokens: number;
55
+ },
56
+ ): void {
57
+ const savings = scope.savings_tokens;
58
+ const pct = scope.original_tokens > 0 ? Math.round((savings / scope.original_tokens) * 100) : 0;
59
+ rows.push({ kind: "scope", label });
60
+ rows.push({ kind: "stat", label: "Tokens Saved", value: savings.toLocaleString("en-US") });
61
+ rows.push({ kind: "stat", label: "Compression Ratio", value: `${pct}%` });
62
+ }
63
+
64
+ export function formatCompressionSidebarRows(
65
+ compression: StatusCompression | undefined,
66
+ ): CompressionRow[] {
67
+ if (!compression || compression.project.events <= 0) return [];
68
+
69
+ const rows: CompressionRow[] = [];
70
+ if (compression.session.events > 0) {
71
+ appendScope(rows, "Session", compression.session);
72
+ }
73
+ appendScope(rows, "Project", compression.project);
74
+
75
+ return rows;
76
+ }
77
+
38
78
  // Map index status → (label, theme color name). The label is what we want
39
79
  // the user to see; the color encodes severity so the eye lands on warnings.
40
80
  function statusDisplay(status: string): { label: string; tone: "ok" | "warn" | "err" | "muted" } {
@@ -104,7 +144,10 @@ function getClient(directory: string): AftRpcClient {
104
144
  if (client) return client;
105
145
  const home = process.env.HOME || process.env.USERPROFILE || "";
106
146
  const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
107
- const storageDir = `${dataHome}/opencode/storage/plugin/aft`;
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`;
108
151
  client = new AftRpcClient(storageDir, directory);
109
152
  sidebarClients.set(directory, client);
110
153
  return client;
@@ -225,6 +268,7 @@ const SidebarContent = (props: {
225
268
  const semanticStatus = () => statusDisplay(s()?.semantic_index?.status ?? "disabled");
226
269
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
227
270
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
271
+ const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
228
272
 
229
273
  // Degraded-mode reason → human-readable hint. Distinct strings per reason
230
274
  // because the UX direction is different: "home_root" tells the user to
@@ -357,6 +401,26 @@ const SidebarContent = (props: {
357
401
  )}
358
402
  <StatRow theme={props.theme} label="Disk" value={formatBytes(semanticBytes())} tone="muted" />
359
403
 
404
+ {/* Compression aggregates. Tabular layout matching Search/Semantic
405
+ Index above: each scope ("Session", "Project") renders as a
406
+ subheader followed by two StatRows (Tokens Saved, Compression
407
+ Ratio). Keeps numbers right-aligned in the value column instead
408
+ of jamming them after the label on the same line. */}
409
+ {compressionRows().length > 0 && (
410
+ <>
411
+ <SectionHeader theme={props.theme} title="Compression" />
412
+ {compressionRows().map((row) =>
413
+ row.kind === "scope" ? (
414
+ <box width="100%">
415
+ <text fg={props.theme.text}>{row.label}</text>
416
+ </box>
417
+ ) : (
418
+ <StatRow theme={props.theme} label={row.label} value={row.value} tone="muted" />
419
+ ),
420
+ )}
421
+ </>
422
+ )}
423
+
360
424
  {/* Surface failures clearly so users know to act (install ONNX,
361
425
  fix config, etc.) rather than silently leaving the panel "off". */}
362
426
  {s()?.semantic_index?.status === "failed" && s()?.semantic_index?.error && (