@phenx-inc/ctlsurf 0.15.2 → 0.16.1

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 (33) hide show
  1. package/out/headless/index.mjs +10 -3
  2. package/out/headless/index.mjs.map +2 -2
  3. package/out/main/index.js +54 -4
  4. package/out/preload/index.js +2 -0
  5. package/out/renderer/assets/{cssMode-CPfLk04b.js → cssMode-DwHD6JSn.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-CDHpuFqv.js → freemarker2-Na9-bXYw.js} +1 -1
  7. package/out/renderer/assets/{handlebars-CgX9Jz36.js → handlebars-D7gzKLS8.js} +1 -1
  8. package/out/renderer/assets/{html-DdgFccDG.js → html-DDK7xN27.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-CRHIwtvP.js → htmlMode-BfbLv-NB.js} +3 -3
  10. package/out/renderer/assets/{index-3sFJEz7i.css → index-DID1LlMU.css} +88 -0
  11. package/out/renderer/assets/{index-DVdWgw7p.js → index-HoDaBul3.js} +340 -96
  12. package/out/renderer/assets/{javascript-CHhxL0s7.js → javascript-DJjWOxID.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-Cy7-eWai.js → jsonMode-BileWbig.js} +3 -3
  14. package/out/renderer/assets/{liquid-vMkF9pfk.js → liquid-DvNU_dI9.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-nAygNzuG.js → lspLanguageFeatures-Bb27EY8c.js} +1 -1
  16. package/out/renderer/assets/{mdx-C4UVz5dW.js → mdx-CLkNPVgD.js} +1 -1
  17. package/out/renderer/assets/{python-BHemzN9g.js → python-DOJdL4kC.js} +1 -1
  18. package/out/renderer/assets/{razor-DH6NPzbo.js → razor-CSr8UlJc.js} +1 -1
  19. package/out/renderer/assets/{tsMode-DxBdlGZB.js → tsMode-sbsAvk21.js} +1 -1
  20. package/out/renderer/assets/{typescript-BRCOaBQ5.js → typescript-r5QB4yRW.js} +1 -1
  21. package/out/renderer/assets/{xml-DVv9gDP1.js → xml-CYhKh3Pi.js} +1 -1
  22. package/out/renderer/assets/{yaml-jB-C3CdS.js → yaml-B8DsoaYW.js} +1 -1
  23. package/out/renderer/index.html +2 -2
  24. package/package.json +1 -1
  25. package/scripts/test-terminal-scroll.cjs +64 -0
  26. package/src/main/index.ts +52 -1
  27. package/src/main/orchestrator.ts +18 -3
  28. package/src/preload/index.ts +4 -0
  29. package/src/renderer/App.tsx +179 -50
  30. package/src/renderer/components/AgentPicker.tsx +61 -10
  31. package/src/renderer/components/ProjectPicker.tsx +44 -13
  32. package/src/renderer/components/TerminalPanel.tsx +88 -27
  33. package/src/renderer/styles.css +88 -0
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-CPfLk04b.js","./lspLanguageFeatures-nAygNzuG.js","./htmlMode-CRHIwtvP.js","./jsonMode-Cy7-eWai.js","./javascript-CHhxL0s7.js","./typescript-BRCOaBQ5.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-DwHD6JSn.js","./lspLanguageFeatures-Bb27EY8c.js","./htmlMode-BfbLv-NB.js","./jsonMode-BileWbig.js","./javascript-DJjWOxID.js","./typescript-r5QB4yRW.js"])))=>i.map(i=>d[i]);
2
2
  function getDefaultExportFromCjs(x) {
3
3
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
4
4
  }
@@ -19525,6 +19525,20 @@ function markGesture(tabId) {
19525
19525
  const s = _terminals.get(tabId);
19526
19526
  if (s) s.userScrollPending = true;
19527
19527
  }
19528
+ function followLatest(state) {
19529
+ state.userScrollPending = false;
19530
+ setPinned(state, true);
19531
+ reassert(state);
19532
+ }
19533
+ function rememberScrollPosition(state) {
19534
+ state.userScrollPending = false;
19535
+ if (isAtBottom(state.terminal)) {
19536
+ setPinned(state, true);
19537
+ } else {
19538
+ state.anchorLine = state.terminal.buffer.active.viewportY;
19539
+ setPinned(state, false);
19540
+ }
19541
+ }
19528
19542
  function getOrCreateTerminal(tabId, onExit) {
19529
19543
  const existing = _terminals.get(tabId);
19530
19544
  if (existing) {
@@ -19550,7 +19564,9 @@ function getOrCreateTerminal(tabId, onExit) {
19550
19564
  fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', monospace",
19551
19565
  theme: currentTheme().terminal,
19552
19566
  scrollback: 1e4,
19553
- scrollOnUserInput: true,
19567
+ // Follow explicit keyboard/paste input below. xterm also counts mouse
19568
+ // reports as user input, which must not pull someone out of scrollback.
19569
+ scrollOnUserInput: false,
19554
19570
  allowTransparency: false
19555
19571
  });
19556
19572
  const fitAddon = new addonFitExports.FitAddon();
@@ -19599,7 +19615,7 @@ function getOrCreateTerminal(tabId, onExit) {
19599
19615
  fitAddon,
19600
19616
  unsubData: null,
19601
19617
  unsubExit: null,
19602
- spawnedAgentId: null,
19618
+ spawnedSessionKey: null,
19603
19619
  pinnedToBottom: true,
19604
19620
  anchorLine: null,
19605
19621
  userScrollPending: false,
@@ -19612,17 +19628,10 @@ function getOrCreateTerminal(tabId, onExit) {
19612
19628
  reassert(state);
19613
19629
  return;
19614
19630
  }
19615
- state.userScrollPending = false;
19616
- if (isAtBottom(terminal)) {
19617
- setPinned(state, true);
19618
- } else {
19619
- state.anchorLine = terminal.buffer.active.viewportY;
19620
- setPinned(state, false);
19621
- }
19631
+ rememberScrollPosition(state);
19622
19632
  });
