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.
Files changed (106) hide show
  1. package/dist/crosscheck/broadcast-composer.d.ts +16 -9
  2. package/dist/crosscheck/broadcast-composer.d.ts.map +1 -1
  3. package/dist/crosscheck/broadcast-composer.js +43 -32
  4. package/dist/crosscheck/broadcast-composer.js.map +1 -1
  5. package/dist/crosscheck/coordinator.d.ts +16 -0
  6. package/dist/crosscheck/coordinator.d.ts.map +1 -1
  7. package/dist/crosscheck/coordinator.js +340 -58
  8. package/dist/crosscheck/coordinator.js.map +1 -1
  9. package/dist/crosscheck/key-helpers.d.ts +2 -2
  10. package/dist/crosscheck/key-helpers.d.ts.map +1 -1
  11. package/dist/crosscheck/key-helpers.js +6 -4
  12. package/dist/crosscheck/key-helpers.js.map +1 -1
  13. package/dist/crosscheck/state.d.ts +12 -1
  14. package/dist/crosscheck/state.d.ts.map +1 -1
  15. package/dist/crosscheck/state.js +77 -17
  16. package/dist/crosscheck/state.js.map +1 -1
  17. package/dist/flow-editor/tui.d.ts +5 -0
  18. package/dist/flow-editor/tui.d.ts.map +1 -1
  19. package/dist/flow-editor/tui.js +34 -23
  20. package/dist/flow-editor/tui.js.map +1 -1
  21. package/dist/offscreen/controller.d.ts.map +1 -1
  22. package/dist/offscreen/controller.js +19 -2
  23. package/dist/offscreen/controller.js.map +1 -1
  24. package/dist/offscreen/session-turn-monitor.d.ts +18 -2
  25. package/dist/offscreen/session-turn-monitor.d.ts.map +1 -1
  26. package/dist/offscreen/session-turn-monitor.js +156 -18
  27. package/dist/offscreen/session-turn-monitor.js.map +1 -1
  28. package/dist/orchestrator.d.ts.map +1 -1
  29. package/dist/orchestrator.js +2 -9
  30. package/dist/orchestrator.js.map +1 -1
  31. package/dist/paste-image/persistence.d.ts +4 -0
  32. package/dist/paste-image/persistence.d.ts.map +1 -1
  33. package/dist/paste-image/persistence.js +17 -0
  34. package/dist/paste-image/persistence.js.map +1 -1
  35. package/dist/preview-panel.d.ts.map +1 -1
  36. package/dist/preview-panel.js +2 -2
  37. package/dist/preview-panel.js.map +1 -1
  38. package/dist/providers/copilot/completion.d.ts +3 -0
  39. package/dist/providers/copilot/completion.d.ts.map +1 -1
  40. package/dist/providers/copilot/completion.js +96 -7
  41. package/dist/providers/copilot/completion.js.map +1 -1
  42. package/dist/providers/copilot/session.d.ts +10 -8
  43. package/dist/providers/copilot/session.d.ts.map +1 -1
  44. package/dist/providers/copilot/session.js +49 -11
  45. package/dist/providers/copilot/session.js.map +1 -1
  46. package/dist/providers/copilot/strategy.d.ts.map +1 -1
  47. package/dist/providers/copilot/strategy.js +5 -1
  48. package/dist/providers/copilot/strategy.js.map +1 -1
  49. package/dist/providers/fork.d.ts +25 -0
  50. package/dist/providers/fork.d.ts.map +1 -1
  51. package/dist/providers/fork.js.map +1 -1
  52. package/dist/providers/types.d.ts +4 -2
  53. package/dist/providers/types.d.ts.map +1 -1
  54. package/dist/startup-tui.d.ts.map +1 -1
  55. package/dist/startup-tui.js +22 -12
  56. package/dist/startup-tui.js.map +1 -1
  57. package/dist/terminal/host-input-mode.d.ts +1 -0
  58. package/dist/terminal/host-input-mode.d.ts.map +1 -1
  59. package/dist/terminal/host-input-mode.js +11 -1
  60. package/dist/terminal/host-input-mode.js.map +1 -1
  61. package/dist/terminal/host.js +1 -1
  62. package/dist/terminal/host.js.map +1 -1
  63. package/dist/terminal/input-router.d.ts +14 -13
  64. package/dist/terminal/input-router.d.ts.map +1 -1
  65. package/dist/terminal/input-router.js +37 -35
  66. package/dist/terminal/input-router.js.map +1 -1
  67. package/dist/terminal/key-event-source.d.ts +2 -0
  68. package/dist/terminal/key-event-source.d.ts.map +1 -1
  69. package/dist/terminal/key-event-source.js +4 -1
  70. package/dist/terminal/key-event-source.js.map +1 -1
  71. package/dist/terminal/key-to-bytes.js +2 -0
  72. package/dist/terminal/key-to-bytes.js.map +1 -1
  73. package/dist/terminal/right-click-paste.d.ts +9 -0
  74. package/dist/terminal/right-click-paste.d.ts.map +1 -0
  75. package/dist/terminal/right-click-paste.js +37 -0
  76. package/dist/terminal/right-click-paste.js.map +1 -0
  77. package/dist/terminal/sgr-mouse.d.ts +17 -0
  78. package/dist/terminal/sgr-mouse.d.ts.map +1 -0
  79. package/dist/terminal/sgr-mouse.js +37 -0
  80. package/dist/terminal/sgr-mouse.js.map +1 -0
  81. package/dist/terminal/stdin-byte-source.d.ts +7 -1
  82. package/dist/terminal/stdin-byte-source.d.ts.map +1 -1
  83. package/dist/terminal/stdin-byte-source.js +14 -3
  84. package/dist/terminal/stdin-byte-source.js.map +1 -1
  85. package/dist/terminal/windows-terminal-shift-enter.d.ts +24 -0
  86. package/dist/terminal/windows-terminal-shift-enter.d.ts.map +1 -0
  87. package/dist/terminal/windows-terminal-shift-enter.js +316 -0
  88. package/dist/terminal/windows-terminal-shift-enter.js.map +1 -0
  89. package/dist/terminal-input.d.ts +12 -1
  90. package/dist/terminal-input.d.ts.map +1 -1
  91. package/dist/terminal-input.js +211 -109
  92. package/dist/terminal-input.js.map +1 -1
  93. package/dist/text-editing/emacs-input.d.ts +3 -0
  94. package/dist/text-editing/emacs-input.d.ts.map +1 -1
  95. package/dist/text-editing/emacs-input.js +8 -1
  96. package/dist/text-editing/emacs-input.js.map +1 -1
  97. package/dist/user/feedback-editing.d.ts.map +1 -1
  98. package/dist/user/feedback-editing.js +2 -5
  99. package/dist/user/feedback-editing.js.map +1 -1
  100. package/dist/user/host-curate-prompt.d.ts.map +1 -1
  101. package/dist/user/host-curate-prompt.js +18 -9
  102. package/dist/user/host-curate-prompt.js.map +1 -1
  103. package/dist/user/host-prompt.d.ts.map +1 -1
  104. package/dist/user/host-prompt.js +18 -9
  105. package/dist/user/host-prompt.js.map +1 -1
  106. 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 { parseSgrMouseFrame, WHEEL_LINES_PER_EVENT, } from "../terminal/input-router.js";
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 resultReady = await fileHasContent(resultFile);
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 = generationDelivered === false
145
- ? resumePrompt
146
- : activityState === "idle"
147
- ? ""
148
- : activityState === "working"
149
- ? RESUME_CONTINUE_PROMPT
150
- : resumePrompt;
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
- if (this.routeMouse(event))
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
- runtime?.resultReady ? "done" : undefined,
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
- : parseSgrMouseFrame(event);
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
- const translated = event.kind === "wheel"
674
- ? { ...event, column: target.localColumn, row: localRow }
675
- : {
676
- kind: "raw",
677
- bytes: Buffer.from(`\x1b[<${point.button};${target.localColumn};${localRow}${point.final}`, "ascii"),
678
- };
679
- const bytes = encodeKeysToBytes([translated], { wheelMode: "sgr" });
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
- const results = await Promise.allSettled(agents.map((agent) => this.sendDispatchAgent(agent, generationId, attemptId)));
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
- const state = this.store.snapshot();
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: this.dispatchAbort.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 (state.generation.id !== generationId)
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
- if (this.store.snapshot().generation.id !== generationId)
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
- await this.store.startGeneration(mode, text, deliverySubmitCounts);
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, text, generationId, generationKind));
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 generation = this.store.snapshot().generation;
1308
- if (generation.id !== generationId)
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, this.dispatchAbort.signal, runtime.monitor.isIdle);
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: this.dispatchAbort.signal,
1546
+ signal: operation.signal,
1323
1547
  composer: provider.composer,
1324
- onSubmitted: () => runtime.monitor.beginTurn(),
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;