@adhdev/daemon-core 0.6.36 → 0.6.37

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 (92) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +9 -12
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/providers/_builtin/acp/codex-cli/provider.json +2 -2
  6. package/providers/_builtin/cli/codex-cli/provider.json +8 -18
  7. package/providers/_builtin/ide/antigravity/provider.json +2 -3
  8. package/providers/_builtin/ide/cursor/provider.json +2 -2
  9. package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/read_chat.js +1 -1
  10. package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/resolve_action.js +1 -1
  11. package/providers/_builtin/ide/windsurf/scripts/1.0/focus_editor.js +16 -10
  12. package/providers/_builtin/ide/windsurf/scripts/1.0/list_chats.js +9 -103
  13. package/providers/_builtin/ide/windsurf/scripts/1.0/list_models.js +43 -26
  14. package/providers/_builtin/ide/windsurf/scripts/1.0/list_modes.js +51 -27
  15. package/providers/_builtin/ide/windsurf/scripts/1.0/list_sessions.js +79 -0
  16. package/providers/_builtin/ide/windsurf/scripts/1.0/new_session.js +30 -41
  17. package/providers/_builtin/ide/windsurf/scripts/1.0/open_panel.js +44 -35
  18. package/providers/_builtin/ide/windsurf/scripts/1.0/read_chat.js +211 -273
  19. package/providers/_builtin/ide/windsurf/scripts/1.0/resolve_action.js +64 -50
  20. package/providers/_builtin/ide/windsurf/scripts/1.0/send_message.js +14 -81
  21. package/providers/_builtin/ide/windsurf/scripts/1.0/set_mode.js +34 -6
  22. package/providers/_builtin/ide/windsurf/scripts/1.0/set_model.js +40 -6
  23. package/providers/_builtin/ide/windsurf/scripts/1.0/switch_session.js +66 -42
  24. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/focus_editor.js +30 -0
  25. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_chats.js +117 -0
  26. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_models.js +39 -0
  27. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_modes.js +39 -0
  28. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/new_session.js +69 -0
  29. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/open_panel.js +58 -0
  30. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/read_chat.js +315 -0
  31. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/resolve_action.js +68 -0
  32. package/providers/_builtin/ide/{cursor/scripts/0.49 → windsurf/scripts/1.0.bak}/scripts.js +27 -23
  33. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/send_message.js +87 -0
  34. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/set_mode.js +15 -0
  35. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/set_model.js +15 -0
  36. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/switch_session.js +58 -0
  37. package/providers/_builtin/registry.json +5 -9
  38. package/src/commands/handler.ts +1 -8
  39. package/src/daemon/dev-server.ts +12 -3
  40. package/src/detection/ide-detector.ts +0 -3
  41. package/src/providers/provider-loader.ts +0 -1
  42. package/providers/_builtin/ide/antigravity/scripts/1.106/list_models.js +0 -38
  43. package/providers/_builtin/ide/antigravity/scripts/1.106/list_modes.js +0 -48
  44. package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +0 -290
  45. package/providers/_builtin/ide/antigravity/scripts/1.106/scripts.js +0 -57
  46. package/providers/_builtin/ide/antigravity/scripts/1.106/set_mode.js +0 -34
  47. package/providers/_builtin/ide/antigravity/scripts/1.106/set_model.js +0 -47
  48. package/providers/_builtin/ide/antigravity/scripts/1.107/focus_editor.js +0 -20
  49. package/providers/_builtin/ide/antigravity/scripts/1.107/list_chats.js +0 -137
  50. package/providers/_builtin/ide/antigravity/scripts/1.107/new_session.js +0 -75
  51. package/providers/_builtin/ide/antigravity/scripts/1.107/resolve_action.js +0 -63
  52. package/providers/_builtin/ide/antigravity/scripts/1.107/send_message.js +0 -56
  53. package/providers/_builtin/ide/antigravity/scripts/1.107/switch_session.js +0 -114
  54. package/providers/_builtin/ide/cursor/scripts/0.49/dismiss_notification.js +0 -30
  55. package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +0 -13
  56. package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +0 -78
  57. package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +0 -40
  58. package/providers/_builtin/ide/cursor/scripts/0.49/list_notifications.js +0 -23
  59. package/providers/_builtin/ide/cursor/scripts/0.49/new_session.js +0 -20
  60. package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +0 -23
  61. package/providers/_builtin/ide/cursor/scripts/0.49/send_message.js +0 -23
  62. package/providers/_builtin/ide/cursor/scripts/0.49/set_mode.js +0 -38
  63. package/providers/_builtin/ide/cursor/scripts/0.49/set_model.js +0 -81
  64. package/providers/_builtin/ide/cursor/scripts/0.49/switch_session.js +0 -28
  65. package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_sessions.js +0 -42
  66. package/providers/_builtin/ide/cursor/scripts/0.49.bak/read_chat.js +0 -79
  67. package/providers/_builtin/ide/cursor/scripts/0.49.bak/resolve_action.js +0 -19
  68. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/focus_editor.js +0 -0
  69. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/list_chats.js +0 -0
  70. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/list_models.js +0 -0
  71. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/list_modes.js +0 -0
  72. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/new_session.js +0 -0
  73. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/read_chat.js +0 -0
  74. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/resolve_action.js +0 -0
  75. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/scripts.js +0 -0
  76. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/send_message.js +0 -0
  77. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/set_mode.js +0 -0
  78. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/set_model.js +0 -0
  79. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/switch_session.js +0 -0
  80. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/dismiss_notification.js +0 -0
  81. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/focus_editor.js +0 -0
  82. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_models.js +0 -0
  83. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_modes.js +0 -0
  84. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_notifications.js +0 -0
  85. /package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/list_sessions.js +0 -0
  86. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/new_session.js +0 -0
  87. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/open_panel.js +0 -0
  88. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/scripts.js +0 -0
  89. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/send_message.js +0 -0
  90. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/set_mode.js +0 -0
  91. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/set_model.js +0 -0
  92. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/switch_session.js +0 -0
