@phenx-inc/ctlsurf 0.9.0 → 0.10.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 (30) hide show
  1. package/out/headless/index.mjs +196 -2
  2. package/out/headless/index.mjs.map +4 -4
  3. package/out/main/index.js +300 -23
  4. package/out/preload/index.js +7 -0
  5. package/out/renderer/assets/{cssMode-BdYp0fp9.js → cssMode-BbCVOTaJ.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-Cya-9zr-.js → freemarker2-DryLywQv.js} +1 -1
  7. package/out/renderer/assets/{handlebars-EJs9W5Ub.js → handlebars-CdUMtSdr.js} +1 -1
  8. package/out/renderer/assets/{html-Ex4qy2lh.js → html-Cwkf2GMH.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-CCm0aJ6C.js → htmlMode-C7PzhXxH.js} +3 -3
  10. package/out/renderer/assets/{index-nvsNYP5c.css → index-BIceGcz3.css} +18 -0
  11. package/out/renderer/assets/{index-Cj7K9-Ba.js → index-Dg1vMspf.js} +31 -24
  12. package/out/renderer/assets/{javascript-B2niCouI.js → javascript-Cy7ROXJl.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-BgCUHaFo.js → jsonMode-BtZtt3QW.js} +3 -3
  14. package/out/renderer/assets/{liquid-DOQmSo4G.js → liquid-xReAQeYs.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-8SIcRAMh.js → lspLanguageFeatures-Dmx0L0Bw.js} +1 -1
  16. package/out/renderer/assets/{mdx-DuKdGNKE.js → mdx-YMNFO1Zz.js} +1 -1
  17. package/out/renderer/assets/{python-BUa4XFG-.js → python-BExaDVxv.js} +1 -1
  18. package/out/renderer/assets/{razor-BUw8jho0.js → razor-HL8ANmQ4.js} +1 -1
  19. package/out/renderer/assets/{tsMode-8FLRnobw.js → tsMode-Dqd8vdLE.js} +1 -1
  20. package/out/renderer/assets/{typescript-rdwAjCi4.js → typescript-Dysz1PTX.js} +1 -1
  21. package/out/renderer/assets/{xml-HOzT59Qg.js → xml-BHp5Z9nc.js} +1 -1
  22. package/out/renderer/assets/{yaml-B81JfLsW.js → yaml-CPDHxVup.js} +1 -1
  23. package/out/renderer/index.html +2 -2
  24. package/package.json +1 -1
  25. package/src/main/agentState.ts +329 -0
  26. package/src/main/index.ts +105 -19
  27. package/src/main/orchestrator.ts +21 -1
  28. package/src/preload/index.ts +8 -0
  29. package/src/renderer/App.tsx +23 -2
  30. package/src/renderer/styles.css +18 -0
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-BdYp0fp9.js","./lspLanguageFeatures-8SIcRAMh.js","./htmlMode-CCm0aJ6C.js","./jsonMode-BgCUHaFo.js","./javascript-B2niCouI.js","./typescript-rdwAjCi4.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-BbCVOTaJ.js","./lspLanguageFeatures-Dmx0L0Bw.js","./htmlMode-C7PzhXxH.js","./jsonMode-BtZtt3QW.js","./javascript-Cy7ROXJl.js","./typescript-Dysz1PTX.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
  }
@@ -207841,7 +207841,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
207841
207841
  modeConfigurationDefault$2
207842
207842
  );
