social-agent-cli 2.2.3 → 2.3.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/mapper.ts CHANGED
@@ -350,30 +350,39 @@ GÖREV: Ekranın mevcut durumunu analiz et. Amaca ulaşmak için kalan adımlar
350
350
 
351
351
  console.log(`\n[heal] AI ekranı analiz ediyor (effort: ${effort})...\n`);
352
352
 
353
- const result = await claude({
354
- prompt,
355
- jsonSchema: REMAINING_STEPS_SCHEMA,
356
- imagePath: screenshotPath,
357
- stream: false,
358
- effort,
359
- });
353
+ let result: any;
354
+ try {
355
+ result = await claude({
356
+ prompt,
357
+ jsonSchema: REMAINING_STEPS_SCHEMA,
358
+ imagePath: screenshotPath,
359
+ stream: false,
360
+ effort,
361
+ });
362
+ } catch (err: any) {
363
+ console.log(`\n[heal] AI hatası: ${err.message}`);
364
+ return originalSteps.slice(failedStepIndex + 1); // başarısız step'i atla
365
+ }
360
366
 
361
- if (!result || !result.remainingSteps) {
362
- console.log(`\n[heal] AI geçerli bir plan döndüremedi, orijinal adımlarla devam ediliyor.`);
363
- return originalSteps.slice(failedStepIndex);
367
+ // result çeşitli formatlarda gelebilir
368
+ if (!result) {
369
+ return originalSteps.slice(failedStepIndex + 1);
364
370
  }
365
371
 
366
- console.log(`\n[heal] Analiz: ${result.analysis || "?"}`);
367
- console.log(`[heal] ${result.remainingSteps.length} yeni adım planlandı`);
372
+ // structured_output içinde olabilir
373
+ if (result.structured_output?.remainingSteps) {
374
+ result = result.structured_output;
375
+ }
368
376
 
369
- // Map'i güncelle: tamamlanan adımlar + yeni kalan adımlar
370
- const currentMap = loadMap(platform);
371
- if (currentMap) {
372
- currentMap.steps = [...completedSteps, ...result.remainingSteps];
373
- currentMap.version++;
374
- currentMap.lastUpdated = new Date().toISOString();
375
- saveMap(currentMap);
377
+ // remainingSteps yoksa steps dene
378
+ const steps = result.remainingSteps || result.steps || result;
379
+
380
+ if (!Array.isArray(steps)) {
381
+ console.log(`\n[heal] Plan parse edilemedi, başarısız adım atlanıyor.`);
382
+ return originalSteps.slice(failedStepIndex + 1);
376
383
  }
377
384
 
378
- return result.remainingSteps;
385
+ console.log(`\n[heal] ${steps.length} yeni adım`);
386
+
387
+ return steps;
379
388
  }
package/ai/runner.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  import { BrowserDriver } from "../platforms/browser/driver.js";
2
- import { loadMap } from "./mapper.js";
3
- import { createPlan, type ExecutionPlan, type PlanStep } from "./planner.js";
4
- import { healAndContinue } from "./mapper.js";
2
+ import { loadMap, healAndContinue } from "./mapper.js";
3
+ import { createPlan } from "./planner.js";
5
4
  import { saveToHistory } from "../core/history.js";
6
- import { injectWatcher, hasUnexpectedChange } from "../core/page-watcher.js";
7
- import { tryReflex } from "../core/reflexes.js";
8
- import type { Post, PostResult } from "../core/types.js";
5
+ import type { Post } from "../core/types.js";
9
6
  import { join } from "node:path";
10
7
  import { PATHS } from "../core/config.js";
11
8
 
@@ -14,62 +11,35 @@ const BROWSER_PLATFORMS: Record<string, string> = {
14
11
  linkedin: "https://www.linkedin.com",
15
12
  };
16
13
 
17
- /**
18
- * Doğal dil komutunu planla ve çalıştır.
19
- */
20
- export async function runCommand(
21
- platform: string,
22
- command: string,
23
- autoConfirm = false
24
- ): Promise<void> {
14
+ export async function runCommand(platform: string, command: string): Promise<void> {
25
15
  const url = BROWSER_PLATFORMS[platform];
26
- if (!url) {
27
- console.log(`"${platform}" browser platformu değil.`);
28
- return;
29
- }
16
+ if (!url) { console.log(`"${platform}" desteklenmiyor.`); return; }
30
17
 
31
- // 1. Plan oluştur
32
18
  const plan = await createPlan(platform, command);
33
-
34
- // 2. Onay al
35
- // Onay sormadan çalıştır - Claude Code zaten kullanıcıdan onay almıştır
36
- console.log("");
37
-
38
- // 3. Adımları çalıştır
39
- console.log("\n[runner] Plan çalıştırılıyor...\n");
19
+ console.log("\n[runner] Çalıştırılıyor...\n");
40
20
 
41
21
  for (const [i, step] of plan.steps.entries()) {
42
- console.log(`━━━ Adım ${i + 1}/${plan.steps.length}: ${step.description} ━━━`);
22
+ console.log(`━━━ ${i + 1}/${plan.steps.length}: ${step.description} ━━━`);
43
23
 
44
24
  if (step.type === "learn_new") {
45
- console.log(`[runner] Yeni aksiyon öğreniliyor: "${step.learnDescription}"`);
46
25
  const driver = new BrowserDriver(platform, url);
47
- await driver.learn(
48
- step.learnDescription || step.description,
49
- step.learnActionName || "custom"
50
- );
26
+ await driver.learn(step.learnDescription || step.description, step.learnActionName || "custom");
51
27
  console.log(`[runner] Öğrenildi!\n`);
52
28
  }
53
29
 
54
- const mapAction = step.type === "use_map"
55
- ? step.mapAction!
56
- : step.learnActionName || "custom";
57
-
30
+ const mapAction = step.type === "use_map" ? step.mapAction! : step.learnActionName || "custom";
58
31
  const map = loadMap(platform, mapAction);
59
- if (!map) {
60
- console.log(`[runner] HATA: "${mapAction}" map'i bulunamadı, atlanıyor.`);
61
- continue;
62
- }
32
+ if (!map) { console.log(`"${mapAction}" bulunamadı, atlanıyor.`); continue; }
63
33
 
64
34
  const repeatCount = step.repeat || 1;
65
35
 
66
36
  for (let r = 0; r < repeatCount; r++) {
67
37
  if (repeatCount > 1) console.log(` [${r + 1}/${repeatCount}]`);
68
38
 
69
- const imageParam = step.parameters["{{IMAGE}}"] || step.parameters["IMAGE"] || "";
39
+ const params = { ...step.parameters };
70
40
  const post: Post = {
71
- content: step.parameters["{{CONTENT}}"] || step.parameters["CONTENT"] || "",
72
- images: imageParam ? [imageParam] : undefined,
41
+ content: params["{{CONTENT}}"] || params["CONTENT"] || "",
42
+ images: (params["{{IMAGE}}"] || params["IMAGE"]) ? [params["{{IMAGE}}"] || params["IMAGE"]] : undefined,
73
43
  platform,
74
44
  };
75
45
 
@@ -77,304 +47,158 @@ export async function runCommand(
77
47
  const page = await driver.launch();
78
48
 
79
49
  try {
80
- // Map'in ilk adımı goto değilse platform ana sayfasına git
81
50
  if (map.steps[0]?.action !== "goto") {
82
- console.log(` [${platform}] ${url} açılıyor...`);
83
51
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
84
52
  await page.waitForTimeout(3000);
85
53
  }
86
54
 
87
- // Periferik görüş - MutationObserver enjekte et
88
- await injectWatcher(page);
89
-
90
55
  let steps = [...map.steps];
91
- let stepIdx = 0;
92
- let healAttempt = 0;
56
+ let healCount = 0;
93
57
 
94
- while (stepIdx < steps.length) {
95
- const mapStep = steps[stepIdx];
96
-
97
- // Placeholder'ları doldur - boş kalanları akıllıca handle et
98
- const resolvedParams = resolveParams(step.parameters, r);
58
+ for (let idx = 0; idx < steps.length; idx++) {
59
+ const s = steps[idx];
60
+ const rp = (t: string) => replacePlaceholders(t, post, params);
99
61
 
100
62
  try {
101
- if (mapStep.action === "goto") {
102
- let gotoUrl = mapStep.url || url;
103
- gotoUrl = replacePlaceholders(gotoUrl, post, resolvedParams);
63
+ console.log(` ${idx + 1}. ${s.description}`);
104
64
 
105
- // Eğer hâlâ {{...}} placeholder varsa, bu adımı atla ve ana sayfaya git
106
- if (gotoUrl.includes("{{")) {
107
- console.log(` Step ${stepIdx + 1}: ${mapStep.description} (ana sayfa kullanılıyor)`);
108
- await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
109
- } else {
110
- console.log(` Step ${stepIdx + 1}: ${mapStep.description}`);
111
- await page.goto(gotoUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
112
- }
113
- if (mapStep.waitMs) await page.waitForTimeout(mapStep.waitMs);
65
+ if (s.action === "goto") {
66
+ let gotoUrl = rp(s.url || url);
67
+ if (gotoUrl.includes("{{")) gotoUrl = url;
68
+ await page.goto(gotoUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
114
69
  } else {
115
- console.log(` Step ${stepIdx + 1}: ${mapStep.description}`);
116
- await executeMapStep(page, mapStep, post, resolvedParams);
117
- }
118
-
119
- stepIdx++;
120
- healAttempt = 0;
121
-
122
- // Aksiyon-algı döngüsü: step sonrası beklenmeyen değişiklik var mı?
123
- await page.waitForTimeout(300);
124
- const change = await hasUnexpectedChange(page);
125
- if (change.changed) {
126
- console.log(` ⚡ ${change.description}`);
127
-
128
- // Sistem 1: Refleks - hızlı, otomatik
129
- const reflex = await tryReflex(page, change.description);
130
- if (reflex.handled) {
131
- console.log(` ↩ Refleks: ${reflex.action}`);
132
- } else {
133
- // Sistem 2: AI - yavaş, bilinçli (sadece refleks çözemediğinde)
134
- console.log(` 🧠 Refleks çözemedi, AI devreye giriyor...`);
135
- const healScreenshot = join(PATHS.screenshots, `${platform}_unexpected.png`);
136
- await page.screenshot({ path: healScreenshot });
137
- const ariaSnap = await page.locator('body').ariaSnapshot();
138
-
139
- const newSteps = await healAndContinue(
140
- platform, stepIdx, steps, healScreenshot, ariaSnap,
141
- `Beklenmeyen değişiklik: ${change.description}. Bu element'i handle edip kalan adımlara devam et.`,
142
- post.content || step.description, "low"
143
- );
144
- steps = [...steps.slice(0, stepIdx), ...newSteps];
145
- }
70
+ await executeStep(page, s, post, params);
146
71
  }
72
+ if (s.waitMs) await page.waitForTimeout(s.waitMs);
147
73
  } catch (err: any) {
148
- console.log(` ✗ ${err.message}`);
149
-
150
- // Sistem 1: Refleks - hata sonrası otomatik düzeltme
151
- const reflex = await tryReflex(page, err.message);
152
- if (reflex.handled) {
153
- console.log(` ↩ Refleks: ${reflex.action}`);
154
- continue; // aynı step'i tekrar dene
74
+ if (healCount >= 2) {
75
+ console.log(` ✗ ${err.message} (atlanıyor)`);
76
+ continue;
155
77
  }
78
+ healCount++;
79
+ console.log(` ✗ ${err.message}`);
80
+ console.log(` → Düzeltiliyor...`);
156
81
 
157
- // Sistem 2: AI
158
- healAttempt++;
159
- const efforts = ["low", "medium", "medium"];
160
- const effort = efforts[Math.min(healAttempt - 1, efforts.length - 1)];
161
-
162
- console.log(` 🧠 AI düzeltiyor (deneme ${healAttempt}/3)...`);
163
-
164
- if (healAttempt > 3) {
165
- console.log(` 3 denemede çözülemedi, atlanıyor.`);
166
- break;
167
- }
168
-
169
- const healScreenshot = join(PATHS.screenshots, `${platform}_heal_${stepIdx}.png`);
170
- await page.screenshot({ path: healScreenshot });
171
-
172
- const ariaSnap = await page.locator('body').ariaSnapshot();
82
+ const ss = join(PATHS.screenshots, `${platform}_heal.png`);
83
+ await page.screenshot({ path: ss }).catch(() => {});
84
+ const aria = await page.locator("body").ariaSnapshot().catch(() => "");
173
85
 
174
- // AI kaldığı yerden devam etsin (her denemede effort artar)
175
- const newSteps = await healAndContinue(
176
- platform,
177
- stepIdx,
178
- steps,
179
- healScreenshot,
180
- ariaSnap,
181
- err.message,
182
- post.content || step.description,
183
- effort
86
+ const fixed = await healAndContinue(
87
+ platform, idx, steps, ss, aria as string,
88
+ err.message, post.content || step.description, "medium"
184
89
  );
185
-
186
- steps = [...steps.slice(0, stepIdx), ...newSteps];
187
- // stepIdx artırma - yeni planı deneyecek
90
+ steps = [...steps.slice(0, idx), ...fixed];
91
+ idx--;
188
92
  }
189
93
  }
190
94
 
191
95
  console.log(` ✓ Tamamlandı`);
192
- saveToHistory({
193
- platform,
194
- content: post.content || step.description,
195
- action: mapAction.includes("post") ? "post" : mapAction,
196
- success: true,
197
- });
96
+ saveToHistory({ platform, content: post.content || step.description, action: mapAction.includes("post") ? "post" : mapAction, success: true });
198
97
  } catch (err: any) {
199
- console.log(` ✗ Hata: ${err.message}`);
200
- saveToHistory({
201
- platform,
202
- content: post.content || step.description,
203
- action: mapAction.includes("post") ? "post" : mapAction,
204
- success: false,
205
- error: err.message,
206
- });
98
+ console.log(` ✗ ${err.message}`);
99
+ saveToHistory({ platform, content: post.content || step.description, action: mapAction, success: false, error: err.message });
207
100
  } finally {
208
101
  await driver.close();
209
102
  }
210
103
 
211
- // Tekrarlar arası rastgele bekleme
212
104
  if (r < repeatCount - 1) {
213
105
  const delay = 2000 + Math.random() * 3000;
214
- console.log(` (${(delay / 1000).toFixed(1)}s bekleniyor...)`);
215
- await new Promise((resolve) => setTimeout(resolve, delay));
106
+ await new Promise((res) => setTimeout(res, delay));
216
107
  }
217
108
  }
218
-
219
109
  console.log("");
220
110
  }
221
111
 
222
- console.log("[runner] Plan tamamlandı.");
112
+ console.log("[runner] Tamamlandı.");
223
113
  process.exit(0);
224
114
  }
225
115
 
226
- /**
227
- * Parametreleri çözümle - boş olanları repeat index'e göre doldur
228
- */
229
- function resolveParams(params: Record<string, string>, repeatIndex: number): Record<string, string> {
230
- const resolved: Record<string, string> = {};
231
- for (const [key, val] of Object.entries(params)) {
232
- resolved[key] = val || "";
233
- }
234
- // Repeat index'i ekle (nth-child selector'ları için)
235
- resolved["{{INDEX}}"] = String(repeatIndex + 1);
236
- resolved["INDEX"] = String(repeatIndex + 1);
237
- return resolved;
238
- }
239
-
240
- /**
241
- * Placeholder'ları değiştir
242
- */
243
116
  function replacePlaceholders(text: string, post: Post, params: Record<string, string>): string {
244
117
  if (!text) return text;
245
- let result = text;
246
- result = result.replace("{{CONTENT}}", post.content || "");
247
- for (const [key, val] of Object.entries(params)) {
248
- const placeholder = key.startsWith("{{") ? key : `{{${key}}}`;
249
- result = result.replace(placeholder, val);
118
+ let r = text.replace("{{CONTENT}}", post.content || "");
119
+ for (const [k, v] of Object.entries(params)) {
120
+ r = r.replace(k.startsWith("{{") ? k : `{{${k}}}`, v);
250
121
  }
251
- return result;
122
+ return r;
252
123
  }
253
124
 
254
- /**
255
- * Tek bir map step'ini parametrelerle çalıştır
256
- */
257
- async function executeMapStep(
258
- page: any,
259
- step: any,
260
- post: Post,
261
- params: Record<string, string>
262
- ): Promise<void> {
263
- function rp(text: string): string {
264
- return replacePlaceholders(text, post, params);
265
- }
266
-
267
- const selectors = [step.selector, ...(step.fallbackSelectors || [])]
268
- .filter(Boolean)
269
- .map((s: string) => rp(s));
125
+ async function executeStep(page: any, step: any, post: Post, params: Record<string, string>): Promise<void> {
126
+ const rp = (t: string) => replacePlaceholders(t, post, params);
127
+ const selectors = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean).map((s: string) => rp(s));
270
128
 
271
129
  switch (step.action) {
272
- case "goto":
273
- await page.goto(rp(step.url), { waitUntil: "domcontentloaded", timeout: 30000 });
274
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
275
- break;
276
-
277
130
  case "wait":
278
131
  await page.waitForTimeout(step.waitMs || 1000);
279
132
  break;
280
133
 
281
134
  case "click": {
282
- let clicked = false;
283
-
284
- // Önce normal selector'ları dene
285
135
  for (const sel of selectors) {
286
- // Hâlâ çözümlenmemiş placeholder varsa atla
287
136
  if (sel.includes("{{")) continue;
288
137
  try {
289
138
  await page.waitForSelector(sel, { timeout: 5000 });
290
139
  await page.click(sel);
291
- clicked = true;
292
- break;
140
+ return;
293
141
  } catch {}
294
142
  }
295
-
296
- // Hiçbiri çalışmadıysa hata fırlat → heal mekanizması devreye girecek
297
- // AI ekranı görüp doğru butonu bulacak
298
- if (!clicked) throw new Error(`Click failed: ${selectors.join(", ")}`);
299
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
300
- break;
143
+ throw new Error(`Click: ${selectors[0]}`);
301
144
  }
302
145
 
303
146
  case "type": {
304
147
  const text = rp(step.value || "");
305
- let typed = false;
306
148
  for (const sel of selectors) {
307
149
  if (sel.includes("{{")) continue;
308
150
  try {
309
151
  await page.waitForSelector(sel, { timeout: 5000 });
310
- const isContentEditable = await page.$eval(sel, (el: Element) =>
311
- el.getAttribute("contenteditable") === "true" ||
312
- el.getAttribute("role") === "textbox"
152
+ const ce = await page.$eval(sel, (el: Element) =>
153
+ el.getAttribute("contenteditable") === "true" || el.getAttribute("role") === "textbox"
313
154
  ).catch(() => false);
314
-
315
- if (isContentEditable) {
155
+ if (ce) {
316
156
  await page.click(sel);
317
- // execCommand insertText ile yapıştır - anında, harf harf değil
318
- await page.evaluate(([selector, content]: string[]) => {
319
- const el = document.querySelector(selector);
320
- if (el) {
321
- el.focus();
322
- document.execCommand("insertText", false, content);
323
- }
157
+ await page.evaluate(([s, t]: string[]) => {
158
+ const el = document.querySelector(s);
159
+ if (el) { (el as HTMLElement).focus(); document.execCommand("insertText", false, t); }
324
160
  }, [sel, text]);
325
161
  } else {
326
162
  await page.fill(sel, text);
327
163
  }
328
- typed = true;
329
- break;
164
+ return;
330
165
  } catch {}
331
166
  }
332
- if (!typed) throw new Error(`Type failed: ${selectors.join(", ")}`);
333
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
334
- break;
167
+ throw new Error(`Type: ${selectors[0]}`);
335
168
  }
336
169
 
337
170
  case "keypress":
338
171
  await page.keyboard.press(step.key || "Enter");
339
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
340
172
  break;
341
173
 
342
174
  case "upload": {
343
175
  const filePath = rp(step.value || "{{IMAGE}}");
344
- if (!filePath || filePath.includes("{{")) break; // dosya yoksa atla
345
-
346
- let uploaded = false;
176
+ if (!filePath || filePath.includes("{{")) break;
347
177
 
348
- // fileChooser event'ini ÖNCE kaydet, SONRA butona tıkla
349
- // Bu sıralama kritik - yoksa OS file picker açılır
350
- if (selectors.length > 0) {
351
- for (const sel of selectors) {
352
- try {
353
- const fileChooserPromise = page.waitForEvent("filechooser", { timeout: 5000 });
354
- await page.click(sel).catch(() => {});
355
- const fileChooser = await fileChooserPromise;
356
- await fileChooser.setFiles(filePath);
357
- uploaded = true;
358
- break;
359
- } catch {
360
- // fileChooser timeout veya click hatası - sonraki selector'ı dene
361
- }
178
+ // 1. input[type=file] direkt (file picker açmaz)
179
+ try {
180
+ const inputs = await page.locator('input[type="file"]').all();
181
+ for (const input of inputs) {
182
+ try { await input.setInputFiles(filePath); return; } catch {}
362
183
  }
363
- }
184
+ } catch {}
364
185
 
365
- // Fallback: sayfadaki input[type=file]'a direkt set et
366
- if (!uploaded) {
186
+ // 2. fileChooser ile butona tıkla
187
+ for (const sel of selectors) {
367
188
  try {
368
- const inputs = await page.locator('input[type="file"]').all();
369
- for (const input of inputs) {
370
- try { await input.setInputFiles(filePath); uploaded = true; break; } catch {}
371
- }
189
+ const fc = await Promise.race([
190
+ (async () => {
191
+ const fcp = page.waitForEvent("filechooser", { timeout: 4000 });
192
+ await page.click(sel);
193
+ return await fcp;
194
+ })(),
195
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
196
+ ]) as any;
197
+ await fc.setFiles(filePath);
198
+ return;
372
199
  } catch {}
373
200
  }
374
-
375
- if (!uploaded) throw new Error(`Upload failed: ${filePath}`);
376
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
377
- break;
201
+ throw new Error(`Upload: ${filePath}`);
378
202
  }
379
203
  }
380
204
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-agent-cli",
3
- "version": "2.2.3",
3
+ "version": "2.3.1",
4
4
  "description": "AI-powered social media agent - free APIs + browser automation with self-healing selectors",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,172 +0,0 @@
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
- }
package/core/reflexes.ts DELETED
@@ -1,187 +0,0 @@
1
- /**
2
- * Reflexes - Sistem 1 (Hızlı, Otomatik)
3
- *
4
- * Kahneman'ın "Thinking, Fast and Slow" modeli:
5
- * - Sistem 1: Hızlı, otomatik, refleks tepkiler (bu dosya)
6
- * - Sistem 2: Yavaş, bilinçli, AI destekli (heal mekanizması)
7
- *
8
- * Bilinen UI pattern'larını AI çağırmadan milisaniyede çözer.
9
- */
10
-
11
- import type { Page } from "playwright";
12
-
13
- interface ReflexResult {
14
- handled: boolean;
15
- action: string; // ne yapıldı
16
- }
17
-
18
- /**
19
- * Sayfadaki beklenmeyen durumu refleksle çözmeye çalış.
20
- * Çözemezse handled: false döner → Sistem 2 (AI) devreye girer.
21
- */
22
- export async function tryReflex(page: Page, changeDescription: string): Promise<ReflexResult> {
23
- // 1. Modal/Dialog kapatma refleksi
24
- const dismissed = await tryDismissDialog(page);
25
- if (dismissed.handled) return dismissed;
26
-
27
- // 2. Cookie/consent banner kapatma
28
- const cookie = await tryDismissCookieBanner(page);
29
- if (cookie.handled) return cookie;
30
-
31
- // 3. File input algılama - dosya bekliyor mu?
32
- const fileInput = await tryDetectFileInput(page);
33
- if (fileInput.handled) return fileInput;
34
-
35
- // 4. Error/alert kapatma
36
- const error = await tryDismissError(page);
37
- if (error.handled) return error;
38
-
39
- // 5. Overlay/backdrop tıklama
40
- const overlay = await tryDismissOverlay(page);
41
- if (overlay.handled) return overlay;
42
-
43
- return { handled: false, action: "reflex bulunamadı" };
44
- }
45
-
46
- /**
47
- * Dialog/Modal kapatma - "close", "cancel", "dismiss", "x" butonlarını ara
48
- */
49
- async function tryDismissDialog(page: Page): Promise<ReflexResult> {
50
- // Önce dialog'un post oluşturma dialog'u olup olmadığını kontrol et - onu kapatma!
51
- const isComposeDialog = await page.evaluate(`(() => {
52
- const dialogs = document.querySelectorAll('[role="dialog"], [aria-modal="true"], dialog');
53
- for (const d of dialogs) {
54
- const text = (d.textContent || '').toLowerCase();
55
- if (text.includes('post') || text.includes('gönderi') || text.includes('tweet') ||
56
- text.includes('paylaş') || text.includes('share') || text.includes('compose') ||
57
- text.includes('yaz') || text.includes('düzenle') || text.includes('edit')) {
58
- return true;
59
- }
60
- }
61
- return false;
62
- })()`).catch(() => false);
63
-
64
- // Compose dialog'u kapatma!
65
- if (isComposeDialog) return { handled: false, action: "compose dialog - kapatılmadı" };
66
-
67
- // Gereksiz dialog'ları kapat
68
- const closeSelectors = [
69
- 'button[aria-label="Close"]',
70
- 'button[aria-label="Kapat"]',
71
- 'button[aria-label="Dismiss"]',
72
- 'button[aria-label="İptal"]',
73
- 'button[aria-label="Cancel"]',
74
- '[role="dialog"] button[aria-label*="close" i]',
75
- '[role="dialog"] button[aria-label*="kapat" i]',
76
- '[aria-modal="true"] button[aria-label*="close" i]',
77
- '[aria-modal="true"] button[aria-label*="kapat" i]',
78
- 'dialog button[aria-label*="close" i]',
79
- ];
80
-
81
- for (const sel of closeSelectors) {
82
- try {
83
- const btn = page.locator(sel).first();
84
- if (await btn.isVisible({ timeout: 500 })) {
85
- await btn.click();
86
- await page.waitForTimeout(300);
87
- return { handled: true, action: `dialog kapatıldı: ${sel}` };
88
- }
89
- } catch {}
90
- }
91
-
92
- return { handled: false, action: "" };
93
- }
94
-
95
- /**
96
- * Cookie/consent banner kapatma
97
- */
98
- async function tryDismissCookieBanner(page: Page): Promise<ReflexResult> {
99
- const cookieSelectors = [
100
- 'button[aria-label*="cookie" i]',
101
- 'button[aria-label*="çerez" i]',
102
- 'button[aria-label*="Accept" i]',
103
- 'button[aria-label*="Kabul" i]',
104
- 'button[aria-label*="consent" i]',
105
- '[id*="cookie"] button',
106
- '[class*="cookie"] button',
107
- '[class*="consent"] button',
108
- ];
109
-
110
- for (const sel of cookieSelectors) {
111
- try {
112
- const btn = page.locator(sel).first();
113
- if (await btn.isVisible({ timeout: 300 })) {
114
- await btn.click();
115
- await page.waitForTimeout(300);
116
- return { handled: true, action: `cookie banner kapatıldı: ${sel}` };
117
- }
118
- } catch {}
119
- }
120
-
121
- return { handled: false, action: "" };
122
- }
123
-
124
- /**
125
- * Yeni file input algılama
126
- */
127
- async function tryDetectFileInput(page: Page): Promise<ReflexResult> {
128
- try {
129
- const hasNewFileInput = await page.evaluate(`(() => {
130
- const inputs = document.querySelectorAll('input[type="file"]');
131
- return inputs.length > 0;
132
- })()`);
133
-
134
- if (hasNewFileInput) {
135
- return { handled: true, action: "file input algılandı - upload step'i çalışabilir" };
136
- }
137
- } catch {}
138
-
139
- return { handled: false, action: "" };
140
- }
141
-
142
- /**
143
- * Error/alert kapatma
144
- */
145
- async function tryDismissError(page: Page): Promise<ReflexResult> {
146
- const errorSelectors = [
147
- '[role="alert"] button',
148
- '[role="alert"] [aria-label*="close" i]',
149
- '[role="alert"] [aria-label*="kapat" i]',
150
- '[aria-live="assertive"] button',
151
- ];
152
-
153
- for (const sel of errorSelectors) {
154
- try {
155
- const btn = page.locator(sel).first();
156
- if (await btn.isVisible({ timeout: 300 })) {
157
- await btn.click();
158
- await page.waitForTimeout(200);
159
- return { handled: true, action: `error kapatıldı: ${sel}` };
160
- }
161
- } catch {}
162
- }
163
-
164
- return { handled: false, action: "" };
165
- }
166
-
167
- /**
168
- * Overlay/backdrop tıklama ile kapatma
169
- */
170
- async function tryDismissOverlay(page: Page): Promise<ReflexResult> {
171
- try {
172
- // ESC tuşu ile kapatmayı dene
173
- await page.keyboard.press("Escape");
174
- await page.waitForTimeout(300);
175
-
176
- // Dialog hâlâ var mı kontrol et
177
- const stillHasDialog = await page.evaluate(`(() => {
178
- return document.querySelectorAll('[role="dialog"], [aria-modal="true"]').length > 0;
179
- })()`);
180
-
181
- if (!stillHasDialog) {
182
- return { handled: true, action: "overlay ESC ile kapatıldı" };
183
- }
184
- } catch {}
185
-
186
- return { handled: false, action: "" };
187
- }