opencode-context-indicator 1.7.0 → 1.8.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/CHANGELOG.md +106 -12
- package/README.md +102 -96
- package/index.js +6 -6
- package/lib/contributors.js +5 -4
- package/lib/limits.js +27 -15
- package/lib/state.js +6 -5
- package/lib/tui-data.d.ts +0 -1
- package/lib/tui-data.js +5 -21
- package/lib/tui-h.ts +73 -0
- package/lib/{tui-panel.tsx → tui-panel.ts} +779 -713
- package/lib/tui-sidebar.ts +153 -0
- package/lib/v2.js +56 -10
- package/package.json +10 -12
- package/tui.ts +317 -0
- package/lib/tui-sidebar.tsx +0 -76
- package/tui.tsx +0 -206
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,97 @@ Notable user-facing changes per release. Dates are commit days (UTC).
|
|
|
4
4
|
The package also ships a dual V1/V2 plugin — see the README for the surface
|
|
5
5
|
matrix; everything below concerns the V2 path unless stated otherwise.
|
|
6
6
|
|
|
7
|
+
## 1.8.0 — 2026-10-31
|
|
8
|
+
|
|
9
|
+
- **The TUI sidebar and the `/cx` panel are live for npm installs too**
|
|
10
|
+
(workaround for upstream anomalyco/opencode#33884). OpenCode skips its
|
|
11
|
+
Solid/Babel JSX transform for any file inside a `node_modules` directory, so a
|
|
12
|
+
JSX-built slot mounted from an npm install rendered once and then froze on
|
|
13
|
+
"no data yet"; 1.7.x avoided that by disabling the surfaces entirely. The TUI
|
|
14
|
+
tree is now built with `jsx()` plus getter props (`lib/tui-h.ts`), which needs
|
|
15
|
+
no transform at all: the host rewrites the literal
|
|
16
|
+
`@opentui/solid/jsx-runtime` import to its own in-process module, so the
|
|
17
|
+
surfaces stay reactive from any install path. The npm-path auto-disable and its
|
|
18
|
+
console hint are gone; `file://` installs behave exactly as before.
|
|
19
|
+
- Relative imports of `.ts` files now carry the extension (`./lib/tui-h.ts`):
|
|
20
|
+
the host decides which files get the runtime rewrite by walking the import
|
|
21
|
+
graph with `import.meta.resolve`, which never probes extensions. An
|
|
22
|
+
extensionless import silently kept the literal `@opentui/solid` specifier
|
|
23
|
+
and failed with `Cannot find package '@opentui/solid'` - gate (b) now
|
|
24
|
+
rejects it.
|
|
25
|
+
- **No runtime dependencies at all.** `@opentui/solid` moved from
|
|
26
|
+
`dependencies` to `devDependencies` (types + local typecheck only, still
|
|
27
|
+
pinned to an exact version). A fresh install drops from ~28 MB / ~800 files to
|
|
28
|
+
a 147 KB tarball (27 files, ~419 KB unpacked), and the duplicate
|
|
29
|
+
`@opentui/core`
|
|
30
|
+
/ Babel toolchain
|
|
31
|
+
(`@babel/core`, `babel-plugin-module-resolver`, `babel-preset-solid`, `s-js`,
|
|
32
|
+
`entities`) no longer lands in the opencode cache next to the host's own copy.
|
|
33
|
+
- **Baseline: opencode 2.2.5 / OpenTUI 0.8.** Dev dependencies, the peer floor
|
|
34
|
+
and the typecheck now target `@opentui/solid` + `@opentui/core` `0.8.0` and
|
|
35
|
+
`@opencode/plugin` `2.2.5` (opencode 2.2.3 made OpenTUI 0.8 mandatory for TUI
|
|
36
|
+
plugins; 2.2.5 is the latest release at the time of writing and still ships
|
|
37
|
+
0.8.0). `jsx()`/getter semantics are unchanged in 0.8 - `spreadExpression`
|
|
38
|
+
still reads every prop inside a `createRenderEffect` - so the JSX-free tree
|
|
39
|
+
behaves exactly as before; the whole gate set (types, fast gate, Check C) was
|
|
40
|
+
re-run on a real 2.2.5 host. A host that still ships OpenTUI 0.7 (opencode
|
|
41
|
+
2.2.0-2.2.2) may not load the TUI surfaces.
|
|
42
|
+
- **Denominator follows opencode 2.2.5.** The context window used for every
|
|
43
|
+
percentage is now `input && context ? min(input, context) : input || context`,
|
|
44
|
+
mirroring core's `inputWindow()` after #60660: a provider that reports an
|
|
45
|
+
input limit above the context window cannot use it in full, because the
|
|
46
|
+
context also holds the reply. Before this release an input limit above the
|
|
47
|
+
context window was used as-is, which overstated the window (and printed a
|
|
48
|
+
compact-at point the host never reaches). Covered by unit test T37. opencode
|
|
49
|
+
2.2.5 also accepts a percentage `compaction.buffer`; that setting is not
|
|
50
|
+
visible to plugins, so a percentage buffer still shows the default reserve.
|
|
51
|
+
- **Gates cover the failure mode end to end.** Fast gate (b) is rewritten: the
|
|
52
|
+
TUI surface must contain no `.tsx`/`.jsx` file and no `@jsx*` pragma, every
|
|
53
|
+
TUI source must parse with esbuild `loader:"ts"` (JSX syntax fails), and
|
|
54
|
+
imports must stay inside the host-bridged allowlist. Gate (c) now walks the
|
|
55
|
+
real import graph and fails if a host-bridged module is a production
|
|
56
|
+
dependency. New e2e **Check C**: `npm pack` → extract into a throwaway
|
|
57
|
+
`node_modules/opencode-context-indicator` → isolated `--standalone` TUI →
|
|
58
|
+
assert an env-gated getter-driven probe keeps re-rendering (a frozen tree
|
|
59
|
+
writes the same value forever). `node test/e2e-tui.mjs --check-c` runs it
|
|
60
|
+
alone, without an LLM call.
|
|
61
|
+
- **Docs:** the README no longer describes a `file://`-only live sidebar;
|
|
62
|
+
"Live sidebar" documents the JSX-free design and the size win.
|
|
63
|
+
|
|
64
|
+
## 1.7.1 — 2026-10-29
|
|
65
|
+
|
|
66
|
+
- **Fixed: the live log no longer prints `ctx=0` above real categories.** A hook
|
|
67
|
+
breakdown whose instance has no request counters (a mirror that only saw the
|
|
68
|
+
compaction, so `input`/`ctx` are still 0) used to be printed as-is: the header
|
|
69
|
+
contradicted the category lines below it. Such an entry now takes the same
|
|
70
|
+
path as a missing/fallback one and borrows the hook-measured `state.json`
|
|
71
|
+
snapshot whole (categories, counters, residual and a `snapshot=` stamp; a
|
|
72
|
+
snapshot without a timestamp is still marked `snapshot=unknown`). A counter is
|
|
73
|
+
knowledge only when it is positive: a borrowed entry whose own counters are 0,
|
|
74
|
+
or a ctx-only snapshot with no input, prints the unknown counter as `?`, and
|
|
75
|
+
the residual as `n/a (input unknown on this instance)` instead of a fabricated
|
|
76
|
+
`0` (`other = input - sum` needs an input). The same rule now applies to a
|
|
77
|
+
local hook entry that knows `ctx` but not `input`. T36 covers all four cases:
|
|
78
|
+
whole-entry borrow, no snapshot, zero-counter snapshot, ctx-only snapshot
|
|
79
|
+
(86 checks).
|
|
80
|
+
- **Fixed: the compaction reserve is 15 % again (1.7.0 regression).** 1.7.0
|
|
81
|
+
switched the denominator to a 10 % share on the strength of an unreleased
|
|
82
|
+
branch head. Every RELEASED host from 2.1.4 through 2.2.2 (2.1.4, 2.2.0,
|
|
83
|
+
2.2.1, 2.2.2) still has `RESERVE_SHARE = 0.15` in
|
|
84
|
+
`packages/core/src/session/compaction.ts` (verified tag by tag), so on those
|
|
85
|
+
hosts 1.7.0 overstated the ceiling and the `compact at` line by ~5 % of the
|
|
86
|
+
window (~26 k tokens at 512 k) and therefore under-reported every `%`. The
|
|
87
|
+
plugin now mirrors `max(floor(window * 0.15), window >= 32_000 ? 16_000 : 0)`
|
|
88
|
+
again; the formula, the 16 k floor and the `2 * RESERVE_MIN` gate are
|
|
89
|
+
unchanged. Docs, the T16/T26 tables and the seed expectations are back on the
|
|
90
|
+
15 % numbers.
|
|
91
|
+
- Dev: the isolated e2e (plugin load + state write) was re-run against the
|
|
92
|
+
**2.2.2** Desktop CLI (`opencode v2.2.2`); the Desktop 2.2.2 `app.asar` still
|
|
93
|
+
carries the `@opencode/plugin/gui` / `@opencode/plugin/rpc` bridge and the
|
|
94
|
+
`ComposerPill` / `Rpc.define` / `session.panel` symbols the experimental panel
|
|
95
|
+
uses, and `opencode-cli.exe` still exposes `opentui:runtime-module:` plus the
|
|
96
|
+
`node_modules` exclusion filter.
|
|
97
|
+
|
|
7
98
|
## 1.7.0 — 2026-10-29
|
|
8
99
|
|
|
9
100
|
Desktop GUI release (experimental): the plugin now has a surface in the
|
|
@@ -60,14 +151,14 @@ OpenCode Desktop app, not just in the terminal.
|
|
|
60
151
|
its `ctx`/`input` counters, tagged `snapshot=<updatedAt>` — instead of this
|
|
61
152
|
instance's fallback-shaped view. Repeated compactions of one session are also
|
|
62
153
|
no longer collapsed into a single note when the host omits the event id.
|
|
63
|
-
- **Reserve share corrected
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
154
|
+
- **Reserve share (claim in 1.7.0 was wrong; corrected in 1.7.1).** This
|
|
155
|
+
release switched the denominator to a 10 % share; no host in the verified
|
|
156
|
+
range 2.1.4 through 2.2.2 used it. `calculateCeiling()` in those tags keeps
|
|
157
|
+
`RESERVE_SHARE = 0.15`, so 1.7.0 overstated the ceiling (and the `compact at`
|
|
158
|
+
line) by ~5 % of the window (~26 k tokens at 512 k) on them and therefore
|
|
159
|
+
under-reported every `%`. The 1.6.0 note that the ceiling was unchanged in
|
|
160
|
+
2.2.0 was right (the formula and the `2 * RESERVE_MIN` gate did stay); the
|
|
161
|
+
share line here was the wrong part.
|
|
71
162
|
- Dev: `lib/format.js` (pure `fmt`/`num`) extracted so the GUI bundle has no
|
|
72
163
|
`node:*` import; gate (a) now expands `dist/`, gate (e)/(f) scan `gui/` and the
|
|
73
164
|
GUI build config, new gate (g) validates the built CJS bundle (app-provided
|
|
@@ -100,11 +191,13 @@ Host alignment release: opencode 2.2.0 moved TUI plugins to OpenTUI 0.7.
|
|
|
100
191
|
opencode 2.0.16+. The TUI module now ships compiled against OpenTUI 0.7, so
|
|
101
192
|
the sidebar and the `/cx` panel require **opencode ≥ 2.2.0** and cannot load on
|
|
102
193
|
an older host. Older-host installation was not re-verified for this release.
|
|
103
|
-
- **Compaction ceiling unchanged (
|
|
194
|
+
- **Compaction ceiling unchanged (this note was right; 1.7.0 briefly wrong).**
|
|
104
195
|
`calculateCeiling()` in 2.2.0 is identical
|
|
105
196
|
to 2.1.x, so the `0.15 / 16k / 32k` reserve formula and every `%` denominator
|
|
106
197
|
are unaffected. (2.2.0 changed how a *compaction request* is sized - measured
|
|
107
|
-
prompt instead of characters - not the trigger.
|
|
198
|
+
prompt instead of characters - not the trigger. 1.7.0 later claimed the share
|
|
199
|
+
had dropped to 10 %; see 1.7.1 - no host in the verified range
|
|
200
|
+
2.1.4-2.2.2 uses it.)
|
|
108
201
|
- Dev: `drift-watch` is green again (host 2.2.0, OpenTUI 0.7.0); `tsc`,
|
|
109
202
|
`prepublish` and the isolated e2e pass against the 2.2.0 CLI.
|
|
110
203
|
|
|
@@ -150,8 +243,9 @@ Fix-and-hygiene release after a full-project review. No new commands.
|
|
|
150
243
|
|
|
151
244
|
- **Compaction ceiling aligned with opencode 2.1.4.** `lib/limits.js`
|
|
152
245
|
now mirrors `calculateCeiling()` exactly: the reserve is
|
|
153
|
-
`max(floor(window * 0.15), window >= 2 * 16_000 ? 16_000 : 0)` (
|
|
154
|
-
|
|
246
|
+
`max(floor(window * 0.15), window >= 2 * 16_000 ? 16_000 : 0)` (the plugin's
|
|
247
|
+
older builds used 10 %; no host in the verified range uses it - the share
|
|
248
|
+
stayed 0.15 there),
|
|
155
249
|
and an override buffer is honored **only while it is smaller than the
|
|
156
250
|
window** — a buffer configured for a larger model is ignored instead of
|
|
157
251
|
leaving zero room. The `compact at` line and the `%` denominator now match the
|
package/README.md
CHANGED
|
@@ -15,8 +15,9 @@ generations) plus an optional TUI module (`./tui` entry) that renders a **live
|
|
|
15
15
|
sidebar** and a **zero-LLM `/cx` breakdown panel** with measured contributors
|
|
16
16
|
and a redacted export, and an **experimental Desktop GUI part**
|
|
17
17
|
(`./gui-experimental`: a side-panel tab and a composer pill). The V2 path is
|
|
18
|
-
**tested against opencode 2.2.0** (the isolated load + state-write
|
|
19
|
-
re-run against the 2.2.
|
|
18
|
+
**tested against opencode 2.2.5 / OpenTUI 0.8** (the isolated load + state-write
|
|
19
|
+
e2e was also re-run against the 2.2.0-2.2.4 Desktop CLI); the V1 path against
|
|
20
|
+
1.18.29+.
|
|
20
21
|
|
|
21
22
|
> **V1 is feature-frozen.** It is kept working for compatibility (bug fixes
|
|
22
23
|
> only); all new features land in the V2 path.
|
|
@@ -32,7 +33,7 @@ re-run against the 2.2.1 Desktop CLI); the V1 path against 1.18.29+.
|
|
|
32
33
|
For every served request the plugin estimates how the context window is split:
|
|
33
34
|
|
|
34
35
|
```
|
|
35
|
-
[2026-…] session=ses_abc model=… ctx=45.2k/120k (
|
|
36
|
+
[2026-…] session=ses_abc model=… ctx=45.2k/120k (37.7%) count=45.2k usable=… limit=… maxOutput=… reserved=… input=44.1k
|
|
36
37
|
user 4.2k (est tokens)
|
|
37
38
|
assistant 3.1k (est tokens)
|
|
38
39
|
reasoning 0.8k (est tokens) (exact 1.2k)
|
|
@@ -42,7 +43,7 @@ For every served request the plugin estimates how the context window is split:
|
|
|
42
43
|
tool schemas 1.4k (est tokens)
|
|
43
44
|
other 5.6k (= input - sum of estimates)
|
|
44
45
|
source context-hook
|
|
45
|
-
prompt cache read=12.0k
|
|
46
|
+
prompt cache read=12.0k write=1.1k (provider-reported; subset of input)
|
|
46
47
|
reclaim ~9.8k redundant tool output (3 duplicate(s) — estimate)
|
|
47
48
|
subagents 2 session(s): in=8.1k out=2.3k r=1.0k worst=12%
|
|
48
49
|
```
|
|
@@ -61,7 +62,9 @@ For every served request the plugin estimates how the context window is split:
|
|
|
61
62
|
`input + output + cache.read + cache.write`. Percentages are **not clamped**
|
|
62
63
|
at 100% — exceeding the window stays visible.
|
|
63
64
|
* `prompt cache` is **provider-reported and exact** (`tokens.cache.read` /
|
|
64
|
-
`tokens.cache.write`)
|
|
65
|
+
`tokens.cache.write`). The live snapshot prints the raw read/write counts;
|
|
66
|
+
`/context` and `/context-breakdown` also append the read share of `input`
|
|
67
|
+
when the provider reported it. It is a *subset* of input,
|
|
65
68
|
so it is deliberately **not** added to the category sum: a cache read means the
|
|
66
69
|
provider served those prompt tokens from its prompt cache, not that they are
|
|
67
70
|
absent from the window. `n/a` when the provider reports nothing.
|
|
@@ -72,17 +75,23 @@ For every served request the plugin estimates how the context window is split:
|
|
|
72
75
|
duplicate group), reported honestly as *"N redundant tool outputs ≈ X tokens"*
|
|
73
76
|
— never as "the model wasted effort". Findings are per-session and **path-free
|
|
74
77
|
in the persisted/exported form**.
|
|
75
|
-
* The denominator is the **opencode 2.
|
|
76
|
-
limit.input
|
|
78
|
+
* The denominator is the **opencode 2.x compaction ceiling**: `window =
|
|
79
|
+
limit.input && limit.context ? min(limit.input, limit.context) : limit.input
|
|
80
|
+
|| limit.context`; `reserve = max(floor(window * 0.15), window >=
|
|
77
81
|
2 * 16_000 ? 16_000 : 0)`; the ceiling is `window - reserve`. This mirrors
|
|
78
|
-
`calculateCeiling()` in `packages/core/src/session/compaction.ts
|
|
79
|
-
|
|
82
|
+
`calculateCeiling()` in `packages/core/src/session/compaction.ts`, whose
|
|
83
|
+
`RESERVE_SHARE` is 0.15 in every released host from 2.1.4 through 2.2.5 (the
|
|
84
|
+
unreleased `v2` branch has a different share; the plugin follows the release,
|
|
85
|
+
not the branch). When opencode
|
|
80
86
|
carries a `compaction.buffer` configured in its own settings, the plugin
|
|
81
87
|
cannot read that value (it is not exposed to plugins), so the default
|
|
82
88
|
reserve formula is used — set `OPENCODE_CONTEXT_INDICATOR_COMPACT_BUFFER`
|
|
83
89
|
to override the reserve for testing or custom setups (non-negative integer <=
|
|
84
90
|
1e9; invalid values are silently ignored; a buffer that is **not smaller than
|
|
85
91
|
the window** is ignored, exactly like core, so it can never leave zero room).
|
|
92
|
+
Since opencode 2.2.5 that setting also accepts a percentage (`"buffer": "20%"`),
|
|
93
|
+
which the plugin cannot read either, so a percentage buffer is **not**
|
|
94
|
+
reflected in the "compact at" line until it is mirrored from core.
|
|
86
95
|
|
|
87
96
|
### Toast (V1 only)
|
|
88
97
|
|
|
@@ -114,9 +123,9 @@ other 5.6k 12%
|
|
|
114
123
|
updated 12:30:01
|
|
115
124
|
```
|
|
116
125
|
|
|
117
|
-
>
|
|
118
|
-
>
|
|
119
|
-
> [Live sidebar
|
|
126
|
+
> Works on **every** install path, including the npm one — the TUI surfaces are
|
|
127
|
+
> built without JSX precisely so `node_modules` installs stay live. See
|
|
128
|
+
> [Live sidebar](#live-sidebar).
|
|
120
129
|
|
|
121
130
|
### Slash commands (V2)
|
|
122
131
|
|
|
@@ -125,13 +134,13 @@ commands:
|
|
|
125
134
|
|
|
126
135
|
| Command | Scope | Delivery | Agent turn? |
|
|
127
136
|
| --- | --- | --- | :---: |
|
|
128
|
-
| `/context` | **this session only** (no subagents); cache + `state.json` only | `resume:true`
|
|
137
|
+
| `/context` | **this session only** (no subagents); cache + `state.json` only | `resume:true` - the model echoes the short summary into the transcript | yes |
|
|
129
138
|
| `/context-breakdown` | this session **+ all subagent descendants** | `resume:true` — the model echoes the table into the transcript | yes |
|
|
130
139
|
| `/context-diff` | **this session only**; previous measurement vs current | `resume:true` — the model echoes the delta table into the transcript | yes |
|
|
131
140
|
|
|
132
141
|
#### `/context` — quick summary (main session only)
|
|
133
142
|
|
|
134
|
-
A
|
|
143
|
+
A short summary of the current session, built purely from the in-memory cache
|
|
135
144
|
and the `state.json` snapshot: **no subagent traversal and no live RPC**, so
|
|
136
145
|
collection is instant (delivery itself still runs an agent turn — see below).
|
|
137
146
|
The synthetic notice chip shows a self-contained one-liner (label =
|
|
@@ -155,6 +164,7 @@ instructions to follow.
|
|
|
155
164
|
<context-summary>
|
|
156
165
|
**Context** — your-model · 41.5k / 168k (25%)
|
|
157
166
|
usr 2.3k · asst 285 · rsn 9.6k · tool 2.1k · out 24.7k · sys 8.5k · schm 3.8k · oth 4.1k
|
|
167
|
+
compact at 143k (25.0k reserve)
|
|
158
168
|
updated 12:36:16 (snap)
|
|
159
169
|
</context-summary>
|
|
160
170
|
```
|
|
@@ -264,7 +274,8 @@ the content is visible in the Desktop (the chip only shows `description`).
|
|
|
264
274
|
`CONTEXT_BREAKDOWN_RESUME` controls `/context-breakdown` back to a plain
|
|
265
275
|
notice if needed.
|
|
266
276
|
|
|
267
|
-
> **E2E note (Desktop 2.0.19, local model `llama3.2:latest`,
|
|
277
|
+
> **E2E note (historical run on Desktop 2.0.19, local model `llama3.2:latest`,
|
|
278
|
+
> `build` agent).**
|
|
268
279
|
> `resume:true` behaved as designed: the synthetic item was drained into
|
|
269
280
|
> `/message` and a turn ran; the assistant message contained the table verbatim
|
|
270
281
|
> (the trailing `_…_` note line was dropped by the model). Because `resume:true`
|
|
@@ -360,9 +371,8 @@ carries its tool name); tool **result outputs** are counted here but not by the
|
|
|
360
371
|
server's categories — which is why the residual `other` can look large. Top 5
|
|
361
372
|
are shown. Empty state: `no messages yet`.
|
|
362
373
|
|
|
363
|
-
Live
|
|
364
|
-
|
|
365
|
-
`/context`, `/context-breakdown` and `/context-diff` instead.
|
|
374
|
+
Live on every install path (npm and `file://`). The Desktop has no TUI, so it
|
|
375
|
+
uses `/context`, `/context-breakdown` and `/context-diff` instead.
|
|
366
376
|
|
|
367
377
|
#### Export (redacted)
|
|
368
378
|
|
|
@@ -471,9 +481,8 @@ Notes — these reflect what the plugin API actually exposes today:
|
|
|
471
481
|
therefore delivers everything through the log file, the TUI sidebar, the
|
|
472
482
|
`/cx` session panel and the `/context` / `/context-breakdown` / `/context-diff` slash commands. The sidebar is a TUI-process
|
|
473
483
|
slot and does not exist in the Desktop app; the slash commands work in **both**.
|
|
474
|
-
The TUI sidebar is
|
|
475
|
-
|
|
476
|
-
[Live sidebar: npm vs file:// install](#live-sidebar-npm-vs-file-install).
|
|
484
|
+
The TUI sidebar is live on **every** install path, npm included — see
|
|
485
|
+
[Live sidebar](#live-sidebar).
|
|
477
486
|
* **V1 has no sidebar and no slash command.** The command/slot API (and the
|
|
478
487
|
`./tui` entry) is a V2 feature; OpenCode 1.x has no equivalent, so V1 is
|
|
479
488
|
toast + log only.
|
|
@@ -535,8 +544,8 @@ or add it to `opencode.jsonc`:
|
|
|
535
544
|
```
|
|
536
545
|
|
|
537
546
|
The `./tui` sidebar entry is loaded automatically alongside the main plugin in
|
|
538
|
-
the terminal TUI
|
|
539
|
-
|
|
547
|
+
the terminal TUI, on every install method — see [Live sidebar](#live-sidebar)
|
|
548
|
+
below.
|
|
540
549
|
For a CLI-only setup against remote servers, the package can also be listed in
|
|
541
550
|
[`cli.json`](https://opencode.ai/v2/docs/cli/plugins):
|
|
542
551
|
|
|
@@ -544,45 +553,38 @@ For a CLI-only setup against remote servers, the package can also be listed in
|
|
|
544
553
|
{ "plugins": ["opencode-context-indicator"] }
|
|
545
554
|
```
|
|
546
555
|
|
|
547
|
-
### Live sidebar
|
|
556
|
+
### Live sidebar
|
|
548
557
|
|
|
549
|
-
The TUI sidebar
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
skips the host Solid transform for any path inside a `node_modules` directory, so
|
|
553
|
-
a slot mounted from an npm install renders once and then never live-updates.
|
|
558
|
+
The TUI sidebar and the `/cx` panel are **TUI-process slots**, live on every
|
|
559
|
+
install path — npm (`opencode plugin add opencode-context-indicator`), `file://`
|
|
560
|
+
and the local `plugins/` copy alike.
|
|
554
561
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
/
|
|
564
|
-
every install.
|
|
565
|
-
|
|
566
|
-
#### Getting the live sidebar (`file://` install)
|
|
562
|
+
That used to be false: OpenCode skips its Solid/JSX transform for any file inside
|
|
563
|
+
a `node_modules` directory (upstream `anomalyco/opencode#33884`), so a JSX-built
|
|
564
|
+
slot mounted from an npm install rendered once and then froze on
|
|
565
|
+
"no data yet". Since **1.8.0** the plugin ships **no JSX at all**: the tree is
|
|
566
|
+
built with `jsx()` and getter props (`lib/tui-h.ts`), which needs no transform,
|
|
567
|
+
and the host rewrites the literal `@opentui/solid/jsx-runtime` import to its own
|
|
568
|
+
in-process module. The package therefore also declares **no runtime
|
|
569
|
+
dependencies** — nothing is installed next to it, so there is no second
|
|
570
|
+
Solid/OpenTUI instance to fight with.
|
|
567
571
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
(e.g. `C:\Users\you\plugins\...`, not `.../node_modules/...`).
|
|
573
|
-
2. Point OpenCode at that directory in `~/.config/opencode/opencode.jsonc`:
|
|
572
|
+
| Install | Desktop | TUI commands | TUI live sidebar | TUI panel (`/cx`) |
|
|
573
|
+
| --- | :---: | :---: | :---: | :---: |
|
|
574
|
+
| npm | ✅ | ✅ | ✅ live | ✅ live |
|
|
575
|
+
| `file://` (any path) | ✅ | ✅ | ✅ live | ✅ live |
|
|
574
576
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
"plugins": ["file:///C:/Users/you/plugins/opencode-context-indicator"]
|
|
578
|
-
}
|
|
579
|
-
```
|
|
577
|
+
`/context`, `/context-breakdown`, `/context-diff` and the breakdown log file work
|
|
578
|
+
on every install regardless.
|
|
580
579
|
|
|
581
|
-
|
|
580
|
+
> If a sidebar shows "no data yet" forever on 1.8.0+, that is a bug — please
|
|
581
|
+
> report it with the host version. On 1.7.x and older, the npm path was disabled
|
|
582
|
+
> by design; upgrading is the fix.
|
|
582
583
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
584
|
+
`test/e2e-tui.mjs` Check C proves this end to end: it runs `npm pack`, extracts
|
|
585
|
+
the tarball into a throwaway `node_modules/opencode-context-indicator`, boots an
|
|
586
|
+
isolated `--standalone` TUI from there and asserts that a getter-driven probe
|
|
587
|
+
node actually re-renders (a frozen tree writes the same value forever).
|
|
586
588
|
|
|
587
589
|
### OpenCode 1.x (V1)
|
|
588
590
|
|
|
@@ -599,25 +601,28 @@ Add the package name to the `plugin` array in your `opencode.json`:
|
|
|
599
601
|
Copy `index.js` and the whole `lib/` directory into
|
|
600
602
|
`~/.config/opencode/plugins/`, keeping the `lib/` subdirectory next to the
|
|
601
603
|
plugin file so the plugin's relative imports resolve. The `lib/` folder is
|
|
602
|
-
support code, not a separate plugin entry
|
|
604
|
+
support code, not a separate plugin entry; copy **every** module in it
|
|
605
|
+
(`cache.js`, `commands.js`, `contributors.js`, `dedup.js`, `desktop.js`,
|
|
606
|
+
`estimate.js`, `format.js`, `limits.js`, `rpc.js`, `state.js`, `tui-data.js`,
|
|
607
|
+
`tui-data.d.ts`, `tui-h.ts`, `tui-panel.ts`, `tui-sidebar.ts`, `tui-theme.js`,
|
|
608
|
+
`v1.js`, `v2.js`) — a
|
|
609
|
+
missing module only disables the surface that imports it, but the server part
|
|
610
|
+
stops loading if one of its own imports is absent:
|
|
603
611
|
|
|
604
612
|
```
|
|
605
|
-
~/.config/opencode/plugins/context-indicator.js
|
|
606
|
-
~/.config/opencode/plugins/lib
|
|
607
|
-
~/.config/opencode/plugins/lib
|
|
608
|
-
~/.config/opencode/plugins/lib/dedup.js
|
|
609
|
-
~/.config/opencode/plugins/lib/estimate.js
|
|
610
|
-
~/.config/opencode/plugins/lib/limits.js
|
|
611
|
-
~/.config/opencode/plugins/lib/state.js
|
|
612
|
-
~/.config/opencode/plugins/lib/v1.js
|
|
613
|
-
~/.config/opencode/plugins/lib/v2.js
|
|
613
|
+
~/.config/opencode/plugins/context-indicator.js (this package's index.js)
|
|
614
|
+
~/.config/opencode/plugins/lib/*.js
|
|
615
|
+
~/.config/opencode/plugins/lib/*.ts
|
|
614
616
|
```
|
|
615
617
|
|
|
618
|
+
`tui.ts` (the `./tui` entry, at the package root) is only needed for the
|
|
619
|
+
terminal TUI surfaces; it imports `lib/tui-h.ts`, `lib/tui-sidebar.ts` and
|
|
620
|
+
`lib/tui-panel.ts` by their `.ts` file names, so copy those alongside the `.js`
|
|
621
|
+
modules.
|
|
622
|
+
|
|
616
623
|
To use the sidebar locally as well, point OpenCode at a `file://` copy of the
|
|
617
|
-
package
|
|
618
|
-
[Live sidebar
|
|
619
|
-
is the only install path that keeps the sidebar live. An npm install auto-disables
|
|
620
|
-
the sidebar instead.
|
|
624
|
+
package — the sidebar is live there too, exactly as with an npm install (see
|
|
625
|
+
[Live sidebar](#live-sidebar)).
|
|
621
626
|
|
|
622
627
|
---
|
|
623
628
|
|
|
@@ -633,25 +638,22 @@ the sidebar instead.
|
|
|
633
638
|
indicator degrades
|
|
634
639
|
gracefully (the breakdown falls back to throttled `session.messages` fetches
|
|
635
640
|
and toasts keep working).
|
|
636
|
-
* **OpenCode ≥ 2.2.
|
|
637
|
-
against **2.2.
|
|
638
|
-
**
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
641
|
+
* **OpenCode ≥ 2.2.3** for the V2 path with the TUI surfaces (built and verified
|
|
642
|
+
against **2.2.5**, the latest at the time of writing, which still ships OpenTUI
|
|
643
|
+
**0.8**; the isolated load + state-write e2e also passes on the 2.2.0-2.2.4
|
|
644
|
+
Desktop CLI). The server-only V2 path keeps working on
|
|
645
|
+
2.0.16+. The TUI module is typed against the OpenTUI generation the host ships,
|
|
646
|
+
so a host on an older OpenTUI (0.7 for opencode 2.2.0-2.2.2, 0.6 for
|
|
647
|
+
opencode ≤ 2.1.4) may not load the TUI surfaces - neither the sidebar nor the
|
|
648
|
+
`/cx` panel - from a 1.8.x install.
|
|
642
649
|
* **TUI sidebar**: terminal TUI only, requires the OpenTUI rendering stack that
|
|
643
|
-
ships with OpenCode. `@opentui/core` and `solid-js` are
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
the
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
(upstream: opencode issue #33884 — `node_modules` plugins are excluded from the
|
|
651
|
-
host Solid transform and get an isolated OpenTUI copy). Because of that same
|
|
652
|
-
upstream limitation, on the **npm** path the sidebar is **auto-disabled**
|
|
653
|
-
(the plugin logs one hint and registers no slot) — use a `file://` install for
|
|
654
|
-
the live sidebar, see [Live sidebar: npm vs file:// install](#live-sidebar-npm-vs-file-install).
|
|
650
|
+
ships with OpenCode. `@opentui/core`, `@opentui/solid` and `solid-js` are
|
|
651
|
+
resolved by OpenCode at runtime from its **own** modules (the TUI loader
|
|
652
|
+
rewrites those imports to in-process instances), so the package declares them
|
|
653
|
+
as optional peers only and installs **no runtime dependencies at all**. This is
|
|
654
|
+
what makes the sidebar live for npm installs as well
|
|
655
|
+
(see [Live sidebar](#live-sidebar)); `@opentui/solid` sits in
|
|
656
|
+
`devDependencies` purely for local typechecking.
|
|
655
657
|
* **Desktop GUI part** (optional, experimental): needs an OpenCode Desktop
|
|
656
658
|
release that loads the `./gui-experimental` export (opencode 2.2.0+, where the
|
|
657
659
|
GUI plugin API exists). The bundle is prebuilt and ships in the package, so it
|
|
@@ -724,10 +726,11 @@ caller.
|
|
|
724
726
|
|
|
725
727
|
```bash
|
|
726
728
|
npm run build:gui # build dist/gui-experimental.cjs (required by the gate)
|
|
727
|
-
npm run test:prepublish # fast gate: tarball +
|
|
729
|
+
npm run test:prepublish # fast gate: tarball + JSX-free TUI + resolve + T1-T36 unit checks + GUI bundle
|
|
728
730
|
npm run test:types # tsc for the TUI entry AND the Desktop GUI part
|
|
729
|
-
npm run test:e2e # full E2E:
|
|
731
|
+
npm run test:e2e # full E2E: plugin load + one "Say OK" round-trip + npm-layout liveness
|
|
730
732
|
npm run test:e2e:quick # E2E load-only, no LLM call
|
|
733
|
+
npm run test:e2e -- --check-c # E2E npm-layout liveness only, no LLM call
|
|
731
734
|
npm run test:drift # advisory: warns on non-breaking host drift (also in CI)
|
|
732
735
|
npm run test:drift:strict # release check: fails on ANY host drift
|
|
733
736
|
```
|
|
@@ -736,7 +739,7 @@ npm run test:drift:strict # release check: fails on ANY host drift
|
|
|
736
739
|
(`npm run build:gui && node test/prepublish.mjs && node test/e2e-tui.mjs --full`) and is skipped with
|
|
737
740
|
`--ignore-scripts`. The whole chain takes roughly **1 minute** (fast gate ~20 s,
|
|
738
741
|
isolated E2E ~10 s warm / up to ~90 s cold); the E2E is bounded by INIT 45 s +
|
|
739
|
-
STEP 80 s, ~
|
|
742
|
+
STEP 80 s + PROBE 45 s, ~4 min total.
|
|
740
743
|
|
|
741
744
|
### What the gates check
|
|
742
745
|
|
|
@@ -745,10 +748,10 @@ STEP 80 s, ~3 min total.
|
|
|
745
748
|
| Step | What | Why |
|
|
746
749
|
|---|---|---|
|
|
747
750
|
| (a) tarball composition | `package.json` `files[]` expands to the base files plus every `lib/*` module (count asserted dynamically); `docs/` may contain images only; `dist/` may contain only the built GUI bundle | no test/node_modules/tsconfig in the tarball, and no published export pointing at nothing |
|
|
748
|
-
| (b)
|
|
749
|
-
| (c) resolve chain |
|
|
750
|
-
| (d) unit harness T1-
|
|
751
|
-
| (e) corporate-identifier scan | no forbidden identifiers in the published surface (`index.js`, `tui.
|
|
751
|
+
| (b) JSX-free TUI | no `.tsx`/`.jsx` in the TUI surface (root + `lib/`), every TUI source parses with esbuild `loader:"ts"` (JSX syntax fails), no `@jsx*` pragma, imports stay inside the host-bridgeable allowlist, relative imports carry a file extension | OpenCode skips its Solid transform under `node_modules` (#33884): one JSX file freezes the npm-installed sidebar |
|
|
752
|
+
| (c) resolve chain | walks the real TUI import graph (`resolve-bridge.cjs`): every bare specifier is host-bridged, every relative one resolves to a shipped file, and **no host-bridged module is a production dependency** (a second Solid/OpenTUI instance freezes the surfaces) | an accidental runtime dependency, or a bad local import, silently breaks the sidebar |
|
|
753
|
+
| (d) unit harness T1-T36 | limit/agent/state fix assertions plus measureContributors, redacted-export, shared category-helper, sanitizer/unicode, prompt-cache, waste-detector, compaction-reserve, top-consumer, diff, subagent-rollup and Desktop-RPC checks (86 total; T30 schema-vs-builder drift, T31 registration + handler, T32-T36 fallback-vs-hook category priority across the report, the shared state file and the live log (T36: a counter-less hook entry borrows the snapshot or prints `?`), T25c/T25d compaction-hook arming, T34 cross-instance summary merge) | regressions in modelLimits, writeStateFile, hydrate, contributors, export, subagent lifetime, and a GUI/server wire mismatch |
|
|
754
|
+
| (e) corporate-identifier scan | no forbidden identifiers in the published surface (`index.js`, `tui.ts`, `lib/*`, `gui/*`, `README.md`, `CHANGELOG.md`, `package.json`, the GUI build config) or the test tree | compliance |
|
|
752
755
|
| (f) non-English scan | no non-ASCII letters (`\p{L}` above U+007F) in the same published surface and test tree (`docs/` is image-only and is not text-scanned) | English-only source rule |
|
|
753
756
|
| (g) Desktop GUI bundle | `dist/gui-experimental.cjs` exists and is a non-trivial single-file CJS bundle: no ESM `import`, no dynamic `import()`, no Node built-in, and every app-provided module (`solid-js`, `solid-js/web`, `@opencode/plugin/gui`, `@opencode/plugin/rpc`) still a `require()` - no bundled second copy, no React/OpenTUI/bare-`opencode` import | a stale or hand-edited bundle, or a build that inlines the app instance, breaks the Desktop panel at load time |
|
|
754
757
|
|
|
@@ -758,6 +761,7 @@ STEP 80 s, ~3 min total.
|
|
|
758
761
|
|---|---|---|
|
|
759
762
|
| A — plugin load | spins a private `opencode --standalone` PTY against an **isolated global config** (`OPENCODE_CONFIG_DIR` → `%TEMP%/oci-e2e-config`, sole plugin entry = this checkout's `file://` URL), then asserts **from the host opencode log** (atomic lines, immune to PTY interleaving) that the plugin loaded and its `entrypoint` is **this checkout's `index.js`** — both fail-closed | catches a plugin that no longer imports cleanly, or an isolated config the host did not honor |
|
|
760
763
|
| B — live update | one `opencode run "Say OK" --standalone` round-trip; asserts the LLM answered **and** the isolated `state.json` gained a session with `ctx > 0` | catches a regression in the `session.idle` → `writeStateFile` path |
|
|
764
|
+
| C — npm-layout liveness | `npm pack` → extract into a throwaway `node_modules/opencode-context-indicator` → isolated `--standalone` TUI from there; an env-gated probe node (getter-driven `ref`) must produce three different rendered texts | proves the #33884 workaround: the packaged install stays **reactive** from a `node_modules` path, where the host skips the Solid transform |
|
|
761
765
|
|
|
762
766
|
The E2E is fully **non-invasive** to your running OpenCode Desktop:
|
|
763
767
|
|
|
@@ -774,6 +778,8 @@ The E2E is fully **non-invasive** to your running OpenCode Desktop:
|
|
|
774
778
|
Check B uses your real global config read-only (the round-trip needs the
|
|
775
779
|
provider/model defined there). Your real config is never modified;
|
|
776
780
|
- `OPENCODE_DISABLE_PROJECT_CONFIG=1` avoids project-level config;
|
|
781
|
+
- Check C uses its own private config, XDG roots, DB and state file under
|
|
782
|
+
`%TEMP%/oci-check-c` (nothing is shared with Check A or with your install);
|
|
777
783
|
- any `opencode-cli` instance the test itself spawned is force-killed after each
|
|
778
784
|
check (`serve --service` — your Desktop background service — is never touched);
|
|
779
785
|
- exactly **one** minimal LLM call (`Say OK`) is made, in the isolated session.
|
|
@@ -793,7 +799,7 @@ and on a daily schedule (GitHub auto-parks scheduled workflows after 60 days of
|
|
|
793
799
|
repo inactivity — a push or manual run re-enables them):
|
|
794
800
|
|
|
795
801
|
1. `npx --no-install tsc --noEmit` and `npx --no-install tsc -p tsconfig.gui.json --noEmit`
|
|
796
|
-
- typecheck of the TUI entry (`tui.
|
|
802
|
+
- typecheck of the TUI entry (`tui.ts` + its `lib/*` imports) and of the
|
|
797
803
|
Desktop GUI part (`gui/*.tsx`, Solid JSX);
|
|
798
804
|
2. `npm run build:gui` - build the Desktop GUI bundle before the gate inspects it;
|
|
799
805
|
3. `test/prepublish.mjs` - the full fast gate (a)-(g), including the standalone
|
package/index.js
CHANGED
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
* %TEMP%\opencode-context-indicator-state.json (os.tmpdir())
|
|
118
118
|
* — a machine-readable { sessionID: { model, providerID, ctx, usable,
|
|
119
119
|
* reserve, limit, categories{...}, updatedAt } } snapshot consumed by the
|
|
120
|
-
* optional TUI sidebar in ./tui.
|
|
120
|
+
* optional TUI sidebar in ./tui.ts (package entry "./tui"). Written atomically
|
|
121
121
|
* (temp + rename), keyed by session, bounded to MAX_TRACKED_SESSIONS, and
|
|
122
122
|
* strictly additive: any failure is swallowed and never affects the log.
|
|
123
123
|
*
|
|
@@ -127,11 +127,11 @@
|
|
|
127
127
|
*
|
|
128
128
|
* - Metric: the native opencode overflow count — tokens.total when present,
|
|
129
129
|
* else input + output + cache.read + cache.write — compared against the
|
|
130
|
-
* model's compaction ceiling (opencode calculateCeiling,
|
|
131
|
-
* window =
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
|
|
130
|
+
* model's compaction ceiling (opencode calculateCeiling, released 2.x):
|
|
131
|
+
* window = min(input, context) when both are known, else input || context;
|
|
132
|
+
* reserve = max(15% of window, 16k when window >= 32k);
|
|
133
|
+
* usable = window - reserve. Percentages are NOT
|
|
134
|
+
* clamped at 100% - exceeding the ceiling must stay visible.
|
|
135
135
|
* - Category estimates use a unicode-aware heuristic (see estimateTokens):
|
|
136
136
|
* Cyrillic ~2.5 chars/token, CJK ~1.5, latin/ASCII ~4. These are ESTIMATES,
|
|
137
137
|
* not a tokenizer; "other" is the residual input − sum of estimates.
|
package/lib/contributors.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// lib/contributors.js
|
|
2
2
|
//
|
|
3
|
-
// Pure contributor-measurement helpers extracted from
|
|
3
|
+
// Pure contributor-measurement helpers extracted from the TUI panel (lib/tui-panel.ts)
|
|
4
|
+
// for unit testing.
|
|
4
5
|
// All functions are stateless transforms of their arguments.
|
|
5
6
|
//
|
|
6
7
|
// Functions: asRecord, safeText, measureContributors, contributorLine,
|
|
@@ -185,7 +186,7 @@ function exportFileName(sessionID, now) {
|
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
// Build the redacted export payload. All IO (mkdirSync/writeFileSync) and
|
|
188
|
-
// toasts stay in tui.
|
|
189
|
+
// toasts stay in lib/tui-panel.ts as the thin exportRedacted() wrapper.
|
|
189
190
|
//
|
|
190
191
|
// Fields: generatedAt, session{id8-prefixed id, model, providerID},
|
|
191
192
|
// family[role/model/ctx/usable/limit/pct/updated], contributors[kind/tool/ref/
|
|
@@ -193,7 +194,7 @@ function exportFileName(sessionID, now) {
|
|
|
193
194
|
// content, NO contributor preview text.
|
|
194
195
|
//
|
|
195
196
|
// panelDenom / panelRole are passed as parameters so this file stays pure and
|
|
196
|
-
// tui.
|
|
197
|
+
// lib/tui-panel.ts controls their source.
|
|
197
198
|
function buildRedactedPayload({ sessionID, root, rows, contributors, now, panelDenom, panelRole, num }) {
|
|
198
199
|
const id8 = String(sessionID).slice(0, 8);
|
|
199
200
|
return {
|
|
@@ -227,7 +228,7 @@ function buildRedactedPayload({ sessionID, root, rows, contributors, now, panelD
|
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
// ---------------------------------------------------------------------------
|
|
230
|
-
// Panel helpers (pure, shared with tui.
|
|
231
|
+
// Panel helpers (pure, shared with lib/tui-panel.ts)
|
|
231
232
|
// ---------------------------------------------------------------------------
|
|
232
233
|
|
|
233
234
|
// Per-row denominator with the same fallback cascade as the server table:
|