social-agent-cli 2.0.0 → 2.1.1
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/ai/runner.ts +23 -3
- package/core/page-watcher.ts +172 -0
- package/package.json +1 -1
package/ai/runner.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { loadMap } from "./mapper.js";
|
|
|
3
3
|
import { createPlan, type ExecutionPlan, type PlanStep } from "./planner.js";
|
|
4
4
|
import { healAndContinue } from "./mapper.js";
|
|
5
5
|
import { saveToHistory } from "../core/history.js";
|
|
6
|
+
import { injectWatcher, hasUnexpectedChange } from "../core/page-watcher.js";
|
|
6
7
|
import type { Post, PostResult } from "../core/types.js";
|
|
7
8
|
import { join } from "node:path";
|
|
8
9
|
import { PATHS } from "../core/config.js";
|
|
@@ -82,9 +83,10 @@ export async function runCommand(
|
|
|
82
83
|
await page.waitForTimeout(3000);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
// Periferik görüş - MutationObserver enjekte et
|
|
87
|
+
await injectWatcher(page);
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
let steps = [...map.steps];
|
|
88
90
|
let stepIdx = 0;
|
|
89
91
|
let healAttempt = 0;
|
|
90
92
|
|
|
@@ -114,7 +116,25 @@ export async function runCommand(
|
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
stepIdx++;
|
|
117
|
-
healAttempt = 0;
|
|
119
|
+
healAttempt = 0;
|
|
120
|
+
|
|
121
|
+
// Aksiyon-algı döngüsü: step sonrası beklenmeyen değişiklik var mı?
|
|
122
|
+
await page.waitForTimeout(300); // kısa bekleme - DOM güncellensin
|
|
123
|
+
const change = await hasUnexpectedChange(page);
|
|
124
|
+
if (change.changed) {
|
|
125
|
+
console.log(` ⚡ ${change.description}`);
|
|
126
|
+
// Beklenmeyen element çıktı - AI'a sor ne yapmalı
|
|
127
|
+
const healScreenshot = join(PATHS.screenshots, `${platform}_unexpected.png`);
|
|
128
|
+
await page.screenshot({ path: healScreenshot });
|
|
129
|
+
const ariaSnap = await page.locator('body').ariaSnapshot();
|
|
130
|
+
|
|
131
|
+
const newSteps = await healAndContinue(
|
|
132
|
+
platform, stepIdx, steps, healScreenshot, ariaSnap,
|
|
133
|
+
`Beklenmeyen değişiklik: ${change.description}. Bu element'i handle edip kalan adımlara devam et.`,
|
|
134
|
+
post.content || step.description, "low"
|
|
135
|
+
);
|
|
136
|
+
steps = [...steps.slice(0, stepIdx), ...newSteps];
|
|
137
|
+
}
|
|
118
138
|
} catch (err: any) {
|
|
119
139
|
healAttempt++;
|
|
120
140
|
const efforts = ["low", "medium", "medium"];
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Watcher - İnsan beyni modeliyle sayfa izleme
|
|
3
|
+
*
|
|
4
|
+
* Beyin modeli:
|
|
5
|
+
* 1. Periferik görüş → MutationObserver ile DOM değişikliklerini sürekli izle
|
|
6
|
+
* 2. Değişiklik algılama → Yeni dialog/modal/overlay çıktığında algıla
|
|
7
|
+
* 3. Dikkat yönlendirme → Beklenmeyen element → otomatik odaklan
|
|
8
|
+
* 4. Aksiyon-algı döngüsü → Her step sonrası durumu doğrula
|
|
9
|
+
* 5. Tahmin → Beklenen sonuçla gerçeği karşılaştır
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Page } from "playwright";
|
|
13
|
+
|
|
14
|
+
export interface PageState {
|
|
15
|
+
hasNewDialog: boolean;
|
|
16
|
+
hasNewOverlay: boolean;
|
|
17
|
+
hasError: boolean;
|
|
18
|
+
newElements: string[]; // yeni eklenen önemli elementlerin açıklamaları
|
|
19
|
+
ariaSnapshot: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sayfaya MutationObserver enjekte et - periferik görüş
|
|
24
|
+
* DOM'a eklenen dialog, modal, overlay, alert gibi elementleri yakalar
|
|
25
|
+
*/
|
|
26
|
+
export async function injectWatcher(page: Page): Promise<void> {
|
|
27
|
+
await page.evaluate(`(() => {
|
|
28
|
+
if (window.__socialAgentWatcher) return;
|
|
29
|
+
window.__socialAgentWatcher = {
|
|
30
|
+
changes: [],
|
|
31
|
+
newDialogs: [],
|
|
32
|
+
newOverlays: [],
|
|
33
|
+
errors: [],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const watcher = window.__socialAgentWatcher;
|
|
37
|
+
|
|
38
|
+
const observer = new MutationObserver((mutations) => {
|
|
39
|
+
for (const mutation of mutations) {
|
|
40
|
+
for (const node of mutation.addedNodes) {
|
|
41
|
+
if (!(node instanceof HTMLElement)) continue;
|
|
42
|
+
|
|
43
|
+
const role = node.getAttribute('role');
|
|
44
|
+
const ariaModal = node.getAttribute('aria-modal');
|
|
45
|
+
const tag = node.tagName.toLowerCase();
|
|
46
|
+
|
|
47
|
+
// Dialog/Modal algılama - standart + heuristik
|
|
48
|
+
const isDialog = role === 'dialog' || role === 'alertdialog' || ariaModal === 'true' || tag === 'dialog';
|
|
49
|
+
const style = window.getComputedStyle(node);
|
|
50
|
+
const isFloating = (style.position === 'fixed' || style.position === 'absolute') && style.zIndex && parseInt(style.zIndex) > 50;
|
|
51
|
+
const cls = (node.className?.toString?.() || '').toLowerCase();
|
|
52
|
+
const looksLikeModal = cls.match(/modal|dialog|popup|overlay|drawer|sheet|toast|snackbar|dropdown|popover/);
|
|
53
|
+
|
|
54
|
+
if (isDialog || (isFloating && looksLikeModal)) {
|
|
55
|
+
watcher.newDialogs.push({
|
|
56
|
+
role: role || tag,
|
|
57
|
+
ariaLabel: node.getAttribute('aria-label') || '',
|
|
58
|
+
text: node.textContent?.substring(0, 200) || '',
|
|
59
|
+
className: cls.substring(0, 100),
|
|
60
|
+
time: Date.now()
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Overlay/backdrop algılama
|
|
65
|
+
if (isFloating && !isDialog && !looksLikeModal) {
|
|
66
|
+
const area = node.offsetWidth * node.offsetHeight;
|
|
67
|
+
const screenArea = window.innerWidth * window.innerHeight;
|
|
68
|
+
// Ekranın %30'undan büyük fixed element = muhtemelen overlay
|
|
69
|
+
if (area > screenArea * 0.3) {
|
|
70
|
+
watcher.newOverlays.push({
|
|
71
|
+
tag: tag,
|
|
72
|
+
class: cls.substring(0, 100),
|
|
73
|
+
time: Date.now()
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Error mesajı algılama - standart + heuristik
|
|
79
|
+
const isError = role === 'alert' || node.getAttribute('aria-live') === 'assertive';
|
|
80
|
+
const looksLikeError = cls.match(/error|warning|alert|danger|notification|banner/);
|
|
81
|
+
if (isError || looksLikeError) {
|
|
82
|
+
watcher.errors.push({
|
|
83
|
+
text: node.textContent?.substring(0, 300) || '',
|
|
84
|
+
time: Date.now()
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
observer.observe(document.body, {
|
|
92
|
+
childList: true,
|
|
93
|
+
subtree: true,
|
|
94
|
+
});
|
|
95
|
+
})()`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Sayfanın mevcut durumunu oku - değişiklik algılama
|
|
100
|
+
*/
|
|
101
|
+
export async function getPageState(page: Page): Promise<PageState> {
|
|
102
|
+
const watcherData = await page.evaluate(`(() => {
|
|
103
|
+
const w = window.__socialAgentWatcher;
|
|
104
|
+
if (!w) return { newDialogs: [], newOverlays: [], errors: [] };
|
|
105
|
+
const data = {
|
|
106
|
+
newDialogs: [...w.newDialogs],
|
|
107
|
+
newOverlays: [...w.newOverlays],
|
|
108
|
+
errors: [...w.errors],
|
|
109
|
+
};
|
|
110
|
+
// Reset - bir kere okuduktan sonra temizle
|
|
111
|
+
w.newDialogs = [];
|
|
112
|
+
w.newOverlays = [];
|
|
113
|
+
w.errors = [];
|
|
114
|
+
return data;
|
|
115
|
+
})()`).catch(() => ({ newDialogs: [], newOverlays: [], errors: [] })) as any;
|
|
116
|
+
|
|
117
|
+
const newElements: string[] = [];
|
|
118
|
+
|
|
119
|
+
for (const d of watcherData.newDialogs) {
|
|
120
|
+
newElements.push(`[dialog] ${d.ariaLabel || d.text.substring(0, 50)}`);
|
|
121
|
+
}
|
|
122
|
+
for (const o of watcherData.newOverlays) {
|
|
123
|
+
newElements.push(`[overlay] ${o.tag}.${o.class.substring(0, 30)}`);
|
|
124
|
+
}
|
|
125
|
+
for (const e of watcherData.errors) {
|
|
126
|
+
newElements.push(`[error] ${e.text.substring(0, 50)}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let ariaSnapshot = "";
|
|
130
|
+
try {
|
|
131
|
+
ariaSnapshot = await page.locator('body').ariaSnapshot();
|
|
132
|
+
} catch {}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
hasNewDialog: watcherData.newDialogs.length > 0,
|
|
136
|
+
hasNewOverlay: watcherData.newOverlays.length > 0,
|
|
137
|
+
hasError: watcherData.errors.length > 0,
|
|
138
|
+
newElements,
|
|
139
|
+
ariaSnapshot,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Step sonrası durumu doğrula - aksiyon-algı döngüsü
|
|
145
|
+
* Beklenmeyen element varsa true döner
|
|
146
|
+
*/
|
|
147
|
+
export async function hasUnexpectedChange(page: Page): Promise<{ changed: boolean; description: string }> {
|
|
148
|
+
const state = await getPageState(page);
|
|
149
|
+
|
|
150
|
+
if (state.hasNewDialog) {
|
|
151
|
+
return {
|
|
152
|
+
changed: true,
|
|
153
|
+
description: `Yeni dialog açıldı: ${state.newElements.filter(e => e.startsWith('[dialog]')).join(', ')}`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (state.hasError) {
|
|
158
|
+
return {
|
|
159
|
+
changed: true,
|
|
160
|
+
description: `Hata mesajı: ${state.newElements.filter(e => e.startsWith('[error]')).join(', ')}`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (state.hasNewOverlay) {
|
|
165
|
+
return {
|
|
166
|
+
changed: true,
|
|
167
|
+
description: `Yeni overlay: ${state.newElements.filter(e => e.startsWith('[overlay]')).join(', ')}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { changed: false, description: "" };
|
|
172
|
+
}
|