@themoltnet/agent-daemon 0.61.0 → 0.62.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.
- package/README.md +10 -9
- package/dist/cli.js +135 -326
- package/package.json +6 -8
package/README.md
CHANGED
|
@@ -15,17 +15,18 @@ from [themolt.net/download](https://themolt.net/download). Opening the app:
|
|
|
15
15
|
|
|
16
16
|
1. installs or updates the publisher-verified Agent CLI bundle under
|
|
17
17
|
`~/.local/share/moltnet/agent`;
|
|
18
|
-
2.
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
2. starts a foreground, supervised Agent Server over a private native socket;
|
|
19
|
+
and
|
|
20
|
+
3. opens the browser only when OAuth approval is required.
|
|
21
21
|
|
|
22
22
|
Closing the status window hides it. **Quit and Stop Server** stops the owned
|
|
23
|
-
server process before the app exits.
|
|
24
|
-
operator, identities, and
|
|
23
|
+
server process before the app exits. Desktop re-establishes its private native
|
|
24
|
+
control channel after a server restart. The local operator, identities, and
|
|
25
|
+
provider configuration persist.
|
|
25
26
|
|
|
26
27
|
Agent CLI updates and desktop-app updates use independent signed channels and
|
|
27
28
|
always require consent. Removing the Agent CLI bundle preserves
|
|
28
|
-
`~/.config/moltnet`.
|
|
29
|
+
`~/.config/moltnet`.
|
|
29
30
|
|
|
30
31
|
### Agent CLI
|
|
31
32
|
|
|
@@ -123,8 +124,8 @@ For config-based runs, the agent's `moltnet.json` lives in the central store
|
|
|
123
124
|
(`~/.config/moltnet/identities/<agent>/`) or, for an external config, next to
|
|
124
125
|
its gitconfig in `.moltnet/<agent>/`. Three paths create it:
|
|
125
126
|
|
|
126
|
-
-
|
|
127
|
-
|
|
127
|
+
- MoltNet Agent creates a managed agent through the Agent Server: keypair and
|
|
128
|
+
agent key generated on this machine, stored under the
|
|
128
129
|
store's `secrets/` directory, no CLI involved.
|
|
129
130
|
- [`moltnet register`](../../docs/start/install-and-initialize.md#register-an-agent)
|
|
130
131
|
creates an OAuth2 identity from the CLI; add a stored agent key with
|
|
@@ -209,7 +210,7 @@ carries them -- read access to the agent's teams and their diaries, and
|
|
|
209
210
|
enrollment into a team. Startup does not check for them: a key without them
|
|
210
211
|
claims and runs work, and the daemon reports which are absent.
|
|
211
212
|
|
|
212
|
-
|
|
213
|
+
Desktop selects the full set by default. Knowledge-enabled workers must add
|
|
213
214
|
`diary:write`, `pack:read`, and `pack:write` when the key is issued.
|
|
214
215
|
|
|
215
216
|
`crypto:sign` is in the minimum because host-capability signing runs on the
|
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import { constants, copyFileSync, createReadStream, createWriteStream, existsSyn
|
|
|
16
16
|
import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, formatSecretReferenceString, getConfigDir, getIdentityDir, hasAgentKeyConfiguration, isCanonicalConfig, parseSecretReferenceString, readConfig, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed, selectAgentKeyReference, signBytes } from "@themoltnet/sdk";
|
|
17
17
|
import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, RuntimeProfilePrerequisiteError, createLocalSeedSigner, resolveAgentIdentity, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
|
|
18
18
|
import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveRuntimeProfileModel } from "@themoltnet/pi-runtime";
|
|
19
|
-
import {
|
|
19
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
20
20
|
import { once } from "node:events";
|
|
21
21
|
import { metrics } from "@opentelemetry/api";
|
|
22
22
|
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
|
|
@@ -28,7 +28,7 @@ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
|
28
28
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
29
29
|
import { homedir, platform, tmpdir } from "node:os";
|
|
30
30
|
import { PI_MODEL_MODALITIES, writePiConfig } from "@themoltnet/pi-runtime/pi-config";
|
|
31
|
-
import { mkdir, open, readFile, readdir, realpath,
|
|
31
|
+
import { chmod, lstat, mkdir, open, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
32
32
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
33
33
|
import { pipeline } from "node:stream/promises";
|
|
34
34
|
import { createInterface } from "node:readline/promises";
|
|
@@ -45,8 +45,6 @@ import { pathToFileURL } from "node:url";
|
|
|
45
45
|
import { StringDecoder } from "node:string_decoder";
|
|
46
46
|
import rateLimit from "@fastify/rate-limit";
|
|
47
47
|
import Fastify from "fastify";
|
|
48
|
-
import "reflect-metadata";
|
|
49
|
-
import { BasicConstraintsExtension, ExtendedKeyUsage, ExtendedKeyUsageExtension, IP, KeyUsageFlags, KeyUsagesExtension, SubjectAlternativeNameExtension, X509CertificateGenerator } from "@peculiar/x509";
|
|
50
48
|
import { createGzip } from "node:zlib";
|
|
51
49
|
//#region ../../libs/tasks/src/rubric.ts
|
|
52
50
|
/**
|
|
@@ -3851,13 +3849,13 @@ function loadUpdateEnvConfig() {
|
|
|
3851
3849
|
}
|
|
3852
3850
|
//#endregion
|
|
3853
3851
|
//#region src/lib/run-project-selection.ts
|
|
3854
|
-
var execFileAsync$
|
|
3852
|
+
var execFileAsync$1 = promisify(execFile);
|
|
3855
3853
|
var GIT_TIMEOUT_MS = 1e4;
|
|
3856
3854
|
var GIT_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
3857
3855
|
async function validateGitSource(source) {
|
|
3858
3856
|
const inherited = processEnvSnapshot();
|
|
3859
3857
|
try {
|
|
3860
|
-
const { stdout } = await execFileAsync$
|
|
3858
|
+
const { stdout } = await execFileAsync$1("git", [
|
|
3861
3859
|
"rev-parse",
|
|
3862
3860
|
"--show-toplevel",
|
|
3863
3861
|
"--verify",
|
|
@@ -4015,11 +4013,9 @@ Commands:
|
|
|
4015
4013
|
once Claim and execute one specific queued task by id, then exit.
|
|
4016
4014
|
drain Poll until the queue has nothing claimable, then exit.
|
|
4017
4015
|
Useful for batch eval runs and demos.
|
|
4018
|
-
server
|
|
4019
|
-
agent/provider config store, and start/stop of poll/drain
|
|
4020
|
-
|
|
4021
|
-
server trust
|
|
4022
|
-
Install the per-user macOS local-HTTPS CA after explicit consent.
|
|
4016
|
+
server Supervisor for managed runs: authorized standalone local control,
|
|
4017
|
+
agent/provider config store, and start/stop of poll/drain child
|
|
4018
|
+
processes. Binds 127.0.0.1 or a private native socket.
|
|
4023
4019
|
providers Manage configured endpoints and Pi OAuth subscriptions without
|
|
4024
4020
|
starting the Agent Server. See \`agent-daemon providers --help\`.
|
|
4025
4021
|
sync-sessions
|
|
@@ -4169,16 +4165,17 @@ function isHelpFlag(args) {
|
|
|
4169
4165
|
return args.includes("--help") || args.includes("-h");
|
|
4170
4166
|
}
|
|
4171
4167
|
var AGENT_SERVER_HELP = `\
|
|
4172
|
-
agent-daemon server —
|
|
4168
|
+
agent-daemon server — local supervisor for managed runs.
|
|
4173
4169
|
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4170
|
+
Standalone mode binds 127.0.0.1 for authorized local-control clients. MoltNet
|
|
4171
|
+
Agent Desktop instead uses a private Unix socket with a process-scoped grant.
|
|
4172
|
+
Both modes configure agents and providers and start/stop child runs.
|
|
4177
4173
|
|
|
4178
4174
|
Options:
|
|
4179
4175
|
--port <n> Loopback port. Default: 17374.
|
|
4180
4176
|
Env: MOLTNET_AGENT_SERVER_PORT.
|
|
4181
|
-
--allowed-origins <csv> Exact
|
|
4177
|
+
--allowed-origins <csv> Exact browser-controller origins allowed
|
|
4178
|
+
local control.
|
|
4182
4179
|
Default: https://console.themolt.net.
|
|
4183
4180
|
Env: MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS.
|
|
4184
4181
|
--root <path> Config root. Default: ~/.config/moltnet
|
|
@@ -4188,14 +4185,13 @@ Options:
|
|
|
4188
4185
|
--heartbeat-interval-ms <n> Child reporter heartbeat cadence. Default: 60000.
|
|
4189
4186
|
--warm-retention-sec <n> Child session/workspace retention. Default: 1800.
|
|
4190
4187
|
--supervised Also stop gracefully when stdin reaches EOF.
|
|
4188
|
+
--native-socket <path> Private native-only socket (requires --supervised).
|
|
4189
|
+
Absolute, at most 100 bytes, with a new socket in
|
|
4190
|
+
a caller-owned 0700 directory. TCP flags are not
|
|
4191
|
+
accepted; inherited TCP env settings are ignored.
|
|
4191
4192
|
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
server trust --status --json
|
|
4195
|
-
server trust --yes --json
|
|
4196
|
-
server trust --remove --yes --json
|
|
4197
|
-
Run \`agent-daemon server trust --remove\` interactively to remove that exact
|
|
4198
|
-
CA. Linux continues to use the Chromium PNA HTTP path.
|
|
4193
|
+
Standalone mode uses loopback HTTP on every platform. Desktop socket mode does
|
|
4194
|
+
not open a TCP listener.
|
|
4199
4195
|
`;
|
|
4200
4196
|
var PROVIDERS_HELP = `\
|
|
4201
4197
|
moltnet-agent providers — manage local model providers.
|
|
@@ -4258,7 +4254,7 @@ async function resolveDaemonAgentIdentity(input) {
|
|
|
4258
4254
|
}
|
|
4259
4255
|
//#endregion
|
|
4260
4256
|
//#region src/lib/correlation.ts
|
|
4261
|
-
var execFileAsync
|
|
4257
|
+
var execFileAsync = promisify(execFile);
|
|
4262
4258
|
var CORRELATION_TRAILER_KEY = "Moltnet-Correlation-Id";
|
|
4263
4259
|
var CORRELATION_MARKER_RE = /<!--\s*moltnet-correlation:\s*([\w-]+)\s*-->/i;
|
|
4264
4260
|
new RegExp(`^${CORRELATION_TRAILER_KEY}:\\s*(\\S+)\\s*$`, "m");
|
|
@@ -4305,7 +4301,7 @@ function makePrBodyAnchorWriter(deps) {
|
|
|
4305
4301
|
function createGhCliClient() {
|
|
4306
4302
|
return {
|
|
4307
4303
|
async get({ owner, repo, number }) {
|
|
4308
|
-
const { stdout } = await execFileAsync
|
|
4304
|
+
const { stdout } = await execFileAsync("gh", [
|
|
4309
4305
|
"api",
|
|
4310
4306
|
`repos/${owner}/${repo}/pulls/${number}`,
|
|
4311
4307
|
"--jq",
|
|
@@ -4314,7 +4310,7 @@ function createGhCliClient() {
|
|
|
4314
4310
|
return JSON.parse(stdout);
|
|
4315
4311
|
},
|
|
4316
4312
|
async patch({ owner, repo, number }, body) {
|
|
4317
|
-
await execFileAsync
|
|
4313
|
+
await execFileAsync("gh", [
|
|
4318
4314
|
"api",
|
|
4319
4315
|
"-X",
|
|
4320
4316
|
"PATCH",
|
|
@@ -9469,6 +9465,29 @@ var NativeGrantService = class {
|
|
|
9469
9465
|
}
|
|
9470
9466
|
};
|
|
9471
9467
|
//#endregion
|
|
9468
|
+
//#region src/lib/agent-server/native-socket.ts
|
|
9469
|
+
/** The supervisor owns the private directory; the server never removes peers. */
|
|
9470
|
+
async function validateNativeSocket(path) {
|
|
9471
|
+
if (!isAbsolute(path) || Buffer.byteLength(path) > 100) throw new Error("Native socket must be an absolute path of at most 100 bytes");
|
|
9472
|
+
const parent = dirname(path);
|
|
9473
|
+
const metadata = await lstat(parent);
|
|
9474
|
+
if (metadata.isSymbolicLink()) throw new Error(`Native socket parent contains a symlink: ${parent}`);
|
|
9475
|
+
if (!metadata.isDirectory()) throw new Error(`Native socket parent is not a directory: ${parent}`);
|
|
9476
|
+
const expectedUid = process.getuid?.();
|
|
9477
|
+
if (expectedUid === void 0 || metadata.uid !== expectedUid) throw new Error(`Native socket parent has uid ${metadata.uid}; expected ${String(expectedUid)}: ${parent}`);
|
|
9478
|
+
const mode = metadata.mode & 511;
|
|
9479
|
+
if (mode !== 448) throw new Error(`Native socket parent has mode ${mode.toString(8)}; expected 700: ${parent}`);
|
|
9480
|
+
const canonical = await realpath(parent);
|
|
9481
|
+
if (canonical !== resolve(parent)) throw new Error(`Native socket parent contains a symlink: ${parent} resolves to ${canonical}`);
|
|
9482
|
+
try {
|
|
9483
|
+
await lstat(path);
|
|
9484
|
+
} catch (error) {
|
|
9485
|
+
if (error.code === "ENOENT") return;
|
|
9486
|
+
throw error;
|
|
9487
|
+
}
|
|
9488
|
+
throw new Error("Native socket path already exists; it was left untouched");
|
|
9489
|
+
}
|
|
9490
|
+
//#endregion
|
|
9472
9491
|
//#region src/lib/agent-server/operator-oauth.ts
|
|
9473
9492
|
var InvalidOperatorGrantError = class extends Error {};
|
|
9474
9493
|
var LOCAL_SCOPE = OPERATOR_OAUTH.localControlScope;
|
|
@@ -9671,8 +9690,8 @@ var OperatorOAuth = class {
|
|
|
9671
9690
|
/**
|
|
9672
9691
|
* Subscription-provider OAuth brokering for Agent Server (#2061 slice 4).
|
|
9673
9692
|
*
|
|
9674
|
-
*
|
|
9675
|
-
* `ModelRuntime.login()` (which owns persistence into the shared
|
|
9693
|
+
* An authorized controller starts the flow; Agent Server runs Pi OAuth
|
|
9694
|
+
* host-side via `ModelRuntime.login()` (which owns persistence into the shared
|
|
9676
9695
|
* `pi/auth.json` and token rotation thereafter). The browser only ever sees
|
|
9677
9696
|
* the provider's authorize URL or device code — never tokens.
|
|
9678
9697
|
*
|
|
@@ -11406,17 +11425,17 @@ async function enrollIdentityTeam(options) {
|
|
|
11406
11425
|
/**
|
|
11407
11426
|
* The identity-wide team/diary binding, read from `<identityDir>/env`.
|
|
11408
11427
|
*
|
|
11409
|
-
* This mirrors the Go CLI's `identityDefaultBinding` (`
|
|
11428
|
+
* This mirrors the Go CLI's `identityDefaultBinding` (`project_selection.go`),
|
|
11410
11429
|
* which is the fallback the CLI uses when a working directory has no
|
|
11411
|
-
*
|
|
11430
|
+
* registered project binding.
|
|
11412
11431
|
*
|
|
11413
11432
|
* The desktop cannot use the CLI's *location* bindings at all — its composer
|
|
11414
11433
|
* has no working directory to key on — but it can honour this identity-wide
|
|
11415
|
-
* default, so an operator
|
|
11416
|
-
*
|
|
11434
|
+
* default, so an operator sees their familiar team preselected rather than an
|
|
11435
|
+
* arbitrary first entry.
|
|
11417
11436
|
*
|
|
11418
11437
|
* Like the CLI, a half-filled pair is treated as no binding: a team without a
|
|
11419
|
-
* diary is
|
|
11438
|
+
* diary is ignored.
|
|
11420
11439
|
*/
|
|
11421
11440
|
function readIdentityDefaultBinding(identityDir) {
|
|
11422
11441
|
let contents;
|
|
@@ -11982,10 +12001,7 @@ function buildAgentServer(options) {
|
|
|
11982
12001
|
const { nativeGrant } = options;
|
|
11983
12002
|
const oauth = options.operatorOAuth;
|
|
11984
12003
|
let restartRequired = false;
|
|
11985
|
-
const fastifyOptions = {
|
|
11986
|
-
bodyLimit: BODY_LIMIT,
|
|
11987
|
-
...options.tls ? { https: options.tls } : {}
|
|
11988
|
-
};
|
|
12004
|
+
const fastifyOptions = { bodyLimit: BODY_LIMIT };
|
|
11989
12005
|
const app = options.logger ? Fastify({
|
|
11990
12006
|
...fastifyOptions,
|
|
11991
12007
|
loggerInstance: options.logger
|
|
@@ -12021,6 +12037,31 @@ function buildAgentServer(options) {
|
|
|
12021
12037
|
browserVerification.set(request, pending);
|
|
12022
12038
|
return pending;
|
|
12023
12039
|
}
|
|
12040
|
+
function hasValidNativeGrant(origin, token) {
|
|
12041
|
+
if (origin !== "moltnet-agent-desktop://native" || typeof token !== "string" || token.length === 0) return false;
|
|
12042
|
+
try {
|
|
12043
|
+
nativeGrant.verify(origin, token);
|
|
12044
|
+
return true;
|
|
12045
|
+
} catch (error) {
|
|
12046
|
+
if (error instanceof NativeGrantError) return false;
|
|
12047
|
+
throw error;
|
|
12048
|
+
}
|
|
12049
|
+
}
|
|
12050
|
+
const nativeVerification = /* @__PURE__ */ new WeakSet();
|
|
12051
|
+
function requireNativeGrant(request) {
|
|
12052
|
+
if (nativeVerification.has(request)) return;
|
|
12053
|
+
const origin = request.headers.origin;
|
|
12054
|
+
const token = request.headers[AGENT_SERVER_TOKEN_HEADER];
|
|
12055
|
+
if (hasValidNativeGrant(origin, token)) {
|
|
12056
|
+
nativeVerification.add(request);
|
|
12057
|
+
return;
|
|
12058
|
+
}
|
|
12059
|
+
request.log.warn({
|
|
12060
|
+
stage: "native-control-authorization",
|
|
12061
|
+
outcome: "rejected"
|
|
12062
|
+
}, "Native control authorization failed");
|
|
12063
|
+
throw new AgentServerHttpError(401, "native_token_invalid", "Native authorization required");
|
|
12064
|
+
}
|
|
12024
12065
|
app.register(rateLimit, {
|
|
12025
12066
|
global: true,
|
|
12026
12067
|
max: options.rateLimitMax ?? RATE_LIMIT_MAX,
|
|
@@ -12032,15 +12073,15 @@ function buildAgentServer(options) {
|
|
|
12032
12073
|
const presented = request.headers[AGENT_SERVER_TOKEN_HEADER];
|
|
12033
12074
|
let authenticated = false;
|
|
12034
12075
|
if (typeof presented === "string" && presented.length > 0) try {
|
|
12035
|
-
if (origin === "moltnet-agent-desktop://native")
|
|
12076
|
+
if (origin === "moltnet-agent-desktop://native") authenticated = hasValidNativeGrant(origin, presented);
|
|
12036
12077
|
else {
|
|
12037
12078
|
if (!oauth) return `unauth:${origin}:${request.ip}`;
|
|
12038
12079
|
await verifyBrowser(request, presented);
|
|
12080
|
+
authenticated = true;
|
|
12039
12081
|
}
|
|
12040
|
-
authenticated = true;
|
|
12041
12082
|
} catch (error) {
|
|
12042
12083
|
if (error instanceof AgentServerHttpError && error.statusCode === 429) throw error;
|
|
12043
|
-
if (origin === "moltnet-agent-desktop://native"
|
|
12084
|
+
if (origin === "moltnet-agent-desktop://native") throw error;
|
|
12044
12085
|
}
|
|
12045
12086
|
return authenticated ? `origin:${origin}` : `unauth:${origin}:${request.ip}`;
|
|
12046
12087
|
}
|
|
@@ -12050,7 +12091,7 @@ function buildAgentServer(options) {
|
|
|
12050
12091
|
const origin = requireOriginHeader(request.headers);
|
|
12051
12092
|
const token = request.headers[AGENT_SERVER_TOKEN_HEADER];
|
|
12052
12093
|
if (typeof token !== "string" || token.length === 0) throw new AgentServerHttpError(401, "authorization_required", "Local control token is required");
|
|
12053
|
-
if (origin === "moltnet-agent-desktop://native")
|
|
12094
|
+
if (origin === "moltnet-agent-desktop://native") requireNativeGrant(request);
|
|
12054
12095
|
else try {
|
|
12055
12096
|
if (!oauth) throw new AgentServerHttpError(503, "oauth_unavailable", "Local OAuth is not configured");
|
|
12056
12097
|
const admission = browserVerification.get(request);
|
|
@@ -12079,6 +12120,9 @@ function buildAgentServer(options) {
|
|
|
12079
12120
|
return origin;
|
|
12080
12121
|
};
|
|
12081
12122
|
app.after(() => {
|
|
12123
|
+
if (options.nativeOnly) app.addHook("preParsing", async (request) => {
|
|
12124
|
+
requireNativeGrant(request);
|
|
12125
|
+
});
|
|
12082
12126
|
app.get("/health", { schema: AgentServerRouteSchemas.health }, async () => ({ status: "ok" }));
|
|
12083
12127
|
app.get("/v1/native/connection-settings", { schema: { hide: true } }, async (request) => {
|
|
12084
12128
|
if (await requireAuthorizedOrigin(request) !== "moltnet-agent-desktop://native" || !options.connectionSettings) throw new AgentServerHttpError(403, "native_required", "Native administration required");
|
|
@@ -12657,196 +12701,42 @@ function normalizeAgentServerError(error) {
|
|
|
12657
12701
|
};
|
|
12658
12702
|
}
|
|
12659
12703
|
//#endregion
|
|
12660
|
-
//#region src/lib/agent-server/tls.ts
|
|
12661
|
-
var execFileAsync = promisify(execFile);
|
|
12662
|
-
var CA_COMMON_NAME = "MoltNet Local Agent CA";
|
|
12663
|
-
var LEAF_COMMON_NAME = "MoltNet Local Agent";
|
|
12664
|
-
var RENEW_BEFORE_MS = 720 * 60 * 60 * 1e3;
|
|
12665
|
-
function loginKeychainPath() {
|
|
12666
|
-
return join(homedir(), "Library", "Keychains", "login.keychain-db");
|
|
12667
|
-
}
|
|
12668
|
-
function pemPrivateKey(key) {
|
|
12669
|
-
return webcrypto.subtle.exportKey("pkcs8", key).then((der) => createPrivateKey({
|
|
12670
|
-
key: Buffer.from(der),
|
|
12671
|
-
format: "der",
|
|
12672
|
-
type: "pkcs8"
|
|
12673
|
-
}).export({
|
|
12674
|
-
format: "pem",
|
|
12675
|
-
type: "pkcs8"
|
|
12676
|
-
}).toString());
|
|
12677
|
-
}
|
|
12678
|
-
async function localTlsMaterialFromDirectory(dir) {
|
|
12679
|
-
try {
|
|
12680
|
-
const [key, cert, ca] = await Promise.all([
|
|
12681
|
-
readFile(join(dir, "loopback-key.pem"), "utf8"),
|
|
12682
|
-
readFile(join(dir, "loopback-cert.pem"), "utf8"),
|
|
12683
|
-
readFile(join(dir, "local-ca.pem"), "utf8")
|
|
12684
|
-
]);
|
|
12685
|
-
const parsed = new X509Certificate(cert);
|
|
12686
|
-
const authority = new X509Certificate(ca);
|
|
12687
|
-
if (Date.parse(parsed.validTo) - Date.now() > RENEW_BEFORE_MS && Date.parse(authority.validTo) - Date.now() > RENEW_BEFORE_MS && authority.keyUsage?.includes("1.3.6.1.5.5.7.3.1") && parsed.checkIP("127.0.0.1") === "127.0.0.1" && parsed.verify(authority.publicKey) && parsed.publicKey.equals(createPublicKey(key))) return {
|
|
12688
|
-
key,
|
|
12689
|
-
cert,
|
|
12690
|
-
ca,
|
|
12691
|
-
fingerprint: new X509Certificate(ca).fingerprint256
|
|
12692
|
-
};
|
|
12693
|
-
} catch {}
|
|
12694
|
-
return null;
|
|
12695
|
-
}
|
|
12696
|
-
/** Read-only inspection for native status polling. */
|
|
12697
|
-
async function inspectLocalTlsMaterial(root) {
|
|
12698
|
-
if (await hasStoredSigningKey(join(root, "tls"))) return null;
|
|
12699
|
-
return localTlsMaterialFromDirectory(join(root, "tls"));
|
|
12700
|
-
}
|
|
12701
|
-
async function hasStoredSigningKey(dir) {
|
|
12702
|
-
return stat(join(dir, "local-ca-key.pem")).then(() => true, (error) => {
|
|
12703
|
-
if (error.code === "ENOENT") return false;
|
|
12704
|
-
throw error;
|
|
12705
|
-
});
|
|
12706
|
-
}
|
|
12707
|
-
/** Creates a per-user CA and loopback-only leaf certificate under a 0700 directory. */
|
|
12708
|
-
async function ensureLocalTlsMaterial(root) {
|
|
12709
|
-
const dir = join(root, "tls");
|
|
12710
|
-
await mkdir(dir, {
|
|
12711
|
-
recursive: true,
|
|
12712
|
-
mode: 448
|
|
12713
|
-
});
|
|
12714
|
-
const previousSigningKey = join(dir, "local-ca-key.pem");
|
|
12715
|
-
const hasPreviousSigningKey = await hasStoredSigningKey(dir);
|
|
12716
|
-
const existing = await localTlsMaterialFromDirectory(dir);
|
|
12717
|
-
if (existing && !hasPreviousSigningKey) return existing;
|
|
12718
|
-
if (isMacos() && await isLocalCaTrusted(root)) await removeLocalCa(root);
|
|
12719
|
-
const caKeys = await webcrypto.subtle.generateKey({
|
|
12720
|
-
name: "ECDSA",
|
|
12721
|
-
namedCurve: "P-256"
|
|
12722
|
-
}, false, ["sign", "verify"]);
|
|
12723
|
-
const ca = (await X509CertificateGenerator.createSelfSigned({
|
|
12724
|
-
name: `CN=${CA_COMMON_NAME}`,
|
|
12725
|
-
keys: caKeys,
|
|
12726
|
-
notAfter: new Date(Date.now() + 366 * 24 * 60 * 60 * 1e3),
|
|
12727
|
-
extensions: [
|
|
12728
|
-
new BasicConstraintsExtension(true, 0, true),
|
|
12729
|
-
new KeyUsagesExtension(KeyUsageFlags.keyCertSign, true),
|
|
12730
|
-
new ExtendedKeyUsageExtension([ExtendedKeyUsage.serverAuth], true)
|
|
12731
|
-
]
|
|
12732
|
-
})).toString("pem");
|
|
12733
|
-
const leafKeys = await webcrypto.subtle.generateKey({
|
|
12734
|
-
name: "ECDSA",
|
|
12735
|
-
namedCurve: "P-256"
|
|
12736
|
-
}, true, ["sign", "verify"]);
|
|
12737
|
-
const leafCert = await X509CertificateGenerator.create({
|
|
12738
|
-
subject: `CN=${LEAF_COMMON_NAME}`,
|
|
12739
|
-
issuer: `CN=${CA_COMMON_NAME}`,
|
|
12740
|
-
publicKey: leafKeys.publicKey,
|
|
12741
|
-
signingKey: caKeys.privateKey,
|
|
12742
|
-
notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3),
|
|
12743
|
-
extensions: [
|
|
12744
|
-
new BasicConstraintsExtension(false, void 0, true),
|
|
12745
|
-
new KeyUsagesExtension(KeyUsageFlags.digitalSignature, true),
|
|
12746
|
-
new ExtendedKeyUsageExtension([ExtendedKeyUsage.serverAuth]),
|
|
12747
|
-
new SubjectAlternativeNameExtension([{
|
|
12748
|
-
type: IP,
|
|
12749
|
-
value: "127.0.0.1"
|
|
12750
|
-
}], true)
|
|
12751
|
-
]
|
|
12752
|
-
});
|
|
12753
|
-
const key = await pemPrivateKey(leafKeys.privateKey);
|
|
12754
|
-
const cert = leafCert.toString("pem");
|
|
12755
|
-
const writes = [writeFile(join(dir, "loopback-key.pem"), key, { mode: 384 }), writeFile(join(dir, "loopback-cert.pem"), cert, { mode: 384 })];
|
|
12756
|
-
writes.push(writeFile(join(dir, "local-ca.pem"), ca, { mode: 384 }));
|
|
12757
|
-
await Promise.all(writes);
|
|
12758
|
-
await rm(previousSigningKey, { force: true });
|
|
12759
|
-
return {
|
|
12760
|
-
key,
|
|
12761
|
-
cert,
|
|
12762
|
-
ca,
|
|
12763
|
-
fingerprint: new X509Certificate(ca).fingerprint256
|
|
12764
|
-
};
|
|
12765
|
-
}
|
|
12766
|
-
async function trustLocalCa(root) {
|
|
12767
|
-
const caPath = join(root, "tls", "local-ca.pem");
|
|
12768
|
-
await execFileAsync("/usr/bin/security", [
|
|
12769
|
-
"add-trusted-cert",
|
|
12770
|
-
"-r",
|
|
12771
|
-
"trustRoot",
|
|
12772
|
-
"-p",
|
|
12773
|
-
"ssl",
|
|
12774
|
-
"-s",
|
|
12775
|
-
"127.0.0.1",
|
|
12776
|
-
"-k",
|
|
12777
|
-
loginKeychainPath(),
|
|
12778
|
-
caPath
|
|
12779
|
-
]);
|
|
12780
|
-
}
|
|
12781
|
-
async function isLocalCaTrusted(root) {
|
|
12782
|
-
try {
|
|
12783
|
-
const ca = await readFile(join(root, "tls", "local-ca.pem"), "utf8");
|
|
12784
|
-
const { stdout } = await execFileAsync("/usr/bin/security", [
|
|
12785
|
-
"find-certificate",
|
|
12786
|
-
"-a",
|
|
12787
|
-
"-p",
|
|
12788
|
-
"-c",
|
|
12789
|
-
CA_COMMON_NAME,
|
|
12790
|
-
loginKeychainPath()
|
|
12791
|
-
]);
|
|
12792
|
-
return stdout.includes(ca.trim());
|
|
12793
|
-
} catch (error) {
|
|
12794
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return false;
|
|
12795
|
-
const stderr = error && typeof error === "object" && "stderr" in error ? error.stderr : void 0;
|
|
12796
|
-
if (typeof stderr === "string" && stderr.trim() === "security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.") return false;
|
|
12797
|
-
throw error;
|
|
12798
|
-
}
|
|
12799
|
-
}
|
|
12800
|
-
async function removeLocalCa(root) {
|
|
12801
|
-
const fingerprint = new X509Certificate(await readFile(join(root, "tls", "local-ca.pem"), "utf8")).fingerprint256.replaceAll(":", "");
|
|
12802
|
-
const caPath = join(root, "tls", "local-ca.pem");
|
|
12803
|
-
await removeTrustSettings([
|
|
12804
|
-
"remove-trusted-cert",
|
|
12805
|
-
"-d",
|
|
12806
|
-
caPath
|
|
12807
|
-
]);
|
|
12808
|
-
await removeTrustSettings(["remove-trusted-cert", caPath]);
|
|
12809
|
-
await execFileAsync("/usr/bin/security", [
|
|
12810
|
-
"delete-certificate",
|
|
12811
|
-
"-Z",
|
|
12812
|
-
fingerprint,
|
|
12813
|
-
loginKeychainPath()
|
|
12814
|
-
]);
|
|
12815
|
-
}
|
|
12816
|
-
function isMacos() {
|
|
12817
|
-
return process.platform === "darwin";
|
|
12818
|
-
}
|
|
12819
|
-
async function removeTrustSettings(args) {
|
|
12820
|
-
try {
|
|
12821
|
-
await execFileAsync("/usr/bin/security", args, { env: { LC_ALL: "C" } });
|
|
12822
|
-
} catch (error) {
|
|
12823
|
-
const stderr = error && typeof error === "object" && "stderr" in error ? error.stderr : void 0;
|
|
12824
|
-
if (typeof stderr === "string" && ["SecTrustSettingsRemoveTrustSettings: No Trust Settings were found.", "SecTrustSettingsRemoveTrustSettings: The specified item could not be found in the keychain."].includes(stderr.trim())) return;
|
|
12825
|
-
throw error;
|
|
12826
|
-
}
|
|
12827
|
-
}
|
|
12828
|
-
//#endregion
|
|
12829
12704
|
//#region src/cli/server.ts
|
|
12830
12705
|
/**
|
|
12831
12706
|
* `moltnet-agent server` — per-user loopback supervisor (#2061).
|
|
12832
12707
|
*
|
|
12833
|
-
* Starts nothing on its own: it binds 127.0.0.1
|
|
12834
|
-
*
|
|
12708
|
+
* Starts nothing on its own: it binds 127.0.0.1 or a private native socket and
|
|
12709
|
+
* waits for an authorized controller to configure agents/providers and
|
|
12710
|
+
* start or stop runs.
|
|
12835
12711
|
*/
|
|
12836
12712
|
var DEFAULT_PORT = OPERATOR_OAUTH.serverPort;
|
|
12837
12713
|
var DEFAULT_ALLOWED_ORIGINS = "https://console.themolt.net";
|
|
12838
12714
|
var SHUTDOWN_TIMEOUT_MS = 15e3;
|
|
12715
|
+
var LOCK_HELD_EXIT_CODE = 75;
|
|
12716
|
+
function agentServerLockExitCode(error) {
|
|
12717
|
+
return error.code === "held" ? LOCK_HELD_EXIT_CODE : 1;
|
|
12718
|
+
}
|
|
12719
|
+
function validateNativeSocketOptions(options) {
|
|
12720
|
+
if (!options.nativeSocket) return void 0;
|
|
12721
|
+
if (!options.supervised) return "--native-socket requires --supervised";
|
|
12722
|
+
if (options.port || options.allowedOrigins) return "--native-socket cannot be combined with TCP options";
|
|
12723
|
+
}
|
|
12724
|
+
function nativeSocketValidationOptions(input) {
|
|
12725
|
+
return {
|
|
12726
|
+
...input.nativeSocket ? { nativeSocket: input.nativeSocket } : {},
|
|
12727
|
+
...input.supervised ? { supervised: true } : {},
|
|
12728
|
+
...input.cliPort ? { port: input.cliPort } : {},
|
|
12729
|
+
...input.cliAllowedOrigins ? { allowedOrigins: input.cliAllowedOrigins } : {}
|
|
12730
|
+
};
|
|
12731
|
+
}
|
|
12839
12732
|
async function runAgentServer(argv) {
|
|
12840
12733
|
if (isHelpFlag(argv)) {
|
|
12841
12734
|
console.log(AGENT_SERVER_HELP);
|
|
12842
12735
|
return 0;
|
|
12843
12736
|
}
|
|
12844
|
-
const trustRequested = argv[0] === "trust";
|
|
12845
|
-
const commandArgs = trustRequested ? argv.slice(1) : argv;
|
|
12846
12737
|
const envConfig = loadAgentServerEnvConfig();
|
|
12847
|
-
if (trustRequested) return runTrustCommand(commandArgs, resolveAgentServerRoot({ root: envConfig.root }));
|
|
12848
12738
|
const { values } = parseArgs({
|
|
12849
|
-
args:
|
|
12739
|
+
args: argv,
|
|
12850
12740
|
options: {
|
|
12851
12741
|
port: { type: "string" },
|
|
12852
12742
|
"allowed-origins": { type: "string" },
|
|
@@ -12854,17 +12744,30 @@ async function runAgentServer(argv) {
|
|
|
12854
12744
|
"api-url": { type: "string" },
|
|
12855
12745
|
"heartbeat-interval-ms": { type: "string" },
|
|
12856
12746
|
"warm-retention-sec": { type: "string" },
|
|
12857
|
-
supervised: { type: "boolean" }
|
|
12747
|
+
supervised: { type: "boolean" },
|
|
12748
|
+
"native-socket": { type: "string" }
|
|
12858
12749
|
}
|
|
12859
12750
|
});
|
|
12751
|
+
const nativeSocket = values["native-socket"];
|
|
12752
|
+
const nativeSocketError = validateNativeSocketOptions(nativeSocketValidationOptions({
|
|
12753
|
+
...nativeSocket ? { nativeSocket } : {},
|
|
12754
|
+
...values.supervised ? { supervised: true } : {},
|
|
12755
|
+
...values.port ? { cliPort: values.port } : {},
|
|
12756
|
+
...values["allowed-origins"] ? { cliAllowedOrigins: values["allowed-origins"] } : {},
|
|
12757
|
+
...envConfig.port ? { envPort: envConfig.port } : {},
|
|
12758
|
+
...envConfig.allowedOrigins ? { envAllowedOrigins: envConfig.allowedOrigins } : {}
|
|
12759
|
+
}));
|
|
12760
|
+
if (nativeSocketError) {
|
|
12761
|
+
console.error(nativeSocketError);
|
|
12762
|
+
return 1;
|
|
12763
|
+
}
|
|
12860
12764
|
const port = Number.parseInt(values.port ?? (envConfig.port || `${DEFAULT_PORT}`), 10);
|
|
12861
12765
|
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
12862
12766
|
console.error(`Invalid --port: ${String(values.port)}`);
|
|
12863
12767
|
return 1;
|
|
12864
12768
|
}
|
|
12865
12769
|
const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
|
|
12866
|
-
const
|
|
12867
|
-
const connectionSettings = new ConnectionSettingsStore(settingsRoot, {
|
|
12770
|
+
const connectionSettings = new ConnectionSettingsStore(values.root ?? resolveAgentServerRoot({ root: envConfig.root }), {
|
|
12868
12771
|
...envConfig.operatorOAuth,
|
|
12869
12772
|
...values["api-url"] || envConfig.apiUrl ? { apiUrl: values["api-url"] || envConfig.apiUrl } : {}
|
|
12870
12773
|
});
|
|
@@ -12892,7 +12795,7 @@ async function runAgentServer(argv) {
|
|
|
12892
12795
|
env: processEnvSnapshot()
|
|
12893
12796
|
});
|
|
12894
12797
|
if (Boolean(values.supervised) && !nativeClient) {
|
|
12895
|
-
console.error(`A supervised Agent Server requires ${NATIVE_TOKEN_ENV}. Start it from MoltNet Agent, or omit --supervised
|
|
12798
|
+
console.error(`A supervised Agent Server requires ${NATIVE_TOKEN_ENV}. Start it from MoltNet Agent, or omit --supervised for an authorized standalone controller.`);
|
|
12896
12799
|
return 1;
|
|
12897
12800
|
}
|
|
12898
12801
|
const shutdownController = new AbortController();
|
|
@@ -12916,8 +12819,8 @@ async function runAgentServer(argv) {
|
|
|
12916
12819
|
runtimeRegistry,
|
|
12917
12820
|
runtimeSettings
|
|
12918
12821
|
});
|
|
12919
|
-
|
|
12920
|
-
const selfOrigin =
|
|
12822
|
+
if (nativeSocket) await validateNativeSocket(nativeSocket);
|
|
12823
|
+
const selfOrigin = nativeSocket ? void 0 : `http://127.0.0.1:${port}`;
|
|
12921
12824
|
const app = buildAgentServer({
|
|
12922
12825
|
operatorOAuth: new OperatorOAuth({
|
|
12923
12826
|
issuer: connection.issuer,
|
|
@@ -12928,6 +12831,7 @@ async function runAgentServer(argv) {
|
|
|
12928
12831
|
consoleClientId: connection.consoleClientId,
|
|
12929
12832
|
callbackPort: OPERATOR_OAUTH.callbackPort
|
|
12930
12833
|
}, root),
|
|
12834
|
+
nativeOnly: Boolean(nativeSocket),
|
|
12931
12835
|
connectionSettings,
|
|
12932
12836
|
operatorApiUrl: connection.apiUrl,
|
|
12933
12837
|
store,
|
|
@@ -12939,12 +12843,8 @@ async function runAgentServer(argv) {
|
|
|
12939
12843
|
subscriptions,
|
|
12940
12844
|
providers,
|
|
12941
12845
|
runtimeRegistry,
|
|
12942
|
-
allowedOrigins,
|
|
12943
|
-
selfOrigin,
|
|
12944
|
-
...tls ? { tls: {
|
|
12945
|
-
key: tls.key,
|
|
12946
|
-
cert: tls.cert
|
|
12947
|
-
} } : {},
|
|
12846
|
+
allowedOrigins: nativeSocket ? [] : allowedOrigins,
|
|
12847
|
+
...selfOrigin ? { selfOrigin } : {},
|
|
12948
12848
|
defaultApiUrl,
|
|
12949
12849
|
runtimeSettings,
|
|
12950
12850
|
...envConfig.activeIdentity ? { activeIdentity: envConfig.activeIdentity } : {},
|
|
@@ -12953,15 +12853,17 @@ async function runAgentServer(argv) {
|
|
|
12953
12853
|
shutdownSignal: shutdownController.signal
|
|
12954
12854
|
});
|
|
12955
12855
|
try {
|
|
12956
|
-
const address = await app.listen({
|
|
12856
|
+
const address = await app.listen(nativeSocket ? { path: nativeSocket } : {
|
|
12957
12857
|
host: "127.0.0.1",
|
|
12958
12858
|
port
|
|
12959
12859
|
});
|
|
12860
|
+
if (nativeSocket) await chmod(nativeSocket, 384);
|
|
12960
12861
|
console.error(`moltnet-agent server listening on ${address}`);
|
|
12961
12862
|
console.error(`config root: ${root}`);
|
|
12962
|
-
console.error(`
|
|
12863
|
+
if (nativeSocket) console.error(`native control socket: ${nativeSocket}`);
|
|
12864
|
+
else console.error(`allowed origins: ${allowedOrigins.join(", ")}`);
|
|
12963
12865
|
if (nativeClient) console.error("native desktop client: authorized");
|
|
12964
|
-
console.error("
|
|
12866
|
+
if (!nativeSocket) console.error("Connect from an allowed local-control client after operator authorization.");
|
|
12965
12867
|
return await waitForAgentServerShutdown(runs, app, shutdownController, Boolean(values.supervised));
|
|
12966
12868
|
} catch (cause) {
|
|
12967
12869
|
await app.close().catch(() => void 0);
|
|
@@ -12975,7 +12877,7 @@ async function runAgentServer(argv) {
|
|
|
12975
12877
|
} catch (cause) {
|
|
12976
12878
|
if (cause instanceof AgentServerLockError) {
|
|
12977
12879
|
console.error(cause.message);
|
|
12978
|
-
return
|
|
12880
|
+
return agentServerLockExitCode(cause);
|
|
12979
12881
|
}
|
|
12980
12882
|
throw cause;
|
|
12981
12883
|
}
|
|
@@ -12983,99 +12885,6 @@ async function runAgentServer(argv) {
|
|
|
12983
12885
|
await shutdownLogger();
|
|
12984
12886
|
}
|
|
12985
12887
|
}
|
|
12986
|
-
async function runTrustCommand(argv, defaultRoot) {
|
|
12987
|
-
try {
|
|
12988
|
-
const { values } = parseArgs({
|
|
12989
|
-
args: argv,
|
|
12990
|
-
options: {
|
|
12991
|
-
root: { type: "string" },
|
|
12992
|
-
remove: { type: "boolean" },
|
|
12993
|
-
status: { type: "boolean" },
|
|
12994
|
-
yes: { type: "boolean" },
|
|
12995
|
-
json: { type: "boolean" }
|
|
12996
|
-
}
|
|
12997
|
-
});
|
|
12998
|
-
const root = values.root ?? defaultRoot;
|
|
12999
|
-
const statusRequested = Boolean(values.status);
|
|
13000
|
-
const removeRequested = Boolean(values.remove);
|
|
13001
|
-
const yes = Boolean(values.yes);
|
|
13002
|
-
const json = Boolean(values.json);
|
|
13003
|
-
if (statusRequested && (removeRequested || yes)) {
|
|
13004
|
-
console.error("Usage: moltnet-agent server trust --status [--json]");
|
|
13005
|
-
return 1;
|
|
13006
|
-
}
|
|
13007
|
-
if (!isMacos()) {
|
|
13008
|
-
if (json) {
|
|
13009
|
-
printTrustStatus({
|
|
13010
|
-
supported: false,
|
|
13011
|
-
trusted: false,
|
|
13012
|
-
fingerprint: null
|
|
13013
|
-
});
|
|
13014
|
-
return 0;
|
|
13015
|
-
}
|
|
13016
|
-
console.error("Local HTTPS trust setup is currently supported on macOS only.");
|
|
13017
|
-
return 1;
|
|
13018
|
-
}
|
|
13019
|
-
if (statusRequested) {
|
|
13020
|
-
const material = await inspectLocalTlsMaterial(root);
|
|
13021
|
-
const trusted = material !== null && await isLocalCaTrusted(root);
|
|
13022
|
-
if (json) printTrustStatus({
|
|
13023
|
-
supported: true,
|
|
13024
|
-
trusted,
|
|
13025
|
-
fingerprint: material?.fingerprint ?? null
|
|
13026
|
-
});
|
|
13027
|
-
else console.log(trusted ? `MoltNet local CA ${material?.fingerprint ?? "(not prepared)"} is trusted.` : `MoltNet local CA ${material?.fingerprint ?? "(not prepared)"} is not trusted.`);
|
|
13028
|
-
return 0;
|
|
13029
|
-
}
|
|
13030
|
-
if (json && !yes) {
|
|
13031
|
-
console.error("Machine-readable trust changes require --yes after native app consent.");
|
|
13032
|
-
return 1;
|
|
13033
|
-
}
|
|
13034
|
-
if (removeRequested) {
|
|
13035
|
-
await removeLocalCa(root);
|
|
13036
|
-
if (json) printTrustStatus({
|
|
13037
|
-
supported: true,
|
|
13038
|
-
trusted: false,
|
|
13039
|
-
fingerprint: null
|
|
13040
|
-
});
|
|
13041
|
-
else console.log("Removed the MoltNet local CA from your login keychain.");
|
|
13042
|
-
return 0;
|
|
13043
|
-
}
|
|
13044
|
-
const material = await ensureLocalTlsMaterial(root);
|
|
13045
|
-
if (yes) await trustLocalCa(root);
|
|
13046
|
-
else await ensureTrustedLocalTls(root);
|
|
13047
|
-
if (json) printTrustStatus({
|
|
13048
|
-
supported: true,
|
|
13049
|
-
trusted: await isLocalCaTrusted(root),
|
|
13050
|
-
fingerprint: material.fingerprint
|
|
13051
|
-
});
|
|
13052
|
-
else console.log("MoltNet local HTTPS trust is ready for this macOS user.");
|
|
13053
|
-
return 0;
|
|
13054
|
-
} catch (cause) {
|
|
13055
|
-
console.error(`Agent Server trust command failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
13056
|
-
return 1;
|
|
13057
|
-
}
|
|
13058
|
-
}
|
|
13059
|
-
function printTrustStatus(status) {
|
|
13060
|
-
console.log(JSON.stringify(status));
|
|
13061
|
-
}
|
|
13062
|
-
async function ensureTrustedLocalTls(root) {
|
|
13063
|
-
const material = await ensureLocalTlsMaterial(root);
|
|
13064
|
-
if (await isLocalCaTrusted(root)) return material;
|
|
13065
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("Local HTTPS trust is not configured. Run `moltnet-agent server trust` from an interactive terminal.");
|
|
13066
|
-
const prompt = createInterface({
|
|
13067
|
-
input: process.stdin,
|
|
13068
|
-
output: process.stdout
|
|
13069
|
-
});
|
|
13070
|
-
try {
|
|
13071
|
-
const answer = await prompt.question(`Trust MoltNet's local CA (${material.fingerprint}) in this macOS login keychain? [y/N] `);
|
|
13072
|
-
if (!/^y(es)?$/i.test(answer.trim())) throw new Error("Local HTTPS trust was not approved.");
|
|
13073
|
-
} finally {
|
|
13074
|
-
prompt.close();
|
|
13075
|
-
}
|
|
13076
|
-
await trustLocalCa(root);
|
|
13077
|
-
return material;
|
|
13078
|
-
}
|
|
13079
12888
|
function waitForAgentServerShutdown(runs, app, shutdownController, supervised) {
|
|
13080
12889
|
return new Promise((resolvePromise) => {
|
|
13081
12890
|
let shuttingDown = false;
|
|
@@ -13488,7 +13297,7 @@ async function writeCache(cache) {
|
|
|
13488
13297
|
}
|
|
13489
13298
|
//#endregion
|
|
13490
13299
|
//#region src/version.ts
|
|
13491
|
-
var DAEMON_VERSION = "0.
|
|
13300
|
+
var DAEMON_VERSION = "0.62.0";
|
|
13492
13301
|
//#endregion
|
|
13493
13302
|
//#region src/cli.ts
|
|
13494
13303
|
async function runAgentDaemonCli(options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.62.0",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Universal MoltNet agent daemon host with a built-in Pi/Gondolin runtime and support for trusted operator-owned runtime modules. CLI: moltnet-agent.",
|
|
@@ -75,7 +75,6 @@
|
|
|
75
75
|
"@opentelemetry/sdk-trace-base": "^2.5.1",
|
|
76
76
|
"@opentelemetry/sdk-trace-node": "^2.5.1",
|
|
77
77
|
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
78
|
-
"@peculiar/x509": "^2.0.0",
|
|
79
78
|
"fastify": "^5.8.5",
|
|
80
79
|
"fastify-plugin": "^5.1.0",
|
|
81
80
|
"jose": "^6.1.3",
|
|
@@ -84,11 +83,10 @@
|
|
|
84
83
|
"pino-opentelemetry-transport": "^3.0.0",
|
|
85
84
|
"pino-pretty": "^13.1.3",
|
|
86
85
|
"proper-lockfile": "4.1.2",
|
|
87
|
-
"reflect-metadata": "^0.2.2",
|
|
88
86
|
"typebox": "^1.2.8",
|
|
89
87
|
"@themoltnet/agent-runtime": "1.2.0",
|
|
90
|
-
"@themoltnet/os-keyring": "0.3.0",
|
|
91
88
|
"@themoltnet/pi-runtime": "0.18.2",
|
|
89
|
+
"@themoltnet/os-keyring": "0.3.0",
|
|
92
90
|
"@themoltnet/sdk": "0.144.0"
|
|
93
91
|
},
|
|
94
92
|
"devDependencies": {
|
|
@@ -99,15 +97,15 @@
|
|
|
99
97
|
"vite": "^8.0.0",
|
|
100
98
|
"vite-plugin-dts": "^4.5.4",
|
|
101
99
|
"vitest": "^3.0.0",
|
|
102
|
-
"@moltnet/agent-eval": "0.1.0",
|
|
103
|
-
"@moltnet/crypto-service": "0.1.0",
|
|
104
100
|
"@moltnet/api-client": "0.1.0",
|
|
101
|
+
"@moltnet/agent-eval": "0.1.0",
|
|
105
102
|
"@moltnet/bootstrap": "0.1.0",
|
|
103
|
+
"@moltnet/crypto-service": "0.1.0",
|
|
106
104
|
"@moltnet/execution-integrations": "0.1.0",
|
|
107
|
-
"@moltnet/execution-plan": "0.1.0",
|
|
108
|
-
"@moltnet/loopback-companion": "0.1.0",
|
|
109
105
|
"@moltnet/models": "0.1.0",
|
|
110
106
|
"@moltnet/observability": "0.1.0",
|
|
107
|
+
"@moltnet/loopback-companion": "0.1.0",
|
|
108
|
+
"@moltnet/execution-plan": "0.1.0",
|
|
111
109
|
"@moltnet/runtime-profiles": "0.1.0",
|
|
112
110
|
"@moltnet/tasks": "0.1.0"
|
|
113
111
|
},
|