tycono 0.3.11-beta.0 → 0.3.11
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
|
@@ -214,7 +214,7 @@ class ExecutionManager {
|
|
|
214
214
|
let harnessTurnCount = 0;
|
|
215
215
|
let softLimitWarned = false;
|
|
216
216
|
let hardLimitReached = false;
|
|
217
|
-
|
|
217
|
+
const outputChunks: string[] = [];
|
|
218
218
|
|
|
219
219
|
const teamStatus: import('../../../shared/types').TeamStatus = {};
|
|
220
220
|
for (const [, e] of this.executions) {
|
|
@@ -258,7 +258,7 @@ class ExecutionManager {
|
|
|
258
258
|
},
|
|
259
259
|
{
|
|
260
260
|
onText: (text) => {
|
|
261
|
-
|
|
261
|
+
outputChunks.push(text);
|
|
262
262
|
execution.stream.emit('text', params.roleId, { text });
|
|
263
263
|
if (execution.sessionId) {
|
|
264
264
|
this.updateSessionRoleMessage(execution, text);
|
|
@@ -501,7 +501,7 @@ class ExecutionManager {
|
|
|
501
501
|
.catch((err: Error) => {
|
|
502
502
|
if (hardLimitReached) {
|
|
503
503
|
execution.result = {
|
|
504
|
-
output:
|
|
504
|
+
output: outputChunks.join(''),
|
|
505
505
|
turns: harnessTurnCount,
|
|
506
506
|
totalTokens: { input: 0, output: 0 },
|
|
507
507
|
toolCalls: [],
|
|
@@ -109,16 +109,44 @@ function writeImmediate(session: Session): void {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/* ─── In-memory cache ───────────────────── */
|
|
112
|
+
/*
|
|
113
|
+
* OOM prevention: inactive sessions cache metadata only (no messages).
|
|
114
|
+
* Messages are loaded on-demand from disk when getSession() is called.
|
|
115
|
+
* Active sessions keep messages in memory for streaming writes.
|
|
116
|
+
*
|
|
117
|
+
* Before: 61 sessions × 50KB avg messages = 3MB+ baseline, grows to 100s of MB
|
|
118
|
+
* After: 61 sessions × 1KB metadata + 5 active × 50KB = ~310KB baseline
|
|
119
|
+
*/
|
|
112
120
|
|
|
113
121
|
const cache = new Map<string, Session>();
|
|
114
122
|
|
|
123
|
+
/** Strip messages from session to save memory (keeps metadata) */
|
|
124
|
+
function stripMessages(session: Session): Session {
|
|
125
|
+
if (session.messages.length === 0) return session;
|
|
126
|
+
return { ...session, messages: [] };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Load full session from disk (with messages) */
|
|
130
|
+
function loadFromDisk(id: string): Session | undefined {
|
|
131
|
+
const p = sessionPath(id);
|
|
132
|
+
if (!fs.existsSync(p)) return undefined;
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(fs.readFileSync(p, 'utf-8')) as Session;
|
|
135
|
+
} catch { return undefined; }
|
|
136
|
+
}
|
|
137
|
+
|
|
115
138
|
function loadAll(): void {
|
|
116
139
|
ensureDir();
|
|
117
140
|
const files = fs.readdirSync(sessionsDir()).filter((f) => f.endsWith('.json'));
|
|
118
141
|
for (const file of files) {
|
|
119
142
|
try {
|
|
120
143
|
const data = JSON.parse(fs.readFileSync(path.join(sessionsDir(), file), 'utf-8')) as Session;
|
|
121
|
-
|
|
144
|
+
// Only keep messages for active sessions; strip for done/interrupted
|
|
145
|
+
if (data.status === 'active' || data.status === 'awaiting_input') {
|
|
146
|
+
cache.set(data.id, data);
|
|
147
|
+
} else {
|
|
148
|
+
cache.set(data.id, stripMessages(data));
|
|
149
|
+
}
|
|
122
150
|
} catch { /* skip corrupted */ }
|
|
123
151
|
}
|
|
124
152
|
}
|
|
@@ -165,7 +193,15 @@ export function createSession(roleId: string, opts: CreateSessionOptions = {}):
|
|
|
165
193
|
|
|
166
194
|
export function getSession(id: string): Session | undefined {
|
|
167
195
|
ensureLoaded();
|
|
168
|
-
|
|
196
|
+
const cached = cache.get(id);
|
|
197
|
+
if (!cached) return undefined;
|
|
198
|
+
|
|
199
|
+
// If messages were stripped (inactive session), reload from disk on demand
|
|
200
|
+
if (cached.messages.length === 0 && cached.status !== 'active') {
|
|
201
|
+
const full = loadFromDisk(id);
|
|
202
|
+
if (full) return full; // Return disk version (don't cache — it's a one-time read)
|
|
203
|
+
}
|
|
204
|
+
return cached;
|
|
169
205
|
}
|
|
170
206
|
|
|
171
207
|
export function listSessions(): Omit<Session, 'messages'>[] {
|
|
@@ -252,6 +288,11 @@ export function updateSession(id: string, updates: Partial<Pick<Session, 'title'
|
|
|
252
288
|
session.updatedAt = new Date().toISOString();
|
|
253
289
|
writeImmediate(session);
|
|
254
290
|
|
|
291
|
+
// OOM prevention: when session becomes inactive, strip messages from cache
|
|
292
|
+
if (updates.status && updates.status !== 'active' && updates.status !== 'awaiting_input') {
|
|
293
|
+
cache.set(id, stripMessages(session));
|
|
294
|
+
}
|
|
295
|
+
|
|
255
296
|
return session;
|
|
256
297
|
}
|
|
257
298
|
|
|
@@ -19,6 +19,7 @@ import path from 'node:path';
|
|
|
19
19
|
import { COMPANY_ROOT } from './file-reader.js';
|
|
20
20
|
import { ActivityStream } from './activity-stream.js';
|
|
21
21
|
import { saveCompletedWave } from './wave-tracker.js';
|
|
22
|
+
import { waveMultiplexer } from './wave-multiplexer.js';
|
|
22
23
|
|
|
23
24
|
/* ─── Types ──────────────────────────────────── */
|
|
24
25
|
|
|
@@ -768,9 +769,17 @@ ${state.continuous ? `## Continuous Improvement Mode (ON)
|
|
|
768
769
|
console.error(`[Supervisor] Failed to auto-save wave ${state.waveId}:`, err);
|
|
769
770
|
}
|
|
770
771
|
|
|
771
|
-
// OOM prevention: clear accumulated
|
|
772
|
+
// OOM prevention: clear accumulated state + wave multiplexer sessions
|
|
772
773
|
state.pendingDirectives = [];
|
|
773
774
|
state.pendingQuestions = [];
|
|
775
|
+
|
|
776
|
+
// Delayed cleanup: remove wave sessions from multiplexer + supervisor map
|
|
777
|
+
// (delay allows SSE clients to receive final events)
|
|
778
|
+
setTimeout(() => {
|
|
779
|
+
waveMultiplexer.cleanupWave(state.waveId);
|
|
780
|
+
this.supervisors.delete(state.waveId);
|
|
781
|
+
console.log(`[Supervisor] Cleaned up wave ${state.waveId} from memory`);
|
|
782
|
+
}, 60_000).unref(); // 1 minute after wave done
|
|
774
783
|
}
|
|
775
784
|
}
|
|
776
785
|
|
|
@@ -182,6 +182,12 @@ class WaveMultiplexer {
|
|
|
182
182
|
const key = `${event.roleId}:${event.seq}`;
|
|
183
183
|
if (client.sentEvents.has(key)) return;
|
|
184
184
|
client.sentEvents.add(key);
|
|
185
|
+
// OOM prevention: cap sentEvents to prevent unbounded Set growth
|
|
186
|
+
if (client.sentEvents.size > 5000) {
|
|
187
|
+
const entries = Array.from(client.sentEvents);
|
|
188
|
+
client.sentEvents.clear();
|
|
189
|
+
for (const e of entries.slice(-2000)) client.sentEvents.add(e);
|
|
190
|
+
}
|
|
185
191
|
|
|
186
192
|
const waveSeq = client.waveSeq++;
|
|
187
193
|
sendSSE(client, 'wave:event', {
|