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.
- package/CHANGELOG.md +21 -0
- package/CODE_OF_CONDUCT.md +24 -0
- package/CONTRIBUTING.md +3 -1
- package/GOVERNANCE.md +50 -0
- package/README.md +26 -0
- package/SUPPORT.md +31 -0
- package/browser-extension/browser-operations.js +10 -4
- package/browser-extension/devtools-input.js +2 -2
- package/browser-extension/manifest.json +2 -2
- package/browser-extension/page-automation.js +1 -1
- package/docs/ARCHITECTURE.md +14 -17
- package/docs/AUDIT.md +28 -0
- package/docs/ENGINEERING.md +8 -2
- package/docs/PROJECT_STANDARDS.md +4 -2
- package/docs/TESTING.md +9 -3
- package/docs/UPGRADING.md +32 -0
- package/package.json +12 -4
- package/scripts/coverage-check.mjs +27 -10
- package/src/local/account-access.mjs +7 -5
- package/src/local/agent-context.mjs +7 -148
- package/src/local/agent-contract.mjs +206 -0
- package/src/local/browser-bridge.mjs +30 -281
- package/src/local/browser-extension-protocol.mjs +19 -4
- package/src/local/browser-operation-service.mjs +325 -0
- package/src/local/call-registry.mjs +44 -1
- package/src/local/capability-ranking.mjs +13 -0
- package/src/local/cli-local-admin.mjs +74 -38
- package/src/local/cli-options.mjs +18 -18
- package/src/local/cli-policy.mjs +1 -1
- package/src/local/cli-service.mjs +132 -0
- package/src/local/cli.mjs +27 -109
- package/src/local/daemon-process.mjs +1 -1
- package/src/local/full-access-test.mjs +1 -1
- package/src/local/job-runner.mjs +9 -3
- package/src/local/managed-job-plan.mjs +3 -3
- package/src/local/monotonic-deadline.mjs +7 -0
- package/src/local/numbers.mjs +8 -0
- package/src/local/policy.mjs +97 -16
- package/src/local/project-metadata.mjs +7 -1
- package/src/local/project-package.mjs +1 -1
- package/src/local/records.mjs +6 -0
- package/src/local/resource-operations.mjs +1 -1
- package/src/local/runtime-capabilities.mjs +66 -0
- package/src/local/runtime-diagnostics.mjs +91 -0
- package/src/local/runtime-reporting.mjs +113 -0
- package/src/local/runtime.mjs +54 -191
- package/src/local/service-convergence.mjs +1 -1
- package/src/local/service.mjs +1 -1
- package/src/local/state.mjs +1 -1
- package/src/local/windows-service.mjs +1 -1
- package/src/local/worker-deployment.mjs +15 -10
- package/src/local/worker-health.mjs +8 -4
- package/src/worker/access.ts +8 -7
- package/src/worker/account-admin.ts +2 -2
- package/src/worker/authority.ts +3 -3
- package/src/worker/http.ts +2 -2
- package/src/worker/index.ts +29 -337
- package/src/worker/oauth-controller.ts +343 -0
- package/src/worker/oauth-state.ts +1 -1
- package/src/worker/oauth-tokens.ts +2 -2
- package/src/worker/tool-catalog.ts +1 -1
- package/tsconfig.json +2 -1
- 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 =
|
|
38
|
-
"client-config"
|
|
39
|
-
uninstall
|
|
40
|
-
|
|
41
|
-
const RESOURCE_POSITIONAL_LIMITS = Object.
|
|
42
|
-
const JOB_POSITIONAL_LIMITS = Object.
|
|
43
|
-
const ACCOUNT_POSITIONAL_LIMITS = Object.
|
|
44
|
-
const ACTION_POSITIONAL_RULES = Object.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
148
|
-
return ACTION_POSITIONAL_RULES
|
|
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;
|
package/src/local/cli-policy.mjs
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
|
@@ -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
|
}
|
package/src/local/job-runner.mjs
CHANGED
|
@@ -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())
|
|
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())
|
|
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");
|
package/src/local/numbers.mjs
CHANGED
|
@@ -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
|