@pasko70/pibo 1.9.2 → 1.9.4

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 (120) 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/assets/{dist-CjYtD7ZT.js → dist-9a5adSX0.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{dist-9lsp1UpA.js → dist-BJftG-mN.js} +1 -1
  16. package/dist/apps/chat-ui/assets/{dist-2KdPXbMT.js → dist-BmIDFlGO.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-BWbWIOcD.js → dist-CCNYUBAQ.js} +1 -1
  18. package/dist/apps/chat-ui/assets/{dist-Cge8JklW.js → dist-Cmr0ZAV7.js} +1 -1
  19. package/dist/apps/chat-ui/assets/{dist-DFhhiR8M.js → dist-Crc-B1sZ.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dist-5GM30SQK.js → dist-DHPwmWcR.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dist-C-5u2QIS.js → dist-U3lqQ0aI.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-Idz5kzy8.js → dist-_poCjCWK.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-Etxmpyxg.js → dist-ffW7mw39.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-BYKMZlI0.js → dist-w2d_3LSu.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{index-BqD_bm7z.js → index-rXMhSu4U.js} +9 -9
  26. package/dist/apps/chat-ui/index.html +17 -17
  27. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  28. package/dist/apps/chat-ui/sw.js +47 -47
  29. package/dist/apps/chat-vscode-web/assets/index-BAMxIaI_.js +41 -0
  30. package/dist/apps/chat-vscode-web/index.html +13 -13
  31. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  32. package/dist/apps/context-files-ui/index.html +11 -11
  33. package/dist/cli.js +39 -39
  34. package/dist/compute/cli.js +54 -54
  35. package/dist/core/session-router.js +5 -2
  36. package/dist/cron/cli.js +15 -15
  37. package/dist/cron/store.js +49 -49
  38. package/dist/data/cli.js +23 -23
  39. package/dist/data/event-log.js +23 -23
  40. package/dist/data/message-store.js +20 -20
  41. package/dist/data/navigation-store.js +9 -9
  42. package/dist/data/observation-store.js +4 -4
  43. package/dist/data/payload-store.js +17 -17
  44. package/dist/data/schema.js +431 -431
  45. package/dist/data/session-store.js +4 -4
  46. package/dist/data/telemetry-queries.js +54 -54
  47. package/dist/data/telemetry.js +197 -197
  48. package/dist/debug/events.js +12 -12
  49. package/dist/debug/failures.js +6 -6
  50. package/dist/debug/index.js +227 -227
  51. package/dist/debug/messages.js +6 -6
  52. package/dist/debug/pty.js +124 -124
  53. package/dist/debug/session.js +29 -29
  54. package/dist/debug/tools.js +5 -5
  55. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  56. package/dist/debug/web-streaming-browser-library.js +925 -925
  57. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  58. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  59. package/dist/debug/web.js +93 -93
  60. package/dist/gateway/cli.js +19 -19
  61. package/dist/mcp/config-command.js +53 -53
  62. package/dist/mcp/index.js +21 -21
  63. package/dist/mcp/registry.js +11 -11
  64. package/dist/pi-packages/cli.js +11 -11
  65. package/dist/plugins/context-files-store.js +110 -110
  66. package/dist/plugins/context-files.js +4 -4
  67. package/dist/ralph/cli.js +18 -18
  68. package/dist/ralph/templates.js +140 -140
  69. package/dist/reliability/store.js +226 -226
  70. package/dist/session-ui/sessionActivity.js +30 -0
  71. package/dist/sessions/pibo-data-store.js +16 -16
  72. package/dist/sessions/sqlite-store.js +53 -53
  73. package/dist/setup/cli.js +58 -58
  74. package/dist/signals/projector.js +26 -11
  75. package/dist/signals/registry.js +41 -0
  76. package/dist/tools/agent-browser-wrapper.js +80 -80
  77. package/dist/tools/browser-use-cdp.js +12 -12
  78. package/dist/tools/browser-use-wrapper.js +762 -762
  79. package/dist/tools/guides.js +538 -538
  80. package/dist/tools/index.js +99 -99
  81. package/dist/tools/runtime/node-worker-source.js +205 -205
  82. package/dist/tools/runtime/python-worker-source.js +177 -177
  83. package/dist/vscode/cli.js +9 -9
  84. package/dist/web-annotations/cdp.js +900 -900
  85. package/dist/web-annotations/store.js +96 -96
  86. package/docs/README.md +23 -23
  87. package/docs/ops/install-developer-host.md +112 -112
  88. package/docs/ops/install-user-host.md +96 -96
  89. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  90. package/docs/ops/vscode-extension-release.md +160 -160
  91. package/package.json +95 -95
  92. package/skills/builtin/graphify/SKILL.md +52 -52
  93. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  94. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  95. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  96. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  97. package/skills/builtin/prd/SKILL.md +143 -143
  98. package/skills/builtin/ralph-loop/SKILL.md +359 -359
  99. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  100. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  101. package/skills/builtin/skill-creator/SKILL.md +513 -513
  102. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  103. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  104. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  105. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  106. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  107. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  108. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  109. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  110. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  111. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  112. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  113. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  114. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  115. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  116. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  117. package/skills/builtin/web-annotations/SKILL.md +93 -93
  118. package/src/mcp/LICENSE.mcp-cli +21 -21
  119. package/dist/apps/chat-vscode-web/assets/index-Cst9OUkC.js +0 -41
  120. package/dist/session-ui/activeTurn.js +0 -126
@@ -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
  }