@pasko70/pibo 1.9.8 → 1.9.10

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 (101) hide show
  1. package/README.md +183 -183
  2. package/context/codex-base-prompt.md +148 -148
  3. package/context/compute-worker.md +23 -23
  4. package/context/pibo-compaction-prompt.md +100 -100
  5. package/context/pibo-native-tooling.md +18 -18
  6. package/context/pibo-system-prompt.md +77 -77
  7. package/dist/apps/chat/agent-store.js +248 -248
  8. package/dist/apps/chat/data/project-service.js +168 -168
  9. package/dist/apps/chat/data/read-state-service.js +18 -18
  10. package/dist/apps/chat/data/session-query-service.js +25 -25
  11. package/dist/apps/chat/data/timeline-query-service.js +14 -14
  12. package/dist/apps/chat/static-assets.js +854 -854
  13. package/dist/apps/chat/workflow-persistence.js +255 -255
  14. package/dist/apps/chat-ui/index.html +16 -16
  15. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  16. package/dist/apps/chat-ui/sw.js +47 -47
  17. package/dist/apps/chat-vscode-web/index.html +12 -12
  18. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  19. package/dist/apps/context-files-ui/index.html +11 -11
  20. package/dist/cli.js +39 -39
  21. package/dist/compute/cli.js +54 -54
  22. package/dist/cron/cli.js +15 -15
  23. package/dist/cron/store.js +49 -49
  24. package/dist/data/cli.js +23 -23
  25. package/dist/data/event-log.js +23 -23
  26. package/dist/data/message-store.js +20 -20
  27. package/dist/data/navigation-store.js +9 -9
  28. package/dist/data/observation-store.js +4 -4
  29. package/dist/data/payload-store.js +17 -17
  30. package/dist/data/schema.js +431 -431
  31. package/dist/data/session-store.js +4 -4
  32. package/dist/data/telemetry-queries.js +54 -54
  33. package/dist/data/telemetry.js +197 -197
  34. package/dist/debug/events.js +12 -12
  35. package/dist/debug/failures.js +6 -6
  36. package/dist/debug/index.js +227 -227
  37. package/dist/debug/messages.js +6 -6
  38. package/dist/debug/pty.js +124 -124
  39. package/dist/debug/session.js +29 -29
  40. package/dist/debug/tools.js +5 -5
  41. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  42. package/dist/debug/web-streaming-browser-library.js +925 -925
  43. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  44. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  45. package/dist/debug/web.js +93 -93
  46. package/dist/gateway/cli.js +19 -19
  47. package/dist/mcp/config-command.js +53 -53
  48. package/dist/mcp/index.js +21 -21
  49. package/dist/mcp/registry.js +11 -11
  50. package/dist/pi-packages/cli.js +11 -11
  51. package/dist/plugins/context-files-store.js +110 -110
  52. package/dist/plugins/context-files.js +4 -4
  53. package/dist/ralph/cli.js +18 -18
  54. package/dist/ralph/templates.js +140 -140
  55. package/dist/reliability/store.js +226 -226
  56. package/dist/sessions/pibo-data-store.js +16 -16
  57. package/dist/sessions/sqlite-store.js +53 -53
  58. package/dist/setup/cli.js +58 -58
  59. package/dist/tools/agent-browser-wrapper.js +80 -80
  60. package/dist/tools/browser-use-cdp.js +12 -12
  61. package/dist/tools/browser-use-wrapper.js +762 -762
  62. package/dist/tools/guides.js +538 -538
  63. package/dist/tools/index.js +99 -99
  64. package/dist/tools/runtime/node-worker-source.js +205 -205
  65. package/dist/tools/runtime/python-worker-source.js +177 -177
  66. package/dist/vscode/cli.js +9 -9
  67. package/dist/web-annotations/cdp.js +900 -900
  68. package/dist/web-annotations/store.js +96 -96
  69. package/docs/README.md +23 -23
  70. package/docs/ops/install-developer-host.md +112 -112
  71. package/docs/ops/install-user-host.md +96 -96
  72. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  73. package/docs/ops/vscode-extension-release.md +160 -160
  74. package/package.json +95 -95
  75. package/skills/builtin/graphify/SKILL.md +52 -52
  76. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  77. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  78. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  79. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  80. package/skills/builtin/prd/SKILL.md +143 -143
  81. package/skills/builtin/ralph-loop/SKILL.md +359 -359
  82. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  83. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  84. package/skills/builtin/skill-creator/SKILL.md +513 -513
  85. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  86. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  87. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  88. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  89. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  90. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  91. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  92. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  93. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  94. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  95. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  96. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  97. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  98. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  99. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  100. package/skills/builtin/web-annotations/SKILL.md +93 -93
  101. package/src/mcp/LICENSE.mcp-cli +21 -21
