machine-bridge-mcp 1.2.8 → 1.2.10

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 (57) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/CONTRIBUTING.md +16 -5
  3. package/README.md +115 -416
  4. package/SECURITY.md +2 -0
  5. package/browser-extension/manifest.json +2 -2
  6. package/docs/ARCHITECTURE.md +19 -9
  7. package/docs/AUDIT.md +20 -0
  8. package/docs/ENGINEERING.md +2 -2
  9. package/docs/LOGGING.md +1 -1
  10. package/docs/MULTI_ACCOUNT.md +2 -2
  11. package/docs/OPERATIONS.md +2 -2
  12. package/docs/OVERVIEW.md +113 -0
  13. package/docs/PROJECT_STANDARDS.md +4 -4
  14. package/docs/RELEASING.md +25 -12
  15. package/docs/TESTING.md +12 -8
  16. package/docs/THREAT_MODEL.md +142 -0
  17. package/package.json +10 -3
  18. package/scripts/check-plan.mjs +91 -0
  19. package/scripts/coverage-check.mjs +22 -0
  20. package/scripts/github-push.mjs +1 -1
  21. package/scripts/github-release.mjs +1 -1
  22. package/scripts/local-release-acceptance.mjs +56 -6
  23. package/scripts/release-acceptance.mjs +15 -3
  24. package/scripts/release-state.mjs +1 -1
  25. package/scripts/run-checks.mjs +29 -0
  26. package/scripts/start-release-candidate.mjs +113 -0
  27. package/src/local/agent-context-projection.mjs +158 -0
  28. package/src/local/agent-context.mjs +23 -332
  29. package/src/local/agent-skill-discovery.mjs +230 -0
  30. package/src/local/agent-text-file.mjs +41 -0
  31. package/src/local/browser-bridge-http.mjs +48 -0
  32. package/src/local/browser-bridge.mjs +48 -222
  33. package/src/local/browser-broker-routes.mjs +136 -0
  34. package/src/local/browser-broker-server.mjs +59 -0
  35. package/src/local/browser-request-registry.mjs +67 -0
  36. package/src/local/managed-job-lock.mjs +99 -0
  37. package/src/local/managed-job-projection.mjs +68 -0
  38. package/src/local/managed-job-runner.mjs +73 -0
  39. package/src/local/managed-job-storage.mjs +93 -0
  40. package/src/local/managed-jobs.mjs +12 -297
  41. package/src/local/relay-call-recovery.mjs +148 -0
  42. package/src/local/relay-connection.mjs +5 -0
  43. package/src/local/runtime-paths.mjs +107 -0
  44. package/src/local/runtime-relay.mjs +89 -0
  45. package/src/local/runtime-tool-handlers.mjs +66 -0
  46. package/src/local/runtime.mjs +80 -242
  47. package/src/local/windows-launcher.mjs +57 -0
  48. package/src/local/windows-service.mjs +7 -56
  49. package/src/worker/daemon-sockets.ts +10 -1
  50. package/src/worker/index.ts +51 -124
  51. package/src/worker/mcp-jsonrpc.ts +94 -0
  52. package/src/worker/oauth-authorization-page.ts +70 -0
  53. package/src/worker/oauth-controller.ts +9 -58
  54. package/src/worker/pending-call-contract.ts +26 -0
  55. package/src/worker/pending-calls.ts +41 -25
  56. package/src/worker/websocket-protocol.ts +24 -0
  57. package/tsconfig.local.json +7 -1
@@ -1,3 +1,5 @@
1
+ import type { PendingCallRecord, RegisterPendingCall } from "./pending-call-contract.ts";
2
+
1
3
  export class PendingCallRegistrationError extends Error {
2
4
  readonly code: "conflict" | "limit_exceeded";
3
5
  readonly retryable: boolean;
@@ -10,30 +12,6 @@ export class PendingCallRegistrationError extends Error {
10
12
  }
11
13
  }
12
14
 
