@phenx-inc/ctlsurf 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-B77M9HlN.js → cssMode-Xx1-SozN.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-oUT2zvyQ.js → freemarker2-DM49yPw1.js} +1 -1
  7. package/out/renderer/assets/{handlebars-jCckoi5j.js → handlebars-DJJBblpV.js} +1 -1
  8. package/out/renderer/assets/{html-kAgrAaKW.js → html-DnMnJOxw.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-DIWkIawy.js → htmlMode-BG5u2CSH.js} +3 -3
  10. package/out/renderer/assets/{index-CN3UN7Tm.js → index-D65QIRev.js} +318 -95
  11. package/out/renderer/assets/{index-3sFJEz7i.css → index-DID1LlMU.css} +88 -0
  12. package/out/renderer/assets/{javascript-B9GYUE-I.js → javascript-Dci_phmN.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-BzFKTZjJ.js → jsonMode-Dmp3JJSm.js} +3 -3
  14. package/out/renderer/assets/{liquid-Ccw-S_Y6.js → liquid-C9KVysk1.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-BE0g7ubH.js → lspLanguageFeatures-IDD0-KWa.js} +1 -1
  16. package/out/renderer/assets/{mdx-oXeAA0qU.js → mdx-ascmBFXX.js} +1 -1
  17. package/out/renderer/assets/{python-DlSX3TvA.js → python-DcbM4Hy5.js} +1 -1
  18. package/out/renderer/assets/{razor-ButO9S7r.js → razor-XJROxpYK.js} +1 -1
  19. package/out/renderer/assets/{tsMode-CTCpE-67.js → tsMode-EXbzul3n.js} +1 -1
  20. package/out/renderer/assets/{typescript-9o62WOnS.js → typescript-DhTtFBLt.js} +1 -1
  21. package/out/renderer/assets/{xml-C4nOH2_D.js → xml-BLfEkVl1.js} +1 -1
  22. package/out/renderer/assets/{yaml-mXYgKoWW.js → yaml-DpzsqvWa.js} +1 -1
  23. package/out/renderer/index.html +2 -2
  24. package/package.json +1 -1
  25. package/src/main/index.ts +52 -1
  26. package/src/main/orchestrator.ts +18 -3
  27. package/src/preload/index.ts +4 -0
  28. package/src/renderer/App.tsx +179 -50
  29. package/src/renderer/components/AgentPicker.tsx +61 -10
  30. package/src/renderer/components/FloatingMic.tsx +15 -11
  31. package/src/renderer/components/ProjectPicker.tsx +44 -13
  32. package/src/renderer/components/TerminalPanel.tsx +45 -9
  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-B77M9HlN.js","./lspLanguageFeatures-BE0g7ubH.js","./htmlMode-DIWkIawy.js","./jsonMode-BzFKTZjJ.js","./javascript-B9GYUE-I.js","./typescript-9o62WOnS.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-Xx1-SozN.js","./lspLanguageFeatures-IDD0-KWa.js","./htmlMode-BG5u2CSH.js","./jsonMode-Dmp3JJSm.js","./javascript-Dci_phmN.js","./typescript-DhTtFBLt.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
  }
@@ -19532,6 +19532,11 @@ function getOrCreateTerminal(tabId, onExit) {
19532
19532
  }
