@btraut/browser-bridge 0.13.0 → 0.13.1
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.
- package/CHANGELOG.md +7 -0
- package/dist/api.js +157 -39
- package/dist/api.js.map +3 -3
- package/dist/index.js +90 -39
- package/dist/index.js.map +3 -3
- package/extension/dist/background.js +92 -80
- package/extension/dist/background.js.map +3 -3
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/browser-bridge/skill.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ The format is based on "Keep a Changelog", and this project adheres to Semantic
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.13.1] - 2026-02-18
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Extension packaging now resolves `@btraut/browser-bridge-shared/dist/*` imports from workspace source during zip builds, so release packaging works from clean CI checkouts without prebuilt shared artifacts.
|
|
14
|
+
- Core startup now handles shared-port collisions more clearly (POST/GET health compatibility probe + actionable occupied-port fallback), drive tab messaging retries transient post-navigation channel-closure races more aggressively, `drive.navigate` avoids false timeouts when URL commit succeeds without a DOM event, and diagnostics now surfaces inspect capability + shared-core metadata mismatch checks in one pass.
|
|
15
|
+
|
|
9
16
|
## [0.13.0] - 2026-02-18
|
|
10
17
|
|
|
11
18
|
### Changed
|
package/dist/api.js
CHANGED
|
@@ -41,6 +41,7 @@ var import_express2 = __toESM(require("express"));
|
|
|
41
41
|
|
|
42
42
|
// packages/shared/src/core-readiness.ts
|
|
43
43
|
var import_promises = require("node:timers/promises");
|
|
44
|
+
var import_node_net = require("node:net");
|
|
44
45
|
|
|
45
46
|
// packages/shared/src/logging.ts
|
|
46
47
|
var import_node_fs2 = require("node:fs");
|
|
@@ -637,6 +638,27 @@ var DEFAULT_HEALTH_RETRY_MS = 250;
|
|
|
637
638
|
var DEFAULT_HEALTH_ATTEMPTS = 20;
|
|
638
639
|
var DEFAULT_HEALTH_TIMEOUT_MS = 2e3;
|
|
639
640
|
var DEFAULT_HEALTH_BUDGET_MS = 15e3;
|
|
641
|
+
var DEFAULT_PORT_REACHABILITY_TIMEOUT_MS = 300;
|
|
642
|
+
var isPortReachableDefault = async (runtime) => {
|
|
643
|
+
return await new Promise((resolve3) => {
|
|
644
|
+
const socket = new import_node_net.Socket();
|
|
645
|
+
let settled = false;
|
|
646
|
+
const finish = (reachable) => {
|
|
647
|
+
if (settled) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
settled = true;
|
|
651
|
+
socket.removeAllListeners();
|
|
652
|
+
socket.destroy();
|
|
653
|
+
resolve3(reachable);
|
|
654
|
+
};
|
|
655
|
+
socket.setTimeout(DEFAULT_PORT_REACHABILITY_TIMEOUT_MS);
|
|
656
|
+
socket.once("connect", () => finish(true));
|
|
657
|
+
socket.once("timeout", () => finish(false));
|
|
658
|
+
socket.once("error", () => finish(false));
|
|
659
|
+
socket.connect(runtime.port, runtime.host);
|
|
660
|
+
});
|
|
661
|
+
};
|
|
640
662
|
var resolveTimeoutMs = (timeoutMs) => {
|
|
641
663
|
const candidate = timeoutMs ?? (process.env.BROWSER_BRIDGE_CORE_TIMEOUT_MS ? Number.parseInt(process.env.BROWSER_BRIDGE_CORE_TIMEOUT_MS, 10) : process.env.BROWSER_VISION_CORE_TIMEOUT_MS ? Number.parseInt(process.env.BROWSER_VISION_CORE_TIMEOUT_MS, 10) : void 0);
|
|
642
664
|
if (candidate === void 0 || candidate === null) {
|
|
@@ -702,57 +724,71 @@ var createCoreReadinessController = (options = {}) => {
|
|
|
702
724
|
baseUrl = `http://${runtime.host}:${runtime.port}`;
|
|
703
725
|
};
|
|
704
726
|
const checkHealth = async () => {
|
|
705
|
-
|
|
706
|
-
const controller = new AbortController();
|
|
707
|
-
const timeout = setTimeout(() => controller.abort(), healthTimeoutMs);
|
|
727
|
+
for (const method of ["POST", "GET"]) {
|
|
708
728
|
try {
|
|
709
|
-
|
|
729
|
+
const controller = new AbortController();
|
|
730
|
+
const timeout = setTimeout(() => controller.abort(), healthTimeoutMs);
|
|
710
731
|
try {
|
|
711
|
-
response
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
732
|
+
let response;
|
|
733
|
+
try {
|
|
734
|
+
response = await fetchImpl(`${baseUrl}/health`, {
|
|
735
|
+
method,
|
|
736
|
+
signal: controller.signal
|
|
737
|
+
});
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (controller.signal.aborted || error instanceof Error && error.name === "AbortError") {
|
|
740
|
+
logger.warn(`${logPrefix}.health.timeout`, {
|
|
741
|
+
base_url: baseUrl,
|
|
742
|
+
method,
|
|
743
|
+
timeout_ms: healthTimeoutMs
|
|
744
|
+
});
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
logger.warn(`${logPrefix}.health.fetch_failed`, {
|
|
718
748
|
base_url: baseUrl,
|
|
719
|
-
|
|
749
|
+
method,
|
|
750
|
+
error
|
|
720
751
|
});
|
|
721
|
-
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
754
|
+
if (!response.ok) {
|
|
755
|
+
logger.warn(`${logPrefix}.health.non_ok`, {
|
|
756
|
+
base_url: baseUrl,
|
|
757
|
+
method,
|
|
758
|
+
status: response.status
|
|
759
|
+
});
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const data = await response.json().catch(() => null);
|
|
763
|
+
const ok = Boolean(data?.ok);
|
|
764
|
+
if (ok) {
|
|
765
|
+
if (method === "GET") {
|
|
766
|
+
logger.info(`${logPrefix}.health.compat_probe`, {
|
|
767
|
+
base_url: baseUrl,
|
|
768
|
+
method
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
return true;
|
|
722
772
|
}
|
|
723
|
-
logger.warn(`${logPrefix}.health.fetch_failed`, {
|
|
724
|
-
base_url: baseUrl,
|
|
725
|
-
error
|
|
726
|
-
});
|
|
727
|
-
throw error;
|
|
728
|
-
}
|
|
729
|
-
if (!response.ok) {
|
|
730
|
-
logger.warn(`${logPrefix}.health.non_ok`, {
|
|
731
|
-
base_url: baseUrl,
|
|
732
|
-
status: response.status
|
|
733
|
-
});
|
|
734
|
-
return false;
|
|
735
|
-
}
|
|
736
|
-
const data = await response.json().catch(() => null);
|
|
737
|
-
const ok = Boolean(data?.ok);
|
|
738
|
-
if (!ok) {
|
|
739
773
|
logger.warn(`${logPrefix}.health.not_ready`, {
|
|
740
|
-
base_url: baseUrl
|
|
774
|
+
base_url: baseUrl,
|
|
775
|
+
method
|
|
741
776
|
});
|
|
777
|
+
} finally {
|
|
778
|
+
clearTimeout(timeout);
|
|
742
779
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
780
|
+
} catch (error) {
|
|
781
|
+
logger.warn(`${logPrefix}.health.error`, {
|
|
782
|
+
base_url: baseUrl,
|
|
783
|
+
method,
|
|
784
|
+
error
|
|
785
|
+
});
|
|
746
786
|
}
|
|
747
|
-
} catch (error) {
|
|
748
|
-
logger.warn(`${logPrefix}.health.error`, {
|
|
749
|
-
base_url: baseUrl,
|
|
750
|
-
error
|
|
751
|
-
});
|
|
752
|
-
return false;
|
|
753
787
|
}
|
|
788
|
+
return false;
|
|
754
789
|
};
|
|
755
790
|
const ensureCoreRunning = async () => {
|
|
791
|
+
const portReachabilityCheck = options.portReachabilityCheck ?? isPortReachableDefault;
|
|
756
792
|
refreshRuntime();
|
|
757
793
|
if (await checkHealth()) {
|
|
758
794
|
logger.debug(`${logPrefix}.ensure_ready.already_running`, {
|
|
@@ -793,6 +829,21 @@ var createCoreReadinessController = (options = {}) => {
|
|
|
793
829
|
health_budget_ms: healthBudgetMs,
|
|
794
830
|
health_timeout_ms: healthTimeoutMs
|
|
795
831
|
});
|
|
832
|
+
let portOccupied = false;
|
|
833
|
+
try {
|
|
834
|
+
portOccupied = await portReachabilityCheck(runtime);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
logger.warn(`${logPrefix}.ensure_ready.port_probe_failed`, {
|
|
837
|
+
host: runtime.host,
|
|
838
|
+
port: runtime.port,
|
|
839
|
+
error
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
if (portOccupied) {
|
|
843
|
+
throw new Error(
|
|
844
|
+
`Core daemon failed to start on ${runtime.host}:${runtime.port}. A process is already listening on this port but did not pass Browser Bridge health checks. Retry with --no-daemon to reuse it, or enable isolated mode (BROWSER_BRIDGE_ISOLATED_MODE=1) for per-worktree ports.`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
796
847
|
throw new Error(
|
|
797
848
|
`Core daemon failed to start on ${runtime.host}:${runtime.port}.`
|
|
798
849
|
);
|
|
@@ -4703,6 +4754,13 @@ var endpointLabel = (endpoint) => {
|
|
|
4703
4754
|
}
|
|
4704
4755
|
return "unknown";
|
|
4705
4756
|
};
|
|
4757
|
+
var readCapability = (capabilities, name) => {
|
|
4758
|
+
if (!capabilities) {
|
|
4759
|
+
return void 0;
|
|
4760
|
+
}
|
|
4761
|
+
const candidate = capabilities[name];
|
|
4762
|
+
return typeof candidate === "boolean" ? candidate : void 0;
|
|
4763
|
+
};
|
|
4706
4764
|
var hasEndpoint = (endpoint) => Boolean(
|
|
4707
4765
|
endpoint && typeof endpoint.host === "string" && endpoint.host.length > 0 && typeof endpoint.port === "number" && Number.isFinite(endpoint.port)
|
|
4708
4766
|
);
|
|
@@ -4781,6 +4839,23 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4781
4839
|
}
|
|
4782
4840
|
});
|
|
4783
4841
|
}
|
|
4842
|
+
if (callerEndpoint?.metadataPath && coreEndpoint?.metadataPath) {
|
|
4843
|
+
const matches = callerEndpoint.metadataPath === coreEndpoint.metadataPath;
|
|
4844
|
+
checks.push({
|
|
4845
|
+
name: "runtime.caller.metadata_path_match",
|
|
4846
|
+
ok: matches,
|
|
4847
|
+
message: matches ? "Caller metadata path matches the active core runtime metadata path." : "Caller metadata path differs from core runtime metadata path (shared core across worktrees).",
|
|
4848
|
+
details: {
|
|
4849
|
+
caller_metadata_path: callerEndpoint.metadataPath,
|
|
4850
|
+
core_metadata_path: coreEndpoint.metadataPath
|
|
4851
|
+
}
|
|
4852
|
+
});
|
|
4853
|
+
if (!matches) {
|
|
4854
|
+
warnings.push(
|
|
4855
|
+
"CLI is talking to a core process started from a different worktree metadata path. If daemon auto-start fails, retry with --no-daemon or enable isolated mode."
|
|
4856
|
+
);
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4784
4859
|
if (extensionConnected && hasEndpoint(coreEndpoint) && hasEndpoint(extensionEndpoint)) {
|
|
4785
4860
|
const matches = coreEndpoint.host === extensionEndpoint.host && coreEndpoint.port === extensionEndpoint.port;
|
|
4786
4861
|
checks.push({
|
|
@@ -4798,6 +4873,49 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4798
4873
|
}
|
|
4799
4874
|
});
|
|
4800
4875
|
}
|
|
4876
|
+
if (extensionConnected) {
|
|
4877
|
+
const capabilityNegotiated = context.runtime?.extension?.capabilityNegotiated ?? false;
|
|
4878
|
+
const capabilities = context.runtime?.extension?.capabilities;
|
|
4879
|
+
const driveNavigateCapability = readCapability(
|
|
4880
|
+
capabilities,
|
|
4881
|
+
"drive.navigate"
|
|
4882
|
+
);
|
|
4883
|
+
const inspectAttachCapability = readCapability(
|
|
4884
|
+
capabilities,
|
|
4885
|
+
"debugger.attach"
|
|
4886
|
+
);
|
|
4887
|
+
const inspectCommandCapability = readCapability(
|
|
4888
|
+
capabilities,
|
|
4889
|
+
"debugger.command"
|
|
4890
|
+
);
|
|
4891
|
+
checks.push({
|
|
4892
|
+
name: "runtime.extension.capability_negotiated",
|
|
4893
|
+
ok: capabilityNegotiated,
|
|
4894
|
+
message: capabilityNegotiated ? "Extension capability negotiation completed." : "Extension capability negotiation is incomplete; action availability may be stale."
|
|
4895
|
+
});
|
|
4896
|
+
checks.push({
|
|
4897
|
+
name: "drive.capability",
|
|
4898
|
+
ok: driveNavigateCapability !== false,
|
|
4899
|
+
message: driveNavigateCapability === false ? "Drive actions are disabled by extension capability negotiation." : "Drive actions are available.",
|
|
4900
|
+
details: driveNavigateCapability === void 0 ? { capability: "drive.navigate", state: "unknown" } : { capability: "drive.navigate", enabled: driveNavigateCapability }
|
|
4901
|
+
});
|
|
4902
|
+
const inspectEnabled = inspectAttachCapability === true && inspectCommandCapability === true;
|
|
4903
|
+
checks.push({
|
|
4904
|
+
name: "inspect.capability",
|
|
4905
|
+
ok: capabilityNegotiated && inspectEnabled,
|
|
4906
|
+
message: capabilityNegotiated && inspectEnabled ? "Inspect debugger capability is enabled." : capabilityNegotiated ? "Inspect debugger capability is disabled in extension options." : "Inspect capability is unknown until extension capability negotiation completes.",
|
|
4907
|
+
details: {
|
|
4908
|
+
required_capabilities: ["debugger.attach", "debugger.command"],
|
|
4909
|
+
debugger_attach: inspectAttachCapability,
|
|
4910
|
+
debugger_command: inspectCommandCapability
|
|
4911
|
+
}
|
|
4912
|
+
});
|
|
4913
|
+
if (capabilityNegotiated && !inspectEnabled) {
|
|
4914
|
+
warnings.push(
|
|
4915
|
+
"Inspect commands require debugger capability. Enable debugger-based inspect in extension options to use inspect.* routes."
|
|
4916
|
+
);
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4801
4919
|
const callerVersion = context.runtime?.caller?.process?.version;
|
|
4802
4920
|
const extensionVersion = extensionConnected ? context.runtime?.extension?.version : void 0;
|
|
4803
4921
|
if (callerVersion && extensionVersion) {
|