machine-bridge-mcp 1.1.5 → 1.2.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CODE_OF_CONDUCT.md +24 -0
  3. package/CONTRIBUTING.md +3 -1
  4. package/GOVERNANCE.md +50 -0
  5. package/README.md +26 -0
  6. package/SUPPORT.md +31 -0
  7. package/browser-extension/browser-operations.js +10 -4
  8. package/browser-extension/devtools-input.js +2 -2
  9. package/browser-extension/manifest.json +2 -2
  10. package/browser-extension/page-automation.js +1 -1
  11. package/docs/ARCHITECTURE.md +14 -17
  12. package/docs/AUDIT.md +28 -0
  13. package/docs/ENGINEERING.md +8 -2
  14. package/docs/PROJECT_STANDARDS.md +4 -2
  15. package/docs/TESTING.md +9 -3
  16. package/docs/UPGRADING.md +32 -0
  17. package/package.json +12 -4
  18. package/scripts/coverage-check.mjs +27 -10
  19. package/src/local/account-access.mjs +7 -5
  20. package/src/local/agent-context.mjs +7 -148
  21. package/src/local/agent-contract.mjs +206 -0
  22. package/src/local/browser-bridge.mjs +30 -281
  23. package/src/local/browser-extension-protocol.mjs +19 -4
  24. package/src/local/browser-operation-service.mjs +325 -0
  25. package/src/local/call-registry.mjs +44 -1
  26. package/src/local/capability-ranking.mjs +13 -0
  27. package/src/local/cli-local-admin.mjs +74 -38
  28. package/src/local/cli-options.mjs +18 -18
  29. package/src/local/cli-policy.mjs +1 -1
  30. package/src/local/cli-service.mjs +132 -0
  31. package/src/local/cli.mjs +27 -109
  32. package/src/local/daemon-process.mjs +1 -1
  33. package/src/local/full-access-test.mjs +1 -1
  34. package/src/local/job-runner.mjs +9 -3
  35. package/src/local/managed-job-plan.mjs +3 -3
  36. package/src/local/monotonic-deadline.mjs +7 -0
  37. package/src/local/numbers.mjs +8 -0
  38. package/src/local/policy.mjs +97 -16
  39. package/src/local/project-metadata.mjs +7 -1
  40. package/src/local/project-package.mjs +1 -1
  41. package/src/local/records.mjs +6 -0
  42. package/src/local/resource-operations.mjs +1 -1
  43. package/src/local/runtime-capabilities.mjs +66 -0
  44. package/src/local/runtime-diagnostics.mjs +91 -0
  45. package/src/local/runtime-reporting.mjs +113 -0
  46. package/src/local/runtime.mjs +54 -191
  47. package/src/local/service-convergence.mjs +1 -1
  48. package/src/local/service.mjs +1 -1
  49. package/src/local/state.mjs +1 -1
  50. package/src/local/windows-service.mjs +1 -1
  51. package/src/local/worker-deployment.mjs +15 -10
  52. package/src/local/worker-health.mjs +8 -4
  53. package/src/worker/access.ts +8 -7
  54. package/src/worker/account-admin.ts +2 -2
  55. package/src/worker/authority.ts +3 -3
  56. package/src/worker/http.ts +2 -2
  57. package/src/worker/index.ts +29 -337
  58. package/src/worker/oauth-controller.ts +343 -0
  59. package/src/worker/oauth-state.ts +1 -1
  60. package/src/worker/oauth-tokens.ts +2 -2
  61. package/src/worker/tool-catalog.ts +1 -1
  62. package/tsconfig.json +2 -1
  63. package/tsconfig.local.json +27 -0
