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
@@ -11,7 +11,7 @@ const VALUE_OPTIONS = new Set([
11
11
 
12
12
  const LOG_FORMATS = new Set(["text", "json"]);
13
13
 
14
- const COMMAND_OPTIONS = {
14
+ const COMMAND_OPTIONS = new Map(Object.entries({
15
15
  start: new Set([
16
16
  "workspace", "stateDir", "workerName", "quiet", "json", "verbose", "logLevel", "logFormat", "rotateSecrets", "forceWorker",
17
17
  "daemonOnly", "noAutostart",
@@ -31,17 +31,17 @@ const COMMAND_OPTIONS = {
31
31
  browser: new Set(["workspace", "stateDir", "json"]),
32
32
  job: new Set(["workspace", "stateDir", "json", "yes"]),
33
33
  uninstall: new Set(["stateDir", "keepWorker", "yes"]),
34
- };
34
+ }));
35
35
 
36
36
  const SINGLE_WORKSPACE_POSITIONAL_COMMANDS = new Set(["start", "stdio", "status", "doctor", "full-test", "rotate-secrets"]);
37
- const STATIC_POSITIONAL_RULES = Object.freeze({
38
- "client-config": Object.freeze({ max: 1, tooMany: "client-config accepts at most one positional client name" }),
39
- uninstall: Object.freeze({ max: 0, tooMany: "uninstall does not accept positional arguments" }),
40
- });
41
- const RESOURCE_POSITIONAL_LIMITS = Object.freeze({ add: 3, "generate-ssh-key": 3, remove: 2, check: 2 });
42
- const JOB_POSITIONAL_LIMITS = Object.freeze({ read: 2, inspect: 2, cancel: 2, approve: 2, submit: 2 });
43
- const ACCOUNT_POSITIONAL_LIMITS = Object.freeze({ list: 1, add: 3, role: 3, enable: 2, disable: 2, "rotate-password": 2, remove: 2 });
44
- const ACTION_POSITIONAL_RULES = Object.freeze({
37
+ const STATIC_POSITIONAL_RULES = new Map([
38
+ ["client-config", Object.freeze({ max: 1, tooMany: "client-config accepts at most one positional client name" })],
39
+ ["uninstall", Object.freeze({ max: 0, tooMany: "uninstall does not accept positional arguments" })],
40
+ ]);
41
+ const RESOURCE_POSITIONAL_LIMITS = new Map(Object.entries({ add: 3, "generate-ssh-key": 3, remove: 2, check: 2 }));
42
+ const JOB_POSITIONAL_LIMITS = new Map(Object.entries({ read: 2, inspect: 2, cancel: 2, approve: 2, submit: 2 }));
43
+ const ACCOUNT_POSITIONAL_LIMITS = new Map(Object.entries({ list: 1, add: 3, role: 3, enable: 2, disable: 2, "rotate-password": 2, remove: 2 }));
44
+ const ACTION_POSITIONAL_RULES = new Map(Object.entries({
45
45
  workspace(args) {
46
46
  const action = String(args._[0] || "show");
47
47
  return { max: action === "set" || action === "select" ? 2 : 1, tooMany: `workspace ${action} received too many positional arguments`, workspaceConflictAfter: 1 };
@@ -50,14 +50,14 @@ const ACTION_POSITIONAL_RULES = Object.freeze({
50
50
  const action = String(args._[0] || "status");
51
51
  return { max: ["install", "status", "stop", "uninstall", "remove"].includes(action) ? 2 : 1, tooMany: `service ${action} received too many positional arguments`, workspaceConflictAfter: 1 };
52
52
  },
53
- autostart(args) { return ACTION_POSITIONAL_RULES.service(args); },
53
+ autostart(args) { return ACTION_POSITIONAL_RULES.get("service")(args); },
54
54
  resource(args) {
55
55
  const action = String(args._[0] || "list");
56
- return { max: RESOURCE_POSITIONAL_LIMITS[action] ?? 1, tooMany: `resource ${action} received too many positional arguments` };
56
+ return { max: RESOURCE_POSITIONAL_LIMITS.get(action) ?? 1, tooMany: `resource ${action} received too many positional arguments` };
57
57
  },
58
58
  account(args) {
59
59
  const action = String(args._[0] || "list");
60
- return { max: ACCOUNT_POSITIONAL_LIMITS[action] ?? 1, tooMany: `account ${action} received too many positional arguments` };
60
+ return { max: ACCOUNT_POSITIONAL_LIMITS.get(action) ?? 1, tooMany: `account ${action} received too many positional arguments` };
61
61
  },
62
62
  browser(args) {
63
63
  const action = String(args._[0] || "status");
@@ -65,9 +65,9 @@ const ACTION_POSITIONAL_RULES = Object.freeze({
65
65
  },
66
66
  job(args) {
67
67
  const action = String(args._[0] || "list");
68
- return { max: JOB_POSITIONAL_LIMITS[action] ?? 1, tooMany: `job ${action} received too many positional arguments` };
68
+ return { max: JOB_POSITIONAL_LIMITS.get(action) ?? 1, tooMany: `job ${action} received too many positional arguments` };
69
69
  },
70
- });
70
+ }));
71
71
 
72
72
  export function normalizeCommand(argv) {
73
73
  if (!argv.length || argv[0].startsWith("--")) return ["start", argv];
@@ -105,7 +105,7 @@ export function parseArgs(argv) {
105
105
  }
106
106
 
107
107
  export function validateCommandOptions(command, args) {
108
- const allowed = COMMAND_OPTIONS[command];
108
+ const allowed = COMMAND_OPTIONS.get(command);
109
109
  if (!allowed) return;
110
110
  for (const key of Object.keys(args)) {
111
111
  if (key === "_" || key === "help" || key === "version") continue;
@@ -144,8 +144,8 @@ function positionalRule(command, args) {
144
144
  if (SINGLE_WORKSPACE_POSITIONAL_COMMANDS.has(command)) {
145
145
  return { max: 1, tooMany: `${command} accepts at most one positional workspace path`, workspaceConflictAfter: 0 };
146
146
  }
147
- if (STATIC_POSITIONAL_RULES[command]) return STATIC_POSITIONAL_RULES[command];
148
- return ACTION_POSITIONAL_RULES[command]?.(args) || null;
147
+ if (STATIC_POSITIONAL_RULES.has(command)) return STATIC_POSITIONAL_RULES.get(command);
148
+ return ACTION_POSITIONAL_RULES.get(command)?.(args) || null;
149
149
  }
150
150
  function parseBooleanOption(value, key) {
151
151
  if (value === "true" || value === "1") return true;
@@ -37,7 +37,7 @@ function policyState(policy) {
37
37
  function selectPolicyBase(args, stored, hasStored) {
38
38
  if (args.profile !== undefined) {
39
39
  const profile = String(args.profile).trim().toLowerCase();
40
- if (!POLICY_PROFILES[profile]) throw new Error(`--profile must be one of: ${Object.keys(POLICY_PROFILES).join(", ")}`);
40
+ if (!Object.hasOwn(POLICY_PROFILES, profile)) throw new Error(`--profile must be one of: ${Object.keys(POLICY_PROFILES).join(", ")}`);
41
41
  return policyProfile(profile, "explicit");
42
42
  }
43
43
  if (!hasStored) return policyProfile(DEFAULT_POLICY_PROFILE, "default");
@@ -0,0 +1,132 @@
1
+ import process from "node:process";
2
+ import * as defaultService from "./service.mjs";
3
+ import { inspectWorkspaceDaemon, stopWorkspaceServiceDaemon } from "./daemon-process.mjs";
4
+ import { stopAndRemoveAutostart } from "./service-lifecycle.mjs";
5
+ import { serviceEnvironmentSummary } from "./service-environment.mjs";
6
+ import { loadState, resolveWorkspace, selectedWorkspace } from "./state.mjs";
7
+
8
+ const SERVICE_ACTION_HANDLERS = new Map([
9
+ ["status", serviceStatusAction],
10
+ ["install", serviceInstallAction],
11
+ ["start", serviceStartAction],
12
+ ["stop", serviceStopAction],
13
+ ["uninstall", serviceUninstallAction],
14
+ ["remove", serviceUninstallAction],
15
+ ]);
16
+
17
+ export function createServiceCommand(dependencies) {
18
+ const context = {
19
+ chooseWorkspace: requiredFunction(dependencies.chooseWorkspace, "chooseWorkspace"),
20
+ stateRootFromArgs: requiredFunction(dependencies.stateRootFromArgs, "stateRootFromArgs"),
21
+ structuredLogger: requiredFunction(dependencies.structuredLogger, "structuredLogger"),
22
+ service: dependencies.service || defaultService,
23
+ inspectWorkspaceDaemon: dependencies.inspectWorkspaceDaemon || inspectWorkspaceDaemon,
24
+ stopWorkspaceServiceDaemon: dependencies.stopWorkspaceServiceDaemon || stopWorkspaceServiceDaemon,
25
+ stopAndRemoveAutostart: dependencies.stopAndRemoveAutostart || stopAndRemoveAutostart,
26
+ serviceEnvironmentSummary: dependencies.serviceEnvironmentSummary || serviceEnvironmentSummary,
27
+ loadState: dependencies.loadState || loadState,
28
+ resolveWorkspace: dependencies.resolveWorkspace || resolveWorkspace,
29
+ selectedWorkspace: dependencies.selectedWorkspace || selectedWorkspace,
30
+ entryScript: dependencies.entryScript || process.argv[1],
31
+ setExitCode: dependencies.setExitCode || setProcessExitCode,
32
+ print: dependencies.print || printLine,
33
+ };
34
+ return (args) => serviceCommand(args, context);
35
+ }
36
+
37
+ async function serviceCommand(args, context) {
38
+ const action = String(args._[0] || "status").toLowerCase();
39
+ const handler = SERVICE_ACTION_HANDLERS.get(action);
40
+ if (!handler) throw new Error(`Unknown service action: ${action}`);
41
+ const stateRoot = context.stateRootFromArgs(args);
42
+ return handler({ args, stateRoot, service: context.service, context });
43
+ }
44
+
45
+ async function serviceStatusAction({ args, stateRoot, service, context }) {
46
+ const status = await service.autostartStatus();
47
+ const state = optionalServiceState(args, stateRoot, context);
48
+ const workspaceDaemon = state ? context.inspectWorkspaceDaemon(state) : null;
49
+ printServiceResult({
50
+ ...status,
51
+ workspace: state?.workspace?.path || null,
52
+ workspace_daemon: workspaceDaemon,
53
+ service_environment: context.serviceEnvironmentSummary(stateRoot),
54
+ effective_active: Boolean(status.active || workspaceDaemon?.alive),
55
+ orphaned_workspace_daemon: Boolean(status.active === false && workspaceDaemon?.alive && workspaceDaemon?.verified_service_daemon),
56
+ }, context, false);
57
+ }
58
+
59
+ async function serviceInstallAction({ args, stateRoot, service, context }) {
60
+ const workspaceArgs = { ...args, _: args._.slice(1) };
61
+ const workspace = await context.chooseWorkspace(workspaceArgs, { promptOnFirstRun: true, save: true, allowPositional: true });
62
+ const state = context.loadState(workspace, { stateDir: stateRoot });
63
+ if (!state.worker?.url) {
64
+ throw new Error("No deployed Worker is recorded for this workspace. Run `machine-mcp` once before `machine-mcp service install`.");
65
+ }
66
+ const result = await service.installAutostart({
67
+ workspace,
68
+ stateRoot,
69
+ entryScript: context.entryScript,
70
+ logger: context.structuredLogger(Boolean(args.quiet)),
71
+ });
72
+ printServiceResult(result, context);
73
+ }
74
+
75
+ async function serviceStartAction({ args, service, context }) {
76
+ const result = await service.startAutostart({ logger: context.structuredLogger(Boolean(args.quiet)) });
77
+ printServiceResult(result, context);
78
+ }
79
+
80
+ async function serviceStopAction({ args, stateRoot, service, context }) {
81
+ const logger = context.structuredLogger(Boolean(args.quiet));
82
+ const provider = await service.stopAutostart({ logger });
83
+ const state = optionalServiceState(args, stateRoot, context);
84
+ const workspaceDaemon = state
85
+ ? await context.stopWorkspaceServiceDaemon(state, { logger, reason: "service stop" })
86
+ : { ok: true, found: false, stopped: false, verified_service_daemon: false, reason: "workspace_not_selected" };
87
+ printServiceResult({
88
+ ...provider,
89
+ ok: provider?.ok !== false && workspaceDaemon.ok,
90
+ workspace: state?.workspace?.path || null,
91
+ workspace_daemon: workspaceDaemon,
92
+ }, context);
93
+ }
94
+
95
+ async function serviceUninstallAction({ args, stateRoot, service, context }) {
96
+ const logger = context.structuredLogger(Boolean(args.quiet));
97
+ const state = optionalServiceState(args, stateRoot, context);
98
+ const lifecycle = await context.stopAndRemoveAutostart({
99
+ states: state ? [state] : [],
100
+ stateRoot,
101
+ logger,
102
+ reason: "service uninstall",
103
+ stopAutostart: service.stopAutostart,
104
+ uninstallAutostart: service.uninstallAutostart,
105
+ stopWorkspaceServiceDaemon: context.stopWorkspaceServiceDaemon,
106
+ });
107
+ printServiceResult({
108
+ ...lifecycle,
109
+ workspace: state?.workspace?.path || null,
110
+ workspace_daemon: lifecycle.workspace_daemons[0] || null,
111
+ autostart_removed: lifecycle.removed,
112
+ }, context);
113
+ }
114
+
115
+ function optionalServiceState(args, stateRoot, context) {
116
+ const requested = args.workspace || args._[1] || context.selectedWorkspace(stateRoot);
117
+ if (!requested || requested === true) return null;
118
+ return context.loadState(context.resolveWorkspace(String(requested)), { stateDir: stateRoot });
119
+ }
120
+
121
+ function printServiceResult(result, context, updateExitCode = true) {
122
+ context.print(JSON.stringify(result, null, 2));
123
+ if (updateExitCode && result?.ok === false) context.setExitCode(1);
124
+ }
125
+
126
+ function requiredFunction(value, name) {
127
+ if (typeof value !== "function") throw new TypeError(`service command requires ${name}`);
128
+ return value;
129
+ }
130
+
131
+ function setProcessExitCode(value) { process.exitCode = value; }
132
+ function printLine(value) { console.log(value); }
package/src/local/cli.mjs CHANGED
@@ -3,13 +3,14 @@ import { join, resolve } from "node:path";
3
3
  import process from "node:process";
4
4
  import readline from "node:readline/promises";
5
5
  import { LocalRuntime } from "./runtime.mjs";
6
- import { acquireDaemonLockWithTakeover, inspectWorkspaceDaemon, stopWorkspaceServiceDaemon } from "./daemon-process.mjs";
6
+ import { acquireDaemonLockWithTakeover, stopWorkspaceServiceDaemon } from "./daemon-process.mjs";
7
7
  import { inspectProcessInstance } from "./process-identity.mjs";
8
8
  import { runStdioServer } from "./stdio.mjs";
9
9
  import { assertCanonicalFullPolicy, POLICY_PROFILES, toolsForPolicy } from "./tools.mjs";
10
10
  import { resolvePolicy } from "./cli-policy.mjs";
11
11
  import { effectiveLogFormat, effectiveLogLevel, normalizeCommand, parseArgs, validateCommandOptions, validateLoggingOptions, validatePositionals } from "./cli-options.mjs";
12
12
  import { createLocalAdminCommands } from "./cli-local-admin.mjs";
13
+ import { createServiceCommand } from "./cli-service.mjs";
13
14
  import { generateAccountPassword } from "./account-admin.mjs";
14
15
  import { accountAdminClient, createAccountCommand } from "./cli-account-admin.mjs";
15
16
  export { resolvePolicy } from "./cli-policy.mjs";
@@ -18,7 +19,7 @@ import { classifyOperationalError, createLogger, sanitizeLogText } from "./log.m
18
19
  import { runExecutable, runWrangler } from "./shell.mjs";
19
20
  import { runFullAccessTest } from "./full-access-test.mjs";
20
21
  import { stopAndRemoveAutostart } from "./service-lifecycle.mjs";
21
- import { loadServiceEnvironment, serviceEnvironmentSummary } from "./service-environment.mjs";
22
+ import { loadServiceEnvironment } from "./service-environment.mjs";
22
23
  import { ensureWorkerDeployment } from "./worker-deployment.mjs";
23
24
  import { workerHealth } from "./worker-health.mjs";
24
25
  export { workerHealthUserReason } from "./worker-health.mjs";
@@ -48,24 +49,25 @@ import {
48
49
 
49
50
  const localAdminCommands = createLocalAdminCommands({ chooseWorkspace, confirm });
50
51
  const accountCommand = createAccountCommand({ chooseWorkspace, confirm });
51
-
52
- const COMMAND_HANDLERS = Object.freeze({
53
- start: startCommand,
54
- stdio: stdioCommand,
55
- "client-config": clientConfigCommand,
56
- status: statusCommand,
57
- doctor: doctorCommand,
58
- "full-test": fullTestCommand,
59
- workspace: workspaceCommand,
60
- service: serviceCommand,
61
- autostart: serviceCommand,
62
- "rotate-secrets": rotateSecretsCommand,
63
- resource: localAdminCommands.resourceCommand,
64
- account: accountCommand,
65
- browser: localAdminCommands.browserCommand,
66
- job: localAdminCommands.jobCommand,
67
- uninstall: uninstallCommand,
68
- });
52
+ const serviceCommand = createServiceCommand({ chooseWorkspace, stateRootFromArgs, structuredLogger });
53
+
54
+ const COMMAND_HANDLERS = new Map([
55
+ ["start", startCommand],
56
+ ["stdio", stdioCommand],
57
+ ["client-config", clientConfigCommand],
58
+ ["status", statusCommand],
59
+ ["doctor", doctorCommand],
60
+ ["full-test", fullTestCommand],
61
+ ["workspace", workspaceCommand],
62
+ ["service", serviceCommand],
63
+ ["autostart", serviceCommand],
64
+ ["rotate-secrets", rotateSecretsCommand],
65
+ ["resource", localAdminCommands.resourceCommand],
66
+ ["account", accountCommand],
67
+ ["browser", localAdminCommands.browserCommand],
68
+ ["job", localAdminCommands.jobCommand],
69
+ ["uninstall", uninstallCommand],
70
+ ]);
69
71
 
70
72
  export async function main(argv = process.argv.slice(2)) {
71
73
  const [command, rest] = normalizeCommand(argv);
@@ -75,7 +77,7 @@ export async function main(argv = process.argv.slice(2)) {
75
77
  validateCommandOptions(command, args);
76
78
  validatePositionals(command, args);
77
79
  validateLoggingOptions(args);
78
- const handler = COMMAND_HANDLERS[command];
80
+ const handler = COMMAND_HANDLERS.get(command);
79
81
  if (handler) return handler(args);
80
82
  console.error(`Unknown command: ${command}`);
81
83
  usage();
@@ -250,7 +252,7 @@ async function startRemoteRuntime({ args, workspace, state, daemonLock, logger }
250
252
  let runtime = null;
251
253
  try {
252
254
  const readiness = await prepareRemoteState({ args, workspace, state, logger });
253
- runtime = createRemoteRuntime({ args, workspace, state, daemonLock, logger });
255
+ runtime = createRemoteRuntime({ args, workspace, state, daemonLock });
254
256
  await runtime.start();
255
257
  reportRemoteReady(args, state, readiness);
256
258
  keepProcessAlive({ daemon: runtime, lock: daemonLock, logger });
@@ -295,7 +297,7 @@ async function ensureInitialOwnerAccount(state) {
295
297
  return { ...created.account, password };
296
298
  }
297
299
 
298
- function createRemoteRuntime({ args, workspace, state, daemonLock, logger }) {
300
+ function createRemoteRuntime({ args, workspace, state, daemonLock }) {
299
301
  return new LocalRuntime({
300
302
  workerUrl: state.worker.url,
301
303
  secret: state.worker.daemonSecret,
@@ -352,7 +354,7 @@ async function clientConfigCommand(args) {
352
354
  const workspace = await chooseWorkspace(workspaceArgs, { promptOnFirstRun: false, save: false, allowPositional: false });
353
355
  const requested = String(args.client || args._[0] || "all").trim().toLowerCase();
354
356
  const profile = String(args.profile || "full").trim().toLowerCase();
355
- if (!POLICY_PROFILES[profile]) throw new Error(`--profile must be one of: ${Object.keys(POLICY_PROFILES).join(", ")}`);
357
+ if (!Object.hasOwn(POLICY_PROFILES, profile)) throw new Error(`--profile must be one of: ${Object.keys(POLICY_PROFILES).join(", ")}`);
356
358
  if (!["all", "claude", "cursor", "codex", "generic"].includes(requested)) throw new Error("client must be all, claude, cursor, codex, or generic");
357
359
  const command = process.execPath;
358
360
  const argsList = [resolve(process.argv[1]), "stdio", "--workspace", workspace, "--profile", profile];
@@ -537,90 +539,6 @@ async function rotateSecretsCommand(args) {
537
539
  }
538
540
  }
539
541
 
540
- async function serviceCommand(args) {
541
- const action = String(args._[0] || "status");
542
- const stateRoot = stateRootFromArgs(args);
543
- const { installAutostart, uninstallAutostart, autostartStatus, startAutostart, stopAutostart } = await import("./service.mjs");
544
- if (action === "status") {
545
- const status = await autostartStatus({ logger: structuredLogger(Boolean(args.quiet)) });
546
- const state = optionalServiceState(args, stateRoot);
547
- const workspaceDaemon = state ? inspectWorkspaceDaemon(state) : null;
548
- console.log(JSON.stringify({
549
- ...status,
550
- workspace: state?.workspace?.path || null,
551
- workspace_daemon: workspaceDaemon,
552
- service_environment: serviceEnvironmentSummary(stateRoot),
553
- effective_active: Boolean(status.active || workspaceDaemon?.alive),
554
- orphaned_workspace_daemon: Boolean(status.active === false && workspaceDaemon?.alive && workspaceDaemon?.verified_service_daemon),
555
- }, null, 2));
556
- return;
557
- }
558
- if (action === "install") {
559
- const workspaceArgs = { ...args, _: args._.slice(1) };
560
- const workspace = await chooseWorkspace(workspaceArgs, { promptOnFirstRun: true, save: true, allowPositional: true });
561
- const state = loadState(workspace, { stateDir: stateRoot });
562
- if (!state.worker?.url) {
563
- throw new Error("No deployed Worker is recorded for this workspace. Run `machine-mcp` once before `machine-mcp service install`.");
564
- }
565
- const result = await installAutostart({ workspace, stateRoot, entryScript: process.argv[1], logger: structuredLogger(Boolean(args.quiet)) });
566
- console.log(JSON.stringify(result, null, 2));
567
- if (result?.ok === false) process.exitCode = 1;
568
- return;
569
- }
570
- if (action === "start") {
571
- const result = await startAutostart({ logger: structuredLogger(Boolean(args.quiet)) });
572
- console.log(JSON.stringify(result, null, 2));
573
- if (result?.ok === false) process.exitCode = 1;
574
- return;
575
- }
576
- if (action === "stop") {
577
- const logger = structuredLogger(Boolean(args.quiet));
578
- const provider = await stopAutostart({ logger });
579
- const state = optionalServiceState(args, stateRoot);
580
- const workspaceDaemon = state
581
- ? await stopWorkspaceServiceDaemon(state, { logger, reason: "service stop" })
582
- : { ok: true, found: false, stopped: false, verified_service_daemon: false, reason: "workspace_not_selected" };
583
- const result = {
584
- ...provider,
585
- ok: provider?.ok !== false && workspaceDaemon.ok,
586
- workspace: state?.workspace?.path || null,
587
- workspace_daemon: workspaceDaemon,
588
- };
589
- console.log(JSON.stringify(result, null, 2));
590
- if (!result.ok) process.exitCode = 1;
591
- return;
592
- }
593
- if (action === "uninstall" || action === "remove") {
594
- const logger = structuredLogger(Boolean(args.quiet));
595
- const state = optionalServiceState(args, stateRoot);
596
- const lifecycle = await stopAndRemoveAutostart({
597
- states: state ? [state] : [],
598
- stateRoot,
599
- logger,
600
- reason: "service uninstall",
601
- stopAutostart,
602
- uninstallAutostart,
603
- stopWorkspaceServiceDaemon,
604
- });
605
- const output = {
606
- ...lifecycle,
607
- workspace: state?.workspace?.path || null,
608
- workspace_daemon: lifecycle.workspace_daemons[0] || null,
609
- autostart_removed: lifecycle.removed,
610
- };
611
- console.log(JSON.stringify(output, null, 2));
612
- if (!output.ok) process.exitCode = 1;
613
- return;
614
- }
615
- throw new Error(`Unknown service action: ${action}`);
616
- }
617
-
618
- function optionalServiceState(args, stateRoot) {
619
- const requested = args.workspace || args._[1] || selectedWorkspace(stateRoot);
620
- if (!requested || requested === true) return null;
621
- return loadState(resolveWorkspace(String(requested)), { stateDir: stateRoot });
622
- }
623
-
624
542
  async function installAutostartBestEffort({ workspace, stateRoot, entryScript, logger }) {
625
543
  try {
626
544
  const { installAutostart } = await import("./service.mjs");
@@ -878,5 +796,5 @@ function version() {
878
796
  }
879
797
 
880
798
  function sleep(ms) {
881
- return new Promise(resolvePromise => setTimeout(resolvePromise, ms));
799
+ return new Promise(resolvePromise => { setTimeout(resolvePromise, ms); });
882
800
  }
@@ -209,5 +209,5 @@ function boundedPositiveInt(value, fallback) {
209
209
  }
210
210
 
211
211
  function sleep(ms) {
212
- return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
212
+ return new Promise((resolvePromise) => { setTimeout(resolvePromise, ms); });
213
213
  }
@@ -183,7 +183,7 @@ async function waitForJob(manager, jobId, timeoutMs) {
183
183
  while (!deadline.expired()) {
184
184
  const value = manager.read({ job_id: jobId });
185
185
  if (TERMINAL_JOB_STATES.has(value.status)) return value;
186
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
186
+ await new Promise((resolvePromise) => { setTimeout(resolvePromise, 50); });
187
187
  }
188
188
  throw new Error("full access managed-job test timed out");
189
189
  }
@@ -71,7 +71,7 @@ async function releaseRecoveryClaim() {
71
71
  const deadline = createMonotonicDeadline(5000);
72
72
  while (!deadline.expired()) {
73
73
  if (removeOwnedJsonFileSync(file, { pid: process.pid, token: recoveryLockToken })) return;
74
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
74
+ await new Promise((resolvePromise) => { setTimeout(resolvePromise, 10); });
75
75
  }
76
76
  throw new Error("recovery runner could not verify ownership of the recovery lock");
77
77
  }
@@ -257,7 +257,10 @@ async function runStep(step, index, phase, plan, resourceContext, cancellationAw
257
257
 
258
258
  function spawnStep(argv, { cwd, env, input, timeoutMs, cancellationAware, captureOutput, captureBudget }) {
259
259
  return new Promise((resolvePromise, rejectPromise) => {
260
- if (cancellationAware && isCancellationRequested()) return rejectPromise(new JobCancelledError());
260
+ if (cancellationAware && isCancellationRequested()) {
261
+ rejectPromise(new JobCancelledError());
262
+ return;
263
+ }
261
264
  const child = spawn(argv[0], argv.slice(1), {
262
265
  cwd,
263
266
  env,
@@ -300,7 +303,10 @@ function spawnStep(argv, { cwd, env, input, timeoutMs, cancellationAware, captur
300
303
  });
301
304
  child.on("error", (error) => finish(() => rejectPromise(error)));
302
305
  child.on("close", (code, signal) => finish(() => {
303
- if (cancellationAware && isCancellationRequested()) return rejectPromise(new JobCancelledError());
306
+ if (cancellationAware && isCancellationRequested()) {
307
+ rejectPromise(new JobCancelledError());
308
+ return;
309
+ }
304
310
  resolvePromise({
305
311
  code: Number.isInteger(code) ? code : 1,
306
312
  signal: signal ? String(signal) : null,
@@ -156,10 +156,10 @@ function referencedResources(steps, registry) {
156
156
  }
157
157
  }
158
158
  if (names.size > MAX_RESOURCES) throw new Error(`job references more than ${MAX_RESOURCES} local resources`);
159
- const out = {};
159
+ const out = Object.create(null);
160
160
  let totalBytes = 0;
161
161
  for (const name of names) {
162
- const resource = registry[name];
162
+ const resource = Object.hasOwn(registry, name) ? registry[name] : null;
163
163
  if (!resource) throw new Error(`unknown local resource: ${name}`);
164
164
  const inspected = inspectResourceFile(resource.path, { allowInsecurePermissions: resource.allowInsecurePermissions === true, includeHash: true });
165
165
  totalBytes += inspected.size;
@@ -174,7 +174,7 @@ function referencedResources(steps, registry) {
174
174
  }
175
175
 
176
176
  export function normalizeResourceRegistry(resources) {
177
- const out = {};
177
+ const out = Object.create(null);
178
178
  if (!resources || typeof resources !== "object" || Array.isArray(resources)) return out;
179
179
  for (const [rawName, rawValue] of Object.entries(resources).slice(0, MAX_RESOURCES)) {
180
180
  const name = validateResourceName(rawName);
@@ -1,5 +1,11 @@
1
+ // @ts-check
2
+
1
3
  import { performance } from "node:perf_hooks";
2
4
 
5
+ /**
6
+ * @param {number} timeoutMs
7
+ * @param {() => number} [now]
8
+ */
3
9
  export function createMonotonicDeadline(timeoutMs, now = () => performance.now()) {
4
10
  const durationMs = Number(timeoutMs);
5
11
  if (!Number.isFinite(durationMs) || durationMs < 0) throw new TypeError("timeoutMs must be a finite non-negative number");
@@ -23,6 +29,7 @@ export function createMonotonicDeadline(timeoutMs, now = () => performance.now()
23
29
  });
24
30
  }
25
31
 
32
+ /** @param {() => number} now */
26
33
  function readSample(now) {
27
34
  const value = Number(now());
28
35
  if (!Number.isFinite(value)) throw new TypeError("monotonic clock returned a non-finite value");
@@ -1,3 +1,11 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @param {unknown} value
5
+ * @param {number} fallback
6
+ * @param {number} minimum
7
+ * @param {number} maximum
8
+ */
1
9
  export function clampInteger(value, fallback, minimum, maximum) {
2
10
  const parsed = typeof value === "number"
3
11
  ? value