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.
- package/CHANGELOG.md +20 -0
- package/CONTRIBUTING.md +16 -5
- package/README.md +115 -416
- package/SECURITY.md +2 -0
- package/browser-extension/manifest.json +2 -2
- package/docs/ARCHITECTURE.md +19 -9
- package/docs/AUDIT.md +20 -0
- package/docs/ENGINEERING.md +2 -2
- package/docs/LOGGING.md +1 -1
- package/docs/MULTI_ACCOUNT.md +2 -2
- package/docs/OPERATIONS.md +2 -2
- package/docs/OVERVIEW.md +113 -0
- package/docs/PROJECT_STANDARDS.md +4 -4
- package/docs/RELEASING.md +25 -12
- package/docs/TESTING.md +12 -8
- package/docs/THREAT_MODEL.md +142 -0
- package/package.json +10 -3
- package/scripts/check-plan.mjs +91 -0
- package/scripts/coverage-check.mjs +22 -0
- package/scripts/github-push.mjs +1 -1
- package/scripts/github-release.mjs +1 -1
- package/scripts/local-release-acceptance.mjs +56 -6
- package/scripts/release-acceptance.mjs +15 -3
- package/scripts/release-state.mjs +1 -1
- package/scripts/run-checks.mjs +29 -0
- package/scripts/start-release-candidate.mjs +113 -0
- package/src/local/agent-context-projection.mjs +158 -0
- package/src/local/agent-context.mjs +23 -332
- package/src/local/agent-skill-discovery.mjs +230 -0
- package/src/local/agent-text-file.mjs +41 -0
- package/src/local/browser-bridge-http.mjs +48 -0
- package/src/local/browser-bridge.mjs +48 -222
- package/src/local/browser-broker-routes.mjs +136 -0
- package/src/local/browser-broker-server.mjs +59 -0
- package/src/local/browser-request-registry.mjs +67 -0
- package/src/local/managed-job-lock.mjs +99 -0
- package/src/local/managed-job-projection.mjs +68 -0
- package/src/local/managed-job-runner.mjs +73 -0
- package/src/local/managed-job-storage.mjs +93 -0
- package/src/local/managed-jobs.mjs +12 -297
- package/src/local/relay-call-recovery.mjs +148 -0
- package/src/local/relay-connection.mjs +5 -0
- package/src/local/runtime-paths.mjs +107 -0
- package/src/local/runtime-relay.mjs +89 -0
- package/src/local/runtime-tool-handlers.mjs +66 -0
- package/src/local/runtime.mjs +80 -242
- package/src/local/windows-launcher.mjs +57 -0
- package/src/local/windows-service.mjs +7 -56
- package/src/worker/daemon-sockets.ts +10 -1
- package/src/worker/index.ts +51 -124
- package/src/worker/mcp-jsonrpc.ts +94 -0
- package/src/worker/oauth-authorization-page.ts +70 -0
- package/src/worker/oauth-controller.ts +9 -58
- package/src/worker/pending-call-contract.ts +26 -0
- package/src/worker/pending-calls.ts +41 -25
- package/src/worker/websocket-protocol.ts +24 -0
- 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
|
-
|
|
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
|
+
}
|
package/tsconfig.local.json
CHANGED
|
@@ -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
|
}
|