machine-bridge-mcp 1.1.4 → 1.2.0

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 +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 +30 -2
  6. package/SECURITY.md +1 -1
  7. package/SUPPORT.md +31 -0
  8. package/browser-extension/browser-operations.js +1 -1
  9. package/browser-extension/manifest.json +2 -2
  10. package/browser-extension/page-automation.js +1 -1
  11. package/docs/ARCHITECTURE.md +17 -18
  12. package/docs/AUDIT.md +28 -0
  13. package/docs/ENGINEERING.md +5 -2
  14. package/docs/LOGGING.md +2 -0
  15. package/docs/OPERATIONS.md +7 -3
  16. package/docs/PROJECT_STANDARDS.md +3 -2
  17. package/docs/TESTING.md +7 -4
  18. package/docs/UPGRADING.md +30 -0
  19. package/package.json +15 -7
  20. package/scripts/coverage-check.mjs +24 -9
  21. package/src/local/agent-context.mjs +7 -148
  22. package/src/local/agent-contract.mjs +206 -0
  23. package/src/local/browser-bridge.mjs +30 -281
  24. package/src/local/browser-extension-protocol.mjs +19 -4
  25. package/src/local/browser-operation-service.mjs +325 -0
  26. package/src/local/call-registry.mjs +44 -1
  27. package/src/local/capability-ranking.mjs +13 -0
  28. package/src/local/cli-account-admin.mjs +1 -1
  29. package/src/local/cli.mjs +16 -7
  30. package/src/local/daemon-process.mjs +1 -1
  31. package/src/local/full-access-test.mjs +1 -1
  32. package/src/local/job-runner.mjs +9 -3
  33. package/src/local/monotonic-deadline.mjs +7 -0
  34. package/src/local/numbers.mjs +8 -0
  35. package/src/local/policy.mjs +88 -12
  36. package/src/local/project-metadata.mjs +7 -1
  37. package/src/local/records.mjs +6 -0
  38. package/src/local/runtime-capabilities.mjs +66 -0
  39. package/src/local/runtime-diagnostics.mjs +91 -0
  40. package/src/local/runtime-reporting.mjs +113 -0
  41. package/src/local/runtime.mjs +53 -190
  42. package/src/local/service-convergence.mjs +1 -1
  43. package/src/local/service-environment.mjs +129 -0
  44. package/src/local/service.mjs +22 -53
  45. package/src/local/state.mjs +2 -2
  46. package/src/local/windows-service.mjs +248 -0
  47. package/src/local/worker-health.mjs +1 -1
  48. package/src/worker/access.ts +4 -4
  49. package/src/worker/account-admin.ts +2 -2
  50. package/src/worker/authority.ts +3 -3
  51. package/src/worker/index.ts +29 -337
  52. package/src/worker/oauth-controller.ts +334 -0
  53. package/src/worker/oauth-state.ts +1 -1
  54. package/src/worker/oauth-tokens.ts +2 -2
  55. package/src/worker/tool-catalog.ts +1 -1
  56. package/tsconfig.json +2 -1
  57. package/tsconfig.local.json +27 -0
