arise-browser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +247 -0
  3. package/deploy/neko/CONTEXT.md +37 -0
  4. package/deploy/neko/arise-browser.service +13 -0
  5. package/deploy/neko/neko.yaml +12 -0
  6. package/deploy/neko/openbox.xml +763 -0
  7. package/deploy/neko/policies.json +28 -0
  8. package/deploy/neko/pulseaudio.pa +16 -0
  9. package/deploy/neko/setup.sh +308 -0
  10. package/deploy/neko/xorg.conf +118 -0
  11. package/dist/bin/arise-browser.d.ts +26 -0
  12. package/dist/bin/arise-browser.d.ts.map +1 -0
  13. package/dist/bin/arise-browser.js +224 -0
  14. package/dist/bin/arise-browser.js.map +1 -0
  15. package/dist/src/browser/action-executor.d.ts +98 -0
  16. package/dist/src/browser/action-executor.d.ts.map +1 -0
  17. package/dist/src/browser/action-executor.js +2726 -0
  18. package/dist/src/browser/action-executor.js.map +1 -0
  19. package/dist/src/browser/behavior-recorder.d.ts +61 -0
  20. package/dist/src/browser/behavior-recorder.d.ts.map +1 -0
  21. package/dist/src/browser/behavior-recorder.js +442 -0
  22. package/dist/src/browser/behavior-recorder.js.map +1 -0
  23. package/dist/src/browser/browser-session.d.ts +202 -0
  24. package/dist/src/browser/browser-session.d.ts.map +1 -0
  25. package/dist/src/browser/browser-session.js +1647 -0
  26. package/dist/src/browser/browser-session.js.map +1 -0
  27. package/dist/src/browser/config.d.ts +43 -0
  28. package/dist/src/browser/config.d.ts.map +1 -0
  29. package/dist/src/browser/config.js +59 -0
  30. package/dist/src/browser/config.js.map +1 -0
  31. package/dist/src/browser/page-snapshot.d.ts +38 -0
  32. package/dist/src/browser/page-snapshot.d.ts.map +1 -0
  33. package/dist/src/browser/page-snapshot.js +241 -0
  34. package/dist/src/browser/page-snapshot.js.map +1 -0
  35. package/dist/src/browser/scripts/behavior_tracker.js +424 -0
  36. package/dist/src/browser/scripts/unified_analyzer.js +1576 -0
  37. package/dist/src/index.d.ts +15 -0
  38. package/dist/src/index.d.ts.map +1 -0
  39. package/dist/src/index.js +15 -0
  40. package/dist/src/index.js.map +1 -0
  41. package/dist/src/lock.d.ts +11 -0
  42. package/dist/src/lock.d.ts.map +1 -0
  43. package/dist/src/lock.js +47 -0
  44. package/dist/src/lock.js.map +1 -0
  45. package/dist/src/logger.d.ts +17 -0
  46. package/dist/src/logger.d.ts.map +1 -0
  47. package/dist/src/logger.js +29 -0
  48. package/dist/src/logger.js.map +1 -0
  49. package/dist/src/server/middleware/auth.d.ts +6 -0
  50. package/dist/src/server/middleware/auth.d.ts.map +1 -0
  51. package/dist/src/server/middleware/auth.js +24 -0
  52. package/dist/src/server/middleware/auth.js.map +1 -0
  53. package/dist/src/server/route-utils.d.ts +15 -0
  54. package/dist/src/server/route-utils.d.ts.map +1 -0
  55. package/dist/src/server/route-utils.js +33 -0
  56. package/dist/src/server/route-utils.js.map +1 -0
  57. package/dist/src/server/routes/action.d.ts +5 -0
  58. package/dist/src/server/routes/action.d.ts.map +1 -0
  59. package/dist/src/server/routes/action.js +69 -0
  60. package/dist/src/server/routes/action.js.map +1 -0
  61. package/dist/src/server/routes/actions.d.ts +3 -0
  62. package/dist/src/server/routes/actions.d.ts.map +1 -0
  63. package/dist/src/server/routes/actions.js +53 -0
  64. package/dist/src/server/routes/actions.js.map +1 -0
  65. package/dist/src/server/routes/cookies.d.ts +3 -0
  66. package/dist/src/server/routes/cookies.d.ts.map +1 -0
  67. package/dist/src/server/routes/cookies.js +27 -0
  68. package/dist/src/server/routes/cookies.js.map +1 -0
  69. package/dist/src/server/routes/download.d.ts +3 -0
  70. package/dist/src/server/routes/download.d.ts.map +1 -0
  71. package/dist/src/server/routes/download.js +35 -0
  72. package/dist/src/server/routes/download.js.map +1 -0
  73. package/dist/src/server/routes/evaluate.d.ts +3 -0
  74. package/dist/src/server/routes/evaluate.d.ts.map +1 -0
  75. package/dist/src/server/routes/evaluate.js +27 -0
  76. package/dist/src/server/routes/evaluate.js.map +1 -0
  77. package/dist/src/server/routes/health.d.ts +3 -0
  78. package/dist/src/server/routes/health.d.ts.map +1 -0
  79. package/dist/src/server/routes/health.js +11 -0
  80. package/dist/src/server/routes/health.js.map +1 -0
  81. package/dist/src/server/routes/navigate.d.ts +3 -0
  82. package/dist/src/server/routes/navigate.d.ts.map +1 -0
  83. package/dist/src/server/routes/navigate.js +36 -0
  84. package/dist/src/server/routes/navigate.js.map +1 -0
  85. package/dist/src/server/routes/page-model.d.ts +3 -0
  86. package/dist/src/server/routes/page-model.d.ts.map +1 -0
  87. package/dist/src/server/routes/page-model.js +22 -0
  88. package/dist/src/server/routes/page-model.js.map +1 -0
  89. package/dist/src/server/routes/pdf.d.ts +3 -0
  90. package/dist/src/server/routes/pdf.d.ts.map +1 -0
  91. package/dist/src/server/routes/pdf.js +20 -0
  92. package/dist/src/server/routes/pdf.js.map +1 -0
  93. package/dist/src/server/routes/recording.d.ts +5 -0
  94. package/dist/src/server/routes/recording.d.ts.map +1 -0
  95. package/dist/src/server/routes/recording.js +217 -0
  96. package/dist/src/server/routes/recording.js.map +1 -0
  97. package/dist/src/server/routes/screenshot.d.ts +3 -0
  98. package/dist/src/server/routes/screenshot.d.ts.map +1 -0
  99. package/dist/src/server/routes/screenshot.js +32 -0
  100. package/dist/src/server/routes/screenshot.js.map +1 -0
  101. package/dist/src/server/routes/snapshot.d.ts +3 -0
  102. package/dist/src/server/routes/snapshot.d.ts.map +1 -0
  103. package/dist/src/server/routes/snapshot.js +454 -0
  104. package/dist/src/server/routes/snapshot.js.map +1 -0
  105. package/dist/src/server/routes/tab-lock.d.ts +3 -0
  106. package/dist/src/server/routes/tab-lock.d.ts.map +1 -0
  107. package/dist/src/server/routes/tab-lock.js +30 -0
  108. package/dist/src/server/routes/tab-lock.js.map +1 -0
  109. package/dist/src/server/routes/tab.d.ts +3 -0
  110. package/dist/src/server/routes/tab.d.ts.map +1 -0
  111. package/dist/src/server/routes/tab.js +47 -0
  112. package/dist/src/server/routes/tab.js.map +1 -0
  113. package/dist/src/server/routes/tabs.d.ts +3 -0
  114. package/dist/src/server/routes/tabs.d.ts.map +1 -0
  115. package/dist/src/server/routes/tabs.js +13 -0
  116. package/dist/src/server/routes/tabs.js.map +1 -0
  117. package/dist/src/server/routes/text.d.ts +3 -0
  118. package/dist/src/server/routes/text.d.ts.map +1 -0
  119. package/dist/src/server/routes/text.js +20 -0
  120. package/dist/src/server/routes/text.js.map +1 -0
  121. package/dist/src/server/routes/upload.d.ts +3 -0
  122. package/dist/src/server/routes/upload.d.ts.map +1 -0
  123. package/dist/src/server/routes/upload.js +38 -0
  124. package/dist/src/server/routes/upload.js.map +1 -0
  125. package/dist/src/server/server.d.ts +7 -0
  126. package/dist/src/server/server.d.ts.map +1 -0
  127. package/dist/src/server/server.js +69 -0
  128. package/dist/src/server/server.js.map +1 -0
  129. package/dist/src/types/index.d.ts +125 -0
  130. package/dist/src/types/index.d.ts.map +1 -0
  131. package/dist/src/types/index.js +5 -0
  132. package/dist/src/types/index.js.map +1 -0
  133. package/dist/src/virtual-display/manager.d.ts +37 -0
  134. package/dist/src/virtual-display/manager.d.ts.map +1 -0
  135. package/dist/src/virtual-display/manager.js +229 -0
  136. package/dist/src/virtual-display/manager.js.map +1 -0
  137. package/dist/src/virtual-display/process-runner.d.ts +43 -0
  138. package/dist/src/virtual-display/process-runner.d.ts.map +1 -0
  139. package/dist/src/virtual-display/process-runner.js +174 -0
  140. package/dist/src/virtual-display/process-runner.js.map +1 -0
  141. package/dist/tsconfig.tsbuildinfo +1 -0
  142. package/package.json +57 -0
  143. package/plugin/openclaw.plugin.json +148 -0
  144. package/skill/arise-browser/SKILL.md +275 -0
  145. package/skill/arise-browser/TRUST.md +42 -0
  146. package/skill/arise-browser/references/api.md +198 -0
  147. package/src/browser/scripts/behavior_tracker.js +424 -0
  148. package/src/browser/scripts/unified_analyzer.js +1576 -0
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Behavior Tracker - Captures user interactions using aria-ref system
3
+ *
4
+ * Records user actions in the same format as action_executor.py expects,
5
+ * using the persistent ref system from unified_analyzer.js.
6
+ *
7
+ * Output format matches ActionExecutor actions:
8
+ * { type: "click", ref: "e42", text: "Submit", role: "button" }
9
+ * { type: "type", ref: "e15", text: "hello", role: "textbox" }
10
+ */
11
+ (function() {
12
+ // Prevent multiple initialization
13
+ if (window._behaviorTrackerInitialized) return;
14
+ window._behaviorTrackerInitialized = true;
15
+
16
+ console.log("🎯 Behavior Tracker initialized (ref-based)");
17
+
18
+ // =========================================================================
19
+ // Element Info Collector - Uses aria-ref from unified_analyzer.js
20
+ // =========================================================================
21
+
22
+ function getElementRef(element) {
23
+ if (!element) return null;
24
+
25
+ // Get aria-ref assigned by unified_analyzer.js
26
+ const ref = element.getAttribute('aria-ref');
27
+ if (ref) return ref;
28
+
29
+ // Walk up to find nearest parent with aria-ref
30
+ let parent = element.parentElement;
31
+ let depth = 0;
32
+ while (parent && depth < 5) {
33
+ const parentRef = parent.getAttribute('aria-ref');
34
+ if (parentRef) return parentRef;
35
+ parent = parent.parentElement;
36
+ depth++;
37
+ }
38
+
39
+ return null;
40
+ }
41
+
42
+ function getElementRole(element) {
43
+ if (!element) return null;
44
+
45
+ // Check explicit role attribute
46
+ const role = element.getAttribute('role');
47
+ if (role) return role;
48
+
49
+ // Infer role from tag name
50
+ const tagName = element.tagName.toLowerCase();
51
+ const roleMap = {
52
+ 'a': 'link',
53
+ 'button': 'button',
54
+ 'input': element.type === 'checkbox' ? 'checkbox'
55
+ : element.type === 'radio' ? 'radio'
56
+ : 'textbox',
57
+ 'select': 'combobox',
58
+ 'textarea': 'textbox',
59
+ 'h1': 'heading', 'h2': 'heading', 'h3': 'heading',
60
+ 'h4': 'heading', 'h5': 'heading', 'h6': 'heading',
61
+ 'img': 'img',
62
+ 'nav': 'navigation',
63
+ 'main': 'main',
64
+ 'ul': 'list', 'ol': 'list',
65
+ 'li': 'listitem',
66
+ 'table': 'table',
67
+ 'tr': 'row',
68
+ 'td': 'cell', 'th': 'cell'
69
+ };
70
+
71
+ return roleMap[tagName] || 'generic';
72
+ }
73
+
74
+ function getElementText(element) {
75
+ if (!element) return '';
76
+
77
+ // Check aria-label first
78
+ const ariaLabel = element.getAttribute('aria-label');
79
+ if (ariaLabel) return ariaLabel.trim();
80
+
81
+ // Check aria-labelledby
82
+ const labelledBy = element.getAttribute('aria-labelledby');
83
+ if (labelledBy) {
84
+ const labelEl = document.getElementById(labelledBy);
85
+ if (labelEl) return labelEl.textContent.trim();
86
+ }
87
+
88
+ // For inputs, use placeholder or value
89
+ if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
90
+ return element.placeholder || '';
91
+ }
92
+
93
+ // Get visible text content (limited)
94
+ const text = (element.textContent || '').trim();
95
+ return text.slice(0, 100);
96
+ }
97
+
98
+ function getElementInfo(element) {
99
+ if (!element) return null;
100
+
101
+ const ref = getElementRef(element);
102
+ const role = getElementRole(element);
103
+ const text = getElementText(element);
104
+ const tagName = element.tagName.toLowerCase();
105
+
106
+ // For links, capture href (useful when ref is missing during navigation)
107
+ let href = null;
108
+ if (tagName === 'a') {
109
+ href = element.href;
110
+ } else {
111
+ // Check if element is inside a link
112
+ const linkParent = element.closest('a');
113
+ if (linkParent) {
114
+ href = linkParent.href;
115
+ }
116
+ }
117
+
118
+ return {
119
+ ref: ref,
120
+ role: role,
121
+ text: text,
122
+ tagName: tagName,
123
+ href: href
124
+ };
125
+ }
126
+
127
+ // =========================================================================
128
+ // Operation Reporter
129
+ // =========================================================================
130
+
131
+ function report(type, elementInfo, additionalData) {
132
+ const timestamp = new Date().toISOString();
133
+
134
+ const operation = {
135
+ type: type,
136
+ timestamp: timestamp,
137
+ url: window.location.href
138
+ };
139
+
140
+ // Add element info if available
141
+ if (elementInfo) {
142
+ if (elementInfo.ref) operation.ref = elementInfo.ref;
143
+ if (elementInfo.text) operation.text = elementInfo.text;
144
+ if (elementInfo.role) operation.role = elementInfo.role;
145
+ if (elementInfo.href) operation.href = elementInfo.href;
146
+ }
147
+
148
+ // Merge additional data
149
+ if (additionalData) {
150
+ Object.assign(operation, additionalData);
151
+ }
152
+
153
+ // Report via CDP binding or postMessage
154
+ if (window.reportUserBehavior) {
155
+ try {
156
+ window.reportUserBehavior(JSON.stringify(operation));
157
+ } catch (e) {
158
+ console.warn('Failed to report via CDP:', e);
159
+ }
160
+ } else {
161
+ // Chrome Extension fallback
162
+ window.postMessage({
163
+ source: 'arise-tracker',
164
+ operation: operation
165
+ }, '*');
166
+ }
167
+ }
168
+
169
+ // =========================================================================
170
+ // Click Handler
171
+ // =========================================================================
172
+
173
+ let isDragging = false;
174
+ let dragStartX = 0;
175
+ let dragStartY = 0;
176
+
177
+ document.addEventListener('mousedown', function(e) {
178
+ isDragging = false;
179
+ dragStartX = e.clientX;
180
+ dragStartY = e.clientY;
181
+ }, true);
182
+
183
+ document.addEventListener('mousemove', function(e) {
184
+ if (e.buttons === 1) {
185
+ const distance = Math.sqrt(
186
+ Math.pow(e.clientX - dragStartX, 2) +
187
+ Math.pow(e.clientY - dragStartY, 2)
188
+ );
189
+ if (distance > 5) {
190
+ isDragging = true;
191
+ }
192
+ }
193
+ }, true);
194
+
195
+ document.addEventListener('click', function(e) {
196
+ // Skip if this was a drag operation
197
+ if (isDragging) {
198
+ isDragging = false;
199
+ return;
200
+ }
201
+
202
+ // Flush all pending type events before click (may trigger navigation)
203
+ flushAllPendingTypes();
204
+
205
+ const info = getElementInfo(e.target);
206
+ // Always report click, even without ref (e.g., navigation links)
207
+ report('click', info);
208
+ }, true);
209
+
210
+ // =========================================================================
211
+ // Input Handler (debounced)
212
+ // =========================================================================
213
+
214
+ const inputTimeouts = new Map(); // key -> { timeout, element, info }
215
+ const INPUT_DEBOUNCE_MS = 1500;
216
+
217
+ /**
218
+ * Flush a pending type event immediately (cancel its debounce timer).
219
+ * Called before navigation-triggering actions (Enter, form submit) to
220
+ * ensure the type event is reported before the page unloads.
221
+ */
222
+ function flushPendingType(key) {
223
+ const pending = inputTimeouts.get(key);
224
+ if (!pending) return;
225
+
226
+ clearTimeout(pending.timeout);
227
+ inputTimeouts.delete(key);
228
+
229
+ const value = (pending.element.value || '').trim();
230
+ if (value) {
231
+ report('type', pending.info, { value: value });
232
+ }
233
+ }
234
+
235
+ function flushAllPendingTypes() {
236
+ for (const key of Array.from(inputTimeouts.keys())) {
237
+ flushPendingType(key);
238
+ }
239
+ }
240
+
241
+ document.addEventListener('input', function(e) {
242
+ const element = e.target;
243
+ const tagName = element.tagName.toLowerCase();
244
+
245
+ // Only track actual input fields
246
+ if (tagName !== 'input' && tagName !== 'textarea' && element.contentEditable !== 'true') {
247
+ return;
248
+ }
249
+
250
+ // Skip password fields
251
+ if (element.type === 'password' || element.type === 'hidden') {
252
+ return;
253
+ }
254
+
255
+ const info = getElementInfo(element);
256
+ if (!info) return;
257
+
258
+ // Debounce by element ref (fall back to tagName to avoid null-key collisions)
259
+ const key = info.ref || ('__' + tagName + '_' + (element.name || element.id || 'anon'));
260
+ if (inputTimeouts.has(key)) {
261
+ clearTimeout(inputTimeouts.get(key).timeout);
262
+ }
263
+
264
+ const timeout = setTimeout(() => {
265
+ const value = element.value || '';
266
+ if (value.trim()) {
267
+ report('type', info, {
268
+ value: value
269
+ });
270
+ }
271
+ inputTimeouts.delete(key);
272
+ }, INPUT_DEBOUNCE_MS);
273
+
274
+ inputTimeouts.set(key, { timeout, element, info });
275
+ }, true);
276
+
277
+ // =========================================================================
278
+ // Navigation Handler (SPA detection)
279
+ // CDP Page.frameNavigated catches full page loads; JS polling catches
280
+ // pushState/replaceState SPA navigations. Deduplication is done in Python.
281
+ // =========================================================================
282
+
283
+ let currentUrl = window.location.href;
284
+
285
+ // Only in main frame
286
+ if (window.self === window.top) {
287
+ // URL polling for SPA navigation
288
+ setInterval(function() {
289
+ if (window.location.href !== currentUrl) {
290
+ // Flush pending type events before SPA navigation
291
+ flushAllPendingTypes();
292
+
293
+ const fromUrl = currentUrl;
294
+ currentUrl = window.location.href;
295
+
296
+ report('navigate', null, {
297
+ url: currentUrl,
298
+ from_url: fromUrl
299
+ });
300
+ }
301
+ }, 500);
302
+
303
+ // Popstate for browser back/forward
304
+ window.addEventListener('popstate', function() {
305
+ flushAllPendingTypes();
306
+
307
+ const fromUrl = currentUrl;
308
+ currentUrl = window.location.href;
309
+
310
+ report('navigate', null, {
311
+ url: currentUrl,
312
+ from_url: fromUrl
313
+ });
314
+ });
315
+ }
316
+
317
+ // Flush all pending type events before page unload
318
+ window.addEventListener('beforeunload', flushAllPendingTypes);
319
+
320
+ // =========================================================================
321
+ // Scroll Handler (throttled)
322
+ // =========================================================================
323
+
324
+ let scrollTimeout;
325
+ let lastScrollY = window.scrollY;
326
+ const SCROLL_THRESHOLD = 100;
327
+
328
+ window.addEventListener('scroll', function() {
329
+ clearTimeout(scrollTimeout);
330
+
331
+ scrollTimeout = setTimeout(function() {
332
+ const currentScrollY = window.scrollY;
333
+ const delta = currentScrollY - lastScrollY;
334
+
335
+ if (Math.abs(delta) > SCROLL_THRESHOLD) {
336
+ report('scroll', null, {
337
+ direction: delta > 0 ? 'down' : 'up',
338
+ amount: Math.abs(Math.round(delta))
339
+ });
340
+ lastScrollY = currentScrollY;
341
+ }
342
+ }, 150);
343
+ });
344
+
345
+ // =========================================================================
346
+ // Select (Dropdown) Handler
347
+ // =========================================================================
348
+
349
+ document.addEventListener('change', function(e) {
350
+ const element = e.target;
351
+
352
+ if (element.tagName.toLowerCase() === 'select') {
353
+ const info = getElementInfo(element);
354
+ if (info) {
355
+ report('select', info, {
356
+ value: element.value
357
+ });
358
+ }
359
+ }
360
+ }, true);
361
+
362
+ // =========================================================================
363
+ // Enter Key Handler
364
+ // =========================================================================
365
+
366
+ document.addEventListener('keydown', function(e) {
367
+ if (e.key === 'Enter') {
368
+ // Flush pending type for this element before enter triggers navigation
369
+ const info = getElementInfo(e.target);
370
+ const key = info && info.ref
371
+ ? info.ref
372
+ : ('__' + e.target.tagName.toLowerCase() + '_' + (e.target.name || e.target.id || 'anon'));
373
+ flushPendingType(key);
374
+ report('enter', info);
375
+ }
376
+ }, true);
377
+
378
+ // =========================================================================
379
+ // Copy/Paste Handlers
380
+ // =========================================================================
381
+
382
+ document.addEventListener('copy', function(e) {
383
+ const selection = window.getSelection();
384
+ const text = selection ? selection.toString() : '';
385
+
386
+ if (text) {
387
+ report('copy', null, {
388
+ text: text.slice(0, 200)
389
+ });
390
+ }
391
+ });
392
+
393
+ document.addEventListener('paste', function(e) {
394
+ const info = getElementInfo(e.target);
395
+ const clipboardData = e.clipboardData || window.clipboardData;
396
+ const text = clipboardData ? clipboardData.getData('text') : '';
397
+
398
+ report('paste', info, {
399
+ text: text.slice(0, 200)
400
+ });
401
+ });
402
+
403
+ // =========================================================================
404
+ // Text Selection Handler (drag-based)
405
+ // =========================================================================
406
+
407
+ document.addEventListener('mouseup', function(e) {
408
+ if (isDragging) {
409
+ setTimeout(function() {
410
+ const selection = window.getSelection();
411
+ const text = selection ? selection.toString().trim() : '';
412
+
413
+ if (text.length > 0) {
414
+ report('select_text', null, {
415
+ text: text.slice(0, 200)
416
+ });
417
+ }
418
+ }, 10);
419
+ }
420
+ isDragging = false;
421
+ }, true);
422
+
423
+ console.log("✅ Behavior Tracker ready");
424
+ })();