19533
19533
  const terminal = new xtermExports.Terminal({
19534
19534
  cursorBlink: true,
19535
+ // Codex and other modern CLIs often render links as OSC 8 hyperlinks
19536
+ // instead of plain URL text. OSC 8 links are handled by xterm core (not
19537
+ // WebLinksAddon), whose default activation calls window.open(). Route them
19538
+ // through the same embedded-panel handler as detected plain-text URLs.
19539
+ linkHandler: { activate: handleTerminalLink },
19535
19540
  // Off by default on macOS; without this a right-click would reselect the
19536
19541
  // word under the cursor and clobber a multi-line table selection.
19537
19542
  rightClickSelectsWord: false,
@@ -19594,7 +19599,7 @@ function getOrCreateTerminal(tabId, onExit) {
19594
19599
  fitAddon,
19595
19600
  unsubData: null,
19596
19601
  unsubExit: null,
19597
- spawnedAgentId: null,
19602
+ spawnedSessionKey: null,
19598
19603
  pinnedToBottom: true,
19599
19604
  anchorLine: null,
19600
19605
  userScrollPending: false,
@@ -19638,6 +19643,25 @@ function getOrCreateTerminal(tabId, onExit) {
19638
19643
  function focusTerminal(tabId) {
19639
19644
  _terminals.get(tabId)?.terminal.focus();
19640
19645
  }
19646
+ const MAX_HANDOFF_CHARS = 1e6;
19647
+ function captureTerminalSession(tabId) {
19648
+ const terminal = _terminals.get(tabId)?.terminal;
19649
+ if (!terminal) return "";
19650
+ const buffer = terminal.buffer.active;
19651
+ const lines = [];
19652
+ for (let i2 = 0; i2 < buffer.length; i2++) {
19653
+ const line = buffer.getLine(i2);
19654
+ if (!line) continue;
19655
+ const text22 = line.translateToString(true);
19656
+ if (line.isWrapped && lines.length > 0) lines[lines.length - 1] += text22;
19657
+ else lines.push(text22);
19658
+ }
19659
+ while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
19660
+ const text2 = lines.join("\n").trim();
19661
+ if (text2.length <= MAX_HANDOFF_CHARS) return text2;
19662
+ return `[Earlier terminal output omitted; showing the last ${MAX_HANDOFF_CHARS.toLocaleString()} characters.]
19663
+ ${text2.slice(-MAX_HANDOFF_CHARS)}`;
19664
+ }
19641
19665
  function destroyTerminal(tabId) {
19642
19666
  const state = _terminals.get(tabId);
19643
19667
  if (!state) return;
@@ -19646,7 +19670,7 @@ function destroyTerminal(tabId) {
19646
19670
  state.terminal.dispose();
19647
19671
  _terminals.delete(tabId);
19648
19672
  }
19649
- function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19673
+ function TerminalPanel({ tabId, agent, sessionVersion, onSpawn, onExit, isActive }) {
19650
19674
  const containerRef = reactExports.useRef(null);
19651
19675
  const [menu, setMenu] = reactExports.useState(null);
19652
19676
  const [snapOpen, setSnapOpen] = reactExports.useState(false);
@@ -19736,7 +19760,7 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19736
19760
  const attached = _terminals.get(tabId);
19737
19761
  if (attached) reassert(attached);
19738
19762
  const onWheel = () => markGesture(tabId);
19739
- terminal.element?.addEventListener("wheel", onWheel, { passive: true });
19763
+ terminal.element?.addEventListener("wheel", onWheel, { capture: true, passive: true });
19740
19764
  let resizeTimeout;
19741
19765
  const handleResize = () => {
19742
19766
  clearTimeout(resizeTimeout);
@@ -19759,15 +19783,16 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19759
19783
  clearTimeout(resizeTimeout);
19760
19784
  window.removeEventListener("resize", handleResize);
19761
19785
  observer.disconnect();
19762
- terminal.element?.removeEventListener("wheel", onWheel);
19786
+ terminal.element?.removeEventListener("wheel", onWheel, true);
19763
19787
  };
19764
19788
  }, [tabId, onExit]);
19765
19789
  reactExports.useEffect(() => {
19766
19790
  if (!agent) return;
19767
19791
  const state = _terminals.get(tabId);
19768
19792
  if (!state) return;
19769
- if (state.spawnedAgentId === agent.id) return;
19770
- state.spawnedAgentId = agent.id;
19793
+ const sessionKey = `${agent.id}:${sessionVersion}`;
19794
+ if (state.spawnedSessionKey === sessionKey) return;
19795
+ state.spawnedSessionKey = sessionKey;
19771
19796
  state.terminal.clear();
19772
19797
  onSpawn(tabId, agent).then(() => {
19773
19798
  const s = _terminals.get(tabId);
@@ -19780,7 +19805,7 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19780
19805
  }
19781
19806
  state.terminal.focus();
19782
19807
  });
19783
- }, [tabId, agent, onSpawn]);
19808
+ }, [tabId, agent, sessionVersion, onSpawn]);
19784
19809
  reactExports.useEffect(() => {
19785
19810
  if (isActive) {
19786
19811
  window.worker.setActiveTab(tabId);
@@ -20318,7 +20343,7 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
20318
20343
  const POS_KEY = "ctlsurf.floatingMicPos";
20319
20344
  const EDGE = 20;
20320
20345
  const TOP_MIN = 46;
20321
- const BOTTOM_GAP = 36;
20346
+ const BOTTOM_GAP = 72;
20322
20347
  function loadPos() {
20323
20348
  try {
20324
20349
  const raw = localStorage.getItem(POS_KEY);
@@ -20366,15 +20391,17 @@ function FloatingMic({ onTranscript, onHide }) {
20366
20391
  };
20367
20392
  }, []);
20368
20393
  reactExports.useEffect(() => {
20369
- if (pos) return;
20370
- const el = elRef.current;
20371
- const w = el?.offsetWidth ?? 64;
20372
- const h2 = el?.offsetHeight ?? 90;
20373
- setPos({
20374
- x: window.innerWidth - w - EDGE,
20375
- y: window.innerHeight - h2 - BOTTOM_GAP
20394
+ setPos((current) => {
20395
+ if (current) return clamp2(current.x, current.y);
20396
+ const el = elRef.current;
20397
+ const w = el?.offsetWidth ?? 64;
20398
+ const h2 = el?.offsetHeight ?? 90;
20399
+ return clamp2(
20400
+ window.innerWidth - w - EDGE,
20401
+ window.innerHeight - h2 - BOTTOM_GAP
20402
+ );
20376
20403
  });
20377
- }, [pos]);
20404
+ }, [clamp2]);
20378
20405
  reactExports.useEffect(() => {
20379
20406
  const onResize = () => setPos((p) => p ? clamp2(p.x, p.y) : p);
20380
20407
  window.addEventListener("resize", onResize);
@@ -208416,7 +208443,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
208416
208443
  modeConfigurationDefault$2
208417
208444
  );
208418
208445
  function getMode$3() {
208419
- return __vitePreload(() => import("./cssMode-B77M9HlN.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
208446
+ return __vitePreload(() => import("./cssMode-Xx1-SozN.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
208420
208447
  }
208421
208448
  languages.onLanguage("less", () => {
208422
208449
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -208521,7 +208548,7 @@ const razorLanguageService = registerHTMLLanguageService(
208521
208548
  );
208522
208549
  const razorDefaults = razorLanguageService.defaults;
208523
208550
  function getMode$2() {
208524
- return __vitePreload(() => import("./htmlMode-DIWkIawy.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
208551
+ return __vitePreload(() => import("./htmlMode-BG5u2CSH.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
208525
208552
  }
208526
208553
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
208527
208554
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -208605,7 +208632,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
208605
208632
  );
208606
208633
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
208607
208634
  function getMode$1() {
208608
- return __vitePreload(() => import("./jsonMode-BzFKTZjJ.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208635
+ return __vitePreload(() => import("./jsonMode-Dmp3JJSm.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208609
208636
  }
208610
208637
  languages.register({
208611
208638
  id: "json",
@@ -208851,7 +208878,7 @@ const getJavaScriptWorker = () => {
208851
208878
  return getMode().then((mode) => mode.getJavaScriptWorker());
208852
208879
  };
208853
208880
  function getMode() {
208854
- return __vitePreload(() => import("./tsMode-CTCpE-67.js"), true ? [] : void 0, import.meta.url);
208881
+ return __vitePreload(() => import("./tsMode-EXbzul3n.js"), true ? [] : void 0, import.meta.url);
208855
208882
  }
208856
208883
  languages.onLanguage("typescript", () => {
208857
208884
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -209046,49 +209073,49 @@ registerLanguage({
209046
209073
  extensions: [".ftl", ".ftlh", ".ftlx"],
209047
209074
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
209048
209075
  loader: () => {
209049
- return __vitePreload(() => import("./freemarker2-oUT2zvyQ.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
209076
+ return __vitePreload(() => import("./freemarker2-DM49yPw1.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
209050
209077
  }
209051
209078
  });
209052
209079
  registerLanguage({
209053
209080
  id: "freemarker2.tag-angle.interpolation-dollar",
209054
209081
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
209055
209082
  loader: () => {
209056
- return __vitePreload(() => import("./freemarker2-oUT2zvyQ.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
209083
+ return __vitePreload(() => import("./freemarker2-DM49yPw1.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
209057
209084
  }
209058
209085
  });
209059
209086
  registerLanguage({
209060
209087
  id: "freemarker2.tag-bracket.interpolation-dollar",
209061
209088
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
209062
209089
  loader: () => {
209063
- return __vitePreload(() => import("./freemarker2-oUT2zvyQ.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
209090
+ return __vitePreload(() => import("./freemarker2-DM49yPw1.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
209064
209091
  }
209065
209092
  });
209066
209093
  registerLanguage({
209067
209094
  id: "freemarker2.tag-angle.interpolation-bracket",
209068
209095
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
209069
209096
  loader: () => {
209070
- return __vitePreload(() => import("./freemarker2-oUT2zvyQ.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
209097
+ return __vitePreload(() => import("./freemarker2-DM49yPw1.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
209071
209098
  }
209072
209099
  });
209073
209100
  registerLanguage({
209074
209101
  id: "freemarker2.tag-bracket.interpolation-bracket",
209075
209102
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
209076
209103
  loader: () => {
209077
- return __vitePreload(() => import("./freemarker2-oUT2zvyQ.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
209104
+ return __vitePreload(() => import("./freemarker2-DM49yPw1.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
209078
209105
  }
209079
209106
  });
209080
209107
  registerLanguage({
209081
209108
  id: "freemarker2.tag-auto.interpolation-dollar",
209082
209109
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
209083
209110
  loader: () => {
209084
- return __vitePreload(() => import("./freemarker2-oUT2zvyQ.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
209111
+ return __vitePreload(() => import("./freemarker2-DM49yPw1.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
209085
209112
  }
209086
209113
  });
209087
209114
  registerLanguage({
209088
209115
  id: "freemarker2.tag-auto.interpolation-bracket",
209089
209116
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
209090
209117
  loader: () => {
209091
- return __vitePreload(() => import("./freemarker2-oUT2zvyQ.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
209118
+ return __vitePreload(() => import("./freemarker2-DM49yPw1.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
209092
209119
  }
209093
209120
  });
209094
209121
  registerLanguage({
@@ -209109,7 +209136,7 @@ registerLanguage({
209109
209136
  extensions: [".handlebars", ".hbs"],
209110
209137
  aliases: ["Handlebars", "handlebars", "hbs"],
209111
209138
  mimetypes: ["text/x-handlebars-template"],
209112
- loader: () => __vitePreload(() => import("./handlebars-jCckoi5j.js"), true ? [] : void 0, import.meta.url)
209139
+ loader: () => __vitePreload(() => import("./handlebars-DJJBblpV.js"), true ? [] : void 0, import.meta.url)
209113
209140
  });
209114
209141
  registerLanguage({
209115
209142
  id: "hcl",
@@ -209122,7 +209149,7 @@ registerLanguage({
209122
209149
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
209123
209150
  aliases: ["HTML", "htm", "html", "xhtml"],
209124
209151
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
209125
- loader: () => __vitePreload(() => import("./html-kAgrAaKW.js"), true ? [] : void 0, import.meta.url)
209152
+ loader: () => __vitePreload(() => import("./html-DnMnJOxw.js"), true ? [] : void 0, import.meta.url)
209126
209153
  });
209127
209154
  registerLanguage({
209128
209155
  id: "ini",
@@ -209145,7 +209172,7 @@ registerLanguage({
209145
209172
  filenames: ["jakefile"],
209146
209173
  aliases: ["JavaScript", "javascript", "js"],
209147
209174
  mimetypes: ["text/javascript"],
209148
- loader: () => __vitePreload(() => import("./javascript-B9GYUE-I.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
209175
+ loader: () => __vitePreload(() => import("./javascript-Dci_phmN.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
209149
209176
  });
209150
209177
  registerLanguage({
209151
209178
  id: "julia",
@@ -209184,7 +209211,7 @@ registerLanguage({
209184
209211
  extensions: [".liquid", ".html.liquid"],
209185
209212
  aliases: ["Liquid", "liquid"],
209186
209213
  mimetypes: ["application/liquid"],
209187
- loader: () => __vitePreload(() => import("./liquid-Ccw-S_Y6.js"), true ? [] : void 0, import.meta.url)
209214
+ loader: () => __vitePreload(() => import("./liquid-C9KVysk1.js"), true ? [] : void 0, import.meta.url)
209188
209215
  });
209189
209216
  registerLanguage({
209190
209217
  id: "m3",
@@ -209202,7 +209229,7 @@ registerLanguage({
209202
209229
  id: "mdx",
209203
209230
  extensions: [".mdx"],
209204
209231
  aliases: ["MDX", "mdx"],
209205
- loader: () => __vitePreload(() => import("./mdx-oXeAA0qU.js"), true ? [] : void 0, import.meta.url)
209232
+ loader: () => __vitePreload(() => import("./mdx-ascmBFXX.js"), true ? [] : void 0, import.meta.url)
209206
209233
  });
209207
209234
  registerLanguage({
209208
209235
  id: "mips",
@@ -209301,7 +209328,7 @@ registerLanguage({
209301
209328
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
209302
209329
  aliases: ["Python", "py"],
209303
209330
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
209304
- loader: () => __vitePreload(() => import("./python-DlSX3TvA.js"), true ? [] : void 0, import.meta.url)
209331
+ loader: () => __vitePreload(() => import("./python-DcbM4Hy5.js"), true ? [] : void 0, import.meta.url)
209305
209332
  });
209306
209333
  registerLanguage({
209307
209334
  id: "qsharp",
@@ -209320,7 +209347,7 @@ registerLanguage({
209320
209347
  extensions: [".cshtml"],
209321
209348
  aliases: ["Razor", "razor"],
209322
209349
  mimetypes: ["text/x-cshtml"],
209323
- loader: () => __vitePreload(() => import("./razor-ButO9S7r.js"), true ? [] : void 0, import.meta.url)
209350
+ loader: () => __vitePreload(() => import("./razor-XJROxpYK.js"), true ? [] : void 0, import.meta.url)
209324
209351
  });
209325
209352
  registerLanguage({
209326
209353
  id: "redis",
@@ -209453,7 +209480,7 @@ registerLanguage({
209453
209480
  aliases: ["TypeScript", "ts", "typescript"],
209454
209481
  mimetypes: ["text/typescript"],
209455
209482
  loader: () => {
209456
- return __vitePreload(() => import("./typescript-9o62WOnS.js"), true ? [] : void 0, import.meta.url);
209483
+ return __vitePreload(() => import("./typescript-DhTtFBLt.js"), true ? [] : void 0, import.meta.url);
209457
209484
  }
209458
209485
  });
209459
209486
  registerLanguage({
@@ -209498,14 +209525,14 @@ registerLanguage({
209498
209525
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
209499
209526
  aliases: ["XML", "xml"],
209500
209527
  mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
209501
- loader: () => __vitePreload(() => import("./xml-C4nOH2_D.js"), true ? [] : void 0, import.meta.url)
209528
+ loader: () => __vitePreload(() => import("./xml-BLfEkVl1.js"), true ? [] : void 0, import.meta.url)
209502
209529
  });
209503
209530
  registerLanguage({
209504
209531
  id: "yaml",
209505
209532
  extensions: [".yaml", ".yml"],
209506
209533
  aliases: ["YAML", "yaml", "YML", "yml"],
209507
209534
  mimetypes: ["application/x-yaml", "text/x-yaml"],
209508
- loader: () => __vitePreload(() => import("./yaml-mXYgKoWW.js"), true ? [] : void 0, import.meta.url)
209535
+ loader: () => __vitePreload(() => import("./yaml-DpzsqvWa.js"), true ? [] : void 0, import.meta.url)
209509
209536
  });
209510
209537
  var __defProp = Object.defineProperty;
209511
209538
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -213279,11 +213306,14 @@ function RemoteConnect({ onConnect, onBack }) {
213279
213306
  ] })
213280
213307
  ] }) });
213281
213308
  }
213282
- function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213283
- const displayPath = cwd2.replace(/^\/Users\/[^/]+/, "~");
213309
+ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd, onCancel, restarting = false }) {
213310
+ const [selectedCwd, setSelectedCwd] = reactExports.useState(cwd2);
213311
+ const displayPath = selectedCwd.replace(/^\/Users\/[^/]+/, "~");
213284
213312
  const [logChat, setLogChat] = reactExports.useState(false);
213285
213313
  const [loaded, setLoaded] = reactExports.useState(false);
213286
213314
  const [remoteMode, setRemoteMode] = reactExports.useState(false);
213315
+ const [handoff, setHandoff] = reactExports.useState("fresh");
213316
+ const [switching, setSwitching] = reactExports.useState(false);
213287
213317
  reactExports.useEffect(() => {
213288
213318
  let cancelled = false;
213289
213319
  window.worker.getLogChat().then((r) => {
@@ -213304,20 +213334,30 @@ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213304
213334
  }
213305
213335
  };
213306
213336
  const handleSelect = async (agent) => {
213337
+ if (switching) return;
213338
+ setSwitching(true);
213307
213339
  if (loaded) {
213308
213340
  try {
213309
213341
  await window.worker.setLogChat(logChat);
213310
213342
  } catch {
213311
213343
  }
213312
213344
  }
213313
- onSelect(agent);
213345
+ try {
213346
+ await onSelect(agent, selectedCwd, handoff);
213347
+ } finally {
213348
+ setSwitching(false);
213349
+ }
213350
+ };
213351
+ const changeCwd = async () => {
213352
+ const next = await onChangeCwd();
213353
+ if (next) setSelectedCwd(next);
213314
213354
  };
213315
213355
  if (remoteMode) {
213316
213356
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
213317
213357
  RemoteConnect,
213318
213358
  {
213319
213359
  onConnect: (agent) => {
213320
- handleSelect(agent);
213360
+ void handleSelect(agent);
213321
213361
  },
213322
213362
  onBack: () => setRemoteMode(false)
213323
213363
  }
@@ -213325,18 +213365,55 @@ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213325
213365
  }
213326
213366
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-modal", children: [
213327
213367
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-brand", children: "ctlsurf" }),
213328
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-cwd", onClick: onChangeCwd, title: "Click to change directory", children: [
213368
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-cwd", onClick: () => void changeCwd(), title: "Click to change directory", children: [
213329
213369
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-cwd-icon", children: "📂" }),
213330
213370
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-cwd-path", children: displayPath }),
213331
213371
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-cwd-change", children: "change" })
213332
213372
  ] }),
213333
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-title", children: "SELECT AGENT" }),
213373
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-title", children: restarting ? "CHANGE SESSION" : "SELECT AGENT" }),
213374
+ restarting && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
213375
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-warning", children: "Choosing an agent restarts this terminal and ends its current process." }),
213376
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("fieldset", { className: "agent-handoff-options", children: [
213377
+ /* @__PURE__ */ jsxRuntimeExports.jsx("legend", { children: "NEW SESSION CONTEXT" }),
213378
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: handoff === "fresh" ? "selected" : "", children: [
213379
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
213380
+ "input",
213381
+ {
213382
+ type: "radio",
213383
+ name: "session-handoff",
213384
+ checked: handoff === "fresh",
213385
+ onChange: () => setHandoff("fresh")
213386
+ }
213387
+ ),
213388
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
213389
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Start fresh" }),
213390
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Do not send anything from this terminal." })
213391
+ ] })
213392
+ ] }),
213393
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: handoff === "context" ? "selected" : "", children: [
213394
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
213395
+ "input",
213396
+ {
213397
+ type: "radio",
213398
+ name: "session-handoff",
213399
+ checked: handoff === "context",
213400
+ onChange: () => setHandoff("context")
213401
+ }
213402
+ ),
213403
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
213404
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Share previous context" }),
213405
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Quote it inline, or use a temporary file when the session is long." })
213406
+ ] })
213407
+ ] })
213408
+ ] })
213409
+ ] }),
213334
213410
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-list", children: [
213335
213411
  agents.map((a) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
213336
213412
  "button",
213337
213413
  {
213338
213414
  className: "agent-picker-item",
213339
- onClick: () => handleSelect(a),
213415
+ disabled: switching,
213416
+ onClick: () => void handleSelect(a),
213340
213417
  children: [
213341
213418
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-name", children: a.name }),
213342
213419
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-desc", children: a.description })
@@ -213348,6 +213425,7 @@ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213348
213425
  "button",
213349
213426
  {
213350
213427
  className: "agent-picker-item",
213428
+ disabled: switching,
213351
213429
  onClick: () => setRemoteMode(true),
213352
213430
  children: [
213353
213431
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-picker-name", children: "🌐 Remote (SSH)" }),
@@ -213367,15 +213445,17 @@ function AgentPicker({ agents, cwd: cwd2, onSelect, onChangeCwd }) {
213367
213445
  ),
213368
213446
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Log chat to ctlsurf" })
213369
213447
  ] }),
213370
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-hint", children: "Select an agent to start" })
213448
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-hint", children: restarting ? "Select an agent to restart in the directory above" : "Select an agent to start" }),
213449
+ onCancel && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "remote-link", disabled: switching, onClick: onCancel, children: "Cancel" })
213371
213450
  ] }) });
213372
213451
  }
213373
- function ProjectPicker({ onDone }) {
213452
+ function ProjectPicker({ onDone, currentProject }) {
213374
213453
  const [state, setState2] = reactExports.useState("loading");
213375
213454
  const [projects, setProjects] = reactExports.useState([]);
213376
213455
  const [errorMsg, setErrorMsg] = reactExports.useState("");
213377
213456
  const [binding, setBinding] = reactExports.useState(false);
213378
213457
  const [filter, setFilter] = reactExports.useState("");
213458
+ const [notifyAgent, setNotifyAgent] = reactExports.useState(true);
213379
213459
  reactExports.useEffect(() => {
213380
213460
  let cancelled = false;
213381
213461
  window.worker.listProjects().then((r) => {
@@ -213399,23 +213479,36 @@ function ProjectPicker({ onDone }) {
213399
213479
  const pick = async (p) => {
213400
213480
  setBinding(true);
213401
213481
  try {
213402
- await window.worker.bindProject(p.root_path);
213403
- } catch {
213482
+ const result = await window.worker.bindProject(p.root_path);
213483
+ if (!result.ok) {
213484
+ setErrorMsg(result.error || "Could not bind project");
213485
+ setState2("error");
213486
+ setBinding(false);
213487
+ return;
213488
+ }
213489
+ onDone({ project: p, notifyAgent });
213490
+ } catch (err) {
213491
+ setErrorMsg(err instanceof Error ? err.message : "Could not bind project");
213492
+ setState2("error");
213493
+ setBinding(false);
213404
213494
  }
213405
- onDone();
213406
213495
  };
213407
213496
  const shown = filter.trim() ? projects.filter((p) => p.name.toLowerCase().includes(filter.toLowerCase()) || p.root_path.toLowerCase().includes(filter.toLowerCase())) : projects;
213408
213497
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "agent-picker-modal", children: [
213409
213498
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-brand", children: "ctlsurf" }),
213410
213499
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "agent-picker-title", children: "SELECT CTLSURF PROJECT" }),
213411
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-target-line", children: "Bind this remote session to a project" }),
213500
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-target-line", children: "Bind the active session to a project" }),
213501
+ currentProject && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "remote-note", children: [
213502
+ "Currently linked to ",
213503
+ currentProject
213504
+ ] }),
213412
213505
  state === "loading" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "remote-status", children: [
213413
213506
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-spinner" }),
213414
213507
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading projects…" })
213415
213508
  ] }),
213416
213509
  state === "error" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-note", children: errorMsg }) }),
213417
213510
  state === "ready" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
213418
- projects.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-note", children: "No ctlsurf projects found. You can skip and bind one later." }),
213511
+ projects.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "remote-note", children: "No ctlsurf projects found." }),
213419
213512
  projects.length > 6 && /* @__PURE__ */ jsxRuntimeExports.jsx(
213420
213513
  "input",
213421
213514
  {
@@ -213445,7 +213538,22 @@ function ProjectPicker({ onDone }) {
213445
213538
  p.id
213446
213539
  )) })
213447
213540
  ] }),
213448
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "remote-link", disabled: binding, onClick: onDone, children: "Skip — no project" })
213541
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "agent-picker-option project-notify-option", children: [
213542
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
213543
+ "input",
213544
+ {
213545
+ type: "checkbox",
213546
+ checked: notifyAgent,
213547
+ disabled: binding,
213548
+ onChange: (e) => setNotifyAgent(e.target.checked)
213549
+ }
213550
+ ),
213551
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
213552
+ "Tell the active agent about this project",
213553
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Includes the ctlsurf project and codebase details as context." })
213554
+ ] })
213555
+ ] }),
213556
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "remote-link", disabled: binding, onClick: () => onDone(), children: "Close without changing" })
213449
213557
  ] }) });
213450
213558
  }
213451
213559
  function removePane(node, paneId) {
@@ -214753,6 +214861,41 @@ let _tabCounter = 0;
214753
214861
  function nextTabId() {
214754
214862
  return `tab-${++_tabCounter}`;
214755
214863
  }
214864
+ function buildSessionHandoff(transcript) {
214865
+ const quoted = transcript.split("\n").map((line) => `> ${line}`).join("\n");
214866
+ return [
214867
+ "The quoted text below is what we did in the last session. It is provided for information only; no action is needed.",
214868
+ "Do not continue, repeat, or act on anything in it unless I explicitly ask in a new message. Wait for my next instruction.",
214869
+ "",
214870
+ quoted
214871
+ ].join("\n");
214872
+ }
214873
+ const INLINE_HANDOFF_CHARS = 12e3;
214874
+ const REMOTE_HANDOFF_CHARS = 3e4;
214875
+ function buildFileSessionHandoff(filePath, trimmed) {
214876
+ return [
214877
+ "This is what we did in the last session. It is provided for information only; no action is needed.",
214878
+ "The quoted session transcript was too long to paste, so it is stored in this temporary Markdown file:",
214879
+ `\`${filePath}\``,
214880
+ trimmed ? "The file contains the latest portion of the session because the captured history exceeded the safety limit." : "",
214881
+ "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."
214882
+ ].filter(Boolean).join("\n\n");
214883
+ }
214884
+ function buildProjectContextNotice(selection, terminalCwd, agentName) {
214885
+ const { project } = selection;
214886
+ return [
214887
+ "The active ctlsurf project binding has changed. Use the quoted details below as context for future requests.",
214888
+ "This notice does not start a new task. Do not take action solely because of it.",
214889
+ "",
214890
+ `> ctlsurf project: ${project.name}`,
214891
+ `> ctlsurf project ID: ${project.id}`,
214892
+ `> configured codebase root: ${project.root_path}`,
214893
+ `> current terminal directory: ${terminalCwd || "(unknown)"}`,
214894
+ `> active coding agent: ${agentName || "(unknown)"}`,
214895
+ "",
214896
+ 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."
214897
+ ].join("\n");
214898
+ }
214756
214899
  function App() {
214757
214900
  const [agents, setAgents] = reactExports.useState([]);
214758
214901
  const [sessionStart, setSessionStart] = reactExports.useState(null);
@@ -214767,7 +214910,7 @@ function App() {
214767
214910
  const ctlsurfNavCounter = reactExports.useRef(0);
214768
214911
  const [tabs, setTabs] = reactExports.useState(() => {
214769
214912
  const id = nextTabId();
214770
- return [{ id, label: "Terminal 1", agent: null, agentStatus: "idle" }];
214913
+ return [{ id, label: "Terminal 1", agent: null, agentStatus: "idle", sessionVersion: 0 }];
214771
214914
  });
214772
214915
  const [activeTabId, setActiveTabId] = reactExports.useState(tabs[0].id);
214773
214916
  const [trackingActive, setTrackingActive] = reactExports.useState(false);
@@ -214880,21 +215023,44 @@ function App() {
214880
215023
  setTimeout(() => window.worker.writePty(activeTabId, "\r"), 120);
214881
215024
  }, [activeTabId]);
214882
215025
  const cwdRef = reactExports.useRef(null);
215026
+ const pendingHandoffsRef = reactExports.useRef(/* @__PURE__ */ new Map());
215027
+ const handoffTimersRef = reactExports.useRef(/* @__PURE__ */ new Map());
215028
+ const handoffGenerationsRef = reactExports.useRef(/* @__PURE__ */ new Map());
214883
215029
  const handleSpawn = reactExports.useCallback(async (tabId, agent) => {
215030
+ const oldTimer = handoffTimersRef.current.get(tabId);
215031
+ if (oldTimer) clearTimeout(oldTimer);
215032
+ handoffTimersRef.current.delete(tabId);
215033
+ const handoffGeneration = (handoffGenerationsRef.current.get(tabId) || 0) + 1;
215034
+ handoffGenerationsRef.current.set(tabId, handoffGeneration);
214884
215035
  setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "active" } : t));
214885
215036
  setSessionStart(/* @__PURE__ */ new Date());
214886
215037
  const spawnCwd = cwdRef.current || await window.worker.getCwd().catch(() => window.worker.getHomePath());
214887
215038
  setCwd(spawnCwd);
214888
215039
  cwdRef.current = spawnCwd;
214889
- await window.worker.spawnAgent(tabId, agent, spawnCwd);
214890
- }, []);
214891
- const handleAgentChange = reactExports.useCallback(async (agentId) => {
214892
- const agent = agents.find((a) => a.id === agentId);
214893
- if (agent) {
214894
- await window.worker.killPty(activeTabId);
214895
- setTabs((prev) => prev.map((t) => t.id === activeTabId ? { ...t, agent, agentStatus: "idle" } : t));
215040
+ try {
215041
+ await window.worker.spawnAgent(tabId, agent, spawnCwd);
215042
+ } catch (err) {
215043
+ console.error("[session] Failed to start agent", err);
215044
+ pendingHandoffsRef.current.delete(tabId);
215045
+ setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "exited" } : t));
215046
+ return;
215047
+ }
215048
+ const handoff = pendingHandoffsRef.current.get(tabId);
215049
+ pendingHandoffsRef.current.delete(tabId);
215050
+ if (handoff) {
215051
+ const timer = setTimeout(() => {
215052
+ if (handoffGenerationsRef.current.get(tabId) !== handoffGeneration) return;
215053
+ window.worker.writePty(tabId, handoff);
215054
+ focusTerminal(tabId);
215055
+ setTimeout(() => {
215056
+ if (handoffGenerationsRef.current.get(tabId) === handoffGeneration) {
215057
+ window.worker.writePty(tabId, "\r");
215058
+ }
215059
+ }, 120);
215060
+ }, 1200);
215061
+ handoffTimersRef.current.set(tabId, timer);
214896
215062
  }
214897
- }, [agents, activeTabId]);
215063
+ }, []);
214898
215064
  const handleExit = reactExports.useCallback((tabId) => {
214899
215065
  setTimeout(() => {
214900
215066
  setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "exited" } : t));
@@ -214903,36 +215069,84 @@ function App() {
214903
215069
  const handleChangeCwd = reactExports.useCallback(async () => {
214904
215070
  const newCwd = await window.worker.browseCwd();
214905
215071
  const activeTab = tabs.find((t) => t.id === activeTabId);
214906
- if (!newCwd || !activeTab?.agent) return;
215072
+ if (!newCwd) return;
214907
215073
  cwdRef.current = newCwd;
214908
215074
  setCwd(newCwd);
214909
- await window.worker.killPty(activeTabId);
214910
- setTabs((prev) => prev.map((t) => t.id === activeTabId ? { ...t, agentStatus: "active" } : t));
214911
- setSessionStart(/* @__PURE__ */ new Date());
214912
- await window.worker.spawnAgent(activeTabId, activeTab.agent, newCwd);
215075
+ if (!activeTab?.agent) return;
215076
+ pendingHandoffsRef.current.delete(activeTabId);
215077
+ setTabs((prev) => prev.map((t) => t.id === activeTabId ? { ...t, agentStatus: "idle", sessionVersion: t.sessionVersion + 1 } : t));
214913
215078
  }, [tabs, activeTabId]);
214914
215079
  const addTab = reactExports.useCallback(() => {
214915
215080
  const id = nextTabId();
214916
215081
  const num = _tabCounter;
214917
- setTabs((prev) => [...prev, { id, label: `Terminal ${num}`, agent: null, agentStatus: "idle" }]);
215082
+ setTabs((prev) => [...prev, { id, label: `Terminal ${num}`, agent: null, agentStatus: "idle", sessionVersion: 0 }]);
214918
215083
  setActiveTabId(id);
214919
215084
  setPickerTargetTabId(id);
214920
215085
  setShowAgentPicker(true);
214921
215086
  }, []);
214922
- const handlePickerSelect = reactExports.useCallback((agent) => {
215087
+ const handlePickerSelect = reactExports.useCallback(async (agent, selectedCwd, handoff) => {
214923
215088
  const tabId = pickerTargetTabId;
214924
215089
  if (tabId) {
214925
- setTabs((prev) => prev.map(
214926
- (t) => t.id === tabId ? { ...t, agent, label: `${agent.name} ${tabs.filter((x) => x.agent?.id === agent.id).length + 1}` } : t
214927
- ));
215090
+ if (handoff === "context") {
215091
+ const transcript = captureTerminalSession(tabId);
215092
+ if (!transcript) {
215093
+ pendingHandoffsRef.current.delete(tabId);
215094
+ } else if (transcript.length > INLINE_HANDOFF_CHARS && !agent.id.startsWith("remote-")) {
215095
+ const result = await window.worker.createSessionHandoffFile(transcript);
215096
+ if (result.ok && result.path) {
215097
+ pendingHandoffsRef.current.set(tabId, buildFileSessionHandoff(result.path, !!result.trimmed));
215098
+ } else {
215099
+ console.warn("[session] Could not create handoff file; using a shortened inline handoff", result.error);
215100
+ pendingHandoffsRef.current.set(tabId, buildSessionHandoff(transcript.slice(-REMOTE_HANDOFF_CHARS)));
215101
+ }
215102
+ } else {
215103
+ const inline2 = transcript.length > REMOTE_HANDOFF_CHARS ? `[Earlier terminal output omitted.]
215104
+ ${transcript.slice(-REMOTE_HANDOFF_CHARS)}` : transcript;
215105
+ pendingHandoffsRef.current.set(tabId, buildSessionHandoff(inline2));
215106
+ }
215107
+ } else {
215108
+ pendingHandoffsRef.current.delete(tabId);
215109
+ }
215110
+ cwdRef.current = selectedCwd;
215111
+ setCwd(selectedCwd);
215112
+ setTabs((prev) => {
215113
+ const matchingAgents = prev.filter((x) => x.agent?.id === agent.id).length;
215114
+ return prev.map((t) => t.id === tabId ? {
215115
+ ...t,
215116
+ agent,
215117
+ agentStatus: "idle",
215118
+ label: `${agent.name} ${matchingAgents + (t.agent?.id === agent.id ? 0 : 1)}`,
215119
+ sessionVersion: t.sessionVersion + 1
215120
+ } : t);
215121
+ });
214928
215122
  }
214929
215123
  setShowAgentPicker(false);
214930
215124
  setPickerTargetTabId(null);
214931
215125
  if (agent.id.startsWith("remote-")) {
214932
215126
  setShowProjectPicker(true);
214933
215127
  }
214934
- }, [pickerTargetTabId, tabs]);
215128
+ }, [pickerTargetTabId]);
215129
+ const openSessionPicker = reactExports.useCallback(() => {
215130
+ setPickerTargetTabId(activeTabId);
215131
+ setShowAgentPicker(true);
215132
+ }, [activeTabId]);
215133
+ const handleProjectPickerDone = reactExports.useCallback(async (selection) => {
215134
+ setShowProjectPicker(false);
215135
+ if (!selection?.notifyAgent) return;
215136
+ const activeAgent = tabs.find((t) => t.id === activeTabId)?.agent;
215137
+ if (!activeAgent) return;
215138
+ const terminalCwd = await window.worker.getActiveTabCwd().catch(() => cwd2);
215139
+ const notice = buildProjectContextNotice(selection, terminalCwd, activeAgent.name);
215140
+ window.worker.writePty(activeTabId, notice);
215141
+ focusTerminal(activeTabId);
215142
+ setTimeout(() => window.worker.writePty(activeTabId, "\r"), 120);
215143
+ }, [activeTabId, cwd2, tabs]);
214935
215144
  const closeTab = reactExports.useCallback(async (tabId) => {
215145
+ pendingHandoffsRef.current.delete(tabId);
215146
+ handoffGenerationsRef.current.set(tabId, (handoffGenerationsRef.current.get(tabId) || 0) + 1);
215147
+ const handoffTimer = handoffTimersRef.current.get(tabId);
215148
+ if (handoffTimer) clearTimeout(handoffTimer);
215149
+ handoffTimersRef.current.delete(tabId);
214936
215150
  await window.worker.killPty(tabId);
214937
215151
  destroyTerminal(tabId);
214938
215152
  setTabs((prev) => {
@@ -214946,7 +215160,7 @@ function App() {
214946
215160
  setActiveTabId(id);
214947
215161
  setPickerTargetTabId(id);
214948
215162
  setShowAgentPicker(true);
214949
- return [{ id, label: `Terminal ${num}`, agent: null, agentStatus: "idle" }];
215163
+ return [{ id, label: `Terminal ${num}`, agent: null, agentStatus: "idle", sessionVersion: 0 }];
214950
215164
  }
214951
215165
  return next;
214952
215166
  });
@@ -215080,6 +215294,7 @@ function App() {
215080
215294
  {
215081
215295
  tabId: tab.id,
215082
215296
  agent: tab.agent,
215297
+ sessionVersion: tab.sessionVersion,
215083
215298
  onSpawn: handleSpawn,
215084
215299
  onExit: handleExit,
215085
215300
  isActive: tab.id === activeTabId
@@ -215210,19 +215425,28 @@ function App() {
215210
215425
  ),
215211
215426
  /* @__PURE__ */ jsxRuntimeExports.jsx(SpeakControls, { remote: activeAgentIsRemote }),
215212
215427
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
215213
- agents.map((a) => {
215214
- const activeTab = tabs.find((t) => t.id === activeTabId);
215215
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
215216
- "button",
215217
- {
215218
- className: `titlebar-agent-btn ${activeTab?.agent?.id === a.id ? "active" : ""}`,
215219
- onClick: () => handleAgentChange(a.id),
215220
- title: a.description,
215221
- children: a.name
215222
- },
215223
- a.id
215224
- );
215225
- }),
215428
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
215429
+ "button",
215430
+ {
215431
+ className: "titlebar-agent-btn active",
215432
+ onClick: openSessionPicker,
215433
+ title: "Change coding agent or project directory for the active terminal",
215434
+ children: [
215435
+ "Agent: ",
215436
+ tabs.find((t) => t.id === activeTabId)?.agent?.name || "Choose",
215437
+ " ▾"
215438
+ ]
215439
+ }
215440
+ ),
215441
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
215442
+ "button",
215443
+ {
215444
+ className: "titlebar-btn",
215445
+ onClick: () => setShowProjectPicker(true),
215446
+ title: "Change the ctlsurf project linked to the active session",
215447
+ children: "Project…"
215448
+ }
215449
+ ),
215226
215450
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
215227
215451
  ALL_PANE_IDS.map((id) => /* @__PURE__ */ jsxRuntimeExports.jsx(
215228
215452
  "button",
@@ -215253,16 +215477,15 @@ function App() {
215253
215477
  agents,
215254
215478
  cwd: cwd2 || "",
215255
215479
  onSelect: handlePickerSelect,
215256
- onChangeCwd: async () => {
215257
- const newCwd = await window.worker.browseCwd();
215258
- if (newCwd) {
215259
- setCwd(newCwd);
215260
- cwdRef.current = newCwd;
215261
- }
215262
- }
215480
+ onChangeCwd: () => window.worker.browseCwd(),
215481
+ onCancel: tabs.find((t) => t.id === pickerTargetTabId)?.agent ? () => {
215482
+ setShowAgentPicker(false);
215483
+ setPickerTargetTabId(null);
215484
+ } : void 0,
215485
+ restarting: !!tabs.find((t) => t.id === pickerTargetTabId)?.agent
215263
215486
  }
215264
215487
  ),
215265
- showProjectPicker && /* @__PURE__ */ jsxRuntimeExports.jsx(ProjectPicker, { onDone: () => setShowProjectPicker(false) }),
215488
+ showProjectPicker && /* @__PURE__ */ jsxRuntimeExports.jsx(ProjectPicker, { currentProject: projectName, onDone: handleProjectPickerDone }),
215266
215489
  showFloatingMic && /* @__PURE__ */ jsxRuntimeExports.jsx(
215267
215490
  FloatingMic,
215268
215491
  {