@@ -0,0 +1,248 @@
1
+ import path from "node:path";
2
+ import { replaceFileAtomicallySync } from "./exclusive-file.mjs";
3
+ import { waitForInactiveStatus } from "./service-convergence.mjs";
4
+
5
+ export const WINDOWS_TASK = "MachineBridgeMCP";
6
+ const WINDOWS_LAUNCHER = "service-launcher.cmd";
7
+ const WINDOWS_TASK_COMMAND_MAX_CHARS = 262;
8
+ const WINDOWS_STATUS_SCRIPT = [
9
+ "$task = Get-ScheduledTask -TaskName 'MachineBridgeMCP' -ErrorAction SilentlyContinue;",
10
+ "if ($null -eq $task) { exit 3 };",
11
+ "$info = Get-ScheduledTaskInfo -TaskName 'MachineBridgeMCP' -ErrorAction Stop;",
12
+ "$payload = [ordered]@{ state = $task.State.ToString(); last_result = [Int64]$info.LastTaskResult; last_run_time = $info.LastRunTime.ToUniversalTime().ToString('o') };",
13
+ "[Console]::Out.Write(($payload | ConvertTo-Json -Compress))",
14
+ ].join(" ");
15
+
16
+ export async function installWindowsTask(spec, logger = console, options = {}) {
17
+ const run = requiredRun(options.run);
18
+ const taskAction = windowsTaskAction(windowsLauncherPath(spec.stateRoot));
19
+ const launcher = writeWindowsLauncher(spec);
20
+ const create = await run("schtasks", [
21
+ "/Create",
22
+ "/TN", WINDOWS_TASK,
23
+ "/SC", "ONLOGON",
24
+ "/TR", taskAction,
25
+ "/RL", "LIMITED",
26
+ "/F",
27
+ ]);
28
+ const status = await waitForWindowsStatus(value => value.installed === true, { ...options, run });
29
+ const ok = create?.code === 0 && status.installed === true;
30
+ if (ok) logger.info?.("Windows Scheduled Task installed for user logon");
31
+ else logger.warn?.("Windows Scheduled Task installation failed", { reason: windowsServiceFailureReason(create, status) });
32
+ return {
33
+ ok,
34
+ provider: "schtasks",
35
+ task: WINDOWS_TASK,
36
+ trigger: "user_logon",
37
+ launcher: launcher.path,
38
+ launcher_restarts_on_failure: true,
39
+ create,
40
+ status,
41
+ reason: ok ? "installed" : windowsServiceFailureReason(create, status),
42
+ };
43
+ }
44
+
45
+ export async function startWindowsTask(logger = console, options = {}) {
46
+ const run = requiredRun(options.run);
47
+ const before = await statusWindowsTask({ ...options, run });
48
+ if (before.installed === null) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "task_status_unavailable", status: before };
49
+ if (before.installed === false) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "not_installed", status: before };
50
+ const command = await run("schtasks", ["/Run", "/TN", WINDOWS_TASK]);
51
+ const after = await waitForWindowsStatus(
52
+ status => status.active === true || completedSince(before, status),
53
+ { ...options, run },
54
+ );
55
+ const completed = completedSince(before, after);
56
+ const ok = after.active === true || completed;
57
+ if (ok) logger.info?.("Windows Scheduled Task started");
58
+ else logger.warn?.("Windows Scheduled Task did not reach a running or successful completed state");
59
+ return {
60
+ ok,
61
+ provider: "schtasks",
62
+ task: WINDOWS_TASK,
63
+ command,
64
+ status: after,
65
+ reason: after.active === true ? "started" : completed ? "completed" : "start_not_observed",
66
+ };
67
+ }
68
+
69
+ export async function stopWindowsTask(logger = console, options = {}) {
70
+ const run = requiredRun(options.run);
71
+ const before = await statusWindowsTask({ ...options, run });
72
+ if (before.installed === null) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "task_status_unavailable", status: before };
73
+ if (before.installed === false || before.active === false) {
74
+ return {
75
+ ok: true,
76
+ provider: "schtasks",
77
+ task: WINDOWS_TASK,
78
+ installed: before.installed,
79
+ active_before: false,
80
+ active: false,
81
+ already_stopped: true,
82
+ reason: before.installed ? "already_stopped" : "not_installed",
83
+ status: before,
84
+ };
85
+ }
86
+ const command = await run("schtasks", ["/End", "/TN", WINDOWS_TASK]);
87
+ const after = await waitForInactiveStatus(
88
+ () => statusWindowsTask({ ...options, run }),
89
+ windowsStatusWaitOptions(options),
90
+ );
91
+ const ok = after?.active === false;
92
+ if (ok) logger.info?.("Windows Scheduled Task stopped");
93
+ else logger.warn?.("Windows Scheduled Task is still active after the stop request");
94
+ return {
95
+ ok,
96
+ provider: "schtasks",
97
+ task: WINDOWS_TASK,
98
+ installed: after?.installed !== false,
99
+ active_before: true,
100
+ active: after?.active !== false,
101
+ already_stopped: false,
102
+ command,
103
+ status: after,
104
+ reason: ok ? "stopped" : "stop_not_observed",
105
+ };
106
+ }
107
+
108
+ export async function uninstallWindowsTask(logger = console, options = {}) {
109
+ const run = requiredRun(options.run);
110
+ const stopped = await stopWindowsTask(logger, { ...options, run });
111
+ if (!stopped.ok) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, reason: "stop_failed" };
112
+ const command = await run("schtasks", ["/Delete", "/TN", WINDOWS_TASK, "/F"]);
113
+ const status = await waitForWindowsStatus(
114
+ value => value.installed === false,
115
+ { ...options, run },
116
+ );
117
+ const ok = status.installed === false;
118
+ if (ok) logger.info?.("Windows Scheduled Task removed");
119
+ else logger.warn?.("Windows Scheduled Task removal could not be verified");
120
+ return { ok, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, command, status, reason: ok ? "removed" : "removal_not_observed" };
121
+ }
122
+
123
+ export async function statusWindowsTask(options = {}) {
124
+ const run = requiredRun(options.run);
125
+ const powershell = String(options.powershell || "powershell.exe");
126
+ const result = await run(powershell, ["-NoProfile", "-NonInteractive", "-Command", WINDOWS_STATUS_SCRIPT]);
127
+ if (result?.code === 3) {
128
+ return { ok: false, provider: "schtasks", task: WINDOWS_TASK, installed: false, active: false, state: "missing" };
129
+ }
130
+ if (result?.code !== 0) {
131
+ return { ok: false, provider: "schtasks", task: WINDOWS_TASK, installed: null, active: null, state: "unknown", query: result };
132
+ }
133
+ let payload;
134
+ try { payload = JSON.parse(String(result.stdout || "")); } catch {
135
+ return { ok: false, provider: "schtasks", task: WINDOWS_TASK, installed: null, active: null, state: "unknown", query: result };
136
+ }
137
+ const state = String(payload?.state || "").trim().toLowerCase();
138
+ const lastResult = Number(payload?.last_result);
139
+ const lastRunTime = typeof payload?.last_run_time === "string" ? payload.last_run_time : null;
140
+ const active = state === "running";
141
+ return {
142
+ ok: true,
143
+ provider: "schtasks",
144
+ task: WINDOWS_TASK,
145
+ installed: true,
146
+ active,
147
+ state: state || "unknown",
148
+ last_result: Number.isFinite(lastResult) ? lastResult : null,
149
+ last_run_time: lastRunTime,
150
+ };
151
+ }
152
+
153
+ export function windowsLauncherPath(stateRoot) {
154
+ return path.join(path.resolve(String(stateRoot)), WINDOWS_LAUNCHER);
155
+ }
156
+
157
+ export function writeWindowsLauncher(spec) {
158
+ const launcherPath = windowsLauncherPath(spec.stateRoot);
159
+ const content = windowsLauncherContent(spec);
160
+ replaceFileAtomicallySync(launcherPath, content, { mode: 0o600 });
161
+ return { path: launcherPath, content };
162
+ }
163
+
164
+ export function windowsLauncherContent(spec) {
165
+ const command = [spec.node, ...(spec.daemonArgs || [])].map(windowsBatchArgument).join(" ");
166
+ const stdout = windowsBatchArgument(spec.stdout);
167
+ const stderr = windowsBatchArgument(spec.stderr);
168
+ return [
169
+ "@echo off",
170
+ "setlocal DisableDelayedExpansion",
171
+ ":restart",
172
+ `${command} 1>>${stdout} 2>>${stderr}`,
173
+ 'set "mbm_exit=%ERRORLEVEL%"',
174
+ 'if "%mbm_exit%"=="0" exit /b 0',
175
+ '"%SystemRoot%\\System32\\timeout.exe" /t 5 /nobreak >nul 2>&1',
176
+ "goto restart",
177
+ "",
178
+ ].join("\r\n");
179
+ }
180
+
181
+ export function windowsTaskAction(launcherPath) {
182
+ const action = String(launcherPath);
183
+ if (action.includes("\0") || /[\r\n]/.test(action)) throw new Error("Windows autostart launcher path contains a prohibited control character");
184
+ if (!path.isAbsolute(action) && !path.win32.isAbsolute(action)) throw new Error("Windows autostart launcher path must be absolute");
185
+ if (action.includes("%")) throw new Error("Windows autostart launcher path must not contain a percent sign because Task Scheduler may expand it as an environment variable");
186
+ if (action.length > WINDOWS_TASK_COMMAND_MAX_CHARS) {
187
+ throw new Error(`Windows autostart action exceeds the ${WINDOWS_TASK_COMMAND_MAX_CHARS}-character Task Scheduler limit; use the default state directory or a shorter --state-dir`);
188
+ }
189
+ return action;
190
+ }
191
+
192
+ export function windowsCommandLineArgument(value) {
193
+ const text = String(value);
194
+ if (text.includes("\0")) throw new Error("Windows command-line argument contains a NUL byte");
195
+ if (/[\r\n]/.test(text)) throw new Error("Windows command-line argument contains a line break");
196
+ const escapedQuotes = text.replace(/(\\*)"/g, (_match, slashes) => `${slashes}${slashes}\\"`);
197
+ const escapedTrailingSlashes = escapedQuotes.replace(/(\\+)$/, slashes => `${slashes}${slashes}`);
198
+ return `"${escapedTrailingSlashes}"`;
199
+ }
200
+
201
+ export function windowsBatchArgument(value) {
202
+ return windowsCommandLineArgument(value).replaceAll("%", "%%");
203
+ }
204
+
205
+ function completedSince(before, after) {
206
+ return after?.installed === true
207
+ && after.active === false
208
+ && after.last_result === 0
209
+ && Boolean(after.last_run_time)
210
+ && after.last_run_time !== before?.last_run_time;
211
+ }
212
+
213
+ function windowsServiceFailureReason(command, status) {
214
+ if (command?.code !== 0) return "task_create_failed";
215
+ if (status?.installed === false) return "task_not_found_after_create";
216
+ if (status?.installed === null) return "task_status_unavailable";
217
+ return "task_installation_unverified";
218
+ }
219
+
220
+ async function waitForWindowsStatus(predicate, options) {
221
+ const attempts = boundedPositiveInteger(options.statusAttempts, 10);
222
+ const delayMs = boundedPositiveInteger(options.statusDelayMs, 100);
223
+ const sleep = typeof options.sleep === "function" ? options.sleep : milliseconds => new Promise(resolve => { setTimeout(resolve, milliseconds); });
224
+ let status = await statusWindowsTask(options);
225
+ for (let index = 1; index < attempts && !predicate(status); index += 1) {
226
+ await sleep(delayMs);
227
+ status = await statusWindowsTask(options);
228
+ }
229
+ return status;
230
+ }
231
+
232
+ function windowsStatusWaitOptions(options) {
233
+ return {
234
+ attempts: boundedPositiveInteger(options.statusAttempts, 10),
235
+ delayMs: boundedPositiveInteger(options.statusDelayMs, 100),
236
+ sleep: typeof options.sleep === "function" ? options.sleep : undefined,
237
+ };
238
+ }
239
+
240
+ function requiredRun(run) {
241
+ if (typeof run !== "function") throw new Error("Windows service command runner is required");
242
+ return run;
243
+ }
244
+
245
+ function boundedPositiveInteger(value, fallback) {
246
+ const number = Number(value);
247
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
248
+ }
@@ -242,5 +242,5 @@ function boundedTimeout(value) {
242
242
  }
243
243
 
244
244
  function sleep(ms) {
245
- return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
245
+ return new Promise((resolvePromise) => { setTimeout(resolvePromise, ms); });
246
246
  }
@@ -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
 
@@ -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";