@aexol/spectral 0.5.1 → 0.6.0
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/relay/dispatcher.js
CHANGED
|
@@ -465,11 +465,13 @@ export function handleClientMessage(frame, deps) {
|
|
|
465
465
|
}
|
|
466
466
|
const content = message.content;
|
|
467
467
|
const isLoop = message.loop === true;
|
|
468
|
+
const loopMaxIterations = message.loopMaxIterations;
|
|
469
|
+
const loopGoal = message.loopGoal;
|
|
468
470
|
const validImages = coerceImages(message.images);
|
|
469
471
|
// Set autonomous iterative loop state before firing the prompt.
|
|
470
472
|
// loop:true → start/renew loop with the current content as original prompt;
|
|
471
473
|
// loop:false → stop any active loop.
|
|
472
|
-
manager.setLoopActive(sessionId, isLoop, content);
|
|
474
|
+
manager.setLoopActive(sessionId, isLoop, content, loopMaxIterations, loopGoal);
|
|
473
475
|
// 2. Attach (idempotent). On first attach we capture the replay payload
|
|
474
476
|
// and synthesize a `session_ready` ws_event so the browser sees the
|
|
475
477
|
// same first frame it would have on a direct WS connection.
|
|
@@ -642,6 +642,7 @@ export class SessionStreamManager {
|
|
|
642
642
|
// the next agent_end would otherwise trigger another prompt.
|
|
643
643
|
stream.loopActive = false;
|
|
644
644
|
stream.loopOriginalPrompt = null;
|
|
645
|
+
stream.loopGoal = null;
|
|
645
646
|
stream.loopIterationCount = 0;
|
|
646
647
|
// Dispose the pi bridge immediately — this tears down pi's session and
|
|
647
648
|
// unsubscribe. The bridge's own event handler is detached; no further
|
|
@@ -731,13 +732,22 @@ export class SessionStreamManager {
|
|
|
731
732
|
* The loop stops when the agent emits `<LOOP_DONE>` in its response or the
|
|
732
733
|
* safety iteration limit is reached.
|
|
733
734
|
*/
|
|
734
|
-
setLoopActive(sessionId, active, originalPrompt) {
|
|
735
|
+
setLoopActive(sessionId, active, originalPrompt, maxIterations, goal) {
|
|
735
736
|
const stream = this.streams.get(sessionId);
|
|
736
737
|
if (stream) {
|
|
737
738
|
stream.loopActive = active;
|
|
738
739
|
stream.loopOriginalPrompt = active ? (originalPrompt ?? null) : null;
|
|
739
|
-
if (
|
|
740
|
+
if (active) {
|
|
741
|
+
if (maxIterations !== undefined && maxIterations > 0) {
|
|
742
|
+
stream.loopMaxIterations = Math.min(maxIterations, MAX_LOOP_ITERATIONS);
|
|
743
|
+
}
|
|
744
|
+
stream.loopGoal = goal?.trim() || null;
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
740
747
|
stream.loopIterationCount = 0;
|
|
748
|
+
stream.loopMaxIterations = MAX_LOOP_ITERATIONS;
|
|
749
|
+
stream.loopGoal = null;
|
|
750
|
+
}
|
|
741
751
|
}
|
|
742
752
|
}
|
|
743
753
|
/**
|
|
@@ -825,6 +835,20 @@ export class SessionStreamManager {
|
|
|
825
835
|
* entry → prepareCompaction() returns undefined → "Already compacted"
|
|
826
836
|
* → the trigger's onError handler catches it harmlessly.
|
|
827
837
|
*/
|
|
838
|
+
buildLoopPrompt(stream) {
|
|
839
|
+
const parts = [];
|
|
840
|
+
if (stream.loopGoal) {
|
|
841
|
+
parts.push(`[GOAL]: ${stream.loopGoal}`);
|
|
842
|
+
parts.push("");
|
|
843
|
+
}
|
|
844
|
+
parts.push(stream.loopOriginalPrompt);
|
|
845
|
+
if (stream.loopGoal) {
|
|
846
|
+
parts.push("");
|
|
847
|
+
parts.push("After completing your work, evaluate whether the goal has been FULLY achieved. " +
|
|
848
|
+
'Include <LOOP_DONE> in your response ONLY if you are confident the goal is completely met.');
|
|
849
|
+
}
|
|
850
|
+
return parts.join("\n");
|
|
851
|
+
}
|
|
828
852
|
async sendNextLoopIteration(stream) {
|
|
829
853
|
const shouldCompact = stream.bridge.compact &&
|
|
830
854
|
typeof stream.contextWindowUsed === "number" &&
|
|
@@ -846,7 +870,7 @@ export class SessionStreamManager {
|
|
|
846
870
|
}
|
|
847
871
|
}
|
|
848
872
|
}
|
|
849
|
-
await this.prompt(stream.sessionId, stream
|
|
873
|
+
await this.prompt(stream.sessionId, this.buildLoopPrompt(stream), undefined);
|
|
850
874
|
}
|
|
851
875
|
// --- internals ----------------------------------------------------------
|
|
852
876
|
createStream(sessionId, history) {
|
|
@@ -877,6 +901,8 @@ export class SessionStreamManager {
|
|
|
877
901
|
loopActive: false,
|
|
878
902
|
loopIterationCount: 0,
|
|
879
903
|
loopOriginalPrompt: null,
|
|
904
|
+
loopMaxIterations: MAX_LOOP_ITERATIONS,
|
|
905
|
+
loopGoal: null,
|
|
880
906
|
forkCompactSourceId: forkSourceId ?? null,
|
|
881
907
|
compacting: false,
|
|
882
908
|
contextWindowUsed: null,
|
|
@@ -1070,15 +1096,17 @@ export class SessionStreamManager {
|
|
|
1070
1096
|
stream.loopActive = false;
|
|
1071
1097
|
stream.loopIterationCount = 0;
|
|
1072
1098
|
stream.loopOriginalPrompt = null;
|
|
1099
|
+
stream.loopGoal = null;
|
|
1073
1100
|
this.broadcast(stream, {
|
|
1074
1101
|
type: "loop_complete",
|
|
1075
1102
|
iterations: completedIterations,
|
|
1076
1103
|
});
|
|
1077
1104
|
}
|
|
1078
|
-
else if (stream.loopIterationCount >=
|
|
1079
|
-
console.log(`[loop] max iterations (${
|
|
1105
|
+
else if (stream.loopIterationCount >= stream.loopMaxIterations) {
|
|
1106
|
+
console.log(`[loop] max iterations (${stream.loopMaxIterations}) reached, stopping`);
|
|
1080
1107
|
stream.loopActive = false;
|
|
1081
1108
|
stream.loopOriginalPrompt = null;
|
|
1109
|
+
stream.loopGoal = null;
|
|
1082
1110
|
this.broadcast(stream, {
|
|
1083
1111
|
type: "loop_max_iterations",
|
|
1084
1112
|
iterations: stream.loopIterationCount,
|
|
@@ -1086,17 +1114,18 @@ export class SessionStreamManager {
|
|
|
1086
1114
|
}
|
|
1087
1115
|
else {
|
|
1088
1116
|
stream.loopIterationCount++;
|
|
1089
|
-
console.log(`[loop] iteration ${stream.loopIterationCount}/${
|
|
1117
|
+
console.log(`[loop] iteration ${stream.loopIterationCount}/${stream.loopMaxIterations}`);
|
|
1090
1118
|
this.broadcast(stream, {
|
|
1091
1119
|
type: "loop_iteration",
|
|
1092
1120
|
iteration: stream.loopIterationCount,
|
|
1093
|
-
maxIterations:
|
|
1121
|
+
maxIterations: stream.loopMaxIterations,
|
|
1094
1122
|
prompt: stream.loopOriginalPrompt,
|
|
1095
1123
|
});
|
|
1096
1124
|
void this.sendNextLoopIteration(stream).catch((err) => {
|
|
1097
1125
|
console.error(`[loop] iteration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1098
1126
|
stream.loopActive = false;
|
|
1099
1127
|
stream.loopOriginalPrompt = null;
|
|
1128
|
+
stream.loopGoal = null;
|
|
1100
1129
|
});
|
|
1101
1130
|
}
|
|
1102
1131
|
}
|