@osovv/vv-opencode 1.1.0 → 1.1.2

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +26 -11
  3. package/dist/commands/doctor.js +11 -1
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/init.js +14 -5
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/install.js +6 -3
  8. package/dist/commands/install.js.map +1 -1
  9. package/dist/commands/launch.d.ts +1 -0
  10. package/dist/commands/launch.js +20 -8
  11. package/dist/commands/launch.js.map +1 -1
  12. package/dist/commands/status.js +12 -2
  13. package/dist/commands/status.js.map +1 -1
  14. package/dist/commands/sync.js +6 -3
  15. package/dist/commands/sync.js.map +1 -1
  16. package/dist/lib/config-layers.d.ts +6 -0
  17. package/dist/lib/config-layers.js +65 -3
  18. package/dist/lib/config-layers.js.map +1 -1
  19. package/dist/lib/opencode.d.ts +34 -1
  20. package/dist/lib/opencode.js +249 -12
  21. package/dist/lib/opencode.js.map +1 -1
  22. package/dist/lib/plugin-toggle-config.d.ts +1 -1
  23. package/dist/lib/plugin-toggle-config.js +2 -0
  24. package/dist/lib/plugin-toggle-config.js.map +1 -1
  25. package/dist/tui/context/analyze.d.ts +4 -0
  26. package/dist/tui/context/analyze.js +256 -0
  27. package/dist/tui/context/analyze.js.map +1 -0
  28. package/dist/tui/context/collect.d.ts +3 -0
  29. package/dist/tui/context/collect.js +94 -0
  30. package/dist/tui/context/collect.js.map +1 -0
  31. package/dist/tui/context/estimate.d.ts +2 -0
  32. package/dist/tui/context/estimate.js +49 -0
  33. package/dist/tui/context/estimate.js.map +1 -0
  34. package/dist/tui/context/plugin.d.ts +10 -0
  35. package/dist/tui/context/plugin.js +91 -0
  36. package/dist/tui/context/plugin.js.map +1 -0
  37. package/dist/tui/context/types.d.ts +61 -0
  38. package/dist/tui/context/types.js +25 -0
  39. package/dist/tui/context/types.js.map +1 -0
  40. package/dist/tui/context/view.d.ts +3 -0
  41. package/dist/tui/context/view.js +45 -0
  42. package/dist/tui/context/view.js.map +1 -0
  43. package/dist/tui.d.ts +7 -0
  44. package/dist/tui.js +27 -0
  45. package/dist/tui.js.map +1 -0
  46. package/package.json +18 -5
  47. package/schemas/vvoc/v3.json +1 -1
@@ -1,4 +1,4 @@
1
- export declare const PLUGIN_TOGGLE_NAMES: readonly ["guardian", "hashline-edit", "model-roles", "system-context-injection", "workflow", "secrets-redaction"];
1
+ export declare const PLUGIN_TOGGLE_NAMES: readonly ["guardian", "hashline-edit", "model-roles", "system-context-injection", "workflow", "secrets-redaction", "context"];
2
2
  export type VvocPluginToggleConfig = Record<string, boolean>;
3
3
  export declare function createDefaultPluginToggleConfig(): VvocPluginToggleConfig;
