@pasko70/pibo 1.9.11 → 1.9.13

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 (111) 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 +25 -14
  12. package/dist/apps/chat/static-assets.js +854 -854
  13. package/dist/apps/chat/trace.js +2 -0
  14. package/dist/apps/chat/web-app.js +7 -0
  15. package/dist/apps/chat/workflow-persistence.js +255 -255
  16. package/dist/apps/chat-ui/index.html +16 -16
  17. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  18. package/dist/apps/chat-ui/sw.js +47 -47
  19. package/dist/apps/chat-vscode-web/index.html +12 -12
  20. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  21. package/dist/apps/context-files-ui/index.html +11 -11
  22. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  23. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.9.13.vsix +0 -0
  24. package/dist/cli.js +39 -39
  25. package/dist/compute/cli.js +54 -54
  26. package/dist/cron/cli.js +15 -15
  27. package/dist/cron/store.js +49 -49
  28. package/dist/data/cli.js +23 -23
  29. package/dist/data/event-log.js +23 -23
  30. package/dist/data/ingest-service.js +7 -0
  31. package/dist/data/message-store.js +31 -20
  32. package/dist/data/navigation-store.js +9 -9
  33. package/dist/data/observation-store.js +4 -4
  34. package/dist/data/payload-store.js +17 -17
  35. package/dist/data/schema.js +433 -431
  36. package/dist/data/session-store.js +4 -4
  37. package/dist/data/telemetry-queries.js +54 -54
  38. package/dist/data/telemetry.js +197 -197
  39. package/dist/debug/events.js +12 -12
  40. package/dist/debug/failures.js +6 -6
  41. package/dist/debug/index.js +227 -227
  42. package/dist/debug/messages.js +6 -6
  43. package/dist/debug/pty.js +124 -124
  44. package/dist/debug/session.js +29 -29
  45. package/dist/debug/tools.js +5 -5
  46. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  47. package/dist/debug/web-streaming-browser-library.js +925 -925
  48. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  49. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  50. package/dist/debug/web.js +93 -93
  51. package/dist/gateway/cli.js +19 -19
  52. package/dist/mcp/config-command.js +53 -53
  53. package/dist/mcp/index.js +21 -21
  54. package/dist/mcp/registry.js +11 -11
  55. package/dist/pi-packages/cli.js +11 -11
  56. package/dist/plugins/context-files-store.js +110 -110
  57. package/dist/plugins/context-files.js +4 -4
  58. package/dist/ralph/cli.js +18 -18
  59. package/dist/ralph/templates.js +140 -140
  60. package/dist/reliability/store.js +226 -226
  61. package/dist/resources/lifecycle.js +4 -6
  62. package/dist/resources/reaper-state.js +30 -3
  63. package/dist/resources/reaper.js +43 -15
  64. package/dist/sessions/pibo-data-store.js +16 -16
  65. package/dist/sessions/sqlite-store.js +53 -53
  66. package/dist/setup/cli.js +58 -58
  67. package/dist/shared/trace-engine.js +3 -2
  68. package/dist/shared/trace-event-projection.js +26 -0
  69. package/dist/tools/agent-browser-wrapper.js +80 -80
  70. package/dist/tools/browser-use-cdp.js +12 -12
  71. package/dist/tools/browser-use-wrapper.js +762 -762
  72. package/dist/tools/guides.js +538 -538
  73. package/dist/tools/index.js +99 -99
  74. package/dist/tools/runtime/node-worker-source.js +205 -205
  75. package/dist/tools/runtime/python-worker-source.js +177 -177
  76. package/dist/vscode/cli.js +9 -9
  77. package/dist/web-annotations/cdp.js +900 -900
  78. package/dist/web-annotations/store.js +96 -96
  79. package/docs/README.md +23 -23
  80. package/docs/ops/install-developer-host.md +112 -112
  81. package/docs/ops/install-user-host.md +96 -96
  82. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  83. package/docs/ops/vscode-extension-release.md +160 -160
  84. package/package.json +95 -95
  85. package/skills/builtin/graphify/SKILL.md +52 -52
  86. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  87. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  88. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  89. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  90. package/skills/builtin/prd/SKILL.md +143 -143
  91. package/skills/builtin/ralph-loop/SKILL.md +359 -359
  92. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  93. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  94. package/skills/builtin/skill-creator/SKILL.md +513 -513
  95. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  96. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  97. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  98. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  99. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  100. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  101. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  102. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  103. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  104. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  105. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  106. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  107. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  108. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  109. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  110. package/skills/builtin/web-annotations/SKILL.md +93 -93
  111. 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
  }