opendevbrowser 0.0.19 → 0.0.21

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 (173) hide show
  1. package/README.md +32 -24
  2. package/dist/automation/coordinator.d.ts +61 -2
  3. package/dist/automation/coordinator.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-manager.d.ts +4 -0
  7. package/dist/browser/canvas-manager.d.ts.map +1 -1
  8. package/dist/browser/manager-types.d.ts +6 -1
  9. package/dist/browser/manager-types.d.ts.map +1 -1
  10. package/dist/browser/ops-browser-manager.d.ts +6 -1
  11. package/dist/browser/ops-browser-manager.d.ts.map +1 -1
  12. package/dist/browser/screencast-recorder.d.ts.map +1 -1
  13. package/dist/browser/session-inspector.d.ts +39 -0
  14. package/dist/browser/session-inspector.d.ts.map +1 -1
  15. package/dist/canvas/document-store.d.ts +14 -5
  16. package/dist/canvas/document-store.d.ts.map +1 -1
  17. package/dist/canvas/starters/catalog.d.ts.map +1 -1
  18. package/dist/canvas/types.d.ts +120 -9
  19. package/dist/canvas/types.d.ts.map +1 -1
  20. package/dist/challenges/action-loop.d.ts +2 -1
  21. package/dist/challenges/action-loop.d.ts.map +1 -1
  22. package/dist/challenges/capture.d.ts +14 -0
  23. package/dist/challenges/capture.d.ts.map +1 -0
  24. package/dist/challenges/index.d.ts +3 -1
  25. package/dist/challenges/index.d.ts.map +1 -1
  26. package/dist/challenges/inspect-plan.d.ts +40 -0
  27. package/dist/challenges/inspect-plan.d.ts.map +1 -0
  28. package/dist/challenges/orchestrator.d.ts.map +1 -1
  29. package/dist/challenges/types.d.ts +34 -0
  30. package/dist/challenges/types.d.ts.map +1 -1
  31. package/dist/{chunk-YBQECXZX.js → chunk-3VA6XR25.js} +3 -3
  32. package/dist/chunk-3VA6XR25.js.map +1 -0
  33. package/dist/{chunk-W4IHGDXV.js → chunk-4KVXCXV3.js} +25778 -24279
  34. package/dist/chunk-4KVXCXV3.js.map +1 -0
  35. package/dist/{chunk-5FZQJRBQ.js → chunk-ZE2E7ZGH.js} +3013 -1010
  36. package/dist/chunk-ZE2E7ZGH.js.map +1 -0
  37. package/dist/cli/commands/challenge-automation-mode.d.ts +3 -0
  38. package/dist/cli/commands/challenge-automation-mode.d.ts.map +1 -0
  39. package/dist/cli/commands/inspiredesign.d.ts +25 -0
  40. package/dist/cli/commands/inspiredesign.d.ts.map +1 -0
  41. package/dist/cli/commands/macro-resolve.d.ts.map +1 -1
  42. package/dist/cli/commands/nav/review-desktop.d.ts +7 -0
  43. package/dist/cli/commands/nav/review-desktop.d.ts.map +1 -0
  44. package/dist/cli/commands/nav/review-shared.d.ts +10 -0
  45. package/dist/cli/commands/nav/review-shared.d.ts.map +1 -0
  46. package/dist/cli/commands/nav/review.d.ts.map +1 -1
  47. package/dist/cli/commands/serve.d.ts.map +1 -1
  48. package/dist/cli/commands/session/inspector-audit.d.ts +7 -0
  49. package/dist/cli/commands/session/inspector-audit.d.ts.map +1 -0
  50. package/dist/cli/commands/session/inspector-plan.d.ts +7 -0
  51. package/dist/cli/commands/session/inspector-plan.d.ts.map +1 -0
  52. package/dist/cli/commands/session/inspector-shared.d.ts +11 -0
  53. package/dist/cli/commands/session/inspector-shared.d.ts.map +1 -0
  54. package/dist/cli/commands/session/inspector.d.ts +1 -11
  55. package/dist/cli/commands/session/inspector.d.ts.map +1 -1
  56. package/dist/cli/commands/status-capabilities.d.ts +7 -0
  57. package/dist/cli/commands/status-capabilities.d.ts.map +1 -0
  58. package/dist/cli/daemon-client.d.ts.map +1 -1
  59. package/dist/cli/daemon-commands.d.ts.map +1 -1
  60. package/dist/cli/daemon-status.d.ts +5 -0
  61. package/dist/cli/daemon-status.d.ts.map +1 -1
  62. package/dist/cli/daemon.d.ts +5 -0
  63. package/dist/cli/daemon.d.ts.map +1 -1
  64. package/dist/cli/help.d.ts +2 -0
  65. package/dist/cli/help.d.ts.map +1 -1
  66. package/dist/cli/index.js +660 -244
  67. package/dist/cli/index.js.map +1 -1
  68. package/dist/cli/remote-manager.d.ts +6 -0
  69. package/dist/cli/remote-manager.d.ts.map +1 -1
  70. package/dist/cli/utils/parse.d.ts +1 -0
  71. package/dist/cli/utils/parse.d.ts.map +1 -1
  72. package/dist/cli/utils/skills.d.ts +1 -1
  73. package/dist/cli/utils/skills.d.ts.map +1 -1
  74. package/dist/cli/utils/workflow-message.d.ts +3 -0
  75. package/dist/cli/utils/workflow-message.d.ts.map +1 -1
  76. package/dist/config.d.ts +1 -0
  77. package/dist/config.d.ts.map +1 -1
  78. package/dist/core/bootstrap.d.ts.map +1 -1
  79. package/dist/core/runtime-assemblies.d.ts +2 -1
  80. package/dist/core/runtime-assemblies.d.ts.map +1 -1
  81. package/dist/core/types.d.ts +1 -1
  82. package/dist/core/types.d.ts.map +1 -1
  83. package/dist/desktop/runtime.d.ts +1 -0
  84. package/dist/desktop/runtime.d.ts.map +1 -1
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +729 -448
  87. package/dist/index.js.map +1 -1
  88. package/dist/inspiredesign/handoff.d.ts +34 -0
  89. package/dist/inspiredesign/handoff.d.ts.map +1 -0
  90. package/dist/opendevbrowser.d.ts.map +1 -1
  91. package/dist/opendevbrowser.js +729 -448
  92. package/dist/opendevbrowser.js.map +1 -1
  93. package/dist/providers/browser-fallback.d.ts.map +1 -1
  94. package/dist/providers/constraint.d.ts +11 -0
  95. package/dist/providers/constraint.d.ts.map +1 -1
  96. package/dist/providers/cookie-source.d.ts +8 -0
  97. package/dist/providers/cookie-source.d.ts.map +1 -0
  98. package/dist/providers/index.d.ts.map +1 -1
  99. package/dist/providers/inspiredesign-capture.d.ts +17 -0
  100. package/dist/providers/inspiredesign-capture.d.ts.map +1 -0
  101. package/dist/providers/inspiredesign-contract.d.ts +110 -0
  102. package/dist/providers/inspiredesign-contract.d.ts.map +1 -0
  103. package/dist/providers/renderer.d.ts +23 -0
  104. package/dist/providers/renderer.d.ts.map +1 -1
  105. package/dist/providers/runtime-bundle.d.ts +2 -1
  106. package/dist/providers/runtime-bundle.d.ts.map +1 -1
  107. package/dist/providers/runtime-factory.d.ts +4 -2
  108. package/dist/providers/runtime-factory.d.ts.map +1 -1
  109. package/dist/providers/shopping/index.d.ts +1 -1
  110. package/dist/providers/types.d.ts +3 -2
  111. package/dist/providers/types.d.ts.map +1 -1
  112. package/dist/providers/workflow-contracts.d.ts +1 -1
  113. package/dist/providers/workflow-contracts.d.ts.map +1 -1
  114. package/dist/providers/workflow-handoff.d.ts +14 -0
  115. package/dist/providers/workflow-handoff.d.ts.map +1 -0
  116. package/dist/providers/workflows.d.ts +21 -2
  117. package/dist/providers/workflows.d.ts.map +1 -1
  118. package/dist/{providers-G36AM3Z2.js → providers-ZIVHHH4F.js} +6 -2
  119. package/dist/public-surface/generated-manifest.d.ts +143 -7
  120. package/dist/public-surface/generated-manifest.d.ts.map +1 -1
  121. package/dist/public-surface/source.d.ts +102 -3
  122. package/dist/public-surface/source.d.ts.map +1 -1
  123. package/dist/relay/protocol.d.ts +1 -1
  124. package/dist/relay/protocol.d.ts.map +1 -1
  125. package/dist/relay/relay-server.d.ts +1 -0
  126. package/dist/relay/relay-server.d.ts.map +1 -1
  127. package/dist/skills/skill-loader.js +1 -1
  128. package/dist/tools/automation-shared.d.ts +6 -0
  129. package/dist/tools/automation-shared.d.ts.map +1 -0
  130. package/dist/tools/index.d.ts.map +1 -1
  131. package/dist/tools/inspiredesign_run.d.ts +4 -0
  132. package/dist/tools/inspiredesign_run.d.ts.map +1 -0
  133. package/dist/tools/review_desktop.d.ts +4 -0
  134. package/dist/tools/review_desktop.d.ts.map +1 -0
  135. package/dist/tools/session_inspector.d.ts.map +1 -1
  136. package/dist/tools/session_inspector_audit.d.ts +4 -0
  137. package/dist/tools/session_inspector_audit.d.ts.map +1 -0
  138. package/dist/tools/session_inspector_plan.d.ts +4 -0
  139. package/dist/tools/session_inspector_plan.d.ts.map +1 -0
  140. package/dist/tools/status_capabilities.d.ts +4 -0
  141. package/dist/tools/status_capabilities.d.ts.map +1 -0
  142. package/extension/dist/background.js +70 -10
  143. package/extension/dist/canvas/canvas-runtime.js +14 -1
  144. package/extension/dist/ops/ops-runtime.js +18 -1
  145. package/extension/dist/popup.js +29 -16
  146. package/extension/dist/services/ConnectionManager.js +27 -2
  147. package/extension/manifest.json +1 -1
  148. package/extension/popup.html +11 -0
  149. package/package.json +5 -5
  150. package/skills/AGENTS.md +2 -2
  151. package/skills/opendevbrowser-best-practices/SKILL.md +50 -15
  152. package/skills/opendevbrowser-best-practices/artifacts/canvas-governance-playbook.md +31 -12
  153. package/skills/opendevbrowser-best-practices/artifacts/command-channel-reference.md +64 -15
  154. package/skills/opendevbrowser-best-practices/artifacts/provider-workflows.md +4 -0
  155. package/skills/opendevbrowser-best-practices/artifacts/skill-runtime-surface-matrix.md +11 -10
  156. package/skills/opendevbrowser-best-practices/assets/templates/canvas-blocker-checklist.json +28 -22
  157. package/skills/opendevbrowser-best-practices/assets/templates/canvas-generation-plan.v1.json +18 -17
  158. package/skills/opendevbrowser-best-practices/assets/templates/canvas-handshake-example.json +135 -17
  159. package/skills/opendevbrowser-best-practices/assets/templates/skill-runtime-pack-matrix.json +55 -8
  160. package/skills/opendevbrowser-best-practices/assets/templates/surface-audit-checklist.json +18 -4
  161. package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +16 -4
  162. package/skills/opendevbrowser-best-practices/scripts/run-robustness-audit.sh +3 -1
  163. package/skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh +68 -25
  164. package/skills/opendevbrowser-design-agent/SKILL.md +9 -4
  165. package/skills/opendevbrowser-design-agent/artifacts/design-workflows.md +15 -6
  166. package/skills/opendevbrowser-design-agent/assets/templates/canvas-generation-plan.design.v1.json +18 -17
  167. package/skills/opendevbrowser-design-agent/scripts/design-workflow.sh +11 -0
  168. package/skills/opendevbrowser-design-agent/scripts/validate-skill-assets.sh +57 -0
  169. package/skills/opendevbrowser-product-presentation-asset/SKILL.md +2 -2
  170. package/dist/chunk-5FZQJRBQ.js.map +0 -1
  171. package/dist/chunk-W4IHGDXV.js.map +0 -1
  172. package/dist/chunk-YBQECXZX.js.map +0 -1
  173. /package/dist/{providers-G36AM3Z2.js.map → providers-ZIVHHH4F.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SkillLoader
3
- } from "../chunk-YBQECXZX.js";
3
+ } from "../chunk-3VA6XR25.js";
4
4
  export {
5
5
  SkillLoader
6
6
  };
