cursorconnect 0.1.10 → 0.1.12
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/bridge-runtime/connector-version.json +1 -1
- package/bridge-runtime/dist/agent-title-match.d.ts +4 -0
- package/bridge-runtime/dist/agent-title-match.js +61 -1
- package/bridge-runtime/dist/cdp-bridge.js +2 -1
- package/bridge-runtime/dist/chat-sync.js +3 -0
- package/bridge-runtime/dist/command-executor.d.ts +2 -0
- package/bridge-runtime/dist/command-executor.js +85 -56
- package/bridge-runtime/dist/composer-images.js +23 -6
- package/bridge-runtime/dist/cursor-window-kind.d.ts +10 -0
- package/bridge-runtime/dist/cursor-window-kind.js +10 -0
- package/bridge-runtime/dist/dom-extractor.d.ts +1 -1
- package/bridge-runtime/dist/dom-extractor.js +0 -1
- package/bridge-runtime/dist/editor-chat-list.d.ts +6 -0
- package/bridge-runtime/dist/editor-chat-list.js +79 -0
- package/bridge-runtime/dist/editor-list-sync.d.ts +3 -0
- package/bridge-runtime/dist/editor-list-sync.js +11 -0
- package/bridge-runtime/dist/editor-tab-focus-dom.d.ts +8 -0
- package/bridge-runtime/dist/editor-tab-focus-dom.js +80 -0
- package/bridge-runtime/dist/extract-page.d.ts +1 -1
- package/bridge-runtime/dist/extract-page.js +177 -30
- package/bridge-runtime/dist/generation-stop-dom.d.ts +5 -0
- package/bridge-runtime/dist/generation-stop-dom.js +67 -0
- package/bridge-runtime/dist/index.js +2 -0
- package/bridge-runtime/dist/queue-remove-dom.d.ts +11 -0
- package/bridge-runtime/dist/queue-remove-dom.js +88 -0
- package/bridge-runtime/dist/relay-upstream.js +2 -0
- package/bridge-runtime/dist/relay.js +35 -15
- package/bridge-runtime/dist/state-manager.d.ts +1 -1
- package/bridge-runtime/dist/types.d.ts +14 -0
- package/bridge-runtime/dist/window-monitor.js +6 -0
- package/bridge-runtime/selectors.json +8 -1
- package/dist/bridge-build.js +2 -1
- package/dist/bundled-bridge-check.js +2 -3
- package/dist/diagnose.js +26 -23
- package/dist/i18n.js +50 -0
- package/dist/index.js +31 -47
- package/dist/launch.js +9 -8
- package/dist/print-pairing.js +8 -7
- package/dist/run-service.js +5 -4
- package/dist/startup-check.js +32 -23
- package/dist/version-check.js +7 -3
- package/locales/en.json +128 -0
- package/locales/ru.json +128 -0
- package/package.json +2 -1
- package/version-policy.json +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runs inside Cursor renderer — must be self-contained.
|
|
3
3
|
*/
|
|
4
|
-
export function extractionFunction(containerSelectors, tabSelectors, inputSelectors, approveSelectors, approveTextMatch, rejectSelectors, rejectTextMatch) {
|
|
4
|
+
export function extractionFunction(containerSelectors, tabSelectors, editorAuxiliaryTabSelectors, inputSelectors, approveSelectors, approveTextMatch, rejectSelectors, rejectTextMatch) {
|
|
5
5
|
function findFirst(sels) {
|
|
6
6
|
for (const sel of sels) {
|
|
7
7
|
try {
|
|
@@ -340,11 +340,33 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
340
340
|
/while dragging, use the arrow keys/i.test(text));
|
|
341
341
|
}
|
|
342
342
|
function removePathFor(el) {
|
|
343
|
-
const
|
|
344
|
-
if (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
343
|
+
const legacy = el.querySelector('button[aria-label="Remove"]');
|
|
344
|
+
if (legacy) {
|
|
345
|
+
const path = buildSelectorPath(legacy);
|
|
346
|
+
if (path)
|
|
347
|
+
return path;
|
|
348
|
+
}
|
|
349
|
+
const removeAction = el.querySelector('[data-queue-action="remove"]');
|
|
350
|
+
if (removeAction) {
|
|
351
|
+
const clickTarget = removeAction.closest('button') ||
|
|
352
|
+
removeAction.querySelector('button, .anysphere-icon-button') ||
|
|
353
|
+
removeAction;
|
|
354
|
+
const path = buildSelectorPath(clickTarget);
|
|
355
|
+
if (path)
|
|
356
|
+
return path;
|
|
357
|
+
}
|
|
358
|
+
const trashIcon = el.querySelector('.codicon-trashcan');
|
|
359
|
+
if (trashIcon) {
|
|
360
|
+
const clickTarget = trashIcon.closest('button') ||
|
|
361
|
+
trashIcon.closest('.anysphere-icon-button') ||
|
|
362
|
+
trashIcon.parentElement;
|
|
363
|
+
if (clickTarget) {
|
|
364
|
+
const path = buildSelectorPath(clickTarget);
|
|
365
|
+
if (path)
|
|
366
|
+
return path;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return undefined;
|
|
348
370
|
}
|
|
349
371
|
function extractQueuedRowText(row) {
|
|
350
372
|
const textRoot = row.querySelector('[class*="ui-tray-row"]') ||
|
|
@@ -352,7 +374,7 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
352
374
|
row;
|
|
353
375
|
const clone = textRoot.cloneNode(true);
|
|
354
376
|
clone
|
|
355
|
-
.querySelectorAll('img, button, [class*="icon"], [aria-hidden="true"], [class*="image-pill"]')
|
|
377
|
+
.querySelectorAll('img, button, .anysphere-icon-button, [class*="queue-item-actions"], [class*="icon"], [aria-hidden="true"], [class*="image-pill"]')
|
|
356
378
|
.forEach((el) => el.remove());
|
|
357
379
|
return (clone.textContent || '').trim().replace(/\s+/g, ' ');
|
|
358
380
|
}
|
|
@@ -421,6 +443,7 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
421
443
|
items.push({ id: `q-line-${idx}-${items.length}`, text: t });
|
|
422
444
|
}
|
|
423
445
|
const itemSelectors = [
|
|
446
|
+
'.composer-toolbar-queue-item',
|
|
424
447
|
'[class*="ui-tray--queued"] [class*="queue-sortable-row"]',
|
|
425
448
|
'[class*="ui-tray--queued"] [class*="ui-tray-row"]',
|
|
426
449
|
'[class*="queue-sortable-row"]',
|
|
@@ -811,6 +834,92 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
811
834
|
t = t.replace(/\d+\s*(?:s|m|h|d|w)\b/gi, '').replace(/\d+[smhdw]$/i, '').trim();
|
|
812
835
|
return t.slice(0, 120);
|
|
813
836
|
}
|
|
837
|
+
/** Tab title from auxiliary bar (aria-label is usually the full name). */
|
|
838
|
+
function cleanAuxiliaryTabTitle(raw, fromAriaLabel) {
|
|
839
|
+
let t = raw.trim().replace(/\s+/g, ' ');
|
|
840
|
+
t = t.replace(/\d+\s*(?:s|m|h|d|w)\b/gi, '').replace(/\d+[smhdw]$/i, '').trim();
|
|
841
|
+
t = t.replace(/⌘[A-Z0-9]+$/i, '').trim();
|
|
842
|
+
// VS Code puts editor-group context in aria-label, not on the visible tab chip.
|
|
843
|
+
t = t.replace(/,\s*Chat Editors:\s*Editor Group\s*\d+\s*$/i, '').trim();
|
|
844
|
+
if (!fromAriaLabel) {
|
|
845
|
+
// Status suffix at end only — do not use /i on "Running" (would strip "running" in "Bot running on…").
|
|
846
|
+
t = t.replace(/\s+(?:Edited|Thinking|Grepping|Running)\s*$/i, '').trim();
|
|
847
|
+
t = t.replace(/\+\d[\d\-·\s\w]*(?:Files?|File)\d*\w*$/i, '').trim();
|
|
848
|
+
}
|
|
849
|
+
return t.slice(0, 120);
|
|
850
|
+
}
|
|
851
|
+
function pickAuxiliaryTabTitle(aria, visible) {
|
|
852
|
+
const cleanedAria = aria ? cleanAuxiliaryTabTitle(aria, true) : '';
|
|
853
|
+
const cleanedVisible = visible ? cleanAuxiliaryTabTitle(visible, false) : '';
|
|
854
|
+
if (cleanedAria && cleanedVisible) {
|
|
855
|
+
if (cleanedVisible.length > cleanedAria.length)
|
|
856
|
+
return cleanedVisible;
|
|
857
|
+
if (cleanedAria.startsWith(cleanedVisible) ||
|
|
858
|
+
cleanedVisible.length < Math.min(cleanedAria.length, 10)) {
|
|
859
|
+
return cleanedAria;
|
|
860
|
+
}
|
|
861
|
+
return cleanedAria.length >= cleanedVisible.length ? cleanedAria : cleanedVisible;
|
|
862
|
+
}
|
|
863
|
+
return cleanedAria || cleanedVisible;
|
|
864
|
+
}
|
|
865
|
+
function isEditorAuxiliaryLayout() {
|
|
866
|
+
if (document.querySelector('[class*="agent-panel"]'))
|
|
867
|
+
return false;
|
|
868
|
+
return !!document.querySelector('#workbench\\.parts\\.auxiliarybar');
|
|
869
|
+
}
|
|
870
|
+
function extractEditorAuxiliaryTabs() {
|
|
871
|
+
if (!isEditorAuxiliaryLayout())
|
|
872
|
+
return [];
|
|
873
|
+
const composerBar = document.querySelector('div.composer-bar.editor[data-composer-id]') ||
|
|
874
|
+
document.querySelector('.composer-bar[data-composer-id]') ||
|
|
875
|
+
document.querySelector('div.composer-bar.editor, .composer-bar');
|
|
876
|
+
const activeComposerId = composerBar?.getAttribute('data-composer-id') ||
|
|
877
|
+
composerBar?.closest('[data-composer-id]')?.getAttribute('data-composer-id') ||
|
|
878
|
+
'';
|
|
879
|
+
const tabEls = [];
|
|
880
|
+
for (const sel of editorAuxiliaryTabSelectors) {
|
|
881
|
+
try {
|
|
882
|
+
document.querySelectorAll(sel).forEach((el) => tabEls.push(el));
|
|
883
|
+
}
|
|
884
|
+
catch {
|
|
885
|
+
/* skip */
|
|
886
|
+
}
|
|
887
|
+
if (tabEls.length)
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
if (!tabEls.length)
|
|
891
|
+
return [];
|
|
892
|
+
const out = [];
|
|
893
|
+
tabEls.forEach((cell, i) => {
|
|
894
|
+
const aria = cell.getAttribute('aria-label')?.trim() ?? '';
|
|
895
|
+
const visible = (cell.textContent || '').trim().replace(/\s+/g, ' ');
|
|
896
|
+
const title = pickAuxiliaryTabTitle(aria, visible);
|
|
897
|
+
if (!title || /^(new agent|marketplace)$/i.test(title))
|
|
898
|
+
return;
|
|
899
|
+
const active = cell.classList.contains('active') ||
|
|
900
|
+
cell.classList.contains('selected') ||
|
|
901
|
+
cell.getAttribute('aria-selected') === 'true';
|
|
902
|
+
const resourceName = cell.getAttribute('data-resource-name')?.trim() || '';
|
|
903
|
+
let composerId = cell.getAttribute('data-composer-id') ||
|
|
904
|
+
cell.closest('[data-composer-id]')?.getAttribute('data-composer-id') ||
|
|
905
|
+
'';
|
|
906
|
+
if (active && activeComposerId)
|
|
907
|
+
composerId = activeComposerId;
|
|
908
|
+
else if (!composerId && resourceName.length >= 32) {
|
|
909
|
+
composerId = resourceName;
|
|
910
|
+
if (activeComposerId && activeComposerId.toLowerCase().startsWith(resourceName.toLowerCase())) {
|
|
911
|
+
composerId = activeComposerId;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
out.push({
|
|
915
|
+
id: composerId || `aux-tab-${i}`,
|
|
916
|
+
composerId: composerId || undefined,
|
|
917
|
+
title,
|
|
918
|
+
active,
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
return out;
|
|
922
|
+
}
|
|
814
923
|
function readAgentBtnMeta(btn) {
|
|
815
924
|
const title = cleanSidebarTitle(btn);
|
|
816
925
|
if (!title || /^more$/i.test(title) || /^less$/i.test(title))
|
|
@@ -827,8 +936,12 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
827
936
|
return { title, composerId, active, isWorking, hasUnread, needsAttention };
|
|
828
937
|
}
|
|
829
938
|
const tabs = [];
|
|
939
|
+
const editorAuxTabs = extractEditorAuxiliaryTabs();
|
|
940
|
+
if (editorAuxTabs.length > 0) {
|
|
941
|
+
tabs.push(...editorAuxTabs);
|
|
942
|
+
}
|
|
830
943
|
const glassTabs = document.querySelectorAll('.glass-sidebar-agent-list-container li.ui-sidebar-menu-item > div.glass-sidebar-agent-menu-btn');
|
|
831
|
-
if (glassTabs.length > 0) {
|
|
944
|
+
if (tabs.length === 0 && glassTabs.length > 0) {
|
|
832
945
|
glassTabs.forEach((cell, i) => {
|
|
833
946
|
const meta = readAgentBtnMeta(cell);
|
|
834
947
|
if (!meta)
|
|
@@ -844,7 +957,7 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
844
957
|
});
|
|
845
958
|
});
|
|
846
959
|
}
|
|
847
|
-
else {
|
|
960
|
+
else if (tabs.length === 0) {
|
|
848
961
|
for (const sel of tabSelectors) {
|
|
849
962
|
document.querySelectorAll(sel).forEach((cell, i) => {
|
|
850
963
|
const title = cell.getAttribute('aria-label') ??
|
|
@@ -1025,28 +1138,56 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
1025
1138
|
r,
|
|
1026
1139
|
].filter((el) => Boolean(el));
|
|
1027
1140
|
}
|
|
1028
|
-
/**
|
|
1029
|
-
function
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1141
|
+
/** Keep in sync with bridge/src/generation-stop-dom.ts */
|
|
1142
|
+
function findGenerationStopButton() {
|
|
1143
|
+
const scopes = [
|
|
1144
|
+
document.querySelector('[class*="agent-panel"]'),
|
|
1145
|
+
document.querySelector('.composer-bar'),
|
|
1146
|
+
document.querySelector('[class*="composer-bar"]'),
|
|
1147
|
+
document,
|
|
1148
|
+
].filter((el) => Boolean(el));
|
|
1149
|
+
const sels = [
|
|
1150
|
+
'button.ui-prompt-input-submit-button[data-state="stop"]',
|
|
1151
|
+
'button[aria-label="Stop generation"]',
|
|
1152
|
+
'button.ui-prompt-input-submit-button[aria-label="Stop generation"]',
|
|
1153
|
+
'button[data-state="stop"]',
|
|
1154
|
+
'.composer-button-area .anysphere-icon-button:has(.codicon-debug-stop)',
|
|
1155
|
+
'.send-with-mode .anysphere-icon-button:has(.codicon-debug-stop)',
|
|
1156
|
+
];
|
|
1157
|
+
for (const scope of scopes) {
|
|
1158
|
+
for (const sel of sels) {
|
|
1159
|
+
try {
|
|
1160
|
+
const hit = scope.querySelector(sel);
|
|
1161
|
+
if (!hit)
|
|
1162
|
+
continue;
|
|
1163
|
+
const btn = hit.classList.contains('codicon-debug-stop')
|
|
1164
|
+
? hit.closest('.anysphere-icon-button, button, [role="button"], a') || hit
|
|
1165
|
+
: hit;
|
|
1166
|
+
const st = getComputedStyle(btn);
|
|
1167
|
+
if (st.display === 'none' || st.visibility === 'hidden' || Number(st.opacity) < 0.05) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
const r = btn.getBoundingClientRect();
|
|
1171
|
+
if (r.width < 2 || r.height < 2)
|
|
1172
|
+
continue;
|
|
1173
|
+
if (btn.closest('.ui-shell-tool-call'))
|
|
1174
|
+
continue;
|
|
1175
|
+
const aria = (btn.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
1176
|
+
if (aria === 'stop command')
|
|
1177
|
+
continue;
|
|
1178
|
+
return btn;
|
|
1179
|
+
}
|
|
1180
|
+
catch {
|
|
1181
|
+
/* skip */
|
|
1182
|
+
}
|
|
1048
1183
|
}
|
|
1049
1184
|
}
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
/** Active generation — Stop in composer (not shell «Stop command» in tool rows). */
|
|
1188
|
+
function detectAgentWorking(root) {
|
|
1189
|
+
if (findGenerationStopButton())
|
|
1190
|
+
return true;
|
|
1050
1191
|
const title = document.querySelector('span.auxiliary-bar-chat-title')?.textContent?.trim() || '';
|
|
1051
1192
|
if (/generat|working|thinking|running|в работе/i.test(title) && !/waiting|approval/i.test(title)) {
|
|
1052
1193
|
return true;
|
|
@@ -1100,6 +1241,10 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
1100
1241
|
agentStatus = 'background_shell';
|
|
1101
1242
|
agentStatusMessage = backgroundStatusText;
|
|
1102
1243
|
}
|
|
1244
|
+
const activeListTab = tabs.find((t) => t.active);
|
|
1245
|
+
if (activeListTab) {
|
|
1246
|
+
activeListTab.isWorking = hasActiveGeneration;
|
|
1247
|
+
}
|
|
1103
1248
|
let contextPercent;
|
|
1104
1249
|
let terminalCount;
|
|
1105
1250
|
for (const el of Array.from(document.querySelectorAll('button, span, div'))) {
|
|
@@ -1266,5 +1411,7 @@ export function extractionFunction(containerSelectors, tabSelectors, inputSelect
|
|
|
1266
1411
|
}
|
|
1267
1412
|
}
|
|
1268
1413
|
export async function extractPageState(client, selectors) {
|
|
1269
|
-
return (await client.callFunction(extractionFunction, selectors.chatContainer.strategies, selectors.chatTabList.strategies, selectors.
|
|
1414
|
+
return (await client.callFunction(extractionFunction, selectors.chatContainer.strategies, selectors.chatTabList.strategies, selectors.editorAuxiliaryTabList?.strategies ?? [
|
|
1415
|
+
'#workbench\\.parts\\.auxiliarybar div.tab[role="tab"]',
|
|
1416
|
+
], selectors.chatInput.strategies, selectors.approveButton.strategies, selectors.approveButton.textMatch ?? [], selectors.rejectButton.strategies, selectors.rejectButton.textMatch ?? []));
|
|
1270
1417
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find / click agent **generation** stop (not shell «Stop command»).
|
|
3
|
+
* Single entry for CDP callFunction — helpers must live inside this function.
|
|
4
|
+
*/
|
|
5
|
+
export function generationStopDomAction(action) {
|
|
6
|
+
function isVisibleButton(btn) {
|
|
7
|
+
const st = getComputedStyle(btn);
|
|
8
|
+
if (st.display === 'none' || st.visibility === 'hidden' || Number(st.opacity) < 0.05) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const r = btn.getBoundingClientRect();
|
|
12
|
+
return r.width >= 2 && r.height >= 2;
|
|
13
|
+
}
|
|
14
|
+
function resolveStopClickTarget(el) {
|
|
15
|
+
if (!el)
|
|
16
|
+
return null;
|
|
17
|
+
if (el.classList.contains('codicon-debug-stop')) {
|
|
18
|
+
return el.closest('.anysphere-icon-button, button, [role="button"], a') || el;
|
|
19
|
+
}
|
|
20
|
+
return el;
|
|
21
|
+
}
|
|
22
|
+
function findGenerationStopButton() {
|
|
23
|
+
const selectors = [
|
|
24
|
+
'button.ui-prompt-input-submit-button[data-state="stop"]',
|
|
25
|
+
'button[aria-label="Stop generation"]',
|
|
26
|
+
'button.ui-prompt-input-submit-button[aria-label="Stop generation"]',
|
|
27
|
+
'button[data-state="stop"]',
|
|
28
|
+
'.composer-button-area .anysphere-icon-button:has(.codicon-debug-stop)',
|
|
29
|
+
'.send-with-mode .anysphere-icon-button:has(.codicon-debug-stop)',
|
|
30
|
+
];
|
|
31
|
+
const scopes = [
|
|
32
|
+
document.querySelector('[class*="agent-panel"]'),
|
|
33
|
+
document.querySelector('.composer-bar'),
|
|
34
|
+
document.querySelector('[class*="composer-bar"]'),
|
|
35
|
+
document,
|
|
36
|
+
].filter((el) => Boolean(el));
|
|
37
|
+
for (const scope of scopes) {
|
|
38
|
+
for (const sel of selectors) {
|
|
39
|
+
try {
|
|
40
|
+
const hit = scope.querySelector(sel);
|
|
41
|
+
const btn = resolveStopClickTarget(hit);
|
|
42
|
+
if (!btn || !isVisibleButton(btn))
|
|
43
|
+
continue;
|
|
44
|
+
if (btn.closest('.ui-shell-tool-call'))
|
|
45
|
+
continue;
|
|
46
|
+
const aria = (btn.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
47
|
+
if (aria === 'stop command')
|
|
48
|
+
continue;
|
|
49
|
+
return btn;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
/* skip */
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const btn = findGenerationStopButton();
|
|
59
|
+
if (action === 'has')
|
|
60
|
+
return !!btn;
|
|
61
|
+
if (!btn)
|
|
62
|
+
return false;
|
|
63
|
+
const target = resolveStopClickTarget(btn) || btn;
|
|
64
|
+
target.scrollIntoView({ block: 'center' });
|
|
65
|
+
target.click();
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
@@ -8,6 +8,7 @@ import { WindowMonitor } from './window-monitor.js';
|
|
|
8
8
|
import { Relay } from './relay.js';
|
|
9
9
|
import { MessageDebugStore } from './message-debug-store.js';
|
|
10
10
|
import { connectorClientVersion } from './connector-client-version.js';
|
|
11
|
+
import { installEditorListSync } from './editor-list-sync.js';
|
|
11
12
|
import { installKeepAwakeShutdown, startKeepAwake } from './keep-awake.js';
|
|
12
13
|
async function main() {
|
|
13
14
|
const config = loadConfig();
|
|
@@ -30,6 +31,7 @@ async function main() {
|
|
|
30
31
|
}
|
|
31
32
|
console.log(`Projects: ${config.cursorProjectsDir}`);
|
|
32
33
|
const stateManager = new StateManager(config.debounceMs);
|
|
34
|
+
installEditorListSync(stateManager);
|
|
33
35
|
const commandExecutor = new CommandExecutor(selectors);
|
|
34
36
|
const cdpBridge = new CDPBridge(config);
|
|
35
37
|
const jsonlIndex = new JsonlIndex(config.cursorProjectsDir);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove a deferred-queue row in the composer (Editor toolbar or Agents tray).
|
|
3
|
+
* Used via CDP callFunction — helpers must stay inside this function.
|
|
4
|
+
*/
|
|
5
|
+
export declare function queueRemoveDomAction(action: 'remove', opts: {
|
|
6
|
+
text?: string;
|
|
7
|
+
selectorPath?: string;
|
|
8
|
+
}): {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
reason?: string;
|
|
11
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove a deferred-queue row in the composer (Editor toolbar or Agents tray).
|
|
3
|
+
* Used via CDP callFunction — helpers must stay inside this function.
|
|
4
|
+
*/
|
|
5
|
+
export function queueRemoveDomAction(action, opts) {
|
|
6
|
+
function normText(s) {
|
|
7
|
+
return s.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
function clickRemoveButton(btn) {
|
|
10
|
+
const el = btn;
|
|
11
|
+
el.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
12
|
+
el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
|
|
13
|
+
el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
|
|
14
|
+
el.click();
|
|
15
|
+
}
|
|
16
|
+
function findRemoveButton(row) {
|
|
17
|
+
const legacy = row.querySelector('button[aria-label="Remove"]');
|
|
18
|
+
if (legacy)
|
|
19
|
+
return legacy;
|
|
20
|
+
const removeAction = row.querySelector('[data-queue-action="remove"]');
|
|
21
|
+
if (removeAction) {
|
|
22
|
+
return (removeAction.closest('button') ||
|
|
23
|
+
removeAction.querySelector('button, .anysphere-icon-button') ||
|
|
24
|
+
removeAction);
|
|
25
|
+
}
|
|
26
|
+
const trash = row.querySelector('.codicon-trashcan');
|
|
27
|
+
if (trash) {
|
|
28
|
+
return (trash.closest('button') ||
|
|
29
|
+
trash.closest('.anysphere-icon-button') ||
|
|
30
|
+
trash.parentElement);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const rowSelectors = [
|
|
35
|
+
'.composer-toolbar-queue-item',
|
|
36
|
+
'[class*="ui-tray--queued"] [class*="queue-sortable-row"]',
|
|
37
|
+
'[class*="ui-tray--queued"] [class*="ui-tray-row"]',
|
|
38
|
+
'[class*="queue-sortable-row"]',
|
|
39
|
+
'[class*="ui-tray-row"]',
|
|
40
|
+
];
|
|
41
|
+
function rowsInScope() {
|
|
42
|
+
const input = document.querySelector(".composer-bar [contenteditable='true'], [contenteditable='true']");
|
|
43
|
+
const scope = input?.closest('[class*="agent-panel"]') ||
|
|
44
|
+
input?.closest('[class*="composer-panel"]') ||
|
|
45
|
+
input?.closest('.composer-bar, [class*="composer-bar"]') ||
|
|
46
|
+
document.querySelector('#composer-toolbar-section') ||
|
|
47
|
+
document.body;
|
|
48
|
+
const out = [];
|
|
49
|
+
for (const sel of rowSelectors) {
|
|
50
|
+
scope.querySelectorAll(sel).forEach((row) => {
|
|
51
|
+
if (!out.includes(row))
|
|
52
|
+
out.push(row);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
if (action !== 'remove')
|
|
58
|
+
return { ok: false, reason: 'unknown action' };
|
|
59
|
+
const want = normText(opts.text ?? '');
|
|
60
|
+
const snippet = want.slice(0, 48);
|
|
61
|
+
if (snippet.length >= 4) {
|
|
62
|
+
for (const row of rowsInScope()) {
|
|
63
|
+
const rowText = normText(row.textContent || '');
|
|
64
|
+
const rowSnippet = rowText.slice(0, 48);
|
|
65
|
+
if (!rowText.includes(snippet) && !rowSnippet.includes(snippet.slice(0, 24))) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const btn = findRemoveButton(row);
|
|
69
|
+
if (!btn)
|
|
70
|
+
return { ok: false, reason: 'remove button not found' };
|
|
71
|
+
clickRemoveButton(btn);
|
|
72
|
+
return { ok: true };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const path = opts.selectorPath?.trim();
|
|
76
|
+
if (path) {
|
|
77
|
+
const el = document.querySelector(path);
|
|
78
|
+
if (!el)
|
|
79
|
+
return { ok: false, reason: 'selector not found' };
|
|
80
|
+
const row = el.closest('.composer-toolbar-queue-item, [class*="queue-sortable-row"], [class*="ui-tray-row"]');
|
|
81
|
+
const btn = row ? findRemoveButton(row) : el;
|
|
82
|
+
if (!btn)
|
|
83
|
+
return { ok: false, reason: 'remove target not found' };
|
|
84
|
+
clickRemoveButton(btn);
|
|
85
|
+
return { ok: true };
|
|
86
|
+
}
|
|
87
|
+
return { ok: false, reason: want ? 'queue row not found' : 'no text or selector' };
|
|
88
|
+
}
|
|
@@ -40,6 +40,7 @@ export class RelayUpstream {
|
|
|
40
40
|
code: identity.pairingCode,
|
|
41
41
|
clientToken: identity.clientToken,
|
|
42
42
|
expiresAt: identity.pairingCodeExpiresAt,
|
|
43
|
+
machineLabel: identity.machineLabel,
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -57,6 +58,7 @@ export class RelayUpstream {
|
|
|
57
58
|
roomId: this.config.relayRoomId,
|
|
58
59
|
clientKind: 'connector',
|
|
59
60
|
clientVersion: connectorClientVersion(),
|
|
61
|
+
machineLabel: loadPairingIdentity()?.machineLabel ?? '',
|
|
60
62
|
},
|
|
61
63
|
});
|
|
62
64
|
this.socket.on('connect', () => {
|
|
@@ -1211,7 +1211,16 @@ export class Relay {
|
|
|
1211
1211
|
}
|
|
1212
1212
|
this.refreshAgentsIndex(true);
|
|
1213
1213
|
}
|
|
1214
|
-
async trySwitchWindowForAgent(agentId) {
|
|
1214
|
+
async trySwitchWindowForAgent(agentId, windowId) {
|
|
1215
|
+
await this.cdpBridge.refreshWindows();
|
|
1216
|
+
if (windowId) {
|
|
1217
|
+
const win = this.cdpBridge.windows.find((w) => w.id === windowId);
|
|
1218
|
+
if (win && win.id !== this.cdpBridge.activeTargetId) {
|
|
1219
|
+
await this.cdpBridge.switchWindow(win.id);
|
|
1220
|
+
console.log(`[relay] CDP → editor window "${win.title}" (${windowId.slice(0, 8)})`);
|
|
1221
|
+
}
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1215
1224
|
const projectDir = this.jsonlIndex.findProjectDirForAgent(agentId);
|
|
1216
1225
|
if (!projectDir)
|
|
1217
1226
|
return;
|
|
@@ -1222,7 +1231,6 @@ export class Relay {
|
|
|
1222
1231
|
const repoName = repoSlug.replace(/-/g, ' ').toLowerCase();
|
|
1223
1232
|
if (!repoName)
|
|
1224
1233
|
return;
|
|
1225
|
-
await this.cdpBridge.refreshWindows();
|
|
1226
1234
|
const match = this.cdpBridge.windows.find((w) => {
|
|
1227
1235
|
const t = w.title.toLowerCase();
|
|
1228
1236
|
return t.includes(repoName) || repoName.split(' ').every((wrd) => t.includes(wrd));
|
|
@@ -1316,17 +1324,29 @@ export class Relay {
|
|
|
1316
1324
|
this.domExtractor.pollNow();
|
|
1317
1325
|
return;
|
|
1318
1326
|
}
|
|
1327
|
+
if (payload.agentId &&
|
|
1328
|
+
(payload.type === 'remove_queued' ||
|
|
1329
|
+
payload.type === 'send_message' ||
|
|
1330
|
+
payload.type === 'stop_agent' ||
|
|
1331
|
+
payload.type === 'focus_agent')) {
|
|
1332
|
+
await this.trySwitchWindowForAgent(payload.agentId, payload.windowId);
|
|
1333
|
+
}
|
|
1319
1334
|
const result = await this.commandExecutor.execute(payload);
|
|
1320
1335
|
reply('command:result', result);
|
|
1321
|
-
if (payload.type === 'stop_agent'
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1336
|
+
if ((payload.type === 'stop_agent' ||
|
|
1337
|
+
payload.type === 'remove_queued' ||
|
|
1338
|
+
payload.type === 'focus_agent') &&
|
|
1339
|
+
result.ok) {
|
|
1340
|
+
if (payload.type === 'stop_agent') {
|
|
1341
|
+
suppressAgentCompletionPush();
|
|
1342
|
+
this.stateManager.patchNow({
|
|
1343
|
+
agentWorking: false,
|
|
1344
|
+
agentStatus: undefined,
|
|
1345
|
+
agentStatusMessage: undefined,
|
|
1346
|
+
composerDraft: undefined,
|
|
1347
|
+
composerDraftImages: undefined,
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1330
1350
|
this.domExtractor.pollNow();
|
|
1331
1351
|
}
|
|
1332
1352
|
}
|
|
@@ -1410,13 +1430,13 @@ export class Relay {
|
|
|
1410
1430
|
this.jsonlIndex.historyReplyInFlight.delete(agentId);
|
|
1411
1431
|
}
|
|
1412
1432
|
}
|
|
1413
|
-
async runAgentsSubscribe({ agentId, title, focus, }) {
|
|
1433
|
+
async runAgentsSubscribe({ agentId, title, focus, windowId, }) {
|
|
1414
1434
|
if (!agentId)
|
|
1415
1435
|
return;
|
|
1416
1436
|
const alreadySubscribed = this.jsonlIndex.getSubscribedAgents().has(agentId);
|
|
1417
1437
|
this.jsonlIndex.subscribe(agentId, title, { emitHistory: !alreadySubscribed });
|
|
1418
1438
|
this.stateManager.patchNow({ lastError: undefined });
|
|
1419
|
-
await this.trySwitchWindowForAgent(agentId);
|
|
1439
|
+
await this.trySwitchWindowForAgent(agentId, windowId);
|
|
1420
1440
|
if (focus === false) {
|
|
1421
1441
|
void this.refreshDomChatOnSubscribe(agentId, title, { clear: !alreadySubscribed });
|
|
1422
1442
|
return;
|
|
@@ -1486,10 +1506,10 @@ export class Relay {
|
|
|
1486
1506
|
this.refreshLentaPendingPatch();
|
|
1487
1507
|
}
|
|
1488
1508
|
}
|
|
1489
|
-
async runAgentsFocus({ agentId, title }, reply) {
|
|
1509
|
+
async runAgentsFocus({ agentId, title, windowId, }, reply) {
|
|
1490
1510
|
if (!agentId)
|
|
1491
1511
|
return;
|
|
1492
|
-
await this.trySwitchWindowForAgent(agentId);
|
|
1512
|
+
await this.trySwitchWindowForAgent(agentId, windowId);
|
|
1493
1513
|
const result = await this.commandExecutor.execute({
|
|
1494
1514
|
id: `focus-${Date.now()}`,
|
|
1495
1515
|
type: 'focus_agent',
|
|
@@ -7,7 +7,7 @@ export declare class StateManager extends EventEmitter {
|
|
|
7
7
|
private pendingPatch;
|
|
8
8
|
constructor(debounceMs: number);
|
|
9
9
|
getState(): CursorState;
|
|
10
|
-
onExtraction(partial: CursorState | null, error?: string): void;
|
|
10
|
+
onExtraction(partial: Partial<CursorState> | null, error?: string): void;
|
|
11
11
|
onConnectionChanged(connected: boolean): void;
|
|
12
12
|
updateWindows(windows: CursorWindow[], activeWindowId: string): void;
|
|
13
13
|
updateWindowSnapshots(snapshots: WindowSnapshot[]): void;
|
|
@@ -6,6 +6,8 @@ export interface SelectorConfig {
|
|
|
6
6
|
chatContainer: SelectorGroup;
|
|
7
7
|
chatInput: SelectorGroup;
|
|
8
8
|
chatTabList: SelectorGroup;
|
|
9
|
+
/** Editor: open agent chats in auxiliary bar tab strip (not agents sidebar). */
|
|
10
|
+
editorAuxiliaryTabList?: SelectorGroup;
|
|
9
11
|
newChatButton: SelectorGroup;
|
|
10
12
|
modeDropdown: SelectorGroup;
|
|
11
13
|
modelDropdown: SelectorGroup;
|
|
@@ -135,11 +137,14 @@ export interface PendingApproval {
|
|
|
135
137
|
description: string;
|
|
136
138
|
actions: ApprovalAction[];
|
|
137
139
|
}
|
|
140
|
+
export type CursorWindowKind = 'editor' | 'agents';
|
|
138
141
|
export interface CursorWindow {
|
|
139
142
|
id: string;
|
|
140
143
|
title: string;
|
|
141
144
|
workspace?: string;
|
|
142
145
|
wsUrl?: string;
|
|
146
|
+
/** `agents` = dedicated Cursor Agents window; `editor` = code editor + auxiliary bar. */
|
|
147
|
+
kind?: CursorWindowKind;
|
|
143
148
|
}
|
|
144
149
|
export interface WindowSnapshot {
|
|
145
150
|
windowId: string;
|
|
@@ -148,8 +153,12 @@ export interface WindowSnapshot {
|
|
|
148
153
|
messageCount: number;
|
|
149
154
|
lastPreview?: string;
|
|
150
155
|
agentStatus?: string;
|
|
156
|
+
/** DOM: active chat generating (composer Stop / status line). */
|
|
157
|
+
agentWorking?: boolean;
|
|
151
158
|
pendingApprovals: number;
|
|
152
159
|
updatedAt: number;
|
|
160
|
+
tabs?: ChatTab[];
|
|
161
|
+
kind?: CursorWindowKind;
|
|
153
162
|
}
|
|
154
163
|
export interface CursorState {
|
|
155
164
|
connected: boolean;
|
|
@@ -191,6 +200,10 @@ export interface CursorState {
|
|
|
191
200
|
lastError?: string;
|
|
192
201
|
/** Cursor sidebar: repos + chats in DOM order (mirrors desktop UI). */
|
|
193
202
|
sidebarRepos?: RepoGroup[];
|
|
203
|
+
/** Editor mode: open editor windows as folders, tabs as chats (see `cursorListMode`). */
|
|
204
|
+
editorRepos?: RepoGroup[];
|
|
205
|
+
/** Which chat list layout the app should show (`editor` vs agents window). */
|
|
206
|
+
cursorListMode?: 'editor' | 'agents-window';
|
|
194
207
|
/** Last DOM poll: extract/filter counters (sync debug). */
|
|
195
208
|
messageDebug?: MessageExtractDebug;
|
|
196
209
|
/** Composer input draft (contenteditable), when non-empty. */
|
|
@@ -205,6 +218,7 @@ export interface CommandPayload {
|
|
|
205
218
|
type: CommandType;
|
|
206
219
|
text?: string;
|
|
207
220
|
tabTitle?: string;
|
|
221
|
+
/** Editor CDP window id (from chat route / agents:index). */
|
|
208
222
|
windowId?: string;
|
|
209
223
|
agentId?: string;
|
|
210
224
|
selectorPath?: string;
|