poly-weaver 0.14.3 → 0.14.6
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/crosscheck/broadcast-composer.d.ts +16 -9
- package/dist/crosscheck/broadcast-composer.d.ts.map +1 -1
- package/dist/crosscheck/broadcast-composer.js +43 -32
- package/dist/crosscheck/broadcast-composer.js.map +1 -1
- package/dist/crosscheck/coordinator.d.ts +16 -0
- package/dist/crosscheck/coordinator.d.ts.map +1 -1
- package/dist/crosscheck/coordinator.js +340 -58
- package/dist/crosscheck/coordinator.js.map +1 -1
- package/dist/crosscheck/key-helpers.d.ts +2 -2
- package/dist/crosscheck/key-helpers.d.ts.map +1 -1
- package/dist/crosscheck/key-helpers.js +6 -4
- package/dist/crosscheck/key-helpers.js.map +1 -1
- package/dist/crosscheck/state.d.ts +12 -1
- package/dist/crosscheck/state.d.ts.map +1 -1
- package/dist/crosscheck/state.js +77 -17
- package/dist/crosscheck/state.js.map +1 -1
- package/dist/flow-editor/tui.d.ts +5 -0
- package/dist/flow-editor/tui.d.ts.map +1 -1
- package/dist/flow-editor/tui.js +34 -23
- package/dist/flow-editor/tui.js.map +1 -1
- package/dist/offscreen/controller.d.ts.map +1 -1
- package/dist/offscreen/controller.js +19 -2
- package/dist/offscreen/controller.js.map +1 -1
- package/dist/offscreen/session-turn-monitor.d.ts +18 -2
- package/dist/offscreen/session-turn-monitor.d.ts.map +1 -1
- package/dist/offscreen/session-turn-monitor.js +156 -18
- package/dist/offscreen/session-turn-monitor.js.map +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +2 -9
- package/dist/orchestrator.js.map +1 -1
- package/dist/paste-image/persistence.d.ts +4 -0
- package/dist/paste-image/persistence.d.ts.map +1 -1
- package/dist/paste-image/persistence.js +17 -0
- package/dist/paste-image/persistence.js.map +1 -1
- package/dist/preview-panel.d.ts.map +1 -1
- package/dist/preview-panel.js +2 -2
- package/dist/preview-panel.js.map +1 -1
- package/dist/providers/copilot/completion.d.ts +3 -0
- package/dist/providers/copilot/completion.d.ts.map +1 -1
- package/dist/providers/copilot/completion.js +96 -7
- package/dist/providers/copilot/completion.js.map +1 -1
- package/dist/providers/copilot/session.d.ts +10 -8
- package/dist/providers/copilot/session.d.ts.map +1 -1
- package/dist/providers/copilot/session.js +49 -11
- package/dist/providers/copilot/session.js.map +1 -1
- package/dist/providers/copilot/strategy.d.ts.map +1 -1
- package/dist/providers/copilot/strategy.js +5 -1
- package/dist/providers/copilot/strategy.js.map +1 -1
- package/dist/providers/fork.d.ts +25 -0
- package/dist/providers/fork.d.ts.map +1 -1
- package/dist/providers/fork.js.map +1 -1
- package/dist/providers/types.d.ts +4 -2
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/startup-tui.d.ts.map +1 -1
- package/dist/startup-tui.js +22 -12
- package/dist/startup-tui.js.map +1 -1
- package/dist/terminal/host-input-mode.d.ts +1 -0
- package/dist/terminal/host-input-mode.d.ts.map +1 -1
- package/dist/terminal/host-input-mode.js +11 -1
- package/dist/terminal/host-input-mode.js.map +1 -1
- package/dist/terminal/host.js +1 -1
- package/dist/terminal/host.js.map +1 -1
- package/dist/terminal/input-router.d.ts +14 -13
- package/dist/terminal/input-router.d.ts.map +1 -1
- package/dist/terminal/input-router.js +37 -35
- package/dist/terminal/input-router.js.map +1 -1
- package/dist/terminal/key-event-source.d.ts +2 -0
- package/dist/terminal/key-event-source.d.ts.map +1 -1
- package/dist/terminal/key-event-source.js +4 -1
- package/dist/terminal/key-event-source.js.map +1 -1
- package/dist/terminal/key-to-bytes.js +2 -0
- package/dist/terminal/key-to-bytes.js.map +1 -1
- package/dist/terminal/right-click-paste.d.ts +9 -0
- package/dist/terminal/right-click-paste.d.ts.map +1 -0
- package/dist/terminal/right-click-paste.js +37 -0
- package/dist/terminal/right-click-paste.js.map +1 -0
- package/dist/terminal/sgr-mouse.d.ts +17 -0
- package/dist/terminal/sgr-mouse.d.ts.map +1 -0
- package/dist/terminal/sgr-mouse.js +37 -0
- package/dist/terminal/sgr-mouse.js.map +1 -0
- package/dist/terminal/stdin-byte-source.d.ts +7 -1
- package/dist/terminal/stdin-byte-source.d.ts.map +1 -1
- package/dist/terminal/stdin-byte-source.js +14 -3
- package/dist/terminal/stdin-byte-source.js.map +1 -1
- package/dist/terminal/windows-terminal-shift-enter.d.ts +24 -0
- package/dist/terminal/windows-terminal-shift-enter.d.ts.map +1 -0
- package/dist/terminal/windows-terminal-shift-enter.js +316 -0
- package/dist/terminal/windows-terminal-shift-enter.js.map +1 -0
- package/dist/terminal-input.d.ts +12 -1
- package/dist/terminal-input.d.ts.map +1 -1
- package/dist/terminal-input.js +211 -109
- package/dist/terminal-input.js.map +1 -1
- package/dist/text-editing/emacs-input.d.ts +3 -0
- package/dist/text-editing/emacs-input.d.ts.map +1 -1
- package/dist/text-editing/emacs-input.js +8 -1
- package/dist/text-editing/emacs-input.js.map +1 -1
- package/dist/user/feedback-editing.d.ts.map +1 -1
- package/dist/user/feedback-editing.js +2 -5
- package/dist/user/feedback-editing.js.map +1 -1
- package/dist/user/host-curate-prompt.d.ts.map +1 -1
- package/dist/user/host-curate-prompt.js +18 -9
- package/dist/user/host-curate-prompt.js.map +1 -1
- package/dist/user/host-prompt.d.ts.map +1 -1
- package/dist/user/host-prompt.js +18 -9
- package/dist/user/host-prompt.js.map +1 -1
- package/package.json +2 -1
|
@@ -6,8 +6,10 @@ import { buildExecutorInitialPrompt, buildExecutorCrosscheckPrompt, buildExecuto
|
|
|
6
6
|
import { sendInquiryQuestion } from "../offscreen/inquiry-sender.js";
|
|
7
7
|
import { SessionTurnMonitor } from "../offscreen/session-turn-monitor.js";
|
|
8
8
|
import { resolveProvider } from "../providers/registry.js";
|
|
9
|
+
import { readSessionFileWithStatus } from "../session/reader.js";
|
|
9
10
|
import { encodeKeysToBytes } from "../terminal/key-to-bytes.js";
|
|
10
|
-
import {
|
|
11
|
+
import { WHEEL_LINES_PER_EVENT, } from "../terminal/input-router.js";
|
|
12
|
+
import { isUnmodifiedRightButtonGestureFrame, isUnmodifiedRightButtonPress, isUnmodifiedRightButtonRelease, parseSgrMouseFrame, } from "../terminal/sgr-mouse.js";
|
|
11
13
|
import { renderViewportLines, shouldShowVtCursor } from "../terminal/render.js";
|
|
12
14
|
import { buildSplitSurface, computeSplitMetrics, SPLIT_COLLAPSE_AT_COLS, } from "../user/split-surface.js";
|
|
13
15
|
import { LiveAgentSession } from "./live-agent-session.js";
|
|
@@ -18,6 +20,8 @@ import { availableBroadcastModes, BroadcastComposer, } from "./broadcast-compose
|
|
|
18
20
|
import { isControlKey, isEnterKey } from "./key-helpers.js";
|
|
19
21
|
import { TASK } from "../flow/types.js";
|
|
20
22
|
import { isArtifactViewerKey } from "../dump/hotkey.js";
|
|
23
|
+
import { handleImagePasteIntent, PASTING_IMAGE_HINT, } from "../paste-image/intents.js";
|
|
24
|
+
import { finalizeImageAttachments, hasImagePlaceholders, } from "../paste-image/persistence.js";
|
|
21
25
|
const HEADER_ROWS = 1;
|
|
22
26
|
const RESULT_POLL_MS = 300;
|
|
23
27
|
const ACTIVITY_HEARTBEAT_MS = 60_000;
|
|
@@ -70,6 +74,8 @@ export class CrosscheckCoordinator {
|
|
|
70
74
|
dispatching = false;
|
|
71
75
|
broadcastSendActive = false;
|
|
72
76
|
dispatchAbort = new AbortController();
|
|
77
|
+
operationFence = 0;
|
|
78
|
+
activeOperationAborts = new Set();
|
|
73
79
|
dispatchOperations = new Set();
|
|
74
80
|
activityOperations = new Set();
|
|
75
81
|
resendAfterLaunch = new Set();
|
|
@@ -83,6 +89,7 @@ export class CrosscheckCoordinator {
|
|
|
83
89
|
transientFlash;
|
|
84
90
|
crosscheckProgress;
|
|
85
91
|
selectionAgent;
|
|
92
|
+
focusedAgentRightButtonOwner;
|
|
86
93
|
crosscheckTurnIdentities = new Map();
|
|
87
94
|
constructor(options) {
|
|
88
95
|
this.options = options;
|
|
@@ -101,6 +108,7 @@ export class CrosscheckCoordinator {
|
|
|
101
108
|
this.detachUi = this.options.host.attachParentUi(this);
|
|
102
109
|
const layout = this.options.host.getLayout();
|
|
103
110
|
const dimensions = this.paneDimensions(layout.columns, layout.childRows);
|
|
111
|
+
await this.reconcilePersistedHistory();
|
|
104
112
|
const persisted = this.store.snapshot();
|
|
105
113
|
const launchAbort = new AbortController();
|
|
106
114
|
this.launchAbort = launchAbort;
|
|
@@ -116,7 +124,8 @@ export class CrosscheckCoordinator {
|
|
|
116
124
|
try {
|
|
117
125
|
const config = this.options.configurations[agent];
|
|
118
126
|
const resultFile = persisted.generation.resultFiles[agent];
|
|
119
|
-
const
|
|
127
|
+
const resultState = persisted.generation.rewind?.[agent];
|
|
128
|
+
const resultReady = resultState ? false : await fileHasContent(resultFile);
|
|
120
129
|
const prior = persisted.sessions[agent];
|
|
121
130
|
const resumeIdentity = prior?.driver === config.driver
|
|
122
131
|
? {
|
|
@@ -141,13 +150,15 @@ export class CrosscheckCoordinator {
|
|
|
141
150
|
: resumeIdentity && priorSubmitCount !== undefined
|
|
142
151
|
? await resolveProvider(config.driver).live.classifyGenerationDelivery(prior.sessionFilePath, priorSubmitCount) === "present"
|
|
143
152
|
: undefined;
|
|
144
|
-
const prompt =
|
|
145
|
-
?
|
|
146
|
-
:
|
|
147
|
-
?
|
|
148
|
-
: activityState === "
|
|
149
|
-
?
|
|
150
|
-
:
|
|
153
|
+
const prompt = persisted.generation.rewind
|
|
154
|
+
? ""
|
|
155
|
+
: generationDelivered === false
|
|
156
|
+
? resumePrompt
|
|
157
|
+
: activityState === "idle"
|
|
158
|
+
? ""
|
|
159
|
+
: activityState === "working"
|
|
160
|
+
? RESUME_CONTINUE_PROMPT
|
|
161
|
+
: resumePrompt;
|
|
151
162
|
const session = await (this.options.launchSession ?? LiveAgentSession.launch)({
|
|
152
163
|
driver: config.driver,
|
|
153
164
|
workdir: this.options.workdir,
|
|
@@ -176,6 +187,13 @@ export class CrosscheckCoordinator {
|
|
|
176
187
|
});
|
|
177
188
|
this.launchingSessions.delete(agent);
|
|
178
189
|
const monitor = new SessionTurnMonitor(config.driver, session.identity.sessionFilePath, session.baseline, {
|
|
190
|
+
initialHistorySnapshot: persisted.sessions[agent]?.historySnapshot,
|
|
191
|
+
onHistorySnapshot: (snapshot) => {
|
|
192
|
+
void this.store.updateSessionHistory(agent, snapshot);
|
|
193
|
+
},
|
|
194
|
+
onHistoryRegression: (_regression, snapshot) => {
|
|
195
|
+
this.runDispatchOperation(agent, this.invalidateGenerationForRewind(agent, snapshot), "invalidate rewound generation");
|
|
196
|
+
},
|
|
179
197
|
onChange: (idle) => {
|
|
180
198
|
this.refreshStatusHint();
|
|
181
199
|
this.options.host.scheduleParentUiRender();
|
|
@@ -200,9 +218,11 @@ export class CrosscheckCoordinator {
|
|
|
200
218
|
session,
|
|
201
219
|
monitor,
|
|
202
220
|
resultReady,
|
|
221
|
+
resultState,
|
|
203
222
|
freshForPendingDispatch,
|
|
204
223
|
launchTurnExpected: session.resumed !== true || prompt.length > 0,
|
|
205
224
|
});
|
|
225
|
+
monitor.startHistoryPolling();
|
|
206
226
|
await this.store.update((state) => {
|
|
207
227
|
const dispatch = state.generation.dispatch;
|
|
208
228
|
if (freshForPendingDispatch
|
|
@@ -221,6 +241,9 @@ export class CrosscheckCoordinator {
|
|
|
221
241
|
...(freshForPendingDispatch && {
|
|
222
242
|
pendingDispatchBaselineReset: true,
|
|
223
243
|
}),
|
|
244
|
+
...(persisted.sessions[agent]?.historySnapshot && {
|
|
245
|
+
historySnapshot: persisted.sessions[agent].historySnapshot,
|
|
246
|
+
}),
|
|
224
247
|
};
|
|
225
248
|
});
|
|
226
249
|
if (freshForPendingDispatch
|
|
@@ -257,7 +280,7 @@ export class CrosscheckCoordinator {
|
|
|
257
280
|
}
|
|
258
281
|
await this.reconcilePersistedDispatch();
|
|
259
282
|
await this.maybeSettleReconcileGate();
|
|
260
|
-
this.broadcast.reconcileMode(this.currentReadiness());
|
|
283
|
+
this.broadcast.reconcileMode(this.currentReadiness(), this.currentGenerationPresentation());
|
|
261
284
|
this.refreshMouseModes();
|
|
262
285
|
this.resultTimer = setInterval(() => {
|
|
263
286
|
void this.refreshResults();
|
|
@@ -294,7 +317,7 @@ export class CrosscheckCoordinator {
|
|
|
294
317
|
focusSwitchHint: this.focusSwitchHint(),
|
|
295
318
|
version: `poly-weaver v${APP_VERSION}`,
|
|
296
319
|
transient: this.currentTransientFlash(),
|
|
297
|
-
});
|
|
320
|
+
}, this.currentGenerationPresentation());
|
|
298
321
|
const lines = [...top.lines.slice(0, agentAreaRows), ...broadcastPane.lines];
|
|
299
322
|
while (lines.length < totalRows)
|
|
300
323
|
lines.push("");
|
|
@@ -534,16 +557,22 @@ export class CrosscheckCoordinator {
|
|
|
534
557
|
if (event.kind === "wheel" || event.kind === "raw") {
|
|
535
558
|
flush();
|
|
536
559
|
forwardedSide = this.focus === "broadcast" ? undefined : this.focus;
|
|
537
|
-
|
|
560
|
+
const mouseFrame = event.kind === "raw" ? parseSgrMouseFrame(event) : undefined;
|
|
561
|
+
if (event.kind === "raw"
|
|
562
|
+
&& this.routeFocusedAgentRightButtonGesture(mouseFrame))
|
|
563
|
+
continue;
|
|
564
|
+
if (this.routeMouse(event, mouseFrame))
|
|
538
565
|
continue;
|
|
539
566
|
}
|
|
540
567
|
if (this.focus === "broadcast") {
|
|
541
568
|
flush();
|
|
542
569
|
const submissionState = this.currentSubmissionState();
|
|
543
|
-
const outcome = this.broadcast.handleKey(event, this.currentReadiness(), submissionState);
|
|
570
|
+
const outcome = this.broadcast.handleKey(event, this.currentReadiness(), submissionState, 0, this.currentGenerationPresentation());
|
|
544
571
|
if (outcome === "submit") {
|
|
545
572
|
this.runDispatchOperation(undefined, this.submitBroadcast(), "broadcast send");
|
|
546
573
|
}
|
|
574
|
+
if (outcome === "clipboard-paste")
|
|
575
|
+
this.requestBroadcastPaste();
|
|
547
576
|
if (outcome === "changed" || outcome === "mode-cycle")
|
|
548
577
|
broadcastChanged = true;
|
|
549
578
|
continue;
|
|
@@ -571,6 +600,12 @@ export class CrosscheckCoordinator {
|
|
|
571
600
|
}
|
|
572
601
|
return true;
|
|
573
602
|
}
|
|
603
|
+
onClipboardPasteRequest() {
|
|
604
|
+
if (this.focus !== "broadcast")
|
|
605
|
+
return false;
|
|
606
|
+
this.requestBroadcastPaste();
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
574
609
|
onResize() {
|
|
575
610
|
this.resizeSessions();
|
|
576
611
|
}
|
|
@@ -592,12 +627,14 @@ export class CrosscheckCoordinator {
|
|
|
592
627
|
const focused = this.focus === agent;
|
|
593
628
|
const config = runtime?.config ?? this.options.configurations[agent];
|
|
594
629
|
const monitor = runtime?.monitor;
|
|
630
|
+
const resultSlot = runtime?.resultState
|
|
631
|
+
?? (runtime?.resultReady ? "done" : undefined);
|
|
595
632
|
const label = [
|
|
596
633
|
config.driver,
|
|
597
634
|
config.model,
|
|
598
635
|
config.effort,
|
|
599
636
|
monitor ? (monitor.isIdle ? "idle" : "working") : "starting",
|
|
600
|
-
|
|
637
|
+
resultSlot,
|
|
601
638
|
].filter(Boolean).join(" | ");
|
|
602
639
|
const color = focused
|
|
603
640
|
? `${MUTED_YELLOW_BG}${TRUE_BLACK_FG}${bold}`
|
|
@@ -619,10 +656,10 @@ export class CrosscheckCoordinator {
|
|
|
619
656
|
showCursor: focused && shouldShowVtCursor(session.vt),
|
|
620
657
|
};
|
|
621
658
|
}
|
|
622
|
-
routeMouse(event) {
|
|
659
|
+
routeMouse(event, frame) {
|
|
623
660
|
const point = event.kind === "wheel"
|
|
624
661
|
? { column: event.column, row: event.row, button: undefined, final: undefined }
|
|
625
|
-
:
|
|
662
|
+
: frame;
|
|
626
663
|
if (!point)
|
|
627
664
|
return false;
|
|
628
665
|
const layout = this.options.host.getLayout();
|
|
@@ -670,17 +707,62 @@ export class CrosscheckCoordinator {
|
|
|
670
707
|
}
|
|
671
708
|
if (!runtime.session.hasMouseReporting() && !runtime.session.vt.isAlternate)
|
|
672
709
|
return true;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (bytes.length > 0)
|
|
681
|
-
runtime.session.pty.write(bytes);
|
|
710
|
+
if (event.kind === "wheel") {
|
|
711
|
+
const bytes = encodeKeysToBytes([{ ...event, column: target.localColumn, row: localRow }], { wheelMode: "sgr" });
|
|
712
|
+
if (bytes.length > 0)
|
|
713
|
+
runtime.session.pty.write(bytes);
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
this.forwardMouseFrameToAgent(target.agent, point);
|
|
682
717
|
return true;
|
|
683
718
|
}
|
|
719
|
+
routeFocusedAgentRightButtonGesture(frame) {
|
|
720
|
+
if (!frame)
|
|
721
|
+
return false;
|
|
722
|
+
const owner = this.focusedAgentRightButtonOwner;
|
|
723
|
+
if (owner && isUnmodifiedRightButtonGestureFrame(frame)) {
|
|
724
|
+
this.forwardMouseFrameToAgent(owner, frame);
|
|
725
|
+
if (isUnmodifiedRightButtonRelease(frame)) {
|
|
726
|
+
this.focusedAgentRightButtonOwner = undefined;
|
|
727
|
+
}
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
if (this.focus === "broadcast" || !isUnmodifiedRightButtonPress(frame)) {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
this.focusedAgentRightButtonOwner = this.focus;
|
|
734
|
+
this.forwardMouseFrameToAgent(this.focus, frame);
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
forwardMouseFrameToAgent(agent, frame) {
|
|
738
|
+
const runtime = this.agents.get(agent);
|
|
739
|
+
if (!runtime)
|
|
740
|
+
return;
|
|
741
|
+
if (!runtime.session.hasMouseReporting() && !runtime.session.vt.isAlternate)
|
|
742
|
+
return;
|
|
743
|
+
const point = this.agentPaneMousePoint(agent, frame);
|
|
744
|
+
runtime.session.pty.write(Buffer.from(`\x1b[<${frame.button};${point.localColumn};${point.localRow}${frame.final}`, "ascii"));
|
|
745
|
+
}
|
|
746
|
+
agentPaneMousePoint(agent, point) {
|
|
747
|
+
const layout = this.options.host.getLayout();
|
|
748
|
+
const totalRows = layout.childRows;
|
|
749
|
+
const agentAreaRows = Math.max(1, totalRows - this.broadcastRowCount(layout.columns, totalRows));
|
|
750
|
+
const metrics = computeSplitMetrics({
|
|
751
|
+
cols: layout.columns,
|
|
752
|
+
rows: agentAreaRows,
|
|
753
|
+
collapseAt: SPLIT_COLLAPSE_AT_COLS,
|
|
754
|
+
});
|
|
755
|
+
const paneStart = metrics.collapsed || agent === "agent-1"
|
|
756
|
+
? 1
|
|
757
|
+
: metrics.leftWidth + 2;
|
|
758
|
+
const paneWidth = metrics.collapsed || agent === "agent-1"
|
|
759
|
+
? metrics.leftWidth
|
|
760
|
+
: metrics.rightWidth;
|
|
761
|
+
const localColumn = Math.min(paneWidth, Math.max(1, point.column - paneStart + 1));
|
|
762
|
+
const maxLocalRow = Math.max(1, agentAreaRows - HEADER_ROWS);
|
|
763
|
+
const localRow = Math.min(maxLocalRow, Math.max(1, point.row - HEADER_ROWS));
|
|
764
|
+
return { localColumn, localRow };
|
|
765
|
+
}
|
|
684
766
|
agentAtColumn(column) {
|
|
685
767
|
const layout = this.options.host.getLayout();
|
|
686
768
|
const totalRows = layout.childRows;
|
|
@@ -709,6 +791,11 @@ export class CrosscheckCoordinator {
|
|
|
709
791
|
agent2Ready: !!this.agents.get("agent-2")?.resultReady,
|
|
710
792
|
};
|
|
711
793
|
}
|
|
794
|
+
currentGenerationPresentation() {
|
|
795
|
+
return this.store?.generationInvalidated()
|
|
796
|
+
? { kind: "rewound" }
|
|
797
|
+
: { kind: "active" };
|
|
798
|
+
}
|
|
712
799
|
currentResultFile(agent) {
|
|
713
800
|
return this.store.snapshot().generation.resultFiles[agent];
|
|
714
801
|
}
|
|
@@ -718,8 +805,63 @@ export class CrosscheckCoordinator {
|
|
|
718
805
|
const generation = this.store.snapshot().generation;
|
|
719
806
|
return generation.id === 0 ? this.options.task : generation.prompt;
|
|
720
807
|
}
|
|
808
|
+
async reconcilePersistedHistory() {
|
|
809
|
+
const snapshot = this.store.snapshot();
|
|
810
|
+
await Promise.all(["agent-1", "agent-2"].map(async (agent) => {
|
|
811
|
+
const session = snapshot.sessions[agent];
|
|
812
|
+
const config = this.options.configurations[agent];
|
|
813
|
+
if (!session || session.driver !== config.driver || !session.historySnapshot) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const provider = resolveProvider(config.driver);
|
|
817
|
+
const buildSnapshot = provider.fork.sessionHistorySnapshot;
|
|
818
|
+
const compare = provider.fork.compareSessionHistory;
|
|
819
|
+
if (!buildSnapshot || !compare)
|
|
820
|
+
return;
|
|
821
|
+
try {
|
|
822
|
+
const { lines, malformed } = await readSessionFileWithStatus(session.sessionFilePath);
|
|
823
|
+
if (malformed)
|
|
824
|
+
return;
|
|
825
|
+
const current = buildSnapshot(lines);
|
|
826
|
+
if (!current)
|
|
827
|
+
return;
|
|
828
|
+
if (compare(session.historySnapshot, current)) {
|
|
829
|
+
await this.invalidateGenerationForRewind(agent, current);
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
await this.store.updateSessionHistory(agent, current);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
catch {
|
|
836
|
+
// Unreadable history is indeterminate; it is never proof of rewind.
|
|
837
|
+
}
|
|
838
|
+
}));
|
|
839
|
+
}
|
|
840
|
+
async invalidateGenerationForRewind(agent, snapshot) {
|
|
841
|
+
this.abortActiveDispatchOperations(new Error("Crosscheck generation invalidated by session rewind"));
|
|
842
|
+
await this.store.invalidateGenerationForRewind(agent, snapshot);
|
|
843
|
+
const rewind = this.store.generationRewindState();
|
|
844
|
+
for (const side of ["agent-1", "agent-2"]) {
|
|
845
|
+
const runtime = this.agents.get(side);
|
|
846
|
+
if (!runtime)
|
|
847
|
+
continue;
|
|
848
|
+
runtime.resultReady = false;
|
|
849
|
+
runtime.resultState = rewind?.[side];
|
|
850
|
+
runtime.freshForPendingDispatch = false;
|
|
851
|
+
}
|
|
852
|
+
this.crosscheckProgress = undefined;
|
|
853
|
+
this.crosscheckTurnIdentities.clear();
|
|
854
|
+
this.resendAfterLaunch.clear();
|
|
855
|
+
this.reconciliationBlocked.clear();
|
|
856
|
+
this.freshDispatchPreparations.clear();
|
|
857
|
+
this.broadcast.reconcileMode(this.currentReadiness(), this.currentGenerationPresentation());
|
|
858
|
+
this.refreshStatusHint();
|
|
859
|
+
this.options.host.scheduleParentUiRender();
|
|
860
|
+
}
|
|
721
861
|
currentCrosscheckStatus() {
|
|
722
862
|
const progress = this.crosscheckProgress;
|
|
863
|
+
if (this.store?.generationInvalidated())
|
|
864
|
+
return { kind: "unavailable" };
|
|
723
865
|
if (progress
|
|
724
866
|
&& this.store
|
|
725
867
|
&& this.store.generationId() === progress.generationId) {
|
|
@@ -747,12 +889,31 @@ export class CrosscheckCoordinator {
|
|
|
747
889
|
return "broadcasting";
|
|
748
890
|
return "ready";
|
|
749
891
|
}
|
|
892
|
+
requestBroadcastPaste() {
|
|
893
|
+
void handleImagePasteIntent(this.broadcast.attachments, () => {
|
|
894
|
+
this.flashTransient(PASTING_IMAGE_HINT, 3000);
|
|
895
|
+
}).then((outcome) => {
|
|
896
|
+
if (outcome.inserted !== undefined) {
|
|
897
|
+
this.broadcast.applyInsertedText(outcome.inserted);
|
|
898
|
+
this.resizeSessions();
|
|
899
|
+
}
|
|
900
|
+
if (outcome.hint)
|
|
901
|
+
this.flashTransient(outcome.hint, 3000);
|
|
902
|
+
else
|
|
903
|
+
this.options.host.scheduleParentUiRender();
|
|
904
|
+
this.refreshStatusHint();
|
|
905
|
+
}).catch((err) => {
|
|
906
|
+
this.flashTransient(err instanceof Error ? err.message : String(err), 3000);
|
|
907
|
+
});
|
|
908
|
+
}
|
|
750
909
|
isArmed() {
|
|
751
910
|
return this.isCrosscheckArmed() || this.isReconcileArmed();
|
|
752
911
|
}
|
|
753
912
|
isCrosscheckArmed() {
|
|
754
913
|
if (!this.store || !this.sessionsReady)
|
|
755
914
|
return false;
|
|
915
|
+
if (this.store.generationInvalidated())
|
|
916
|
+
return false;
|
|
756
917
|
if (this.dispatching || this.store.generationConsumed() || this.store.hasDispatch()) {
|
|
757
918
|
return false;
|
|
758
919
|
}
|
|
@@ -761,6 +922,8 @@ export class CrosscheckCoordinator {
|
|
|
761
922
|
isReconcileArmed() {
|
|
762
923
|
if (!this.store || !this.sessionsReady)
|
|
763
924
|
return false;
|
|
925
|
+
if (this.store.generationInvalidated())
|
|
926
|
+
return false;
|
|
764
927
|
if (this.dispatching
|
|
765
928
|
|| !this.store.generationConsumed()
|
|
766
929
|
|| this.store.hasDispatch()
|
|
@@ -898,9 +1061,11 @@ export class CrosscheckCoordinator {
|
|
|
898
1061
|
const attemptId = this.beginCrosscheckProgress(generationId, token, phase);
|
|
899
1062
|
const agents = ["agent-1", "agent-2"];
|
|
900
1063
|
try {
|
|
901
|
-
await this.withDispatchLifecycle(agents, async () => {
|
|
1064
|
+
await this.withDispatchLifecycle(agents, async (operation) => {
|
|
902
1065
|
await this.store.update((state) => {
|
|
903
|
-
if (state.generation.id !== generationId
|
|
1066
|
+
if (state.generation.id !== generationId
|
|
1067
|
+
|| state.generation.rewind
|
|
1068
|
+
|| !this.isOperationCurrent(operation))
|
|
904
1069
|
return;
|
|
905
1070
|
state.generation.reconcileAllowed = false;
|
|
906
1071
|
state.generation.reconcileSettled = false;
|
|
@@ -920,12 +1085,16 @@ export class CrosscheckCoordinator {
|
|
|
920
1085
|
},
|
|
921
1086
|
};
|
|
922
1087
|
});
|
|
923
|
-
|
|
1088
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1089
|
+
return;
|
|
1090
|
+
const results = await Promise.allSettled(agents.map((agent) => this.sendDispatchAgent(agent, generationId, attemptId, operation)));
|
|
924
1091
|
for (const [index, result] of results.entries()) {
|
|
925
1092
|
if (result.status === "rejected") {
|
|
926
1093
|
this.warnDispatchFailure(agents[index], "send", result.reason);
|
|
927
1094
|
}
|
|
928
1095
|
}
|
|
1096
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1097
|
+
return;
|
|
929
1098
|
await this.finalizeDispatchIfComplete(generationId);
|
|
930
1099
|
});
|
|
931
1100
|
}
|
|
@@ -933,12 +1102,12 @@ export class CrosscheckCoordinator {
|
|
|
933
1102
|
this.settleUnsubmittedCrosscheckSlots(attemptId);
|
|
934
1103
|
}
|
|
935
1104
|
}
|
|
936
|
-
async sendDispatchAgent(agent, generationId, progressAttemptId) {
|
|
1105
|
+
async sendDispatchAgent(agent, generationId, progressAttemptId, operation = this.currentDispatchOperationContext()) {
|
|
937
1106
|
if (this.reconciliationBlocked.has(agent))
|
|
938
1107
|
return;
|
|
939
|
-
|
|
940
|
-
if (state.generation.id !== generationId)
|
|
1108
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
941
1109
|
return;
|
|
1110
|
+
const state = this.store.snapshot();
|
|
942
1111
|
const dispatch = state.generation.dispatch;
|
|
943
1112
|
if (!dispatch || dispatch.agents[agent].status !== "pending")
|
|
944
1113
|
return;
|
|
@@ -950,9 +1119,11 @@ export class CrosscheckCoordinator {
|
|
|
950
1119
|
const releaseInputLock = this.lockAutomatedInput(agent);
|
|
951
1120
|
try {
|
|
952
1121
|
await (this.options.sendInquiry ?? sendInquiryQuestion)(runtime.session, this.buildDispatchPrompt(agent, generationId, dispatch.phase, dispatch.round), {
|
|
953
|
-
signal:
|
|
1122
|
+
signal: operation.signal,
|
|
954
1123
|
composer: provider.composer,
|
|
955
1124
|
onSubmitted: () => {
|
|
1125
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1126
|
+
return;
|
|
956
1127
|
submitted = true;
|
|
957
1128
|
if (progressAttemptId) {
|
|
958
1129
|
this.markCrosscheckSlotSubmitted(agent, generationId, dispatch.token, progressAttemptId);
|
|
@@ -964,13 +1135,17 @@ export class CrosscheckCoordinator {
|
|
|
964
1135
|
finally {
|
|
965
1136
|
releaseInputLock();
|
|
966
1137
|
}
|
|
967
|
-
if (!submitted)
|
|
1138
|
+
if (!submitted || !this.isCurrentGenerationOperation(generationId, operation))
|
|
968
1139
|
return;
|
|
969
1140
|
await this.transitionDispatchStatus(agent, generationId, dispatch.token, "pending", "submitted");
|
|
970
1141
|
const classification = await provider.live.classifyDispatchDelivery(runtime.session.identity.sessionFilePath, dispatch.agents[agent].baseline);
|
|
1142
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1143
|
+
return;
|
|
971
1144
|
if (classification === "present") {
|
|
972
1145
|
await this.markDispatchDelivered(agent, generationId, dispatch.token, "submitted");
|
|
973
1146
|
}
|
|
1147
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1148
|
+
return;
|
|
974
1149
|
await this.finalizeDispatchIfComplete(generationId);
|
|
975
1150
|
}
|
|
976
1151
|
buildDispatchPrompt(agent, generationId, phase, round) {
|
|
@@ -1020,9 +1195,9 @@ export class CrosscheckCoordinator {
|
|
|
1020
1195
|
const lockedAgents = ["agent-1", "agent-2"].filter((agent) => stateAtStart.generation.dispatch?.agents[agent].status === "pending"
|
|
1021
1196
|
&& !this.reconciliationBlocked.has(agent));
|
|
1022
1197
|
try {
|
|
1023
|
-
await this.withDispatchLifecycle(lockedAgents, async () => {
|
|
1198
|
+
await this.withDispatchLifecycle(lockedAgents, async (operation) => {
|
|
1024
1199
|
const state = this.store.snapshot();
|
|
1025
|
-
if (
|
|
1200
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1026
1201
|
return;
|
|
1027
1202
|
const dispatch = state.generation.dispatch;
|
|
1028
1203
|
if (!dispatch || (state.generation.consumed && dispatch.phase === "crosscheck")) {
|
|
@@ -1036,15 +1211,17 @@ export class CrosscheckCoordinator {
|
|
|
1036
1211
|
continue;
|
|
1037
1212
|
const current = await this.reconcileDispatchSide(agent, generationId, dispatch, expectedStatus, async () => {
|
|
1038
1213
|
try {
|
|
1039
|
-
await this.sendDispatchAgent(agent, generationId, attemptId);
|
|
1214
|
+
await this.sendDispatchAgent(agent, generationId, attemptId, operation);
|
|
1040
1215
|
}
|
|
1041
1216
|
catch (err) {
|
|
1042
1217
|
this.warnDispatchFailure(agent, "retry", err);
|
|
1043
1218
|
}
|
|
1044
|
-
});
|
|
1219
|
+
}, operation);
|
|
1045
1220
|
if (!current)
|
|
1046
1221
|
return;
|
|
1047
1222
|
}
|
|
1223
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1224
|
+
return;
|
|
1048
1225
|
await this.finalizeDispatchIfComplete(generationId);
|
|
1049
1226
|
});
|
|
1050
1227
|
}
|
|
@@ -1052,7 +1229,9 @@ export class CrosscheckCoordinator {
|
|
|
1052
1229
|
this.settleUnsubmittedCrosscheckSlots(attemptId);
|
|
1053
1230
|
}
|
|
1054
1231
|
}
|
|
1055
|
-
async reconcileDispatchSide(agent, generationId, dispatch, expectedStatus, onAbsent) {
|
|
1232
|
+
async reconcileDispatchSide(agent, generationId, dispatch, expectedStatus, onAbsent, operation = this.currentDispatchOperationContext()) {
|
|
1233
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1234
|
+
return false;
|
|
1056
1235
|
const target = onAbsent
|
|
1057
1236
|
? (() => {
|
|
1058
1237
|
const runtime = this.agents.get(agent);
|
|
@@ -1077,6 +1256,8 @@ export class CrosscheckCoordinator {
|
|
|
1077
1256
|
}
|
|
1078
1257
|
const classification = await resolveProvider(target.driver)
|
|
1079
1258
|
.live.classifyDispatchDelivery(target.sessionFilePath, dispatch.agents[agent].baseline);
|
|
1259
|
+
if (!this.isCurrentGenerationOperation(generationId, operation))
|
|
1260
|
+
return false;
|
|
1080
1261
|
if (!this.isCurrentDispatch(generationId, dispatch.token))
|
|
1081
1262
|
return false;
|
|
1082
1263
|
if (!this.isCurrentDispatchSide(agent, generationId, dispatch.token, expectedStatus))
|
|
@@ -1107,14 +1288,16 @@ export class CrosscheckCoordinator {
|
|
|
1107
1288
|
return true;
|
|
1108
1289
|
}
|
|
1109
1290
|
async withDispatchLifecycle(lockedAgents, dispatch) {
|
|
1291
|
+
const operation = this.beginDispatchOperation();
|
|
1110
1292
|
this.dispatching = true;
|
|
1111
1293
|
this.refreshStatusHint();
|
|
1112
1294
|
this.options.host.scheduleParentUiRender();
|
|
1113
1295
|
const releaseInputLocks = lockedAgents.map((agent) => this.lockAutomatedInput(agent));
|
|
1114
1296
|
try {
|
|
1115
|
-
await dispatch();
|
|
1297
|
+
await dispatch(operation);
|
|
1116
1298
|
}
|
|
1117
1299
|
finally {
|
|
1300
|
+
operation.release();
|
|
1118
1301
|
for (const release of releaseInputLocks)
|
|
1119
1302
|
release();
|
|
1120
1303
|
this.dispatching = false;
|
|
@@ -1157,28 +1340,40 @@ export class CrosscheckCoordinator {
|
|
|
1157
1340
|
}
|
|
1158
1341
|
async settleReconcileGate(generationId) {
|
|
1159
1342
|
const generation = this.store.snapshot().generation;
|
|
1343
|
+
const round = generation.reconcileRound;
|
|
1160
1344
|
if (generation.id !== generationId
|
|
1161
1345
|
|| !generation.reconcilePendingSettlement
|
|
1162
|
-
|| generation.dispatch
|
|
1346
|
+
|| generation.dispatch
|
|
1347
|
+
|| !this.allAgentsIdle())
|
|
1163
1348
|
return;
|
|
1164
|
-
const round = generation.reconcileRound;
|
|
1165
1349
|
const paths = ["agent-1", "agent-2"].map((agent) => round === 0
|
|
1166
1350
|
? crosscheckJsonFilePath(this.options.persistentDir, agent, generationId)
|
|
1167
1351
|
: reconcileJsonFilePath(this.options.persistentDir, agent, generationId, round));
|
|
1168
1352
|
const [agent1, agent2] = await Promise.all(paths.map((path) => readReviewResult(path)));
|
|
1169
|
-
|
|
1353
|
+
const afterRead = this.store.snapshot().generation;
|
|
1354
|
+
if (afterRead.id !== generationId
|
|
1355
|
+
|| afterRead.reconcileRound !== round
|
|
1356
|
+
|| !afterRead.reconcilePendingSettlement
|
|
1357
|
+
|| afterRead.dispatch
|
|
1358
|
+
|| !this.allAgentsIdle())
|
|
1170
1359
|
return;
|
|
1171
1360
|
if (!agent1 || !agent2) {
|
|
1361
|
+
let markedMissing = false;
|
|
1172
1362
|
await this.store.update((state) => {
|
|
1173
1363
|
if (state.generation.id !== generationId
|
|
1364
|
+
|| state.generation.reconcileRound !== round
|
|
1174
1365
|
|| !state.generation.reconcilePendingSettlement
|
|
1175
|
-
|| state.generation.dispatch
|
|
1366
|
+
|| state.generation.dispatch
|
|
1367
|
+
|| !this.allAgentsIdle())
|
|
1176
1368
|
return;
|
|
1177
1369
|
state.generation.reconcileJsonMissing = true;
|
|
1178
1370
|
state.generation.reconcileAllowed = false;
|
|
1179
1371
|
state.generation.reconcileSettled = false;
|
|
1180
1372
|
state.generation.reconcilePendingSettlement = false;
|
|
1373
|
+
markedMissing = true;
|
|
1181
1374
|
});
|
|
1375
|
+
if (!markedMissing)
|
|
1376
|
+
return;
|
|
1182
1377
|
this.options.output.warn("Crosscheck review JSON is missing or malformed; Ctrl+X Reconcile is unavailable.");
|
|
1183
1378
|
this.refreshStatusHint();
|
|
1184
1379
|
this.options.host.scheduleParentUiRender();
|
|
@@ -1187,16 +1382,22 @@ export class CrosscheckCoordinator {
|
|
|
1187
1382
|
const reconcileAllowed = round === 0
|
|
1188
1383
|
? true
|
|
1189
1384
|
: agent1.rejected.length > 0 || agent2.rejected.length > 0;
|
|
1385
|
+
let settled = false;
|
|
1190
1386
|
await this.store.update((state) => {
|
|
1191
1387
|
if (state.generation.id !== generationId
|
|
1388
|
+
|| state.generation.reconcileRound !== round
|
|
1192
1389
|
|| !state.generation.reconcilePendingSettlement
|
|
1193
|
-
|| state.generation.dispatch
|
|
1390
|
+
|| state.generation.dispatch
|
|
1391
|
+
|| !this.allAgentsIdle())
|
|
1194
1392
|
return;
|
|
1195
1393
|
state.generation.reconcileJsonMissing = false;
|
|
1196
1394
|
state.generation.reconcileAllowed = reconcileAllowed;
|
|
1197
1395
|
state.generation.reconcileSettled = true;
|
|
1198
1396
|
state.generation.reconcilePendingSettlement = false;
|
|
1397
|
+
settled = true;
|
|
1199
1398
|
});
|
|
1399
|
+
if (!settled)
|
|
1400
|
+
return;
|
|
1200
1401
|
this.refreshStatusHint();
|
|
1201
1402
|
this.options.host.scheduleParentUiRender();
|
|
1202
1403
|
}
|
|
@@ -1209,6 +1410,7 @@ export class CrosscheckCoordinator {
|
|
|
1209
1410
|
transitionDispatchStatus(agent, generationId, dispatchToken, expectedStatus, nextStatus) {
|
|
1210
1411
|
return this.store.update((state) => {
|
|
1211
1412
|
if (state.generation.id === generationId
|
|
1413
|
+
&& !state.generation.rewind
|
|
1212
1414
|
&& state.generation.dispatch?.token === dispatchToken
|
|
1213
1415
|
&& state.generation.dispatch.agents[agent].status === expectedStatus) {
|
|
1214
1416
|
state.generation.dispatch.agents[agent].status = nextStatus;
|
|
@@ -1236,24 +1438,42 @@ export class CrosscheckCoordinator {
|
|
|
1236
1438
|
&& ["agent-1", "agent-2"].some((agent) => this.hasPendingCrosscheck(agent)))
|
|
1237
1439
|
return;
|
|
1238
1440
|
const submission = this.broadcast.beginSubmission();
|
|
1441
|
+
let finalizedText;
|
|
1239
1442
|
const agents = ["agent-1", "agent-2"];
|
|
1240
1443
|
let initiated = false;
|
|
1241
1444
|
this.broadcastSendActive = true;
|
|
1242
1445
|
this.refreshStatusHint();
|
|
1243
1446
|
this.options.host.scheduleParentUiRender();
|
|
1244
1447
|
try {
|
|
1245
|
-
await this.withDispatchLifecycle(agents, async () => {
|
|
1448
|
+
await this.withDispatchLifecycle(agents, async (operation) => {
|
|
1246
1449
|
for (const agent of agents) {
|
|
1247
1450
|
if (!this.agents.has(agent)) {
|
|
1248
1451
|
throw new Error(`${agentLabel(agent)} is not ready for Broadcast`);
|
|
1249
1452
|
}
|
|
1250
1453
|
}
|
|
1454
|
+
if (hasImagePlaceholders(text)) {
|
|
1455
|
+
try {
|
|
1456
|
+
finalizedText = await finalizeImageAttachments(text, this.broadcast.attachments, this.options.persistentDir);
|
|
1457
|
+
}
|
|
1458
|
+
catch (err) {
|
|
1459
|
+
this.flashTransient(err instanceof Error ? err.message : String(err), 3000);
|
|
1460
|
+
throw err;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
else {
|
|
1464
|
+
finalizedText = text;
|
|
1465
|
+
}
|
|
1466
|
+
if (!this.isOperationCurrent(operation))
|
|
1467
|
+
return;
|
|
1251
1468
|
if (mode === "steer" || mode === "follow-up") {
|
|
1252
1469
|
const deliverySubmitCounts = await this.captureGenerationDeliveryCounts();
|
|
1253
|
-
|
|
1470
|
+
if (!this.isOperationCurrent(operation))
|
|
1471
|
+
return;
|
|
1472
|
+
await this.store.startGeneration(mode, finalizedText, deliverySubmitCounts);
|
|
1254
1473
|
this.crosscheckProgress = undefined;
|
|
1255
1474
|
for (const runtime of this.agents.values()) {
|
|
1256
1475
|
runtime.resultReady = false;
|
|
1476
|
+
runtime.resultState = undefined;
|
|
1257
1477
|
runtime.freshForPendingDispatch = false;
|
|
1258
1478
|
}
|
|
1259
1479
|
this.resendAfterLaunch.clear();
|
|
@@ -1265,12 +1485,14 @@ export class CrosscheckCoordinator {
|
|
|
1265
1485
|
const generationKind = mode === "steer" || mode === "follow-up"
|
|
1266
1486
|
? mode
|
|
1267
1487
|
: undefined;
|
|
1268
|
-
const sends = agents.map((agent) => this.sendBroadcastAgent(agent,
|
|
1488
|
+
const sends = agents.map((agent) => this.sendBroadcastAgent(agent, finalizedText, generationId, generationKind, operation));
|
|
1269
1489
|
initiated = true;
|
|
1270
1490
|
this.broadcast.completeSubmission(submission);
|
|
1271
1491
|
this.resizeSessions();
|
|
1272
1492
|
this.options.host.scheduleParentUiRender();
|
|
1273
1493
|
const results = await Promise.allSettled(sends);
|
|
1494
|
+
if (!this.isOperationCurrent(operation))
|
|
1495
|
+
return;
|
|
1274
1496
|
for (const [index, result] of results.entries()) {
|
|
1275
1497
|
if (result.status === "rejected") {
|
|
1276
1498
|
this.warnDispatchFailure(agents[index], "broadcast send", result.reason);
|
|
@@ -1294,19 +1516,18 @@ export class CrosscheckCoordinator {
|
|
|
1294
1516
|
return [agent, runtime];
|
|
1295
1517
|
});
|
|
1296
1518
|
await Promise.all(runtimes.map(([, runtime]) => runtime.monitor.waitForPendingTurnAttempts()));
|
|
1297
|
-
const entries = await Promise.all(runtimes.map(async ([agent, runtime]) =>
|
|
1298
|
-
const lines = await resolveProvider(runtime.config.driver).live.readLines(runtime.session.identity.sessionFilePath);
|
|
1299
|
-
return [agent, runtime.monitor.expectedSubmitCount(lines)];
|
|
1300
|
-
}));
|
|
1519
|
+
const entries = await Promise.all(runtimes.map(async ([agent, runtime]) => [agent, await runtime.monitor.expectedSubmitCountFromSession()]));
|
|
1301
1520
|
return Object.fromEntries(entries);
|
|
1302
1521
|
}
|
|
1303
|
-
async sendBroadcastAgent(agent, text, generationId, generationKind) {
|
|
1522
|
+
async sendBroadcastAgent(agent, text, generationId, generationKind, operation = this.currentDispatchOperationContext()) {
|
|
1304
1523
|
const runtime = this.agents.get(agent);
|
|
1305
1524
|
if (!runtime)
|
|
1306
1525
|
throw new Error(`${agentLabel(agent)} is not ready for Broadcast`);
|
|
1307
|
-
const
|
|
1308
|
-
if (
|
|
1526
|
+
const conversational = generationKind === undefined;
|
|
1527
|
+
if (!this.isCurrentGenerationOperation(generationId, operation, { allowRewound: conversational })) {
|
|
1309
1528
|
return;
|
|
1529
|
+
}
|
|
1530
|
+
const generation = this.store.snapshot().generation;
|
|
1310
1531
|
const prompt = generationKind === "steer"
|
|
1311
1532
|
? buildExecutorSteerPrompt(text, generation.resultFiles[agent])
|
|
1312
1533
|
: generationKind === "follow-up"
|
|
@@ -1316,12 +1537,19 @@ export class CrosscheckCoordinator {
|
|
|
1316
1537
|
const releaseInputLock = this.lockAutomatedInput(agent);
|
|
1317
1538
|
try {
|
|
1318
1539
|
if (generationKind && provider.cancelSequence) {
|
|
1319
|
-
await sendCancelSequence(runtime.session.pty, provider.cancelSequence,
|
|
1540
|
+
await sendCancelSequence(runtime.session.pty, provider.cancelSequence, operation.signal, runtime.monitor.isIdle);
|
|
1541
|
+
}
|
|
1542
|
+
if (!this.isCurrentGenerationOperation(generationId, operation, { allowRewound: conversational })) {
|
|
1543
|
+
return;
|
|
1320
1544
|
}
|
|
1321
1545
|
await (this.options.sendInquiry ?? sendInquiryQuestion)(runtime.session, prompt, {
|
|
1322
|
-
signal:
|
|
1546
|
+
signal: operation.signal,
|
|
1323
1547
|
composer: provider.composer,
|
|
1324
|
-
onSubmitted: () =>
|
|
1548
|
+
onSubmitted: () => {
|
|
1549
|
+
if (!this.isCurrentGenerationOperation(generationId, operation, { allowRewound: conversational }))
|
|
1550
|
+
return;
|
|
1551
|
+
runtime.monitor.beginTurn();
|
|
1552
|
+
},
|
|
1325
1553
|
...(generationKind ? { escapeBeforePaste: true } : {}),
|
|
1326
1554
|
});
|
|
1327
1555
|
}
|
|
@@ -1342,6 +1570,8 @@ export class CrosscheckCoordinator {
|
|
|
1342
1570
|
async performResultRefresh() {
|
|
1343
1571
|
let changed = false;
|
|
1344
1572
|
const generation = this.store.snapshot().generation;
|
|
1573
|
+
if (generation.rewind)
|
|
1574
|
+
return;
|
|
1345
1575
|
const readiness = await Promise.all(["agent-1", "agent-2"].map(async (agent) => ({
|
|
1346
1576
|
agent,
|
|
1347
1577
|
ready: await fileHasContent(generation.resultFiles[agent]),
|
|
@@ -1357,7 +1587,7 @@ export class CrosscheckCoordinator {
|
|
|
1357
1587
|
}
|
|
1358
1588
|
}
|
|
1359
1589
|
if (changed) {
|
|
1360
|
-
this.broadcast.reconcileMode(this.currentReadiness());
|
|
1590
|
+
this.broadcast.reconcileMode(this.currentReadiness(), this.currentGenerationPresentation());
|
|
1361
1591
|
this.refreshStatusHint();
|
|
1362
1592
|
this.options.host.scheduleParentUiRender();
|
|
1363
1593
|
this.maybeRetryPersistedDispatch();
|
|
@@ -1539,6 +1769,7 @@ export class CrosscheckCoordinator {
|
|
|
1539
1769
|
this.finishing = true;
|
|
1540
1770
|
this.launchAbort?.abort(new Error("Crosscheck launch cancelled"));
|
|
1541
1771
|
this.dispatchAbort.abort(new Error("Crosscheck coordinator stopped"));
|
|
1772
|
+
this.abortActiveDispatchOperations(new Error("Crosscheck coordinator stopped"));
|
|
1542
1773
|
this.finishResolve?.({ kind: "crosscheck", interrupted: true });
|
|
1543
1774
|
}
|
|
1544
1775
|
fail(error) {
|
|
@@ -1547,6 +1778,7 @@ export class CrosscheckCoordinator {
|
|
|
1547
1778
|
this.finishing = true;
|
|
1548
1779
|
this.launchAbort?.abort(error);
|
|
1549
1780
|
this.dispatchAbort.abort(error);
|
|
1781
|
+
this.abortActiveDispatchOperations(error);
|
|
1550
1782
|
if (this.finishReject)
|
|
1551
1783
|
this.finishReject(error);
|
|
1552
1784
|
else
|
|
@@ -1557,6 +1789,7 @@ export class CrosscheckCoordinator {
|
|
|
1557
1789
|
if (!this.dispatchAbort.signal.aborted) {
|
|
1558
1790
|
this.dispatchAbort.abort(new Error("Crosscheck coordinator disposed"));
|
|
1559
1791
|
}
|
|
1792
|
+
this.abortActiveDispatchOperations(new Error("Crosscheck coordinator disposed"));
|
|
1560
1793
|
if (this.resultTimer)
|
|
1561
1794
|
clearInterval(this.resultTimer);
|
|
1562
1795
|
this.resultTimer = undefined;
|
|
@@ -1594,6 +1827,55 @@ export class CrosscheckCoordinator {
|
|
|
1594
1827
|
});
|
|
1595
1828
|
this.dispatchOperations.add(tracked);
|
|
1596
1829
|
}
|
|
1830
|
+
beginDispatchOperation() {
|
|
1831
|
+
const controller = new AbortController();
|
|
1832
|
+
const abortFromRoot = () => {
|
|
1833
|
+
controller.abort(this.dispatchAbort.signal.reason
|
|
1834
|
+
?? new Error("Crosscheck dispatch cancelled"));
|
|
1835
|
+
};
|
|
1836
|
+
if (this.dispatchAbort.signal.aborted) {
|
|
1837
|
+
abortFromRoot();
|
|
1838
|
+
}
|
|
1839
|
+
else {
|
|
1840
|
+
this.dispatchAbort.signal.addEventListener("abort", abortFromRoot, { once: true });
|
|
1841
|
+
}
|
|
1842
|
+
this.activeOperationAborts.add(controller);
|
|
1843
|
+
let released = false;
|
|
1844
|
+
return {
|
|
1845
|
+
fence: this.operationFence,
|
|
1846
|
+
signal: controller.signal,
|
|
1847
|
+
release: () => {
|
|
1848
|
+
if (released)
|
|
1849
|
+
return;
|
|
1850
|
+
released = true;
|
|
1851
|
+
this.dispatchAbort.signal.removeEventListener("abort", abortFromRoot);
|
|
1852
|
+
this.activeOperationAborts.delete(controller);
|
|
1853
|
+
},
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
currentDispatchOperationContext() {
|
|
1857
|
+
return {
|
|
1858
|
+
fence: this.operationFence,
|
|
1859
|
+
signal: this.dispatchAbort.signal,
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
isOperationCurrent(operation) {
|
|
1863
|
+
return operation.fence === this.operationFence
|
|
1864
|
+
&& !operation.signal.aborted
|
|
1865
|
+
&& !this.dispatchAbort.signal.aborted;
|
|
1866
|
+
}
|
|
1867
|
+
isCurrentGenerationOperation(generationId, operation, options = {}) {
|
|
1868
|
+
if (!this.isOperationCurrent(operation))
|
|
1869
|
+
return false;
|
|
1870
|
+
return this.store.isGenerationCurrent(generationId, options);
|
|
1871
|
+
}
|
|
1872
|
+
abortActiveDispatchOperations(reason) {
|
|
1873
|
+
this.operationFence++;
|
|
1874
|
+
for (const controller of this.activeOperationAborts) {
|
|
1875
|
+
controller.abort(reason);
|
|
1876
|
+
}
|
|
1877
|
+
this.activeOperationAborts.clear();
|
|
1878
|
+
}
|
|
1597
1879
|
startActivityHeartbeat() {
|
|
1598
1880
|
if (!this.options.touchActivity)
|
|
1599
1881
|
return;
|