207843
207843
  function getMode$3() {
207844
- return __vitePreload(() => import("./cssMode-BdYp0fp9.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
207844
+ return __vitePreload(() => import("./cssMode-BbCVOTaJ.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
207845
207845
  }
207846
207846
  languages.onLanguage("less", () => {
207847
207847
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -207946,7 +207946,7 @@ const razorLanguageService = registerHTMLLanguageService(
207946
207946
  );
207947
207947
  const razorDefaults = razorLanguageService.defaults;
207948
207948
  function getMode$2() {
207949
- return __vitePreload(() => import("./htmlMode-CCm0aJ6C.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
207949
+ return __vitePreload(() => import("./htmlMode-C7PzhXxH.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
207950
207950
  }
207951
207951
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
207952
207952
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -208030,7 +208030,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
208030
208030
  );
208031
208031
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
208032
208032
  function getMode$1() {
208033
- return __vitePreload(() => import("./jsonMode-BgCUHaFo.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208033
+ return __vitePreload(() => import("./jsonMode-BtZtt3QW.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208034
208034
  }
208035
208035
  languages.register({
208036
208036
  id: "json",
@@ -208276,7 +208276,7 @@ const getJavaScriptWorker = () => {
208276
208276
  return getMode().then((mode) => mode.getJavaScriptWorker());
208277
208277
  };
208278
208278
  function getMode() {
208279
- return __vitePreload(() => import("./tsMode-8FLRnobw.js"), true ? [] : void 0, import.meta.url);
208279
+ return __vitePreload(() => import("./tsMode-Dqd8vdLE.js"), true ? [] : void 0, import.meta.url);
208280
208280
  }
208281
208281
  languages.onLanguage("typescript", () => {
208282
208282
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -208471,49 +208471,49 @@ registerLanguage({
208471
208471
  extensions: [".ftl", ".ftlh", ".ftlx"],
208472
208472
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
208473
208473
  loader: () => {
208474
- return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208474
+ return __vitePreload(() => import("./freemarker2-DryLywQv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208475
208475
  }
208476
208476
  });
208477
208477
  registerLanguage({
208478
208478
  id: "freemarker2.tag-angle.interpolation-dollar",
208479
208479
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
208480
208480
  loader: () => {
208481
- return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
208481
+ return __vitePreload(() => import("./freemarker2-DryLywQv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
208482
208482
  }
208483
208483
  });
208484
208484
  registerLanguage({
208485
208485
  id: "freemarker2.tag-bracket.interpolation-dollar",
208486
208486
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
208487
208487
  loader: () => {
208488
- return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
208488
+ return __vitePreload(() => import("./freemarker2-DryLywQv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
208489
208489
  }
208490
208490
  });
208491
208491
  registerLanguage({
208492
208492
  id: "freemarker2.tag-angle.interpolation-bracket",
208493
208493
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
208494
208494
  loader: () => {
208495
- return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
208495
+ return __vitePreload(() => import("./freemarker2-DryLywQv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
208496
208496
  }
208497
208497
  });
208498
208498
  registerLanguage({
208499
208499
  id: "freemarker2.tag-bracket.interpolation-bracket",
208500
208500
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
208501
208501
  loader: () => {
208502
- return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
208502
+ return __vitePreload(() => import("./freemarker2-DryLywQv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
208503
208503
  }
208504
208504
  });
208505
208505
  registerLanguage({
208506
208506
  id: "freemarker2.tag-auto.interpolation-dollar",
208507
208507
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
208508
208508
  loader: () => {
208509
- return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208509
+ return __vitePreload(() => import("./freemarker2-DryLywQv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208510
208510
  }
208511
208511
  });
208512
208512
  registerLanguage({
208513
208513
  id: "freemarker2.tag-auto.interpolation-bracket",
208514
208514
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
208515
208515
  loader: () => {
208516
- return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
208516
+ return __vitePreload(() => import("./freemarker2-DryLywQv.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
208517
208517
  }
208518
208518
  });
208519
208519
  registerLanguage({
@@ -208534,7 +208534,7 @@ registerLanguage({
208534
208534
  extensions: [".handlebars", ".hbs"],
208535
208535
  aliases: ["Handlebars", "handlebars", "hbs"],
208536
208536
  mimetypes: ["text/x-handlebars-template"],
208537
- loader: () => __vitePreload(() => import("./handlebars-EJs9W5Ub.js"), true ? [] : void 0, import.meta.url)
208537
+ loader: () => __vitePreload(() => import("./handlebars-CdUMtSdr.js"), true ? [] : void 0, import.meta.url)
208538
208538
  });
208539
208539
  registerLanguage({
208540
208540
  id: "hcl",
@@ -208547,7 +208547,7 @@ registerLanguage({
208547
208547
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
208548
208548
  aliases: ["HTML", "htm", "html", "xhtml"],
208549
208549
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
208550
- loader: () => __vitePreload(() => import("./html-Ex4qy2lh.js"), true ? [] : void 0, import.meta.url)
208550
+ loader: () => __vitePreload(() => import("./html-Cwkf2GMH.js"), true ? [] : void 0, import.meta.url)
208551
208551
  });
208552
208552
  registerLanguage({
208553
208553
  id: "ini",
@@ -208570,7 +208570,7 @@ registerLanguage({
208570
208570
  filenames: ["jakefile"],
208571
208571
  aliases: ["JavaScript", "javascript", "js"],
208572
208572
  mimetypes: ["text/javascript"],
208573
- loader: () => __vitePreload(() => import("./javascript-B2niCouI.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
208573
+ loader: () => __vitePreload(() => import("./javascript-Cy7ROXJl.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
208574
208574
  });
208575
208575
  registerLanguage({
208576
208576
  id: "julia",
@@ -208609,7 +208609,7 @@ registerLanguage({
208609
208609
  extensions: [".liquid", ".html.liquid"],
208610
208610
  aliases: ["Liquid", "liquid"],
208611
208611
  mimetypes: ["application/liquid"],
208612
- loader: () => __vitePreload(() => import("./liquid-DOQmSo4G.js"), true ? [] : void 0, import.meta.url)
208612
+ loader: () => __vitePreload(() => import("./liquid-xReAQeYs.js"), true ? [] : void 0, import.meta.url)
208613
208613
  });
208614
208614
  registerLanguage({
208615
208615
  id: "m3",
@@ -208627,7 +208627,7 @@ registerLanguage({
208627
208627
  id: "mdx",
208628
208628
  extensions: [".mdx"],
208629
208629
  aliases: ["MDX", "mdx"],
208630
- loader: () => __vitePreload(() => import("./mdx-DuKdGNKE.js"), true ? [] : void 0, import.meta.url)
208630
+ loader: () => __vitePreload(() => import("./mdx-YMNFO1Zz.js"), true ? [] : void 0, import.meta.url)
208631
208631
  });
208632
208632
  registerLanguage({
208633
208633
  id: "mips",
@@ -208726,7 +208726,7 @@ registerLanguage({
208726
208726
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
208727
208727
  aliases: ["Python", "py"],
208728
208728
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
208729
- loader: () => __vitePreload(() => import("./python-BUa4XFG-.js"), true ? [] : void 0, import.meta.url)
208729
+ loader: () => __vitePreload(() => import("./python-BExaDVxv.js"), true ? [] : void 0, import.meta.url)
208730
208730
  });
208731
208731
  registerLanguage({
208732
208732
  id: "qsharp",
@@ -208745,7 +208745,7 @@ registerLanguage({
208745
208745
  extensions: [".cshtml"],
208746
208746
  aliases: ["Razor", "razor"],
208747
208747
  mimetypes: ["text/x-cshtml"],
208748
- loader: () => __vitePreload(() => import("./razor-BUw8jho0.js"), true ? [] : void 0, import.meta.url)
208748
+ loader: () => __vitePreload(() => import("./razor-HL8ANmQ4.js"), true ? [] : void 0, import.meta.url)
208749
208749
  });
208750
208750
  registerLanguage({
208751
208751
  id: "redis",
@@ -208878,7 +208878,7 @@ registerLanguage({
208878
208878
  aliases: ["TypeScript", "ts", "typescript"],
208879
208879
  mimetypes: ["text/typescript"],
208880
208880
  loader: () => {
208881
- return __vitePreload(() => import("./typescript-rdwAjCi4.js"), true ? [] : void 0, import.meta.url);
208881
+ return __vitePreload(() => import("./typescript-Dysz1PTX.js"), true ? [] : void 0, import.meta.url);
208882
208882
  }
208883
208883
  });
208884
208884
  registerLanguage({
@@ -208923,14 +208923,14 @@ registerLanguage({
208923
208923
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
208924
208924
  aliases: ["XML", "xml"],
208925
208925
  mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
208926
- loader: () => __vitePreload(() => import("./xml-HOzT59Qg.js"), true ? [] : void 0, import.meta.url)
208926
+ loader: () => __vitePreload(() => import("./xml-BHp5Z9nc.js"), true ? [] : void 0, import.meta.url)
208927
208927
  });
208928
208928
  registerLanguage({
208929
208929
  id: "yaml",
208930
208930
  extensions: [".yaml", ".yml"],
208931
208931
  aliases: ["YAML", "yaml", "YML", "yml"],
208932
208932
  mimetypes: ["application/x-yaml", "text/x-yaml"],
208933
- loader: () => __vitePreload(() => import("./yaml-B81JfLsW.js"), true ? [] : void 0, import.meta.url)
208933
+ loader: () => __vitePreload(() => import("./yaml-CPDHxVup.js"), true ? [] : void 0, import.meta.url)
208934
208934
  });
208935
208935
  var __defProp = Object.defineProperty;
208936
208936
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -213332,6 +213332,7 @@ function App() {
213332
213332
  });
213333
213333
  const [activeTabId, setActiveTabId] = reactExports.useState(tabs[0].id);
213334
213334
  const [trackingActive, setTrackingActive] = reactExports.useState(false);
213335
+ const [agentStates, setAgentStates] = reactExports.useState({});
213335
213336
  const [showTicketPanel, setShowTicketPanel] = reactExports.useState(false);
213336
213337
  const [showFloatingMic, setShowFloatingMic] = reactExports.useState(() => {
213337
213338
  try {
@@ -213394,6 +213395,13 @@ function App() {
213394
213395
  });
213395
213396
  return unsub;
213396
213397
  }, []);
213398
+ reactExports.useEffect(() => {
213399
+ const unsub = window.worker.onAgentState((tabId, state) => {
213400
+ if (!tabId) return;
213401
+ setAgentStates((prev) => prev[tabId] === state ? prev : { ...prev, [tabId]: state });
213402
+ });
213403
+ return unsub;
213404
+ }, []);
213397
213405
  reactExports.useEffect(() => {
213398
213406
  let cancelled = false;
213399
213407
  const refresh = async () => {
@@ -213585,8 +213593,7 @@ function App() {
213585
213593
  children: [
213586
213594
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "terminal-tab-label", children: [
213587
213595
  tab.agent ? tab.agent.name : tab.label,
213588
- tab.agentStatus === "active" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "terminal-tab-dot active" }),
213589
- tab.agentStatus === "exited" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "terminal-tab-dot exited" })
213596
+ tab.agentStatus === "exited" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "terminal-tab-dot exited" }) : agentStates[tab.id] === "awaiting_decision" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "terminal-tab-dot decision", title: "Awaiting your approval" }) : agentStates[tab.id] === "awaiting_input" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "terminal-tab-dot awaiting", title: "Awaiting your input" }) : tab.agentStatus === "active" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "terminal-tab-dot active" }) : null
213590
213597
  ] }),
213591
213598
  tabs.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx(
213592
213599
  "button",
@@ -1,5 +1,5 @@
1
- import { conf as conf$1, language as language$1 } from "./typescript-rdwAjCi4.js";
2
- import "./index-Cj7K9-Ba.js";
1
+ import { conf as conf$1, language as language$1 } from "./typescript-Dysz1PTX.js";
2
+ import "./index-Dg1vMspf.js";
3
3
  const conf = conf$1;
4
4
  const language = {
5
5
  // Set defaultToken to invalid to see what you do not tokenize yet
@@ -1,6 +1,6 @@
1
- import { c as createWebWorker, l as languages, e as editor } from "./index-Cj7K9-Ba.js";
2
- import { f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter, H as HoverAdapter, b as DocumentSymbolAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, S as SelectionRangeAdapter, e as DiagnosticsAdapter } from "./lspLanguageFeatures-8SIcRAMh.js";
3
- import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-8SIcRAMh.js";
1
+ import { c as createWebWorker, l as languages, e as editor } from "./index-Dg1vMspf.js";
2
+ import { f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter, H as HoverAdapter, b as DocumentSymbolAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, S as SelectionRangeAdapter, e as DiagnosticsAdapter } from "./lspLanguageFeatures-Dmx0L0Bw.js";
3
+ import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-Dmx0L0Bw.js";
4
4
  const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
5
5
  class WorkerManager {
6
6
  constructor(defaults) {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-Cj7K9-Ba.js";
1
+ import { l as languages } from "./index-Dg1vMspf.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,4 +1,4 @@
1
- import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-Cj7K9-Ba.js";
1
+ import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-Dg1vMspf.js";
2
2
  var DocumentUri;
3
3
  (function(DocumentUri2) {
4
4
  function is(value) {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-Cj7K9-Ba.js";
1
+ import { l as languages } from "./index-Dg1vMspf.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  blockComment: ["{/*", "*/}"]
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-Cj7K9-Ba.js";
1
+ import { l as languages } from "./index-Dg1vMspf.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  lineComment: "#",
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-Cj7K9-Ba.js";
1
+ import { l as languages } from "./index-Dg1vMspf.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,4 +1,4 @@
1
- import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-Cj7K9-Ba.js";
1
+ import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-Dg1vMspf.js";
2
2
  class WorkerManager {
3
3
  constructor(_modeId, _defaults) {
4
4
  this._modeId = _modeId;
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-Cj7K9-Ba.js";
1
+ import { l as languages } from "./index-Dg1vMspf.js";
2
2
  const conf = {
3
3
  wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
4
4
  comments: {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-Cj7K9-Ba.js";
1
+ import { l as languages } from "./index-Dg1vMspf.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  blockComment: ["<!--", "-->"]
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-Cj7K9-Ba.js";
1
+ import { l as languages } from "./index-Dg1vMspf.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  lineComment: "#"
@@ -49,8 +49,8 @@
49
49
  color: #565f89;
50
50
  }
51
51
  </style>
52
- <script type="module" crossorigin src="./assets/index-Cj7K9-Ba.js"></script>
53
- <link rel="stylesheet" crossorigin href="./assets/index-nvsNYP5c.css">
52
+ <script type="module" crossorigin src="./assets/index-Dg1vMspf.js"></script>
53
+ <link rel="stylesheet" crossorigin href="./assets/index-BIceGcz3.css">
54
54
  </head>
55
55
  <body>
56
56
  <div id="boot-splash">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phenx-inc/ctlsurf",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
5
5
  "main": "out/main/index.js",
6
6
  "bin": {
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Agent State Monitor
3
+ *
4
+ * Detects, in real time, whether a coding agent is BUSY (actively working) or
5
+ * AWAITING the user — either waiting for the next prompt (`awaiting_input`) or
6
+ * blocked on an approval/permission decision (`awaiting_decision`).
7
+ *
8
+ * This is the cross-platform "PTY backbone": it watches the raw terminal byte
9
+ * stream (which every agent produces regardless of OS) and infers state from
10
+ * the TUI's own affordances — the busy spinner (`esc to interrupt`) and
11
+ * approval prompts. It is deliberately independent of chat logging and of the
12
+ * ConversationBridge, whose transcript cleaner actively *discards* the spinner
13
+ * lines this monitor needs.
14
+ *
15
+ * Agents with native hooks (Claude Code, Codex) can deliver an authoritative
16
+ * state via `applyHookEvent()`, which overrides the heuristic for a short
17
+ * window. See agentHooks.ts.
18
+ */
19
+
20
+ export type AgentState = 'idle' | 'busy' | 'awaiting_input' | 'awaiting_decision'
21
+
22
+ export type AgentStateSink = (info: {
23
+ tabId: string
24
+ state: AgentState
25
+ /** True when any tracked tab is awaiting the user (drives the dock/taskbar). */
26
+ anyAwaiting: boolean
27
+ }) => void
28
+
29
+ // The spinner ticks roughly once per second; allow a couple of missed ticks
30
+ // before we stop considering the agent busy.
31
+ const BUSY_GRACE_MS = 2_500
32
+ // Output must be quiet this long after the spinner stops before we declare the
33
+ // agent is awaiting the user (avoids flagging mid-stream pauses).
34
+ const SETTLE_MS = 1_200
35
+ // How long a seen approval prompt keeps a tab in `awaiting_decision`.
36
+ const DECISION_TTL_MS = 4_000
37
+ // Evaluation cadence.
38
+ const TICK_MS = 400
39
+ // A hook-delivered state is authoritative for this long, suppressing the
40
+ // heuristic for that tab.
41
+ const HOOK_AUTHORITY_MS = 6_000
42
+ // Only the trailing slice of each tab's output is kept, enough to catch a
43
+ // marker split across two PTY chunks.
44
+ const TAIL_KEEP_CHARS = 240
45
+
46
+ // Claude Code's working spinner: a cycling star glyph followed by a gerund that
47
+ // ends in an ellipsis, e.g. "✶ Tempering…" or "✳ Cogitating...". The completion
48
+ // line ("✻ Worked for 2s") has the glyph but no ellipsis, so it doesn't match —
49
+ // letting the agent settle to `awaiting_input`. Newer Claude builds (Opus 4.8)
50
+ // don't always print "esc to interrupt" for fast turns, so this is the primary
51
+ // Claude signal; the older hints are kept for other builds and Codex.
52
+ const CLAUDE_SPINNER = /[✶✻✳✺✢✷✸✹❋][^\n]{0,60}(?:…|\.\.\.)/
53
+
54
+ /** Busy markers — the spinner/working hints a TUI shows while actively working. */
55
+ function hasBusyMarker(text: string): boolean {
56
+ return (
57
+ /esc to interrupt/i.test(text) ||
58
+ /\bWorking \(\d+s/i.test(text) ||
59
+ CLAUDE_SPINNER.test(text)
60
+ )
61
+ }
62
+
63
+ /**
64
+ * Approval / permission prompts. Conservative on purpose: a false positive only
65
+ * mislabels the sub-state (the dock fires on either awaiting state anyway), but
66
+ * we still avoid obviously-noisy matches.
67
+ */
68
+ const APPROVAL_PATTERNS: RegExp[] = [
69
+ /\bDo you want to (proceed|continue|create|make this edit|run)\b/i, // Claude Code
70
+ /❯\s*\d+\.\s*Yes\b/i, // Claude numbered-choice prompt
71
+ /\bAllow command\b/i, // Codex
72
+ /\bRun this command\?/i, // Codex
73
+ /\bApprove\b[^\n]*\?/i,
74
+ /\(y\/n\)/i,
75
+ /\[y\/N\]/i,
76
+ ]
77
+
78
+ function hasApprovalMarker(text: string): boolean {
79
+ return APPROVAL_PATTERNS.some((re) => re.test(text))
80
+ }
81
+
82
+ /** Strip CSI/OSC escape sequences so markers aren't hidden behind color codes. */
83
+ function stripAnsi(str: string): string {
84
+ return str
85
+ // eslint-disable-next-line no-control-regex
86
+ .replace(/\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, '')
87
+ // eslint-disable-next-line no-control-regex
88
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
89
+ // eslint-disable-next-line no-control-regex
90
+ .replace(/\x1b/g, '')
91
+ }
92
+
93
+ interface TabRecord {
94
+ /** Agent id (e.g. 'claude', 'codex'); reserved for future per-agent tuning. */
95
+ agentId: string
96
+ cwd: string
97
+ state: AgentState
98
+ /** True once a busy spinner has been seen since the last user input. */
99
+ everBusy: boolean
100
+ lastBusyAt: number
101
+ lastOutputAt: number
102
+ lastDecisionAt: number
103
+ /** Authoritative state from a hook, and when it was delivered. */
104
+ hookState: AgentState | null
105
+ hookAt: number
106
+ /** Trailing output slice, to catch markers split across PTY chunks. */
107
+ tail: string
108
+ }
109
+
110
+ export class AgentStateMonitor {
111
+ private readonly sink: AgentStateSink
112
+ private readonly now: () => number
113
+ private tabs = new Map<string, TabRecord>()
114
+ private timer: ReturnType<typeof setInterval> | null = null
115
+ private lastAnyAwaiting = false
116
+
117
+ constructor(sink: AgentStateSink, now: () => number = Date.now) {
118
+ this.sink = sink
119
+ this.now = now
120
+ }
121
+
122
+ start(): void {
123
+ if (this.timer) return
124
+ this.timer = setInterval(() => this.tick(), TICK_MS)
125
+ }
126
+
127
+ stop(): void {
128
+ if (this.timer) {
129
+ clearInterval(this.timer)
130
+ this.timer = null
131
+ }
132
+ this.tabs.clear()
133
+ this.lastAnyAwaiting = false
134
+ }
135
+
136
+ /** Register a tab so its raw PTY output can be classified. */
137
+ trackTab(tabId: string, agentId: string, cwd: string): void {
138
+ const now = this.now()
139
+ this.tabs.set(tabId, {
140
+ agentId,
141
+ cwd,
142
+ state: 'idle',
143
+ everBusy: false,
144
+ lastBusyAt: 0,
145
+ lastOutputAt: now,
146
+ lastDecisionAt: 0,
147
+ hookState: null,
148
+ hookAt: 0,
149
+ tail: '',
150
+ })
151
+ }
152
+
153
+ removeTab(tabId: string): void {
154
+ if (this.tabs.delete(tabId)) this.emitAggregate()
155
+ }
156
+
157
+ /** Feed a raw PTY output chunk for a tab. */
158
+ feed(tabId: string, data: string): void {
159
+ const rec = this.tabs.get(tabId)
160
+ if (!rec) return
161
+ const now = this.now()
162
+ rec.lastOutputAt = now
163
+
164
+ const text = rec.tail + stripAnsi(data)
165
+ rec.tail = text.length > TAIL_KEEP_CHARS ? text.slice(-TAIL_KEEP_CHARS) : text
166
+
167
+ if (hasBusyMarker(text)) {
168
+ rec.lastBusyAt = now
169
+ rec.everBusy = true
170
+ }
171
+ if (hasApprovalMarker(text)) {
172
+ rec.lastDecisionAt = now
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Note input written to a tab's PTY. Only a *submit* (data containing a
178
+ * newline) counts as the user engaging — clearing any awaiting state and
179
+ * resetting the "has worked" flag so a leftover idle box doesn't re-flag.
180
+ *
181
+ * Individual keystrokes and, crucially, the terminal's own auto-responses
182
+ * (cursor-position / device-attribute replies that TUIs constantly trigger,
183
+ * which xterm.js writes straight back to the PTY) are ignored — otherwise
184
+ * the state would churn busy↔idle on every control-sequence handshake.
185
+ */
186
+ noteInput(tabId: string, data: string): void {
187
+ if (!/[\r\n]/.test(data)) return
188
+ const rec = this.tabs.get(tabId)
189
+ if (!rec) return
190
+ const now = this.now()
191
+ rec.everBusy = false
192
+ rec.lastBusyAt = now // a submit means work is coming → busy, not awaiting
193
+ rec.lastDecisionAt = 0
194
+ rec.hookState = null
195
+ this.setState(rec, tabId, 'busy')
196
+ }
197
+
198
+ /**
199
+ * Apply an authoritative state from an agent hook, addressed by cwd. Overrides
200
+ * the heuristic for HOOK_AUTHORITY_MS. `kind`:
201
+ * 'awaiting_input' — turn done / idle prompt (Stop, idle_prompt, agent-turn-complete)
202
+ * 'awaiting_decision' — permission/approval prompt (PermissionRequest, permission_prompt)
203
+ * 'busy' — user submitted a prompt (UserPromptSubmit)
204
+ */
205
+ applyHookEvent(cwd: string, kind: AgentState): void {
206
+ const target = stripTrailingSep(cwd)
207
+ const now = this.now()
208
+ for (const [tabId, rec] of this.tabs) {
209
+ if (stripTrailingSep(rec.cwd) !== target) continue
210
+ rec.hookState = kind
211
+ rec.hookAt = now
212
+ if (kind === 'busy') {
213
+ rec.everBusy = false
214
+ rec.lastBusyAt = now
215
+ }
216
+ this.setState(rec, tabId, kind)
217
+ }
218
+ }
219
+
220
+ private tick(): void {
221
+ const now = this.now()
222
+ for (const [tabId, rec] of this.tabs) {
223
+ // Hook state wins while fresh.
224
+ if (rec.hookState && now - rec.hookAt < HOOK_AUTHORITY_MS) {
225
+ this.setState(rec, tabId, rec.hookState)
226
+ continue
227
+ }
228
+
229
+ // `> 0` guards the "never seen" sentinel so a small/zero clock value
230
+ // doesn't read as recent activity.
231
+ const busyRecently = rec.lastBusyAt > 0 && now - rec.lastBusyAt < BUSY_GRACE_MS
232
+ const settled = now - rec.lastOutputAt >= SETTLE_MS
233
+ const decisionRecent = rec.lastDecisionAt > 0 && now - rec.lastDecisionAt < DECISION_TTL_MS
234
+
235
+ let next: AgentState | null = null
236
+ if (busyRecently) {
237
+ next = 'busy'
238
+ } else if (settled && rec.everBusy) {
239
+ // Awaiting the user. Keep the sub-state stable: once we're showing
240
+ // awaiting_decision it sticks (and awaiting_input may upgrade to it),
241
+ // but we never flap back down — the agent's screen repaints make an
242
+ // approval marker match only intermittently, which otherwise flickers
243
+ // the indicator between amber and red.
244
+ next = rec.state === 'awaiting_decision' || decisionRecent
245
+ ? 'awaiting_decision'
246
+ : 'awaiting_input'
247
+ } else if (settled && !rec.everBusy) {
248
+ next = 'idle'
249
+ }
250
+ // next === null → output still streaming after spinner stopped; hold the
251
+ // current state until it settles.
252
+ if (next) this.setState(rec, tabId, next)
253
+ }
254
+ this.emitAggregate()
255
+ }
256
+
257
+ private setState(rec: TabRecord, tabId: string, state: AgentState): void {
258
+ if (rec.state === state) return
259
+ rec.state = state
260
+ this.sink({ tabId, state, anyAwaiting: this.computeAnyAwaiting() })
261
+ this.lastAnyAwaiting = this.computeAnyAwaiting()
262
+ }
263
+
264
+ private emitAggregate(): void {
265
+ const anyAwaiting = this.computeAnyAwaiting()
266
+ if (anyAwaiting === this.lastAnyAwaiting) return
267
+ this.lastAnyAwaiting = anyAwaiting
268
+ // Re-emit with a representative tab so the dock/aggregate listener updates
269
+ // even when no single tab's state label changed this tick.
270
+ const tabId = this.firstAwaitingTabId() ?? ''
271
+ const state = tabId ? (this.tabs.get(tabId)!.state) : 'idle'
272
+ this.sink({ tabId, state, anyAwaiting })
273
+ }
274
+
275
+ private computeAnyAwaiting(): boolean {
276
+ for (const rec of this.tabs.values()) {
277
+ if (rec.state === 'awaiting_input' || rec.state === 'awaiting_decision') return true
278
+ }
279
+ return false
280
+ }
281
+
282
+ private firstAwaitingTabId(): string | null {
283
+ for (const [tabId, rec] of this.tabs) {
284
+ if (rec.state === 'awaiting_input' || rec.state === 'awaiting_decision') return tabId
285
+ }
286
+ return null
287
+ }
288
+ }
289
+
290
+ function stripTrailingSep(p: string): string {
291
+ return p.length > 1 ? p.replace(/[/\\]+$/, '') : p
292
+ }
293
+
294
+ /**
295
+ * Decides whether the dock/taskbar "awaiting input" attention (blink + bounce)
296
+ * should currently be active. Pure boolean logic, separated from the Electron
297
+ * bindings in index.ts so it can be tested without a window.
298
+ *
299
+ * Rules:
300
+ * - Attention is active only while an agent is awaiting AND the window is
301
+ * unfocused AND the current awaiting episode hasn't been acknowledged.
302
+ * - Focusing the window during an episode acknowledges it; so does already
303
+ * being focused at the moment the episode begins (you saw it happen).
304
+ * - Acknowledgement lasts for the whole episode: blurring again does NOT
305
+ * re-nag. A fresh episode (agent works, then waits again) starts unack'd.
306
+ */
307
+ export class AttentionController {
308
+ private awaiting = false
309
+ private focused = false
310
+ private acknowledged = false
311
+
312
+ /** Update awaiting state; returns whether attention should be active. */
313
+ setAwaiting(on: boolean): boolean {
314
+ if (on && !this.awaiting) this.acknowledged = this.focused // new episode
315
+ this.awaiting = on
316
+ return this.active()
317
+ }
318
+
319
+ /** Update window focus; returns whether attention should be active. */
320
+ setFocused(focused: boolean): boolean {
321
+ this.focused = focused
322
+ if (focused && this.awaiting) this.acknowledged = true
323
+ return this.active()
324
+ }
325
+
326
+ private active(): boolean {
327
+ return this.awaiting && !this.focused && !this.acknowledged
328
+ }
329
+ }