4
4
  export declare function isPluginEnabled(config: {
@@ -18,6 +18,7 @@
18
18
  // END_MODULE_MAP
19
19
  //
20
20
  // START_CHANGE_SUMMARY
21
+ // LAST_CHANGE: [C-CONTEXT-TUI-PLUGIN - Added the default-enabled context TUI plugin toggle.]
21
22
  // LAST_CHANGE: [v1.2.0 - Made plugin toggle checks pure over the already-loaded vvoc config snapshot.]
22
23
  // LAST_CHANGE: [v1.1.0 - Loaded plugin toggles from the effective vvoc config source.]
23
24
  // LAST_CHANGE: [v1.0.0 - Initial implementation for runtime plugin toggle.]
@@ -30,6 +31,7 @@ export const PLUGIN_TOGGLE_NAMES = [
30
31
  "system-context-injection",
31
32
  "workflow",
32
33
  "secrets-redaction",
34
+ "context",
33
35
  ];
34
36
  // END_BLOCK_CONSTANTS
35
37
  // START_BLOCK_DEFAULT_CONFIG
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-toggle-config.js","sourceRoot":"","sources":["../../src/lib/plugin-toggle-config.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,iBAAiB;AACjB,wBAAwB;AACxB,+IAA+I;AAC/I,0GAA0G;AAC1G,oBAAoB;AACpB,kDAAkD;AAClD,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,uEAAuE;AACvE,qEAAqE;AACrE,2FAA2F;AAC3F,kGAAkG;AAClG,+EAA+E;AAC/E,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,yGAAyG;AACzG,yFAAyF;AACzF,8EAA8E;AAC9E,qBAAqB;AAErB,wBAAwB;AACxB,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,UAAU;IACV,eAAe;IACf,aAAa;IACb,0BAA0B;IAC1B,UAAU;IACV,mBAAmB;CACX,CAAC;AAGX,sBAAsB;AAEtB,6BAA6B;AAC7B,MAAM,UAAU,+BAA+B;IAC7C,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,mBAAmB,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AACD,2BAA2B;AAE3B,gCAAgC;AAChC,kCAAkC;AAClC,0FAA0F;AAC1F,sKAAsK;AACtK,wHAAwH;AACxH,uBAAuB;AACvB,0BAA0B;AAC1B,gCAAgC;AAChC,MAAM,UAAU,eAAe,CAC7B,MAA4C,EAC5C,UAAkB;IAElB,6BAA6B;IAC7B,2BAA2B;IAE3B,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC;IACjD,2DAA2D;IAC3D,IAAI,OAAO,WAAW,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CACT,sEAAsE;YACpE,UAAU;YACV,YAAY;YACZ,WAAW,CACd,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAC;AACnD,8BAA8B"}
1
+ {"version":3,"file":"plugin-toggle-config.js","sourceRoot":"","sources":["../../src/lib/plugin-toggle-config.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,iBAAiB;AACjB,wBAAwB;AACxB,+IAA+I;AAC/I,0GAA0G;AAC1G,oBAAoB;AACpB,kDAAkD;AAClD,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,uEAAuE;AACvE,qEAAqE;AACrE,2FAA2F;AAC3F,kGAAkG;AAClG,+EAA+E;AAC/E,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,+FAA+F;AAC/F,yGAAyG;AACzG,yFAAyF;AACzF,8EAA8E;AAC9E,qBAAqB;AAErB,wBAAwB;AACxB,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,UAAU;IACV,eAAe;IACf,aAAa;IACb,0BAA0B;IAC1B,UAAU;IACV,mBAAmB;IACnB,SAAS;CACD,CAAC;AAGX,sBAAsB;AAEtB,6BAA6B;AAC7B,MAAM,UAAU,+BAA+B;IAC7C,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,mBAAmB,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AACD,2BAA2B;AAE3B,gCAAgC;AAChC,kCAAkC;AAClC,0FAA0F;AAC1F,sKAAsK;AACtK,wHAAwH;AACxH,uBAAuB;AACvB,0BAA0B;AAC1B,gCAAgC;AAChC,MAAM,UAAU,eAAe,CAC7B,MAA4C,EAC5C,UAAkB;IAElB,6BAA6B;IAC7B,2BAA2B;IAE3B,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC;IACjD,2DAA2D;IAC3D,IAAI,OAAO,WAAW,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CACT,sEAAsE;YACpE,UAAU;YACV,YAAY;YACZ,WAAW,CACd,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAC;AACnD,8BAA8B"}
@@ -0,0 +1,4 @@
1
+ import type { Message } from "@opencode-ai/sdk/v2";
2
+ import type { ContextAnalysis, ContextAnalysisInput } from "./types.js";
3
+ export declare function analyzeContext(input: ContextAnalysisInput): ContextAnalysis;
4
+ export declare function selectActiveMessages(messages: readonly Message[]): readonly Message[];
@@ -0,0 +1,256 @@
1
+ // FILE: src/tui/context/analyze.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Derive a measured-versus-estimated active context breakdown from observable OpenCode session data.
5
+ // SCOPE: Compaction cutoff, provider usage baseline, model capacity, skill/tool/message categorization, residual unknown context, and MCP summary preservation.
6
+ // DEPENDS: [@opencode-ai/sdk/v2, src/tui/context/estimate.ts, src/tui/context/types.ts]
7
+ // LINKS: [M-PLUGIN-CONTEXT-TUI, DF-CONTEXT-INSPECTION, V-M-PLUGIN-CONTEXT-TUI]
8
+ // ROLE: CORE_LOGIC
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // analyzeContext - Produce the complete context analysis rendered by the TUI plugin.
14
+ // selectActiveMessages - Keep only the latest compaction summary and subsequent turns.
15
+ // END_MODULE_MAP
16
+ //
17
+ // START_CHANGE_SUMMARY
18
+ // LAST_CHANGE: [C-CONTEXT-TUI-PLUGIN - Added honest active-context categorization and provider residual accounting.]
19
+ // END_CHANGE_SUMMARY
20
+ import { estimateTextTokens, estimateValueTokens } from "./estimate.js";
21
+ const BUILTIN_TOOL_IDS = new Set([
22
+ "bash",
23
+ "glob",
24
+ "grep",
25
+ "list",
26
+ "read",
27
+ "question",
28
+ "skill",
29
+ "task",
30
+ "todowrite",
31
+ "webfetch",
32
+ "write",
33
+ ]);
34
+ const VVOC_TOOL_IDS = new Set(["edit", "work_item_open", "work_item_list", "work_item_close"]);
35
+ const CATEGORY_LABELS = {
36
+ system: "Agent/system instructions",
37
+ "skill-catalog": "Skill catalog",
38
+ "loaded-skills": "Loaded skill results",
39
+ "builtin-tool-schemas": "Built-in tool schemas",
40
+ "vvoc-tool-schemas": "vvoc tool schemas",
41
+ "external-tool-schemas": "External/plugin/MCP schemas",
42
+ "user-messages": "User messages",
43
+ "assistant-messages": "Assistant messages",
44
+ "tool-results": "Tool calls and results",
45
+ files: "Files and attachments",
46
+ "compacted-summary": "Compacted summary",
47
+ "provider-only": "Unknown/provider-only",
48
+ };
49
+ // START_BLOCK_CONTEXT_ANALYSIS
50
+ export function analyzeContext(input) {
51
+ const activeMessages = selectActiveMessages(input.messages);
52
+ const activeMessageIDs = new Set(activeMessages.map((message) => message.id));
53
+ const activeParts = input.parts.filter((part) => activeMessageIDs.has(part.messageID));
54
+ const latestAssistant = findLatestAssistant(activeMessages);
55
+ const latestUser = findLatestUser(activeMessages);
56
+ const currentAgent = latestAssistant?.agent ?? latestUser?.agent;
57
+ const counters = createCategoryCounter();
58
+ const agentPrompt = input.agents.find((agent) => agent.name === currentAgent)?.prompt;
59
+ counters.system += estimateTextTokens(agentPrompt);
60
+ counters.system += estimateTextTokens(latestUser?.system);
61
+ for (const skill of input.skills) {
62
+ counters["skill-catalog"] += estimateValueTokens({
63
+ name: skill.name,
64
+ description: skill.description,
65
+ location: skill.location,
66
+ });
67
+ }
68
+ for (const tool of input.tools) {
69
+ counters[classifyToolSchema(tool)] += estimateValueTokens({
70
+ id: tool.id,
71
+ description: tool.description,
72
+ parameters: tool.parameters,
73
+ });
74
+ }
75
+ const messageByID = new Map(activeMessages.map((message) => [message.id, message]));
76
+ for (const part of activeParts) {
77
+ countPart(part, messageByID.get(part.messageID), counters);
78
+ }
79
+ const categories = buildKnownCategories(counters);
80
+ const estimatedKnownTokens = categories.reduce((total, category) => total + category.estimatedTokens, 0);
81
+ const measured = latestAssistant
82
+ ? createMeasuredUsage(latestAssistant, input.model?.contextLimit)
83
+ : undefined;
84
+ const providerOnlyTokens = measured ? Math.max(0, measured.usedTokens - estimatedKnownTokens) : 0;
85
+ const estimationDriftTokens = measured
86
+ ? Math.max(0, estimatedKnownTokens - measured.usedTokens)
87
+ : 0;
88
+ if (providerOnlyTokens > 0) {
89
+ categories.push({
90
+ id: "provider-only",
91
+ label: CATEGORY_LABELS["provider-only"],
92
+ estimatedTokens: providerOnlyTokens,
93
+ detail: "Measured provider usage not attributable through public TUI/SDK data",
94
+ source: "provider-residual",
95
+ });
96
+ }
97
+ return {
98
+ sessionID: input.sessionID,
99
+ agent: currentAgent,
100
+ model: input.model,
101
+ measured,
102
+ categories,
103
+ estimatedKnownTokens,
104
+ estimatedTotalTokens: estimatedKnownTokens + providerOnlyTokens,
105
+ estimationDriftTokens,
106
+ compacted: activeMessages.length < input.messages.length,
107
+ activeMessageCount: activeMessages.length,
108
+ mcpServers: [...input.mcpServers],
109
+ warnings: [...(input.warnings ?? [])],
110
+ };
111
+ }
112
+ export function selectActiveMessages(messages) {
113
+ let summaryIndex = -1;
114
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
115
+ const message = messages[index];
116
+ if (message?.role === "assistant" && message.summary === true) {
117
+ summaryIndex = index;
118
+ break;
119
+ }
120
+ }
121
+ return summaryIndex >= 0 ? messages.slice(summaryIndex) : messages;
122
+ }
123
+ // END_BLOCK_CONTEXT_ANALYSIS
124
+ // START_BLOCK_CATEGORY_COUNTING
125
+ function createCategoryCounter() {
126
+ return {
127
+ system: 0,
128
+ "skill-catalog": 0,
129
+ "loaded-skills": 0,
130
+ "builtin-tool-schemas": 0,
131
+ "vvoc-tool-schemas": 0,
132
+ "external-tool-schemas": 0,
133
+ "user-messages": 0,
134
+ "assistant-messages": 0,
135
+ "tool-results": 0,
136
+ files: 0,
137
+ "compacted-summary": 0,
138
+ };
139
+ }
140
+ function classifyToolSchema(tool) {
141
+ if (VVOC_TOOL_IDS.has(tool.id))
142
+ return "vvoc-tool-schemas";
143
+ if (BUILTIN_TOOL_IDS.has(tool.id))
144
+ return "builtin-tool-schemas";
145
+ return "external-tool-schemas";
146
+ }
147
+ function countPart(part, message, counters) {
148
+ switch (part.type) {
149
+ case "text": {
150
+ if (part.ignored)
151
+ return;
152
+ if (message?.role === "user") {
153
+ counters["user-messages"] += estimateTextTokens(part.text);
154
+ }
155
+ else if (message?.role === "assistant" && message.summary) {
156
+ counters["compacted-summary"] += estimateTextTokens(part.text);
157
+ }
158
+ else {
159
+ counters["assistant-messages"] += estimateTextTokens(part.text);
160
+ }
161
+ return;
162
+ }
163
+ case "reasoning":
164
+ counters["assistant-messages"] += estimateTextTokens(part.text);
165
+ return;
166
+ case "subtask":
167
+ counters["assistant-messages"] += estimateValueTokens({
168
+ prompt: part.prompt,
169
+ description: part.description,
170
+ agent: part.agent,
171
+ });
172
+ return;
173
+ case "agent":
174
+ counters["user-messages"] += estimateValueTokens({
175
+ name: part.name,
176
+ source: part.source?.value,
177
+ });
178
+ return;
179
+ case "file":
180
+ counters.files += estimateValueTokens({
181
+ filename: part.filename,
182
+ mime: part.mime,
183
+ source: part.source?.text.value,
184
+ url: part.url.startsWith("data:") ? undefined : part.url,
185
+ });
186
+ return;
187
+ case "tool":
188
+ countToolPart(part, counters);
189
+ return;
190
+ default:
191
+ return;
192
+ }
193
+ }
194
+ function countToolPart(part, counters) {
195
+ const state = part.state;
196
+ const payload = {
197
+ tool: part.tool,
198
+ input: state.input,
199
+ output: state.status === "completed" ? state.output : undefined,
200
+ error: state.status === "error" ? state.error : undefined,
201
+ };
202
+ const target = part.tool === "skill" ? "loaded-skills" : "tool-results";
203
+ counters[target] += estimateValueTokens(payload);
204
+ if (state.status === "completed") {
205
+ for (const attachment of state.attachments ?? []) {
206
+ counters.files += estimateValueTokens({
207
+ filename: attachment.filename,
208
+ mime: attachment.mime,
209
+ source: attachment.source?.text.value,
210
+ });
211
+ }
212
+ }
213
+ }
214
+ function buildKnownCategories(counters) {
215
+ return Object.entries(counters)
216
+ .filter(([, estimatedTokens]) => estimatedTokens > 0)
217
+ .map(([id, estimatedTokens]) => ({
218
+ id,
219
+ label: CATEGORY_LABELS[id],
220
+ estimatedTokens,
221
+ source: "estimated",
222
+ }));
223
+ }
224
+ // END_BLOCK_CATEGORY_COUNTING
225
+ // START_BLOCK_MEASURED_USAGE
226
+ function createMeasuredUsage(message, contextLimit) {
227
+ const usedTokens = message.tokens.input + message.tokens.cache.read + message.tokens.output;
228
+ const normalizedLimit = contextLimit && contextLimit > 0 ? contextLimit : undefined;
229
+ return {
230
+ usedTokens,
231
+ contextLimit: normalizedLimit,
232
+ remainingTokens: normalizedLimit === undefined ? undefined : Math.max(0, normalizedLimit - usedTokens),
233
+ percentUsed: normalizedLimit === undefined ? undefined : (usedTokens / normalizedLimit) * 100,
234
+ inputTokens: message.tokens.input,
235
+ cacheReadTokens: message.tokens.cache.read,
236
+ outputTokens: message.tokens.output,
237
+ };
238
+ }
239
+ function findLatestAssistant(messages) {
240
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
241
+ const message = messages[index];
242
+ if (message?.role === "assistant")
243
+ return message;
244
+ }
245
+ return undefined;
246
+ }
247
+ function findLatestUser(messages) {
248
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
249
+ const message = messages[index];
250
+ if (message?.role === "user")
251
+ return message;
252
+ }
253
+ return undefined;
254
+ }
255
+ // END_BLOCK_MEASURED_USAGE
256
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../../src/tui/context/analyze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
2
+ import type { ContextAnalysis } from "./types.js";
3
+ export declare function collectContextAnalysis(api: TuiPluginApi, sessionID: string): Promise<ContextAnalysis>;
@@ -0,0 +1,94 @@
1
+ // FILE: src/tui/context/collect.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Collect bounded observable session, skill, agent, tool, model, and MCP data from the OpenCode TUI API.
5
+ // SCOPE: Active session snapshots, safe SDK lookups, provider/model limit resolution, and bounded warning capture.
6
+ // DEPENDS: [@opencode-ai/plugin/tui, @opencode-ai/sdk/v2, src/tui/context/analyze.ts, src/tui/context/types.ts]
7
+ // LINKS: [M-PLUGIN-CONTEXT-TUI, DF-CONTEXT-INSPECTION, V-M-PLUGIN-CONTEXT-TUI]
8
+ // ROLE: INTEGRATION
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // collectContextAnalysis - Read current TUI state and SDK catalogs, then invoke the pure analyzer.
14
+ // END_MODULE_MAP
15
+ //
16
+ // START_CHANGE_SUMMARY
17
+ // LAST_CHANGE: [C-CONTEXT-TUI-PLUGIN - Added fail-soft OpenCode TUI context collection.]
18
+ // END_CHANGE_SUMMARY
19
+ import { analyzeContext } from "./analyze.js";
20
+ // START_BLOCK_CONTEXT_COLLECTION
21
+ export async function collectContextAnalysis(api, sessionID) {
22
+ const messages = [...api.state.session.messages(sessionID)];
23
+ const parts = messages.flatMap((message) => [...api.state.part(message.id)]);
24
+ const warnings = [];
25
+ const directory = api.state.path.directory;
26
+ const modelRef = resolveModelReference(messages);
27
+ const [agents, skills, tools] = await Promise.all([
28
+ readCatalog("agents", () => api.client.app.agents({ directory }), [], warnings),
29
+ readCatalog("skills", () => api.client.app.skills({ directory }), [], warnings),
30
+ modelRef
31
+ ? readCatalog("tool schemas", () => api.client.tool.list({
32
+ directory,
33
+ provider: modelRef.providerID,
34
+ model: modelRef.modelID,
35
+ }), [], warnings)
36
+ : Promise.resolve([]),
37
+ ]);
38
+ const provider = modelRef
39
+ ? api.state.provider.find((candidate) => candidate.id === modelRef.providerID)
40
+ : undefined;
41
+ const model = modelRef ? provider?.models[modelRef.modelID] : undefined;
42
+ return analyzeContext({
43
+ sessionID,
44
+ messages,
45
+ parts,
46
+ agents,
47
+ skills,
48
+ tools,
49
+ mcpServers: api.state.mcp().map((server) => ({
50
+ name: server.name,
51
+ status: server.status,
52
+ error: server.error,
53
+ })),
54
+ model: modelRef
55
+ ? {
56
+ providerID: modelRef.providerID,
57
+ modelID: modelRef.modelID,
58
+ name: model?.name,
59
+ contextLimit: model?.limit.context,
60
+ outputLimit: model?.limit.output,
61
+ }
62
+ : undefined,
63
+ warnings,
64
+ });
65
+ }
66
+ async function readCatalog(label, load, fallback, warnings) {
67
+ try {
68
+ const response = await load();
69
+ if (response.data !== undefined)
70
+ return response.data;
71
+ warnings.push(`${label}: unavailable`);
72
+ }
73
+ catch (error) {
74
+ warnings.push(`${label}: ${boundedError(error)}`);
75
+ }
76
+ return fallback;
77
+ }
78
+ function resolveModelReference(messages) {
79
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
80
+ const message = messages[index];
81
+ if (!message)
82
+ continue;
83
+ if (message.role === "assistant") {
84
+ return { providerID: message.providerID, modelID: message.modelID };
85
+ }
86
+ return message.model;
87
+ }
88
+ return undefined;
89
+ }
90
+ function boundedError(error) {
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ return message.length > 160 ? `${message.slice(0, 157)}...` : message;
93
+ }
94
+ //# sourceMappingURL=collect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.js","sourceRoot":"","sources":["../../../src/tui/context/collect.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,iBAAiB;AACjB,wBAAwB;AACxB,oHAAoH;AACpH,qHAAqH;AACrH,kHAAkH;AAClH,iFAAiF;AACjF,sBAAsB;AACtB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,qGAAqG;AACrG,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,2FAA2F;AAC3F,qBAAqB;AAIrB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,iCAAiC;AACjC,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAiB,EACjB,SAAiB;IAEjB,MAAM,QAAQ,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;IAC3C,MAAM,QAAQ,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAEjD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAChD,WAAW,CAAU,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC;QACxF,WAAW,CAAiB,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC;QAC/F,QAAQ;YACN,CAAC,CAAC,WAAW,CACT,cAAc,EACd,GAAG,EAAE,CACH,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;gBACnB,SAAS;gBACT,QAAQ,EAAE,QAAQ,CAAC,UAAU;gBAC7B,KAAK,EAAE,QAAQ,CAAC,OAAO;aACxB,CAAC,EACJ,EAAE,EACF,QAAQ,CACT;YACH,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;KACxB,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,QAAQ;QACvB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,QAAQ,CAAC,UAAU,CAAC;QAC9E,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAExE,OAAO,cAAc,CAAC;QACpB,SAAS;QACT,QAAQ;QACR,KAAK;QACL,MAAM;QACN,MAAM;QACN,KAAK;QACL,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC3C,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAC;QACH,KAAK,EAAE,QAAQ;YACb,CAAC,CAAC;gBACE,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,IAAI,EAAE,KAAK,EAAE,IAAI;gBACjB,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO;gBAClC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM;aACjC;YACH,CAAC,CAAC,SAAS;QACb,QAAQ;KACT,CAAC,CAAC;AACL,CAAC;AAKD,KAAK,UAAU,WAAW,CACxB,KAAa,EACb,IAA8B,EAC9B,QAAW,EACX,QAAkB;IAElB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC;QAC9B,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC,IAAI,CAAC;QACtD,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,eAAe,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAkE;IAC/F,KAAK,IAAI,KAAK,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACjC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QACtE,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC;IACvB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AACxE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function estimateTextTokens(value: string | undefined): number;
2
+ export declare function estimateValueTokens(value: unknown): number;
@@ -0,0 +1,49 @@
1
+ // FILE: src/tui/context/estimate.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Provide deterministic, provider-neutral token approximations for context category comparison.
5
+ // SCOPE: Plain text and JSON-compatible value estimation only; no provider-specific tokenizer claims.
6
+ // DEPENDS: [none]
7
+ // LINKS: [M-PLUGIN-CONTEXT-TUI, V-M-PLUGIN-CONTEXT-TUI]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // estimateTextTokens - Approximate text tokens with separate ASCII and non-ASCII weighting.
14
+ // estimateValueTokens - Serialize a JSON-compatible value and estimate its token count.
15
+ // END_MODULE_MAP
16
+ //
17
+ // START_CHANGE_SUMMARY
18
+ // LAST_CHANGE: [C-CONTEXT-TUI-PLUGIN - Added deterministic provider-neutral context estimates.]
19
+ // END_CHANGE_SUMMARY
20
+ // START_BLOCK_TOKEN_ESTIMATION
21
+ export function estimateTextTokens(value) {
22
+ if (!value?.trim())
23
+ return 0;
24
+ let ascii = 0;
25
+ let nonAscii = 0;
26
+ for (const character of value) {
27
+ if (character.codePointAt(0) <= 0x7f) {
28
+ ascii += 1;
29
+ }
30
+ else {
31
+ nonAscii += 1;
32
+ }
33
+ }
34
+ return Math.max(1, Math.ceil(ascii / 4 + nonAscii));
35
+ }
36
+ export function estimateValueTokens(value) {
37
+ if (value === undefined || value === null)
38
+ return 0;
39
+ if (typeof value === "string")
40
+ return estimateTextTokens(value);
41
+ try {
42
+ return estimateTextTokens(JSON.stringify(value));
43
+ }
44
+ catch {
45
+ return estimateTextTokens(String(value));
46
+ }
47
+ }
48
+ // END_BLOCK_TOKEN_ESTIMATION
49
+ //# sourceMappingURL=estimate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estimate.js","sourceRoot":"","sources":["../../../src/tui/context/estimate.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,iBAAiB;AACjB,wBAAwB;AACxB,2GAA2G;AAC3G,wGAAwG;AACxG,oBAAoB;AACpB,0DAA0D;AAC1D,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,8FAA8F;AAC9F,0FAA0F;AAC1F,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,kGAAkG;AAClG,qBAAqB;AAErB,+BAA+B;AAC/B,MAAM,UAAU,kBAAkB,CAAC,KAAyB;IAC1D,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,OAAO,CAAC,CAAC;IAE7B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;QAC9B,IAAI,SAAS,CAAC,WAAW,CAAC,CAAC,CAAE,IAAI,IAAI,EAAE,CAAC;YACtC,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;aAAM,CAAC;YACN,QAAQ,IAAI,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAEhE,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AACD,6BAA6B"}
@@ -0,0 +1,10 @@
1
+ import type { PluginOptions } from "@opencode-ai/plugin";
2
+ import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui";
3
+ import type { ContextAnalysis } from "./types.js";
4
+ export type ContextTuiDependencies = {
5
+ enabled: (api: TuiPluginApi) => Promise<boolean>;
6
+ collect: (api: TuiPluginApi, sessionID: string) => Promise<ContextAnalysis>;
7
+ open: (api: TuiPluginApi, analysis: ContextAnalysis) => void;
8
+ };
9
+ export declare function registerContextTuiPlugin(api: TuiPluginApi, options: PluginOptions | undefined, dependencies?: ContextTuiDependencies): Promise<void>;
10
+ export declare const ContextTuiPlugin: TuiPlugin;
@@ -0,0 +1,91 @@
1
+ // FILE: src/tui/context/plugin.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Register the modern OpenCode /context TUI command and connect config toggles, collection, and rendering.
5
+ // SCOPE: Plugin enablement, active-session gating, keymap command registration, lifecycle cleanup, and bounded error toasts.
6
+ // DEPENDS: [@opencode-ai/plugin/tui, src/lib/config-layers.ts, src/lib/plugin-toggle-config.ts, src/tui/context/collect.ts, src/tui/context/view.tsx]
7
+ // LINKS: [M-PLUGIN-CONTEXT-TUI, M-CONFIG-LAYERS, M-PLUGIN-TOGGLE-CONFIG, DF-CONTEXT-INSPECTION, V-M-PLUGIN-CONTEXT-TUI]
8
+ // ROLE: INTEGRATION
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // registerContextTuiPlugin - Register /context with injectable dependencies for focused tests.
14
+ // ContextTuiPlugin - Default production TUI plugin factory.
15
+ // END_MODULE_MAP
16
+ //
17
+ // START_CHANGE_SUMMARY
18
+ // LAST_CHANGE: [C-CONTEXT-TUI-PLUGIN - Added modern slash command registration and fail-soft dialog execution.]
19
+ // END_CHANGE_SUMMARY
20
+ import { loadVvocConfigForRead } from "../../lib/config-layers.js";
21
+ import { isVvocPluginEnabled } from "../../lib/plugin-toggle-config.js";
22
+ import { collectContextAnalysis } from "./collect.js";
23
+ import { openContextDialog } from "./view.js";
24
+ const CONTEXT_COMMAND = "vvoc.context.show";
25
+ const DEFAULT_DEPENDENCIES = {
26
+ enabled: async (api) => {
27
+ const vvoc = await loadVvocConfigForRead({
28
+ scope: "effective",
29
+ allowDefault: true,
30
+ cwd: api.state.path.directory,
31
+ });
32
+ return isVvocPluginEnabled(vvoc.config, "context");
33
+ },
34
+ collect: collectContextAnalysis,
35
+ open: openContextDialog,
36
+ };
37
+ // START_BLOCK_CONTEXT_COMMAND_REGISTRATION
38
+ export async function registerContextTuiPlugin(api, options, dependencies = DEFAULT_DEPENDENCIES) {
39
+ if (options?.enabled === false || !(await dependencies.enabled(api)))
40
+ return;
41
+ const unregister = api.keymap.registerLayer({
42
+ commands: [
43
+ {
44
+ name: CONTEXT_COMMAND,
45
+ title: "Context usage",
46
+ description: "Show measured context usage and an approximate source breakdown",
47
+ category: "VVOC",
48
+ namespace: "palette",
49
+ slashName: "context",
50
+ enabled: () => api.route.current.name === "session",
51
+ async run() {
52
+ const route = api.route.current;
53
+ const sessionID = route.name === "session" &&
54
+ "params" in route &&
55
+ typeof route.params?.sessionID === "string"
56
+ ? route.params.sessionID
57
+ : undefined;
58
+ if (!sessionID) {
59
+ api.ui.toast({
60
+ variant: "warning",
61
+ title: "Context usage",
62
+ message: "Open a session before running /context.",
63
+ });
64
+ return;
65
+ }
66
+ try {
67
+ const analysis = await dependencies.collect(api, sessionID);
68
+ dependencies.open(api, analysis);
69
+ }
70
+ catch (error) {
71
+ api.ui.toast({
72
+ variant: "error",
73
+ title: "Context usage unavailable",
74
+ message: boundedError(error),
75
+ });
76
+ }
77
+ },
78
+ },
79
+ ],
80
+ });
81
+ api.lifecycle.onDispose(unregister);
82
+ }
83
+ // END_BLOCK_CONTEXT_COMMAND_REGISTRATION
84
+ export const ContextTuiPlugin = async (api, options) => {
85
+ await registerContextTuiPlugin(api, options);
86
+ };
87
+ function boundedError(error) {
88
+ const message = error instanceof Error ? error.message : String(error);
89
+ return message.length > 180 ? `${message.slice(0, 177)}...` : message;
90
+ }
91
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/tui/context/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import type { Agent, Message, Part, ToolListItem } from "@opencode-ai/sdk/v2";
2
+ export type ContextSkill = {
3
+ name: string;
4
+ description?: string;
5
+ location: string;
6
+ content: string;
7
+ };
8
+ export type ContextMcpServer = {
9
+ name: string;
10
+ status: string;
11
+ error?: string;
12
+ };
13
+ export type ContextModel = {
14
+ providerID: string;
15
+ modelID: string;
16
+ name?: string;
17
+ contextLimit?: number;
18
+ outputLimit?: number;
19
+ };
20
+ export type ContextAnalysisInput = {
21
+ sessionID: string;
22
+ messages: readonly Message[];
23
+ parts: readonly Part[];
24
+ agents: readonly Agent[];
25
+ skills: readonly ContextSkill[];
26
+ tools: readonly ToolListItem[];
27
+ mcpServers: readonly ContextMcpServer[];
28
+ model?: ContextModel;
29
+ warnings?: readonly string[];
30
+ };
31
+ export type ContextCategoryId = "system" | "skill-catalog" | "loaded-skills" | "builtin-tool-schemas" | "vvoc-tool-schemas" | "external-tool-schemas" | "user-messages" | "assistant-messages" | "tool-results" | "files" | "compacted-summary" | "provider-only";
32
+ export type ContextCategory = {
33
+ id: ContextCategoryId;
34
+ label: string;
35
+ estimatedTokens: number;
36
+ detail?: string;
37
+ source: "estimated" | "provider-residual";
38
+ };
39
+ export type ContextMeasuredUsage = {
40
+ usedTokens: number;
41
+ contextLimit?: number;
42
+ remainingTokens?: number;
43
+ percentUsed?: number;
44
+ inputTokens: number;
45
+ cacheReadTokens: number;
46
+ outputTokens: number;
47
+ };
48
+ export type ContextAnalysis = {
49
+ sessionID: string;
50
+ agent?: string;
51
+ model?: ContextModel;
52
+ measured?: ContextMeasuredUsage;
53
+ categories: ContextCategory[];
54
+ estimatedKnownTokens: number;
55
+ estimatedTotalTokens: number;
56
+ estimationDriftTokens: number;
57
+ compacted: boolean;
58
+ activeMessageCount: number;
59
+ mcpServers: ContextMcpServer[];
60
+ warnings: string[];
61
+ };