opencode-self-improvement 0.8.5 → 0.8.6
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/package.json +1 -1
- package/src/index.ts +19 -6
- package/src/sessions.ts +34 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-self-improvement",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "Durable memory for opencode: remember facts, decisions, and commands across sessions, plus a one-command per-repo setup (/repo-init).",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { runWriteGate, type GateContext, type WriteCandidate } from "./gates.ts"
|
|
|
29
29
|
import { pruneMemory } from "./prune.ts";
|
|
30
30
|
import { exportMemories, findStaleOpen, importMemories } from "./portable.ts";
|
|
31
31
|
import { listSnapshots, restoreSnapshot } from "./undo.ts";
|
|
32
|
+
import { SESSION_CAP, SessionLru } from "./sessions.ts";
|
|
32
33
|
import { record } from "./evidence.ts";
|
|
33
34
|
import {
|
|
34
35
|
capToBudget,
|
|
@@ -604,13 +605,15 @@ const impl = {
|
|
|
604
605
|
});
|
|
605
606
|
|
|
606
607
|
// Per-session frozen injection epochs: the relevance block plus the store
|
|
607
|
-
// fingerprint it was computed against. Recomputed
|
|
608
|
-
//
|
|
609
|
-
// prompt/context/compaction hooks) — so the system prefix, and with it
|
|
610
|
-
// provider prompt cache, survives ordinary requests.
|
|
608
|
+
// fingerprint it was computed against. Recomputed on the first prompt of a
|
|
609
|
+
// tracked session, when injectable rows change, or after a compaction (see
|
|
610
|
+
// the prompt/context/compaction hooks) — so the system prefix, and with it
|
|
611
|
+
// the provider prompt cache, survives ordinary requests. The shared LRU
|
|
612
|
+
// below may evict the whole session state; a later prompt starts it fresh.
|
|
611
613
|
const epoch = new Map<string, { version: string; block: string; at: string; reason: "first" | "store_changed" }>();
|
|
612
|
-
// Latest user prompt per session. A changed store is re-scored
|
|
613
|
-
// even when the change happened during a tool loop with no new
|
|
614
|
+
// Latest user prompt per tracked session. A changed store is re-scored
|
|
615
|
+
// against it even when the change happened during a tool loop with no new
|
|
616
|
+
// prompt. It is evicted together with the epoch and session ledgers.
|
|
614
617
|
const latestPrompt = new Map<string, string>();
|
|
615
618
|
// Recomputes since server start: every one invalidated a prompt cache, so
|
|
616
619
|
// it is the honest cost number the surfaces show (status files / tools).
|
|
@@ -622,6 +625,13 @@ const impl = {
|
|
|
622
625
|
// (memory_status, in-process).
|
|
623
626
|
const sessionStats = new Map<string, SessionStats>();
|
|
624
627
|
const measuredTurns = new Map<string, Measured>();
|
|
628
|
+
// Every map above is keyed by session and dropped together on eviction.
|
|
629
|
+
const sessions = new SessionLru(SESSION_CAP, (sid) => {
|
|
630
|
+
epoch.delete(sid);
|
|
631
|
+
latestPrompt.delete(sid);
|
|
632
|
+
sessionStats.delete(sid);
|
|
633
|
+
measuredTurns.delete(sid);
|
|
634
|
+
});
|
|
625
635
|
const statsFor = (sid: string): SessionStats => {
|
|
626
636
|
let st = sessionStats.get(sid);
|
|
627
637
|
if (!st) {
|
|
@@ -739,6 +749,7 @@ const impl = {
|
|
|
739
749
|
// Route store reads in this hook through the right session's scope.
|
|
740
750
|
setCurrent({ sessionID: event?.sessionID });
|
|
741
751
|
const sid = event?.sessionID;
|
|
752
|
+
if (typeof sid === "string") sessions.touch(sid);
|
|
742
753
|
const mems = await injectableRows();
|
|
743
754
|
if (typeof sid === "string") await refreshEpoch(sid, mems);
|
|
744
755
|
const parts: string[] = [];
|
|
@@ -773,6 +784,7 @@ const impl = {
|
|
|
773
784
|
setCurrent({ sessionID: event?.sessionID });
|
|
774
785
|
const sid = event?.sessionID;
|
|
775
786
|
if (typeof sid === "string") {
|
|
787
|
+
sessions.touch(sid);
|
|
776
788
|
markTurn(sid);
|
|
777
789
|
await checkNewSession(sid);
|
|
778
790
|
await bindSessionFromMessages(sid);
|
|
@@ -839,6 +851,7 @@ const impl = {
|
|
|
839
851
|
const sid = info.sessionID;
|
|
840
852
|
const t = info.tokens;
|
|
841
853
|
if (typeof sid !== "string" || !t) continue;
|
|
854
|
+
sessions.touch(sid);
|
|
842
855
|
measuredTurns.set(sid, {
|
|
843
856
|
at: new Date().toISOString(),
|
|
844
857
|
input: Number(t.input) || 0,
|
package/src/sessions.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Bounded per-session state for a long-lived server. The plugin keeps several
|
|
2
|
+
// session-keyed maps (epoch, latest prompt, stats, measured usage); a resident
|
|
3
|
+
// `opencode serve` would otherwise hold an entry per session it ever saw. One
|
|
4
|
+
// recency list owns eviction so the maps are always dropped together: an
|
|
5
|
+
// evicted session simply starts over (no block until its next prompt, then a
|
|
6
|
+
// "first" epoch), which costs one recompute and never serves stale state.
|
|
7
|
+
// Same cap as repokey.ts's session-scope cache.
|
|
8
|
+
export const SESSION_CAP = 64;
|
|
9
|
+
|
|
10
|
+
export class SessionLru {
|
|
11
|
+
private readonly order = new Set<string>();
|
|
12
|
+
private readonly cap: number;
|
|
13
|
+
private readonly onEvict: (sid: string) => void;
|
|
14
|
+
|
|
15
|
+
constructor(cap: number, onEvict: (sid: string) => void) {
|
|
16
|
+
this.cap = cap;
|
|
17
|
+
this.onEvict = onEvict;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Mark a session as just used; evicts the least recently used beyond the cap.
|
|
21
|
+
touch(sid: string): void {
|
|
22
|
+
this.order.delete(sid);
|
|
23
|
+
this.order.add(sid);
|
|
24
|
+
while (this.order.size > this.cap) {
|
|
25
|
+
const oldest = this.order.values().next().value as string;
|
|
26
|
+
this.order.delete(oldest);
|
|
27
|
+
this.onEvict(oldest);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get size(): number {
|
|
32
|
+
return this.order.size;
|
|
33
|
+
}
|
|
34
|
+
}
|