19633
+ terminal.onKey(() => followLatest(state));
19623
19634
  terminal.onData((data) => {
19624
- setPinned(state, true);
19625
- terminal.scrollToBottom();
19626
19635
  window.worker.writePty(tabId, data);
19627
19636
  });
19628
19637
  state.unsubData = window.worker.onPtyData((eventTabId, data) => {
@@ -19643,6 +19652,25 @@ function getOrCreateTerminal(tabId, onExit) {
19643
19652
  function focusTerminal(tabId) {
19644
19653
  _terminals.get(tabId)?.terminal.focus();
19645
19654
  }
19655
+ const MAX_HANDOFF_CHARS = 1e6;
19656
+ function captureTerminalSession(tabId) {
19657
+ const terminal = _terminals.get(tabId)?.terminal;
19658
+ if (!terminal) return "";
19659
+ const buffer = terminal.buffer.active;
19660
+ const lines = [];
19661
+ for (let i2 = 0; i2 < buffer.length; i2++) {
19662
+ const line = buffer.getLine(i2);
19663
+ if (!line) continue;
19664
+ const text22 = line.translateToString(true);
19665
+ if (line.isWrapped && lines.length > 0) lines[lines.length - 1] += text22;
19666
+ else lines.push(text22);
19667
+ }
19668
+ while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
19669
+ const text2 = lines.join("\n").trim();
19670
+ if (text2.length <= MAX_HANDOFF_CHARS) return text2;
19671
+ return `[Earlier terminal output omitted; showing the last ${MAX_HANDOFF_CHARS.toLocaleString()} characters.]
19672
+ ${text2.slice(-MAX_HANDOFF_CHARS)}`;
19673
+ }
19646
19674
  function destroyTerminal(tabId) {
19647
19675
  const state = _terminals.get(tabId);
19648
19676
  if (!state) return;
@@ -19651,7 +19679,7 @@ function destroyTerminal(tabId) {
19651
19679
  state.terminal.dispose();
19652
19680
  _terminals.delete(tabId);
19653
19681
  }
19654
- function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19682
+ function TerminalPanel({ tabId, agent, sessionVersion, onSpawn, onExit, isActive }) {
19655
19683
  const containerRef = reactExports.useRef(null);
19656
19684
  const [menu, setMenu] = reactExports.useState(null);
19657
19685
  const [snapOpen, setSnapOpen] = reactExports.useState(false);
@@ -19661,8 +19689,7 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19661
19689
  const jumpToLatest = () => {
19662
19690
  const s = _terminals.get(tabId);
19663
19691
  if (!s) return;
19664
- setPinned(s, true);
19665
- s.terminal.scrollToBottom();
19692
+ followLatest(s);
19666
19693
  s.terminal.focus();
19667
19694
  };
19668
19695
  const handleContextMenu = (e) => {
@@ -19742,6 +19769,22 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19742
19769
  if (attached) reassert(attached);
19743
19770
  const onWheel = () => markGesture(tabId);
19744
19771
  terminal.element?.addEventListener("wheel", onWheel, { capture: true, passive: true });
19772
+ const viewport = terminal.element?.querySelector(".xterm-viewport");
19773
+ const onViewportScroll = () => {
19774
+ const state = _terminals.get(tabId);
19775
+ if (state && !state.holding && state.userScrollPending) rememberScrollPosition(state);
19776
+ };
19777
+ viewport?.addEventListener("scroll", onViewportScroll);
19778
+ const onTextInput = () => {
19779
+ const state = _terminals.get(tabId);
19780
+ if (state) followLatest(state);
19781
+ };
19782
+ const onPaste = (event) => {
19783
+ if (event.clipboardData?.getData("text/plain")) onTextInput();
19784
+ };
19785
+ terminal.textarea?.addEventListener("paste", onPaste, true);
19786
+ terminal.textarea?.addEventListener("input", onTextInput, true);
19787
+ terminal.textarea?.addEventListener("compositionend", onTextInput, true);
19745
19788
  let resizeTimeout;
19746
19789
  const handleResize = () => {
19747
19790
  clearTimeout(resizeTimeout);
@@ -19765,14 +19808,19 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19765
19808
  window.removeEventListener("resize", handleResize);
19766
19809
  observer.disconnect();
19767
19810
  terminal.element?.removeEventListener("wheel", onWheel, true);
19811
+ viewport?.removeEventListener("scroll", onViewportScroll);
19812
+ terminal.textarea?.removeEventListener("paste", onPaste, true);
19813
+ terminal.textarea?.removeEventListener("input", onTextInput, true);
19814
+ terminal.textarea?.removeEventListener("compositionend", onTextInput, true);
19768
19815
  };
19769
19816
  }, [tabId, onExit]);
19770
19817
  reactExports.useEffect(() => {
19771
19818
  if (!agent) return;
19772
19819
  const state = _terminals.get(tabId);
19773
19820
  if (!state) return;
19774
- if (state.spawnedAgentId === agent.id) return;
19775
- state.spawnedAgentId = agent.id;
19821
+ const sessionKey = `${agent.id}:${sessionVersion}`;
19822
+ if (state.spawnedSessionKey === sessionKey) return;
19823
+ state.spawnedSessionKey = sessionKey;
19776
19824
  state.terminal.clear();
19777
19825
  onSpawn(tabId, agent).then(() => {
19778
19826
  const s = _terminals.get(tabId);
@@ -19785,7 +19833,7 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19785
19833
  }
19786
19834
  state.terminal.focus();
19787
19835
  });
19788
- }, [tabId, agent, onSpawn]);
19836
+ }, [tabId, agent, sessionVersion, onSpawn]);
19789
19837
  reactExports.useEffect(() => {
19790
19838
  if (isActive) {
19791
19839
  window.worker.setActiveTab(tabId);
@@ -208423,7 +208471,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
208423
208471
  modeConfigurationDefault$2
208424
208472
  );
208425
208473
  function getMode$3() {
208426
- return __vitePreload(() => import("./cssMode-CPfLk04b.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
208474
+ return __vitePreload(() => import("./cssMode-DwHD6JSn.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
208427
208475
  }
208428
208476
  languages.onLanguage("less", () => {
208429
208477
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -208528,7 +208576,7 @@ const razorLanguageService = registerHTMLLanguageService(
208528
208576
  );
208529
208577
  const razorDefaults = razorLanguageService.defaults;
208530
208578
  function getMode$2() {
208531
- return __vitePreload(() => import("./htmlMode-CRHIwtvP.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
208579
+ return __vitePreload(() => import("./htmlMode-BfbLv-NB.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
208532
208580
  }
208533
208581
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
208534
208582
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -208612,7 +208660,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
208612
208660
  );
208613
208661
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
208614
208662
  function getMode$1() {
208615
- return __vitePreload(() => import("./jsonMode-Cy7-eWai.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208663
+ return __vitePreload(() => import("./jsonMode-BileWbig.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208616
208664
  }
208617
208665
  languages.register({
208618
208666
  id: "json",
@@ -208858,7 +208906,7 @@ const getJavaScriptWorker = () => {
208858
208906
  return getMode().then((mode) => mode.getJavaScriptWorker());
208859
208907
  };
208860
208908
  function getMode() {
208861
- return __vitePreload(() => import("./tsMode-DxBdlGZB.js"), true ? [] : void 0, import.meta.url);
208909
+ return __vitePreload(() => import("./tsMode-sbsAvk21.js"), true ? [] : void 0, import.meta.url);
208862
208910
  }
208863
208911
  languages.onLanguage("typescript", () => {
208864
208912
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -209053,49 +209101,49 @@ registerLanguage({
209053
209101
  extensions: [".ftl", ".ftlh", ".ftlx"],
209054
209102
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
209055
209103
  loader: () => {
209056
- return __vitePreload(() => import("./freemarker2-CDHpuFqv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
209104
+ return __vitePreload(() => import("./freemarker2-Na9-bXYw.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
209057
209105
  }
209058
209106
  });
209059
209107
  registerLanguage({
209060
209108
  id: "freemarker2.tag-angle.interpolation-dollar",
209061
209109
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
209062
209110
  loader: () => {
209063
- return __vitePreload(() => import("./freemarker2-CDHpuFqv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
209111
+ return __vitePreload(() => import("./freemarker2-Na9-bXYw.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
209064
209112
  }
209065
209113
  });
209066
209114
  registerLanguage({
209067
209115
  id: "freemarker2.tag-bracket.interpolation-dollar",
209068
209116
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
209069
209117
  loader: () => {
209070
- return __vitePreload(() => import("./freemarker2-CDHpuFqv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
209118
+ return __vitePreload(() => import("./freemarker2-Na9-bXYw.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
209071
209119
  }
209072
209120
  });
209073
209121
  registerLanguage({
209074
209122
  id: "freemarker2.tag-angle.interpolation-bracket",
209075
209123
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
209076
209124
  loader: () => {
209077
- return __vitePreload(() => import("./freemarker2-CDHpuFqv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
209125
+ return __vitePreload(() => import("./freemarker2-Na9-bXYw.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
209078
209126
  }
209079
209127
  });
209080
209128
  registerLanguage({
209081
209129
  id: "freemarker2.tag-bracket.interpolation-bracket",
209082
209130
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
209083
209131
  loader: () => {
209084
- return __vitePreload(() => import("./freemarker2-CDHpuFqv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
209132
+ return __vitePreload(() => import("./freemarker2-Na9-bXYw.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
209085
209133
  }
209086
209134
  });
209087
209135
  registerLanguage({
209088
209136
  id: "freemarker2.tag-auto.interpolation-dollar",
209089
209137
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
209090
209138
  loader: () => {
209091
- return __vitePreload(() => import("./freemarker2-CDHpuFqv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
209139
+ return __vitePreload(() => import("./freemarker2-Na9-bXYw.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
209092
209140
  }
209093
209141
  });
209094
209142
  registerLanguage({
209095
209143
  id: "freemarker2.tag-auto.interpolation-bracket",
209096
209144
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
209097
209145
  loader: () => {
209098
- return __vitePreload(() => import("./freemarker2-CDHpuFqv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
209146
+ return __vitePreload(() => import("./freemarker2-Na9-bXYw.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
209099
209147
  }
209100
209148
  });
209101
209149
  registerLanguage({
@@ -209116,7 +209164,7 @@ registerLanguage({
209116
209164
  extensions: [".handlebars", ".hbs"],
209117
209165
  aliases: ["Handlebars", "handlebars", "hbs"],
209118
209166
  mimetypes: ["text/x-handlebars-template"],
209119
- loader: () => __vitePreload(() => import("./handlebars-CgX9Jz36.js"), true ? [] : void 0, import.meta.url)
209167
+ loader: () => __vitePreload(() => import("./handlebars-D7gzKLS8.js"), true ? [] : void 0, import.meta.url)
209120
209168
  });
209121
209169
  registerLanguage({
209122
209170
  id: "hcl",
@@ -209129,7 +209177,7 @@ registerLanguage({
209129
209177
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
209130
209178
  aliases: ["HTML", "htm", "html", "xhtml"],
209131
209179
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
209132
- loader: () => __vitePreload(() => import("./html-DdgFccDG.js"), true ? [] : void 0, import.meta.url)
209180
+ loader: () => __vitePreload(() => import("./html-DDK7xN27.js"), true ? [] : void 0, import.meta.url)
209133
209181
  });
209134
209182
  registerLanguage({
209135
209183
  id: "ini",
@@ -209152,7 +209200,7 @@ registerLanguage({
209152
209200
  filenames: ["jakefile"],
209153
209201
  aliases: ["JavaScript", "javascript", "js"],
209154
209202
  mimetypes: ["text/javascript"],
209155
- loader: () => __vitePreload(() => import("./javascript-CHhxL0s7.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
209203
+ loader: () => __vitePreload(() => import("./javascript-DJjWOxID.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
209156
209204
  });
209157
209205
  registerLanguage({
209158
209206
  id: "julia",
@@ -209191,7 +209239,7 @@ registerLanguage({
209191
209239
  extensions: [".liquid", ".html.liquid"],
209192
209240
  aliases: ["Liquid", "liquid"],
209193
209241
  mimetypes: ["application/liquid"],
209194
- loader: () => __vitePreload(() => import("./liquid-vMkF9pfk.js"), true ? [] : void 0, import.meta.url)
209242
+ loader: () => __vitePreload(() => import("./liquid-DvNU_dI9.js"), true ? [] : void 0, import.meta.url)
209195
209243
  });
209196
209244
  registerLanguage({
209197
209245
  id: "m3",
@@ -209209,7 +209257,7 @@ registerLanguage({
209209
209257
  id: "mdx",
209210
209258
  extensions: [".mdx"],
209211
209259
  aliases: ["MDX", "mdx"],
209212
- loader: () => __vitePreload(() => import("./mdx-C4UVz5dW.js"), true ? [] : void 0, import.meta.url)
209260
+ loader: () => __vitePreload(() => import("./mdx-CLkNPVgD.js"), true ? [] : void 0, import.meta.url)
209213
209261
  });
209214
209262
  registerLanguage({
209215
209263
  id: "mips",
@@ -209308,7 +209356,7 @@ registerLanguage({
209308
209356
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
209309
209357
  aliases: ["Python", "py"],
209310
209358
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
209311
- loader: () => __vitePreload(() => import("./python-BHemzN9g.js"), true ? [] : void 0, import.meta.url)
209359
+ loader: () => __vitePreload(() => import("./python-DOJdL4kC.js"), true ? [] : void 0, import.meta.url)
209312
209360
  });
209313
209361
  registerLanguage({
209314
209362
  id: "qsharp",
@@ -209327,7 +209375,7 @@ registerLanguage({
209327
209375
  extensions: [".cshtml"],
209328
209376
  aliases: ["Razor", "razor"],
209329
209377
  mimetypes: ["text/x-cshtml"],
209330
- loader: () => __vitePreload(() => import("./razor-DH6NPzbo.js"), true ? [] : void 0, import.meta.url)
209378
+ loader: () => __vitePreload(() => import("./razor-CSr8UlJc.js"), true ? [] : void 0, import.meta.url)
209331
209379
  });
209332
209380
  registerLanguage({
209333
209381
  id: "redis",
@@ -209460,7 +209508,7 @@ registerLanguage({
209460
209508
  aliases: ["TypeScript", "ts", "typescript"],
209461
209509
  mimetypes: ["text/typescript"],
209462
209510
  loader: () => {
209463
- return __vitePreload(() => import("./typescript-BRCOaBQ5.js"), true ? [] : void 0, import.meta.url);
209511
+ return __vitePreload(() => import("./typescript-r5QB4yRW.js"), true ? [] : void 0, import.meta.url);
209464
209512
  }
209465
209513
  });
209466
209514
  registerLanguage({
@@ -209505,14 +209553,14 @@ registerLanguage({
209505
209553
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
209506
209554
  aliases: ["XML", "xml"],
209507
209555
  mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
209508
- loader: () => __vitePreload(() => import("./xml-DVv9gDP1.js"), true ? [] : void 0, import.meta.url)
209556
+ loader: () => __vitePreload(() => import("./xml-CYhKh3Pi.js"), true ? [] : void 0, import.meta.url)
209509
209557
  });
209510
209558
  registerLanguage({
209511
209559
  id: "yaml",
209512
209560
  extensions: [".yaml", ".yml"],
209513
209561
  aliases: ["YAML", "yaml", "YML", "yml"],
209514
209562
  mimetypes: ["application/x-yaml", "text/x-yaml"],
209515
- loader: () => __vitePreload(() => import("./yaml-jB-C3CdS.js"), true ? [] : void 0, import.meta.url)
209563
+ loader: () => __vitePreload(() => import("./yaml-B8DsoaYW.js"), true ? [] : void 0, import.meta.url)
209516
209564
  });
209517
209565
  var __defProp = Object.defineProperty;
209518
209566
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -213286,11 +213334,14 @@ function RemoteConnect({ onConnect, onBack }) {
213286
213334
  ] })
213287
213335
  ] }) });
213288
213336
  }
213289
- function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213290
- const displayPath = cwd2.replace(/^\/Users\/[^/]+/, "~");
213337
+ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd, onCancel, restarting = false }) {
213338
+ const [selectedCwd, setSelectedCwd] = reactExports.useState(cwd2);
213339
+ const displayPath = selectedCwd.replace(/^\/Users\/[^/]+/, "~");
213291
213340
  const [logChat, setLogChat] = reactExports.useState(false);
213292
213341
  const [loaded, setLoaded] = reactExports.useState(false);
213293
213342
  const [remoteMode, setRemoteMode] = reactExports.useState(false);
213343
+ const [handoff, setHandoff] = reactExports.useState("fresh");
213344
+ const [switching, setSwitching] = reactExports.useState(false);
213294
213345
  reactExports.useEffect(() => {
213295
213346
  let cancelled = false;
213296
213347
  window.worker.getLogChat().then((r) => {
@@ -213311,20 +213362,30 @@ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213311
213362
  }
213312
213363
  };
213313
213364
  const handleSelect = async (agent) => {
213365
+ if (switching) return;
213366
+ setSwitching(true);
213314
213367
  if (loaded) {
213315
213368
  try {
213316
213369
  await window.worker.setLogChat(logChat);
213317
213370
  } catch {
213318
213371
  }
213319
213372
  }
213320
- onSelect(agent);
213373
+ try {
213374
+ await onSelect(agent, selectedCwd, handoff);
213375
+ } finally {
213376
+ setSwitching(false);
213377
+ }
213378
+ };
213379
+ const changeCwd = async () => {
213380
+ const next = await onChangeCwd();
213381
+ if (next) setSelectedCwd(next);
213321
213382
  };
213322
213383
  if (remoteMode) {
213323
213384
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
213324
213385
  RemoteConnect,
213325
213386
  {
213326
213387
  onConnect: (agent) => {
213327
- handleSelect(agent);
213388
+ void handleSelect(agent);
213328
213389
  },
213329
213390
  onBack: () => setRemoteMode(false)
213330
213391
  }
@@ -213332,18 +213393,55 @@ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213332
213393
  }
213333
213394
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-modal", children: [
213334
213395
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-brand", children: "ctlsurf" }),
213335
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-cwd", onClick: onChangeCwd, title: "Click to change directory", children: [
213396
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-cwd", onClick: () => void changeCwd(), title: "Click to change directory", children: [
213336
213397
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-cwd-icon", children: "📂" }),
213337
213398
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-cwd-path", children: displayPath }),
213338
213399
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-cwd-change", children: "change" })
213339
213400
  ] }),
213340
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-title", children: "SELECT AGENT" }),
213401
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-title", children: restarting ? "CHANGE SESSION" : "SELECT AGENT" }),
213402
+ restarting && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
213403
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-warning", children: "Choosing an agent restarts this terminal and ends its current process." }),
213404
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("fieldset", { className: "agent-handoff-options", children: [
213405
+ /* @__PURE__ */ jsxRuntimeExports.jsx("legend", { children: "NEW SESSION CONTEXT" }),
213406
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: handoff === "fresh" ? "selected" : "", children: [
213407
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
213408
+ "input",
213409
+ {
213410
+ type: "radio",
213411
+ name: "session-handoff",
213412
+ checked: handoff === "fresh",
213413
+ onChange: () => setHandoff("fresh")
213414
+ }
213415
+ ),
213416
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
213417
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Start fresh" }),
213418
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Do not send anything from this terminal." })
213419
+ ] })
213420
+ ] }),
213421
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: handoff === "context" ? "selected" : "", children: [
213422
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
213423
+ "input",
213424
+ {
213425
+ type: "radio",
213426
+ name: "session-handoff",
213427
+ checked: handoff === "context",
213428
+ onChange: () => setHandoff("context")
213429
+ }
213430
+ ),
213431
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
213432
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Share previous context" }),
213433
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Quote it inline, or use a temporary file when the session is long." })
213434
+ ] })
213435
+ ] })
213436
+ ] })
213437
+ ] }),
213341
213438
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-list", children: [
213342
213439
  agents.map((a) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
213343
213440
  "button",
213344
213441
  {
213345
213442
  className: "agent-picker-item",
213346
- onClick: () => handleSelect(a),
213443
+ disabled: switching,
213444
+ onClick: () => void handleSelect(a),
213347
213445
  children: [
213348
213446
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-name", children: a.name }),
213349
213447
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-desc", children: a.description })
@@ -213355,6 +213453,7 @@ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213355
213453
  "button",
213356
213454
  {
213357
213455
  className: "agent-picker-item",
213456
+ disabled: switching,
213358
213457
  onClick: () => setRemoteMode(true),
213359
213458
  children: [
213360
213459
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-name", children: "🌐 Remote (SSH)" }),
@@ -213374,15 +213473,17 @@ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213374
213473
  ),
213375
213474
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Log chat to ctlsurf" })
213376
213475
  ] }),
213377
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-hint", children: "Select an agent to start" })
213476
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-hint", children: restarting ? "Select an agent to restart in the directory above" : "Select an agent to start" }),
213477
+ onCancel && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "remote-link", disabled: switching, onClick: onCancel, children: "Cancel" })
213378
213478
  ] }) });
213379
213479
  }
213380
- function ProjectPicker({ onDone }) {
213480
+ function ProjectPicker({ onDone, currentProject }) {
213381
213481
  const [state, setState2] = reactExports.useState("loading");
213382
213482
  const [projects, setProjects] = reactExports.useState([]);
213383
213483
  const [errorMsg, setErrorMsg] = reactExports.useState("");
213384
213484
  const [binding, setBinding] = reactExports.useState(false);
213385
213485
  const [filter, setFilter] = reactExports.useState("");
213486
+ const [notifyAgent, setNotifyAgent] = reactExports.useState(true);
213386
213487
  reactExports.useEffect(() => {
213387
213488
  let cancelled = false;
213388
213489
  window.worker.listProjects().then((r) => {
@@ -213406,23 +213507,36 @@ function ProjectPicker({ onDone }) {
213406
213507
  const pick = async (p) => {
213407
213508
  setBinding(true);
213408
213509
  try {
213409
- await window.worker.bindProject(p.root_path);
213410
- } catch {
213510
+ const result = await window.worker.bindProject(p.root_path);
213511
+ if (!result.ok) {
213512
+ setErrorMsg(result.error || "Could not bind project");
213513
+ setState2("error");
213514
+ setBinding(false);
213515
+ return;
213516
+ }
213517
+ onDone({ project: p, notifyAgent });
213518
+ } catch (err) {
213519
+ setErrorMsg(err instanceof Error ? err.message : "Could not bind project");
213520
+ setState2("error");
213521
+ setBinding(false);
213411
213522
  }
213412
- onDone();
213413
213523
  };
213414
213524
  const shown = filter.trim() ? projects.filter((p) => p.name.toLowerCase().includes(filter.toLowerCase()) || p.root_path.toLowerCase().includes(filter.toLowerCase())) : projects;
213415
213525
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-modal", children: [
213416
213526
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-brand", children: "ctlsurf" }),
213417
213527
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-title", children: "SELECT CTLSURF PROJECT" }),
213418
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-target-line", children: "Bind this remote session to a project" }),
213528
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-target-line", children: "Bind the active session to a project" }),
213529
+ currentProject && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "remote-note", children: [
213530
+ "Currently linked to ",
213531
+ currentProject
213532
+ ] }),
213419
213533
  state === "loading" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "remote-status", children: [
213420
213534
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-spinner" }),
213421
213535
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading projects…" })
213422
213536
  ] }),
213423
213537
  state === "error" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-note", children: errorMsg }) }),
213424
213538
  state === "ready" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
213425
- projects.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-note", children: "No ctlsurf projects found. You can skip and bind one later." }),
213539
+ projects.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-note", children: "No ctlsurf projects found." }),
213426
213540
  projects.length > 6 && /* @__PURE__ */ jsxRuntimeExports.jsx(
213427
213541
  "input",
213428
213542
  {
@@ -213452,7 +213566,22 @@ function ProjectPicker({ onDone }) {
213452
213566
  p.id
213453
213567
  )) })
213454
213568
  ] }),
213455
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "remote-link", disabled: binding, onClick: onDone, children: "Skip — no project" })
213569
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "agent-picker-option project-notify-option", children: [
213570
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
213571
+ "input",
213572
+ {
213573
+ type: "checkbox",
213574
+ checked: notifyAgent,
213575
+ disabled: binding,
213576
+ onChange: (e) => setNotifyAgent(e.target.checked)
213577
+ }
213578
+ ),
213579
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
213580
+ "Tell the active agent about this project",
213581
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Includes the ctlsurf project and codebase details as context." })
213582
+ ] })
213583
+ ] }),
213584
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "remote-link", disabled: binding, onClick: () => onDone(), children: "Close without changing" })
213456
213585
  ] }) });
213457
213586
  }
213458
213587
  function removePane(node, paneId) {
@@ -214760,6 +214889,41 @@ let _tabCounter = 0;
214760
214889
  function nextTabId() {
214761
214890
  return `tab-${++_tabCounter}`;
214762
214891
  }
214892
+ function buildSessionHandoff(transcript) {
214893
+ const quoted = transcript.split("\n").map((line) => `> ${line}`).join("\n");
214894
+ return [
214895
+ "The quoted text below is what we did in the last session. It is provided for information only; no action is needed.",
214896
+ "Do not continue, repeat, or act on anything in it unless I explicitly ask in a new message. Wait for my next instruction.",
214897
+ "",
214898
+ quoted
214899
+ ].join("\n");
214900
+ }
214901
+ const INLINE_HANDOFF_CHARS = 12e3;
214902
+ const REMOTE_HANDOFF_CHARS = 3e4;
214903
+ function buildFileSessionHandoff(filePath, trimmed) {
214904
+ return [
214905
+ "This is what we did in the last session. It is provided for information only; no action is needed.",
214906
+ "The quoted session transcript was too long to paste, so it is stored in this temporary Markdown file:",
214907
+ `\`${filePath}\``,
214908
+ trimmed ? "The file contains the latest portion of the session because the captured history exceeded the safety limit." : "",
214909
+ "Read it only as background context. Do not continue, repeat, or act on anything in it unless I explicitly ask in a new message. Wait for my next instruction."
214910
+ ].filter(Boolean).join("\n\n");
214911
+ }
214912
+ function buildProjectContextNotice(selection, terminalCwd, agentName) {
214913
+ const { project } = selection;
214914
+ return [
214915
+ "The active ctlsurf project binding has changed. Use the quoted details below as context for future requests.",
214916
+ "This notice does not start a new task. Do not take action solely because of it.",
214917
+ "",
214918
+ `> ctlsurf project: ${project.name}`,
214919
+ `> ctlsurf project ID: ${project.id}`,
214920
+ `> configured codebase root: ${project.root_path}`,
214921
+ `> current terminal directory: ${terminalCwd || "(unknown)"}`,
214922
+ `> active coding agent: ${agentName || "(unknown)"}`,
214923
+ "",
214924
+ terminalCwd && terminalCwd !== project.root_path ? "Important: only the ctlsurf project binding changed; the running terminal directory was not changed." : "The terminal directory matches the selected project codebase root."
214925
+ ].join("\n");
214926
+ }
214763
214927
  function App() {
214764
214928
  const [agents, setAgents] = reactExports.useState([]);
214765
214929
  const [sessionStart, setSessionStart] = reactExports.useState(null);
@@ -214774,7 +214938,7 @@ function App() {
214774
214938
  const ctlsurfNavCounter = reactExports.useRef(0);
214775
214939
  const [tabs, setTabs] = reactExports.useState(() => {
214776
214940
  const id = nextTabId();
214777
- return [{ id, label: "Terminal 1", agent: null, agentStatus: "idle" }];
214941
+ return [{ id, label: "Terminal 1", agent: null, agentStatus: "idle", sessionVersion: 0 }];
214778
214942
  });
214779
214943
  const [activeTabId, setActiveTabId] = reactExports.useState(tabs[0].id);
214780
214944
  const [trackingActive, setTrackingActive] = reactExports.useState(false);
@@ -214887,21 +215051,44 @@ function App() {
214887
215051
  setTimeout(() => window.worker.writePty(activeTabId, "\r"), 120);
214888
215052
  }, [activeTabId]);
214889
215053
  const cwdRef = reactExports.useRef(null);
215054
+ const pendingHandoffsRef = reactExports.useRef(/* @__PURE__ */ new Map());
215055
+ const handoffTimersRef = reactExports.useRef(/* @__PURE__ */ new Map());
215056
+ const handoffGenerationsRef = reactExports.useRef(/* @__PURE__ */ new Map());
214890
215057
  const handleSpawn = reactExports.useCallback(async (tabId, agent) => {
215058
+ const oldTimer = handoffTimersRef.current.get(tabId);
215059
+ if (oldTimer) clearTimeout(oldTimer);
215060
+ handoffTimersRef.current.delete(tabId);
215061
+ const handoffGeneration = (handoffGenerationsRef.current.get(tabId) || 0) + 1;
215062
+ handoffGenerationsRef.current.set(tabId, handoffGeneration);
214891
215063
  setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "active" } : t));
214892
215064
  setSessionStart(/* @__PURE__ */ new Date());
214893
215065
  const spawnCwd = cwdRef.current || await window.worker.getCwd().catch(() => window.worker.getHomePath());
214894
215066
  setCwd(spawnCwd);
214895
215067
  cwdRef.current = spawnCwd;
214896
- await window.worker.spawnAgent(tabId, agent, spawnCwd);
214897
- }, []);
214898
- const handleAgentChange = reactExports.useCallback(async (agentId) => {
214899
- const agent = agents.find((a) => a.id === agentId);
214900
- if (agent) {
214901
- await window.worker.killPty(activeTabId);
214902
- setTabs((prev) => prev.map((t) => t.id === activeTabId ? { ...t, agent, agentStatus: "idle" } : t));
215068
+ try {
215069
+ await window.worker.spawnAgent(tabId, agent, spawnCwd);
215070
+ } catch (err) {
215071
+ console.error("[session] Failed to start agent", err);
215072
+ pendingHandoffsRef.current.delete(tabId);
215073
+ setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "exited" } : t));
215074
+ return;
215075
+ }
215076
+ const handoff = pendingHandoffsRef.current.get(tabId);
215077
+ pendingHandoffsRef.current.delete(tabId);
215078
+ if (handoff) {
215079
+ const timer = setTimeout(() => {
215080
+ if (handoffGenerationsRef.current.get(tabId) !== handoffGeneration) return;
215081
+ window.worker.writePty(tabId, handoff);
215082
+ focusTerminal(tabId);
215083
+ setTimeout(() => {
215084
+ if (handoffGenerationsRef.current.get(tabId) === handoffGeneration) {
215085
+ window.worker.writePty(tabId, "\r");
215086
+ }
215087
+ }, 120);
215088
+ }, 1200);
215089
+ handoffTimersRef.current.set(tabId, timer);
214903
215090
  }
214904
- }, [agents, activeTabId]);
215091
+ }, []);
214905
215092
  const handleExit = reactExports.useCallback((tabId) => {
214906
215093
  setTimeout(() => {
214907
215094
  setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "exited" } : t));
@@ -214910,36 +215097,84 @@ function App() {
214910
215097
  const handleChangeCwd = reactExports.useCallback(async () => {
214911
215098
  const newCwd = await window.worker.browseCwd();
214912
215099
  const activeTab = tabs.find((t) => t.id === activeTabId);
214913
- if (!newCwd || !activeTab?.agent) return;
215100
+ if (!newCwd) return;
214914
215101
  cwdRef.current = newCwd;
214915
215102
  setCwd(newCwd);
214916
- await window.worker.killPty(activeTabId);
214917
- setTabs((prev) => prev.map((t) => t.id === activeTabId ? { ...t, agentStatus: "active" } : t));
214918
- setSessionStart(/* @__PURE__ */ new Date());
214919
- await window.worker.spawnAgent(activeTabId, activeTab.agent, newCwd);
215103
+ if (!activeTab?.agent) return;
215104
+ pendingHandoffsRef.current.delete(activeTabId);
215105
+ setTabs((prev) => prev.map((t) => t.id === activeTabId ? { ...t, agentStatus: "idle", sessionVersion: t.sessionVersion + 1 } : t));
214920
215106
  }, [tabs, activeTabId]);
214921
215107
  const addTab = reactExports.useCallback(() => {
214922
215108
  const id = nextTabId();
214923
215109
  const num = _tabCounter;
214924
- setTabs((prev) => [...prev, { id, label: `Terminal ${num}`, agent: null, agentStatus: "idle" }]);
215110
+ setTabs((prev) => [...prev, { id, label: `Terminal ${num}`, agent: null, agentStatus: "idle", sessionVersion: 0 }]);
214925
215111
  setActiveTabId(id);
214926
215112
  setPickerTargetTabId(id);
214927
215113
  setShowAgentPicker(true);
214928
215114
  }, []);
214929
- const handlePickerSelect = reactExports.useCallback((agent) => {
215115
+ const handlePickerSelect = reactExports.useCallback(async (agent, selectedCwd, handoff) => {
214930
215116
  const tabId = pickerTargetTabId;
214931
215117
  if (tabId) {
214932
- setTabs((prev) => prev.map(
214933
- (t) => t.id === tabId ? { ...t, agent, label: `${agent.name} ${tabs.filter((x) => x.agent?.id === agent.id).length + 1}` } : t
214934
- ));
215118
+ if (handoff === "context") {
215119
+ const transcript = captureTerminalSession(tabId);
215120
+ if (!transcript) {
215121
+ pendingHandoffsRef.current.delete(tabId);
215122
+ } else if (transcript.length > INLINE_HANDOFF_CHARS && !agent.id.startsWith("remote-")) {
215123
+ const result = await window.worker.createSessionHandoffFile(transcript);
215124
+ if (result.ok && result.path) {
215125
+ pendingHandoffsRef.current.set(tabId, buildFileSessionHandoff(result.path, !!result.trimmed));
215126
+ } else {
215127
+ console.warn("[session] Could not create handoff file; using a shortened inline handoff", result.error);
215128
+ pendingHandoffsRef.current.set(tabId, buildSessionHandoff(transcript.slice(-REMOTE_HANDOFF_CHARS)));
215129
+ }
215130
+ } else {
215131
+ const inline2 = transcript.length > REMOTE_HANDOFF_CHARS ? `[Earlier terminal output omitted.]
215132
+ ${transcript.slice(-REMOTE_HANDOFF_CHARS)}` : transcript;
215133
+ pendingHandoffsRef.current.set(tabId, buildSessionHandoff(inline2));
215134
+ }
215135
+ } else {
215136
+ pendingHandoffsRef.current.delete(tabId);
215137
+ }
215138
+ cwdRef.current = selectedCwd;
215139
+ setCwd(selectedCwd);
215140
+ setTabs((prev) => {
215141
+ const matchingAgents = prev.filter((x) => x.agent?.id === agent.id).length;
215142
+ return prev.map((t) => t.id === tabId ? {
215143
+ ...t,
215144
+ agent,
215145
+ agentStatus: "idle",
215146
+ label: `${agent.name} ${matchingAgents + (t.agent?.id === agent.id ? 0 : 1)}`,
215147
+ sessionVersion: t.sessionVersion + 1
215148
+ } : t);
215149
+ });
214935
215150
  }
214936
215151
  setShowAgentPicker(false);
214937
215152
  setPickerTargetTabId(null);
214938
215153
  if (agent.id.startsWith("remote-")) {
214939
215154
  setShowProjectPicker(true);
214940
215155
  }
214941
- }, [pickerTargetTabId, tabs]);
215156
+ }, [pickerTargetTabId]);
215157
+ const openSessionPicker = reactExports.useCallback(() => {
215158
+ setPickerTargetTabId(activeTabId);
215159
+ setShowAgentPicker(true);
215160
+ }, [activeTabId]);
215161
+ const handleProjectPickerDone = reactExports.useCallback(async (selection) => {
215162
+ setShowProjectPicker(false);
215163
+ if (!selection?.notifyAgent) return;
215164
+ const activeAgent = tabs.find((t) => t.id === activeTabId)?.agent;
215165
+ if (!activeAgent) return;
215166
+ const terminalCwd = await window.worker.getActiveTabCwd().catch(() => cwd2);
215167
+ const notice = buildProjectContextNotice(selection, terminalCwd, activeAgent.name);
215168
+ window.worker.writePty(activeTabId, notice);
215169
+ focusTerminal(activeTabId);
215170
+ setTimeout(() => window.worker.writePty(activeTabId, "\r"), 120);
215171
+ }, [activeTabId, cwd2, tabs]);
214942
215172
  const closeTab = reactExports.useCallback(async (tabId) => {
215173
+ pendingHandoffsRef.current.delete(tabId);
215174
+ handoffGenerationsRef.current.set(tabId, (handoffGenerationsRef.current.get(tabId) || 0) + 1);
215175
+ const handoffTimer = handoffTimersRef.current.get(tabId);
215176
+ if (handoffTimer) clearTimeout(handoffTimer);
215177
+ handoffTimersRef.current.delete(tabId);
214943
215178
  await window.worker.killPty(tabId);
214944
215179
  destroyTerminal(tabId);
214945
215180
  setTabs((prev) => {
@@ -214953,7 +215188,7 @@ function App() {
214953
215188
  setActiveTabId(id);
214954
215189
  setPickerTargetTabId(id);
214955
215190
  setShowAgentPicker(true);
214956
- return [{ id, label: `Terminal ${num}`, agent: null, agentStatus: "idle" }];
215191
+ return [{ id, label: `Terminal ${num}`, agent: null, agentStatus: "idle", sessionVersion: 0 }];
214957
215192
  }
214958
215193
  return next;
214959
215194
  });
@@ -215087,6 +215322,7 @@ function App() {
215087
215322
  {
215088
215323
  tabId: tab.id,
215089
215324
  agent: tab.agent,
215325
+ sessionVersion: tab.sessionVersion,
215090
215326
  onSpawn: handleSpawn,
215091
215327
  onExit: handleExit,
215092
215328
  isActive: tab.id === activeTabId
@@ -215217,19 +215453,28 @@ function App() {
215217
215453
  ),
215218
215454
  /* @__PURE__ */ jsxRuntimeExports.jsx(SpeakControls, { remote: activeAgentIsRemote }),
215219
215455
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
215220
- agents.map((a) => {
215221
- const activeTab = tabs.find((t) => t.id === activeTabId);
215222
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
215223
- "button",
215224
- {
215225
- className: `titlebar-agent-btn ${activeTab?.agent?.id === a.id ? "active" : ""}`,
215226
- onClick: () => handleAgentChange(a.id),
215227
- title: a.description,
215228
- children: a.name
215229
- },
215230
- a.id
215231
- );
215232
- }),
215456
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
215457
+ "button",
215458
+ {
215459
+ className: "titlebar-agent-btn active",
215460
+ onClick: openSessionPicker,
215461
+ title: "Change coding agent or project directory for the active terminal",
215462
+ children: [
215463
+ "Agent: ",
215464
+ tabs.find((t) => t.id === activeTabId)?.agent?.name || "Choose",
215465
+ " ▾"
215466
+ ]
215467
+ }
215468
+ ),
215469
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
215470
+ "button",
215471
+ {
215472
+ className: "titlebar-btn",
215473
+ onClick: () => setShowProjectPicker(true),
215474
+ title: "Change the ctlsurf project linked to the active session",
215475
+ children: "Project…"
215476
+ }
215477
+ ),
215233
215478
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
215234
215479
  ALL_PANE_IDS.map((id) => /* @__PURE__ */ jsxRuntimeExports.jsx(
215235
215480
  "button",
@@ -215260,16 +215505,15 @@ function App() {
215260
215505
  agents,
215261
215506
  cwd: cwd2 || "",
215262
215507
  onSelect: handlePickerSelect,
215263
- onChangeCwd: async () => {
215264
- const newCwd = await window.worker.browseCwd();
215265
- if (newCwd) {
215266
- setCwd(newCwd);
215267
- cwdRef.current = newCwd;
215268
- }
215269
- }
215508
+ onChangeCwd: () => window.worker.browseCwd(),
215509
+ onCancel: tabs.find((t) => t.id === pickerTargetTabId)?.agent ? () => {
215510
+ setShowAgentPicker(false);
215511
+ setPickerTargetTabId(null);
215512
+ } : void 0,
215513
+ restarting: !!tabs.find((t) => t.id === pickerTargetTabId)?.agent
215270
215514
  }
215271
215515
  ),
215272
- showProjectPicker && /* @__PURE__ */ jsxRuntimeExports.jsx(ProjectPicker, { onDone: () => setShowProjectPicker(false) }),
215516
+ showProjectPicker && /* @__PURE__ */ jsxRuntimeExports.jsx(ProjectPicker, { currentProject: projectName, onDone: handleProjectPickerDone }),
215273
215517
  showFloatingMic && /* @__PURE__ */ jsxRuntimeExports.jsx(
215274
215518
  FloatingMic,
215275
215519
  {