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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micode",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "OpenCode plugin with Brainstorm-Research-Plan-Implement workflow",
5
5
  "module": "src/index.ts",
6
6
  "main": "src/index.ts",
@@ -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.50;
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 usage on assistant message completion
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
- // Skip if this is already a summary message
174
- if (info?.summary === true) return;
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;