opendevbrowser 0.0.16 → 0.0.17

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 (210) hide show
  1. package/README.md +51 -27
  2. package/dist/browser/annotation-manager.d.ts +3 -0
  3. package/dist/browser/annotation-manager.d.ts.map +1 -1
  4. package/dist/browser/browser-manager.d.ts +6 -1
  5. package/dist/browser/browser-manager.d.ts.map +1 -1
  6. package/dist/browser/canvas-client.d.ts +53 -0
  7. package/dist/browser/canvas-client.d.ts.map +1 -0
  8. package/dist/browser/canvas-code-sync-manager.d.ts +79 -0
  9. package/dist/browser/canvas-code-sync-manager.d.ts.map +1 -0
  10. package/dist/browser/canvas-manager.d.ts +94 -0
  11. package/dist/browser/canvas-manager.d.ts.map +1 -0
  12. package/dist/browser/canvas-runtime-preview-bridge.d.ts +20 -0
  13. package/dist/browser/canvas-runtime-preview-bridge.d.ts.map +1 -0
  14. package/dist/browser/canvas-session-sync-manager.d.ts +21 -0
  15. package/dist/browser/canvas-session-sync-manager.d.ts.map +1 -0
  16. package/dist/browser/manager-types.d.ts +13 -1
  17. package/dist/browser/manager-types.d.ts.map +1 -1
  18. package/dist/browser/ops-browser-manager.d.ts +11 -1
  19. package/dist/browser/ops-browser-manager.d.ts.map +1 -1
  20. package/dist/canvas/code-sync/apply-tsx.d.ts +23 -0
  21. package/dist/canvas/code-sync/apply-tsx.d.ts.map +1 -0
  22. package/dist/canvas/code-sync/graph.d.ts +5 -0
  23. package/dist/canvas/code-sync/graph.d.ts.map +1 -0
  24. package/dist/canvas/code-sync/hash.d.ts +3 -0
  25. package/dist/canvas/code-sync/hash.d.ts.map +1 -0
  26. package/dist/canvas/code-sync/import.d.ts +18 -0
  27. package/dist/canvas/code-sync/import.d.ts.map +1 -0
  28. package/dist/canvas/code-sync/manifest.d.ts +5 -0
  29. package/dist/canvas/code-sync/manifest.d.ts.map +1 -0
  30. package/dist/canvas/code-sync/tsx-adapter.d.ts +8 -0
  31. package/dist/canvas/code-sync/tsx-adapter.d.ts.map +1 -0
  32. package/dist/canvas/code-sync/types.d.ts +152 -0
  33. package/dist/canvas/code-sync/types.d.ts.map +1 -0
  34. package/dist/canvas/code-sync/write.d.ts +9 -0
  35. package/dist/canvas/code-sync/write.d.ts.map +1 -0
  36. package/dist/canvas/document-store.d.ts +81 -0
  37. package/dist/canvas/document-store.d.ts.map +1 -0
  38. package/dist/canvas/export.d.ts +12 -0
  39. package/dist/canvas/export.d.ts.map +1 -0
  40. package/dist/canvas/repo-store.d.ts +10 -0
  41. package/dist/canvas/repo-store.d.ts.map +1 -0
  42. package/dist/canvas/surface-palette.d.ts +15 -0
  43. package/dist/canvas/surface-palette.d.ts.map +1 -0
  44. package/dist/canvas/types.d.ts +255 -0
  45. package/dist/canvas/types.d.ts.map +1 -0
  46. package/dist/canvas-runtime-preview-bridge-HBEHXM4T.js +7 -0
  47. package/dist/canvas-runtime-preview-bridge-HBEHXM4T.js.map +1 -0
  48. package/dist/{chunk-ST7CO5FA.js → chunk-5J3IFL3X.js} +11577 -13539
  49. package/dist/chunk-5J3IFL3X.js.map +1 -0
  50. package/dist/chunk-D633UO34.js +8149 -0
  51. package/dist/chunk-D633UO34.js.map +1 -0
  52. package/dist/{chunk-7W3SPXIB.js → chunk-FUSXMW3G.js} +4 -1
  53. package/dist/chunk-TBUCZX4A.js +34 -0
  54. package/dist/chunk-TBUCZX4A.js.map +1 -0
  55. package/dist/chunk-V7KUDHDG.js +276 -0
  56. package/dist/chunk-V7KUDHDG.js.map +1 -0
  57. package/dist/chunk-Y2KL55OG.js +59 -0
  58. package/dist/chunk-Y2KL55OG.js.map +1 -0
  59. package/dist/cli/args.d.ts +3 -3
  60. package/dist/cli/args.d.ts.map +1 -1
  61. package/dist/cli/commands/annotate.d.ts +11 -0
  62. package/dist/cli/commands/annotate.d.ts.map +1 -1
  63. package/dist/cli/commands/canvas.d.ts +45 -0
  64. package/dist/cli/commands/canvas.d.ts.map +1 -0
  65. package/dist/cli/commands/devtools/perf.d.ts.map +1 -1
  66. package/dist/cli/commands/devtools/screenshot.d.ts +1 -0
  67. package/dist/cli/commands/devtools/screenshot.d.ts.map +1 -1
  68. package/dist/cli/commands/dom/attr.d.ts.map +1 -1
  69. package/dist/cli/commands/dom/checked.d.ts.map +1 -1
  70. package/dist/cli/commands/dom/enabled.d.ts.map +1 -1
  71. package/dist/cli/commands/dom/html.d.ts.map +1 -1
  72. package/dist/cli/commands/dom/text.d.ts.map +1 -1
  73. package/dist/cli/commands/dom/value.d.ts.map +1 -1
  74. package/dist/cli/commands/dom/visible.d.ts.map +1 -1
  75. package/dist/cli/commands/export/clone-component.d.ts +9 -0
  76. package/dist/cli/commands/export/clone-component.d.ts.map +1 -1
  77. package/dist/cli/commands/export/clone-page.d.ts +8 -0
  78. package/dist/cli/commands/export/clone-page.d.ts.map +1 -1
  79. package/dist/cli/commands/interact/check.d.ts.map +1 -1
  80. package/dist/cli/commands/interact/click.d.ts.map +1 -1
  81. package/dist/cli/commands/interact/hover.d.ts.map +1 -1
  82. package/dist/cli/commands/interact/press.d.ts.map +1 -1
  83. package/dist/cli/commands/interact/scroll-into-view.d.ts.map +1 -1
  84. package/dist/cli/commands/interact/scroll.d.ts.map +1 -1
  85. package/dist/cli/commands/interact/select.d.ts.map +1 -1
  86. package/dist/cli/commands/interact/type.d.ts.map +1 -1
  87. package/dist/cli/commands/interact/uncheck.d.ts.map +1 -1
  88. package/dist/cli/commands/native.d.ts +12 -1
  89. package/dist/cli/commands/native.d.ts.map +1 -1
  90. package/dist/cli/commands/nav/goto.d.ts.map +1 -1
  91. package/dist/cli/commands/nav/snapshot.d.ts.map +1 -1
  92. package/dist/cli/commands/nav/wait.d.ts.map +1 -1
  93. package/dist/cli/commands/serve.d.ts +5 -0
  94. package/dist/cli/commands/serve.d.ts.map +1 -1
  95. package/dist/cli/commands/session/connect.d.ts.map +1 -1
  96. package/dist/cli/commands/status.d.ts +5 -0
  97. package/dist/cli/commands/status.d.ts.map +1 -1
  98. package/dist/cli/daemon-commands.d.ts.map +1 -1
  99. package/dist/cli/help.d.ts +5 -0
  100. package/dist/cli/help.d.ts.map +1 -1
  101. package/dist/cli/index.js +724 -163
  102. package/dist/cli/index.js.map +1 -1
  103. package/dist/cli/remote-canvas-manager.d.ts +8 -0
  104. package/dist/cli/remote-canvas-manager.d.ts.map +1 -0
  105. package/dist/cli/remote-manager.d.ts +3 -1
  106. package/dist/cli/remote-manager.d.ts.map +1 -1
  107. package/dist/cli/remote-relay.d.ts +2 -0
  108. package/dist/cli/remote-relay.d.ts.map +1 -1
  109. package/dist/cli/utils/parse.d.ts +1 -0
  110. package/dist/cli/utils/parse.d.ts.map +1 -1
  111. package/dist/core/bootstrap.d.ts.map +1 -1
  112. package/dist/core/types.d.ts +2 -0
  113. package/dist/core/types.d.ts.map +1 -1
  114. package/dist/fs-UMRKOBNN.js +7 -0
  115. package/dist/fs-UMRKOBNN.js.map +1 -0
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +192 -67
  118. package/dist/index.js.map +1 -1
  119. package/dist/{macros-NUBRM44Y.js → macros-ND2M7LWU.js} +2 -2
  120. package/dist/opendevbrowser.d.ts.map +1 -1
  121. package/dist/opendevbrowser.js +192 -67
  122. package/dist/opendevbrowser.js.map +1 -1
  123. package/dist/providers/index.d.ts.map +1 -1
  124. package/dist/providers/shopping/index.d.ts.map +1 -1
  125. package/dist/providers-G3LRHQXX.js +121 -0
  126. package/dist/providers-G3LRHQXX.js.map +1 -0
  127. package/dist/relay/protocol.d.ts +85 -3
  128. package/dist/relay/protocol.d.ts.map +1 -1
  129. package/dist/relay/relay-server.d.ts +14 -1
  130. package/dist/relay/relay-server.d.ts.map +1 -1
  131. package/dist/relay/relay-types.d.ts +3 -0
  132. package/dist/relay/relay-types.d.ts.map +1 -1
  133. package/dist/runtime-factory-BICHDPE7.js +13 -0
  134. package/dist/runtime-factory-BICHDPE7.js.map +1 -0
  135. package/dist/tools/annotate.d.ts.map +1 -1
  136. package/dist/tools/canvas.d.ts +4 -0
  137. package/dist/tools/canvas.d.ts.map +1 -0
  138. package/dist/tools/check.d.ts.map +1 -1
  139. package/dist/tools/click.d.ts.map +1 -1
  140. package/dist/tools/clone_component.d.ts.map +1 -1
  141. package/dist/tools/clone_page.d.ts.map +1 -1
  142. package/dist/tools/connect.d.ts.map +1 -1
  143. package/dist/tools/deps.d.ts +2 -0
  144. package/dist/tools/deps.d.ts.map +1 -1
  145. package/dist/tools/dom_get_html.d.ts.map +1 -1
  146. package/dist/tools/dom_get_text.d.ts.map +1 -1
  147. package/dist/tools/get_attr.d.ts.map +1 -1
  148. package/dist/tools/get_value.d.ts.map +1 -1
  149. package/dist/tools/goto.d.ts.map +1 -1
  150. package/dist/tools/hover.d.ts.map +1 -1
  151. package/dist/tools/index.d.ts.map +1 -1
  152. package/dist/tools/is_checked.d.ts.map +1 -1
  153. package/dist/tools/is_enabled.d.ts.map +1 -1
  154. package/dist/tools/is_visible.d.ts.map +1 -1
  155. package/dist/tools/launch.d.ts.map +1 -1
  156. package/dist/tools/macro_resolve.d.ts.map +1 -1
  157. package/dist/tools/perf.d.ts.map +1 -1
  158. package/dist/tools/press.d.ts.map +1 -1
  159. package/dist/tools/product_video_run.d.ts.map +1 -1
  160. package/dist/tools/research_run.d.ts.map +1 -1
  161. package/dist/tools/response.d.ts +4 -1
  162. package/dist/tools/response.d.ts.map +1 -1
  163. package/dist/tools/screenshot.d.ts.map +1 -1
  164. package/dist/tools/scroll.d.ts.map +1 -1
  165. package/dist/tools/scroll_into_view.d.ts.map +1 -1
  166. package/dist/tools/select.d.ts.map +1 -1
  167. package/dist/tools/shopping_run.d.ts.map +1 -1
  168. package/dist/tools/snapshot.d.ts.map +1 -1
  169. package/dist/tools/type.d.ts.map +1 -1
  170. package/dist/tools/uncheck.d.ts.map +1 -1
  171. package/dist/tools/wait.d.ts.map +1 -1
  172. package/dist/tools/workflow-runtime.d.ts +1 -2
  173. package/dist/tools/workflow-runtime.d.ts.map +1 -1
  174. package/extension/canvas.html +636 -0
  175. package/extension/dist/annotate-content.css +15 -6
  176. package/extension/dist/annotate-content.js +119 -9
  177. package/extension/dist/annotation-payload.js +163 -0
  178. package/extension/dist/background.js +148 -18
  179. package/extension/dist/canvas/canvas-runtime.js +1061 -0
  180. package/extension/dist/canvas/model.js +213 -0
  181. package/extension/dist/canvas/viewport-fit.js +67 -0
  182. package/extension/dist/canvas-page.js +1801 -0
  183. package/extension/dist/ops/dom-bridge.js +116 -3
  184. package/extension/dist/ops/ops-runtime.js +508 -44
  185. package/extension/dist/ops/ops-session-store.js +21 -114
  186. package/extension/dist/ops/target-session-coordinator.js +157 -0
  187. package/extension/dist/popup.js +155 -31
  188. package/extension/dist/services/ConnectionManager.js +17 -0
  189. package/extension/dist/services/RelayClient.js +9 -0
  190. package/extension/dist/services/TabManager.js +35 -12
  191. package/extension/dist/types.js +2 -0
  192. package/extension/manifest.json +1 -1
  193. package/extension/popup.html +52 -0
  194. package/package.json +6 -4
  195. package/skills/AGENTS.md +5 -2
  196. package/skills/opendevbrowser-best-practices/SKILL.md +71 -3
  197. package/skills/opendevbrowser-best-practices/artifacts/canvas-governance-playbook.md +141 -0
  198. package/skills/opendevbrowser-best-practices/artifacts/command-channel-reference.md +113 -17
  199. package/skills/opendevbrowser-best-practices/assets/templates/canvas-blocker-checklist.json +70 -0
  200. package/skills/opendevbrowser-best-practices/assets/templates/canvas-feedback-eval.json +73 -0
  201. package/skills/opendevbrowser-best-practices/assets/templates/canvas-generation-plan.v1.json +67 -0
  202. package/skills/opendevbrowser-best-practices/assets/templates/canvas-handshake-example.json +126 -0
  203. package/skills/opendevbrowser-best-practices/assets/templates/robustness-checklist.json +57 -0
  204. package/skills/opendevbrowser-best-practices/assets/templates/surface-audit-checklist.json +7 -3
  205. package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +26 -0
  206. package/skills/opendevbrowser-best-practices/scripts/run-robustness-audit.sh +82 -1
  207. package/skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh +225 -84
  208. package/dist/chunk-ST7CO5FA.js.map +0 -1
  209. /package/dist/{chunk-7W3SPXIB.js.map → chunk-FUSXMW3G.js.map} +0 -0
  210. /package/dist/{macros-NUBRM44Y.js.map → macros-ND2M7LWU.js.map} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_OPS_PARALLELISM_POLICY, createOpsGovernorState } from "./parallelism-governor.js";
