@cortexkit/aft-opencode 0.26.3 → 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,25 @@
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";
17
+ type SdkMessage = {
18
+ info?: {
19
+ id?: string;
20
+ role?: string;
21
+ };
22
+ parts?: Array<{
23
+ type?: string;
24
+ text?: string;
25
+ ignored?: boolean;
26
+ }>;
27
+ };
28
+ export declare const SESSION_MESSAGES_LIMIT = 50;
29
+ /**
30
+ * @internal — exported only so tests can pin the bounded-call contract.
31
+ * Production callers go through `sendStatus` / `cleanupWarnings`, both of
32
+ * which gate on a real `readDesktopState()` before reaching this helper.
33
+ */
34
+ export declare function getSessionMessages(client: unknown, sessionId: string): Promise<SdkMessage[]>;
16
35
  export interface NotificationOptions {
17
36
  /** The OpenCode SDK client */
18
37
  client: unknown;
@@ -32,6 +51,7 @@ export interface ConfigureWarning {
32
51
  export interface ConfigureWarningOptions {
33
52
  client: unknown;
34
53
  sessionId: string;
54
+ bridge: Pick<BinaryBridge, "send">;
35
55
  storageDir: string;
36
56
  pluginVersion: string;
37
57
  projectRoot?: string;
@@ -61,4 +81,5 @@ export declare function deliverConfigureWarnings(opts: ConfigureWarningOptions,
61
81
  * Desktop only — TUI toasts are inherently transient and don't need cleanup.
62
82
  */
63
83
  export declare function cleanupWarnings(opts: NotificationOptions): Promise<void>;
84
+ export {};
64
85
  //# sourceMappingURL=notifications.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAyOH,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"}
@@ -31,15 +31,6 @@ export interface ResolvedPromptContext {
31
31
  };
32
32
  variant?: string;
33
33
  }
34
- /**
35
- * Read recent messages from the OpenCode session and resolve the most
36
- * recent assistant prompt context. Falls back to user messages if no
37
- * assistant has the field. Merges across messages so partial fields are
38
- * filled in. Returns null if no usable context is found.
39
- *
40
- * Mirrors `resolveSessionPromptParams` in `opencode-xtra` (the working
41
- * reference implementation).
42
- */
43
34
  export declare function resolvePromptContext(client: unknown, sessionId: string): Promise<ResolvedPromptContext | null>;
44
35
  export interface LastAssistantModel {
45
36
  providerID: string;
@@ -1 +1 @@
1
- {"version":3,"file":"last-assistant-model.d.ts","sourceRoot":"","sources":["../../src/shared/last-assistant-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA0ED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAwCvC;AAID,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAQpC"}
1
+ {"version":3,"file":"last-assistant-model.d.ts","sourceRoot":"","sources":["../../src/shared/last-assistant-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAkGD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA8CvC;AAID,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAQpC"}
@@ -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,16 @@ 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;
72
+ /**
73
+ * Human-readable explanation for a synthetic snapshot (e.g.
74
+ * `cache_role === "not_initialized"`). When the plugin returns a placeholder
75
+ * because no bridge has been spawned yet, this message tells the user what
76
+ * to expect; the TUI dialog renders it instead of an empty grid of zeros.
77
+ * Empty string when the snapshot is real bridge data.
78
+ */
79
+ message: string;
60
80
  }
61
81
  export declare function formatBytes(bytes: number): string;
62
82
  export declare function coerceAftStatus(response: Record<string, unknown>): AftStatusSnapshot;
@@ -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;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,CAiEpF;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.3",
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.3",
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.3",
44
- "@cortexkit/aft-darwin-x64": "0.26.3",
45
- "@cortexkit/aft-linux-arm64": "0.26.3",
46
- "@cortexkit/aft-linux-x64": "0.26.3",
47
- "@cortexkit/aft-win32-x64": "0.26.3"
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";
@@ -375,13 +393,15 @@ function coerceAftStatus(response) {
375
393
  id: readString(session.id, "__default__"),
376
394
  tracked_files: readNumber(session.tracked_files),
377
395
  checkpoints: readNumber(session.checkpoints)
378
- }
396
+ },
397
+ compression: readCompression(response.compression),
398
+ message: readString(response.message, "")
379
399
  };
380
400
  }
381
401
 
382
402
  // src/tui/sidebar.tsx
383
403
  import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js";
384
- import { jsxDEV } from "@opentui/solid/jsx-dev-runtime";
404
+ import { jsxDEV, Fragment } from "@opentui/solid/jsx-dev-runtime";
385
405
  var SINGLE_BORDER = { type: "single" };
386
406
  var REFRESH_DEBOUNCE_MS = 200;
387
407
  var POLL_INTERVAL_MS = 1500;
@@ -405,6 +425,23 @@ function formatCount(n) {
405
425
  return `${Math.round(n / 1000)}K`;
406
426
  return String(n);
407
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
+ }
408
445
  function statusDisplay(status) {
409
446
  switch (status) {
410
447
  case "ready":
@@ -473,7 +510,7 @@ function getClient(directory) {
473
510
  return client;
474
511
  const home = process.env.HOME || process.env.USERPROFILE || "";
475
512
  const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
476
- const storageDir = `${dataHome}/opencode/storage/plugin/aft`;
513
+ const storageDir = `${dataHome}/cortexkit/aft`;
477
514
  client = new AftRpcClient(storageDir, directory);
478
515
  sidebarClients.set(directory, client);
479
516
  return client;
@@ -561,6 +598,7 @@ var SidebarContent = (props) => {
561
598
  const semanticStatus = () => statusDisplay(s()?.semantic_index?.status ?? "disabled");
562
599
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
563
600
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
601
+ const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
564
602
  const degradedReasonLabel = (reason) => {
565
603
  if (reason === "home_root") {
566
604
  return "project root is your home directory";
@@ -644,6 +682,14 @@ var SidebarContent = (props) => {
644
682
  ]
645
683
  }, undefined, true, undefined, this)
646
684
  }, undefined, false, undefined, this),
685
+ s()?.cache_role === "not_initialized" && /* @__PURE__ */ jsxDEV("box", {
686
+ marginTop: 1,
687
+ width: "100%",
688
+ children: /* @__PURE__ */ jsxDEV("text", {
689
+ fg: props.theme.textMuted,
690
+ children: s().message || "Waiting for first tool call to populate"
691
+ }, undefined, false, undefined, this)
692
+ }, undefined, false, undefined, this),
647
693
  /* @__PURE__ */ jsxDEV(SectionHeader, {
648
694
  theme: props.theme,
649
695
  title: "Search Index"
@@ -694,6 +740,26 @@ var SidebarContent = (props) => {
694
740
  value: formatBytes2(semanticBytes()),
695
741
  tone: "muted"
696
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),
697
763
  s()?.semantic_index?.status === "failed" && s()?.semantic_index?.error && /* @__PURE__ */ jsxDEV("box", {
698
764
  marginTop: 1,
699
765
  width: "100%",
@@ -735,7 +801,7 @@ function getRpcClient(directory) {
735
801
  return client;
736
802
  const home = process.env.HOME || process.env.USERPROFILE || "";
737
803
  const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
738
- const storageDir = `${dataHome}/opencode/storage/plugin/aft`;
804
+ const storageDir = `${dataHome}/cortexkit/aft`;
739
805
  client = new AftRpcClient(storageDir, directory);
740
806
  rpcClients.set(directory, client);
741
807
  return client;
@@ -825,6 +891,7 @@ var StatusDialog = (props) => {
825
891
  }, POLL_INTERVAL_MS2);
826
892
  onCleanup2(() => clearInterval(timer));
827
893
  const cacheRoleTone = (role) => role === "main" ? "accent" : role === "worktree" ? "warn" : "muted";
894
+ const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
828
895
  return /* @__PURE__ */ jsxDEV2("box", {
829
896
  flexDirection: "column",
830
897
  width: "100%",
@@ -863,7 +930,16 @@ var StatusDialog = (props) => {
863
930
  children: error()
864
931
  }, undefined, false, undefined, this)
865
932
  }, undefined, false, undefined, this) : null,
866
- status() ? /* @__PURE__ */ jsxDEV2("box", {
933
+ status()?.cache_role === "not_initialized" ? /* @__PURE__ */ jsxDEV2("box", {
934
+ width: "100%",
935
+ marginTop: 1,
936
+ justifyContent: "center",
937
+ children: /* @__PURE__ */ jsxDEV2("text", {
938
+ fg: t().textMuted,
939
+ children: status().message || "Waiting for first tool call to populate"
940
+ }, undefined, false, undefined, this)
941
+ }, undefined, false, undefined, this) : null,
942
+ status() && status().cache_role !== "not_initialized" ? /* @__PURE__ */ jsxDEV2("box", {
867
943
  flexDirection: "column",
868
944
  width: "100%",
869
945
  marginBottom: 1,
@@ -1092,6 +1168,31 @@ var StatusDialog = (props) => {
1092
1168
  ]
1093
1169
  }, undefined, true, undefined, this)
1094
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,
1095
1196
  /* @__PURE__ */ jsxDEV2("box", {
1096
1197
  marginTop: 1,
1097
1198
  justifyContent: "flex-end",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.26.3",
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.3",
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.3",
40
- "@cortexkit/aft-darwin-x64": "0.26.3",
41
- "@cortexkit/aft-linux-arm64": "0.26.3",
42
- "@cortexkit/aft-linux-x64": "0.26.3",
43
- "@cortexkit/aft-win32-x64": "0.26.3"
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",
@@ -110,7 +110,22 @@ function isComplete(ctx: ResolvedPromptContext): boolean {
110
110
  *
111
111
  * Mirrors `resolveSessionPromptParams` in `opencode-xtra` (the working
112
112
  * reference implementation).
113
+ *
114
+ * Bounded via `query.limit` — without it, OpenCode's legacy
115
+ * `/session/{id}/message` endpoint hydrates the ENTIRE session. Large
116
+ * sessions can carry 30k-45k messages / 100k+ parts and blow the host's
117
+ * memory just to find the last assistant turn. We only need the most
118
+ * recent prompt context, so 50 is plenty (the very last assistant
119
+ * usually has everything; the merge fallback rarely needs more).
120
+ *
121
+ * Future v2 migration: once `@opencode-ai/sdk` exposes `client.v2.session.messages`
122
+ * with projected shapes (user / assistant / agent-switched / model-switched / ...),
123
+ * prefer that with `{ limit: 50, order: "desc" }` and fall back to this bounded
124
+ * legacy call when v2 returns no items (older sessions aren't backfilled into v2).
125
+ * See https://github.com/cortexkit/aft notes for tracking.
113
126
  */
127
+ const PROMPT_CONTEXT_MESSAGE_LIMIT = 50;
128
+
114
129
  export async function resolvePromptContext(
115
130
  client: unknown,
116
131
  sessionId: string,
@@ -118,14 +133,20 @@ export async function resolvePromptContext(
118
133
  if (!client || !sessionId) return null;
119
134
  const c = client as {
120
135
  session?: {
121
- messages?: (input: { path: { id: string } }) => Promise<{ data?: unknown[] } | unknown[]>;
136
+ messages?: (input: {
137
+ path: { id: string };
138
+ query?: { limit?: number };
139
+ }) => Promise<{ data?: unknown[] } | unknown[]>;
122
140
  };
123
141
  };
124
142
  if (typeof c.session?.messages !== "function") return null;
125
143
 
126
144
  let messages: unknown[] = [];
127
145
  try {
128
- const response = await c.session.messages({ path: { id: sessionId } });
146
+ const response = await c.session.messages({
147
+ path: { id: sessionId },
148
+ query: { limit: PROMPT_CONTEXT_MESSAGE_LIMIT },
149
+ });
129
150
  messages = extractMessages(response);
130
151
  } catch {
131
152
  return null;
@@ -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,16 @@ 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;
74
+ /**
75
+ * Human-readable explanation for a synthetic snapshot (e.g.
76
+ * `cache_role === "not_initialized"`). When the plugin returns a placeholder
77
+ * because no bridge has been spawned yet, this message tells the user what
78
+ * to expect; the TUI dialog renders it instead of an empty grid of zeros.
79
+ * Empty string when the snapshot is real bridge data.
80
+ */
81
+ message: string;
60
82
  }
61
83
 
62
84
  function asRecord(value: unknown): Record<string, unknown> {
@@ -83,6 +105,25 @@ function readOptionalNumber(value: unknown): number | null {
83
105
  return typeof value === "number" && Number.isFinite(value) ? value : null;
84
106
  }
85
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
+
86
127
  function formatFlag(enabled: boolean): string {
87
128
  return enabled ? "enabled" : "disabled";
88
129
  }
@@ -170,6 +211,8 @@ export function coerceAftStatus(response: Record<string, unknown>): AftStatusSna
170
211
  tracked_files: readNumber(session.tracked_files),
171
212
  checkpoints: readNumber(session.checkpoints),
172
213
  },
214
+ compression: readCompression(response.compression),
215
+ message: readString(response.message, ""),
173
216
  };
174
217
  }
175
218
 
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
@@ -178,8 +189,21 @@ const StatusDialog = (props: StatusDialogProps) => {
178
189
  </box>
179
190
  ) : null}
180
191
 
192
+ {/* Lazy-bridge placeholder: when no bridge has spawned for this project
193
+ yet (because the user has not made any tool call since opening
194
+ OpenCode), the RPC server returns a synthetic snapshot with
195
+ cache_role === "not_initialized". Show the explanatory message
196
+ instead of an empty grid of zeros and "unknown" rows. */}
197
+ {status()?.cache_role === "not_initialized" ? (
198
+ <box width="100%" marginTop={1} justifyContent="center">
199
+ <text fg={t().textMuted}>
200
+ {status()!.message || "Waiting for first tool call to populate"}
201
+ </text>
202
+ </box>
203
+ ) : null}
204
+
181
205
  {/* Header rows — paths span full width since they can be long */}
182
- {status() ? (
206
+ {status() && status()!.cache_role !== "not_initialized" ? (
183
207
  <box flexDirection="column" width="100%" marginBottom={1}>
184
208
  <R
185
209
  theme={t()}
@@ -361,6 +385,28 @@ const StatusDialog = (props: StatusDialogProps) => {
361
385
  </box>
362
386
  ) : null}
363
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
+
364
410
  {/* Footer */}
365
411
  <box marginTop={1} justifyContent="flex-end" width="100%">
366
412
  <text fg={t().textMuted}>Enter or Esc to close</text>