@themoltnet/agent-daemon 0.62.0 → 0.63.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 +27 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +164 -72
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -649,10 +649,36 @@ Server configuration root. **Reset to release defaults** clears those overrides
|
|
|
649
649
|
when applied; it does not delete agent credentials. Launch environment values
|
|
650
650
|
have priority and appear read-only. Operators who explicitly set both
|
|
651
651
|
`MOLTNET_OPERATOR_API_URL` and `MOLTNET_OPERATOR_OAUTH_ISSUER` should also use
|
|
652
|
-
`
|
|
652
|
+
`MOLTNET_HOME` to select a dedicated environment root.
|
|
653
653
|
|
|
654
654
|
Release defaults use `https://api.themolt.net`, `https://auth.themolt.net`, and
|
|
655
655
|
public client IDs `moltnet-native` / `moltnet-console`. Hosted client registration
|
|
656
656
|
and coordinated configuration are tracked in
|
|
657
657
|
[moltnet-operations #8](https://github.com/getlarge/moltnet-operations/issues/8).
|
|
658
658
|
These IDs are public configuration; they do not contain a client secret.
|
|
659
|
+
|
|
660
|
+
### Isolated local development
|
|
661
|
+
|
|
662
|
+
Use the development targets to keep development state and installations in
|
|
663
|
+
separate directories:
|
|
664
|
+
|
|
665
|
+
```bash
|
|
666
|
+
pnpm exec nx run @moltnet/agent-desktop:tauri:dev
|
|
667
|
+
pnpm exec nx run @themoltnet/agent-daemon:dev -- server
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
The launchers choose `~/.local/share/moltnet/development/<worktree-id>/store`
|
|
671
|
+
and the sibling `agent` installation directory. The worktree ID is a stable
|
|
672
|
+
hash of the canonical checkout path. Repeated launches reuse that environment;
|
|
673
|
+
another worktree gets its own environment. Use `MOLTNET_DEV_HOME` and `MOLTNET_DEV_AGENT_HOME` to override these development
|
|
674
|
+
locations. Use absolute overrides: relative paths start at the Nx target’s
|
|
675
|
+
working directory (`apps/agent-daemon` for daemon `dev`, the repository root
|
|
676
|
+
for Desktop `tauri:dev`). All daemon `dev` commands, including `poll`, use
|
|
677
|
+
this isolated store.
|
|
678
|
+
The launchers ignore inherited production store and installation selectors.
|
|
679
|
+
The renderer-only Desktop `dev` target starts Vite without a native server.
|
|
680
|
+
|
|
681
|
+
For direct CLI or SDK development commands, set `MOLTNET_HOME` explicitly. See
|
|
682
|
+
[Store selection and keyring namespaces](../../docs/reference/agent-configuration.md#store-selection-and-keyring-namespaces)
|
|
683
|
+
for precedence, secret namespaces and daemon discovery. Automated tests use fresh
|
|
684
|
+
temporary roots.
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGzD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGzD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+ClB"}
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import "typebox/value";
|
|
|
11
11
|
import { execFile, spawn } from "node:child_process";
|
|
12
12
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
13
13
|
import { parseArgs, parseEnv, promisify } from "node:util";
|
|
14
|
-
import { CredentialPersistenceError, EnrollmentRecoveryError, FILE_SECRET_PROVIDER, FileSecretProvider, ProjectConfigError, ProvisioningNotStartedError, RegisterIdentityError, WORKSPACE_STRATEGIES, boundedIdentitySignal, canonicalDirectory, connect, createNodeSecretProviderRegistry, enrollTeam, getProjectConfigPath, readProjectConfig, register, resolveProjectBinding } from "@themoltnet/sdk/node";
|
|
14
|
+
import { CredentialPersistenceError, EnrollmentRecoveryError, FILE_SECRET_PROVIDER, FileSecretProvider, ProjectConfigError, ProvisioningNotStartedError, RegisterIdentityError, WORKSPACE_STRATEGIES, boundedIdentitySignal, canonicalDirectory, canonicalStoreRoot, connect, createNodeSecretProviderRegistry, defaultStoreRoot, enrollTeam, getProjectConfigPath, isDefaultStore, readProjectConfig, register, resolveProjectBinding, resolveStoreRoot, resolveStoreSelection } from "@themoltnet/sdk/node";
|
|
15
15
|
import { constants, copyFileSync, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
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";
|
|
@@ -3757,6 +3757,10 @@ async function resolveSelectionApiUrl(agentName, options) {
|
|
|
3757
3757
|
* here, so the rest of the daemon imports typed values rather than
|
|
3758
3758
|
* sprinkling string lookups across the codebase.
|
|
3759
3759
|
*/
|
|
3760
|
+
/** All daemon commands announce the same legacy alias policy as Go and Desktop. */
|
|
3761
|
+
function legacyStoreNotice() {
|
|
3762
|
+
return process.env["MOLTNET_AGENT_SERVER_ROOT"] !== void 0 ? "MOLTNET_AGENT_SERVER_ROOT is deprecated; use MOLTNET_HOME. Both select the entire store; no data is migrated." : void 0;
|
|
3763
|
+
}
|
|
3760
3764
|
function loadConfig() {
|
|
3761
3765
|
assertSingleCredentialForm("MOLTNET_AGENT_KEY", "MOLTNET_AGENT_KEY_REF");
|
|
3762
3766
|
assertSingleCredentialForm("MOLTNET_PRIVATE_KEY", "MOLTNET_PRIVATE_KEY_REF");
|
|
@@ -3767,7 +3771,7 @@ function loadConfig() {
|
|
|
3767
3771
|
profilePrerequisiteEnv: process.env,
|
|
3768
3772
|
profilePrerequisitePath: process.env.PATH ?? "",
|
|
3769
3773
|
piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
|
|
3770
|
-
agentServerRoot:
|
|
3774
|
+
agentServerRoot: resolveStoreRoot(),
|
|
3771
3775
|
credentialSource: detectCredentialSource(process.env),
|
|
3772
3776
|
apiUrl: process.env["MOLTNET_API_URL"] ?? "",
|
|
3773
3777
|
signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
|
|
@@ -3815,7 +3819,8 @@ function activatePiCodingAgentDir(path, env = {}) {
|
|
|
3815
3819
|
process.env["PI_CODING_AGENT_DIR"] = path;
|
|
3816
3820
|
Object.assign(process.env, env);
|
|
3817
3821
|
}
|
|
3818
|
-
function loadAgentServerEnvConfig() {
|
|
3822
|
+
function loadAgentServerEnvConfig(root) {
|
|
3823
|
+
const selection = resolveStoreSelection({ root });
|
|
3819
3824
|
const issuer = process.env["MOLTNET_OPERATOR_OAUTH_ISSUER"];
|
|
3820
3825
|
const publicUrl = process.env["MOLTNET_OPERATOR_OAUTH_PUBLIC_URL"] ?? issuer;
|
|
3821
3826
|
const nativeClientId = process.env["MOLTNET_NATIVE_OAUTH_CLIENT_ID"];
|
|
@@ -3831,7 +3836,8 @@ function loadAgentServerEnvConfig() {
|
|
|
3831
3836
|
},
|
|
3832
3837
|
port: process.env["MOLTNET_AGENT_SERVER_PORT"] ?? "",
|
|
3833
3838
|
allowedOrigins: process.env["MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS"] ?? "",
|
|
3834
|
-
root:
|
|
3839
|
+
root: selection.root,
|
|
3840
|
+
rootSource: selection.source,
|
|
3835
3841
|
apiUrl: process.env["MOLTNET_API_URL"] ?? "",
|
|
3836
3842
|
logLevel: process.env["LOG_LEVEL"] ?? "",
|
|
3837
3843
|
activeIdentity: process.env["MOLTNET_ACTIVE_IDENTITY"] ?? ""
|
|
@@ -3841,8 +3847,9 @@ function loadAgentServerEnvConfig() {
|
|
|
3841
3847
|
function processEnvSnapshot() {
|
|
3842
3848
|
return process.env;
|
|
3843
3849
|
}
|
|
3844
|
-
function loadUpdateEnvConfig() {
|
|
3850
|
+
function loadUpdateEnvConfig(root) {
|
|
3845
3851
|
return {
|
|
3852
|
+
...!isDefaultStore({ root }) ? { storeRoot: resolveStoreRoot({ root }) } : {},
|
|
3846
3853
|
xdgCacheHome: process.env["XDG_CACHE_HOME"] ?? "",
|
|
3847
3854
|
localAppData: process.env["LOCALAPPDATA"] ?? ""
|
|
3848
3855
|
};
|
|
@@ -4172,14 +4179,14 @@ Agent Desktop instead uses a private Unix socket with a process-scoped grant.
|
|
|
4172
4179
|
Both modes configure agents and providers and start/stop child runs.
|
|
4173
4180
|
|
|
4174
4181
|
Options:
|
|
4175
|
-
--port <n> Loopback port. Default: 17374.
|
|
4182
|
+
--port <n> Loopback port. Default store: 17374; isolated stores: an available port.
|
|
4176
4183
|
Env: MOLTNET_AGENT_SERVER_PORT.
|
|
4177
4184
|
--allowed-origins <csv> Exact browser-controller origins allowed
|
|
4178
4185
|
local control.
|
|
4179
4186
|
Default: https://console.themolt.net.
|
|
4180
4187
|
Env: MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS.
|
|
4181
4188
|
--root <path> Config root. Default: ~/.config/moltnet
|
|
4182
|
-
(or MOLTNET_AGENT_SERVER_ROOT).
|
|
4189
|
+
(or MOLTNET_HOME; legacy MOLTNET_AGENT_SERVER_ROOT).
|
|
4183
4190
|
--api-url <url> Default MoltNet API for new managed agents.
|
|
4184
4191
|
Default: https://api.themolt.net.
|
|
4185
4192
|
--heartbeat-interval-ms <n> Child reporter heartbeat cadence. Default: 60000.
|
|
@@ -4208,8 +4215,8 @@ Usage:
|
|
|
4208
4215
|
[--root <path>]
|
|
4209
4216
|
moltnet-agent providers logout <id> [--yes] [--root <path>]
|
|
4210
4217
|
|
|
4211
|
-
The default root is ~/.config/moltnet.
|
|
4212
|
-
|
|
4218
|
+
The default root is ~/.config/moltnet. MOLTNET_HOME selects another store.
|
|
4219
|
+
MOLTNET_AGENT_SERVER_ROOT is a legacy alias; conflicting values fail. API keys are accepted only from redirected stdin; they
|
|
4213
4220
|
are stored separately and providers.json contains only a secret reference.
|
|
4214
4221
|
|
|
4215
4222
|
--model declares a text-only model. --model-input declares a model together
|
|
@@ -5655,20 +5662,9 @@ var AgentServerStoreError = class extends Error {
|
|
|
5655
5662
|
this.code = code;
|
|
5656
5663
|
}
|
|
5657
5664
|
};
|
|
5658
|
-
/**
|
|
5659
|
-
* `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
|
|
5660
|
-
*
|
|
5661
|
-
* Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
|
|
5662
|
-
* and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
|
|
5663
|
-
* so honouring XDG here gave one application two config roots: on a machine
|
|
5664
|
-
* with the variable set, the daemon wrote identities the CLI and SDK could not
|
|
5665
|
-
* read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
|
|
5666
|
-
* genuinely custom location.
|
|
5667
|
-
*/
|
|
5665
|
+
/** Explicit root, MOLTNET_HOME, legacy alias, then the unchanged default store. */
|
|
5668
5666
|
function resolveAgentServerRoot(input) {
|
|
5669
|
-
|
|
5670
|
-
if (override) return override;
|
|
5671
|
-
return getConfigDir();
|
|
5667
|
+
return getConfigDir(input);
|
|
5672
5668
|
}
|
|
5673
5669
|
/**
|
|
5674
5670
|
* Copy a model entry, dropping an empty `input` so it never reaches the wire.
|
|
@@ -6102,7 +6098,7 @@ async function resolvePiAgentDir(cfg, agentRoot, profiles, options = {}) {
|
|
|
6102
6098
|
};
|
|
6103
6099
|
}
|
|
6104
6100
|
const repoPiDir = join(agentRoot, ".pi");
|
|
6105
|
-
const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot }));
|
|
6101
|
+
const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot || void 0 }));
|
|
6106
6102
|
const providers = store.readProviders();
|
|
6107
6103
|
const storeHasAuth = existsSync(store.piAuthJsonPath);
|
|
6108
6104
|
if (Object.keys(providers).length === 0 && !storeHasAuth) {
|
|
@@ -6183,7 +6179,7 @@ function mergePiModels(store, repo) {
|
|
|
6183
6179
|
//#endregion
|
|
6184
6180
|
//#region src/lib/state-dir.ts
|
|
6185
6181
|
function ensureDaemonStateDirs(mountPath) {
|
|
6186
|
-
const rootDir =
|
|
6182
|
+
const rootDir = canonicalStoreRoot(join(mountPath, ".moltnet", "d"));
|
|
6187
6183
|
const piSessionsDir = join(rootDir, "pi-sessions");
|
|
6188
6184
|
mkdirSync(rootDir, {
|
|
6189
6185
|
recursive: true,
|
|
@@ -6198,17 +6194,6 @@ function ensureDaemonStateDirs(mountPath) {
|
|
|
6198
6194
|
piSessionsDir: realpathSync(piSessionsDir)
|
|
6199
6195
|
};
|
|
6200
6196
|
}
|
|
6201
|
-
function canonicalStatePath(path) {
|
|
6202
|
-
let parent = resolve(path);
|
|
6203
|
-
const missing = [];
|
|
6204
|
-
while (!existsSync(parent)) {
|
|
6205
|
-
missing.unshift(basename(parent));
|
|
6206
|
-
const next = dirname(parent);
|
|
6207
|
-
if (next === parent) throw new Error(`Cannot resolve daemon state root ${path}`);
|
|
6208
|
-
parent = next;
|
|
6209
|
-
}
|
|
6210
|
-
return join(realpathSync(parent), ...missing);
|
|
6211
|
-
}
|
|
6212
6197
|
//#endregion
|
|
6213
6198
|
//#region src/lib/prepare-runtime-profile.ts
|
|
6214
6199
|
/** Validate and prepare a profile through the shared daemon execution path. */
|
|
@@ -8865,12 +8850,13 @@ function parseProviderArgs(command, args) {
|
|
|
8865
8850
|
}
|
|
8866
8851
|
}
|
|
8867
8852
|
async function createContext(rootFlag, dependencies, installInterruptHandler) {
|
|
8868
|
-
const
|
|
8853
|
+
const root = resolveAgentServerRoot({ root: rootFlag ?? dependencies.envRoot ?? loadAgentServerEnvConfig().root });
|
|
8854
|
+
const store = new AgentServerStore(root).ensure();
|
|
8869
8855
|
const secrets = new FileSecretProvider({
|
|
8870
8856
|
root: store.secretsDir,
|
|
8871
8857
|
writable: true
|
|
8872
8858
|
});
|
|
8873
|
-
const secretProviders = createNodeSecretProviderRegistry().register(secrets);
|
|
8859
|
+
const secretProviders = createNodeSecretProviderRegistry({ store: { root } }).register(secrets);
|
|
8874
8860
|
const stderr = dependencies.stderr ?? console.error;
|
|
8875
8861
|
const logger = createCliLogger(stderr);
|
|
8876
8862
|
const configuration = dependencies.configuration ?? new ProviderConfigurationService({
|
|
@@ -9358,11 +9344,67 @@ var ConnectionSettingsStore = class {
|
|
|
9358
9344
|
};
|
|
9359
9345
|
/** A custom service gets its own identities, keys and runtime configuration. */
|
|
9360
9346
|
function connectionStateRoot(root, settings) {
|
|
9361
|
-
const identity = [settings.apiUrl.replace(
|
|
9347
|
+
const identity = [settings.apiUrl.replace(/\/+$/u, ""), settings.issuer.replace(/\/+$/u, "")];
|
|
9362
9348
|
if (identity[0] === RELEASE_CONNECTION.apiUrl && identity[1] === RELEASE_CONNECTION.issuer) return root;
|
|
9363
9349
|
return join(root, "environments", createHash("sha256").update(JSON.stringify(identity)).digest("hex"));
|
|
9364
9350
|
}
|
|
9365
9351
|
//#endregion
|
|
9352
|
+
//#region src/lib/agent-server/endpoint.ts
|
|
9353
|
+
function defaultAgentServerPort(root) {
|
|
9354
|
+
return isDefaultStore({ root }) ? OPERATOR_OAUTH.serverPort : 0;
|
|
9355
|
+
}
|
|
9356
|
+
function endpointPath(root) {
|
|
9357
|
+
return join(root, "agent-server-endpoint.json");
|
|
9358
|
+
}
|
|
9359
|
+
function validateUrl(value) {
|
|
9360
|
+
const url = new URL(value);
|
|
9361
|
+
if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || url.port === "0" || url.username || url.password || url.pathname !== "/" || url.search || url.hash || !/^http:\/\/127\.0\.0\.1(?::[1-9]\d{0,4})?$/.test(value)) throw new Error("Agent Server discovery requires a loopback HTTP origin");
|
|
9362
|
+
}
|
|
9363
|
+
function readAgentServerEndpoint(root) {
|
|
9364
|
+
let raw;
|
|
9365
|
+
try {
|
|
9366
|
+
raw = readFileSync(endpointPath(root), "utf8");
|
|
9367
|
+
} catch (cause) {
|
|
9368
|
+
if (cause.code === "ENOENT") return null;
|
|
9369
|
+
throw cause;
|
|
9370
|
+
}
|
|
9371
|
+
try {
|
|
9372
|
+
const value = JSON.parse(raw);
|
|
9373
|
+
if (value.version !== 1 || typeof value.instanceId !== "string" || value.instanceId.length !== 36 || !/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/iu.test(value.instanceId) || typeof value.url !== "string") throw new Error("Invalid Agent Server discovery metadata");
|
|
9374
|
+
validateUrl(value.url);
|
|
9375
|
+
if (value.pid !== void 0) {
|
|
9376
|
+
if (!Number.isSafeInteger(value.pid) || value.pid <= 0 || value.pid > 2147483647) throw new Error("Invalid process ID");
|
|
9377
|
+
try {
|
|
9378
|
+
process.kill(value.pid, 0);
|
|
9379
|
+
} catch (cause) {
|
|
9380
|
+
if (cause.code === "ESRCH") return null;
|
|
9381
|
+
}
|
|
9382
|
+
}
|
|
9383
|
+
return value;
|
|
9384
|
+
} catch (cause) {
|
|
9385
|
+
throw new Error(`Invalid Agent Server discovery at ${endpointPath(root)}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
9386
|
+
}
|
|
9387
|
+
}
|
|
9388
|
+
/** Publish and release only while holding the selected store's singleton lock. */
|
|
9389
|
+
function publishAgentServerEndpoint(root, url) {
|
|
9390
|
+
validateUrl(url);
|
|
9391
|
+
const endpoint = {
|
|
9392
|
+
version: 1,
|
|
9393
|
+
instanceId: randomUUID(),
|
|
9394
|
+
pid: process.pid,
|
|
9395
|
+
url
|
|
9396
|
+
};
|
|
9397
|
+
writeJsonAtomic(endpointPath(root), endpoint);
|
|
9398
|
+
return {
|
|
9399
|
+
record: endpoint,
|
|
9400
|
+
release() {
|
|
9401
|
+
try {
|
|
9402
|
+
if (readAgentServerEndpoint(root)?.instanceId === endpoint.instanceId) rmSync(endpointPath(root), { force: true });
|
|
9403
|
+
} catch {}
|
|
9404
|
+
}
|
|
9405
|
+
};
|
|
9406
|
+
}
|
|
9407
|
+
//#endregion
|
|
9366
9408
|
//#region src/lib/agent-server/lock.ts
|
|
9367
9409
|
var AgentServerLockError = class extends Error {
|
|
9368
9410
|
name = "AgentServerLockError";
|
|
@@ -9396,7 +9438,14 @@ async function acquireAgentServerLock(root, options = {}) {
|
|
|
9396
9438
|
}
|
|
9397
9439
|
});
|
|
9398
9440
|
} catch (cause) {
|
|
9399
|
-
if (cause.code === "ELOCKED")
|
|
9441
|
+
if (cause.code === "ELOCKED") {
|
|
9442
|
+
let holder = "";
|
|
9443
|
+
try {
|
|
9444
|
+
const endpoint = readAgentServerEndpoint(root);
|
|
9445
|
+
if (endpoint?.pid) holder = ` (PID ${endpoint.pid}, instance ${endpoint.instanceId})`;
|
|
9446
|
+
} catch {}
|
|
9447
|
+
throw new AgentServerLockError("held", `another moltnet-agent server process already owns ${path}${holder}; stop that process before retrying`, { cause });
|
|
9448
|
+
}
|
|
9400
9449
|
throw new AgentServerLockError("failed", `could not acquire Agent Server lock ${path}: ${cause.message}`, { cause });
|
|
9401
9450
|
}
|
|
9402
9451
|
let released = false;
|
|
@@ -9411,11 +9460,15 @@ async function acquireAgentServerLock(root, options = {}) {
|
|
|
9411
9460
|
}
|
|
9412
9461
|
/** Always release after normal completion or startup/runtime failure. */
|
|
9413
9462
|
async function withAgentServerLock(root, work, options) {
|
|
9414
|
-
const held =
|
|
9463
|
+
const held = [];
|
|
9415
9464
|
try {
|
|
9465
|
+
held.push(await acquireAgentServerLock(root, options));
|
|
9466
|
+
if (options?.stateRoot && options.stateRoot !== root) held.push(await acquireAgentServerLock(options.stateRoot, options));
|
|
9416
9467
|
return await work();
|
|
9417
9468
|
} finally {
|
|
9418
|
-
|
|
9469
|
+
for (const entry of held.reverse()) await entry.release().catch((error) => {
|
|
9470
|
+
console.error(`Could not release Agent Server lock ${entry.path}: ${String(error)}`);
|
|
9471
|
+
});
|
|
9419
9472
|
}
|
|
9420
9473
|
}
|
|
9421
9474
|
//#endregion
|
|
@@ -9450,6 +9503,10 @@ function applyNativeClientGrant(options) {
|
|
|
9450
9503
|
nativeGrant.grantNative(token);
|
|
9451
9504
|
return true;
|
|
9452
9505
|
}
|
|
9506
|
+
//#endregion
|
|
9507
|
+
//#region src/lib/agent-server/native-grant-service.ts
|
|
9508
|
+
/** Process-only native grant. Browser authorization belongs to OperatorOAuth. */
|
|
9509
|
+
var NATIVE_CLIENT_ORIGIN = "moltnet-agent-desktop://native";
|
|
9453
9510
|
var NativeGrantError = class extends Error {
|
|
9454
9511
|
code = "native_token_invalid";
|
|
9455
9512
|
};
|
|
@@ -10585,6 +10642,8 @@ var RunManager = class {
|
|
|
10585
10642
|
const homeDir = join(dirname(piDir), "home");
|
|
10586
10643
|
const env = {
|
|
10587
10644
|
HOME: homeDir,
|
|
10645
|
+
MOLTNET_DEFAULT_STORE_ROOT: defaultStoreRoot({ env: this.options.baseEnv }),
|
|
10646
|
+
MOLTNET_HOME: getConfigDir({ root: this.options.storeRoot ?? this.store.root }),
|
|
10588
10647
|
PI_CODING_AGENT_DIR: piDir,
|
|
10589
10648
|
XDG_CACHE_HOME: join(homeDir, ".cache"),
|
|
10590
10649
|
XDG_CONFIG_HOME: join(homeDir, ".config"),
|
|
@@ -10628,7 +10687,7 @@ var RunManager = class {
|
|
|
10628
10687
|
env["MOLTNET_AGENT_KEY"] = requireCredentialSnapshot(agent).agentKey;
|
|
10629
10688
|
env["MOLTNET_API_URL"] = activation.apiUrl ?? (activation.source === "external" ? activation.configApiUrl : "");
|
|
10630
10689
|
if (activation.source === "managed") {
|
|
10631
|
-
if (
|
|
10690
|
+
if (config.keys.private_key_ref?.provider !== "file") throw new AgentServerRunError("invalid_store", "The managed identity store is inconsistent: its signing key must be file-backed. Restore the identity store or use credential recovery.");
|
|
10632
10691
|
env["MOLTNET_PRIVATE_KEY_REF"] = formatSecretReferenceString(config.keys.private_key_ref);
|
|
10633
10692
|
env["MOLTNET_SECRET_ROOT"] = this.store.secretsDir;
|
|
10634
10693
|
} else try {
|
|
@@ -11231,6 +11290,20 @@ function writeRegistry(path, entries) {
|
|
|
11231
11290
|
renameSync(temp, path);
|
|
11232
11291
|
}
|
|
11233
11292
|
//#endregion
|
|
11293
|
+
//#region src/lib/agent-server/secret-providers.ts
|
|
11294
|
+
function createAgentServerSecretProviders(settings, store) {
|
|
11295
|
+
const secrets = new FileSecretProvider({
|
|
11296
|
+
root: store.secretsDir,
|
|
11297
|
+
writable: true
|
|
11298
|
+
});
|
|
11299
|
+
const registry = () => createNodeSecretProviderRegistry({ store: { root: settings.root } });
|
|
11300
|
+
return {
|
|
11301
|
+
secrets,
|
|
11302
|
+
secretProviders: registry().register(secrets),
|
|
11303
|
+
externalSecretProviders: registry()
|
|
11304
|
+
};
|
|
11305
|
+
}
|
|
11306
|
+
//#endregion
|
|
11234
11307
|
//#region src/lib/agent-server/readiness.ts
|
|
11235
11308
|
/**
|
|
11236
11309
|
* Whether a runtime profile can execute on *this* machine.
|
|
@@ -11997,7 +12070,8 @@ function requestOperationSignal(request, shutdownSignal) {
|
|
|
11997
12070
|
else request.raw.once("aborted", () => disconnected.abort({ source: "request" }));
|
|
11998
12071
|
return shutdownSignal ? AbortSignal.any([disconnected.signal, shutdownSignal]) : disconnected.signal;
|
|
11999
12072
|
}
|
|
12000
|
-
function buildAgentServer(
|
|
12073
|
+
function buildAgentServer(input) {
|
|
12074
|
+
const options = { ...input };
|
|
12001
12075
|
const { nativeGrant } = options;
|
|
12002
12076
|
const oauth = options.operatorOAuth;
|
|
12003
12077
|
let restartRequired = false;
|
|
@@ -12006,12 +12080,13 @@ function buildAgentServer(options) {
|
|
|
12006
12080
|
...fastifyOptions,
|
|
12007
12081
|
loggerInstance: options.logger
|
|
12008
12082
|
}) : Fastify(fastifyOptions);
|
|
12083
|
+
app.addHook("onListen", async () => {
|
|
12084
|
+
const address = app.server.address();
|
|
12085
|
+
if (address && typeof address !== "string") options.selfOrigin = `http://127.0.0.1:${address.port}`;
|
|
12086
|
+
});
|
|
12009
12087
|
options.registerOpenApi?.(app);
|
|
12010
12088
|
for (const schema of AGENT_SERVER_SCHEMAS) app.addSchema(schema);
|
|
12011
|
-
const
|
|
12012
|
-
registerLoopbackSecurity(app, {
|
|
12013
|
-
isOriginAllowed: (origin) => origin === "moltnet-agent-desktop://native" || browserOrigins.has(origin),
|
|
12014
|
-
...options.selfOrigin ? { selfOrigins: [options.selfOrigin] } : {},
|
|
12089
|
+
const securityOptions = {
|
|
12015
12090
|
allowedHeaders: [AGENT_SERVER_TOKEN_HEADER],
|
|
12016
12091
|
methods: [
|
|
12017
12092
|
"GET",
|
|
@@ -12020,7 +12095,18 @@ function buildAgentServer(options) {
|
|
|
12020
12095
|
"DELETE",
|
|
12021
12096
|
"OPTIONS"
|
|
12022
12097
|
]
|
|
12098
|
+
};
|
|
12099
|
+
if (options.nativeOnly) registerLoopbackSecurity(app, {
|
|
12100
|
+
...securityOptions,
|
|
12101
|
+
isOriginAllowed: (origin) => origin === NATIVE_CLIENT_ORIGIN
|
|
12023
12102
|
});
|
|
12103
|
+
else {
|
|
12104
|
+
const browserOrigins = new OriginAllowlist(options.allowedOrigins);
|
|
12105
|
+
registerLoopbackSecurity(app, {
|
|
12106
|
+
...securityOptions,
|
|
12107
|
+
isOriginAllowed: (origin) => origin === "moltnet-agent-desktop://native" || origin === options.selfOrigin || browserOrigins.has(origin)
|
|
12108
|
+
});
|
|
12109
|
+
}
|
|
12024
12110
|
let verificationWindow = Date.now();
|
|
12025
12111
|
let verifications = 0;
|
|
12026
12112
|
const browserVerification = /* @__PURE__ */ new WeakMap();
|
|
@@ -12670,7 +12756,7 @@ function normalizeAgentServerError(error) {
|
|
|
12670
12756
|
message: error.message
|
|
12671
12757
|
};
|
|
12672
12758
|
if (error instanceof AgentServerRunError) return {
|
|
12673
|
-
statusCode: error.code === "run_not_found" ? 404 : 400,
|
|
12759
|
+
statusCode: error.code === "run_not_found" ? 404 : error.code === "invalid_store" ? 409 : 400,
|
|
12674
12760
|
code: error.code,
|
|
12675
12761
|
message: error.message
|
|
12676
12762
|
};
|
|
@@ -12709,7 +12795,6 @@ function normalizeAgentServerError(error) {
|
|
|
12709
12795
|
* waits for an authorized controller to configure agents/providers and
|
|
12710
12796
|
* start or stop runs.
|
|
12711
12797
|
*/
|
|
12712
|
-
var DEFAULT_PORT = OPERATOR_OAUTH.serverPort;
|
|
12713
12798
|
var DEFAULT_ALLOWED_ORIGINS = "https://console.themolt.net";
|
|
12714
12799
|
var SHUTDOWN_TIMEOUT_MS = 15e3;
|
|
12715
12800
|
var LOCK_HELD_EXIT_CODE = 75;
|
|
@@ -12734,7 +12819,6 @@ async function runAgentServer(argv) {
|
|
|
12734
12819
|
console.log(AGENT_SERVER_HELP);
|
|
12735
12820
|
return 0;
|
|
12736
12821
|
}
|
|
12737
|
-
const envConfig = loadAgentServerEnvConfig();
|
|
12738
12822
|
const { values } = parseArgs({
|
|
12739
12823
|
args: argv,
|
|
12740
12824
|
options: {
|
|
@@ -12748,6 +12832,7 @@ async function runAgentServer(argv) {
|
|
|
12748
12832
|
"native-socket": { type: "string" }
|
|
12749
12833
|
}
|
|
12750
12834
|
});
|
|
12835
|
+
const envConfig = loadAgentServerEnvConfig(values.root);
|
|
12751
12836
|
const nativeSocket = values["native-socket"];
|
|
12752
12837
|
const nativeSocketError = validateNativeSocketOptions(nativeSocketValidationOptions({
|
|
12753
12838
|
...nativeSocket ? { nativeSocket } : {},
|
|
@@ -12761,13 +12846,14 @@ async function runAgentServer(argv) {
|
|
|
12761
12846
|
console.error(nativeSocketError);
|
|
12762
12847
|
return 1;
|
|
12763
12848
|
}
|
|
12764
|
-
const port = Number.parseInt(values.port ?? (envConfig.port || `${
|
|
12765
|
-
if (!Number.isInteger(port) || port <
|
|
12849
|
+
const port = Number.parseInt(values.port ?? (envConfig.port || `${defaultAgentServerPort(envConfig.root)}`), 10);
|
|
12850
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
12766
12851
|
console.error(`Invalid --port: ${String(values.port)}`);
|
|
12767
12852
|
return 1;
|
|
12768
12853
|
}
|
|
12769
12854
|
const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
|
|
12770
|
-
const
|
|
12855
|
+
const settingsRoot = envConfig.root;
|
|
12856
|
+
const connectionSettings = new ConnectionSettingsStore(settingsRoot, {
|
|
12771
12857
|
...envConfig.operatorOAuth,
|
|
12772
12858
|
...values["api-url"] || envConfig.apiUrl ? { apiUrl: values["api-url"] || envConfig.apiUrl } : {}
|
|
12773
12859
|
});
|
|
@@ -12782,13 +12868,8 @@ async function runAgentServer(argv) {
|
|
|
12782
12868
|
});
|
|
12783
12869
|
try {
|
|
12784
12870
|
try {
|
|
12785
|
-
return await withAgentServerLock(
|
|
12786
|
-
const secrets =
|
|
12787
|
-
root: store.secretsDir,
|
|
12788
|
-
writable: true
|
|
12789
|
-
});
|
|
12790
|
-
const secretProviders = createNodeSecretProviderRegistry().register(secrets);
|
|
12791
|
-
const externalSecretProviders = createNodeSecretProviderRegistry();
|
|
12871
|
+
return await withAgentServerLock(settingsRoot, async () => {
|
|
12872
|
+
const { secrets, secretProviders, externalSecretProviders } = createAgentServerSecretProviders(connectionSettings, store);
|
|
12792
12873
|
const nativeGrant = new NativeGrantService();
|
|
12793
12874
|
const nativeClient = applyNativeClientGrant({
|
|
12794
12875
|
nativeGrant,
|
|
@@ -12812,6 +12893,7 @@ async function runAgentServer(argv) {
|
|
|
12812
12893
|
const runtimeRegistry = new RuntimeRegistry(store.root);
|
|
12813
12894
|
const runs = new RunManager({
|
|
12814
12895
|
store,
|
|
12896
|
+
storeRoot: settingsRoot,
|
|
12815
12897
|
secretProviders,
|
|
12816
12898
|
externalSecretProviders,
|
|
12817
12899
|
baseEnv: processEnvSnapshot(),
|
|
@@ -12852,14 +12934,20 @@ async function runAgentServer(argv) {
|
|
|
12852
12934
|
logger,
|
|
12853
12935
|
shutdownSignal: shutdownController.signal
|
|
12854
12936
|
});
|
|
12937
|
+
let endpoint;
|
|
12855
12938
|
try {
|
|
12856
12939
|
const address = await app.listen(nativeSocket ? { path: nativeSocket } : {
|
|
12857
12940
|
host: "127.0.0.1",
|
|
12858
12941
|
port
|
|
12859
12942
|
});
|
|
12860
12943
|
if (nativeSocket) await chmod(nativeSocket, 384);
|
|
12944
|
+
if (!nativeSocket) {
|
|
12945
|
+
endpoint = publishAgentServerEndpoint(settingsRoot, address);
|
|
12946
|
+
console.error(`discovery: ${join(settingsRoot, "agent-server-endpoint.json")}`);
|
|
12947
|
+
}
|
|
12861
12948
|
console.error(`moltnet-agent server listening on ${address}`);
|
|
12862
|
-
console.error(`
|
|
12949
|
+
console.error(`store root: ${settingsRoot} (${envConfig.rootSource})`);
|
|
12950
|
+
console.error(`connection state: ${root}`);
|
|
12863
12951
|
if (nativeSocket) console.error(`native control socket: ${nativeSocket}`);
|
|
12864
12952
|
else console.error(`allowed origins: ${allowedOrigins.join(", ")}`);
|
|
12865
12953
|
if (nativeClient) console.error("native desktop client: authorized");
|
|
@@ -12868,12 +12956,17 @@ async function runAgentServer(argv) {
|
|
|
12868
12956
|
} catch (cause) {
|
|
12869
12957
|
await app.close().catch(() => void 0);
|
|
12870
12958
|
throw cause;
|
|
12959
|
+
} finally {
|
|
12960
|
+
endpoint?.release();
|
|
12871
12961
|
}
|
|
12872
|
-
}, {
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12962
|
+
}, {
|
|
12963
|
+
stateRoot: root,
|
|
12964
|
+
onCompromised: (error) => {
|
|
12965
|
+
console.error(error.message);
|
|
12966
|
+
process.exitCode = 1;
|
|
12967
|
+
process.kill(process.pid, "SIGTERM");
|
|
12968
|
+
}
|
|
12969
|
+
});
|
|
12877
12970
|
} catch (cause) {
|
|
12878
12971
|
if (cause instanceof AgentServerLockError) {
|
|
12879
12972
|
console.error(cause.message);
|
|
@@ -13278,6 +13371,7 @@ function compareVersions(a, b) {
|
|
|
13278
13371
|
}
|
|
13279
13372
|
function cachePath() {
|
|
13280
13373
|
const env = loadUpdateEnvConfig();
|
|
13374
|
+
if (env.storeRoot) return join(env.storeRoot, "cache", "updates", "agent.json");
|
|
13281
13375
|
return join(env.xdgCacheHome || (platform() === "win32" ? env.localAppData || join(homedir(), "AppData", "Local") : join(homedir(), ".cache")), "moltnet", "updates", "agent.json");
|
|
13282
13376
|
}
|
|
13283
13377
|
async function readCache() {
|
|
@@ -13297,16 +13391,14 @@ async function writeCache(cache) {
|
|
|
13297
13391
|
}
|
|
13298
13392
|
//#endregion
|
|
13299
13393
|
//#region src/version.ts
|
|
13300
|
-
var DAEMON_VERSION = "0.
|
|
13394
|
+
var DAEMON_VERSION = "0.63.0";
|
|
13301
13395
|
//#endregion
|
|
13302
13396
|
//#region src/cli.ts
|
|
13303
13397
|
async function runAgentDaemonCli(options) {
|
|
13398
|
+
const notice = legacyStoreNotice();
|
|
13399
|
+
if (notice) console.error(notice);
|
|
13304
13400
|
const [subcommand, ...rest] = options.argv ?? process.argv.slice(2);
|
|
13305
|
-
if ([
|
|
13306
|
-
"poll",
|
|
13307
|
-
"drain",
|
|
13308
|
-
"serve"
|
|
13309
|
-
].includes(subcommand ?? "") && !isWorkspaceInvocation()) checkDaemonUpdate({ currentVersion: DAEMON_VERSION }).then((update) => {
|
|
13401
|
+
if (["poll", "drain"].includes(subcommand ?? "") && !isWorkspaceInvocation()) checkDaemonUpdate({ currentVersion: DAEMON_VERSION }).then((update) => {
|
|
13310
13402
|
if (update.updateAvailable) pino({ name: "moltnet.agent-daemon" }).info({
|
|
13311
13403
|
event: "agent-daemon.update_available",
|
|
13312
13404
|
update
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.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.",
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
"pino-pretty": "^13.1.3",
|
|
85
85
|
"proper-lockfile": "4.1.2",
|
|
86
86
|
"typebox": "^1.2.8",
|
|
87
|
-
"@themoltnet/agent-runtime": "1.
|
|
88
|
-
"@themoltnet/pi-runtime": "0.18.
|
|
89
|
-
"@themoltnet/
|
|
90
|
-
"@themoltnet/
|
|
87
|
+
"@themoltnet/agent-runtime": "1.3.0",
|
|
88
|
+
"@themoltnet/pi-runtime": "0.18.3",
|
|
89
|
+
"@themoltnet/sdk": "0.145.0",
|
|
90
|
+
"@themoltnet/os-keyring": "0.4.0"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@fastify/swagger": "^9.6.1",
|
|
@@ -97,15 +97,15 @@
|
|
|
97
97
|
"vite": "^8.0.0",
|
|
98
98
|
"vite-plugin-dts": "^4.5.4",
|
|
99
99
|
"vitest": "^3.0.0",
|
|
100
|
-
"@moltnet/api-client": "0.1.0",
|
|
101
100
|
"@moltnet/agent-eval": "0.1.0",
|
|
102
|
-
"@moltnet/
|
|
101
|
+
"@moltnet/api-client": "0.1.0",
|
|
103
102
|
"@moltnet/crypto-service": "0.1.0",
|
|
104
103
|
"@moltnet/execution-integrations": "0.1.0",
|
|
104
|
+
"@moltnet/execution-plan": "0.1.0",
|
|
105
|
+
"@moltnet/loopback-companion": "0.1.0",
|
|
106
|
+
"@moltnet/bootstrap": "0.1.0",
|
|
105
107
|
"@moltnet/models": "0.1.0",
|
|
106
108
|
"@moltnet/observability": "0.1.0",
|
|
107
|
-
"@moltnet/loopback-companion": "0.1.0",
|
|
108
|
-
"@moltnet/execution-plan": "0.1.0",
|
|
109
109
|
"@moltnet/runtime-profiles": "0.1.0",
|
|
110
110
|
"@moltnet/tasks": "0.1.0"
|
|
111
111
|
},
|
|
@@ -137,6 +137,8 @@
|
|
|
137
137
|
"inputs": [
|
|
138
138
|
"default",
|
|
139
139
|
"^production",
|
|
140
|
+
"{workspaceRoot}/test-fixtures/agent-server-endpoint.json",
|
|
141
|
+
"{workspaceRoot}/test-fixtures/connection-state-root.json",
|
|
140
142
|
"{workspaceRoot}/docs/operate/*.md",
|
|
141
143
|
"{workspaceRoot}/docs/.vitepress/config.ts"
|
|
142
144
|
]
|
|
@@ -145,7 +147,7 @@
|
|
|
145
147
|
},
|
|
146
148
|
"scripts": {
|
|
147
149
|
"cli": "tsx src/main.ts",
|
|
148
|
-
"dev": "tsx watch src/main.ts",
|
|
150
|
+
"dev": "tsx ../../tools/src/desktop-development.ts tsx watch src/main.ts",
|
|
149
151
|
"start": "node dist/main.js",
|
|
150
152
|
"build": "vite build",
|
|
151
153
|
"generate:openapi": "tsx scripts/generate-openapi.ts",
|