@@ -1,12 +1,11 @@
1
- import { randomBytes } from "node:crypto";
2
1
  import { mkdirSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
3
- import { lstat, realpath, rm, stat, writeFile } from "node:fs/promises";
2
+ import { lstat, realpath, stat } from "node:fs/promises";
4
3
  import { tmpdir } from "node:os";
5
4
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
5
  import { RelayConnection } from "./relay-connection.mjs";
7
6
  import { ProcessSessionManager } from "./process-sessions.mjs";
8
7
  export { MAX_COMMAND_BYTES } from "./process-sessions.mjs";
9
- import { allToolNames, isCanonicalFullPolicy, MCP_PROTOCOL_VERSION, MCP_SUPPORTED_PROTOCOL_VERSIONS, normalizePolicy, POLICY_PROFILES, PolicyGate, SERVER_NAME } from "./tools.mjs";
8
+ import { MCP_SUPPORTED_PROTOCOL_VERSIONS, normalizePolicy, PolicyGate, SERVER_NAME } from "./tools.mjs";
10
9
  import { publicError } from "./errors.mjs";
11
10
  import { ProcessTracker } from "./process-tracker.mjs";
12
11
  import { CallRegistry } from "./call-registry.mjs";
@@ -15,7 +14,7 @@ import { ToolExecutor } from "./tool-executor.mjs";
15
14
  import { boundedErrorMessage, ProcessExecutionService } from "./process-execution.mjs";
16
15
  import { GitService } from "./git-service.mjs";
17
16
  import { LifecycleController } from "./lifecycle.mjs";
18
- import { MAX_WRITE_BYTES, readBoundedFile, sha256, WorkspaceFileService } from "./workspace-file-service.mjs";
17
+ import { MAX_WRITE_BYTES, sha256, WorkspaceFileService } from "./workspace-file-service.mjs";
19
18
  export { MAX_WRITE_BYTES, sha256 } from "./workspace-file-service.mjs";
20
19
  import { classifyOperationalError } from "./log.mjs";
21
20
  import { inspectResourceFile, ManagedJobManager } from "./managed-jobs.mjs";
@@ -29,6 +28,12 @@ import { readBoundedRegularFileSync } from "./secure-file.mjs";
29
28
  import { clampInteger } from "./numbers.mjs";
30
29
  import { isPlainRecord } from "./records.mjs";
31
30
  import { AccountAccessGate, normalizeAccountRole } from "./account-access.mjs";
31
+ import { buildProjectOverview, buildRuntimeInfo } from "./runtime-reporting.mjs";
32
+ import { diagnoseRuntime as runRuntimeDiagnostics } from "./runtime-diagnostics.mjs";
33
+ import {
34
+ resolveTaskCapabilities as resolveRuntimeTaskCapabilities,
35
+ sessionBootstrap as buildRuntimeSessionBootstrap,
36
+ } from "./runtime-capabilities.mjs";
32
37
 
33
38
  const MAX_WS_MESSAGE_BYTES = 8 * 1024 * 1024;
34
39
  const MAX_CONCURRENT_TOOL_CALLS = 16;
@@ -232,53 +237,21 @@ export class LocalRuntime {
232
237
  }
233
238
 
234
239
  runtimeInfo() {
235
- return {
236
- name: SERVER_NAME,
237
- protocol_version: MCP_PROTOCOL_VERSION,
238
- supported_protocol_versions: MCP_SUPPORTED_PROTOCOL_VERSIONS,
239
- workspace: this.displayPath(this.workspace),
240
- workspace_name: this.policy.exposeAbsolutePaths ? basename(this.workspace) : "workspace",
240
+ return buildRuntimeInfo({
241
+ workspace: this.workspace,
242
+ displayPath: (value) => this.displayPath(value),
241
243
  policy: this.policy,
242
- policy_contract: {
243
- named_profile_is_canonical: this.policy.profile === "custom" || policyMatchesNamedProfile(this.policy),
244
- full_catalog_complete: this.policy.profile === "full" ? isCanonicalFullPolicy(this.policy) && this.tools().length + 1 === allToolNames().length : null,
245
- machine_bridge_internal_denials_under_full: this.policy.profile === "full" && isCanonicalFullPolicy(this.policy) ? false : null,
246
- },
247
- enforcement: {
248
- filesystem_scope: this.policy.unrestrictedPaths ? "local-user-accessible" : "workspace",
249
- sensitive_filename_filter: false,
250
- operating_system_permissions_apply: true,
251
- host_policy_is_independent: true,
252
- },
253
- tool_delivery: {
254
- full_profile_scope: "local-daemon-and-relay-advertisement",
255
- daemon_advertised_tool_count: this.tools().length + 1,
256
- host_exposed_tools_known_to_server: false,
257
- host_may_expose_subset: true,
258
- },
259
- tools: ["server_info", ...this.tools()],
260
- observability: {
261
- relay_readiness: "authenticated-hello-acknowledged",
262
- brief_relay_interruptions: "debug-only",
263
- raw_transport_details: "debug-only",
264
- per_tool_events: "structured-debug-events",
265
- default_logs_include_tool_failures: false,
266
- tool_arguments_or_results_logged: false,
267
- capability_routing: this.capabilityObserver.snapshot(),
268
- tool_calls: this.observability.snapshot(),
269
- in_flight_calls: this.callRegistry.snapshot(),
270
- },
271
- runtime: {
272
- environment: this.policy.minimalEnv ? "isolated-minimal" : "full-parent",
273
- lifecycle: this.lifecycle.snapshot(),
274
- relay: this.relay?.status?.() || null,
275
- runtime_dir: this.policy.exposeAbsolutePaths ? this.runtimeDir : "<private-runtime-dir>",
276
- processes: this.processTracker.snapshot(),
277
- process_sessions: this.processSessionManager.status(),
278
- managed_jobs: this.managedJobManager.status(),
279
- local_resources: this.managedJobManager.resourceInfo(),
280
- },
281
- };
244
+ toolNames: this.tools(),
245
+ capabilityObserver: this.capabilityObserver,
246
+ observability: this.observability,
247
+ callRegistry: this.callRegistry,
248
+ lifecycle: this.lifecycle,
249
+ relayStatus: () => this.relay?.status?.() || null,
250
+ runtimeDir: this.runtimeDir,
251
+ processTracker: this.processTracker,
252
+ processSessionManager: this.processSessionManager,
253
+ managedJobManager: this.managedJobManager,
254
+ });
282
255
  }
283
256
 
284
257
  async start() {
@@ -426,18 +399,17 @@ export class LocalRuntime {
426
399
  }
427
400
 
428
401
  async projectOverview(context = {}) {
429
- this.throwIfCancelled(context);
430
- const top = await this.listDir(".", context).catch(error => ({ error: this.safeErrorMessage(error), entries: [] }));
431
- const git = await this.runProcess("git", ["-c", "core.fsmonitor=false", "-C", this.workspace, "rev-parse", "--show-toplevel"], 10_000, true, 512 * 1024, context);
432
- return {
433
- workspace: this.displayPath(this.workspace),
434
- workspaceName: this.policy.exposeAbsolutePaths ? basename(this.workspace) : "workspace",
435
- gitRoot: git.code === 0 ? this.displayPath(git.stdout.trim()) : "",
402
+ return buildProjectOverview({
403
+ workspace: this.workspace,
404
+ displayPath: (value) => this.displayPath(value),
436
405
  policy: this.policy,
437
- tools: ["server_info", ...this.tools()],
438
- capabilityRouting: this.capabilityObserver.snapshot(),
439
- topLevel: top.entries || [],
440
- };
406
+ toolNames: this.tools(),
407
+ capabilityObserver: this.capabilityObserver,
408
+ listTopLevel: (callContext) => this.listDir(".", callContext),
409
+ runProcess: (...args) => this.runProcess(...args),
410
+ safeErrorMessage: (error) => this.safeErrorMessage(error),
411
+ throwIfCancelled: (callContext) => this.throwIfCancelled(callContext),
412
+ }, context);
441
413
  }
442
414
 
443
415
  listRoots() { return this.workspaceFileService.listRoots(); }
@@ -475,82 +447,15 @@ export class LocalRuntime {
475
447
  }
476
448
 
477
449
  async diagnoseRuntime(context = {}) {
478
- this.throwIfCancelled(context);
479
- const checks = [];
480
- checks.push({
481
- layer: "mcp-host-to-daemon",
482
- ok: true,
483
- detail: "This diagnostic request reached the local Machine Bridge runtime.",
484
- });
485
- checks.push({
486
- layer: "machine-bridge-policy",
487
- ok: this.policy.execMode === "direct" || this.policy.execMode === "shell",
488
- detail: `profile=${this.policy.profile}; exec_mode=${this.policy.execMode}; unrestricted_paths=${this.policy.unrestrictedPaths}`,
489
- });
490
-
491
- const probe = join(this.runtimeDir, `.diagnostic-${process.pid}-${randomBytes(6).toString("hex")}`);
492
- try {
493
- await writeFile(probe, "ok\n", { mode: 0o600, flag: "wx" });
494
- const { buffer } = await readBoundedFile(probe, 64, "diagnostic file");
495
- checks.push({ layer: "local-filesystem", ok: buffer.toString("utf8") === "ok\n", error_class: null });
496
- } catch (error) {
497
- checks.push({ layer: "local-filesystem", ok: false, error_class: classifyOperationalError(error) });
498
- } finally {
499
- await rm(probe, { force: true }).catch(() => {});
500
- }
501
-
502
- if (this.policy.execMode === "direct" || this.policy.execMode === "shell") {
503
- const direct = await this.runProcess(
504
- process.execPath,
505
- ["-e", "process.stdout.write('ok')"],
506
- 5000,
507
- true,
508
- 1024,
509
- context,
510
- this.workspace,
511
- ).catch((error) => ({ code: 127, stdout: "", stderr: "", error_class: classifyOperationalError(error) }));
512
- checks.push({
513
- layer: "local-process-spawn",
514
- ok: direct.code === 0 && direct.stdout === "ok",
515
- error_class: direct.error_class || (direct.code === 0 ? null : classifyOperationalError(direct.stderr || direct.stdout || "execution failed")),
516
- });
517
- } else {
518
- checks.push({ layer: "local-process-spawn", ok: false, skipped: true, error_class: "policy_denied" });
519
- }
520
-
521
- if (this.policy.execMode === "shell") {
522
- const result = await this.processExecutionService.probeShell(context)
523
- .catch((error) => ({ code: 127, error_class: classifyOperationalError(error) }));
524
- checks.push({
525
- layer: "local-shell",
526
- ok: result.code === 0,
527
- error_class: result.error_class || (result.code === 0 ? null : classifyOperationalError(result.stderr || result.stdout || "execution failed")),
528
- });
529
- } else {
530
- checks.push({ layer: "local-shell", ok: false, skipped: true, error_class: "policy_denied" });
531
- }
532
-
533
- const storage = this.managedJobManager.diagnoseStorage();
534
- checks.push({ layer: "managed-job-storage", ...storage });
535
- const resources = this.managedJobManager.listResources();
536
- checks.push({
537
- layer: "local-resource-registry",
538
- ok: resources.resources.every((resource) => resource.available),
539
- registered: resources.count,
540
- unavailable: resources.resources.filter((resource) => !resource.available).map((resource) => ({ name: resource.name, error_class: resource.error_class })),
541
- });
542
-
543
- return {
544
- request_reached_local_runtime: true,
545
- interpretation: {
546
- tool_call_blocked_before_response: "host/platform or connector gateway",
547
- diagnostic_reached_daemon_but_spawn_failed: "local OS, endpoint security, shell configuration, or Machine Bridge policy",
548
- managed_job_accepted_then_later_tools_blocked: "job continues independently; inspect with local CLI or a later read_job call",
549
- },
450
+ return runRuntimeDiagnostics({
550
451
  policy: this.policy,
551
- checks,
552
- ok: checks.filter((check) => !check.skipped).every((check) => check.ok),
553
- };
452
+ runtimeDir: this.runtimeDir,
453
+ workspace: this.workspace,
454
+ runProcess: (...args) => this.runProcess(...args),
455
+ probeShell: (callContext) => this.processExecutionService.probeShell(callContext),
456
+ managedJobManager: this.managedJobManager,
457
+ throwIfCancelled: (callContext) => this.throwIfCancelled(callContext),
458
+ }, context);
554
459
  }
555
460
 
556
461
  async generateSshKeyResource(args = {}, context = {}) {
@@ -588,48 +493,27 @@ export class LocalRuntime {
588
493
  };
589
494
  }
590
495
 
591
-
592
496
  async sessionBootstrap(args = {}, context = {}) {
593
- const bootstrap = await this.agentContextManager.sessionBootstrap(args, context);
594
- bootstrap.local_automation = {
595
- applications: this.appAutomationManager.capabilities(),
596
- browser: this.policy.profile === "full" ? {
597
- existing_profile: true,
598
- extension_bridge: true,
599
- status_tool: "browser_status",
600
- } : null,
601
- };
602
- this.capabilityObserver.recordBootstrap(bootstrap);
603
- return bootstrap;
497
+ return buildRuntimeSessionBootstrap({
498
+ agentContextManager: this.agentContextManager,
499
+ appAutomationManager: this.appAutomationManager,
500
+ capabilityObserver: this.capabilityObserver,
501
+ policy: this.policy,
502
+ }, args, context);
604
503
  }
605
504
 
606
505
  async resolveTaskCapabilities(args = {}, context = {}) {
607
- const result = await this.agentContextManager.resolveTaskCapabilities(args, context);
608
- const task = String(args.task || "");
609
- if (this.policy.profile === "full") {
610
- const applications = await this.appAutomationManager.listApplications({ query: "", max_results: 500 }, context).catch(() => ({ applications: [] }));
611
- const lower = task.toLowerCase();
612
- result.application_matches = applications.applications
613
- .map((application) => ({ application, score: applicationMatchScore(lower, application) }))
614
- .filter((item) => item.score > 0)
615
- .sort((left, right) => right.score - left.score || left.application.name.localeCompare(right.application.name))
616
- .slice(0, 20)
617
- .map(({ application, score }) => ({ ...application, score }));
618
- } else {
619
- result.application_matches = [];
620
- }
621
- if (result.application_matches.length) {
622
- result.recommended_tools = [...new Set([...result.recommended_tools, "list_local_applications", "open_local_application", "inspect_local_application", "operate_local_application"])];
623
- }
624
- result.browser_backend = this.policy.profile === "full" ? { tool: "browser_status", existing_profile: true, extension_bridge: true } : null;
625
- result.routing_observability = "Call server_info or project_overview to verify that bootstrap and task capability resolution reached the local runtime.";
626
- this.capabilityObserver.recordResolution(task, result);
627
- return result;
506
+ return resolveRuntimeTaskCapabilities({
507
+ agentContextManager: this.agentContextManager,
508
+ appAutomationManager: this.appAutomationManager,
509
+ capabilityObserver: this.capabilityObserver,
510
+ policy: this.policy,
511
+ }, args, context);
628
512
  }
629
513
 
630
514
  readLocalResourceBinary(name) {
631
515
  const registry = this.managedJobManager.currentResources();
632
- const resource = registry[name];
516
+ const resource = Object.hasOwn(registry, name) ? registry[name] : null;
633
517
  if (!resource) throw new Error(`unknown local resource: ${name}`);
634
518
  const inspected = inspectResourceFile(resource.path, { allowInsecurePermissions: resource.allowInsecurePermissions === true });
635
519
  if (inspected.size > 1024 * 1024) throw new Error("local resource exceeds 1 MiB browser injection limit");
@@ -749,25 +633,6 @@ export class LocalRuntime {
749
633
  }
750
634
  }
751
635
 
752
- function applicationMatchScore(task, application) {
753
- const name = String(application.name || "").toLowerCase();
754
- const id = String(application.id || "").toLowerCase();
755
- if (!name) return 0;
756
- if (task.includes(name)) return 10 + Math.min(name.length, 20);
757
- const words = name.split(/[^\p{L}\p{N}]+/u).filter((word) => word.length >= 2);
758
- return words.reduce((score, word) => score + (task.includes(word) ? 2 : 0), id && task.includes(id) ? 5 : 0);
759
- }
760
-
761
- function policyMatchesNamedProfile(policy) {
762
- const named = POLICY_PROFILES[policy.profile];
763
- if (!named) return false;
764
- return policy.allowWrite === named.allowWrite
765
- && policy.execMode === named.execMode
766
- && policy.unrestrictedPaths === named.unrestrictedPaths
767
- && policy.minimalEnv === named.minimalEnv
768
- && policy.exposeAbsolutePaths === named.exposeAbsolutePaths;
769
- }
770
-
771
636
  function stateRootFromProfileStatePath(statePath) {
772
637
  const absolute = resolve(statePath);
773
638
  if (basename(absolute) !== "state.json") throw new Error("local resource state path is invalid");
@@ -854,8 +719,6 @@ function normalizeRelayAuthorization(value) {
854
719
  return Object.freeze({ account_id: accountId, account_version: accountVersion, role });
855
720
  }
856
721
 
857
-
858
-
859
722
  function collectToolPathCandidates(error, toolArgs, workspace) {
860
723
  const candidates = new Set();
861
724
  for (const value of [error?.path, error?.dest]) if (typeof value === "string" && value) candidates.add(value);
@@ -16,5 +16,5 @@ export async function waitForInactiveStatus(
16
16
  }
17
17
 
18
18
  function delay(milliseconds) {
19
- return new Promise(resolve => setTimeout(resolve, milliseconds));
19
+ return new Promise(resolve => { setTimeout(resolve, milliseconds); });
20
20
  }
@@ -48,7 +48,7 @@ export async function uninstallAutostart({ stateRoot, logger = console } = {}) {
48
48
  return uninstallSystemd(logger);
49
49
  }
50
50
 
51
- export async function autostartStatus({ logger = console } = {}) {
51
+ export async function autostartStatus() {
52
52
  if (process.platform === "darwin") return statusLaunchd();
53
53
  if (process.platform === "win32") return statusWindowsTask({ run: serviceRun });
54
54
  return statusSystemd();
@@ -279,7 +279,7 @@ export async function acquireStartupLockWithWait(state, options = {}) {
279
279
  logger.info?.(`waiting for ${ownerPid} to finish the current startup/state operation`);
280
280
  const deadline = createMonotonicDeadline(timeoutMs);
281
281
  while (!deadline.expired()) {
282
- await new Promise((resolvePromise) => setTimeout(resolvePromise, Math.min(pollMs, Math.max(1, deadline.remainingMs()))));
282
+ await new Promise((resolvePromise) => { setTimeout(resolvePromise, Math.min(pollMs, Math.max(1, deadline.remainingMs()))); });
283
283
  lock = acquireStartupLock(state, metadata);
284
284
  if (lock.acquired) {
285
285
  logger.info?.("the previous startup/state operation finished; continuing");
@@ -220,7 +220,7 @@ function windowsServiceFailureReason(command, status) {
220
220
  async function waitForWindowsStatus(predicate, options) {
221
221
  const attempts = boundedPositiveInteger(options.statusAttempts, 10);
222
222
  const delayMs = boundedPositiveInteger(options.statusDelayMs, 100);
223
- const sleep = typeof options.sleep === "function" ? options.sleep : milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
223
+ const sleep = typeof options.sleep === "function" ? options.sleep : milliseconds => new Promise(resolve => { setTimeout(resolve, milliseconds); });
224
224
  let status = await statusWindowsTask(options);
225
225
  for (let index = 1; index < attempts && !predicate(status); index += 1) {
226
226
  await sleep(delayMs);
@@ -5,6 +5,7 @@ import { runWrangler } from "./shell.mjs";
5
5
  import { packageRoot, saveState } from "./state.mjs";
6
6
  import { withWorkerSecretsFile } from "./worker-secret-file.mjs";
7
7
  import {
8
+ normalizeWorkerOrigin,
8
9
  retryWorkerHealth,
9
10
  workerHealthRequiresRedeploy,
10
11
  workerHealthUserReason,
@@ -52,7 +53,7 @@ export async function ensureWorkerDeployment(state, args = {}, options = {}) {
52
53
  "--secrets-file", secretFile,
53
54
  ], { capture: true }));
54
55
 
55
- const detectedUrl = extractWorkerUrl(deploy.stdout) || extractWorkerUrl(deploy.stderr);
56
+ const detectedUrl = extractWorkerUrl(deploy.stdout, state.worker.name) || extractWorkerUrl(deploy.stderr, state.worker.name);
56
57
  const recordedUrl = workerUrlMatchesName(state.worker.url, state.worker.name) ? state.worker.url : "";
57
58
  const workerUrl = detectedUrl || recordedUrl;
58
59
  if (!workerUrl) {
@@ -93,20 +94,24 @@ export function workerDeploymentFingerprint(state, options = {}) {
93
94
  return fingerprint.digest("hex");
94
95
  }
95
96
 
96
- export function extractWorkerUrl(text = "") {
97
- const matches = [...String(text).matchAll(/https:\/\/[^\s"'<>]+\.workers\.dev[^\s"'<>]*/g)];
98
- if (matches.length) return matches.at(-1)[0].replace(/[),.]+$/, "");
99
- const anyHttps = [...String(text).matchAll(/https:\/\/[^\s"'<>]+/g)];
100
- return anyHttps.find(match => /workers\.dev|\/healthz|\/mcp/.test(match[0]))?.[0]?.replace(/[),.]+$/, "") || "";
97
+ export function extractWorkerUrl(text = "", workerName = "") {
98
+ const candidates = [...String(text).matchAll(/https:\/\/[^\s"'<>]+/g)]
99
+ .map((match) => match[0].replace(/[),.;:!?]+$/, ""));
100
+ for (const candidate of candidates.reverse()) {
101
+ try {
102
+ return normalizeWorkerOrigin(candidate, workerName);
103
+ } catch {
104
+ // Wrangler output may contain unrelated links; only a canonical matching workers.dev origin is deployment evidence.
105
+ }
106
+ }
107
+ return "";
101
108
  }
102
109
 
103
110
  export function workerUrlMatchesName(workerUrl, workerName) {
104
111
  if (!workerUrl || !workerName) return false;
105
112
  try {
106
- const url = new URL(String(workerUrl));
107
- return url.protocol === "https:"
108
- && url.hostname.endsWith(".workers.dev")
109
- && url.hostname.startsWith(`${String(workerName)}.`);
113
+ normalizeWorkerOrigin(workerUrl, workerName);
114
+ return true;
110
115
  } catch {
111
116
  return false;
112
117
  }
@@ -53,8 +53,8 @@ export async function retryWorkerHealth(workerUrl, expectedVersion, attempts, op
53
53
  return last;
54
54
  }
55
55
 
56
- export function workerHealthUrl(workerUrl, expectedWorkerName = "") {
57
- const base = new URL(`${String(workerUrl).replace(/\/+$/, "")}/`);
56
+ export function normalizeWorkerOrigin(workerUrl, expectedWorkerName = "") {
57
+ const base = new URL(String(workerUrl));
58
58
  const hostname = base.hostname.toLowerCase();
59
59
  const expectedName = String(expectedWorkerName || "").toLowerCase();
60
60
  if (base.protocol !== "https:" || base.port || base.username || base.password || base.search || base.hash || base.pathname !== "/") {
@@ -64,7 +64,11 @@ export function workerHealthUrl(workerUrl, expectedWorkerName = "") {
64
64
  if (expectedName && (!WORKER_NAME.test(expectedName) || hostname.split(".")[0] !== expectedName)) {
65
65
  throw new Error("Worker URL hostname does not match the recorded Worker name");
66
66
  }
67
- return `https://${hostname}/healthz`;
67
+ return `https://${hostname}`;
68
+ }
69
+
70
+ export function workerHealthUrl(workerUrl, expectedWorkerName = "") {
71
+ return `${normalizeWorkerOrigin(workerUrl, expectedWorkerName)}/healthz`;
68
72
  }
69
73
 
70
74
  export function isRetryableWorkerHealthError(value) {
@@ -242,5 +246,5 @@ function boundedTimeout(value) {
242
246
  }
243
247
 
244
248
  function sleep(ms) {
245
- return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
249
+ return new Promise((resolvePromise) => { setTimeout(resolvePromise, ms); });
246
250
  }
@@ -1,7 +1,7 @@
1
- import accessContract from "../shared/access-contract.json";
2
- import policyContract from "../shared/policy-contract.json";
3
- import toolCatalog from "../shared/tool-catalog.json";
4
- import { policyAllowsAvailability, type DaemonPolicy } from "./policy";
1
+ import accessContract from "../shared/access-contract.json" with { type: "json" };
2
+ import policyContract from "../shared/policy-contract.json" with { type: "json" };
3
+ import toolCatalog from "../shared/tool-catalog.json" with { type: "json" };
4
+ import { policyAllowsAvailability, type DaemonPolicy } from "./policy.ts";
5
5
 
6
6
  export type AccountRole = keyof typeof accessContract.roles;
7
7
 
@@ -11,16 +11,17 @@ const toolAvailability = new Map((toolCatalog as Array<{ name: string; availabil
11
11
 
12
12
  export const ACCOUNT_ACCESS_REVISION = Number(accessContract.revision);
13
13
  export const ACCOUNT_ROLES = Object.freeze(Object.keys(roles));
14
+ export const DEFAULT_ACCOUNT_ROLE = String(accessContract.defaultRole) as AccountRole;
14
15
  export const OWNER_ACCOUNT_ROLE = String(accessContract.ownerRole) as AccountRole;
15
16
 
16
17
  export function normalizeAccountRole(value: unknown): AccountRole | null {
17
18
  const role = String(value ?? "").trim().toLowerCase();
18
- return role in roles ? role as AccountRole : null;
19
+ return Object.hasOwn(roles, role) ? role as AccountRole : null;
19
20
  }
20
21
 
21
22
  export function accountRolePolicy(role: AccountRole): DaemonPolicy {
22
- const profileName = roles[role]?.profile;
23
- const profile = profiles[profileName];
23
+ const profileName = Object.hasOwn(roles, role) ? roles[role].profile : undefined;
24
+ const profile = profileName && Object.hasOwn(profiles, profileName) ? profiles[profileName] : undefined;
24
25
  if (!profile) throw new Error(`account role references an unknown policy profile: ${role}`);
25
26
  return {
26
27
  profile: String(profile.profile),
@@ -1,8 +1,8 @@
1
- import { json, methodNotAllowed, parseRequestBody } from "./http";
1
+ import { json, methodNotAllowed, parseRequestBody } from "./http.ts";
2
2
  import {
3
3
  accountByName, createAccount, publicAccount, replaceAccountPassword, revokeAccountCredentials,
4
4
  safeEqual, updateAccount, type AccountRecord, type OAuthStore,
5
- } from "./oauth-state";
5
+ } from "./oauth-state.ts";
6
6
 
7
7
  const BODY_LIMIT_BYTES = 64 * 1024;
8
8
  const MAX_ACCOUNTS = 64;
@@ -1,6 +1,6 @@
1
- import policyContract from "../shared/policy-contract.json";
2
- import { accountRolePolicy, accountRoleToolNames, type AccountRole } from "./access";
3
- import type { DaemonPolicy } from "./policy";
1
+ import policyContract from "../shared/policy-contract.json" with { type: "json" };
2
+ import { accountRolePolicy, accountRoleToolNames, type AccountRole } from "./access.ts";
3
+ import type { DaemonPolicy } from "./policy.ts";
4
4
 
5
5
  type EffectivePolicy = {
6
6
  scope: "authenticated_account_effective_authority";
@@ -218,9 +218,9 @@ export function searchParamsEntries(params: URLSearchParams): Array<[string, str
218
218
  }
219
219
 
220
220
  export function searchParamsObject(params: URLSearchParams): Record<string, unknown> {
221
- const out: Record<string, unknown> = {};
221
+ const out = Object.create(null) as Record<string, unknown>;
222
222
  params.forEach((value, key) => {
223
- if (out[key] === undefined) out[key] = value;
223
+ if (!Object.hasOwn(out, key)) out[key] = value;
224
224
  else if (Array.isArray(out[key])) (out[key] as string[]).push(value);
225
225
  else out[key] = [out[key] as string, value];
226
226
  });