@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.
- package/out/headless/index.mjs +196 -2
- package/out/headless/index.mjs.map +4 -4
- package/out/main/index.js +300 -23
- package/out/preload/index.js +7 -0
- package/out/renderer/assets/{cssMode-BdYp0fp9.js → cssMode-BbCVOTaJ.js} +3 -3
- package/out/renderer/assets/{freemarker2-Cya-9zr-.js → freemarker2-DryLywQv.js} +1 -1
- package/out/renderer/assets/{handlebars-EJs9W5Ub.js → handlebars-CdUMtSdr.js} +1 -1
- package/out/renderer/assets/{html-Ex4qy2lh.js → html-Cwkf2GMH.js} +1 -1
- package/out/renderer/assets/{htmlMode-CCm0aJ6C.js → htmlMode-C7PzhXxH.js} +3 -3
- package/out/renderer/assets/{index-nvsNYP5c.css → index-BIceGcz3.css} +18 -0
- package/out/renderer/assets/{index-Cj7K9-Ba.js → index-Dg1vMspf.js} +31 -24
- package/out/renderer/assets/{javascript-B2niCouI.js → javascript-Cy7ROXJl.js} +2 -2
- package/out/renderer/assets/{jsonMode-BgCUHaFo.js → jsonMode-BtZtt3QW.js} +3 -3
- package/out/renderer/assets/{liquid-DOQmSo4G.js → liquid-xReAQeYs.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-8SIcRAMh.js → lspLanguageFeatures-Dmx0L0Bw.js} +1 -1
- package/out/renderer/assets/{mdx-DuKdGNKE.js → mdx-YMNFO1Zz.js} +1 -1
- package/out/renderer/assets/{python-BUa4XFG-.js → python-BExaDVxv.js} +1 -1
- package/out/renderer/assets/{razor-BUw8jho0.js → razor-HL8ANmQ4.js} +1 -1
- package/out/renderer/assets/{tsMode-8FLRnobw.js → tsMode-Dqd8vdLE.js} +1 -1
- package/out/renderer/assets/{typescript-rdwAjCi4.js → typescript-Dysz1PTX.js} +1 -1
- package/out/renderer/assets/{xml-HOzT59Qg.js → xml-BHp5Z9nc.js} +1 -1
- package/out/renderer/assets/{yaml-B81JfLsW.js → yaml-CPDHxVup.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/agentState.ts +329 -0
- package/src/main/index.ts +105 -19
- package/src/main/orchestrator.ts +21 -1
- package/src/preload/index.ts +8 -0
- package/src/renderer/App.tsx +23 -2
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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"
|
|
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-
|
|
2
|
-
import "./index-
|
|
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-
|
|
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-
|
|
3
|
-
import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-
|
|
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) {
|
package/out/renderer/assets/{lspLanguageFeatures-8SIcRAMh.js → lspLanguageFeatures-Dmx0L0Bw.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-
|
|
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 { 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-
|
|
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;
|
package/out/renderer/index.html
CHANGED
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
color: #565f89;
|
|
50
50
|
}
|
|
51
51
|
</style>
|
|
52
|
-
<script type="module" crossorigin src="./assets/index-
|
|
53
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
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.
|
|
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
|
+
}
|