micode 0.8.1 → 0.8.3
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/hooks/auto-compact.ts +52 -5
package/package.json
CHANGED
|
@@ -4,13 +4,23 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
6
6
|
// Compact when this percentage of context is used
|
|
7
|
-
const COMPACT_THRESHOLD = 0.
|
|
7
|
+
const COMPACT_THRESHOLD = 0.5;
|
|
8
8
|
|
|
9
9
|
const LEDGER_DIR = "thoughts/ledgers";
|
|
10
10
|
|
|
11
|
+
// Timeout for waiting for compaction to complete (2 minutes)
|
|
12
|
+
const COMPACTION_TIMEOUT_MS = 120_000;
|
|
13
|
+
|
|
14
|
+
interface PendingCompaction {
|
|
15
|
+
resolve: () => void;
|
|
16
|
+
reject: (error: Error) => void;
|
|
17
|
+
timeoutId: ReturnType<typeof setTimeout>;
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
interface AutoCompactState {
|
|
12
21
|
inProgress: Set<string>;
|
|
13
22
|
lastCompactTime: Map<string, number>;
|
|
23
|
+
pendingCompactions: Map<string, PendingCompaction>;
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
// Cooldown between compaction attempts (prevent rapid re-triggering)
|
|
@@ -20,6 +30,7 @@ export function createAutoCompactHook(ctx: PluginInput) {
|
|
|
20
30
|
const state: AutoCompactState = {
|
|
21
31
|
inProgress: new Set(),
|
|
22
32
|
lastCompactTime: new Map(),
|
|
33
|
+
pendingCompactions: new Map(),
|
|
23
34
|
};
|
|
24
35
|
|
|
25
36
|
async function writeSummaryToLedger(sessionID: string): Promise<void> {
|
|
@@ -78,6 +89,17 @@ ${summaryText}
|
|
|
78
89
|
}
|
|
79
90
|
}
|
|
80
91
|
|
|
92
|
+
function waitForCompaction(sessionID: string): Promise<void> {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const timeoutId = setTimeout(() => {
|
|
95
|
+
state.pendingCompactions.delete(sessionID);
|
|
96
|
+
reject(new Error("Compaction timed out"));
|
|
97
|
+
}, COMPACTION_TIMEOUT_MS);
|
|
98
|
+
|
|
99
|
+
state.pendingCompactions.set(sessionID, { resolve, reject, timeoutId });
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
81
103
|
async function triggerCompaction(
|
|
82
104
|
sessionID: string,
|
|
83
105
|
providerID: string,
|
|
@@ -111,15 +133,23 @@ ${summaryText}
|
|
|
111
133
|
})
|
|
112
134
|
.catch(() => {});
|
|
113
135
|
|
|
136
|
+
// Set up listener BEFORE calling summarize to avoid race condition
|
|
137
|
+
// (summary message event could fire before we start listening)
|
|
138
|
+
const compactionPromise = waitForCompaction(sessionID);
|
|
139
|
+
|
|
140
|
+
// Start the compaction - this returns immediately while compaction runs async
|
|
114
141
|
await ctx.client.session.summarize({
|
|
115
142
|
path: { id: sessionID },
|
|
116
143
|
body: { providerID, modelID },
|
|
117
144
|
query: { directory: ctx.directory },
|
|
118
145
|
});
|
|
119
146
|
|
|
147
|
+
// Wait for the summary message to be created (message.updated with summary: true)
|
|
148
|
+
await compactionPromise;
|
|
149
|
+
|
|
120
150
|
state.lastCompactTime.set(sessionID, Date.now());
|
|
121
151
|
|
|
122
|
-
// Write summary to ledger file
|
|
152
|
+
// Write summary to ledger file (only after compaction is confirmed complete)
|
|
123
153
|
await writeSummaryToLedger(sessionID);
|
|
124
154
|
|
|
125
155
|
await ctx.client.tui
|
|
@@ -159,19 +189,36 @@ ${summaryText}
|
|
|
159
189
|
if (sessionInfo?.id) {
|
|
160
190
|
state.inProgress.delete(sessionInfo.id);
|
|
161
191
|
state.lastCompactTime.delete(sessionInfo.id);
|
|
192
|
+
const pending = state.pendingCompactions.get(sessionInfo.id);
|
|
193
|
+
if (pending) {
|
|
194
|
+
clearTimeout(pending.timeoutId);
|
|
195
|
+
state.pendingCompactions.delete(sessionInfo.id);
|
|
196
|
+
pending.reject(new Error("Session deleted"));
|
|
197
|
+
}
|
|
162
198
|
}
|
|
163
199
|
return;
|
|
164
200
|
}
|
|
165
201
|
|
|
166
|
-
// Monitor
|
|
202
|
+
// Monitor message events
|
|
167
203
|
if (event.type === "message.updated") {
|
|
168
204
|
const info = props?.info as Record<string, unknown> | undefined;
|
|
169
205
|
const sessionID = info?.sessionID as string | undefined;
|
|
170
206
|
|
|
171
207
|
if (!sessionID || info?.role !== "assistant") return;
|
|
172
208
|
|
|
173
|
-
//
|
|
174
|
-
if (info?.summary === true)
|
|
209
|
+
// Check if this is a summary message - signals compaction complete
|
|
210
|
+
if (info?.summary === true) {
|
|
211
|
+
const pending = state.pendingCompactions.get(sessionID);
|
|
212
|
+
if (pending) {
|
|
213
|
+
clearTimeout(pending.timeoutId);
|
|
214
|
+
state.pendingCompactions.delete(sessionID);
|
|
215
|
+
pending.resolve();
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Skip triggering compaction if we're already waiting for one
|
|
221
|
+
if (state.pendingCompactions.has(sessionID)) return;
|
|
175
222
|
|
|
176
223
|
const tokens = info?.tokens as { input?: number; cache?: { read?: number } } | undefined;
|
|
177
224
|
const inputTokens = tokens?.input || 0;
|