@aexol/spectral 0.4.10 → 0.4.12
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/dist/server/session-stream.js +37 -42
- package/package.json +1 -1
|
@@ -47,8 +47,6 @@ const MAX_LOOP_ITERATIONS = 100;
|
|
|
47
47
|
* Aligned with the observational memory extension default (memory/config.ts).
|
|
48
48
|
*/
|
|
49
49
|
const LOOP_COMPACTION_THRESHOLD_TOKENS = 50_000;
|
|
50
|
-
/** Maximum time (ms) to wait for compaction to finish between loop iterations. */
|
|
51
|
-
const LOOP_COMPACTION_MAX_WAIT_MS = 30_000;
|
|
52
50
|
/**
|
|
53
51
|
* Number of accumulated wire events before flushing the in-flight turn
|
|
54
52
|
* to SQLite. Batch-persisting means a server crash mid-turn only loses
|
|
@@ -546,42 +544,44 @@ export class SessionStreamManager {
|
|
|
546
544
|
* A duplicate call from the extension's delayed compaction trigger is
|
|
547
545
|
* harmless — pi throws "Already compacted" which the extension catches.
|
|
548
546
|
*/
|
|
549
|
-
async sendNextLoopIteration(stream) {
|
|
550
|
-
// The observational-memory extension's compaction-trigger fires on
|
|
551
|
-
// agent_end (via setTimeout, so it's slightly delayed). We wait for
|
|
552
|
-
// either:
|
|
553
|
-
// (a) compaction to start and finish (stream.compacting becomes
|
|
554
|
-
// true then false), or
|
|
555
|
-
// (b) a grace period to elapse without compaction starting (context
|
|
556
|
-
// was below the threshold, or the extension deferred).
|
|
557
|
-
//
|
|
558
|
-
// This guarantees the LLM sees the compacted context when the
|
|
559
|
-
// compaction threshold was reached, and doesn't delay the loop when
|
|
560
|
-
// no compaction is needed.
|
|
561
|
-
const GRACE_MS = 500; // give the trigger's setTimeout a chance
|
|
562
|
-
const start = Date.now();
|
|
563
|
-
// Wait for the trigger to start compaction, or for grace period to pass.
|
|
564
|
-
while (!stream.compacting && Date.now() - start < GRACE_MS) {
|
|
565
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
566
|
-
}
|
|
567
|
-
if (stream.compacting) {
|
|
568
|
-
console.log("[loop] compaction in progress, waiting...");
|
|
569
|
-
await this.waitForCompactionOrTimeout(stream);
|
|
570
|
-
console.log("[loop] compaction finished, sending next iteration");
|
|
571
|
-
}
|
|
572
|
-
await this.prompt(stream.sessionId, stream.loopOriginalPrompt, undefined);
|
|
573
|
-
}
|
|
574
547
|
/**
|
|
575
|
-
*
|
|
548
|
+
* Send the next prompt for an autonomous loop iteration, compacting first
|
|
549
|
+
* if the context window exceeds the threshold.
|
|
550
|
+
*
|
|
551
|
+
* Instead of polling for the extension's delayed compaction-trigger (which
|
|
552
|
+
* fires via setTimeout), we proactively call bridge.compact() directly.
|
|
553
|
+
* bridge.compact() → session.compact() → fires session_before_compact
|
|
554
|
+
* (where the extension's compaction-hook provides the observational-memory
|
|
555
|
+
* summary), then appends the compaction entry, reloads the compacted
|
|
556
|
+
* context into agent.state.messages, and emits compaction_start/end.
|
|
557
|
+
*
|
|
558
|
+
* The extension's trigger still fires (via setTimeout), but by the time
|
|
559
|
+
* its callback runs, bridge.compact() has already appended the compaction
|
|
560
|
+
* entry → prepareCompaction() returns undefined → "Already compacted"
|
|
561
|
+
* → the trigger's onError handler catches it harmlessly.
|
|
576
562
|
*/
|
|
577
|
-
async
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
563
|
+
async sendNextLoopIteration(stream) {
|
|
564
|
+
const shouldCompact = stream.bridge.compact &&
|
|
565
|
+
typeof stream.contextWindowUsed === "number" &&
|
|
566
|
+
stream.contextWindowUsed > LOOP_COMPACTION_THRESHOLD_TOKENS;
|
|
567
|
+
if (shouldCompact) {
|
|
568
|
+
try {
|
|
569
|
+
console.log(`[loop] compacting context (~${stream.contextWindowUsed.toLocaleString()} tokens > ${LOOP_COMPACTION_THRESHOLD_TOKENS.toLocaleString()} threshold)`);
|
|
570
|
+
await stream.bridge.compact();
|
|
571
|
+
console.log("[loop] compaction complete, sending next iteration");
|
|
572
|
+
}
|
|
573
|
+
catch (err) {
|
|
574
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
575
|
+
if (msg === "Already compacted" ||
|
|
576
|
+
msg === "Nothing to compact (session too small)") {
|
|
577
|
+
console.log("[loop] compaction already done or not needed, proceeding");
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
console.error(`[loop] compaction failed: ${msg}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
584
583
|
}
|
|
584
|
+
await this.prompt(stream.sessionId, stream.loopOriginalPrompt, undefined);
|
|
585
585
|
}
|
|
586
586
|
// --- internals ----------------------------------------------------------
|
|
587
587
|
createStream(sessionId, history) {
|
|
@@ -614,7 +614,6 @@ export class SessionStreamManager {
|
|
|
614
614
|
loopOriginalPrompt: null,
|
|
615
615
|
forkCompactSourceId: forkSourceId ?? null,
|
|
616
616
|
compacting: false,
|
|
617
|
-
compactionEndResolve: null,
|
|
618
617
|
contextWindowUsed: null,
|
|
619
618
|
contextWindowMax: null,
|
|
620
619
|
};
|
|
@@ -763,17 +762,13 @@ export class SessionStreamManager {
|
|
|
763
762
|
if (event.contextWindowMax != null)
|
|
764
763
|
stream.contextWindowMax = event.contextWindowMax;
|
|
765
764
|
}
|
|
766
|
-
// Track compaction lifecycle so
|
|
767
|
-
//
|
|
765
|
+
// Track compaction lifecycle so prompt() can block new messages while
|
|
766
|
+
// compaction is running.
|
|
768
767
|
if (event.type === "compaction_start") {
|
|
769
768
|
stream.compacting = true;
|
|
770
769
|
}
|
|
771
770
|
if (event.type === "compaction_end") {
|
|
772
771
|
stream.compacting = false;
|
|
773
|
-
if (stream.compactionEndResolve) {
|
|
774
|
-
stream.compactionEndResolve();
|
|
775
|
-
stream.compactionEndResolve = null;
|
|
776
|
-
}
|
|
777
772
|
}
|
|
778
773
|
this.broadcast(stream, event);
|
|
779
774
|
if (event.type === "agent_end") {
|