13
- export interface PendingCallRecord {
14
- id: string;
15
- socket: WebSocket;
16
- clientRequestKey?: string;
17
- tool: string;
18
- startedAt: number;
19
- timeout: ReturnType<typeof setTimeout>;
20
- resolve: (value: unknown) => void;
21
- reject: (error: Error) => void;
22
- signal?: AbortSignal;
23
- abortHandler?: () => void;
24
- }
25
-
26
- interface RegisterPendingCall {
27
- id: string;
28
- socket: WebSocket;
29
- clientRequestKey?: string;
30
- tool: string;
31
- timeoutMs: number;
32
- onTimeout: (record: PendingCallRecord) => Error;
33
- signal?: AbortSignal;
34
- onAbort?: (record: PendingCallRecord) => Error;
35
- }
36
-
37
15
  export class PendingCallRegistry {
38
16
  private readonly maximum: number;
39
17
  private readonly byId = new Map<string, PendingCallRecord>();
@@ -78,6 +56,7 @@ export class PendingCallRegistry {
78
56
  const record: PendingCallRecord = {
79
57
  id: input.id,
80
58
  socket: input.socket,
59
+ daemonInstanceId: input.daemonInstanceId,
81
60
  clientRequestKey: input.clientRequestKey,
82
61
  tool: String(input.tool || "unknown"),
83
62
  startedAt,
@@ -126,16 +105,52 @@ export class PendingCallRegistry {
126
105
  return ids.length;
127
106
  }
128
107
 
129
- snapshot(): { active: number; request_keys: number; maximum: number; oldest_ms: number; by_tool: Record<string, number> } {
108
+ detachSocket(
109
+ socket: WebSocket,
110
+ graceMs: number,
111
+ createError: (record: PendingCallRecord) => Error,
112
+ ): number {
113
+ const records = [...this.byId.values()].filter((record) => record.socket === socket);
114
+ const delay = Math.max(1, Math.floor(Number(graceMs) || 1));
115
+ for (const record of records) {
116
+ record.socket = undefined;
117
+ if (record.reconnectTimeout) clearTimeout(record.reconnectTimeout);
118
+ record.reconnectTimeout = setTimeout(() => {
119
+ const current = this.byId.get(record.id);
120
+ if (!current || current.socket) return;
121
+ const expired = this.take(record.id);
122
+ if (expired) expired.reject(createError(expired));
123
+ }, delay);
124
+ }
125
+ return records.length;
126
+ }
127
+
128
+ rebindInstance(daemonInstanceId: string, socket: WebSocket): string[] {
129
+ if (!daemonInstanceId) return [];
130
+ const rebound: string[] = [];
131
+ for (const record of this.byId.values()) {
132
+ if (record.socket || record.daemonInstanceId !== daemonInstanceId) continue;
133
+ if (record.reconnectTimeout) clearTimeout(record.reconnectTimeout);
134
+ record.reconnectTimeout = undefined;
135
+ record.socket = socket;
136
+ rebound.push(record.id);
137
+ }
138
+ return rebound;
139
+ }
140
+
141
+ snapshot(): { active: number; detached: number; request_keys: number; maximum: number; oldest_ms: number; by_tool: Record<string, number> } {
130
142
  const now = performance.now();
131
143
  const byTool: Record<string, number> = {};
144
+ let detached = 0;
132
145
  let oldestMs = 0;
133
146
  for (const record of this.byId.values()) {
134
147
  byTool[record.tool] = (byTool[record.tool] || 0) + 1;
148
+ if (!record.socket) detached += 1;
135
149
  oldestMs = Math.max(oldestMs, now - record.startedAt);
136
150
  }
137
151
  return {
138
152
  active: this.byId.size,
153
+ detached,
139
154
  request_keys: this.byRequestKey.size,
140
155
  maximum: this.maximum,
141
156
  oldest_ms: oldestMs,
@@ -147,6 +162,7 @@ export class PendingCallRegistry {
147
162
  const record = this.byId.get(id);
148
163
  if (!record) return undefined;
149
164
  clearTimeout(record.timeout);
165
+ if (record.reconnectTimeout) clearTimeout(record.reconnectTimeout);
150
166
  if (record.signal && record.abortHandler) record.signal.removeEventListener("abort", record.abortHandler);
151
167
  this.byId.delete(id);
152
168
  if (record.clientRequestKey && this.byRequestKey.get(record.clientRequestKey) === id) {
@@ -0,0 +1,24 @@
1
+ export function isObjectRecord(value: unknown): value is Record<string, unknown> {
2
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+
5
+ export function rejectDaemonMessage(ws: WebSocket, error: string, closeCode: number, closeReason: string): void {
6
+ sendWebSocketQuietly(ws, { type: "error", error });
7
+ closeWebSocketQuietly(ws, closeCode, closeReason);
8
+ }
9
+
10
+ export function sendWebSocketQuietly(ws: WebSocket, value: unknown): void {
11
+ try {
12
+ ws.send(typeof value === "string" ? value : JSON.stringify(value));
13
+ } catch {
14
+ // Best-effort protocol cleanup must not replace the primary timeout or rejection.
15
+ }
16
+ }
17
+
18
+ export function closeWebSocketQuietly(ws: WebSocket, code?: number, reason?: string): void {
19
+ try {
20
+ ws.close(code, reason);
21
+ } catch {
22
+ // The socket may already be closed or detached; no recovery remains at this boundary.
23
+ }
24
+ }
@@ -22,6 +22,12 @@
22
22
  "src/local/agent-contract.mjs",
23
23
  "src/local/browser-extension-protocol.mjs",
24
24
  "src/local/call-registry.mjs",
25
- "src/local/policy.mjs"
25
+ "src/local/policy.mjs",
26
+ "src/local/runtime-paths.mjs",
27
+ "src/local/relay-call-recovery.mjs",
28
+ "src/local/agent-context-projection.mjs",
29
+ "src/local/agent-skill-discovery.mjs",
30
+ "src/local/agent-text-file.mjs",
31
+ "src/local/managed-job-projection.mjs"
26
32
  ]
27
33
  }