2
+ import { createCoordinatorId, TargetSessionCoordinator } from "./target-session-coordinator.js";
2
3
  export class OpsRefStore {
3
4
  refsByTarget = new Map();
4
5
  snapshotByTarget = new Map();
@@ -7,7 +8,7 @@ export class OpsRefStore {
7
8
  for (const entry of entries) {
8
9
  map.set(entry.ref, entry);
9
10
  }
10
- const snapshotId = createId();
11
+ const snapshotId = createCoordinatorId();
11
12
  this.refsByTarget.set(targetId, map);
12
13
  this.snapshotByTarget.set(targetId, snapshotId);
13
14
  return { snapshotId, targetId, count: entries.length };
@@ -31,32 +32,12 @@ export class OpsRefStore {
31
32
  }
32
33
  }
33
34
  export class OpsSessionStore {
34
- sessions = new Map();
35
- tabToSession = new Map();
35
+ coordinator = new TargetSessionCoordinator();
36
36
  createSession(ownerClientId, tabId, leaseId, info, options) {
37
- const id = createId();
38
- const targetId = `tab-${tabId}`;
39
37
  const parallelismPolicy = options?.parallelismPolicy ?? DEFAULT_OPS_PARALLELISM_POLICY;
40
- const target = {
41
- targetId,
42
- tabId,
43
- url: info?.url,
44
- title: info?.title
45
- };
46
- const session = {
47
- id,
48
- ownerClientId,
49
- leaseId,
50
- state: "active",
51
- tabId,
52
- targetId,
53
- activeTargetId: targetId,
54
- createdAt: Date.now(),
55
- lastUsedAt: Date.now(),
56
- targets: new Map([[targetId, target]]),
57
- nameToTarget: new Map(),
58
- targetToName: new Map(),
38
+ return this.coordinator.createSession(ownerClientId, tabId, leaseId, info, {
59
39
  refStore: new OpsRefStore(),
40
+ syntheticTargets: new Map(),
60
41
  consoleEvents: [],
61
42
  networkEvents: [],
62
43
  networkRequests: new Map(),
@@ -72,132 +53,58 @@ export class OpsSessionStore {
72
53
  frozenSignals: 0,
73
54
  parallelismPolicy,
74
55
  parallelismState: createOpsGovernorState(parallelismPolicy, "extensionOpsHeaded")
75
- };
76
- this.sessions.set(id, session);
77
- this.tabToSession.set(tabId, id);
78
- return session;
56
+ });
79
57
  }
80
58
  get(sessionId) {
81
- return this.sessions.get(sessionId) ?? null;
59
+ return this.coordinator.get(sessionId);
82
60
  }
83
61
  getByTabId(tabId) {
84
- const id = this.tabToSession.get(tabId);
85
- if (!id)
86
- return null;
87
- return this.sessions.get(id) ?? null;
62
+ return this.coordinator.getByTabId(tabId);
88
63
  }
89
64
  listOwnedBy(clientId) {
90
- return Array.from(this.sessions.values()).filter((session) => session.ownerClientId === clientId);
65
+ return this.coordinator.listOwnedBy(clientId);
91
66
  }
92
67
  delete(sessionId) {
93
- const session = this.sessions.get(sessionId) ?? null;
94
- if (!session)
95
- return null;
96
- this.sessions.delete(sessionId);
97
- for (const target of session.targets.values()) {
98
- this.tabToSession.delete(target.tabId);
99
- }
100
- return session;
68
+ return this.coordinator.delete(sessionId);
101
69
  }
102
70
  addTarget(sessionId, tabId, info) {
103
- const session = this.requireSession(sessionId);
104
- const targetId = `tab-${tabId}`;
105
- const target = {
106
- targetId,
107
- tabId,
108
- url: info?.url,
109
- title: info?.title
110
- };
111
- session.targets.set(targetId, target);
112
- this.tabToSession.set(tabId, sessionId);
113
- if (!session.activeTargetId) {
114
- session.activeTargetId = targetId;
115
- }
116
- return target;
71
+ return this.coordinator.addTarget(sessionId, tabId, info);
117
72
  }
118
73
  removeTarget(sessionId, targetId) {
74
+ const target = this.coordinator.removeTarget(sessionId, targetId);
119
75
  const session = this.requireSession(sessionId);
120
- const target = session.targets.get(targetId) ?? null;
121
76
  if (!target)
122
77
  return null;
123
- session.targets.delete(targetId);
124
- this.tabToSession.delete(target.tabId);
125
- const name = session.targetToName.get(targetId);
126
- if (name) {
127
- session.targetToName.delete(targetId);
128
- session.nameToTarget.delete(name);
129
- }
130
- if (session.activeTargetId === targetId) {
131
- const [first] = session.targets.keys();
132
- session.activeTargetId = first ?? "";
133
- }
134
78
  session.targetQueues.delete(targetId);
135
79
  session.targetQueueDepth.delete(targetId);
136
80
  session.targetQueueOldestAt.delete(targetId);
137
81
  session.refStore.clearTarget(targetId);
82
+ session.syntheticTargets.delete(targetId);
138
83
  return target;
139
84
  }
140
85
  getTargetIdByTabId(sessionId, tabId) {
141
- const session = this.requireSession(sessionId);
142
- for (const target of session.targets.values()) {
143
- if (target.tabId === tabId) {
144
- return target.targetId;
145
- }
146
- }
147
- return null;
86
+ return this.coordinator.getTargetIdByTabId(sessionId, tabId);
148
87
  }
149
88
  removeTargetByTabId(sessionId, tabId) {
150
- const targetId = this.getTargetIdByTabId(sessionId, tabId);
89
+ const targetId = this.coordinator.getTargetIdByTabId(sessionId, tabId);
151
90
  if (!targetId)
152
91
  return null;
153
92
  return this.removeTarget(sessionId, targetId);
154
93
  }
155
94
  setActiveTarget(sessionId, targetId) {
156
- const session = this.requireSession(sessionId);
157
- if (!session.targets.has(targetId)) {
158
- throw new Error(`Unknown targetId: ${targetId}`);
159
- }
160
- session.activeTargetId = targetId;
95
+ this.coordinator.setActiveTarget(sessionId, targetId);
161
96
  }
162
97
  setName(sessionId, targetId, name) {
163
- const session = this.requireSession(sessionId);
164
- const trimmed = name.trim();
165
- if (!trimmed) {
166
- throw new Error("Name must be non-empty");
167
- }
168
- if (!session.targets.has(targetId)) {
169
- throw new Error(`Unknown targetId: ${targetId}`);
170
- }
171
- const existing = session.nameToTarget.get(trimmed);
172
- if (existing && existing !== targetId) {
173
- throw new Error(`Name already in use: ${trimmed}`);
174
- }
175
- const previousName = session.targetToName.get(targetId);
176
- if (previousName && previousName !== trimmed) {
177
- session.nameToTarget.delete(previousName);
178
- }
179
- session.nameToTarget.set(trimmed, targetId);
180
- session.targetToName.set(targetId, trimmed);
98
+ this.coordinator.setName(sessionId, targetId, name);
181
99
  }
182
100
  getTargetIdByName(sessionId, name) {
183
- const session = this.requireSession(sessionId);
184
- return session.nameToTarget.get(name.trim()) ?? null;
101
+ return this.coordinator.getTargetIdByName(sessionId, name);
185
102
  }
186
103
  listNamedTargets(sessionId) {
187
- const session = this.requireSession(sessionId);
188
- return Array.from(session.nameToTarget.entries()).map(([name, targetId]) => ({ name, targetId }));
104
+ return this.coordinator.listNamedTargets(sessionId);
189
105
  }
190
106
  requireSession(sessionId) {
191
- const session = this.sessions.get(sessionId);
192
- if (!session) {
193
- throw new Error(`Unknown sessionId: ${sessionId}`);
194
- }
195
- return session;
107
+ return this.coordinator.requireSession(sessionId);
196
108
  }
197
109
  }
198
- const createId = () => {
199
- if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
200
- return crypto.randomUUID();
201
- }
202
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
203
- };
110
+ export const createOpsSessionId = () => createCoordinatorId();
@@ -0,0 +1,157 @@
1
+ export class TargetSessionCoordinator {
2
+ sessions = new Map();
3
+ tabToSession = new Map();
4
+ createSession(ownerClientId, tabId, leaseId, info, extra, sessionId) {
5
+ const id = sessionId ?? createCoordinatorId();
6
+ const targetId = `tab-${tabId}`;
7
+ const target = {
8
+ targetId,
9
+ tabId,
10
+ url: info?.url,
11
+ title: info?.title
12
+ };
13
+ const createdAt = Date.now();
14
+ const session = {
15
+ id,
16
+ ownerClientId,
17
+ leaseId,
18
+ state: "active",
19
+ tabId,
20
+ targetId,
21
+ activeTargetId: targetId,
22
+ createdAt,
23
+ lastUsedAt: createdAt,
24
+ targets: new Map([[targetId, target]]),
25
+ nameToTarget: new Map(),
26
+ targetToName: new Map(),
27
+ ...extra
28
+ };
29
+ this.sessions.set(id, session);
30
+ this.tabToSession.set(tabId, id);
31
+ return session;
32
+ }
33
+ get(sessionId) {
34
+ return this.sessions.get(sessionId) ?? null;
35
+ }
36
+ getByTabId(tabId) {
37
+ const sessionId = this.tabToSession.get(tabId);
38
+ if (!sessionId) {
39
+ return null;
40
+ }
41
+ return this.sessions.get(sessionId) ?? null;
42
+ }
43
+ listOwnedBy(clientId) {
44
+ return Array.from(this.sessions.values()).filter((session) => session.ownerClientId === clientId);
45
+ }
46
+ delete(sessionId) {
47
+ const session = this.sessions.get(sessionId) ?? null;
48
+ if (!session) {
49
+ return null;
50
+ }
51
+ this.sessions.delete(sessionId);
52
+ for (const target of session.targets.values()) {
53
+ this.tabToSession.delete(target.tabId);
54
+ }
55
+ return session;
56
+ }
57
+ addTarget(sessionId, tabId, info) {
58
+ const session = this.requireSession(sessionId);
59
+ const targetId = `tab-${tabId}`;
60
+ const target = {
61
+ targetId,
62
+ tabId,
63
+ url: info?.url,
64
+ title: info?.title
65
+ };
66
+ session.targets.set(targetId, target);
67
+ this.tabToSession.set(tabId, sessionId);
68
+ if (!session.activeTargetId) {
69
+ session.activeTargetId = targetId;
70
+ }
71
+ return target;
72
+ }
73
+ removeTarget(sessionId, targetId) {
74
+ const session = this.requireSession(sessionId);
75
+ const target = session.targets.get(targetId) ?? null;
76
+ if (!target) {
77
+ return null;
78
+ }
79
+ session.targets.delete(targetId);
80
+ this.tabToSession.delete(target.tabId);
81
+ const name = session.targetToName.get(targetId);
82
+ if (name) {
83
+ session.targetToName.delete(targetId);
84
+ session.nameToTarget.delete(name);
85
+ }
86
+ if (session.activeTargetId === targetId) {
87
+ const [first] = session.targets.keys();
88
+ session.activeTargetId = first ?? "";
89
+ }
90
+ return target;
91
+ }
92
+ getTargetIdByTabId(sessionId, tabId) {
93
+ const session = this.requireSession(sessionId);
94
+ for (const target of session.targets.values()) {
95
+ if (target.tabId === tabId) {
96
+ return target.targetId;
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ removeTargetByTabId(sessionId, tabId) {
102
+ const targetId = this.getTargetIdByTabId(sessionId, tabId);
103
+ if (!targetId) {
104
+ return null;
105
+ }
106
+ return this.removeTarget(sessionId, targetId);
107
+ }
108
+ setActiveTarget(sessionId, targetId) {
109
+ const session = this.requireSession(sessionId);
110
+ if (!session.targets.has(targetId)) {
111
+ throw new Error(`Unknown targetId: ${targetId}`);
112
+ }
113
+ session.activeTargetId = targetId;
114
+ }
115
+ setName(sessionId, targetId, name) {
116
+ const session = this.requireSession(sessionId);
117
+ const trimmed = name.trim();
118
+ if (!trimmed) {
119
+ throw new Error("Name must be non-empty");
120
+ }
121
+ if (!session.targets.has(targetId)) {
122
+ throw new Error(`Unknown targetId: ${targetId}`);
123
+ }
124
+ const existing = session.nameToTarget.get(trimmed);
125
+ if (existing && existing !== targetId) {
126
+ throw new Error(`Name already in use: ${trimmed}`);
127
+ }
128
+ const previousName = session.targetToName.get(targetId);
129
+ if (previousName && previousName !== trimmed) {
130
+ session.nameToTarget.delete(previousName);
131
+ }
132
+ session.nameToTarget.set(trimmed, targetId);
133
+ session.targetToName.set(targetId, trimmed);
134
+ }
135
+ getTargetIdByName(sessionId, name) {
136
+ const session = this.requireSession(sessionId);
137
+ return session.nameToTarget.get(name.trim()) ?? null;
138
+ }
139
+ listNamedTargets(sessionId) {
140
+ const session = this.requireSession(sessionId);
141
+ return Array.from(session.nameToTarget.entries()).map(([name, targetId]) => ({ name, targetId }));
142
+ }
143
+ requireSession(sessionId) {
144
+ const session = this.sessions.get(sessionId);
145
+ if (!session) {
146
+ throw new Error(`Unknown sessionId: ${sessionId}`);
147
+ }
148
+ session.lastUsedAt = Date.now();
149
+ return session;
150
+ }
151
+ }
152
+ export const createCoordinatorId = () => {
153
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
154
+ return crypto.randomUUID();
155
+ }
156
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
157
+ };
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_AUTO_CONNECT, DEFAULT_AUTO_PAIR, DEFAULT_DISCOVERY_PORT, DEFAULT_NATIVE_ENABLED, DEFAULT_PAIRING_ENABLED, DEFAULT_PAIRING_TOKEN, DEFAULT_RELAY_PORT } from "./relay-settings.js";
2
2
  import { logError } from "./logging.js";
3
+ import { describeAnnotationItem, filterAnnotationPayload } from "./annotation-payload.js";
3
4
  const statusEl = document.getElementById("status");
4
5
  const statusIndicator = document.getElementById("statusIndicator");
5
6
  const statusPill = document.getElementById("statusPill");
@@ -22,6 +23,9 @@ const nativeEnabledInput = document.getElementById("nativeEnabled");
22
23
  const annotationContextInput = document.getElementById("annotationContext");
23
24
  const annotationStartButton = document.getElementById("annotationStart");
24
25
  const annotationCopyButton = document.getElementById("annotationCopy");
26
+ const annotationSendButton = document.getElementById("annotationSend");
27
+ const annotationRefreshButton = document.getElementById("annotationRefresh");
28
+ const annotationItems = document.getElementById("annotationItems");
25
29
  const annotationNote = document.getElementById("annotationNote");
26
30
  if (!statusEl
27
31
  || !statusIndicator
@@ -45,12 +49,16 @@ if (!statusEl
45
49
  || !annotationContextInput
46
50
  || !annotationStartButton
47
51
  || !annotationCopyButton
52
+ || !annotationSendButton
53
+ || !annotationRefreshButton
54
+ || !annotationItems
48
55
  || !annotationNote) {
49
56
  throw new Error("Popup DOM missing required elements");
50
57
  }
51
58
  const defaultNote = "Local relay only. Tokens stay on-device.";
52
59
  const defaultAnnotationNote = "No annotations captured yet.";
53
60
  const LAST_ANNOTATION_META_KEY = "annotationLastMeta";
61
+ let lastAnnotationPayload = null;
54
62
  const setNote = (message) => {
55
63
  const next = message && message.trim() ? message : defaultNote;
56
64
  statusNote.textContent = next;
@@ -207,13 +215,24 @@ const refreshStatus = async () => {
207
215
  setInjectionStatus(null);
208
216
  }
209
217
  };
210
- const setCopyEnabled = (enabled) => {
218
+ const setAnnotationActionsEnabled = (enabled) => {
211
219
  annotationCopyButton.disabled = !enabled;
220
+ annotationSendButton.disabled = !enabled;
212
221
  };
213
222
  const buildPopupAnnotationOptions = () => {
214
223
  const context = annotationContextInput.value.trim();
215
224
  return context ? { context } : undefined;
216
225
  };
226
+ const withPopupContext = (payload) => {
227
+ const context = annotationContextInput.value.trim();
228
+ if (!context) {
229
+ return payload;
230
+ }
231
+ return {
232
+ ...payload,
233
+ context
234
+ };
235
+ };
217
236
  const fetchTokenFromPlugin = async (port) => {
218
237
  try {
219
238
  const response = await fetch(`http://127.0.0.1:${port}/pair`, {
@@ -332,16 +351,76 @@ const formatAnnotationSummary = (meta) => {
332
351
  const target = meta.url ? ` on ${meta.url}` : "";
333
352
  return `Last annotation: ${count} item${count === 1 ? "" : "s"}${target}.`;
334
353
  };
354
+ const renderAnnotationItems = (payload) => {
355
+ annotationItems.innerHTML = "";
356
+ if (!payload || payload.annotations.length === 0) {
357
+ const empty = document.createElement("div");
358
+ empty.className = "annotation-empty";
359
+ empty.textContent = "No annotation items available.";
360
+ annotationItems.append(empty);
361
+ return;
362
+ }
363
+ for (const item of payload.annotations) {
364
+ const row = document.createElement("div");
365
+ row.className = "annotation-item";
366
+ const summary = document.createElement("div");
367
+ summary.className = "annotation-item-summary";
368
+ summary.textContent = describeAnnotationItem(item);
369
+ const meta = document.createElement("div");
370
+ meta.className = "annotation-item-meta";
371
+ meta.textContent = `${item.tag} • ${Math.round(item.rect.width)}×${Math.round(item.rect.height)}`;
372
+ const actions = document.createElement("div");
373
+ actions.className = "annotation-item-actions";
374
+ const copyButton = document.createElement("button");
375
+ copyButton.className = "secondary";
376
+ copyButton.type = "button";
377
+ copyButton.textContent = "Copy item";
378
+ copyButton.addEventListener("click", () => {
379
+ void copySelectedAnnotation([item.id], "Copied annotation item payload.");
380
+ });
381
+ const sendButton = document.createElement("button");
382
+ sendButton.className = "secondary";
383
+ sendButton.type = "button";
384
+ sendButton.textContent = "Send item";
385
+ sendButton.addEventListener("click", () => {
386
+ void sendSelectedAnnotation([item.id], "popup_item", describeAnnotationItem(item), "Sent annotation item to agent inbox.");
387
+ });
388
+ actions.append(copyButton, sendButton);
389
+ row.append(summary, meta, actions);
390
+ annotationItems.append(row);
391
+ }
392
+ };
393
+ const loadAnnotationPayload = async (preferScreenshots) => {
394
+ let response = await sendMessage({
395
+ type: "annotation:getPayload",
396
+ includeScreenshots: preferScreenshots
397
+ });
398
+ if (!response.payload && preferScreenshots) {
399
+ response = await sendMessage({
400
+ type: "annotation:getPayload",
401
+ includeScreenshots: false
402
+ });
403
+ }
404
+ return response;
405
+ };
406
+ const refreshAnnotationPayload = async () => {
407
+ const response = await loadAnnotationPayload(false);
408
+ lastAnnotationPayload = response.payload ? withPopupContext(response.payload) : null;
409
+ renderAnnotationItems(lastAnnotationPayload);
410
+ };
335
411
  const refreshLastAnnotationMeta = async () => {
336
412
  try {
337
413
  const response = await sendMessage({ type: "annotation:lastMeta" });
338
414
  const meta = response.meta;
339
415
  if (meta && meta.status === "ok") {
340
- setCopyEnabled(true);
416
+ setAnnotationActionsEnabled(true);
341
417
  setAnnotationNote(formatAnnotationSummary(meta));
418
+ await refreshAnnotationPayload();
342
419
  return;
343
420
  }
344
- setCopyEnabled(false);
421
+ setAnnotationActionsEnabled(false);
422
+ lastAnnotationPayload = null;
423
+ renderAnnotationItems(null);
345
424
  if (meta) {
346
425
  setAnnotationNote(formatAnnotationSummary(meta));
347
426
  }
@@ -351,7 +430,9 @@ const refreshLastAnnotationMeta = async () => {
351
430
  }
352
431
  catch (error) {
353
432
  logError("popup.annotation_meta", error, { code: "annotation_meta_failed" });
354
- setCopyEnabled(false);
433
+ setAnnotationActionsEnabled(false);
434
+ lastAnnotationPayload = null;
435
+ renderAnnotationItems(null);
355
436
  setAnnotationNote("Annotation status unavailable.");
356
437
  }
357
438
  };
@@ -377,6 +458,49 @@ const copyTextToClipboard = async (text) => {
377
458
  throw new Error("Clipboard copy failed");
378
459
  }
379
460
  };
461
+ const copyPayloadToClipboard = async (payload, message) => {
462
+ await copyTextToClipboard(JSON.stringify(payload, null, 2));
463
+ setAnnotationNote(message);
464
+ };
465
+ const sendPayloadToAgent = async (payload, source, label, successMessage) => {
466
+ const response = await sendMessage({
467
+ type: "annotation:sendPayload",
468
+ payload,
469
+ source,
470
+ label
471
+ });
472
+ if (!response.ok) {
473
+ throw new Error(response.error?.message ?? "Agent dispatch failed.");
474
+ }
475
+ setAnnotationNote(successMessage);
476
+ };
477
+ const copySelectedAnnotation = async (annotationIds, successMessage = "Copied annotation payload to clipboard.") => {
478
+ const response = await loadAnnotationPayload(true);
479
+ if (!response.payload) {
480
+ setAnnotationActionsEnabled(false);
481
+ setAnnotationNote("No completed annotation payload available.");
482
+ renderAnnotationItems(null);
483
+ return;
484
+ }
485
+ const payload = withPopupContext(annotationIds && annotationIds.length > 0
486
+ ? filterAnnotationPayload(response.payload, annotationIds, { includeScreenshots: Boolean(response.payload.screenshots?.length) })
487
+ : response.payload);
488
+ await copyPayloadToClipboard(payload, response.warning ? `${successMessage} ${response.warning}` : successMessage);
489
+ await refreshLastAnnotationMeta();
490
+ };
491
+ const sendSelectedAnnotation = async (annotationIds, source, label, successMessage) => {
492
+ const response = await loadAnnotationPayload(true);
493
+ if (!response.payload) {
494
+ setAnnotationActionsEnabled(false);
495
+ setAnnotationNote("No completed annotation payload available.");
496
+ renderAnnotationItems(null);
497
+ return;
498
+ }
499
+ const payload = withPopupContext(annotationIds && annotationIds.length > 0
500
+ ? filterAnnotationPayload(response.payload, annotationIds, { includeScreenshots: Boolean(response.payload.screenshots?.length) })
501
+ : response.payload);
502
+ await sendPayloadToAgent(payload, source, label, successMessage);
503
+ };
380
504
  const toggle = async () => {
381
505
  const isConnected = statusEl.textContent === "Connected";
382
506
  setNote();
@@ -445,49 +569,48 @@ annotationStartButton.addEventListener("click", () => {
445
569
  if (!response.ok) {
446
570
  const message = response.error?.message ?? "Annotation start failed.";
447
571
  setAnnotationNote(message);
448
- setCopyEnabled(false);
572
+ setAnnotationActionsEnabled(false);
573
+ lastAnnotationPayload = null;
574
+ renderAnnotationItems(null);
449
575
  return;
450
576
  }
451
577
  setAnnotationNote("Annotation started. Switch to the tab to select elements.");
452
- setCopyEnabled(false);
578
+ setAnnotationActionsEnabled(false);
579
+ lastAnnotationPayload = null;
580
+ renderAnnotationItems(null);
453
581
  void refreshInjectionStatus();
454
582
  })().catch((error) => {
455
583
  const message = error instanceof Error ? error.message : "Annotation start failed.";
456
584
  setAnnotationNote(message);
457
- setCopyEnabled(false);
585
+ setAnnotationActionsEnabled(false);
586
+ lastAnnotationPayload = null;
587
+ renderAnnotationItems(null);
458
588
  });
459
589
  });
460
590
  annotationCopyButton.addEventListener("click", () => {
461
591
  (async () => {
462
592
  setAnnotationNote("Preparing annotation payload...");
463
- let response = await sendMessage({
464
- type: "annotation:getPayload",
465
- includeScreenshots: true
466
- });
467
- if (!response.payload) {
468
- response = await sendMessage({
469
- type: "annotation:getPayload",
470
- includeScreenshots: false
471
- });
472
- }
473
- if (!response.payload) {
474
- setAnnotationNote("No completed annotation payload available.");
475
- setCopyEnabled(false);
476
- return;
477
- }
478
- await copyTextToClipboard(JSON.stringify(response.payload, null, 2));
479
- if (response.warning) {
480
- setAnnotationNote(`Copied payload (${response.warning.replace(/\.$/, "")}).`);
481
- }
482
- else {
483
- setAnnotationNote("Copied annotation payload to clipboard.");
484
- }
485
- await refreshLastAnnotationMeta();
593
+ await copySelectedAnnotation(undefined, "Copied annotation payload to clipboard.");
486
594
  })().catch((error) => {
487
595
  const message = error instanceof Error ? error.message : "Copy failed.";
488
596
  setAnnotationNote(message);
489
597
  });
490
598
  });
599
+ annotationSendButton.addEventListener("click", () => {
600
+ (async () => {
601
+ setAnnotationNote("Sending annotation payload to agent inbox...");
602
+ await sendSelectedAnnotation(undefined, "popup_all", "Popup annotation payload", "Sent annotation payload to agent inbox.");
603
+ })().catch((error) => {
604
+ const message = error instanceof Error ? error.message : "Send failed.";
605
+ setAnnotationNote(message);
606
+ });
607
+ });
608
+ annotationRefreshButton.addEventListener("click", () => {
609
+ refreshLastAnnotationMeta().catch((error) => {
610
+ const message = error instanceof Error ? error.message : "Refresh failed.";
611
+ setAnnotationNote(message);
612
+ });
613
+ });
491
614
  autoPairInput.addEventListener("change", () => {
492
615
  const enabled = autoPairInput.checked;
493
616
  pairingTokenInput.disabled = !pairingEnabledInput.checked || enabled;
@@ -560,7 +683,8 @@ loadSettings().catch((error) => {
560
683
  });
561
684
  refreshLastAnnotationMeta().catch((error) => {
562
685
  logError("popup.annotation_meta", error, { code: "annotation_meta_failed" });
563
- setCopyEnabled(false);
686
+ setAnnotationActionsEnabled(false);
687
+ renderAnnotationItems(null);
564
688
  setAnnotationNote();
565
689
  });
566
690
  chrome.storage.onChanged.addListener((changes, areaName) => {
@@ -55,6 +55,7 @@ export class ConnectionManager {
55
55
  connectPromise = null;
56
56
  annotationHandler = null;
57
57
  opsHandler = null;
58
+ canvasHandler = null;
58
59
  heartbeatTimer = null;
59
60
  heartbeatInFlight = false;
60
61
  heartbeatIntervalMs = 25_000;
@@ -91,6 +92,9 @@ export class ConnectionManager {
91
92
  onOpsMessage(handler) {
92
93
  this.opsHandler = handler;
93
94
  }
95
+ onCanvasMessage(handler) {
96
+ this.canvasHandler = handler;
97
+ }
94
98
  sendAnnotationResponse(response) {
95
99
  if (!this.relay)
96
100
  return;
@@ -121,6 +125,16 @@ export class ConnectionManager {
121
125
  logError("relay.send_ops_message", error, { code: "relay_send_failed" });
122
126
  }
123
127
  }
128
+ sendCanvasMessage(message) {
129
+ if (!this.relay)
130
+ return;
131
+ try {
132
+ this.relay.sendCanvasMessage(message);
133
+ }
134
+ catch (error) {
135
+ logError("relay.send_canvas_message", error, { code: "relay_send_failed" });
136
+ }
137
+ }
124
138
  getCdpRouter() {
125
139
  return this.cdp;
126
140
  }
@@ -370,6 +384,9 @@ export class ConnectionManager {
370
384
  onOpsMessage: (message) => {
371
385
  this.opsHandler?.(message);
372
386
  },
387
+ onCanvasMessage: (message) => {
388
+ this.canvasHandler?.(message);
389
+ },
373
390
  onClose: (detail) => {
374
391
  this.handleRelayClose(detail);
375
392
  }