@pasko70/pibo 1.4.0 → 1.4.2

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 (129) 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 +82 -82
  8. package/dist/apps/chat/data/project-service.js +166 -166
  9. package/dist/apps/chat/data/read-state-service.js +18 -18
  10. package/dist/apps/chat/data/timeline-query-service.js +8 -8
  11. package/dist/apps/chat/model-catalog.js +5 -1
  12. package/dist/apps/chat/static-assets.js +853 -853
  13. package/dist/apps/chat/workflow-persistence.js +255 -255
  14. package/dist/apps/chat-ui/assets/{dist-7YaJd19a.js → dist-B2BEpL7n.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{dist-CiYeO8nN.js → dist-BAGS_xkV.js} +1 -1
  16. package/dist/apps/chat-ui/assets/{dist-BIvPnn_C.js → dist-BAXNalar.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-CjSI6y5z.js → dist-BwUvs6Ph.js} +1 -1
  18. package/dist/apps/chat-ui/assets/{dist-Ubstha8t.js → dist-C0zsJ8II.js} +1 -1
  19. package/dist/apps/chat-ui/assets/{dist-KXCMNKIL.js → dist-C3PnEkhb.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dist-CVQU42Fn.js → dist-CYPL-B2Z.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dist-BbNE72h8.js → dist-CiDSXgtg.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-D7TCkoFT.js → dist-DnACFKyO.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-ChSZNqKE.js → dist-ZB1-ui2y.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-8Noo5eCN.js → dist-vKlxFkTa.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{index-CmxtUVG1.js → index-0x7tuTNX.js} +3 -3
  26. package/dist/apps/chat-ui/assets/{index-C25VYnyb.css → index-B-qaya1G.css} +1 -1
  27. package/dist/apps/chat-ui/index.html +18 -18
  28. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  29. package/dist/apps/chat-ui/sw.js +41 -41
  30. package/dist/apps/chat-vscode-web/index.html +12 -12
  31. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  32. package/dist/apps/context-files-ui/index.html +11 -11
  33. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  34. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.4.2.vsix +0 -0
  35. package/dist/bin/pibo.js +0 -0
  36. package/dist/bin/rg.js +0 -0
  37. package/dist/cli.js +35 -35
  38. package/dist/compute/cli.js +54 -54
  39. package/dist/core/runtime.js +2 -0
  40. package/dist/core/session-router.js +1 -0
  41. package/dist/cron/cli.js +15 -15
  42. package/dist/cron/store.js +49 -49
  43. package/dist/data/cli.js +23 -23
  44. package/dist/data/event-log.js +23 -23
  45. package/dist/data/message-store.js +20 -20
  46. package/dist/data/navigation-store.js +9 -9
  47. package/dist/data/observation-store.js +4 -4
  48. package/dist/data/payload-store.js +17 -17
  49. package/dist/data/schema.js +429 -429
  50. package/dist/data/session-store.js +4 -4
  51. package/dist/data/telemetry-queries.js +54 -54
  52. package/dist/data/telemetry.js +156 -156
  53. package/dist/debug/events.js +12 -12
  54. package/dist/debug/failures.js +6 -6
  55. package/dist/debug/index.js +207 -207
  56. package/dist/debug/messages.js +6 -6
  57. package/dist/debug/pty.js +124 -124
  58. package/dist/debug/session.js +29 -29
  59. package/dist/debug/tools.js +5 -5
  60. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  61. package/dist/debug/web-streaming-browser-library.js +925 -925
  62. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  63. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  64. package/dist/debug/web.js +93 -93
  65. package/dist/gateway/cli.js +19 -19
  66. package/dist/mcp/config-command.js +53 -53
  67. package/dist/mcp/index.js +21 -21
  68. package/dist/mcp/registry.js +11 -11
  69. package/dist/pi-packages/cli.js +11 -11
  70. package/dist/plugins/context-files-store.js +110 -110
  71. package/dist/plugins/context-files.js +4 -4
  72. package/dist/providers/glm.js +59 -0
  73. package/dist/ralph/cli.js +18 -18
  74. package/dist/ralph/templates.js +140 -140
  75. package/dist/reliability/store.js +226 -226
  76. package/dist/sessions/pibo-data-store.js +16 -16
  77. package/dist/sessions/sqlite-store.js +53 -53
  78. package/dist/setup/cli.js +58 -58
  79. package/dist/tools/agent-browser-wrapper.js +80 -80
  80. package/dist/tools/browser-use-cdp.js +12 -12
  81. package/dist/tools/browser-use-wrapper.js +762 -762
  82. package/dist/tools/guides.js +455 -455
  83. package/dist/tools/index.js +99 -99
  84. package/dist/tools/runtime/node-worker-source.js +205 -205
  85. package/dist/tools/runtime/python-worker-source.js +177 -177
  86. package/dist/vscode/cli.js +9 -9
  87. package/dist/web-annotations/cdp.js +900 -900
  88. package/dist/web-annotations/store.js +96 -96
  89. package/docs/README.md +23 -23
  90. package/docs/ops/install-developer-host.md +112 -112
  91. package/docs/ops/install-user-host.md +96 -96
  92. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  93. package/docs/ops/vscode-extension-release.md +160 -160
  94. package/package.json +1 -1
  95. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  96. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  97. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  98. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  99. package/skills/builtin/prd/SKILL.md +143 -143
  100. package/skills/builtin/ralph-loop/SKILL.md +359 -359
  101. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  102. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  103. package/skills/builtin/skill-creator/SKILL.md +513 -513
  104. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  105. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  106. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  107. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  108. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  109. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  110. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  111. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  112. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  113. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  114. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  115. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  116. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  117. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  118. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  119. package/skills/builtin/web-annotations/SKILL.md +93 -93
  120. package/src/mcp/LICENSE.mcp-cli +21 -21
  121. package/dist/apps/vscode-artifacts/pibo-vscode-1.3.0.vsix +0 -0
  122. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.3.vsix +0 -0
  123. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.4.vsix +0 -0
  124. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.5.vsix +0 -0
  125. package/dist/core/shared-app.js +0 -17
  126. package/dist/data/final-app-space-cutover-migration.js +0 -728
  127. package/dist/data/shared-app-migration.js +0 -757
  128. package/dist/session-ui/ownerViewModel.js +0 -27
  129. package/dist/shared-app.js +0 -4
@@ -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
  }