@@ -1,303 +1,303 @@
1
1
  export function buildSnapshotExpression(options) {
2
- return `(() => {
3
- const options = ${JSON.stringify(options)};
4
- ${browserSnapshotLibrary()}
5
- return captureSnapshot(options);
2
+ return `(() => {
3
+ const options = ${JSON.stringify(options)};
4
+ ${browserSnapshotLibrary()}
5
+ return captureSnapshot(options);
6
6
  })()`;
7
7
  }
8
8
  export function buildWatchExpression(options) {
9
- return `(async () => {
10
- const options = ${JSON.stringify(options)};
11
- ${browserSnapshotLibrary()}
12
- return await runWatch(options);
9
+ return `(async () => {
10
+ const options = ${JSON.stringify(options)};
11
+ ${browserSnapshotLibrary()}
12
+ return await runWatch(options);
13
13
  })()`;
14
14
  }
15
15
  function browserSnapshotLibrary() {
16
- return String.raw `
17
- function nowIso() { return new Date().toISOString(); }
18
- function safeString(value) { return typeof value === 'string' ? value : ''; }
19
- function short(value, limit) {
20
- const text = safeString(value).replace(/\s+/g, ' ').trim();
21
- return text.length > limit ? text.slice(0, Math.max(0, limit - 1)) + '…' : text;
22
- }
23
- function redactText(element, options) {
24
- const tag = element.tagName.toLowerCase();
25
- const debug = element.getAttribute('data-pibo-debug') || '';
26
- if (tag === 'textarea' || tag === 'input' || debug === 'composer') {
27
- const value = 'value' in element ? String(element.value || '') : '';
28
- return value ? '[redacted:' + value.length + ' chars]' : '';
29
- }
30
- const text = element.innerText || element.textContent || '';
31
- if (!options.includeText && /message|trace|terminal|composer/i.test(debug)) return text ? '[redacted]' : '';
32
- return short(text, options.textLimit);
33
- }
34
- function classSummary(element) {
35
- const value = safeString(element.getAttribute('class'));
36
- if (!value) return undefined;
37
- const parts = value.split(/\s+/).filter(Boolean);
38
- const useful = parts.filter((part) => /selected|active|hidden|opacity|translate|animate|border|bg-|text-|ring|disabled|pointer|sr-only/.test(part));
39
- return (useful.length ? useful : parts.slice(0, 6)).slice(0, 10).join(' ');
40
- }
41
- function attrMap(element) {
42
- const attrs = {};
43
- const allow = /^(id|role|aria-|data-pibo-|data-testid$|disabled$|checked$|selected$|hidden$|tabindex$|title$)/;
44
- for (const attr of Array.from(element.attributes || [])) {
45
- if (!allow.test(attr.name)) continue;
46
- if (/token|cookie|authorization|secret|password/i.test(attr.name)) {
47
- attrs[attr.name] = '[redacted]';
48
- } else {
49
- attrs[attr.name] = short(attr.value, 120);
50
- }
51
- }
52
- if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
53
- attrs.value = element.value ? '[redacted:' + element.value.length + ' chars]' : '';
54
- attrs.disabled = Boolean(element.disabled);
55
- }
56
- return attrs;
57
- }
58
- function roleOf(element) {
59
- const explicit = element.getAttribute('role');
60
- if (explicit) return explicit;
61
- const tag = element.tagName.toLowerCase();
62
- if (tag === 'button') return 'button';
63
- if (tag === 'a') return 'link';
64
- if (tag === 'textarea') return 'textbox';
65
- if (tag === 'input') return 'input';
66
- if (tag === 'select') return 'combobox';
67
- if (tag === 'main') return 'main';
68
- if (tag === 'aside') return 'complementary';
69
- if (tag === 'nav') return 'navigation';
70
- return undefined;
71
- }
72
- function nameOf(element, options) {
73
- const aria = element.getAttribute('aria-label');
74
- if (aria) return short(aria, options.textLimit);
75
- const title = element.getAttribute('title');
76
- if (title) return short(title, options.textLimit);
77
- const tag = element.tagName.toLowerCase();
78
- if (tag === 'input' || tag === 'textarea') return short(element.getAttribute('placeholder') || '', options.textLimit);
79
- const debug = element.getAttribute('data-pibo-debug');
80
- if (debug === 'session-row') return short(element.getAttribute('data-pibo-title') || element.innerText || '', options.textLimit);
81
- return undefined;
82
- }
83
- function elementPath(element) {
84
- const parts = [];
85
- let current = element;
86
- while (current && current.nodeType === Node.ELEMENT_NODE && current !== document.documentElement) {
87
- const tag = current.tagName.toLowerCase();
88
- const parent = current.parentElement;
89
- if (!parent) {
90
- parts.unshift(tag);
91
- break;
92
- }
93
- const index = Array.from(parent.children).filter((child) => child.tagName === current.tagName).indexOf(current) + 1;
94
- parts.unshift(tag + ':nth-of-type(' + index + ')');
95
- current = parent;
96
- }
97
- return parts.join('>');
98
- }
99
- function identityOf(element) {
100
- const debug = element.getAttribute('data-pibo-debug');
101
- const session = element.getAttribute('data-pibo-session-id');
102
- const room = element.getAttribute('data-pibo-room-id');
103
- const view = element.getAttribute('data-pibo-view-id');
104
- const testId = element.getAttribute('data-testid');
105
- const id = element.id;
106
- if (debug && session) return { identity: debug + ':' + session, kind: 'pibo-session' };
107
- if (debug && room) return { identity: debug + ':' + room, kind: 'pibo-room' };
108
- if (debug && view) return { identity: debug + ':' + view, kind: 'pibo-view' };
109
- if (debug) return { identity: debug, kind: 'pibo-debug' };
110
- if (testId) return { identity: 'testid:' + testId, kind: 'testid' };
111
- if (id) return { identity: 'id:' + id, kind: 'id' };
112
- const role = roleOf(element);
113
- const name = nameOf(element, { textLimit: 40 }) || '';
114
- if (role && name) return { identity: role + ':' + name, kind: 'role-name' };
115
- return { identity: 'path:' + elementPath(element), kind: 'path' };
116
- }
117
- function boxOf(element) {
118
- const rect = element.getBoundingClientRect();
119
- return { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) };
120
- }
121
- function isImportantElement(element, depth) {
122
- if (!(element instanceof Element)) return false;
123
- const tag = element.tagName.toLowerCase();
124
- if (depth === 0) return true;
125
- if (['script', 'style', 'svg', 'path', 'rect', 'circle', 'line', 'polyline', 'polygon'].includes(tag)) return false;
126
- if (element.hasAttribute('data-pibo-debug') || element.hasAttribute('data-pibo-session-id') || element.hasAttribute('data-testid')) return true;
127
- if (element.hasAttribute('aria-label') || element.hasAttribute('title') || element.hasAttribute('role')) return true;
128
- if (['button', 'a', 'input', 'textarea', 'select', 'option', 'main', 'aside', 'nav'].includes(tag)) return true;
129
- if (element === document.activeElement) return true;
130
- if (element.getAttribute('aria-selected') === 'true' || element.getAttribute('data-pibo-selected') === 'true' || element.hasAttribute('hidden')) return true;
131
- const text = (element.innerText || element.textContent || '').replace(/\s+/g, ' ').trim();
132
- if (text && element.children.length === 0 && depth <= 4) return true;
133
- return false;
134
- }
135
- function summarizeElement(element, depth, ref, options) {
136
- const ident = identityOf(element);
137
- const node = {
138
- ref,
139
- identity: ident.identity,
140
- identityKind: ident.kind,
141
- depth,
142
- tag: element.tagName.toLowerCase(),
143
- attributes: attrMap(element),
144
- path: elementPath(element),
145
- };
146
- const role = roleOf(element); if (role) node.role = role;
147
- const name = nameOf(element, options); if (name) node.name = name;
148
- const text = redactText(element, options); if (text) node.text = text;
149
- const classes = classSummary(element); if (classes) node.classSummary = classes;
150
- if (document.activeElement === element) node.focused = true;
151
- if (options.includeLayout) node.box = boxOf(element);
152
- return node;
153
- }
154
- function captureSnapshot(options) {
155
- const root = document.querySelector(options.scope);
156
- const nodes = [];
157
- const omitted = { nodes: 0, depth: 0, budget: false };
158
- let refSeq = 0;
159
- function walk(element, depth) {
160
- if (!(element instanceof Element)) return;
161
- if (depth > options.maxDepth) { omitted.depth += 1; return; }
162
- if (isImportantElement(element, depth)) {
163
- if (nodes.length >= options.maxNodes) { omitted.nodes += 1; omitted.budget = true; return; }
164
- const node = summarizeElement(element, depth, '@n' + (++refSeq), options);
165
- nodes.push(node);
166
- }
167
- for (const child of Array.from(element.children)) walk(child, depth + 1);
168
- }
169
- if (root) walk(root, 0);
170
- const active = document.activeElement instanceof Element ? summarizeElement(document.activeElement, 0, '@focus', options) : undefined;
171
- return {
172
- kind: 'snapshot',
173
- createdAt: nowIso(),
174
- url: location.href,
175
- title: document.title || '',
176
- scope: options.scope,
177
- rootFound: Boolean(root),
178
- root: nodes[0],
179
- activeElement: active ? { identity: active.identity, tag: active.tag, name: active.name, path: active.path } : undefined,
180
- nodes,
181
- omitted,
182
- };
183
- }
184
- function mutationTarget(mutation, options) {
185
- const target = mutation.target instanceof Element ? mutation.target : mutation.target.parentElement;
186
- return target ? summarizeElement(target, 0, '@target', options) : undefined;
187
- }
188
- function pushEvent(events, omitted, maxEvents, event) {
189
- if (events.length >= maxEvents) { omitted.events += 1; return; }
190
- events.push(event);
191
- }
192
- function findNewSessionButton() {
193
- const candidates = Array.from(document.querySelectorAll('button'));
194
- return candidates.find((button) => {
195
- const label = [button.getAttribute('aria-label'), button.getAttribute('title'), button.textContent].filter(Boolean).join(' ');
196
- return /New Session/i.test(label);
197
- });
198
- }
199
- async function runWatch(options) {
200
- const root = document.querySelector(options.scope);
201
- const events = [];
202
- const omitted = { events: 0, nodes: 0, depth: 0, budget: false };
203
- const start = performance.now();
204
- const at = () => Math.max(0, Math.round(performance.now() - start));
205
- const originalPushState = history.pushState;
206
- const originalReplaceState = history.replaceState;
207
- let action = undefined;
208
- const routeEvent = (kind, beforeUrl, afterUrl) => {
209
- if (beforeUrl !== afterUrl) pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'route', kind, before: beforeUrl, after: afterUrl });
210
- };
211
- history.pushState = function(...args) {
212
- const beforeUrl = location.href;
213
- const result = originalPushState.apply(this, args);
214
- routeEvent('pushState', beforeUrl, location.href);
215
- return result;
216
- };
217
- history.replaceState = function(...args) {
218
- const beforeUrl = location.href;
219
- const result = originalReplaceState.apply(this, args);
220
- routeEvent('replaceState', beforeUrl, location.href);
221
- return result;
222
- };
223
- const onPopState = () => pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'route', kind: 'popstate', after: location.href });
224
- const onFocusIn = (event) => {
225
- if (!(event.target instanceof Element)) return;
226
- if (root && !root.contains(event.target)) return;
227
- const node = summarizeElement(event.target, 0, '@focus', options);
228
- pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'focus', kind: 'focusin', target: node.identity, node });
229
- };
230
- const observer = root ? new MutationObserver((mutations) => {
231
- for (const mutation of mutations) {
232
- if (mutation.type === 'childList') {
233
- for (const added of Array.from(mutation.addedNodes)) {
234
- if (!(added instanceof Element)) continue;
235
- const node = summarizeElement(added, 0, '@added', options);
236
- pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'dom', kind: 'added', target: node.identity, node });
237
- }
238
- for (const removed of Array.from(mutation.removedNodes)) {
239
- if (!(removed instanceof Element)) continue;
240
- const node = summarizeElement(removed, 0, '@removed', options);
241
- pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'dom', kind: 'removed', target: node.identity, node });
242
- }
243
- } else if (mutation.type === 'attributes') {
244
- const node = mutationTarget(mutation, options);
245
- if (!node) continue;
246
- const name = mutation.attributeName || 'attribute';
247
- const after = mutation.target instanceof Element ? mutation.target.getAttribute(name) : undefined;
248
- pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'dom', kind: 'attr', target: node.identity, detail: name, before: mutation.oldValue || '', after: after || '', node });
249
- } else if (mutation.type === 'characterData') {
250
- const node = mutationTarget(mutation, options);
251
- pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'dom', kind: 'text', target: node ? node.identity : undefined, before: short(mutation.oldValue || '', options.textLimit), after: short(mutation.target.textContent || '', options.textLimit), node });
252
- }
253
- }
254
- }) : undefined;
255
- if (observer && root) observer.observe(root, { childList: true, subtree: true, attributes: true, attributeOldValue: true, characterData: true, characterDataOldValue: true, attributeFilter: ['class', 'style', 'hidden', 'aria-selected', 'aria-expanded', 'data-pibo-selected', 'data-pibo-session-id', 'data-pibo-selected-session-id', 'data-pibo-state', 'data-pibo-debug'] });
256
- document.addEventListener('focusin', onFocusIn, true);
257
- window.addEventListener('popstate', onPopState, true);
258
- const before = captureSnapshot(options);
259
- omitted.nodes += before.omitted.nodes;
260
- omitted.depth += before.omitted.depth;
261
- omitted.budget = omitted.budget || before.omitted.budget;
262
- if (options.action === 'new-session') {
263
- try {
264
- const button = findNewSessionButton();
265
- action = { requested: 'new-session', performed: Boolean(button) };
266
- if (button) {
267
- const node = summarizeElement(button, 0, '@action', options);
268
- pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'action', kind: 'click', target: node.identity, detail: 'New Session', node });
269
- button.click();
270
- } else {
271
- action.error = 'New Session button not found';
272
- }
273
- } catch (error) {
274
- action = { requested: 'new-session', performed: false, error: String(error && error.message ? error.message : error) };
275
- }
276
- }
277
- await new Promise((resolve) => setTimeout(resolve, options.durationMs));
278
- observer?.disconnect();
279
- document.removeEventListener('focusin', onFocusIn, true);
280
- window.removeEventListener('popstate', onPopState, true);
281
- history.pushState = originalPushState;
282
- history.replaceState = originalReplaceState;
283
- const after = captureSnapshot(options);
284
- omitted.nodes += after.omitted.nodes;
285
- omitted.depth += after.omitted.depth;
286
- omitted.budget = omitted.budget || after.omitted.budget || omitted.events > 0;
287
- return {
288
- kind: 'watch',
289
- createdAt: nowIso(),
290
- url: location.href,
291
- title: document.title || '',
292
- scope: options.scope,
293
- durationMs: options.durationMs,
294
- rootFound: Boolean(before.rootFound || after.rootFound),
295
- events,
296
- before,
297
- after,
298
- omitted,
299
- action,
300
- };
301
- }
16
+ return String.raw `
17
+ function nowIso() { return new Date().toISOString(); }
18
+ function safeString(value) { return typeof value === 'string' ? value : ''; }
19
+ function short(value, limit) {
20
+ const text = safeString(value).replace(/\s+/g, ' ').trim();
21
+ return text.length > limit ? text.slice(0, Math.max(0, limit - 1)) + '…' : text;
22
+ }
23
+ function redactText(element, options) {
24
+ const tag = element.tagName.toLowerCase();
25
+ const debug = element.getAttribute('data-pibo-debug') || '';
26
+ if (tag === 'textarea' || tag === 'input' || debug === 'composer') {
27
+ const value = 'value' in element ? String(element.value || '') : '';
28
+ return value ? '[redacted:' + value.length + ' chars]' : '';
29
+ }
30
+ const text = element.innerText || element.textContent || '';
31
+ if (!options.includeText && /message|trace|terminal|composer/i.test(debug)) return text ? '[redacted]' : '';
32
+ return short(text, options.textLimit);
33
+ }
34
+ function classSummary(element) {
35
+ const value = safeString(element.getAttribute('class'));
36
+ if (!value) return undefined;
37
+ const parts = value.split(/\s+/).filter(Boolean);
38
+ const useful = parts.filter((part) => /selected|active|hidden|opacity|translate|animate|border|bg-|text-|ring|disabled|pointer|sr-only/.test(part));
39
+ return (useful.length ? useful : parts.slice(0, 6)).slice(0, 10).join(' ');
40
+ }
41
+ function attrMap(element) {
42
+ const attrs = {};
43
+ const allow = /^(id|role|aria-|data-pibo-|data-testid$|disabled$|checked$|selected$|hidden$|tabindex$|title$)/;
44
+ for (const attr of Array.from(element.attributes || [])) {
45
+ if (!allow.test(attr.name)) continue;
46
+ if (/token|cookie|authorization|secret|password/i.test(attr.name)) {
47
+ attrs[attr.name] = '[redacted]';
48
+ } else {
49
+ attrs[attr.name] = short(attr.value, 120);
50
+ }
51
+ }
52
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
53
+ attrs.value = element.value ? '[redacted:' + element.value.length + ' chars]' : '';
54
+ attrs.disabled = Boolean(element.disabled);
55
+ }
56
+ return attrs;
57
+ }
58
+ function roleOf(element) {
59
+ const explicit = element.getAttribute('role');
60
+ if (explicit) return explicit;
61
+ const tag = element.tagName.toLowerCase();
62
+ if (tag === 'button') return 'button';
63
+ if (tag === 'a') return 'link';
64
+ if (tag === 'textarea') return 'textbox';
65
+ if (tag === 'input') return 'input';
66
+ if (tag === 'select') return 'combobox';
67
+ if (tag === 'main') return 'main';
68
+ if (tag === 'aside') return 'complementary';
69
+ if (tag === 'nav') return 'navigation';
70
+ return undefined;
71
+ }
72
+ function nameOf(element, options) {
73
+ const aria = element.getAttribute('aria-label');
74
+ if (aria) return short(aria, options.textLimit);
75
+ const title = element.getAttribute('title');
76
+ if (title) return short(title, options.textLimit);
77
+ const tag = element.tagName.toLowerCase();
78
+ if (tag === 'input' || tag === 'textarea') return short(element.getAttribute('placeholder') || '', options.textLimit);
79
+ const debug = element.getAttribute('data-pibo-debug');
80
+ if (debug === 'session-row') return short(element.getAttribute('data-pibo-title') || element.innerText || '', options.textLimit);
81
+ return undefined;
82
+ }
83
+ function elementPath(element) {
84
+ const parts = [];
85
+ let current = element;
86
+ while (current && current.nodeType === Node.ELEMENT_NODE && current !== document.documentElement) {
87
+ const tag = current.tagName.toLowerCase();
88
+ const parent = current.parentElement;
89
+ if (!parent) {
90
+ parts.unshift(tag);
91
+ break;
92
+ }
93
+ const index = Array.from(parent.children).filter((child) => child.tagName === current.tagName).indexOf(current) + 1;
94
+ parts.unshift(tag + ':nth-of-type(' + index + ')');
95
+ current = parent;
96
+ }
97
+ return parts.join('>');
98
+ }
99
+ function identityOf(element) {
100
+ const debug = element.getAttribute('data-pibo-debug');
101
+ const session = element.getAttribute('data-pibo-session-id');
102
+ const room = element.getAttribute('data-pibo-room-id');
103
+ const view = element.getAttribute('data-pibo-view-id');
104
+ const testId = element.getAttribute('data-testid');
105
+ const id = element.id;
106
+ if (debug && session) return { identity: debug + ':' + session, kind: 'pibo-session' };
107
+ if (debug && room) return { identity: debug + ':' + room, kind: 'pibo-room' };
108
+ if (debug && view) return { identity: debug + ':' + view, kind: 'pibo-view' };
109
+ if (debug) return { identity: debug, kind: 'pibo-debug' };
110
+ if (testId) return { identity: 'testid:' + testId, kind: 'testid' };
111
+ if (id) return { identity: 'id:' + id, kind: 'id' };
112
+ const role = roleOf(element);
113
+ const name = nameOf(element, { textLimit: 40 }) || '';
114
+ if (role && name) return { identity: role + ':' + name, kind: 'role-name' };
115
+ return { identity: 'path:' + elementPath(element), kind: 'path' };
116
+ }
117
+ function boxOf(element) {
118
+ const rect = element.getBoundingClientRect();
119
+ return { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) };
120
+ }
121
+ function isImportantElement(element, depth) {
122
+ if (!(element instanceof Element)) return false;
123
+ const tag = element.tagName.toLowerCase();
124
+ if (depth === 0) return true;
125
+ if (['script', 'style', 'svg', 'path', 'rect', 'circle', 'line', 'polyline', 'polygon'].includes(tag)) return false;
126
+ if (element.hasAttribute('data-pibo-debug') || element.hasAttribute('data-pibo-session-id') || element.hasAttribute('data-testid')) return true;
127
+ if (element.hasAttribute('aria-label') || element.hasAttribute('title') || element.hasAttribute('role')) return true;
128
+ if (['button', 'a', 'input', 'textarea', 'select', 'option', 'main', 'aside', 'nav'].includes(tag)) return true;
129
+ if (element === document.activeElement) return true;
130
+ if (element.getAttribute('aria-selected') === 'true' || element.getAttribute('data-pibo-selected') === 'true' || element.hasAttribute('hidden')) return true;
131
+ const text = (element.innerText || element.textContent || '').replace(/\s+/g, ' ').trim();
132
+ if (text && element.children.length === 0 && depth <= 4) return true;
133
+ return false;
134
+ }
135
+ function summarizeElement(element, depth, ref, options) {
136
+ const ident = identityOf(element);
137
+ const node = {
138
+ ref,
139
+ identity: ident.identity,
140
+ identityKind: ident.kind,
141
+ depth,
142
+ tag: element.tagName.toLowerCase(),
143
+ attributes: attrMap(element),
144
+ path: elementPath(element),
145
+ };
146
+ const role = roleOf(element); if (role) node.role = role;
147
+ const name = nameOf(element, options); if (name) node.name = name;
148
+ const text = redactText(element, options); if (text) node.text = text;
149
+ const classes = classSummary(element); if (classes) node.classSummary = classes;
150
+ if (document.activeElement === element) node.focused = true;
151
+ if (options.includeLayout) node.box = boxOf(element);
152
+ return node;
153
+ }
154
+ function captureSnapshot(options) {
155
+ const root = document.querySelector(options.scope);
156
+ const nodes = [];
157
+ const omitted = { nodes: 0, depth: 0, budget: false };
158
+ let refSeq = 0;
159
+ function walk(element, depth) {
160
+ if (!(element instanceof Element)) return;
161
+ if (depth > options.maxDepth) { omitted.depth += 1; return; }
162
+ if (isImportantElement(element, depth)) {
163
+ if (nodes.length >= options.maxNodes) { omitted.nodes += 1; omitted.budget = true; return; }
164
+ const node = summarizeElement(element, depth, '@n' + (++refSeq), options);
165
+ nodes.push(node);
166
+ }
167
+ for (const child of Array.from(element.children)) walk(child, depth + 1);
168
+ }
169
+ if (root) walk(root, 0);
170
+ const active = document.activeElement instanceof Element ? summarizeElement(document.activeElement, 0, '@focus', options) : undefined;
171
+ return {
172
+ kind: 'snapshot',
173
+ createdAt: nowIso(),
174
+ url: location.href,
175
+ title: document.title || '',
176
+ scope: options.scope,
177
+ rootFound: Boolean(root),
178
+ root: nodes[0],
179
+ activeElement: active ? { identity: active.identity, tag: active.tag, name: active.name, path: active.path } : undefined,
180
+ nodes,
181
+ omitted,
182
+ };
183
+ }
184
+ function mutationTarget(mutation, options) {
185
+ const target = mutation.target instanceof Element ? mutation.target : mutation.target.parentElement;
186
+ return target ? summarizeElement(target, 0, '@target', options) : undefined;
187
+ }
188
+ function pushEvent(events, omitted, maxEvents, event) {
189
+ if (events.length >= maxEvents) { omitted.events += 1; return; }
190
+ events.push(event);
191
+ }
192
+ function findNewSessionButton() {
193
+ const candidates = Array.from(document.querySelectorAll('button'));
194
+ return candidates.find((button) => {
195
+ const label = [button.getAttribute('aria-label'), button.getAttribute('title'), button.textContent].filter(Boolean).join(' ');
196
+ return /New Session/i.test(label);
197
+ });
198
+ }
199
+ async function runWatch(options) {
200
+ const root = document.querySelector(options.scope);
201
+ const events = [];
202
+ const omitted = { events: 0, nodes: 0, depth: 0, budget: false };
203
+ const start = performance.now();
204
+ const at = () => Math.max(0, Math.round(performance.now() - start));
205
+ const originalPushState = history.pushState;
206
+ const originalReplaceState = history.replaceState;
207
+ let action = undefined;
208
+ const routeEvent = (kind, beforeUrl, afterUrl) => {
209
+ if (beforeUrl !== afterUrl) pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'route', kind, before: beforeUrl, after: afterUrl });
210
+ };
211
+ history.pushState = function(...args) {
212
+ const beforeUrl = location.href;
213
+ const result = originalPushState.apply(this, args);
214
+ routeEvent('pushState', beforeUrl, location.href);
215
+ return result;
216
+ };
217
+ history.replaceState = function(...args) {
218
+ const beforeUrl = location.href;
219
+ const result = originalReplaceState.apply(this, args);
220
+ routeEvent('replaceState', beforeUrl, location.href);
221
+ return result;
222
+ };
223
+ const onPopState = () => pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'route', kind: 'popstate', after: location.href });
224
+ const onFocusIn = (event) => {
225
+ if (!(event.target instanceof Element)) return;
226
+ if (root && !root.contains(event.target)) return;
227
+ const node = summarizeElement(event.target, 0, '@focus', options);
228
+ pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'focus', kind: 'focusin', target: node.identity, node });
229
+ };
230
+ const observer = root ? new MutationObserver((mutations) => {
231
+ for (const mutation of mutations) {
232
+ if (mutation.type === 'childList') {
233
+ for (const added of Array.from(mutation.addedNodes)) {
234
+ if (!(added instanceof Element)) continue;
235
+ const node = summarizeElement(added, 0, '@added', options);
236
+ pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'dom', kind: 'added', target: node.identity, node });
237
+ }
238
+ for (const removed of Array.from(mutation.removedNodes)) {
239
+ if (!(removed instanceof Element)) continue;
240
+ const node = summarizeElement(removed, 0, '@removed', options);
241
+ pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'dom', kind: 'removed', target: node.identity, node });
242
+ }
243
+ } else if (mutation.type === 'attributes') {
244
+ const node = mutationTarget(mutation, options);
245
+ if (!node) continue;
246
+ const name = mutation.attributeName || 'attribute';
247
+ const after = mutation.target instanceof Element ? mutation.target.getAttribute(name) : undefined;
248
+ pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'dom', kind: 'attr', target: node.identity, detail: name, before: mutation.oldValue || '', after: after || '', node });
249
+ } else if (mutation.type === 'characterData') {
250
+ const node = mutationTarget(mutation, options);
251
+ pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'dom', kind: 'text', target: node ? node.identity : undefined, before: short(mutation.oldValue || '', options.textLimit), after: short(mutation.target.textContent || '', options.textLimit), node });
252
+ }
253
+ }
254
+ }) : undefined;
255
+ if (observer && root) observer.observe(root, { childList: true, subtree: true, attributes: true, attributeOldValue: true, characterData: true, characterDataOldValue: true, attributeFilter: ['class', 'style', 'hidden', 'aria-selected', 'aria-expanded', 'data-pibo-selected', 'data-pibo-session-id', 'data-pibo-selected-session-id', 'data-pibo-state', 'data-pibo-debug'] });
256
+ document.addEventListener('focusin', onFocusIn, true);
257
+ window.addEventListener('popstate', onPopState, true);
258
+ const before = captureSnapshot(options);
259
+ omitted.nodes += before.omitted.nodes;
260
+ omitted.depth += before.omitted.depth;
261
+ omitted.budget = omitted.budget || before.omitted.budget;
262
+ if (options.action === 'new-session') {
263
+ try {
264
+ const button = findNewSessionButton();
265
+ action = { requested: 'new-session', performed: Boolean(button) };
266
+ if (button) {
267
+ const node = summarizeElement(button, 0, '@action', options);
268
+ pushEvent(events, omitted, options.maxEvents, { t: at(), source: 'action', kind: 'click', target: node.identity, detail: 'New Session', node });
269
+ button.click();
270
+ } else {
271
+ action.error = 'New Session button not found';
272
+ }
273
+ } catch (error) {
274
+ action = { requested: 'new-session', performed: false, error: String(error && error.message ? error.message : error) };
275
+ }
276
+ }
277
+ await new Promise((resolve) => setTimeout(resolve, options.durationMs));
278
+ observer?.disconnect();
279
+ document.removeEventListener('focusin', onFocusIn, true);
280
+ window.removeEventListener('popstate', onPopState, true);
281
+ history.pushState = originalPushState;
282
+ history.replaceState = originalReplaceState;
283
+ const after = captureSnapshot(options);
284
+ omitted.nodes += after.omitted.nodes;
285
+ omitted.depth += after.omitted.depth;
286
+ omitted.budget = omitted.budget || after.omitted.budget || omitted.events > 0;
287
+ return {
288
+ kind: 'watch',
289
+ createdAt: nowIso(),
290
+ url: location.href,
291
+ title: document.title || '',
292
+ scope: options.scope,
293
+ durationMs: options.durationMs,
294
+ rootFound: Boolean(before.rootFound || after.rootFound),
295
+ events,
296
+ before,
297
+ after,
298
+ omitted,
299
+ action,
300
+ };
301
+ }
302
302
  `;
303
303
  }