@@ -10,49 +10,58 @@
10
10
  * 반환: 'visible' | 'opened' | 'error: ...'
11
11
  * 최종 확인: Windsurf 1.108.x (2026-03-10)
12
12
  */
13
- (() => {
13
+ (async () => {
14
14
  try {
15
- // ─── 1. Cascade 패널이 이미 열려 있는지 확인 ───
16
- const cascade = document.querySelector('#windsurf\\.cascadePanel') ||
17
- document.querySelector('.chat-client-root');
18
- const sidebar = document.getElementById('workbench.parts.auxiliarybar');
15
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
16
+ const isOpen = () => {
17
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') || document.querySelector('.chat-client-root');
18
+ return !!(cascade && cascade.offsetWidth > 0 && cascade.offsetHeight > 0);
19
+ };
19
20
 
20
- // 패널이 존재하고 보이면 이미 열린 상태
21
- if (cascade && cascade.offsetWidth > 0 && cascade.offsetHeight > 0) {
22
- return 'visible';
23
- }
24
- if (sidebar && sidebar.offsetWidth > 0 && sidebar.offsetHeight > 0 && cascade) {
25
- return 'visible';
21
+ if (isOpen()) {
22
+ return JSON.stringify({ opened: true });
26
23
  }
27
24
 
28
- // ─── 2. Toggle 버튼 클릭 시도 ───
29
- const toggleBtns = Array.from(document.querySelectorAll('li.action-item a, button, [role="button"]'));
30
- for (const btn of toggleBtns) {
31
- const label = (btn.getAttribute('aria-label') || '').toLowerCase();
32
- if (label.includes('toggle cascade') || label.includes('toggle secondary') ||
33
- label.includes('toggle auxiliary') || label.includes('cascade')) {
34
- if (btn.offsetWidth > 0 || btn.offsetHeight > 0) {
35
- btn.click();
36
- return 'opened (toggle)';
37
- }
25
+ const toggles = Array.from(document.querySelectorAll('a[role="checkbox"], a[role="button"], button, [role="button"]'))
26
+ .filter(el => el.offsetWidth > 0 && el.offsetHeight > 0);
27
+
28
+ const toggle = toggles.find(el => {
29
+ const label = ((el.getAttribute('aria-label') || '') + ' ' + (el.getAttribute('title') || '')).toLowerCase();
30
+ return label.includes('cascade') || label.includes('toggle cascade') || label.includes('toggle auxiliary') || label.includes('toggle secondary');
31
+ });
32
+
33
+ if (toggle) {
34
+ toggle.click();
35
+ await wait(250);
36
+ if (isOpen()) {
37
+ return JSON.stringify({ opened: true });
38
38
  }
39
39
  }
40
40
 
41
- // ─── 3. Cmd+L 단축키 폴백 (Windsurf 공식 단축키) ───
42
- // keyCode: 76, modifiers: 4 (Meta/Cmd)
43
- document.dispatchEvent(new KeyboardEvent('keydown', {
44
- key: 'l', code: 'KeyL', keyCode: 76,
45
- metaKey: true, ctrlKey: false,
46
- bubbles: true, cancelable: true,
47
- }));
48
- document.dispatchEvent(new KeyboardEvent('keyup', {
49
- key: 'l', code: 'KeyL', keyCode: 76,
50
- metaKey: true, ctrlKey: false,
51
- bubbles: true, cancelable: true,
52
- }));
41
+ const fireShortcut = (metaKey, ctrlKey) => {
42
+ const down = new KeyboardEvent('keydown', {
43
+ key: 'l', code: 'KeyL', keyCode: 76, which: 76,
44
+ metaKey, ctrlKey,
45
+ bubbles: true, cancelable: true,
46
+ });
47
+ const up = new KeyboardEvent('keyup', {
48
+ key: 'l', code: 'KeyL', keyCode: 76, which: 76,
49
+ metaKey, ctrlKey,
50
+ bubbles: true, cancelable: true,
51
+ });
52
+ document.dispatchEvent(down);
53
+ document.dispatchEvent(up);
54
+ };
55
+
56
+ fireShortcut(true, false);
57
+ await wait(250);
58
+ if (!isOpen()) {
59
+ fireShortcut(false, true);
60
+ await wait(250);
61
+ }
53
62
 
54
- return 'opened (Cmd+L)';
63
+ return JSON.stringify({ opened: isOpen() });
55
64
  } catch (e) {
56
- return 'error: ' + e.message;
65
+ return JSON.stringify({ opened: false, error: e.message });
57
66
  }
58
67
  })()
@@ -1,315 +1,253 @@
1
1
  /**
2
- * Windsurf v1 — read_chat (v1 — Cascade DOM + Fiber)
3
- *
4
- * Windsurf는 VS Code 포크, 채팅 UI를 "Cascade"라고 부릅니다.
5
- *
6
- * DOM 구조:
7
- * #windsurf.cascadePanel → .chat-client-root
8
- * 스크롤: .cascade-scrollbar
9
- * 메시지 리스트: .cascade-scrollbar .pb-20 > .flex.flex-col > .flex.flex-col.gap-2\.5
10
- * 사용자 메시지: hasProse=false, hasWhitespace=true
11
- * AI 메시지: [class*="prose"] (prose-sm)
12
- * 피드백 UI: .mark-js-ignore (무시)
13
- * 입력: [data-lexical-editor="true"]
14
- *
15
- * Fiber props:
16
- * cascadeId: 세션 ID
17
- * isRunning: 생성 중 여부
18
- * hasPendingTerminalCommand: 승인 대기
19
- * copyableText: AI 응답 마크다운 원본
20
- *
21
- * 최종 확인: Windsurf (2026-03-06)
2
+ * Windsurf v1 — read_chat
22
3
  */
23
4
  (() => {
24
5
  try {
25
- // ─── 1. 컨테이너 ───
26
- const cascade = document.querySelector('#windsurf\\.cascadePanel')
27
- || document.querySelector('.chat-client-root');
28
- if (!cascade) {
29
- return { id: 'no_cascade', status: 'idle', title: 'No Cascade', messages: [], inputContent: '', activeModal: null };
30
- }
31
-
32
- // ─── 2. Fiber에서 cascadeId, isRunning 추출 (턴 요소에서 탐색) ───
33
- let cascadeId = 'cascade';
34
- let isRunning = false;
35
- let hasPendingCmd = false;
36
- try {
37
- // 턴 요소에서 Fiber 탐색 (cascadePanel 루트보다 깊이 6에서 cascadeId 발견)
38
- const scrollArea = cascade.querySelector('.cascade-scrollbar');
39
- const gapEls = scrollArea ? scrollArea.querySelectorAll('[class*="gap-2"]') : [];
40
- let firstTurn = null;
41
- for (const el of gapEls) {
42
- if (el.children.length >= 1 && el.closest('.cascade-scrollbar')) {
43
- firstTurn = el.children[0]; break;
44
- }
45
- }
46
- const fiberTarget = firstTurn || cascade;
47
- const fk = Object.keys(fiberTarget).find(k => k.startsWith('__reactFiber'));
48
- if (fk) {
49
- let fiber = fiberTarget[fk];
50
- for (let d = 0; d < 50 && fiber; d++) {
51
- const p = fiber.memoizedProps || fiber.pendingProps || {};
52
- if (p.cascadeId && typeof p.cascadeId === 'string') cascadeId = p.cascadeId;
53
- if (p.isRunning === true) isRunning = true;
54
- if (p.hasPendingTerminalCommand === true) hasPendingCmd = true;
55
- fiber = fiber.return;
56
- }
57
- }
58
- } catch (_) { }
6
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') || document.querySelector('.chat-client-root');
7
+ const normalize = (text) => (text || '').replace(/[\u200b\u00a0]+/g, ' ').replace(/\s+/g, ' ').trim();
8
+ const cleanText = (text) => normalize((text || '').replace(/Run⌥⏎/g, 'Run').replace(/\b(Skip|Accept|Reject|Approve|Allow|Deny)\b/g, ' $1 ').replace(/\s+/g, ' '));
9
+ const isVisible = (el) => !!(el && el.offsetWidth > 0 && el.offsetHeight > 0);
59
10
 
60
- // ─── 3. 상태 감지 ───
61
- let status = 'idle';
62
- if (isRunning) status = 'generating';
63
-
64
- // Signal A: Stop 버튼
65
- if (status === 'idle') {
66
- const allBtns = Array.from(document.querySelectorAll('button'));
67
- const stopBtn = allBtns.find(b => {
68
- if (b.offsetWidth === 0) return false;
69
- const label = (b.getAttribute('aria-label') || '').toLowerCase();
70
- const text = (b.textContent || '').trim().toLowerCase();
71
- return label.includes('stop') || label === 'cancel generation'
72
- || text === 'stop' || text === 'stop generating';
73
- });
74
- if (stopBtn) status = 'generating';
11
+ if (!cascade) {
12
+ return JSON.stringify({ id: 'no_cascade', status: 'idle', title: 'Cascade', messages: [], inputContent: '', activeModal: null });
75
13
  }
76
14
 
77
- // Signal B: 입력창 placeholder
78
- if (status === 'idle') {
79
- const editor = cascade.querySelector('[data-lexical-editor="true"]');
80
- if (editor) {
81
- const ph = (editor.getAttribute('placeholder') || '').toLowerCase();
82
- if (ph.includes('wait') || ph.includes('generating')) status = 'generating';
15
+ const title = normalize(
16
+ document.querySelector('[id^="cascade-tab-"] .truncate, [id^="cascade-tab-"] span')?.textContent
17
+ || document.title.split('').pop()
18
+ || 'Cascade'
19
+ );
20
+
21
+ const activeTab = Array.from(document.querySelectorAll('[id^="cascade-tab-"]')).find(isVisible);
22
+ let cascadeId = activeTab?.id?.replace(/^cascade-tab-/, '') || 'active';
23
+ let fiberRunning = false;
24
+ let fiberPendingCommand = false;
25
+
26
+ const readFiberValue = (node) => {
27
+ if (!node || typeof node !== 'object') return;
28
+ const key = Object.keys(node).find(name => name.startsWith('__reactFiber'));
29
+ if (!key) return;
30
+ let fiber = node[key];
31
+ for (let depth = 0; fiber && depth < 40; depth += 1, fiber = fiber.return) {
32
+ const props = fiber.memoizedProps || fiber.pendingProps || {};
33
+ if (!cascadeId && typeof props.cascadeId === 'string') cascadeId = props.cascadeId;
34
+ if (props.cascadeId && typeof props.cascadeId === 'string') cascadeId = props.cascadeId;
35
+ if (props.isRunning === true) fiberRunning = true;
36
+ if (props.hasPendingTerminalCommand === true) fiberPendingCommand = true;
83
37
  }
84
- }
38
+ };
85
39
 
86
- const titleParts = document.title.split(' \u2014 ');
87
- const title = (titleParts.length >= 2 ? titleParts[titleParts.length - 1] : titleParts[0] || '').trim() || 'Cascade';
40
+ readFiberValue(cascade);
88
41
 
89
- // ─── 4. HTML → Markdown 변환기 ───
90
42
  const BLOCK_TAGS = new Set(['DIV', 'P', 'BR', 'LI', 'TR', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER']);
91
- function extractCodeText(node) {
92
- if (node.nodeType === 3) return node.textContent || '';
93
- if (node.nodeType !== 1) return '';
43
+ const extractCodeText = (node) => {
44
+ if (!node) return '';
45
+ if (node.nodeType === Node.TEXT_NODE) return node.textContent || '';
46
+ if (node.nodeType !== Node.ELEMENT_NODE) return '';
94
47
  if (node.tagName === 'BR') return '\n';
95
48
  const parts = [];
96
49
  for (const child of node.childNodes) {
97
- const isBlock = child.nodeType === 1 && BLOCK_TAGS.has(child.tagName);
50
+ const block = child.nodeType === Node.ELEMENT_NODE && BLOCK_TAGS.has(child.tagName);
98
51
  const text = extractCodeText(child);
99
- if (text) {
100
- if (isBlock && parts.length > 0) parts.push('\n');
101
- parts.push(text);
102
- if (isBlock) parts.push('\n');
103
- }
52
+ if (!text) continue;
53
+ if (block && parts.length > 0) parts.push('\n');
54
+ parts.push(text);
55
+ if (block) parts.push('\n');
104
56
  }
105
- return parts.join('').replace(/\n{2,}/g, '\n');
106
- }
107
- function htmlToMd(node) {
108
- if (node.nodeType === 3) return node.textContent || '';
109
- if (node.nodeType !== 1) return '';
57
+ return parts.join('').replace(/\n{3,}/g, '\n\n');
58
+ };
59
+ const htmlToMd = (node) => {
60
+ if (!node) return '';
61
+ if (node.nodeType === Node.TEXT_NODE) return node.textContent || '';
62
+ if (node.nodeType !== Node.ELEMENT_NODE) return '';
110
63
  const tag = node.tagName;
111
64
  if (tag === 'STYLE' || tag === 'SCRIPT' || tag === 'SVG') return '';
112
- if (tag === 'TABLE') {
113
- const rows = Array.from(node.querySelectorAll('tr'));
114
- if (rows.length === 0) return '';
115
- const table = rows.map(tr =>
116
- Array.from(tr.querySelectorAll('th, td')).map(cell => (cell.textContent || '').trim().replace(/\|/g, '\\|'))
117
- );
118
- const colCount = Math.max(...table.map(r => r.length));
119
- const header = table[0] || [];
120
- const sep = Array(colCount).fill('---');
121
- const body = table.slice(1);
122
- let md = '| ' + header.join(' | ') + ' |\n';
123
- md += '| ' + sep.join(' | ') + ' |\n';
124
- for (const row of body) {
125
- while (row.length < colCount) row.push('');
126
- md += '| ' + row.join(' | ') + ' |\n';
127
- }
128
- return '\n' + md + '\n';
65
+ if (tag === 'BR') return '\n';
66
+ if (tag === 'CODE' && node.parentElement?.tagName !== 'PRE') {
67
+ return '`' + extractCodeText(node).replace(/`/g, '\\`') + '`';
129
68
  }
130
- if (tag === 'UL') return '\n' + Array.from(node.children).map(li => '- ' + childrenToMd(li).trim()).join('\n') + '\n';
131
- if (tag === 'OL') return '\n' + Array.from(node.children).map((li, i) => (i + 1) + '. ' + childrenToMd(li).trim()).join('\n') + '\n';
132
- if (tag === 'LI') return childrenToMd(node);
133
- if (tag === 'H1') return '\n# ' + childrenToMd(node).trim() + '\n';
134
- if (tag === 'H2') return '\n## ' + childrenToMd(node).trim() + '\n';
135
- if (tag === 'H3') return '\n### ' + childrenToMd(node).trim() + '\n';
136
- if (tag === 'H4') return '\n#### ' + childrenToMd(node).trim() + '\n';
137
- if (tag === 'STRONG' || tag === 'B') return '**' + childrenToMd(node).trim() + '**';
138
- if (tag === 'EM' || tag === 'I') return '*' + childrenToMd(node).trim() + '*';
139
69
  if (tag === 'PRE') {
140
- const codeEl = node.querySelector('code');
141
- const lang = codeEl ? (codeEl.className.match(/language-(\w+)/)?.[1] || '') : '';
142
- const code = extractCodeText(codeEl || node);
143
- return '\n```' + lang + '\n' + code.trim() + '\n```\n';
70
+ return `\n\n\0\u0006\n${extractCodeText(node).trim()}\n\0\u0006\n\n`;
144
71
  }
145
- if (tag === 'CODE') {
146
- if (node.parentElement && node.parentElement.tagName === 'PRE') return node.textContent || '';
147
- return '`' + (node.textContent || '').trim() + '`';
72
+ if (tag === 'A') {
73
+ const text = normalize(Array.from(node.childNodes).map(htmlToMd).join('')) || normalize(node.textContent);
74
+ const href = node.getAttribute('href') || '';
75
+ return href ? `[${text || href}](${href})` : text;
148
76
  }
149
- if (tag === 'BLOCKQUOTE') return '\n> ' + childrenToMd(node).trim().replace(/\n/g, '\n> ') + '\n';
150
- if (tag === 'A') return '[' + childrenToMd(node).trim() + '](' + (node.getAttribute('href') || '') + ')';
151
- if (tag === 'BR') return '\n';
152
- if (tag === 'P') return '\n' + childrenToMd(node).trim() + '\n';
153
- return childrenToMd(node);
154
- }
155
- function childrenToMd(node) {
156
- return Array.from(node.childNodes).map(htmlToMd).join('');
157
- }
158
-
159
- function getCleanMd(el) {
160
- const clone = el.cloneNode(true);
161
- clone.querySelectorAll('button, [role="button"], style, script, svg, .codicon, [class*="feedback"]').forEach(n => n.remove());
162
- clone.querySelectorAll('*').forEach(child => {
163
- if (!child.parentNode) return;
164
- const t = (child.textContent || '').trim();
165
- if (t.length > 60) return;
166
- const low = t.toLowerCase();
167
- if (/^(analyzed\s+\d|edited\s+\d|ran\s+\S|terminal\s|reading|searching)/i.test(low)) child.remove();
168
- });
169
- let md = htmlToMd(clone);
170
- md = md.replace(/\n{3,}/g, '\n\n').trim();
171
- return md;
172
- }
173
-
174
- // ─── 5. 메시지 수집 ───
175
- const collected = [];
176
- const seenHashes = new Set();
77
+ if (tag === 'TABLE') {
78
+ const rows = Array.from(node.querySelectorAll('tr')).map(tr => Array.from(tr.querySelectorAll('th,td')).map(cell => normalize(cell.textContent).replace(/\|/g, '\\|')));
79
+ if (!rows.length) return '';
80
+ const colCount = Math.max(...rows.map(row => row.length));
81
+ const header = rows[0].concat(Array(Math.max(0, colCount - rows[0].length)).fill(''));
82
+ const body = rows.slice(1).map(row => row.concat(Array(Math.max(0, colCount - row.length)).fill('')));
83
+ const divider = Array(colCount).fill('---');
84
+ return '\n' + ['| ' + header.join(' | ') + ' |', '| ' + divider.join(' | ') + ' |', ...body.map(row => '| ' + row.join(' | ') + ' |')].join('\n') + '\n';
85
+ }
86
+ const children = Array.from(node.childNodes).map(htmlToMd).join('');
87
+ if (tag === 'P') return children.trim() + '\n\n';
88
+ if (tag === 'LI') return '- ' + children.trim() + '\n';
89
+ if (tag === 'UL' || tag === 'OL') return '\n' + children + '\n';
90
+ if (/H[1-6]/.test(tag)) return '\n' + '#'.repeat(Number(tag[1])) + ' ' + normalize(node.textContent) + '\n\n';
91
+ return children;
92
+ };
93
+ const getCopyableText = (node) => {
94
+ if (!node) return '';
95
+ const nodes = [node, ...node.querySelectorAll('*')].slice(0, 80);
96
+ for (const candidate of nodes) {
97
+ const key = Object.keys(candidate).find(name => name.startsWith('__reactFiber'));
98
+ if (!key) continue;
99
+ let fiber = candidate[key];
100
+ for (let depth = 0; fiber && depth < 15; depth += 1, fiber = fiber.return) {
101
+ const props = fiber.memoizedProps || fiber.pendingProps || {};
102
+ if (typeof props.copyableText === 'string' && props.copyableText.trim()) {
103
+ return props.copyableText.trim();
104
+ }
105
+ }
106
+ }
107
+ return '';
108
+ };
109
+ const getCleanMd = (node) => htmlToMd(node)
110
+ .replace(/\u00060\u0006/g, '```')
111
+ .replace(/\n{3,}/g, '\n\n')
112
+ .replace(/[ \t]+\n/g, '\n')
113
+ .trim();
114
+
115
+ const collectApprovalActions = () => {
116
+ const dialogs = [document.querySelector('.monaco-dialog-box, [role="dialog"]'), cascade].filter(Boolean);
117
+ const actionNames = [];
118
+ for (const root of dialogs) {
119
+ const buttons = Array.from(root.querySelectorAll('button, [role="button"], .monaco-button')).filter(isVisible);
120
+ for (const button of buttons) {
121
+ const label = normalize(button.innerText || button.textContent || button.getAttribute('aria-label'));
122
+ if (!label || label.length > 40) continue;
123
+ if (!/^(run|skip|accept|reject|approve|allow|deny)\b/i.test(label)) continue;
124
+ if (!actionNames.some(existing => existing.toLowerCase() === label.toLowerCase())) {
125
+ actionNames.push(label);
126
+ }
127
+ }
128
+ }
129
+ return actionNames;
130
+ };
177
131
 
178
- const scrollArea = cascade.querySelector('.cascade-scrollbar');
179
- if (!scrollArea) {
180
- return { id: cascadeId, status, title, messages: [], inputContent: '', activeModal: null };
181
- }
132
+ const approvalActions = collectApprovalActions();
133
+ let status = 'idle';
134
+ let activeModal = null;
182
135
 
183
- // 메시지 리스트 컨테이너 찾기: .gap-2.5 자식 2개 이상
184
- let msgContainer = null;
185
- const gapEls = scrollArea.querySelectorAll('[class*="gap-2"]');
186
- for (const el of gapEls) {
187
- if (el.children.length >= 2 && el.closest('.cascade-scrollbar')) {
188
- msgContainer = el; break;
136
+ if (approvalActions.length > 0) {
137
+ status = 'waiting_approval';
138
+ activeModal = {
139
+ message: normalize(cascade.querySelector('button')?.closest('[class*="terminal"],[class*="shadow-step"],.monaco-dialog-box,[role="dialog"]')?.innerText || 'Approval required'),
140
+ actions: approvalActions,
141
+ };
142
+ } else {
143
+ const stopButton = Array.from(cascade.querySelectorAll('button, [role="button"]')).find(el => {
144
+ if (!isVisible(el)) return false;
145
+ const text = normalize(el.innerText || el.textContent || el.getAttribute('aria-label'));
146
+ return /^(stop|cancel generation|stop generating)\b/i.test(text);
147
+ });
148
+ const spinnerSignal = Array.from(cascade.querySelectorAll('*')).some(el => {
149
+ if (!isVisible(el)) return false;
150
+ const cls = String(el.className || '');
151
+ return /animate-spin|animate-pulse|animate-bounce|loading-spinner|lucide-loader|stream/i.test(cls);
152
+ });
153
+ const stateLabelSignal = Array.from(cascade.querySelectorAll('*')).some(el => {
154
+ if (!isVisible(el)) return false;
155
+ if (el.children.length > 2) return false;
156
+ if (el.closest('[class*="popover"], [class*="tooltip"], [data-radix-popper-content-wrapper]')) return false;
157
+ const text = normalize(el.innerText || el.textContent || el.getAttribute('aria-label'));
158
+ return text.length > 0 && text.length <= 30 && /^(thinking|generating|sailing|loading)\b/i.test(text);
159
+ });
160
+ if (fiberRunning || stopButton || spinnerSignal || stateLabelSignal) {
161
+ status = 'generating';
189
162
  }
190
163
  }
191
164
 
192
- if (msgContainer) {
193
- const turns = Array.from(msgContainer.children);
194
- for (const turn of turns) {
195
- // .mark-js-ignore = 피드백 UI → 무시
196
- if ((turn.className || '').includes('mark-js-ignore')) continue;
197
- if (turn.offsetHeight < 10) continue;
198
-
199
- // 역할 판별: .prose = AI, 아니면 user
200
- const proseEl = turn.querySelector('[class*="prose"]');
201
- const role = proseEl ? 'assistant' : 'user';
202
-
203
- let text = '';
204
- if (role === 'assistant') {
205
- // AI: Fiber copyableText가 있으면 사용 (이미 마크다운!)
206
- try {
207
- const fk = Object.keys(turn).find(k => k.startsWith('__reactFiber'));
208
- if (fk) {
209
- let fiber = turn[fk];
210
- for (let d = 0; d < 20 && fiber; d++) {
211
- const p = fiber.memoizedProps || {};
212
- if (p.copyableText && typeof p.copyableText === 'string' && p.copyableText.length > 5) {
213
- text = p.copyableText;
214
- break;
215
- }
216
- fiber = fiber.return;
217
- }
218
- }
219
- } catch (_) { }
165
+ const scroll = cascade.querySelector('.cascade-scrollbar');
166
+ const candidates = scroll
167
+ ? Array.from(scroll.querySelectorAll('div')).filter(el => String(el.className || '').includes('flex flex-col gap-2.5') && el.children.length > 0)
168
+ : [];
169
+ const listRoot = candidates.sort((left, right) => right.children.length - left.children.length)[0] || null;
170
+
171
+ const messages = [];
172
+ const seen = new Set();
173
+ if (listRoot) {
174
+ for (const child of Array.from(listRoot.children)) {
175
+ if (!isVisible(child)) continue;
176
+ const rawText = normalize(child.innerText || child.textContent);
177
+ if (!rawText) continue;
178
+ if ((child.className || '').includes('mark-js-ignore') || /feedback submitted/i.test(rawText)) continue;
179
+
180
+ readFiberValue(child);
181
+
182
+ const isUser = !!child.querySelector('.justify-end');
183
+ let kind = 'standard';
184
+ if (/Command\b/i.test(rawText) || child.querySelector('.terminal-text, [class*="bg-ide-terminal-background"]')) {
185
+ kind = 'terminal';
186
+ } else if (/tasks done|Created Todo List|\bnew\b \+\d+|shadow-step/i.test(rawText + ' ' + (child.innerHTML || ''))) {
187
+ kind = 'tool';
188
+ } else if (!isUser && /step-by-step reasoning|reasoning process|thought process/i.test(rawText)) {
189
+ kind = 'thought';
190
+ }
220
191
 
221
- // Fiber에 없으면 HTML→Markdown
222
- if (!text) {
223
- const mdRoot = proseEl || turn;
224
- text = getCleanMd(mdRoot);
225
- }
192
+ let content = '';
193
+ if (isUser) {
194
+ const bubble = child.querySelector('[class*="whitespace-pre-wrap"]') || child;
195
+ content = normalize(bubble.innerText || bubble.textContent);
196
+ } else if (kind === 'terminal') {
197
+ const label = normalize(child.querySelector('.codeium-text-medium')?.textContent || 'Command');
198
+ const command = normalize(child.querySelector('.terminal-text,.whitespace-pre-wrap,.monaco-tokenized-source')?.textContent || rawText)
199
+ .replace(/\bRun\b.*$/i, '')
200
+ .replace(/\bSkip\b.*$/i, '')
201
+ .trim();
202
+ content = `${label}\n\n\0\u0006sh\n${command}\n\0\u0006`.replace(/\u00060\u0006/g, '```');
203
+ } else if (kind === 'tool') {
204
+ content = cleanText(rawText);
226
205
  } else {
227
- // 사용자: whitespace-pre-wrap 요소에서 텍스트 추출
228
- const whitespace = turn.querySelector('[class*="whitespace"]');
229
- text = (whitespace || turn).innerText?.trim() || '';
206
+ const prose = child.querySelector('.prose');
207
+ content = getCopyableText(child) || getCleanMd(prose || child) || cleanText(rawText);
230
208
  }
231
209
 
232
- if (!text || text.length < 1) continue;
233
-
234
- const hash = role + ':' + text.slice(0, 200);
235
- if (seenHashes.has(hash)) continue;
236
- seenHashes.add(hash);
237
- collected.push({ role, text, el: turn });
210
+ content = content.replace(/\n{3,}/g, '\n\n').trim();
211
+ if (!content) continue;
212
+
213
+ const hash = `${isUser ? 'user' : 'assistant'}:${kind}:${content.slice(0, 240)}`;
214
+ if (seen.has(hash)) continue;
215
+ seen.add(hash);
216
+
217
+ const message = {
218
+ role: isUser ? 'user' : 'assistant',
219
+ content: content.length > 12000 ? `${content.slice(0, 12000)}\n[... truncated]` : content,
220
+ index: messages.length,
221
+ };
222
+ if (kind !== 'standard') message.kind = kind;
223
+ if (kind === 'terminal') {
224
+ message.meta = {
225
+ label: normalize(child.querySelector('.codeium-text-medium')?.textContent || 'Command'),
226
+ isRunning: status === 'generating' && /command/i.test(content),
227
+ };
228
+ }
229
+ messages.push(message);
238
230
  }
239
231
  }
240
232
 
241
- // DOM 순서 정렬
242
- collected.sort((a, b) => {
243
- const pos = a.el.compareDocumentPosition(b.el);
244
- if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
245
- if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
246
- return 0;
247
- });
248
-
249
- // 최신 30개만
250
- const trimmed = collected.length > 30 ? collected.slice(-30) : collected;
251
-
252
- const final = trimmed.map((m, i) => ({
253
- id: 'msg_' + i,
254
- role: m.role,
255
- content: m.text.length > 6000 ? m.text.slice(0, 6000) + '\n[... truncated]' : m.text,
256
- index: i,
257
- kind: 'standard'
258
- }));
259
-
260
- // ─── 6. 입력창 ───
261
233
  const editor = cascade.querySelector('[data-lexical-editor="true"]')
262
234
  || cascade.querySelector('[contenteditable="true"][role="textbox"]')
263
235
  || cascade.querySelector('textarea:not(.xterm-helper-textarea)');
264
- const inputContent = editor ? (editor.innerText || editor.value || '').trim() : '';
265
-
266
- // ─── 7. 모달/승인 감지 ───
267
- let activeModal = null;
268
- try {
269
- // Fiber: hasPendingTerminalCommand
270
- if (hasPendingCmd) {
271
- const allBtns = Array.from(document.querySelectorAll('button')).filter(b => b.offsetWidth > 0);
272
- const approvalBtns = allBtns.filter(b => {
273
- const t = (b.textContent || '').trim().toLowerCase();
274
- return /^(run|reject|skip|approve|allow|deny|cancel|accept)\b/i.test(t);
275
- });
276
- const btnTexts = [...new Set(approvalBtns.map(b => (b.textContent || '').trim()).filter(t => t.length < 40))];
277
- activeModal = { message: 'Terminal command pending', buttons: btnTexts.length > 0 ? btnTexts : ['Run', 'Reject'] };
278
- }
236
+ const inputContent = normalize(editor?.innerText || editor?.value || '');
279
237
 
280
- // Dialog 폴백
281
- if (!activeModal) {
282
- const dialog = document.querySelector('.monaco-dialog-box, [role="dialog"]');
283
- if (dialog && dialog.offsetWidth > 80) {
284
- const msg = (dialog.querySelector('.dialog-message') || dialog).innerText?.trim() || '';
285
- const buttons = Array.from(dialog.querySelectorAll('.monaco-button, button'))
286
- .map(b => (b.innerText || '').trim()).filter(t => t.length > 0 && t.length < 30);
287
- if (msg || buttons.length > 0) {
288
- activeModal = { message: msg.slice(0, 300), buttons };
289
- }
290
- }
291
- }
292
-
293
- // 인라인 승인 버튼
294
- if (!activeModal) {
295
- const allBtns = Array.from(document.querySelectorAll('button')).filter(b => b.offsetWidth > 0);
296
- const approvalBtns = allBtns.filter(b => {
297
- const t = (b.textContent || '').trim().toLowerCase();
298
- if (t.length > 40) return false;
299
- return /^(run|reject|skip|approve|allow|deny)\b/i.test(t)
300
- || t === 'always allow' || t === 'always deny';
301
- });
302
- if (approvalBtns.length >= 2) {
303
- const btnTexts = [...new Set(approvalBtns.map(b => (b.textContent || '').trim()))];
304
- activeModal = { message: '', buttons: btnTexts };
305
- }
306
- }
307
-
308
- if (activeModal) status = 'waiting_approval';
309
- } catch (_) { activeModal = null; }
238
+ if (status === 'idle' && fiberPendingCommand && approvalActions.length > 0) {
239
+ status = 'waiting_approval';
240
+ }
310
241
 
311
- return { id: cascadeId, status, title, messages: final, inputContent, activeModal };
242
+ return JSON.stringify({
243
+ id: cascadeId,
244
+ status,
245
+ title,
246
+ messages,
247
+ inputContent,
248
+ activeModal,
249
+ });
312
250
  } catch (e) {
313
- return { id: 'error', status: 'error', error: e.message, messages: [] };
251
+ return JSON.stringify({ id: 'error', status: 'idle', title: 'Cascade', messages: [], inputContent: '', activeModal: null, error: e.message });
314
252
  }
315
253
  })()