@@ -0,0 +1,6 @@
1
+ import type { SessionInspectorHandle } from "../browser/manager-types";
2
+ import type { AutomationCoordinatorLike } from "../automation/coordinator";
3
+ import type { ToolDeps } from "./deps";
4
+ export declare function requireAutomationCoordinator(deps: ToolDeps): AutomationCoordinatorLike | string;
5
+ export declare function requireSessionInspectorHandle(deps: ToolDeps): SessionInspectorHandle | string;
6
+ //# sourceMappingURL=automation-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automation-shared.d.ts","sourceRoot":"","sources":["../../src/tools/automation-shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGvC,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,QAAQ,GACb,yBAAyB,GAAG,MAAM,CAGpC;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,QAAQ,GACb,sBAAsB,GAAG,MAAM,CAOjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAmEvC,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAE5E,eAAO,MAAM,qBAAqB,aAAiD,CAAC;AAEpF,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAsF1E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAwEvC,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAE5E,eAAO,MAAM,qBAAqB,aAAiD,CAAC;AAEpF,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAoG1E"}
@@ -0,0 +1,4 @@
1
+ import type { ToolDefinition } from "@opencode-ai/plugin";
2
+ import type { ToolDeps } from "./deps";
3
+ export declare function createInspiredesignRunTool(deps: ToolDeps): ToolDefinition;
4
+ //# sourceMappingURL=inspiredesign_run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspiredesign_run.d.ts","sourceRoot":"","sources":["../../src/tools/inspiredesign_run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAYvC,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAgDzE"}
@@ -0,0 +1,4 @@
1
+ import type { ToolDefinition } from "@opencode-ai/plugin";
2
+ import type { ToolDeps } from "./deps";
3
+ export declare function createReviewDesktopTool(deps: ToolDeps): ToolDefinition;
4
+ //# sourceMappingURL=review_desktop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review_desktop.d.ts","sourceRoot":"","sources":["../../src/tools/review_desktop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAMvC,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CA6BtE"}
@@ -1 +1 @@
1
- {"version":3,"file":"session_inspector.d.ts","sourceRoot":"","sources":["../../src/tools/session_inspector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAKvC,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAqCzE"}
1
+ {"version":3,"file":"session_inspector.d.ts","sourceRoot":"","sources":["../../src/tools/session_inspector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAMvC,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAqCzE"}
@@ -0,0 +1,4 @@
1
+ import type { ToolDefinition } from "@opencode-ai/plugin";
2
+ import type { ToolDeps } from "./deps";
3
+ export declare function createSessionInspectorAuditTool(deps: ToolDeps): ToolDefinition;
4
+ //# sourceMappingURL=session_inspector_audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_inspector_audit.d.ts","sourceRoot":"","sources":["../../src/tools/session_inspector_audit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAUvC,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CA6D9E"}
@@ -0,0 +1,4 @@
1
+ import type { ToolDefinition } from "@opencode-ai/plugin";
2
+ import type { ToolDeps } from "./deps";
3
+ export declare function createSessionInspectorPlanTool(deps: ToolDeps): ToolDefinition;
4
+ //# sourceMappingURL=session_inspector_plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_inspector_plan.d.ts","sourceRoot":"","sources":["../../src/tools/session_inspector_plan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAOvC,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAyB7E"}
@@ -0,0 +1,4 @@
1
+ import type { ToolDefinition } from "@opencode-ai/plugin";
2
+ import type { ToolDeps } from "./deps";
3
+ export declare function createStatusCapabilitiesTool(deps: ToolDeps): ToolDefinition;
4
+ //# sourceMappingURL=status_capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status_capabilities.d.ts","sourceRoot":"","sources":["../../src/tools/status_capabilities.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAOvC,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAyB3E"}
@@ -27,6 +27,7 @@ const nativePort = new NativePortManager({
27
27
  },
28
28
  onDisconnect: () => {
29
29
  updateBadge(getEffectiveStatus());
30
+ void refreshBadgeFromBackgroundStatus();
30
31
  }
31
32
  });
32
33
  let autoConnectInFlight = false;
@@ -49,6 +50,7 @@ const LAST_AGENT_ANNOTATION_PAYLOAD_KEY = "annotationAgentPayloadSansScreenshots
49
50
  const LAST_ANNOTATABLE_TAB_ID_KEY = "annotationLastTabId";
50
51
  const POPUP_ANNOTATION_TARGET_TTL_MS = 10_000;
51
52
  const BADGE_CONNECTED_DOT_COLOR = "#16a34a";
53
+ const BADGE_WARNING_DOT_COLOR = "#f59e0b";
52
54
  const BADGE_DISCONNECTED_DOT_COLOR = "#dc2626";
53
55
  const annotationSessions = new Map();
54
56
  let lastAnnotationFull = null;
@@ -66,9 +68,12 @@ connection.onOpsMessage((message) => {
66
68
  connection.onCanvasMessage((message) => {
67
69
  canvasRuntime.handleMessage(message);
68
70
  });
69
- const updateBadge = (status) => {
70
- const isConnected = status === "connected";
71
- const dotColor = isConnected ? BADGE_CONNECTED_DOT_COLOR : BADGE_DISCONNECTED_DOT_COLOR;
71
+ const updateBadge = (tone) => {
72
+ const dotColor = tone === "connected"
73
+ ? BADGE_CONNECTED_DOT_COLOR
74
+ : tone === "warning"
75
+ ? BADGE_WARNING_DOT_COLOR
76
+ : BADGE_DISCONNECTED_DOT_COLOR;
72
77
  chrome.action.setBadgeText({ text: "●" });
73
78
  if (typeof chrome.action.setBadgeTextColor === "function") {
74
79
  chrome.action.setBadgeTextColor({ color: dotColor });
@@ -86,10 +91,31 @@ const getEffectiveStatus = () => {
86
91
  }
87
92
  return "disconnected";
88
93
  };
94
+ const hasReadyRelayHandshake = (health) => {
95
+ return health?.extensionConnected === true && health.extensionHandshakeComplete === true;
96
+ };
97
+ const deriveBackgroundStatus = (relayStatus, nativeHealth, relayHealth, reconnectSuppressed) => {
98
+ if (relayStatus === "connected") {
99
+ return "connected";
100
+ }
101
+ if (nativeHealth?.status === "connected") {
102
+ return "connected";
103
+ }
104
+ if (reconnectSuppressed) {
105
+ return "disconnected";
106
+ }
107
+ return hasReadyRelayHandshake(relayHealth) ? "connected" : "disconnected";
108
+ };
109
+ const deriveBadgeTone = (status, relayHealth) => {
110
+ if (status === "connected") {
111
+ return "connected";
112
+ }
113
+ return hasReadyRelayHandshake(relayHealth) ? "warning" : "disconnected";
114
+ };
89
115
  const buildStatusMessage = async () => {
90
116
  const error = connection.getLastError();
91
117
  const relayStatus = connection.getStatus();
92
- const status = getEffectiveStatus();
118
+ const reconnectSuppressed = connection.isReconnectSuppressed();
93
119
  let note = error?.message;
94
120
  let relayHealth = null;
95
121
  const isNativeEnabled = nativeEnabled;
@@ -123,12 +149,18 @@ const buildStatusMessage = async () => {
123
149
  });
124
150
  const port = parsePort(stored.relayPort) ?? DEFAULT_RELAY_PORT;
125
151
  relayHealth = await fetchRelayHealth(port);
126
- note = statusNoteOverride ?? buildRelayHealthNote(relayHealth);
127
- if (!statusNoteOverride && isNativeEnabled && nativeHealth?.status === "error") {
152
+ if (hasReadyRelayHandshake(relayHealth)) {
153
+ note = `Connected to 127.0.0.1:${port}`;
154
+ }
155
+ else {
156
+ note = statusNoteOverride ?? buildRelayHealthNote(relayHealth);
157
+ }
158
+ if (!hasReadyRelayHandshake(relayHealth) && !statusNoteOverride && isNativeEnabled && nativeHealth?.status === "error") {
128
159
  note = buildNativeHealthNote(nativeHealth);
129
160
  }
130
161
  }
131
162
  }
163
+ const status = deriveBackgroundStatus(relayStatus, nativeHealth, relayHealth, reconnectSuppressed);
132
164
  if (!error) {
133
165
  const relayNotice = connection.getRelayNotice();
134
166
  if (relayNotice) {
@@ -144,6 +176,21 @@ const buildStatusMessage = async () => {
144
176
  nativeEnabled: isNativeEnabled
145
177
  };
146
178
  };
179
+ let badgeRefreshVersion = 0;
180
+ const refreshBadgeFromBackgroundStatus = async () => {
181
+ const refreshVersion = ++badgeRefreshVersion;
182
+ let tone = getEffectiveStatus();
183
+ try {
184
+ const statusMessage = await buildStatusMessage();
185
+ tone = deriveBadgeTone(statusMessage.status, statusMessage.relayHealth ?? null);
186
+ }
187
+ catch {
188
+ // Keep the synchronous local status when relay/native health cannot be fetched.
189
+ }
190
+ if (refreshVersion === badgeRefreshVersion) {
191
+ updateBadge(tone);
192
+ }
193
+ };
147
194
  const setStorage = (items) => {
148
195
  return new Promise((resolve) => {
149
196
  chrome.storage.local.set(items, () => resolve());
@@ -248,6 +295,7 @@ const attemptNativeConnect = async () => {
248
295
  logError("native_port.ping", error, { code: "native_ping_failed" });
249
296
  }
250
297
  updateBadge(getEffectiveStatus());
298
+ await refreshBadgeFromBackgroundStatus();
251
299
  return nativePort.isConnected();
252
300
  };
253
301
  const parsePort = (value) => {
@@ -1271,7 +1319,7 @@ const attemptAutoConnect = async () => {
1271
1319
  });
1272
1320
  const autoConnect = typeof data.autoConnect === "boolean" ? data.autoConnect : DEFAULT_AUTO_CONNECT;
1273
1321
  autoConnectEnabled = autoConnect;
1274
- if (!autoConnect || connection.getStatus() === "connected") {
1322
+ if (!autoConnect || connection.getStatus() === "connected" || connection.isReconnectSuppressed()) {
1275
1323
  clearRetry();
1276
1324
  return;
1277
1325
  }
@@ -1382,6 +1430,7 @@ const autoConnect = async () => {
1382
1430
  connection.onStatus((status) => {
1383
1431
  const effectiveStatus = status === "connected" ? "connected" : (nativeEnabled && nativePort.isConnected()) ? "connected" : "disconnected";
1384
1432
  updateBadge(effectiveStatus);
1433
+ void refreshBadgeFromBackgroundStatus();
1385
1434
  if (status === "connected") {
1386
1435
  nativePort.disconnect();
1387
1436
  setStatusNoteOverride(null);
@@ -1392,6 +1441,10 @@ connection.onStatus((status) => {
1392
1441
  clearTimeout(session.timeoutId);
1393
1442
  }
1394
1443
  annotationSessions.clear();
1444
+ if (connection.isReconnectSuppressed()) {
1445
+ clearRetry();
1446
+ return;
1447
+ }
1395
1448
  if (autoConnectEnabled) {
1396
1449
  scheduleRetry();
1397
1450
  }
@@ -1471,6 +1524,7 @@ chrome.storage.onChanged.addListener((changes, area) => {
1471
1524
  nativePort.disconnect();
1472
1525
  }
1473
1526
  updateBadge(getEffectiveStatus());
1527
+ void refreshBadgeFromBackgroundStatus();
1474
1528
  }
1475
1529
  if (changes.autoConnect) {
1476
1530
  autoConnectEnabled =
@@ -1496,7 +1550,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1496
1550
  };
1497
1551
  if (message.type === "status") {
1498
1552
  (async () => {
1499
- respond(await buildStatusMessage());
1553
+ const statusMessage = await buildStatusMessage();
1554
+ updateBadge(deriveBadgeTone(statusMessage.status, statusMessage.relayHealth ?? null));
1555
+ respond(statusMessage);
1500
1556
  })().catch((error) => {
1501
1557
  logError("popup.status", error, { code: "status_failed" });
1502
1558
  respond({
@@ -1513,7 +1569,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1513
1569
  if (connection.getStatus() !== "connected") {
1514
1570
  await attemptNativeConnect();
1515
1571
  }
1516
- respond(await buildStatusMessage());
1572
+ const statusMessage = await buildStatusMessage();
1573
+ updateBadge(deriveBadgeTone(statusMessage.status, statusMessage.relayHealth ?? null));
1574
+ respond(statusMessage);
1517
1575
  })().catch((error) => {
1518
1576
  logError("popup.connect", error, { code: "connect_failed" });
1519
1577
  connection.disconnect();
@@ -1531,7 +1589,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1531
1589
  await connection.disconnect();
1532
1590
  nativePort.disconnect();
1533
1591
  connection.clearLastError();
1534
- respond(await buildStatusMessage());
1592
+ const statusMessage = await buildStatusMessage();
1593
+ updateBadge(deriveBadgeTone(statusMessage.status, statusMessage.relayHealth ?? null));
1594
+ respond(statusMessage);
1535
1595
  })().catch((error) => {
1536
1596
  logError("popup.disconnect", error, { code: "disconnect_failed" });
1537
1597
  connection.disconnect();
@@ -180,6 +180,9 @@ export class CanvasRuntime {
180
180
  return;
181
181
  }
182
182
  for (const session of this.sessions.listOwnedBy(clientId)) {
183
+ if (this.shouldPreserveSessionOnDisconnect(session)) {
184
+ continue;
185
+ }
183
186
  void this.closeRuntimeSession(session, "client_disconnected");
184
187
  }
185
188
  for (const [sessionId, session] of this.overlaySessions.entries()) {
@@ -524,6 +527,10 @@ export class CanvasRuntime {
524
527
  overlayState: result?.overlayState ?? "mounted"
525
528
  };
526
529
  }
530
+ shouldPreserveSessionOnDisconnect(session) {
531
+ return typeof session.designTabTargetId === "string"
532
+ && session.targets.has(session.designTabTargetId);
533
+ }
527
534
  createOrReplaceSession(message, tabId, document, previewMode, record) {
528
535
  const canvasSessionId = resolveCanvasSessionId(message, record);
529
536
  const clientId = requireString(message.clientId, "clientId");
@@ -632,7 +639,13 @@ export class CanvasRuntime {
632
639
  const session = resolveSessionForMessage(this.sessions, message, payload);
633
640
  const clientId = requireString(message.clientId, "clientId");
634
641
  const leaseId = optionalString(message.leaseId);
635
- if (session.ownerClientId !== clientId || (leaseId && session.leaseId !== leaseId)) {
642
+ if (session.ownerClientId !== clientId) {
643
+ if (!leaseId || session.leaseId !== leaseId) {
644
+ throw new Error("Canvas session ownership mismatch.");
645
+ }
646
+ session.ownerClientId = clientId;
647
+ }
648
+ if (leaseId && session.leaseId !== leaseId) {
636
649
  throw new Error("Canvas session ownership mismatch.");
637
650
  }
638
651
  return session;
@@ -3570,7 +3570,12 @@ export class OpsRuntime {
3570
3570
  this.sendError(message, buildError("invalid_request", "No active target", false));
3571
3571
  return null;
3572
3572
  }
3573
- const target = this.resolveRequestedTargetContext(session, targetId, explicitTargetId !== null);
3573
+ let target = this.resolveRequestedTargetContext(session, targetId, explicitTargetId !== null);
3574
+ if (!target
3575
+ && explicitTargetId !== null
3576
+ && this.shouldRecoverExplicitCanvasOverlayTarget(message.command, targetId)) {
3577
+ target = this.recoverExplicitCanvasOverlayTarget(session, targetId);
3578
+ }
3574
3579
  if (!target) {
3575
3580
  this.sendError(message, buildError("invalid_request", "Active target missing", false));
3576
3581
  return null;
@@ -3592,6 +3597,18 @@ export class OpsRuntime {
3592
3597
  }
3593
3598
  return target;
3594
3599
  }
3600
+ shouldRecoverExplicitCanvasOverlayTarget(command, targetId) {
3601
+ return command.startsWith("canvas.overlay.") && parseTabTargetId(targetId) !== null;
3602
+ }
3603
+ recoverExplicitCanvasOverlayTarget(session, targetId) {
3604
+ const tabId = parseTabTargetId(targetId);
3605
+ if (tabId === null) {
3606
+ return null;
3607
+ }
3608
+ const recoveredTarget = session.targets.get(targetId) ?? this.sessions.addTarget(session.id, tabId);
3609
+ session.activeTargetId = recoveredTarget.targetId;
3610
+ return this.resolveRequestedTargetContext(session, recoveredTarget.targetId, true);
3611
+ }
3595
3612
  isAllowedCanvasRestrictionTarget(session, targetId, target) {
3596
3613
  if (this.isAllowedCanvasTargetUrl(target.url)) {
3597
3614
  return true;
@@ -82,18 +82,31 @@ const setHealthNote = (message) => {
82
82
  currentHealthNote = next;
83
83
  healthNote.textContent = next;
84
84
  };
85
- const setStatus = (status) => {
86
- const isConnected = status === "connected";
87
- statusEl.textContent = isConnected ? "Connected" : "Disconnected";
85
+ const hasRelayHealthyMismatch = (response) => {
86
+ return response.status === "disconnected"
87
+ && response.relayHealth?.extensionConnected === true
88
+ && response.relayHealth.extensionHandshakeComplete === true;
89
+ };
90
+ const applyStatusTone = (label, tone) => {
91
+ const isConnected = tone === "connected";
92
+ const isWarning = tone === "warning";
93
+ statusEl.textContent = label;
88
94
  toggleButton.textContent = isConnected ? "Disconnect" : "Connect";
89
- if (isConnected) {
90
- statusIndicator.classList.add("connected");
91
- statusPill.classList.add("connected");
95
+ statusIndicator.classList.toggle("connected", isConnected);
96
+ statusPill.classList.toggle("connected", isConnected);
97
+ statusIndicator.classList.toggle("warning", isWarning);
98
+ statusPill.classList.toggle("warning", isWarning);
99
+ };
100
+ const setStatus = (response) => {
101
+ if (response.status === "connected") {
102
+ applyStatusTone("Connected", "connected");
103
+ return;
92
104
  }
93
- else {
94
- statusIndicator.classList.remove("connected");
95
- statusPill.classList.remove("connected");
105
+ if (hasRelayHealthyMismatch(response)) {
106
+ applyStatusTone("Relay active", "warning");
107
+ return;
96
108
  }
109
+ applyStatusTone("Disconnected", "disconnected");
97
110
  };
98
111
  const setHealthValue = (element, value, tone) => {
99
112
  element.textContent = value;
@@ -180,7 +193,7 @@ const setNativeHealth = (health, enabled) => {
180
193
  }
181
194
  };
182
195
  const applyStatus = (response) => {
183
- setStatus(response.status);
196
+ setStatus(response);
184
197
  setNote(response.note);
185
198
  setHealth(response.relayHealth ?? null);
186
199
  setNativeHealth(response.nativeHealth ?? null, response.nativeEnabled === true);
@@ -239,7 +252,7 @@ const refreshStatus = async () => {
239
252
  }
240
253
  catch (error) {
241
254
  logError("popup.status_refresh", error, { code: "status_refresh_failed" });
242
- setStatus("disconnected");
255
+ setStatus({ status: "disconnected", relayHealth: null });
243
256
  const message = error instanceof Error ? error.message : "Background unavailable";
244
257
  setNote(message);
245
258
  setHealth(null);
@@ -563,7 +576,7 @@ const toggle = async () => {
563
576
  });
564
577
  }
565
578
  else {
566
- setStatus("disconnected");
579
+ setStatus({ status: "disconnected", relayHealth: null });
567
580
  setNote("Auto-pair failed. Start the daemon and retry.");
568
581
  setTimeout(() => refreshStatus(), 2000);
569
582
  return;
@@ -579,7 +592,7 @@ const toggle = async () => {
579
592
  }
580
593
  catch (error) {
581
594
  logError("popup.toggle", error, { code: "toggle_failed" });
582
- setStatus("disconnected");
595
+ setStatus({ status: "disconnected", relayHealth: null });
583
596
  const message = error instanceof Error ? error.message : "Background unavailable";
584
597
  setNote(message);
585
598
  setHealth(null);
@@ -588,7 +601,7 @@ const toggle = async () => {
588
601
  toggleButton.addEventListener("click", () => {
589
602
  toggle().catch((error) => {
590
603
  logError("popup.toggle", error, { code: "toggle_failed" });
591
- setStatus("disconnected");
604
+ setStatus({ status: "disconnected", relayHealth: null });
592
605
  });
593
606
  });
594
607
  annotationStartButton.addEventListener("click", () => {
@@ -663,7 +676,7 @@ autoConnectInput.addEventListener("change", () => {
663
676
  if (enabled && statusEl.textContent !== "Connected") {
664
677
  toggle().catch((error) => {
665
678
  logError("popup.auto_connect", error, { code: "auto_connect_failed" });
666
- setStatus("disconnected");
679
+ setStatus({ status: "disconnected", relayHealth: null });
667
680
  setNote();
668
681
  });
669
682
  }
@@ -702,7 +715,7 @@ relayPortInput.addEventListener("input", () => {
702
715
  });
703
716
  refreshStatus().catch((error) => {
704
717
  logError("popup.refresh_status", error, { code: "status_refresh_failed" });
705
- setStatus("disconnected");
718
+ setStatus({ status: "disconnected", relayHealth: null });
706
719
  setNote();
707
720
  });
708
721
  loadSettings().catch((error) => {
@@ -65,6 +65,7 @@ export class ConnectionManager {
65
65
  relayEpoch = null;
66
66
  relayConfirmedPort = null;
67
67
  relayNotice = null;
68
+ reconnectSuppressed = false;
68
69
  maxReconnectDelayMs = RECONNECT_MAX_DELAY_MS;
69
70
  connectPromise = null;
70
71
  annotationHandler = null;
@@ -96,6 +97,9 @@ export class ConnectionManager {
96
97
  getRelayNotice() {
97
98
  return this.relayNotice;
98
99
  }
100
+ isReconnectSuppressed() {
101
+ return this.reconnectSuppressed;
102
+ }
99
103
  getLastError() {
100
104
  return this.lastError;
101
105
  }
@@ -245,6 +249,7 @@ export class ConnectionManager {
245
249
  }
246
250
  try {
247
251
  this.clearLastError();
252
+ this.reconnectSuppressed = false;
248
253
  this.relayNotice = null;
249
254
  this.shouldReconnect = true;
250
255
  this.reconnectAttempts = 0;
@@ -275,6 +280,7 @@ export class ConnectionManager {
275
280
  if (this.disconnecting)
276
281
  return;
277
282
  this.disconnecting = true;
283
+ this.reconnectSuppressed = false;
278
284
  this.shouldReconnect = false;
279
285
  this.clearReconnectTimer();
280
286
  this.stopHeartbeat();
@@ -512,7 +518,7 @@ export class ConnectionManager {
512
518
  this.canvasHandler?.(message);
513
519
  },
514
520
  onClose: (detail) => {
515
- this.handleRelayClose(detail);
521
+ this.handleRelayClose(relay, detail);
516
522
  }
517
523
  });
518
524
  this.relay = relay;
@@ -563,10 +569,25 @@ export class ConnectionManager {
563
569
  throw new ConnectionError("relay_connect_failed", "Relay connection failed. Start the daemon and retry.");
564
570
  }
565
571
  }
566
- handleRelayClose(detail) {
572
+ handleRelayClose(closedRelay, detail) {
573
+ if (this.relay !== closedRelay) {
574
+ return;
575
+ }
567
576
  this.stopHeartbeat();
568
577
  this.relay = null;
569
578
  this.cdp.markClientClosed();
579
+ if (this.isRelayReplacedByNewClient(detail)) {
580
+ this.reconnectSuppressed = true;
581
+ this.shouldReconnect = false;
582
+ this.clearReconnectTimer();
583
+ this.setStatus("disconnected");
584
+ this.relayInstanceId = null;
585
+ this.relayConfirmedPort = null;
586
+ this.relayEpoch = null;
587
+ this.relayNotice = "Another extension client took over the relay connection. This client will stay disconnected until you reconnect it explicitly.";
588
+ return;
589
+ }
590
+ this.reconnectSuppressed = false;
570
591
  if (detail && (detail.code === 1008 || detail.reason?.includes("Invalid pairing token"))) {
571
592
  this.clearStoredPairingToken();
572
593
  }
@@ -1005,11 +1026,15 @@ export class ConnectionManager {
1005
1026
  this.reconnectTimer = null;
1006
1027
  }
1007
1028
  }
1029
+ isRelayReplacedByNewClient(detail) {
1030
+ return detail?.reason === "Replaced by a new extension client";
1031
+ }
1008
1032
  async verifyHandshakeHealth(relay, source) {
1009
1033
  try {
1010
1034
  const health = await relay.sendPing(this.heartbeatTimeoutMs);
1011
1035
  if (health.extensionConnected && health.extensionHandshakeComplete) {
1012
1036
  if (this.relay === relay) {
1037
+ this.reconnectSuppressed = false;
1013
1038
  this.relayNotice = null;
1014
1039
  }
1015
1040
  return true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "OpenDevBrowser Relay",
4
- "version": "0.0.19",
4
+ "version": "0.0.21",
5
5
  "description": "Optional bridge to reuse existing Chrome tabs with OpenDevBrowser.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -126,6 +126,12 @@
126
126
  background: var(--accent-soft);
127
127
  }
128
128
 
129
+ .status-pill.warning {
130
+ color: var(--text);
131
+ border-color: rgba(245, 158, 11, 0.55);
132
+ background: rgba(245, 158, 11, 0.18);
133
+ }
134
+
129
135
  .status-indicator {
130
136
  width: 8px;
131
137
  height: 8px;
@@ -140,6 +146,11 @@
140
146
  box-shadow: 0 0 12px rgba(32, 213, 198, 0.55);
141
147
  }
142
148
 
149
+ .status-indicator.warning {
150
+ background: #f59e0b;
151
+ box-shadow: 0 0 12px rgba(245, 158, 11, 0.45);
152
+ }
153
+
143
154
  .panel {
144
155
  position: relative;
145
156
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opendevbrowser",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Browser automation runtime with snapshot-refs-actions, browser replay screencasts, public read-only desktop observation, and browser-scoped computer-use orchestration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,10 +45,10 @@
45
45
  "node": ">=18"
46
46
  },
47
47
  "scripts": {
48
- "build": "tsup src/index.ts src/cli/index.ts src/skills/skill-loader.ts --format esm --clean --sourcemap && tsc --emitDeclarationOnly --declaration --declarationMap -p tsconfig.json && node scripts/postbuild-dist.mjs",
48
+ "build": "node scripts/run-package-tool.mjs tsup src/index.ts src/cli/index.ts src/skills/skill-loader.ts --format esm --clean --sourcemap && node scripts/run-package-tool.mjs tsc --emitDeclarationOnly --declaration --declarationMap -p tsconfig.json && node scripts/postbuild-dist.mjs",
49
49
  "dev": "tsup src/index.ts src/cli/index.ts src/skills/skill-loader.ts --format esm --dts --watch",
50
- "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
51
- "typecheck": "tsc --noEmit -p tsconfig.json",
50
+ "lint": "node scripts/run-package-tool.mjs eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
51
+ "typecheck": "node scripts/run-package-tool.mjs tsc --noEmit -p tsconfig.json",
52
52
  "test": "node scripts/run-vitest-coverage.mjs",
53
53
  "test:release-gate": "node scripts/release-gate-test-groups.mjs",
54
54
  "test:release-gate:g1": "node scripts/release-gate-test-groups.mjs --group 1",
@@ -57,7 +57,7 @@
57
57
  "test:release-gate:g4": "node scripts/release-gate-test-groups.mjs --group 4",
58
58
  "test:release-gate:g5": "node scripts/release-gate-test-groups.mjs --group 5",
59
59
  "extension:sync": "node scripts/sync-extension-version.mjs",
60
- "extension:build": "npm run extension:sync && tsc -p extension/tsconfig.json && node scripts/copy-extension-assets.mjs",
60
+ "extension:build": "npm run extension:sync && node scripts/run-package-tool.mjs tsc -p extension/tsconfig.json && node scripts/copy-extension-assets.mjs",
61
61
  "extension:pack": "cd extension && zip -r ../opendevbrowser-extension.zip manifest.json popup.html canvas.html dist/ icons/",
62
62
  "extension:store": "node scripts/chrome-store-publish.mjs",
63
63
  "version:check": "node scripts/verify-versions.mjs",
package/skills/AGENTS.md CHANGED
@@ -39,9 +39,9 @@ Content organized by topic for filtering.
39
39
  3. `.codex/skills/` (project compatibility)
40
40
  4. `$CODEX_HOME/skills` (global compatibility; fallback `~/.codex/skills`)
41
41
  5. `.claude/skills/` (ClaudeCode project compatibility)
42
- 6. `$CLAUDECODE_HOME/skills` or `$CLAUDE_HOME/skills` (ClaudeCode global compatibility; fallback `~/.claude/skills`)
42
+ 6. `$CLAUDECODE_HOME/skills` (ClaudeCode global compatibility; fallback `~/.claude/skills`)
43
43
  7. `.amp/skills/` (AmpCLI project compatibility)
44
- 8. `$AMPCLI_HOME/skills` or `$AMP_CLI_HOME/skills` or `$AMP_HOME/skills` (AmpCLI global compatibility; fallback `~/.amp/skills`)
44
+ 8. `$AMP_CLI_HOME/skills` (AmpCLI global compatibility; fallback `~/.amp/skills`)
45
45
  9. `skillPaths` config (custom)
46
46
  10. Bundled package `skills/` directory as a fallback when no installed copy is available
47
47