opencode-codex-memory 0.6.5 → 0.7.1
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/README.md +26 -28
- package/dist/opencode.json +1 -1
- package/dist/src/citation.d.ts +9 -0
- package/dist/src/citation.js +68 -11
- package/dist/src/db.js +10 -0
- package/dist/src/host-client.d.ts +1 -0
- package/dist/src/host-client.js +1 -0
- package/dist/src/index.d.ts +12 -2
- package/dist/src/index.js +32 -5
- package/dist/src/llm.d.ts +6 -0
- package/dist/src/llm.js +21 -10
- package/dist/src/phase2.d.ts +2 -0
- package/dist/src/phase2.js +1 -1
- package/dist/src/rollout-input.d.ts +6 -0
- package/dist/src/rollout-input.js +111 -0
- package/dist/src/store.d.ts +17 -1
- package/dist/src/store.js +90 -4
- package/dist/src/v2/agents.d.ts +53 -0
- package/dist/src/v2/agents.js +204 -0
- package/dist/src/v2/citation-overlay.d.ts +7 -0
- package/dist/src/v2/citation-overlay.js +52 -0
- package/dist/src/v2/index.d.ts +7 -0
- package/dist/src/v2/index.js +10 -0
- package/dist/src/v2/injection.d.ts +14 -0
- package/dist/src/v2/injection.js +19 -0
- package/dist/src/v2/plugin.d.ts +7 -0
- package/dist/src/v2/plugin.js +482 -0
- package/dist/src/v2/service.d.ts +78 -0
- package/dist/src/v2/service.js +195 -0
- package/dist/src/v2/shim.d.ts +47 -0
- package/dist/src/v2/shim.js +591 -0
- package/dist/src/v2/status-rpc.d.ts +197 -0
- package/dist/src/v2/status-rpc.js +159 -0
- package/dist/src/v2/status.d.ts +3 -0
- package/dist/src/v2/status.js +83 -0
- package/dist/src/v2/tools.d.ts +33 -0
- package/dist/src/v2/tools.js +57 -0
- package/dist/src/v2/tui.d.ts +3 -0
- package/dist/src/v2/tui.js +750 -0
- package/opencode.json +1 -1
- package/package.json +38 -2
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/v2/tui.tsx
|
|
3
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
4
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
5
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
6
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
7
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
8
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
9
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
10
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
11
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
12
|
+
import { createSignal, onCleanup, For } from "solid-js";
|
|
13
|
+
|
|
14
|
+
// src/v2/status-rpc.ts
|
|
15
|
+
var MEMORY_STATUS_ACTIVITIES = [
|
|
16
|
+
"idle",
|
|
17
|
+
"extracting",
|
|
18
|
+
"consolidating",
|
|
19
|
+
"retrying",
|
|
20
|
+
"error",
|
|
21
|
+
"read_only",
|
|
22
|
+
"disabled",
|
|
23
|
+
"stopping"
|
|
24
|
+
];
|
|
25
|
+
var INJECTED_SCHEMA = {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
sessionTokens: { type: "number" },
|
|
29
|
+
sessionRequests: { type: "number" },
|
|
30
|
+
totalTokens: { type: "number" },
|
|
31
|
+
totalRequests: { type: "number" }
|
|
32
|
+
},
|
|
33
|
+
required: ["sessionTokens", "sessionRequests", "totalTokens", "totalRequests"],
|
|
34
|
+
additionalProperties: false
|
|
35
|
+
};
|
|
36
|
+
var MemoryStatusRpc = {
|
|
37
|
+
id: "opencode-codex-memory",
|
|
38
|
+
methods: {
|
|
39
|
+
status: {
|
|
40
|
+
input: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: { sessionID: { type: "string" } },
|
|
43
|
+
additionalProperties: false
|
|
44
|
+
},
|
|
45
|
+
output: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
activity: { type: "string", enum: [...MEMORY_STATUS_ACTIVITIES] },
|
|
49
|
+
useMemories: { type: "boolean" },
|
|
50
|
+
generateMemories: { type: "boolean" },
|
|
51
|
+
extractModel: { type: ["string", "null"] },
|
|
52
|
+
consolidationModel: { type: ["string", "null"] },
|
|
53
|
+
codexImport: { type: "boolean" },
|
|
54
|
+
lastSuccessAt: { type: ["number", "null"] },
|
|
55
|
+
retryAt: { type: ["number", "null"] },
|
|
56
|
+
warnings: { type: "array", items: { type: "string" } },
|
|
57
|
+
sessionMode: { type: ["string", "null"], enum: ["enabled", "disabled", "polluted", null] },
|
|
58
|
+
memoryRoot: { type: "string" },
|
|
59
|
+
injected: INJECTED_SCHEMA
|
|
60
|
+
},
|
|
61
|
+
required: [
|
|
62
|
+
"activity",
|
|
63
|
+
"useMemories",
|
|
64
|
+
"generateMemories",
|
|
65
|
+
"extractModel",
|
|
66
|
+
"consolidationModel",
|
|
67
|
+
"codexImport",
|
|
68
|
+
"lastSuccessAt",
|
|
69
|
+
"retryAt",
|
|
70
|
+
"warnings",
|
|
71
|
+
"sessionMode",
|
|
72
|
+
"memoryRoot",
|
|
73
|
+
"injected"
|
|
74
|
+
],
|
|
75
|
+
additionalProperties: false
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
setOption: {
|
|
79
|
+
input: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
key: { type: "string", enum: ["use_memories", "generate_memories"] },
|
|
83
|
+
value: { type: "boolean" }
|
|
84
|
+
},
|
|
85
|
+
required: ["key", "value"],
|
|
86
|
+
additionalProperties: false
|
|
87
|
+
},
|
|
88
|
+
output: { type: "object", properties: { ok: { type: "boolean" } }, required: ["ok"], additionalProperties: false }
|
|
89
|
+
},
|
|
90
|
+
setSessionMode: {
|
|
91
|
+
input: {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
sessionID: { type: "string" },
|
|
95
|
+
mode: { type: "string", enum: ["enabled", "disabled"] }
|
|
96
|
+
},
|
|
97
|
+
required: ["sessionID", "mode"],
|
|
98
|
+
additionalProperties: false
|
|
99
|
+
},
|
|
100
|
+
output: { type: "object", properties: { ok: { type: "boolean" } }, required: ["ok"], additionalProperties: false }
|
|
101
|
+
},
|
|
102
|
+
consolidateNow: {
|
|
103
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
104
|
+
output: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: { status: { type: "string" } },
|
|
107
|
+
required: ["status"],
|
|
108
|
+
additionalProperties: false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
events: {
|
|
113
|
+
changed: { schema: { type: "object", properties: {}, additionalProperties: false } }
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
function isRecord(value) {
|
|
117
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
118
|
+
}
|
|
119
|
+
function isMemoryStatus(value) {
|
|
120
|
+
if (!isRecord(value))
|
|
121
|
+
return false;
|
|
122
|
+
if (typeof value.activity !== "string" || !MEMORY_STATUS_ACTIVITIES.includes(value.activity)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
for (const key of ["useMemories", "generateMemories", "codexImport"]) {
|
|
126
|
+
if (typeof value[key] !== "boolean")
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
for (const key of ["extractModel", "consolidationModel", "lastSuccessAt", "retryAt"]) {
|
|
130
|
+
const v = value[key];
|
|
131
|
+
if (v !== null && typeof v !== (key === "extractModel" || key === "consolidationModel" ? "string" : "number")) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!Array.isArray(value.warnings) || !value.warnings.every((w) => typeof w === "string")) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (value.sessionMode !== null && value.sessionMode !== "enabled" && value.sessionMode !== "disabled" && value.sessionMode !== "polluted") {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (typeof value.memoryRoot !== "string")
|
|
142
|
+
return false;
|
|
143
|
+
const injected = value.injected;
|
|
144
|
+
if (!isRecord(injected))
|
|
145
|
+
return false;
|
|
146
|
+
for (const key of ["sessionTokens", "sessionRequests", "totalTokens", "totalRequests"]) {
|
|
147
|
+
if (typeof injected[key] !== "number")
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/v2/tui.tsx
|
|
154
|
+
var labels = {
|
|
155
|
+
idle: "Idle",
|
|
156
|
+
extracting: "Extracting",
|
|
157
|
+
consolidating: "Consolidating",
|
|
158
|
+
retrying: "Waiting to retry",
|
|
159
|
+
error: "Needs attention",
|
|
160
|
+
read_only: "Read only",
|
|
161
|
+
disabled: "Disabled",
|
|
162
|
+
stopping: "Stopping"
|
|
163
|
+
};
|
|
164
|
+
function elapsed(time, now) {
|
|
165
|
+
if (time == null)
|
|
166
|
+
return "\u2014";
|
|
167
|
+
const minutes = Math.max(0, Math.floor((now - time) / 60000));
|
|
168
|
+
if (minutes === 0)
|
|
169
|
+
return "just now";
|
|
170
|
+
if (minutes < 60)
|
|
171
|
+
return `${minutes}m ago`;
|
|
172
|
+
if (minutes < 1440)
|
|
173
|
+
return `${Math.floor(minutes / 60)}h ago`;
|
|
174
|
+
return `${Math.floor(minutes / 1440)}d ago`;
|
|
175
|
+
}
|
|
176
|
+
function remaining(time, now) {
|
|
177
|
+
if (time == null)
|
|
178
|
+
return "\u2014";
|
|
179
|
+
const minutes = Math.max(0, Math.ceil((time - now) / 60000));
|
|
180
|
+
if (minutes < 1)
|
|
181
|
+
return "in <1m";
|
|
182
|
+
if (minutes < 60)
|
|
183
|
+
return `in ${minutes}m`;
|
|
184
|
+
if (minutes < 1440)
|
|
185
|
+
return `in ${Math.floor(minutes / 60)}h ${minutes % 60}m`;
|
|
186
|
+
return `in ${Math.floor(minutes / 1440)}d`;
|
|
187
|
+
}
|
|
188
|
+
function registerSlot(context, name, render) {
|
|
189
|
+
const slot = context.ui.slot;
|
|
190
|
+
const claim = slot.length <= 1 ? {
|
|
191
|
+
append: name,
|
|
192
|
+
render
|
|
193
|
+
} : undefined;
|
|
194
|
+
const dispose = claim ? slot(claim) : slot(name, render);
|
|
195
|
+
return typeof dispose === "function" ? dispose : () => {};
|
|
196
|
+
}
|
|
197
|
+
function themeColor(theme, ...paths) {
|
|
198
|
+
for (const path of paths) {
|
|
199
|
+
let cursor = theme;
|
|
200
|
+
for (const key of path) {
|
|
201
|
+
if (cursor === null || typeof cursor !== "object") {
|
|
202
|
+
cursor = undefined;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
cursor = cursor[key];
|
|
206
|
+
}
|
|
207
|
+
if (cursor !== null && typeof cursor === "object" && "default" in cursor) {
|
|
208
|
+
cursor = cursor.default;
|
|
209
|
+
}
|
|
210
|
+
if (cursor !== undefined && cursor !== null)
|
|
211
|
+
return cursor;
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
function formatTokens(n) {
|
|
216
|
+
if (n >= 1e6)
|
|
217
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
218
|
+
if (n >= 1000)
|
|
219
|
+
return `${(n / 1000).toFixed(1)}K`;
|
|
220
|
+
return String(n);
|
|
221
|
+
}
|
|
222
|
+
async function fetchStatus(rpc, context, sessionID) {
|
|
223
|
+
const result = await rpc.status(sessionID ? {
|
|
224
|
+
sessionID
|
|
225
|
+
} : {}, {
|
|
226
|
+
location: context.location ?? context.data.location.default(),
|
|
227
|
+
signal: AbortSignal.timeout(5000)
|
|
228
|
+
});
|
|
229
|
+
if (!isMemoryStatus(result))
|
|
230
|
+
throw new Error("invalid memory status payload");
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
function statusRows(s, now) {
|
|
234
|
+
return [{
|
|
235
|
+
label: "Activity",
|
|
236
|
+
value: labels[s.activity]
|
|
237
|
+
}, {
|
|
238
|
+
label: "Read memories",
|
|
239
|
+
value: s.useMemories ? "On" : "Off",
|
|
240
|
+
tone: s.useMemories ? "ok" : "muted"
|
|
241
|
+
}, {
|
|
242
|
+
label: "Write memories",
|
|
243
|
+
value: s.generateMemories ? "On" : "Off",
|
|
244
|
+
tone: s.generateMemories ? "ok" : "muted"
|
|
245
|
+
}, ...s.sessionMode ? [{
|
|
246
|
+
label: "This session",
|
|
247
|
+
value: s.sessionMode === "enabled" ? "Learning" : s.sessionMode === "disabled" ? "Not learning" : "Excluded (external context)",
|
|
248
|
+
tone: s.sessionMode === "enabled" ? "ok" : "muted"
|
|
249
|
+
}] : [], {
|
|
250
|
+
label: "Last consolidated",
|
|
251
|
+
value: s.lastSuccessAt == null ? "Never" : elapsed(s.lastSuccessAt, now)
|
|
252
|
+
}, ...s.retryAt == null ? [] : [{
|
|
253
|
+
label: "Next retry",
|
|
254
|
+
value: remaining(s.retryAt, now),
|
|
255
|
+
tone: "warn"
|
|
256
|
+
}]];
|
|
257
|
+
}
|
|
258
|
+
function usageRows(s) {
|
|
259
|
+
const inj = s.injected;
|
|
260
|
+
const per = inj.sessionRequests > 0 ? Math.round(inj.sessionTokens / inj.sessionRequests) : inj.totalRequests > 0 ? Math.round(inj.totalTokens / inj.totalRequests) : 0;
|
|
261
|
+
return [{
|
|
262
|
+
label: "Block size",
|
|
263
|
+
value: per ? `~${formatTokens(per)} tokens / request` : "\u2014"
|
|
264
|
+
}, {
|
|
265
|
+
label: "This session",
|
|
266
|
+
value: `~${formatTokens(inj.sessionTokens)} tokens \xB7 ${inj.sessionRequests} req`
|
|
267
|
+
}, {
|
|
268
|
+
label: "Since start",
|
|
269
|
+
value: `~${formatTokens(inj.totalTokens)} tokens \xB7 ${inj.totalRequests} req`
|
|
270
|
+
}];
|
|
271
|
+
}
|
|
272
|
+
function configRows(s) {
|
|
273
|
+
return [{
|
|
274
|
+
label: "Extraction",
|
|
275
|
+
value: s.extractModel ?? "Host default"
|
|
276
|
+
}, {
|
|
277
|
+
label: "Consolidation",
|
|
278
|
+
value: s.consolidationModel ?? "Host default"
|
|
279
|
+
}, {
|
|
280
|
+
label: "Codex import",
|
|
281
|
+
value: s.codexImport ? "On" : "Off",
|
|
282
|
+
tone: s.codexImport ? "ok" : "muted"
|
|
283
|
+
}, {
|
|
284
|
+
label: "Location",
|
|
285
|
+
value: s.memoryRoot
|
|
286
|
+
}];
|
|
287
|
+
}
|
|
288
|
+
var TABS = ["Overview", "Controls"];
|
|
289
|
+
function MemoryDialog(context, sessionID, initial) {
|
|
290
|
+
const rpc = context.client.rpc(MemoryStatusRpc);
|
|
291
|
+
const [status, setStatus] = createSignal(initial);
|
|
292
|
+
const [tab, setTab] = createSignal("Overview");
|
|
293
|
+
const [cursor, setCursor] = createSignal(0);
|
|
294
|
+
const [notice, setNotice] = createSignal("");
|
|
295
|
+
const [busy, setBusy] = createSignal(false);
|
|
296
|
+
const now = () => Date.now();
|
|
297
|
+
const refresh = async () => {
|
|
298
|
+
try {
|
|
299
|
+
setStatus(await fetchStatus(rpc, context, sessionID));
|
|
300
|
+
} catch {
|
|
301
|
+
setStatus(null);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const unsubscribe = rpc.events.on("changed", () => void refresh());
|
|
305
|
+
const timer = setInterval(() => void refresh(), 30000);
|
|
306
|
+
onCleanup(() => {
|
|
307
|
+
clearInterval(timer);
|
|
308
|
+
unsubscribe();
|
|
309
|
+
});
|
|
310
|
+
const call = async (method, input, done) => {
|
|
311
|
+
if (busy())
|
|
312
|
+
return;
|
|
313
|
+
setBusy(true);
|
|
314
|
+
try {
|
|
315
|
+
await rpc[method](input, {
|
|
316
|
+
location: context.location ?? context.data.location.default(),
|
|
317
|
+
signal: AbortSignal.timeout(5000)
|
|
318
|
+
});
|
|
319
|
+
setNotice(done);
|
|
320
|
+
await refresh();
|
|
321
|
+
} catch (err2) {
|
|
322
|
+
setNotice(`Failed: ${err2.message}`);
|
|
323
|
+
} finally {
|
|
324
|
+
setBusy(false);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const controls = () => {
|
|
328
|
+
const s = status();
|
|
329
|
+
if (!s)
|
|
330
|
+
return [];
|
|
331
|
+
const list = [{
|
|
332
|
+
id: "read",
|
|
333
|
+
title: "Use memories",
|
|
334
|
+
hint: "Inject the memory summary into every model request",
|
|
335
|
+
on: s.useMemories,
|
|
336
|
+
enabled: true,
|
|
337
|
+
run: () => void call("setOption", {
|
|
338
|
+
key: "use_memories",
|
|
339
|
+
value: !s.useMemories
|
|
340
|
+
}, s.useMemories ? "Memories are no longer injected." : "Memories are injected again.")
|
|
341
|
+
}, {
|
|
342
|
+
id: "learn",
|
|
343
|
+
title: "Learn from sessions",
|
|
344
|
+
hint: "Background extraction and consolidation of finished conversations",
|
|
345
|
+
on: s.generateMemories,
|
|
346
|
+
enabled: true,
|
|
347
|
+
run: () => void call("setOption", {
|
|
348
|
+
key: "generate_memories",
|
|
349
|
+
value: !s.generateMemories
|
|
350
|
+
}, s.generateMemories ? "Learning paused." : "Learning resumed.")
|
|
351
|
+
}];
|
|
352
|
+
if (sessionID) {
|
|
353
|
+
const learning = s.sessionMode !== "disabled" && s.sessionMode !== "polluted";
|
|
354
|
+
list.push({
|
|
355
|
+
id: "session",
|
|
356
|
+
title: "Learn from this session",
|
|
357
|
+
hint: s.sessionMode === "polluted" ? "Auto-excluded: this session pulled in external context" : "Whether this conversation may be extracted into memory",
|
|
358
|
+
on: learning,
|
|
359
|
+
enabled: true,
|
|
360
|
+
run: () => void call("setSessionMode", {
|
|
361
|
+
sessionID,
|
|
362
|
+
mode: learning ? "disabled" : "enabled"
|
|
363
|
+
}, learning ? "This session will not be learned from." : "This session will be learned from.")
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const running = s.activity === "consolidating" || s.activity === "extracting";
|
|
367
|
+
list.push({
|
|
368
|
+
id: "now",
|
|
369
|
+
title: "Consolidate now",
|
|
370
|
+
hint: running ? "Already running" : s.generateMemories ? "Extract idle sessions and rebuild the summary, skipping the 6h cooldown" : "Turn on learning first",
|
|
371
|
+
enabled: s.generateMemories && !running,
|
|
372
|
+
run: () => void call("consolidateNow", {}, "Consolidation started in the background.")
|
|
373
|
+
});
|
|
374
|
+
return list;
|
|
375
|
+
};
|
|
376
|
+
const activate = (i) => {
|
|
377
|
+
const c = controls()[i];
|
|
378
|
+
if (c && c.enabled)
|
|
379
|
+
c.run();
|
|
380
|
+
};
|
|
381
|
+
const move = (d) => {
|
|
382
|
+
const n = controls().length;
|
|
383
|
+
if (n === 0)
|
|
384
|
+
return;
|
|
385
|
+
setCursor((cursor() + d + n) % n);
|
|
386
|
+
};
|
|
387
|
+
context.keymap.layer(() => ({
|
|
388
|
+
enabled: () => true,
|
|
389
|
+
commands: [{
|
|
390
|
+
title: "Previous tab",
|
|
391
|
+
bind: "left",
|
|
392
|
+
run: () => {
|
|
393
|
+
setTab(TABS[(TABS.indexOf(tab()) + TABS.length - 1) % TABS.length]);
|
|
394
|
+
}
|
|
395
|
+
}, {
|
|
396
|
+
title: "Next tab",
|
|
397
|
+
bind: "right",
|
|
398
|
+
run: () => {
|
|
399
|
+
setTab(TABS[(TABS.indexOf(tab()) + 1) % TABS.length]);
|
|
400
|
+
}
|
|
401
|
+
}]
|
|
402
|
+
}));
|
|
403
|
+
context.keymap.layer(() => ({
|
|
404
|
+
enabled: () => tab() === "Controls",
|
|
405
|
+
commands: [{
|
|
406
|
+
title: "Previous control",
|
|
407
|
+
bind: "up",
|
|
408
|
+
run: () => move(-1)
|
|
409
|
+
}, {
|
|
410
|
+
title: "Next control",
|
|
411
|
+
bind: "down",
|
|
412
|
+
run: () => move(1)
|
|
413
|
+
}, {
|
|
414
|
+
title: "Toggle control",
|
|
415
|
+
bind: "return",
|
|
416
|
+
run: () => activate(cursor())
|
|
417
|
+
}, {
|
|
418
|
+
title: "Toggle control",
|
|
419
|
+
bind: "space",
|
|
420
|
+
run: () => activate(cursor())
|
|
421
|
+
}]
|
|
422
|
+
}));
|
|
423
|
+
const th = context.theme;
|
|
424
|
+
const text = themeColor(th, ["text", "default"], ["text"]);
|
|
425
|
+
const muted = themeColor(th, ["text", "subdued"], ["textMuted"], ["text"]);
|
|
426
|
+
const ok = themeColor(th, ["text", "feedback", "success"], ["text"]);
|
|
427
|
+
const warn = themeColor(th, ["text", "feedback", "warning"], ["text"]);
|
|
428
|
+
const err = themeColor(th, ["text", "feedback", "error"], ["text"]);
|
|
429
|
+
const accent = themeColor(th, ["text", "action", "primary"], ["text"]);
|
|
430
|
+
const selectedBg = themeColor(th, ["background", "action", "primary", "$focused"], ["background", "action", "primary", "focused"]);
|
|
431
|
+
const toneColor = (t) => t === "ok" ? ok : t === "warn" ? warn : t === "muted" ? muted : text;
|
|
432
|
+
const LABEL_W = 18;
|
|
433
|
+
const Rows = (props) => _$createComponent(For, {
|
|
434
|
+
get each() {
|
|
435
|
+
return props.rows;
|
|
436
|
+
},
|
|
437
|
+
children: (r) => (() => {
|
|
438
|
+
var _el$ = _$createElement("text"), _el$2 = _$createElement("span");
|
|
439
|
+
_$insertNode(_el$, _el$2);
|
|
440
|
+
_$setProp(_el$2, "style", {
|
|
441
|
+
fg: muted
|
|
442
|
+
});
|
|
443
|
+
_$insert(_el$2, () => r.label.padEnd(LABEL_W));
|
|
444
|
+
_$insert(_el$, () => r.value, null);
|
|
445
|
+
_$effect((_$p) => _$setProp(_el$, "fg", toneColor(r.tone), _$p));
|
|
446
|
+
return _el$;
|
|
447
|
+
})()
|
|
448
|
+
});
|
|
449
|
+
const Section = (props) => (() => {
|
|
450
|
+
var _el$3 = _$createElement("box"), _el$4 = _$createElement("text"), _el$5 = _$createElement("b");
|
|
451
|
+
_$insertNode(_el$3, _el$4);
|
|
452
|
+
_$setProp(_el$3, "marginTop", 1);
|
|
453
|
+
_$insertNode(_el$4, _el$5);
|
|
454
|
+
_$setProp(_el$4, "fg", muted);
|
|
455
|
+
_$insert(_el$5, () => props.title.toUpperCase());
|
|
456
|
+
return _el$3;
|
|
457
|
+
})();
|
|
458
|
+
const Note = (props) => (() => {
|
|
459
|
+
var _el$6 = _$createElement("box"), _el$7 = _$createElement("text");
|
|
460
|
+
_$insertNode(_el$6, _el$7);
|
|
461
|
+
_$setProp(_el$6, "marginTop", 1);
|
|
462
|
+
_$insert(_el$7, () => props.children);
|
|
463
|
+
_$effect((_$p) => _$setProp(_el$7, "fg", props.fg, _$p));
|
|
464
|
+
return _el$6;
|
|
465
|
+
})();
|
|
466
|
+
return (() => {
|
|
467
|
+
var _el$8 = _$createElement("box"), _el$9 = _$createElement("box"), _el$0 = _$createElement("box");
|
|
468
|
+
_$insertNode(_el$8, _el$9);
|
|
469
|
+
_$insertNode(_el$8, _el$0);
|
|
470
|
+
_$setProp(_el$8, "flexDirection", "column");
|
|
471
|
+
_$setProp(_el$8, "paddingLeft", 1);
|
|
472
|
+
_$setProp(_el$8, "paddingRight", 1);
|
|
473
|
+
_$setProp(_el$9, "flexDirection", "row");
|
|
474
|
+
_$setProp(_el$9, "gap", 2);
|
|
475
|
+
_$insert(_el$9, _$createComponent(For, {
|
|
476
|
+
each: TABS,
|
|
477
|
+
children: (t) => (() => {
|
|
478
|
+
var _el$1 = _$createElement("box"), _el$10 = _$createElement("text");
|
|
479
|
+
_$insertNode(_el$1, _el$10);
|
|
480
|
+
_$setProp(_el$1, "paddingLeft", 1);
|
|
481
|
+
_$setProp(_el$1, "paddingRight", 1);
|
|
482
|
+
_$setProp(_el$1, "onMouseDown", () => setTab(t));
|
|
483
|
+
_$insert(_el$10, (() => {
|
|
484
|
+
var _c$2 = _$memo(() => tab() === t);
|
|
485
|
+
return () => _c$2() ? (() => {
|
|
486
|
+
var _el$11 = _$createElement("b");
|
|
487
|
+
_$insert(_el$11, t);
|
|
488
|
+
return _el$11;
|
|
489
|
+
})() : t;
|
|
490
|
+
})());
|
|
491
|
+
_$effect((_p$) => {
|
|
492
|
+
var _v$ = tab() === t ? selectedBg : undefined, _v$2 = tab() === t ? text : muted;
|
|
493
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$1, "backgroundColor", _v$, _p$.e));
|
|
494
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$10, "fg", _v$2, _p$.t));
|
|
495
|
+
return _p$;
|
|
496
|
+
}, {
|
|
497
|
+
e: undefined,
|
|
498
|
+
t: undefined
|
|
499
|
+
});
|
|
500
|
+
return _el$1;
|
|
501
|
+
})()
|
|
502
|
+
}));
|
|
503
|
+
_$setProp(_el$0, "flexDirection", "column");
|
|
504
|
+
_$insert(_el$0, (() => {
|
|
505
|
+
var _c$ = _$memo(() => status() === null);
|
|
506
|
+
return () => _c$() ? _$createComponent(Note, {
|
|
507
|
+
fg: err,
|
|
508
|
+
children: "Memory status unavailable \u2014 is the server plugin loaded?"
|
|
509
|
+
}) : _$memo(() => tab() === "Overview")() ? (() => {
|
|
510
|
+
var _el$12 = _$createElement("box");
|
|
511
|
+
_$setProp(_el$12, "flexDirection", "column");
|
|
512
|
+
_$insert(_el$12, _$createComponent(Section, {
|
|
513
|
+
title: "Status"
|
|
514
|
+
}), null);
|
|
515
|
+
_$insert(_el$12, _$createComponent(Rows, {
|
|
516
|
+
get rows() {
|
|
517
|
+
return statusRows(status(), now());
|
|
518
|
+
}
|
|
519
|
+
}), null);
|
|
520
|
+
_$insert(_el$12, _$createComponent(Section, {
|
|
521
|
+
title: "Context usage"
|
|
522
|
+
}), null);
|
|
523
|
+
_$insert(_el$12, _$createComponent(Rows, {
|
|
524
|
+
get rows() {
|
|
525
|
+
return usageRows(status());
|
|
526
|
+
}
|
|
527
|
+
}), null);
|
|
528
|
+
_$insert(_el$12, _$createComponent(Section, {
|
|
529
|
+
title: "Setup"
|
|
530
|
+
}), null);
|
|
531
|
+
_$insert(_el$12, _$createComponent(Rows, {
|
|
532
|
+
get rows() {
|
|
533
|
+
return configRows(status());
|
|
534
|
+
}
|
|
535
|
+
}), null);
|
|
536
|
+
_$insert(_el$12, (() => {
|
|
537
|
+
var _c$3 = _$memo(() => status().warnings.length > 0);
|
|
538
|
+
return () => _c$3() ? (() => {
|
|
539
|
+
var _el$13 = _$createElement("box");
|
|
540
|
+
_$setProp(_el$13, "flexDirection", "column");
|
|
541
|
+
_$insert(_el$13, _$createComponent(Section, {
|
|
542
|
+
title: "Attention"
|
|
543
|
+
}), null);
|
|
544
|
+
_$insert(_el$13, _$createComponent(For, {
|
|
545
|
+
get each() {
|
|
546
|
+
return status().warnings;
|
|
547
|
+
},
|
|
548
|
+
children: (w) => (() => {
|
|
549
|
+
var _el$14 = _$createElement("text");
|
|
550
|
+
_$setProp(_el$14, "fg", warn);
|
|
551
|
+
_$insert(_el$14, `! ${w}`);
|
|
552
|
+
return _el$14;
|
|
553
|
+
})()
|
|
554
|
+
}), null);
|
|
555
|
+
return _el$13;
|
|
556
|
+
})() : _$createElement("box");
|
|
557
|
+
})(), null);
|
|
558
|
+
_$insert(_el$12, _$createComponent(Note, {
|
|
559
|
+
fg: muted,
|
|
560
|
+
children: "Token figures are chars/4 estimates; the block is re-sent each request and normally served from the prompt cache."
|
|
561
|
+
}), null);
|
|
562
|
+
return _el$12;
|
|
563
|
+
})() : (() => {
|
|
564
|
+
var _el$16 = _$createElement("box");
|
|
565
|
+
_$setProp(_el$16, "flexDirection", "column");
|
|
566
|
+
_$setProp(_el$16, "marginTop", 1);
|
|
567
|
+
_$insert(_el$16, _$createComponent(For, {
|
|
568
|
+
get each() {
|
|
569
|
+
return controls();
|
|
570
|
+
},
|
|
571
|
+
children: (c, i) => {
|
|
572
|
+
const selected = () => cursor() === i();
|
|
573
|
+
const fgTitle = () => !c.enabled ? muted : text;
|
|
574
|
+
const indicator = () => c.on === undefined ? "\u25B8" : c.on ? "\u25CF" : "\u25CB";
|
|
575
|
+
const indicatorFg = () => !c.enabled ? muted : c.on === undefined ? accent : c.on ? ok : muted;
|
|
576
|
+
const state = () => c.on === undefined ? "" : c.on ? "On" : "Off";
|
|
577
|
+
return (() => {
|
|
578
|
+
var _el$17 = _$createElement("box"), _el$18 = _$createElement("text"), _el$19 = _$createElement("span"), _el$20 = _$createElement("span"), _el$21 = _$createElement("text");
|
|
579
|
+
_$insertNode(_el$17, _el$18);
|
|
580
|
+
_$insertNode(_el$17, _el$21);
|
|
581
|
+
_$setProp(_el$17, "flexDirection", "column");
|
|
582
|
+
_$setProp(_el$17, "paddingLeft", 1);
|
|
583
|
+
_$setProp(_el$17, "paddingRight", 1);
|
|
584
|
+
_$setProp(_el$17, "onMouseDown", () => {
|
|
585
|
+
setCursor(i());
|
|
586
|
+
activate(i());
|
|
587
|
+
});
|
|
588
|
+
_$insertNode(_el$18, _el$19);
|
|
589
|
+
_$insertNode(_el$18, _el$20);
|
|
590
|
+
_$insert(_el$19, indicator);
|
|
591
|
+
_$insert(_el$18, (() => {
|
|
592
|
+
var _c$4 = _$memo(() => !!selected());
|
|
593
|
+
return () => _c$4() ? (() => {
|
|
594
|
+
var _el$22 = _$createElement("b");
|
|
595
|
+
_$insert(_el$22, () => ` ${c.title.padEnd(26)}`);
|
|
596
|
+
return _el$22;
|
|
597
|
+
})() : ` ${c.title.padEnd(26)}`;
|
|
598
|
+
})(), _el$20);
|
|
599
|
+
_$insert(_el$20, state);
|
|
600
|
+
_$setProp(_el$21, "fg", muted);
|
|
601
|
+
_$insert(_el$21, () => ` ${c.hint}`);
|
|
602
|
+
_$effect((_p$) => {
|
|
603
|
+
var _v$3 = selected() ? selectedBg : undefined, _v$4 = fgTitle(), _v$5 = {
|
|
604
|
+
fg: indicatorFg()
|
|
605
|
+
}, _v$6 = {
|
|
606
|
+
fg: c.on ? ok : muted
|
|
607
|
+
};
|
|
608
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$17, "backgroundColor", _v$3, _p$.e));
|
|
609
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$18, "fg", _v$4, _p$.t));
|
|
610
|
+
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$19, "style", _v$5, _p$.a));
|
|
611
|
+
_v$6 !== _p$.o && (_p$.o = _$setProp(_el$20, "style", _v$6, _p$.o));
|
|
612
|
+
return _p$;
|
|
613
|
+
}, {
|
|
614
|
+
e: undefined,
|
|
615
|
+
t: undefined,
|
|
616
|
+
a: undefined,
|
|
617
|
+
o: undefined
|
|
618
|
+
});
|
|
619
|
+
return _el$17;
|
|
620
|
+
})();
|
|
621
|
+
}
|
|
622
|
+
}), null);
|
|
623
|
+
_$insert(_el$16, _$createComponent(Note, {
|
|
624
|
+
fg: muted,
|
|
625
|
+
children: "Global settings last until the server restarts; edit opencode.json to make them permanent."
|
|
626
|
+
}), null);
|
|
627
|
+
return _el$16;
|
|
628
|
+
})();
|
|
629
|
+
})());
|
|
630
|
+
_$insert(_el$8, _$createComponent(Note, {
|
|
631
|
+
fg: ok,
|
|
632
|
+
get children() {
|
|
633
|
+
return notice();
|
|
634
|
+
}
|
|
635
|
+
}), null);
|
|
636
|
+
_$insert(_el$8, _$createComponent(Note, {
|
|
637
|
+
fg: muted,
|
|
638
|
+
get children() {
|
|
639
|
+
return tab() === "Controls" ? "\u2191\u2193 move \xB7 enter toggle \xB7 click a tab or control \xB7 esc close" : "click a tab \xB7 esc close \xB7 ask the assistant for memory_inspect for raw diagnostics";
|
|
640
|
+
}
|
|
641
|
+
}), null);
|
|
642
|
+
return _el$8;
|
|
643
|
+
})();
|
|
644
|
+
}
|
|
645
|
+
function StatusPanel(context, sessionID) {
|
|
646
|
+
const rpc = context.client.rpc(MemoryStatusRpc);
|
|
647
|
+
const [status, setStatus] = createSignal();
|
|
648
|
+
const [unavailable, setUnavailable] = createSignal(false);
|
|
649
|
+
const refresh = async () => {
|
|
650
|
+
try {
|
|
651
|
+
setStatus(await fetchStatus(rpc, context, sessionID));
|
|
652
|
+
setUnavailable(false);
|
|
653
|
+
} catch {
|
|
654
|
+
setUnavailable(true);
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
const unsubscribe = rpc.events.on("changed", () => void refresh());
|
|
658
|
+
const timer = setInterval(() => void refresh(), 30000);
|
|
659
|
+
onCleanup(() => {
|
|
660
|
+
clearInterval(timer);
|
|
661
|
+
unsubscribe();
|
|
662
|
+
});
|
|
663
|
+
refresh();
|
|
664
|
+
const title = () => {
|
|
665
|
+
if (unavailable())
|
|
666
|
+
return "Unavailable";
|
|
667
|
+
const s = status();
|
|
668
|
+
if (!s)
|
|
669
|
+
return "Loading\u2026";
|
|
670
|
+
return s.useMemories ? "Enabled" : "Disabled";
|
|
671
|
+
};
|
|
672
|
+
const color = () => {
|
|
673
|
+
if (unavailable())
|
|
674
|
+
return themeColor(context.theme, ["text", "feedback", "error"], ["text"]);
|
|
675
|
+
if (status()?.useMemories)
|
|
676
|
+
return themeColor(context.theme, ["text", "feedback", "success"], ["text"]);
|
|
677
|
+
return themeColor(context.theme, ["text", "subdued"], ["textMuted"], ["text"]);
|
|
678
|
+
};
|
|
679
|
+
const muted = () => themeColor(context.theme, ["text", "subdued"], ["textMuted"], ["text"]);
|
|
680
|
+
const heading = () => themeColor(context.theme, ["text", "default"], ["text"]);
|
|
681
|
+
return (() => {
|
|
682
|
+
var _el$23 = _$createElement("box"), _el$24 = _$createElement("text"), _el$26 = _$createElement("text"), _el$27 = _$createTextNode(`\u25CF `), _el$28 = _$createElement("text");
|
|
683
|
+
_$insertNode(_el$23, _el$24);
|
|
684
|
+
_$insertNode(_el$23, _el$26);
|
|
685
|
+
_$insertNode(_el$23, _el$28);
|
|
686
|
+
_$setProp(_el$23, "flexDirection", "column");
|
|
687
|
+
_$insertNode(_el$24, _$createTextNode(`Memory`));
|
|
688
|
+
_$insertNode(_el$26, _el$27);
|
|
689
|
+
_$insert(_el$26, title, null);
|
|
690
|
+
_$insertNode(_el$28, _$createTextNode(` /memory`));
|
|
691
|
+
_$effect((_p$) => {
|
|
692
|
+
var _v$7 = heading(), _v$8 = color(), _v$9 = muted();
|
|
693
|
+
_v$7 !== _p$.e && (_p$.e = _$setProp(_el$24, "fg", _v$7, _p$.e));
|
|
694
|
+
_v$8 !== _p$.t && (_p$.t = _$setProp(_el$26, "fg", _v$8, _p$.t));
|
|
695
|
+
_v$9 !== _p$.a && (_p$.a = _$setProp(_el$28, "fg", _v$9, _p$.a));
|
|
696
|
+
return _p$;
|
|
697
|
+
}, {
|
|
698
|
+
e: undefined,
|
|
699
|
+
t: undefined,
|
|
700
|
+
a: undefined
|
|
701
|
+
});
|
|
702
|
+
return _el$23;
|
|
703
|
+
})();
|
|
704
|
+
}
|
|
705
|
+
function KeymapLayer(context) {
|
|
706
|
+
context.keymap.layer(() => ({
|
|
707
|
+
mode: "global",
|
|
708
|
+
commands: [{
|
|
709
|
+
id: "opencode-codex-memory.status",
|
|
710
|
+
title: "Memory",
|
|
711
|
+
description: "Memory status and controls",
|
|
712
|
+
group: "Memory",
|
|
713
|
+
palette: true,
|
|
714
|
+
slash: {
|
|
715
|
+
name: "memory",
|
|
716
|
+
aliases: ["memory-status"]
|
|
717
|
+
},
|
|
718
|
+
run: async () => {
|
|
719
|
+
const rpc = context.client.rpc(MemoryStatusRpc);
|
|
720
|
+
const route = context.ui.router.current();
|
|
721
|
+
const sessionID = route.type === "session" ? route.sessionID : undefined;
|
|
722
|
+
let initial = null;
|
|
723
|
+
try {
|
|
724
|
+
initial = await fetchStatus(rpc, context, sessionID);
|
|
725
|
+
} catch {
|
|
726
|
+
initial = null;
|
|
727
|
+
}
|
|
728
|
+
context.ui.dialog.show(() => MemoryDialog(context, sessionID, initial));
|
|
729
|
+
context.ui.dialog.set({
|
|
730
|
+
size: "medium"
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}]
|
|
734
|
+
}));
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
var tui_default = Plugin.define({
|
|
738
|
+
id: "opencode-codex-memory.tui",
|
|
739
|
+
setup(context) {
|
|
740
|
+
const offSidebar = registerSlot(context, "sidebar.content", (input) => StatusPanel(context, input?.sessionID));
|
|
741
|
+
const offApp = registerSlot(context, "app", () => KeymapLayer(context));
|
|
742
|
+
return () => {
|
|
743
|
+
offSidebar();
|
|
744
|
+
offApp();
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
export {
|
|
749
|
+
tui_default as default
|
|
750
|
+
};
|