@themoltnet/agent-daemon 0.49.1 → 0.51.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/dist/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as cryptoService, defaultPiDaemonAdapter, i as compileExecutionPlan, n as createExecutionPlanSnapshot, r as parseCredentialRequirements, t as runtimeExecutionOffer } from "./pi.js";
3
3
  import { assertRuntimeAdapterSupportsProfile } from "./runtime.js";
4
+ import { createRequire } from "node:module";
5
+ import { pino, transport } from "pino";
4
6
  import { Type } from "typebox";
5
7
  import "multiformats/cid";
6
8
  import "multiformats/codecs/json";
@@ -16,7 +18,6 @@ import { constants, createReadStream, createWriteStream, existsSync, lstatSync,
16
18
  import { AuthenticationError, MoltNetError, agentKeyKey, assertTrustedConfigApiUrl, createExecutorAttestor, deriveMcpUrl, formatSecretReferenceString, identitySeedKey, parseSecretReferenceString, readConfig, register, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed, resolveOAuth2ClientSecret } from "@themoltnet/sdk";
17
19
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
18
20
  import { once } from "node:events";
19
- import { pino, transport } from "pino";
20
21
  import { metrics } from "@opentelemetry/api";
21
22
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
22
23
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
@@ -26,19 +27,20 @@ import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
26
27
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
27
28
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
28
29
  import { AsyncLocalStorage } from "node:async_hooks";
29
- import { mkdir, open, realpath, stat } from "node:fs/promises";
30
+ import { mkdir, open, readFile, realpath, stat, writeFile } from "node:fs/promises";
30
31
  import { pipeline } from "node:stream/promises";
31
32
  import cors from "@fastify/cors";
32
33
  import helmet from "@fastify/helmet";
33
- import { getOAuthProviders } from "@earendil-works/pi-ai/oauth";
34
- import { AuthStorage } from "@earendil-works/pi-coding-agent";
34
+ import { lock, lockSync } from "proper-lockfile";
35
+ import { ModelRuntime, readStoredCredential } from "@earendil-works/pi-coding-agent";
35
36
  import { Transform, Writable } from "node:stream";
36
37
  import { writePiConfig } from "@themoltnet/pi-runtime/pi-config";
37
- import { homedir } from "node:os";
38
- import { lock } from "proper-lockfile";
38
+ import { homedir, platform } from "node:os";
39
+ import { pathToFileURL } from "node:url";
39
40
  import { StringDecoder } from "node:string_decoder";
40
41
  import rateLimit from "@fastify/rate-limit";
41
42
  import Fastify from "fastify";
43
+ import { isIP } from "node:net";
42
44
  import { createGzip } from "node:zlib";
43
45
  //#region ../../libs/tasks/src/rubric.ts
44
46
  /**
@@ -800,7 +802,7 @@ Object.freeze(ALL_CREDENTIAL_SCOPES.filter((scope) => scope !== CREDENTIAL_SCOPE
800
802
  ].filter((scope) => scope !== CREDENTIAL_SCOPES.HumanProfile);
801
803
  //#endregion
802
804
  //#region ../../libs/models/src/preview-sign.ts
803
- function schemaRef$1(schema, id) {
805
+ function schemaRef$2(schema, id) {
804
806
  return Type.Unsafe(Type.Ref(id));
805
807
  }
806
808
  var PreviewSignBase64UrlSchema = Type.String({
@@ -825,8 +827,8 @@ var PreviewSignEs256PublicKeySchema = Type.Object({
825
827
  kty: Type.Literal(2),
826
828
  algorithm: Type.Literal(-7),
827
829
  curve: Type.Literal(1),
828
- x: schemaRef$1(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url"),
829
- y: schemaRef$1(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url")
830
+ x: schemaRef$2(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url"),
831
+ y: schemaRef$2(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url")
830
832
  }, {
831
833
  $id: "PreviewSignEs256PublicKey",
832
834
  additionalProperties: false
@@ -835,8 +837,8 @@ var PreviewSignEcdhEsHkdf256PublicKeySchema = Type.Object({
835
837
  kty: Type.Literal(2),
836
838
  algorithm: Type.Literal(-25),
837
839
  curve: Type.Literal(1),
838
- x: schemaRef$1(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url"),
839
- y: schemaRef$1(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url")
840
+ x: schemaRef$2(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url"),
841
+ y: schemaRef$2(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url")
840
842
  }, {
841
843
  $id: "PreviewSignEcdhEsHkdf256PublicKey",
842
844
  additionalProperties: false
@@ -845,8 +847,8 @@ var PreviewSignEsp256PublicKeySchema = Type.Object({
845
847
  kty: Type.Literal(2),
846
848
  algorithm: Type.Literal(-9),
847
849
  curve: Type.Literal(1),
848
- x: schemaRef$1(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url"),
849
- y: schemaRef$1(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url")
850
+ x: schemaRef$2(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url"),
851
+ y: schemaRef$2(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url")
850
852
  }, {
851
853
  $id: "PreviewSignEsp256PublicKey",
852
854
  additionalProperties: false
@@ -855,18 +857,18 @@ var PreviewSignArkgSeedPublicKeySchema = Type.Object({
855
857
  kty: Type.Literal(-65537),
856
858
  algorithm: Type.Literal(-65700),
857
859
  derivedAlgorithm: Type.Literal(-9),
858
- blindingKey: schemaRef$1(PreviewSignEs256PublicKeySchema, "PreviewSignEs256PublicKey"),
859
- kemKey: schemaRef$1(PreviewSignEcdhEsHkdf256PublicKeySchema, "PreviewSignEcdhEsHkdf256PublicKey")
860
+ blindingKey: schemaRef$2(PreviewSignEs256PublicKeySchema, "PreviewSignEs256PublicKey"),
861
+ kemKey: schemaRef$2(PreviewSignEcdhEsHkdf256PublicKeySchema, "PreviewSignEcdhEsHkdf256PublicKey")
860
862
  }, {
861
863
  $id: "PreviewSignArkgSeedPublicKey",
862
864
  additionalProperties: false
863
865
  });
864
866
  var PreviewSignPublicMaterialSchema = Type.Object({
865
867
  version: Type.Literal(1),
866
- outerCredentialId: schemaRef$1(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
867
- outerPublicKey: schemaRef$1(PreviewSignEs256PublicKeySchema, "PreviewSignEs256PublicKey"),
868
- previewKeyHandle: schemaRef$1(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
869
- seedPublicKey: schemaRef$1(PreviewSignArkgSeedPublicKeySchema, "PreviewSignArkgSeedPublicKey")
868
+ outerCredentialId: schemaRef$2(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
869
+ outerPublicKey: schemaRef$2(PreviewSignEs256PublicKeySchema, "PreviewSignEs256PublicKey"),
870
+ previewKeyHandle: schemaRef$2(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
871
+ seedPublicKey: schemaRef$2(PreviewSignArkgSeedPublicKeySchema, "PreviewSignArkgSeedPublicKey")
870
872
  }, {
871
873
  $id: "PreviewSignPublicMaterial",
872
874
  additionalProperties: false
@@ -874,19 +876,19 @@ var PreviewSignPublicMaterialSchema = Type.Object({
874
876
  var PreviewSignChallengeSchema = Type.Object({
875
877
  verificationMethod: Type.Literal("human-hardware-previewsign"),
876
878
  version: Type.Literal(1),
877
- envelope: schemaRef$1(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
878
- digest: schemaRef$1(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url"),
879
- additionalArguments: schemaRef$1(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
880
- outerCredentialId: schemaRef$1(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
881
- outerPublicKey: schemaRef$1(PreviewSignEs256PublicKeySchema, "PreviewSignEs256PublicKey"),
882
- previewKeyHandle: schemaRef$1(PreviewSignBase64UrlSchema, "PreviewSignBase64Url")
879
+ envelope: schemaRef$2(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
880
+ digest: schemaRef$2(PreviewSignSha256Base64UrlSchema, "PreviewSignSha256Base64Url"),
881
+ additionalArguments: schemaRef$2(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
882
+ outerCredentialId: schemaRef$2(PreviewSignBase64UrlSchema, "PreviewSignBase64Url"),
883
+ outerPublicKey: schemaRef$2(PreviewSignEs256PublicKeySchema, "PreviewSignEs256PublicKey"),
884
+ previewKeyHandle: schemaRef$2(PreviewSignBase64UrlSchema, "PreviewSignBase64Url")
883
885
  }, {
884
886
  $id: "PreviewSignChallenge",
885
887
  additionalProperties: false
886
888
  });
887
889
  var PreviewSignChallengeValueSchema = Type.Object({
888
890
  verificationMethod: Type.Literal("human-hardware-previewsign"),
889
- value: schemaRef$1(PreviewSignChallengeSchema, "PreviewSignChallenge")
891
+ value: schemaRef$2(PreviewSignChallengeSchema, "PreviewSignChallenge")
890
892
  }, {
891
893
  $id: "PreviewSignChallengeValue",
892
894
  additionalProperties: false
@@ -894,14 +896,14 @@ var PreviewSignChallengeValueSchema = Type.Object({
894
896
  var PreviewSignChallengeOperationSchema = Type.Union([Type.Literal("credential-registration"), Type.Literal("signing-request")], { $id: "PreviewSignChallengeOperation" });
895
897
  var PreviewSignReceiptSchema = Type.Object({
896
898
  version: Type.Literal(1),
897
- signature: schemaRef$1(PreviewSignP256DerSignatureBase64UrlSchema, "PreviewSignP256DerSignatureBase64Url")
899
+ signature: schemaRef$2(PreviewSignP256DerSignatureBase64UrlSchema, "PreviewSignP256DerSignatureBase64Url")
898
900
  }, {
899
901
  $id: "PreviewSignReceipt",
900
902
  additionalProperties: false
901
903
  });
902
904
  var PreviewSignReceiptValueSchema = Type.Object({
903
905
  verificationMethod: Type.Literal("human-hardware-previewsign"),
904
- value: schemaRef$1(PreviewSignReceiptSchema, "PreviewSignReceipt")
906
+ value: schemaRef$2(PreviewSignReceiptSchema, "PreviewSignReceipt")
905
907
  }, {
906
908
  $id: "PreviewSignReceiptValue",
907
909
  additionalProperties: false
@@ -1595,7 +1597,7 @@ Type.Union([
1595
1597
  ]);
1596
1598
  //#endregion
1597
1599
  //#region ../../libs/models/src/signer-protocol.ts
1598
- function schemaRef(schema) {
1600
+ function schemaRef$1(schema) {
1599
1601
  const id = schemaId(schema);
1600
1602
  return Type.Ref(id);
1601
1603
  }
@@ -1624,13 +1626,13 @@ var SignerProblemSchema = Type.Object({
1624
1626
  $id: "SignerProblem",
1625
1627
  additionalProperties: false
1626
1628
  });
1627
- var SignerCeremonyParamsSchema = Type.Object({ ceremonyId: Type.Unsafe(schemaRef(SignerBase64UrlSchema)) }, {
1629
+ var SignerCeremonyParamsSchema = Type.Object({ ceremonyId: Type.Unsafe(schemaRef$1(SignerBase64UrlSchema)) }, {
1628
1630
  $id: "SignerCeremonyParams",
1629
1631
  additionalProperties: false
1630
1632
  });
1631
1633
  var SignerSessionSchema = Type.Object({
1632
1634
  version: Type.Literal(1),
1633
- token: Type.Unsafe(schemaRef(SignerBase64UrlSchema)),
1635
+ token: Type.Unsafe(schemaRef$1(SignerBase64UrlSchema)),
1634
1636
  expiresAt: Type.String()
1635
1637
  }, {
1636
1638
  $id: "SignerSession",
@@ -1643,25 +1645,25 @@ var SignerEnrollmentCeremonyRequestSchema = Type.Object({
1643
1645
  minLength: 1,
1644
1646
  maxLength: 255
1645
1647
  }),
1646
- teamId: Type.Unsafe(schemaRef(SignerUuidSchema))
1648
+ teamId: Type.Unsafe(schemaRef$1(SignerUuidSchema))
1647
1649
  }, {
1648
1650
  $id: "SignerEnrollmentCeremonyRequest",
1649
1651
  additionalProperties: false
1650
1652
  });
1651
1653
  var SignerChallengeCeremonyRequestSchema = Type.Object({
1652
1654
  version: Type.Literal(1),
1653
- operation: Type.Unsafe(schemaRef(SignerChallengeOperationSchema)),
1654
- resourceId: Type.Unsafe(schemaRef(SignerUuidSchema)),
1655
- challenge: Type.Unsafe(schemaRef(SignerPreviewSignChallengeValueSchema))
1655
+ operation: Type.Unsafe(schemaRef$1(SignerChallengeOperationSchema)),
1656
+ resourceId: Type.Unsafe(schemaRef$1(SignerUuidSchema)),
1657
+ challenge: Type.Unsafe(schemaRef$1(SignerPreviewSignChallengeValueSchema))
1656
1658
  }, {
1657
1659
  $id: "SignerChallengeCeremonyRequest",
1658
1660
  additionalProperties: false
1659
1661
  });
1660
- var SignerCeremonyRequestSchema = Type.Union([Type.Unsafe(schemaRef(SignerEnrollmentCeremonyRequestSchema)), Type.Unsafe(schemaRef(SignerChallengeCeremonyRequestSchema))], { $id: "SignerCeremonyRequest" });
1662
+ var SignerCeremonyRequestSchema = Type.Union([Type.Unsafe(schemaRef$1(SignerEnrollmentCeremonyRequestSchema)), Type.Unsafe(schemaRef$1(SignerChallengeCeremonyRequestSchema))], { $id: "SignerCeremonyRequest" });
1661
1663
  var SignerCeremonySchema = Type.Object({
1662
1664
  version: Type.Literal(1),
1663
- id: Type.Unsafe(schemaRef(SignerBase64UrlSchema)),
1664
- operation: Type.Unsafe(schemaRef(SignerOperationSchema)),
1665
+ id: Type.Unsafe(schemaRef$1(SignerBase64UrlSchema)),
1666
+ operation: Type.Unsafe(schemaRef$1(SignerOperationSchema)),
1665
1667
  approvalUrl: Type.String(),
1666
1668
  expiresAt: Type.String()
1667
1669
  }, {
@@ -1671,7 +1673,7 @@ var SignerCeremonySchema = Type.Object({
1671
1673
  var SignerPendingResultSchema = Type.Object({
1672
1674
  version: Type.Literal(1),
1673
1675
  status: Type.Literal("pending"),
1674
- operation: Type.Unsafe(schemaRef(SignerOperationSchema))
1676
+ operation: Type.Unsafe(schemaRef$1(SignerOperationSchema))
1675
1677
  }, {
1676
1678
  $id: "SignerPendingResult",
1677
1679
  additionalProperties: false
@@ -1680,7 +1682,7 @@ var SignerEnrollmentResultSchema = Type.Object({
1680
1682
  version: Type.Literal(1),
1681
1683
  status: Type.Literal("completed"),
1682
1684
  operation: Type.Literal("credential-enrollment"),
1683
- publicMaterial: Type.Unsafe(schemaRef(SignerPreviewSignPublicMaterialSchema))
1685
+ publicMaterial: Type.Unsafe(schemaRef$1(SignerPreviewSignPublicMaterialSchema))
1684
1686
  }, {
1685
1687
  $id: "SignerEnrollmentResult",
1686
1688
  additionalProperties: false
@@ -1689,8 +1691,8 @@ var SignerReceiptSchema = PreviewSignReceiptValueSchema;
1689
1691
  var SignerSignatureResultSchema = Type.Object({
1690
1692
  version: Type.Literal(1),
1691
1693
  status: Type.Literal("completed"),
1692
- operation: Type.Unsafe(schemaRef(SignerChallengeOperationSchema)),
1693
- receipt: Type.Unsafe(schemaRef(SignerReceiptSchema))
1694
+ operation: Type.Unsafe(schemaRef$1(SignerChallengeOperationSchema)),
1695
+ receipt: Type.Unsafe(schemaRef$1(SignerReceiptSchema))
1694
1696
  }, {
1695
1697
  $id: "SignerSignatureResult",
1696
1698
  additionalProperties: false
@@ -1698,7 +1700,7 @@ var SignerSignatureResultSchema = Type.Object({
1698
1700
  var SignerFailedResultSchema = Type.Object({
1699
1701
  version: Type.Literal(1),
1700
1702
  status: Type.Literal("failed"),
1701
- operation: Type.Unsafe(schemaRef(SignerOperationSchema)),
1703
+ operation: Type.Unsafe(schemaRef$1(SignerOperationSchema)),
1702
1704
  code: Type.String(),
1703
1705
  message: Type.String()
1704
1706
  }, {
@@ -1706,10 +1708,10 @@ var SignerFailedResultSchema = Type.Object({
1706
1708
  additionalProperties: false
1707
1709
  });
1708
1710
  var SignerCeremonyResultSchema = Type.Union([
1709
- Type.Unsafe(schemaRef(SignerPendingResultSchema)),
1710
- Type.Unsafe(schemaRef(SignerEnrollmentResultSchema)),
1711
- Type.Unsafe(schemaRef(SignerSignatureResultSchema)),
1712
- Type.Unsafe(schemaRef(SignerFailedResultSchema))
1711
+ Type.Unsafe(schemaRef$1(SignerPendingResultSchema)),
1712
+ Type.Unsafe(schemaRef$1(SignerEnrollmentResultSchema)),
1713
+ Type.Unsafe(schemaRef$1(SignerSignatureResultSchema)),
1714
+ Type.Unsafe(schemaRef$1(SignerFailedResultSchema))
1713
1715
  ], { $id: "SignerCeremonyResult" });
1714
1716
  ({ ...previewSignSchemaContext }), schemaId(SignerUuidSchema), schemaId(SignerOperationSchema), schemaId(SignerProblemSchema), schemaId(SignerCeremonyParamsSchema), schemaId(SignerSessionSchema), schemaId(SignerEnrollmentCeremonyRequestSchema), schemaId(SignerChallengeCeremonyRequestSchema), schemaId(SignerCeremonyRequestSchema), schemaId(SignerCeremonySchema), schemaId(SignerPendingResultSchema), schemaId(SignerEnrollmentResultSchema), schemaId(SignerSignatureResultSchema), schemaId(SignerFailedResultSchema), schemaId(SignerCeremonyResultSchema);
1715
1717
  //#endregion
@@ -3426,11 +3428,13 @@ Commands:
3426
3428
  once Claim and execute one specific queued task by id, then exit.
3427
3429
  drain Poll until the queue has nothing claimable, then exit.
3428
3430
  Useful for batch eval runs and demos.
3429
- serve Loopback supervisor for console-managed runs: pairing,
3431
+ server Loopback supervisor for console-managed runs: pairing,
3430
3432
  agent/provider config store, and start/stop of poll/drain
3431
3433
  child processes. Binds 127.0.0.1 only.
3432
3434
  sync-sessions
3433
3435
  Repair durable runtime-session checkpoints from local slot files.
3436
+ update check
3437
+ Check the stable MoltNet agent release without reading credentials.
3434
3438
 
3435
3439
  Run \`agent-daemon <command> --help\` for command-specific flags.
3436
3440
 
@@ -3561,8 +3565,8 @@ Example:
3561
3565
  function isHelpFlag(args) {
3562
3566
  return args.includes("--help") || args.includes("-h");
3563
3567
  }
3564
- var SERVE_HELP = `\
3565
- agent-daemon serve — loopback supervisor for console-managed runs.
3568
+ var AGENT_SERVER_HELP = `\
3569
+ agent-daemon server — loopback supervisor for console-managed runs.
3566
3570
 
3567
3571
  Binds 127.0.0.1 only. A paired Console origin configures agents and
3568
3572
  providers (secret references only) and starts/stops poll/drain runs as
@@ -3570,12 +3574,12 @@ child processes of this supervisor.
3570
3574
 
3571
3575
  Options:
3572
3576
  --port <n> Loopback port. Default: 17374.
3573
- Env: MOLTNET_SERVE_PORT.
3577
+ Env: MOLTNET_AGENT_SERVER_PORT.
3574
3578
  --allowed-origins <csv> Exact Console origins allowed to pair.
3575
3579
  Default: https://console.themolt.net.
3576
- Env: MOLTNET_SERVE_ALLOWED_ORIGINS.
3580
+ Env: MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS.
3577
3581
  --root <path> Config root. Default: ~/.config/moltnet
3578
- (or MOLTNET_SERVE_ROOT).
3582
+ (or MOLTNET_AGENT_SERVER_ROOT).
3579
3583
  --api-url <url> Default MoltNet API for new managed agents.
3580
3584
  Default: https://api.themolt.net.
3581
3585
  `;
@@ -3658,7 +3662,7 @@ async function validateStartupBinding(options) {
3658
3662
  const assessment = assessStartupBinding(whoami, options.teamId);
3659
3663
  if (!assessment.ok) throw new Error(`Daemon startup validation failed: ${assessment.reason}`);
3660
3664
  const expected = options.expectedIdentity;
3661
- if (expected && !assessIdentityPin(whoami, expected).ok) throw new Error("Daemon startup validation failed: authenticated identity does not match the serve activation.");
3665
+ if (expected && !assessIdentityPin(whoami, expected).ok) throw new Error("Daemon startup validation failed: authenticated identity does not match the Agent Server activation.");
3662
3666
  return whoami;
3663
3667
  }
3664
3668
  /**
@@ -3784,20 +3788,26 @@ function readBoolean(name, value) {
3784
3788
  function activatePiCodingAgentDir(path) {
3785
3789
  process.env["PI_CODING_AGENT_DIR"] = path;
3786
3790
  }
3787
- function loadServeEnvConfig() {
3791
+ function loadAgentServerEnvConfig() {
3788
3792
  return {
3789
- port: process.env["MOLTNET_SERVE_PORT"] ?? "",
3790
- allowedOrigins: process.env["MOLTNET_SERVE_ALLOWED_ORIGINS"] ?? "",
3791
- root: process.env["MOLTNET_SERVE_ROOT"] ?? "",
3793
+ port: process.env["MOLTNET_AGENT_SERVER_PORT"] ?? "",
3794
+ allowedOrigins: process.env["MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS"] ?? "",
3795
+ root: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
3792
3796
  xdgConfigHome: process.env["XDG_CONFIG_HOME"] ?? "",
3793
3797
  apiUrl: process.env["MOLTNET_API_URL"] ?? "",
3794
3798
  logLevel: process.env["LOG_LEVEL"] ?? ""
3795
3799
  };
3796
3800
  }
3797
- /** Full process environment for spawned serve run children. */
3801
+ /** Full process environment for spawned Agent Server run children. */
3798
3802
  function processEnvSnapshot() {
3799
3803
  return process.env;
3800
3804
  }
3805
+ function loadUpdateEnvConfig() {
3806
+ return {
3807
+ xdgCacheHome: process.env["XDG_CACHE_HOME"] ?? "",
3808
+ localAppData: process.env["LOCALAPPDATA"] ?? ""
3809
+ };
3810
+ }
3801
3811
  //#endregion
3802
3812
  //#region src/lib/abort-active-attempt.ts
3803
3813
  /** Best-effort signal cleanup; lease expiry remains the final backstop. */
@@ -4859,7 +4869,7 @@ function executionIntentFromRuntimeProfile(input) {
4859
4869
  * local configuration and logs its value-free decisions. It gates nothing
4860
4870
  * yet — enforcement is a separate, later change.
4861
4871
  */
4862
- var DEFAULT_GOVERNANCE_OBSERVE_TIMEOUT_MS = 2e3;
4872
+ var DEFAULT_GOVERNANCE_OBAGENT_SERVER_TIMEOUT_MS = 2e3;
4863
4873
  /**
4864
4874
  * Two distinct provenance sources (#2022 review): requirements come from a
4865
4875
  * profile-side/private input (`MOLTNET_PROFILE_CREDENTIAL_REQUIREMENTS`, a
@@ -4988,7 +4998,7 @@ async function observeGovernancePlan(input) {
4988
4998
  * The observer is intentionally non-gating: failures and deadlines are logged
4989
4999
  * with claim correlation and converted to `null`.
4990
5000
  */
4991
- async function observeGovernancePlanSafely(input, timeoutMs = DEFAULT_GOVERNANCE_OBSERVE_TIMEOUT_MS) {
5001
+ async function observeGovernancePlanSafely(input, timeoutMs = DEFAULT_GOVERNANCE_OBAGENT_SERVER_TIMEOUT_MS) {
4992
5002
  let timeout;
4993
5003
  try {
4994
5004
  return await Promise.race([observeGovernancePlan(input), new Promise((_resolve, reject) => {
@@ -7056,6 +7066,8 @@ function requireOriginHeader(headers) {
7056
7066
  //#endregion
7057
7067
  //#region ../../libs/loopback-companion/src/fastify.ts
7058
7068
  var CORS_PREFLIGHT_MAX_AGE_SECONDS = 600;
7069
+ var PRIVATE_NETWORK_REQUEST_HEADER = "access-control-request-private-network";
7070
+ var PRIVATE_NETWORK_ALLOW_HEADER = "access-control-allow-private-network";
7059
7071
  /**
7060
7072
  * Enforce that the `Host` header identifies loopback. Blocks DNS-rebinding
7061
7073
  * setups where a public hostname resolves to 127.0.0.1: the browser then
@@ -7093,8 +7105,9 @@ function registerLoopbackSecurity(app, options) {
7093
7105
  requireLoopbackHost(request);
7094
7106
  done();
7095
7107
  });
7096
- app.addHook("onSend", async (_request, reply, payload) => {
7108
+ app.addHook("onSend", async (request, reply, payload) => {
7097
7109
  reply.header("cache-control", "no-store");
7110
+ if (request.method === "OPTIONS" && request.headers[PRIVATE_NETWORK_REQUEST_HEADER] === "true" && reply.getHeader("access-control-allow-origin") === request.headers.origin) reply.header(PRIVATE_NETWORK_ALLOW_HEADER, "true");
7098
7111
  return payload;
7099
7112
  });
7100
7113
  app.removeContentTypeParser("application/json");
@@ -7170,9 +7183,65 @@ function rejectExplicitCrossSite(headers) {
7170
7183
  if (headerValue(headers, "sec-fetch-site") === "cross-site") throw new LoopbackViolationError("cross_site_rejected", "Request must not originate cross-site");
7171
7184
  }
7172
7185
  //#endregion
7173
- //#region src/lib/serve/pairing.ts
7186
+ //#region src/lib/agent-server/lock.ts
7187
+ var AgentServerLockError = class extends Error {
7188
+ name = "AgentServerLockError";
7189
+ constructor(code, message, options) {
7190
+ super(message, options);
7191
+ this.code = code;
7192
+ }
7193
+ };
7174
7194
  /**
7175
- * One-click pairing ceremony for the serve supervisor, mirroring the signer's
7195
+ * Acquire the per-root singleton lock. `proper-lockfile` uses an atomic lock
7196
+ * directory at exactly `<root>/agent-server.lock`, recovers stale owners, and keeps
7197
+ * the mtime fresh while the supervisor is alive.
7198
+ */
7199
+ async function acquireAgentServerLock(root, options = {}) {
7200
+ const path = join(root, "agent-server.lock");
7201
+ let releaseLock;
7202
+ try {
7203
+ releaseLock = await lock(root, {
7204
+ lockfilePath: path,
7205
+ realpath: false,
7206
+ retries: 0,
7207
+ ...options.staleMs === void 0 ? {} : { stale: options.staleMs },
7208
+ ...options.updateMs === void 0 ? {} : { update: options.updateMs },
7209
+ onCompromised: (cause) => {
7210
+ const error = new AgentServerLockError("compromised", `Agent Server lock ${path} was compromised: ${cause.message}`, { cause });
7211
+ if (options.onCompromised) {
7212
+ options.onCompromised(error);
7213
+ return;
7214
+ }
7215
+ throw error;
7216
+ }
7217
+ });
7218
+ } catch (cause) {
7219
+ if (cause.code === "ELOCKED") throw new AgentServerLockError("held", `another moltnet-agent server process already owns ${path}`, { cause });
7220
+ throw new AgentServerLockError("failed", `could not acquire Agent Server lock ${path}: ${cause.message}`, { cause });
7221
+ }
7222
+ let released = false;
7223
+ return {
7224
+ path,
7225
+ async release() {
7226
+ if (released) return;
7227
+ released = true;
7228
+ await releaseLock();
7229
+ }
7230
+ };
7231
+ }
7232
+ /** Always release after normal completion or startup/runtime failure. */
7233
+ async function withAgentServerLock(root, work, options) {
7234
+ const held = await acquireAgentServerLock(root, options);
7235
+ try {
7236
+ return await work();
7237
+ } finally {
7238
+ await held.release();
7239
+ }
7240
+ }
7241
+ //#endregion
7242
+ //#region src/lib/agent-server/pairing.ts
7243
+ /**
7244
+ * One-click pairing ceremony for the agent server, mirroring the signer's
7176
7245
  * session/ceremony pattern (#2062 design):
7177
7246
  *
7178
7247
  * 1. Console (allowed origin) POSTs `/v1/pairings` → pending pairing bound
@@ -7193,8 +7262,8 @@ function rejectExplicitCrossSite(headers) {
7193
7262
  * supervisor process.
7194
7263
  */
7195
7264
  var PENDING_TTL_MS = 600 * 1e3;
7196
- var ServePairingError = class extends Error {
7197
- name = "ServePairingError";
7265
+ var AgentServerPairingError = class extends Error {
7266
+ name = "AgentServerPairingError";
7198
7267
  constructor(code, message) {
7199
7268
  super(message);
7200
7269
  this.code = code;
@@ -7242,7 +7311,7 @@ var PairingService = class {
7242
7311
  /** Data for the local approval page. */
7243
7312
  approval(pairingId) {
7244
7313
  const pairing = this.require(pairingId);
7245
- if (pairing.approved) throw new ServePairingError("pairing_invalid", "Pairing is already approved");
7314
+ if (pairing.approved) throw new AgentServerPairingError("pairing_invalid", "Pairing is already approved");
7246
7315
  return {
7247
7316
  origin: pairing.origin,
7248
7317
  confirmToken: pairing.confirmToken
@@ -7250,15 +7319,15 @@ var PairingService = class {
7250
7319
  }
7251
7320
  confirm(pairingId, confirmToken) {
7252
7321
  const pairing = this.require(pairingId);
7253
- if (pairing.approved || !safeEqual(pairing.confirmToken, confirmToken)) throw new ServePairingError("pairing_invalid", "Confirmation token is not valid");
7322
+ if (pairing.approved || !safeEqual(pairing.confirmToken, confirmToken)) throw new AgentServerPairingError("pairing_invalid", "Confirmation token is not valid");
7254
7323
  pairing.approved = true;
7255
7324
  pairing.bearerToken = this.token();
7256
7325
  return { origin: pairing.origin };
7257
7326
  }
7258
7327
  claim(pairingId, origin) {
7259
7328
  const pairing = this.require(pairingId);
7260
- if (pairing.origin !== origin) throw new ServePairingError("pairing_origin_mismatch", "Pairing belongs to a different origin");
7261
- if (!pairing.approved || !pairing.bearerToken) throw new ServePairingError("pairing_not_approved", "Pairing has not been approved yet");
7329
+ if (pairing.origin !== origin) throw new AgentServerPairingError("pairing_origin_mismatch", "Pairing belongs to a different origin");
7330
+ if (!pairing.approved || !pairing.bearerToken) throw new AgentServerPairingError("pairing_not_approved", "Pairing has not been approved yet");
7262
7331
  const token = pairing.bearerToken;
7263
7332
  this.pending.delete(pairingId);
7264
7333
  this.paired.set(origin, sha256Hex(token));
@@ -7266,12 +7335,12 @@ var PairingService = class {
7266
7335
  }
7267
7336
  verify(origin, token) {
7268
7337
  const tokenHash = this.paired.get(origin);
7269
- if (!tokenHash || !safeEqual(tokenHash, sha256Hex(token))) throw new ServePairingError("pairing_token_invalid", "Pairing token is not valid for this origin");
7338
+ if (!tokenHash || !safeEqual(tokenHash, sha256Hex(token))) throw new AgentServerPairingError("pairing_token_invalid", "Pairing token is not valid for this origin");
7270
7339
  }
7271
7340
  require(pairingId) {
7272
7341
  this.sweep();
7273
7342
  const pairing = this.pending.get(pairingId);
7274
- if (!pairing) throw new ServePairingError("pairing_not_found", "Pairing was not found or has expired");
7343
+ if (!pairing) throw new AgentServerPairingError("pairing_not_found", "Pairing was not found or has expired");
7275
7344
  return pairing;
7276
7345
  }
7277
7346
  };
@@ -7321,24 +7390,25 @@ function renderPairingResultPage(input) {
7321
7390
  `;
7322
7391
  }
7323
7392
  //#endregion
7324
- //#region src/lib/serve/provider-login.ts
7393
+ //#region src/lib/agent-server/provider-login.ts
7325
7394
  /**
7326
- * Subscription-provider OAuth brokering for `serve` (#2061 slice 4).
7395
+ * Subscription-provider OAuth brokering for Agent Server (#2061 slice 4).
7327
7396
  *
7328
- * The console clicks "Connect"; serve runs the Pi OAuth flow host-side via
7329
- * `AuthStorage.login()` (which owns persistence into the shared
7397
+ * The console clicks "Connect"; agent server runs the Pi OAuth flow host-side via
7398
+ * `ModelRuntime.login()` (which owns persistence into the shared
7330
7399
  * `pi/auth.json` and token rotation thereafter). The browser only ever sees
7331
7400
  * the provider's authorize URL or device code — never tokens.
7332
7401
  *
7333
- * Provider ids come from pi-ai's own OAuth registry (`getOAuthProviders`),
7334
- * so serve stays in lockstep with what Pi can actually authenticate
7335
- * (anthropic, openai-codex, github-copilot, …) without hardcoding.
7402
+ * Provider ids come from Pi's model runtime, so Agent Server stays in lockstep
7403
+ * with supported subscription providers. GitHub Copilot is intentionally
7404
+ * excluded: MoltNet does not broker editor-seat subscription credentials.
7336
7405
  */
7337
7406
  var LOGIN_TTL_MS = 600 * 1e3;
7338
7407
  /** How long `start()` waits for the flow to surface a URL / device code. */
7339
7408
  var START_INFO_TIMEOUT_MS = 5e3;
7340
- var ServeSubscriptionError = class extends Error {
7341
- name = "ServeSubscriptionError";
7409
+ var EXCLUDED_SUBSCRIPTION_PROVIDERS = new Set(["github-copilot"]);
7410
+ var AgentServerSubscriptionError = class extends Error {
7411
+ name = "AgentServerSubscriptionError";
7342
7412
  constructor(code, message) {
7343
7413
  super(message);
7344
7414
  this.code = code;
@@ -7349,37 +7419,49 @@ var silentLogger = {
7349
7419
  warn: () => void 0,
7350
7420
  error: () => void 0
7351
7421
  };
7352
- var ProviderLoginService = class {
7422
+ var ProviderLoginService = class ProviderLoginService {
7353
7423
  logins = /* @__PURE__ */ new Map();
7354
- authStorage;
7355
7424
  logger;
7356
7425
  constructor(options) {
7357
7426
  this.options = options;
7358
- this.authStorage = options.authStorage ?? AuthStorage.create(this.options.authPath);
7359
7427
  this.logger = options.logger ?? silentLogger;
7360
7428
  }
7429
+ static async create(options) {
7430
+ const modelRuntime = await ModelRuntime.create({
7431
+ authPath: options.authPath,
7432
+ refreshOnCreate: false
7433
+ });
7434
+ return new ProviderLoginService({
7435
+ ...options,
7436
+ modelRuntime
7437
+ });
7438
+ }
7361
7439
  now() {
7362
7440
  return this.options.now?.() ?? Date.now();
7363
7441
  }
7364
7442
  providers() {
7365
- return this.options.listProviders?.() ?? getOAuthProviders().map((provider) => ({
7443
+ return (this.options.listProviders ? this.options.listProviders() : this.runtime().getProviders().filter((provider) => provider.auth.oauth !== void 0).map((provider) => ({
7366
7444
  id: provider.id,
7367
7445
  name: provider.name
7368
- }));
7446
+ }))).filter((provider) => !EXCLUDED_SUBSCRIPTION_PROVIDERS.has(provider.id));
7369
7447
  }
7370
7448
  connected(providerId) {
7371
7449
  if (this.options.isConnected) return this.options.isConnected(providerId);
7372
7450
  try {
7373
- return (this.options.authStorage ? this.authStorage : AuthStorage.create(this.options.authPath)).getAuthStatus(providerId).configured;
7451
+ return readStoredCredential(providerId, this.options.authPath) !== void 0;
7374
7452
  } catch (error) {
7375
7453
  this.logger.warn({
7376
- event: "serve.subscription_auth_read_failed",
7454
+ event: "agent-server.subscription_auth_read_failed",
7377
7455
  providerId,
7378
7456
  ...safeLoginError(error)
7379
7457
  }, "Could not read subscription authentication state");
7380
7458
  return false;
7381
7459
  }
7382
7460
  }
7461
+ runtime() {
7462
+ if (!this.options.modelRuntime) throw new Error("ProviderLoginService requires a model runtime when production adapters are not overridden");
7463
+ return this.options.modelRuntime;
7464
+ }
7383
7465
  list() {
7384
7466
  this.sweep();
7385
7467
  return this.providers().map((provider) => ({
@@ -7390,40 +7472,42 @@ var ProviderLoginService = class {
7390
7472
  sweep() {
7391
7473
  const now = this.now();
7392
7474
  for (const [id, login] of this.logins) if (login.startedAt + LOGIN_TTL_MS <= now) {
7393
- this.invalidate(login, "expired");
7475
+ if (login.status === "pending") this.invalidate(login, "expired");
7394
7476
  this.logins.delete(id);
7395
7477
  }
7396
7478
  }
7397
7479
  restoreCredential(login) {
7398
7480
  try {
7399
- if (login.previousCredential) this.authStorage.set(login.providerId, login.previousCredential);
7400
- else this.authStorage.logout(login.providerId);
7481
+ restoreStoredCredential(this.options.authPath, login.providerId, login.previousCredential);
7482
+ return true;
7401
7483
  } catch (error) {
7402
7484
  this.logger.error({
7403
- event: "serve.subscription_login_cleanup_failed",
7485
+ event: "agent-server.subscription_login_cleanup_failed",
7404
7486
  operationId: login.operationId,
7405
7487
  providerId: login.providerId,
7406
7488
  ...safeLoginError(error)
7407
7489
  }, "Could not restore subscription credentials after an invalidated login");
7490
+ return false;
7408
7491
  }
7409
7492
  }
7410
7493
  invalidate(login, transition) {
7411
- if (login.invalidated) return;
7494
+ if (login.invalidated) return true;
7412
7495
  login.invalidated = true;
7413
7496
  login.abort.abort(/* @__PURE__ */ new Error(`subscription login ${transition}`));
7414
- this.restoreCredential(login);
7497
+ const restored = this.restoreCredential(login);
7415
7498
  login.infoArrived();
7416
7499
  this.logger.info({
7417
- event: "serve.subscription_login_transition",
7500
+ event: "agent-server.subscription_login_transition",
7418
7501
  operationId: login.operationId,
7419
7502
  providerId: login.providerId,
7420
7503
  transition
7421
7504
  }, "Subscription login invalidated");
7505
+ return restored;
7422
7506
  }
7423
7507
  status(providerId) {
7424
7508
  this.sweep();
7425
7509
  const login = this.logins.get(providerId);
7426
- if (!login) throw new ServeSubscriptionError("login_not_found", `no login in progress for "${providerId}"`);
7510
+ if (!login) throw new AgentServerSubscriptionError("login_not_found", `no login in progress for "${providerId}"`);
7427
7511
  return snapshot(login);
7428
7512
  }
7429
7513
  /**
@@ -7433,7 +7517,7 @@ var ProviderLoginService = class {
7433
7517
  */
7434
7518
  async start(providerId) {
7435
7519
  this.sweep();
7436
- if (!this.providers().some((provider) => provider.id === providerId)) throw new ServeSubscriptionError("provider_unknown", `"${providerId}" is not a known subscription provider`);
7520
+ if (!this.providers().some((provider) => provider.id === providerId)) throw new AgentServerSubscriptionError("provider_unknown", `"${providerId}" is not a known subscription provider`);
7437
7521
  const existing = this.logins.get(providerId);
7438
7522
  if (existing && existing.status === "pending") return snapshot(existing);
7439
7523
  let infoArrived = () => void 0;
@@ -7449,17 +7533,17 @@ var ProviderLoginService = class {
7449
7533
  infoArrived,
7450
7534
  abort,
7451
7535
  invalidated: false,
7452
- previousCredential: this.authStorage.get(providerId)
7536
+ previousCredential: readStoredCredential(providerId, this.options.authPath)
7453
7537
  };
7454
7538
  this.logins.set(providerId, login);
7455
7539
  this.logger.info({
7456
- event: "serve.subscription_login_transition",
7540
+ event: "agent-server.subscription_login_transition",
7457
7541
  operationId: login.operationId,
7458
7542
  providerId,
7459
7543
  transition: "started"
7460
7544
  }, "Subscription login started");
7461
7545
  const callbacks = createLoginCallbacks(login, this.logger);
7462
- (this.options.runLogin ?? ((id, loginCallbacks) => this.authStorage.login(id, loginCallbacks)))(providerId, callbacks).then(() => {
7546
+ (this.options.runLogin ?? ((id, loginCallbacks) => this.runtime().login(id, "oauth", toAuthInteraction(loginCallbacks)).then(() => void 0)))(providerId, callbacks).then(() => {
7463
7547
  if (login.invalidated) {
7464
7548
  this.restoreCredential(login);
7465
7549
  return;
@@ -7468,7 +7552,7 @@ var ProviderLoginService = class {
7468
7552
  login.status = "failed";
7469
7553
  login.error = "Subscription sign-in completed, but credentials were not persisted. Start again to retry.";
7470
7554
  this.logger.error({
7471
- event: "serve.subscription_login_transition",
7555
+ event: "agent-server.subscription_login_transition",
7472
7556
  operationId: login.operationId,
7473
7557
  providerId,
7474
7558
  transition: "persistence_failed"
@@ -7476,7 +7560,7 @@ var ProviderLoginService = class {
7476
7560
  } else {
7477
7561
  login.status = "completed";
7478
7562
  this.logger.info({
7479
- event: "serve.subscription_login_transition",
7563
+ event: "agent-server.subscription_login_transition",
7480
7564
  operationId: login.operationId,
7481
7565
  providerId,
7482
7566
  transition: "completed"
@@ -7491,7 +7575,7 @@ var ProviderLoginService = class {
7491
7575
  login.status = "failed";
7492
7576
  login.error = publicLoginError(error);
7493
7577
  this.logger.warn({
7494
- event: "serve.subscription_login_transition",
7578
+ event: "agent-server.subscription_login_transition",
7495
7579
  operationId: login.operationId,
7496
7580
  providerId,
7497
7581
  transition: "failed",
@@ -7499,16 +7583,24 @@ var ProviderLoginService = class {
7499
7583
  }, "Subscription login failed");
7500
7584
  login.infoArrived();
7501
7585
  });
7502
- await Promise.race([infoPromise, new Promise((resolvePromise) => {
7503
- setTimeout(resolvePromise, START_INFO_TIMEOUT_MS).unref?.();
7504
- })]);
7586
+ if (!await Promise.race([infoPromise, new Promise((resolvePromise) => {
7587
+ setTimeout(() => resolvePromise(), START_INFO_TIMEOUT_MS).unref?.();
7588
+ })]).then(() => login.authUrl !== void 0 || login.userCode !== void 0 || login.status !== "pending")) {
7589
+ login.waitingForAuthorization = true;
7590
+ this.logger.warn({
7591
+ event: "serve.subscription_login_start_info_timeout",
7592
+ operationId: login.operationId,
7593
+ providerId,
7594
+ timeoutMs: START_INFO_TIMEOUT_MS
7595
+ }, "Subscription login is still waiting for authorization information");
7596
+ }
7505
7597
  return snapshot(login);
7506
7598
  }
7507
7599
  /** Abort an in-flight login and forget it. */
7508
7600
  cancel(providerId) {
7509
7601
  const login = this.logins.get(providerId);
7510
- if (!login) throw new ServeSubscriptionError("login_not_found", `no login in progress for "${providerId}"`);
7511
- this.invalidate(login, "cancelled");
7602
+ if (!login) throw new AgentServerSubscriptionError("login_not_found", `no login in progress for "${providerId}"`);
7603
+ if (!this.invalidate(login, "cancelled")) throw new AgentServerSubscriptionError("login_cleanup_failed", `could not safely cancel login for "${providerId}"; credential cleanup requires intervention`);
7512
7604
  this.logins.delete(providerId);
7513
7605
  return {
7514
7606
  providerId,
@@ -7517,17 +7609,82 @@ var ProviderLoginService = class {
7517
7609
  }
7518
7610
  /** Abort every pending flow during supervisor shutdown. */
7519
7611
  close() {
7520
- for (const login of this.logins.values()) this.invalidate(login, "shutdown");
7612
+ for (const login of this.logins.values()) if (login.status === "pending") this.invalidate(login, "shutdown");
7521
7613
  this.logins.clear();
7522
7614
  }
7523
7615
  };
7616
+ function toAuthInteraction(callbacks) {
7617
+ return {
7618
+ ...callbacks.signal ? { signal: callbacks.signal } : {},
7619
+ notify: (event) => {
7620
+ switch (event.type) {
7621
+ case "auth_url":
7622
+ callbacks.onAuth({
7623
+ url: event.url,
7624
+ ...event.instructions ? { instructions: event.instructions } : {}
7625
+ });
7626
+ break;
7627
+ case "device_code":
7628
+ callbacks.onDeviceCode({
7629
+ userCode: event.userCode,
7630
+ verificationUri: event.verificationUri
7631
+ });
7632
+ break;
7633
+ case "info":
7634
+ case "progress":
7635
+ callbacks.onProgress?.(event.message);
7636
+ break;
7637
+ }
7638
+ },
7639
+ prompt: async (prompt) => {
7640
+ if (prompt.type !== "select") return callbacks.onPrompt({ message: prompt.message });
7641
+ const selected = await callbacks.onSelect({
7642
+ message: prompt.message,
7643
+ options: prompt.options.map(({ id, label }) => ({
7644
+ id,
7645
+ label
7646
+ }))
7647
+ });
7648
+ if (!selected) throw new AgentServerSubscriptionError("login_unsupported_prompt", "This provider flow did not offer a supported sign-in method");
7649
+ return selected;
7650
+ }
7651
+ };
7652
+ }
7653
+ function restoreStoredCredential(authPath, providerId, credential) {
7654
+ mkdirSync(dirname(authPath), {
7655
+ recursive: true,
7656
+ mode: 448
7657
+ });
7658
+ try {
7659
+ writeFileSync(authPath, "{}\n", {
7660
+ flag: "wx",
7661
+ mode: 384
7662
+ });
7663
+ } catch (error) {
7664
+ if (error.code !== "EEXIST") throw error;
7665
+ }
7666
+ const release = lockSync(authPath, { realpath: false });
7667
+ const temp = `${authPath}.${randomBytes(6).toString("hex")}.tmp`;
7668
+ try {
7669
+ const parsed = JSON.parse(readFileSync(authPath, "utf8"));
7670
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`Pi credential store is not an object: ${authPath}`);
7671
+ const credentials = parsed;
7672
+ if (credential) credentials[providerId] = credential;
7673
+ else delete credentials[providerId];
7674
+ writeFileSync(temp, `${JSON.stringify(credentials, null, 2)}\n`, { mode: 384 });
7675
+ renameSync(temp, authPath);
7676
+ } finally {
7677
+ rmSync(temp, { force: true });
7678
+ release();
7679
+ }
7680
+ }
7524
7681
  function createLoginCallbacks(login, logger) {
7525
7682
  return {
7526
7683
  onAuth: (info) => {
7527
7684
  login.authUrl = info.url;
7528
7685
  if (info.instructions) login.instructions = info.instructions;
7529
7686
  logger.info({
7530
- event: "serve.subscription_login_transition",
7687
+ event: "agent-server.subscription_login_transition",
7531
7688
  operationId: login.operationId,
7532
7689
  providerId: login.providerId,
7533
7690
  transition: "authorization_ready"
@@ -7538,20 +7695,20 @@ function createLoginCallbacks(login, logger) {
7538
7695
  login.userCode = info.userCode;
7539
7696
  login.verificationUri = info.verificationUri;
7540
7697
  logger.info({
7541
- event: "serve.subscription_login_transition",
7698
+ event: "agent-server.subscription_login_transition",
7542
7699
  operationId: login.operationId,
7543
7700
  providerId: login.providerId,
7544
7701
  transition: "device_code_ready"
7545
7702
  }, "Subscription device code ready");
7546
7703
  login.infoArrived();
7547
7704
  },
7548
- onPrompt: () => Promise.reject(new ServeSubscriptionError("login_unsupported_prompt", "This provider flow needs an interactive prompt; run `pi /login` in a terminal instead")),
7705
+ onPrompt: () => Promise.reject(new AgentServerSubscriptionError("login_unsupported_prompt", "This provider flow needs an interactive prompt; run `pi /login` in a terminal instead")),
7549
7706
  onSelect: (prompt) => Promise.resolve((prompt.options.find((option) => /device/i.test(option.id)) ?? prompt.options[0])?.id),
7550
7707
  signal: login.abort.signal
7551
7708
  };
7552
7709
  }
7553
7710
  function publicLoginError(error) {
7554
- if (error instanceof ServeSubscriptionError) return error.message;
7711
+ if (error instanceof AgentServerSubscriptionError) return error.message;
7555
7712
  return "Subscription sign-in failed. Start again to retry.";
7556
7713
  }
7557
7714
  function safeLoginError(error) {
@@ -7561,7 +7718,7 @@ function safeLoginError(error) {
7561
7718
  return result;
7562
7719
  }
7563
7720
  function snapshot(login) {
7564
- const { providerId, status, authUrl, instructions, userCode, verificationUri, error } = login;
7721
+ const { providerId, status, authUrl, instructions, userCode, verificationUri, error, waitingForAuthorization } = login;
7565
7722
  return {
7566
7723
  providerId,
7567
7724
  status,
@@ -7569,28 +7726,29 @@ function snapshot(login) {
7569
7726
  ...instructions ? { instructions } : {},
7570
7727
  ...userCode ? { userCode } : {},
7571
7728
  ...verificationUri ? { verificationUri } : {},
7572
- ...error ? { error } : {}
7729
+ ...error ? { error } : {},
7730
+ ...waitingForAuthorization ? { waitingForAuthorization } : {}
7573
7731
  };
7574
7732
  }
7575
7733
  var NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
7576
7734
  var PROVIDER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
7577
7735
  function assertStoreName(kind, value) {
7578
- if (!NAME_RE.test(value)) throw new ServeStoreError("invalid_name", `${kind} must match ${NAME_RE.source}`);
7736
+ if (!NAME_RE.test(value)) throw new AgentServerStoreError("invalid_name", `${kind} must match ${NAME_RE.source}`);
7579
7737
  return value;
7580
7738
  }
7581
7739
  function assertProviderId(value) {
7582
- if (!PROVIDER_ID_RE.test(value)) throw new ServeStoreError("invalid_name", `provider id must match ${PROVIDER_ID_RE.source}`);
7740
+ if (!PROVIDER_ID_RE.test(value)) throw new AgentServerStoreError("invalid_name", `provider id must match ${PROVIDER_ID_RE.source}`);
7583
7741
  return value;
7584
7742
  }
7585
- var ServeStoreError = class extends Error {
7586
- name = "ServeStoreError";
7743
+ var AgentServerStoreError = class extends Error {
7744
+ name = "AgentServerStoreError";
7587
7745
  constructor(code, message, options) {
7588
7746
  super(message, options);
7589
7747
  this.code = code;
7590
7748
  }
7591
7749
  };
7592
- /** `MOLTNET_SERVE_ROOT` override, else `$XDG_CONFIG_HOME/moltnet`, else `~/.config/moltnet`. */
7593
- function resolveServeRoot(input) {
7750
+ /** `MOLTNET_AGENT_SERVER_ROOT` override, else `$XDG_CONFIG_HOME/moltnet`, else `~/.config/moltnet`. */
7751
+ function resolveAgentServerRoot(input) {
7594
7752
  const override = input.root?.trim();
7595
7753
  if (override) return override;
7596
7754
  const xdg = input.xdgConfigHome?.trim();
@@ -7601,7 +7759,7 @@ function providerEnvName(providerId) {
7601
7759
  }
7602
7760
  function assertProviderEnvName(providerId, value) {
7603
7761
  const expected = providerEnvName(providerId);
7604
- if (value !== expected) throw new ServeStoreError("invalid_state", `provider envName must be ${expected}`);
7762
+ if (value !== expected) throw new AgentServerStoreError("invalid_state", `provider envName must be ${expected}`);
7605
7763
  return value;
7606
7764
  }
7607
7765
  function readJson(path) {
@@ -7610,12 +7768,12 @@ function readJson(path) {
7610
7768
  raw = readFileSync(path, "utf8");
7611
7769
  } catch (cause) {
7612
7770
  if (cause.code === "ENOENT") return null;
7613
- throw new ServeStoreError("io_error", `could not read state at ${path}`, { cause });
7771
+ throw new AgentServerStoreError("io_error", `could not read state at ${path}`, { cause });
7614
7772
  }
7615
7773
  try {
7616
7774
  return JSON.parse(raw);
7617
7775
  } catch (cause) {
7618
- throw new ServeStoreError("invalid_state", `corrupt JSON at ${path}`, { cause });
7776
+ throw new AgentServerStoreError("invalid_state", `corrupt JSON at ${path}`, { cause });
7619
7777
  }
7620
7778
  }
7621
7779
  function writeJsonAtomic(path, value) {
@@ -7630,7 +7788,7 @@ function writeJsonAtomic(path, value) {
7630
7788
  throw cause;
7631
7789
  }
7632
7790
  }
7633
- var ServeStore = class {
7791
+ var AgentServerStore = class {
7634
7792
  root;
7635
7793
  agentsDir;
7636
7794
  runsDir;
@@ -7660,23 +7818,23 @@ var ServeStore = class {
7660
7818
  });
7661
7819
  return this;
7662
7820
  }
7663
- get servePath() {
7664
- return join(this.root, "serve.json");
7821
+ get statePath() {
7822
+ return join(this.root, "agent-server.json");
7665
7823
  }
7666
- readServeState() {
7667
- const state = readJson(this.servePath);
7824
+ readAgentServerState() {
7825
+ const state = readJson(this.statePath);
7668
7826
  if (!state) return {
7669
7827
  version: 1,
7670
7828
  pendingRegistrations: {},
7671
7829
  activations: {}
7672
7830
  };
7673
- if (!isRecord$1(state) || state.version !== 1) throw new ServeStoreError("invalid_state", `serve.json version ${String(isRecord$1(state) ? state.version : void 0)} is not supported`);
7674
- if ("pairedOrigins" in state) throw new ServeStoreError("invalid_state", "serve.json uses the obsolete pairing format; clear the unreleased serve store and reconfigure it");
7675
- if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new ServeStoreError("invalid_state", "serve.json is missing the version 1 activation map; clear the unreleased serve store and reconfigure it");
7831
+ if (!isRecord$1(state) || state.version !== 1) throw new AgentServerStoreError("invalid_state", `agent-server.json version ${String(isRecord$1(state) ? state.version : void 0)} is not supported`);
7832
+ if ("pairedOrigins" in state) throw new AgentServerStoreError("invalid_state", "agent-server.json uses the obsolete pairing format; clear the unreleased agent server store and reconfigure it");
7833
+ if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new AgentServerStoreError("invalid_state", "agent-server.json is missing the version 1 activation map; clear the unreleased agent server store and reconfigure it");
7676
7834
  for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
7677
7835
  for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
7678
7836
  assertStoreName("agent name", alias);
7679
- if (!isRecord$1(registration) || typeof registration.apiUrl !== "string" || registration.apiUrl.length === 0 || typeof registration.createdAt !== "string" || registration.createdAt.length === 0) throw new ServeStoreError("invalid_state", `pending registration "${alias}" is not valid`);
7837
+ if (!isRecord$1(registration) || typeof registration.apiUrl !== "string" || registration.apiUrl.length === 0 || typeof registration.createdAt !== "string" || registration.createdAt.length === 0) throw new AgentServerStoreError("invalid_state", `pending registration "${alias}" is not valid`);
7680
7838
  }
7681
7839
  return {
7682
7840
  version: 1,
@@ -7684,8 +7842,8 @@ var ServeStore = class {
7684
7842
  activations: state.activations
7685
7843
  };
7686
7844
  }
7687
- writeServeState(state) {
7688
- writeJsonAtomic(this.servePath, state);
7845
+ writeAgentServerState(state) {
7846
+ writeJsonAtomic(this.statePath, state);
7689
7847
  }
7690
7848
  agentPath(name) {
7691
7849
  return storeChildPath(this.agentsDir, "agent name", name, ".json");
@@ -7700,37 +7858,37 @@ var ServeStore = class {
7700
7858
  rmSync(this.agentPath(alias), { force: true });
7701
7859
  }
7702
7860
  readActivation(alias) {
7703
- return this.readServeState().activations[assertStoreName("agent name", alias)] ?? null;
7861
+ return this.readAgentServerState().activations[assertStoreName("agent name", alias)] ?? null;
7704
7862
  }
7705
7863
  hasPendingRegistration(alias) {
7706
- return Boolean(this.readServeState().pendingRegistrations[assertStoreName("agent name", alias)]);
7864
+ return Boolean(this.readAgentServerState().pendingRegistrations[assertStoreName("agent name", alias)]);
7707
7865
  }
7708
7866
  reserveRegistration(alias, apiUrl) {
7709
7867
  const name = assertStoreName("agent name", alias);
7710
- const state = this.readServeState();
7711
- if (state.activations[name] || state.pendingRegistrations[name]) throw new ServeStoreError("already_exists", `agent "${name}" already exists in the serve store`);
7868
+ const state = this.readAgentServerState();
7869
+ if (state.activations[name] || state.pendingRegistrations[name]) throw new AgentServerStoreError("already_exists", `agent "${name}" already exists in the agent server store`);
7712
7870
  state.pendingRegistrations[name] = {
7713
7871
  apiUrl,
7714
7872
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
7715
7873
  };
7716
- this.writeServeState(state);
7874
+ this.writeAgentServerState(state);
7717
7875
  }
7718
7876
  clearPendingRegistration(alias) {
7719
7877
  const name = assertStoreName("agent name", alias);
7720
- const state = this.readServeState();
7878
+ const state = this.readAgentServerState();
7721
7879
  delete state.pendingRegistrations[name];
7722
- this.writeServeState(state);
7880
+ this.writeAgentServerState(state);
7723
7881
  }
7724
7882
  writeActivation(activation) {
7725
7883
  const alias = assertStoreName("agent name", activation.alias);
7726
7884
  validateActivation(alias, activation);
7727
- const state = this.readServeState();
7885
+ const state = this.readAgentServerState();
7728
7886
  state.activations[alias] = activation;
7729
7887
  if (activation.source === "managed") delete state.pendingRegistrations[alias];
7730
- this.writeServeState(state);
7888
+ this.writeAgentServerState(state);
7731
7889
  }
7732
7890
  listActivations() {
7733
- return Object.values(this.readServeState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
7891
+ return Object.values(this.readAgentServerState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
7734
7892
  }
7735
7893
  get providersPath() {
7736
7894
  return join(this.root, "providers.json");
@@ -7760,17 +7918,17 @@ var ServeStore = class {
7760
7918
  root = realpathSync(this.runsDir);
7761
7919
  runDir = realpathSync(this.runDir(id));
7762
7920
  } catch (cause) {
7763
- throw new ServeStoreError("io_error", "could not resolve run directory", { cause });
7921
+ throw new AgentServerStoreError("io_error", "could not resolve run directory", { cause });
7764
7922
  }
7765
- if (!isStrictDescendant(root, runDir)) throw new ServeStoreError("invalid_state", "run directory escapes its store");
7923
+ if (!isStrictDescendant(root, runDir)) throw new AgentServerStoreError("invalid_state", "run directory escapes its store");
7766
7924
  const logPath = join(runDir, "daemon.log");
7767
7925
  try {
7768
- if (lstatSync(logPath).isSymbolicLink()) throw new ServeStoreError("invalid_state", "run log must not be a symbolic link");
7926
+ if (lstatSync(logPath).isSymbolicLink()) throw new AgentServerStoreError("invalid_state", "run log must not be a symbolic link");
7769
7927
  const resolvedLog = realpathSync(logPath);
7770
- if (!isStrictDescendant(runDir, resolvedLog)) throw new ServeStoreError("invalid_state", "run log escapes its store");
7928
+ if (!isStrictDescendant(runDir, resolvedLog)) throw new AgentServerStoreError("invalid_state", "run log escapes its store");
7771
7929
  } catch (cause) {
7772
- if (cause instanceof ServeStoreError) throw cause;
7773
- if (cause.code !== "ENOENT") throw new ServeStoreError("io_error", "could not resolve run log", { cause });
7930
+ if (cause instanceof AgentServerStoreError) throw cause;
7931
+ if (cause.code !== "ENOENT") throw new AgentServerStoreError("io_error", "could not resolve run log", { cause });
7774
7932
  }
7775
7933
  return logPath;
7776
7934
  }
@@ -7857,7 +8015,7 @@ function storeChildPath(root, kind, value, suffix = "") {
7857
8015
  const name = assertStoreName(kind, value);
7858
8016
  const normalizedRoot = resolve(root);
7859
8017
  const candidate = resolve(normalizedRoot, `${name}${suffix}`);
7860
- if (!isStrictDescendant(normalizedRoot, candidate)) throw new ServeStoreError("invalid_name", `${kind} escapes its store`);
8018
+ if (!isStrictDescendant(normalizedRoot, candidate)) throw new AgentServerStoreError("invalid_name", `${kind} escapes its store`);
7861
8019
  return candidate;
7862
8020
  }
7863
8021
  function isStrictDescendant(root, candidate) {
@@ -7866,7 +8024,7 @@ function isStrictDescendant(root, candidate) {
7866
8024
  }
7867
8025
  function validateActivation(alias, value) {
7868
8026
  const invalid = () => {
7869
- throw new ServeStoreError("invalid_state", `activation "${alias}" is not a valid version 1 activation`);
8027
+ throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 1 activation`);
7870
8028
  };
7871
8029
  if (!isRecord$1(value)) invalid();
7872
8030
  const activation = value;
@@ -7884,20 +8042,20 @@ function validateActivation(alias, value) {
7884
8042
  if (activation.source !== "external" || typeof activation.configPath !== "string" || activation.configPath.length === 0 || typeof activation.configApiUrl !== "string" || activation.configApiUrl.length === 0 || activation.apiUrl !== void 0 && typeof activation.apiUrl !== "string") invalid();
7885
8043
  }
7886
8044
  //#endregion
7887
- //#region src/lib/serve/identity.ts
8045
+ //#region src/lib/agent-server/identity.ts
7888
8046
  /**
7889
- * Serve identity activation (#2061/#1834 boundary).
8047
+ * AgentServer identity activation (#2061/#1834 boundary).
7890
8048
  *
7891
8049
  * Managed agent files are canonical `MoltNetConfig` documents. Alias,
7892
8050
  * provenance, pinned identity material, and external config paths live only
7893
- * in the versioned activation map in `serve.json`. Every attach and run loads
8051
+ * in the versioned activation map in `agent-server.json`. Every attach and run loads
7894
8052
  * the current config and verifies it with authenticated `whoami` before use.
7895
8053
  */
7896
8054
  var MAX_CONFIG_BYTES = 64 * 1024;
7897
8055
  var IDENTITY_OPERATION_TIMEOUT_MS = 15e3;
7898
8056
  var pendingAliases = /* @__PURE__ */ new WeakMap();
7899
- var ServeIdentityError = class extends Error {
7900
- name = "ServeIdentityError";
8057
+ var AgentServerIdentityError = class extends Error {
8058
+ name = "AgentServerIdentityError";
7901
8059
  constructor(code, message, options) {
7902
8060
  super(message, options);
7903
8061
  this.code = code;
@@ -7909,13 +8067,13 @@ function reserveAlias(store, alias) {
7909
8067
  pending = /* @__PURE__ */ new Set();
7910
8068
  pendingAliases.set(store, pending);
7911
8069
  }
7912
- if (pending.has(alias) || store.hasPendingRegistration(alias) || store.readActivation(alias) || store.readAgentConfig(alias)) throw new ServeIdentityError("agent_exists", `agent "${alias}" already exists in the serve store`);
8070
+ if (pending.has(alias) || store.hasPendingRegistration(alias) || store.readActivation(alias) || store.readAgentConfig(alias)) throw new AgentServerIdentityError("agent_exists", `agent "${alias}" already exists in the agent server store`);
7913
8071
  pending.add(alias);
7914
8072
  return () => pending.delete(alias);
7915
8073
  }
7916
8074
  async function createManagedAgent(store, secrets, input, connectAgent = connect) {
7917
8075
  const alias = assertStoreName("agent name", input.name);
7918
- if (!input.enrollmentToken.trim()) throw new ServeIdentityError("enrollment_required", "an enrollment token from the target team is required — a self-registered agent would be stranded in its own personal team");
8076
+ if (!input.enrollmentToken.trim()) throw new AgentServerIdentityError("enrollment_required", "an enrollment token from the target team is required — a self-registered agent would be stranded in its own personal team");
7919
8077
  const releaseAlias = reserveAlias(store, alias);
7920
8078
  let registeredIdentityId;
7921
8079
  try {
@@ -7923,7 +8081,7 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
7923
8081
  try {
7924
8082
  apiUrl = requireSecureCredentialApiUrl(input.apiUrl);
7925
8083
  } catch (cause) {
7926
- throw new ServeIdentityError("registration_failed", "registration API URL must use HTTPS or HTTP loopback", { cause });
8084
+ throw new AgentServerIdentityError("registration_failed", "registration API URL must use HTTPS or HTTP loopback", { cause });
7927
8085
  }
7928
8086
  store.reserveRegistration(alias, apiUrl);
7929
8087
  const result = await register({
@@ -7932,7 +8090,7 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
7932
8090
  enrollmentToken: input.enrollmentToken,
7933
8091
  signal: boundedIdentitySignal(input.signal)
7934
8092
  });
7935
- if (result.credentials.type !== "agent_key") throw new ServeIdentityError("unsupported_credential", `registration returned credential type "${result.credentials.type}"; serve manages agent-key credentials only`);
8093
+ if (result.credentials.type !== "agent_key") throw new AgentServerIdentityError("unsupported_credential", `registration returned credential type "${result.credentials.type}"; agent server manages agent-key credentials only`);
7936
8094
  const now = (/* @__PURE__ */ new Date()).toISOString();
7937
8095
  const { identityId, fingerprint, publicKey, privateKey } = result.identity;
7938
8096
  registeredIdentityId = identityId;
@@ -7990,9 +8148,9 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
7990
8148
  } catch (cause) {
7991
8149
  if (!registeredIdentityId && cause instanceof MoltNetError && cause.statusCode !== void 0 && cause.statusCode >= 400 && cause.statusCode < 500) {
7992
8150
  store.clearPendingRegistration(alias);
7993
- throw new ServeIdentityError("registration_failed", `registration for "${alias}" was rejected`, { cause });
8151
+ throw new AgentServerIdentityError("registration_failed", `registration for "${alias}" was rejected`, { cause });
7994
8152
  }
7995
- if (store.hasPendingRegistration(alias)) throw new ServeIdentityError("registration_incomplete", registeredIdentityId ? `identity "${registeredIdentityId}" was registered but local activation is incomplete; reconcile or clear its pending serve record before retrying` : `registration for "${alias}" may be incomplete; inspect the remote API before changing its pending serve record`, { cause });
8153
+ if (store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("registration_incomplete", registeredIdentityId ? `identity "${registeredIdentityId}" was registered but local activation is incomplete; reconcile or clear its pending Agent Server record before retrying` : `registration for "${alias}" may be incomplete; inspect the remote API before changing its pending Agent Server record`, { cause });
7996
8154
  throw cause;
7997
8155
  } finally {
7998
8156
  releaseAlias();
@@ -8004,22 +8162,22 @@ async function reconcileManagedRegistration(store, secrets, aliasInput, action,
8004
8162
  const activation = store.readActivation(alias);
8005
8163
  if (activation) {
8006
8164
  const config = store.readAgentConfig(alias);
8007
- if (!config) throw new ServeIdentityError("registration_incomplete", `managed activation for "${alias}" is missing its canonical config`);
8165
+ if (!config) throw new AgentServerIdentityError("registration_incomplete", `managed activation for "${alias}" is missing its canonical config`);
8008
8166
  return {
8009
8167
  activation,
8010
8168
  config
8011
8169
  };
8012
8170
  }
8013
- if (!store.hasPendingRegistration(alias)) throw new ServeIdentityError("config_not_found", `agent "${alias}" has no pending registration to reconcile`);
8171
+ if (!store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("config_not_found", `agent "${alias}" has no pending registration to reconcile`);
8014
8172
  const config = store.readAgentConfig(alias);
8015
8173
  if (action === "abandon") {
8016
8174
  store.removeAgentConfig(alias);
8017
8175
  store.clearPendingRegistration(alias);
8018
8176
  return null;
8019
8177
  }
8020
- if (!config?.agent_key_ref || config.agent_key_ref.provider !== FILE_SECRET_PROVIDER || config.keys.private_key_ref?.provider !== FILE_SECRET_PROVIDER) throw new ServeIdentityError("registration_incomplete", `pending registration for "${alias}" does not have complete managed references`);
8178
+ if (!config?.agent_key_ref || config.agent_key_ref.provider !== FILE_SECRET_PROVIDER || config.keys.private_key_ref?.provider !== FILE_SECRET_PROVIDER) throw new AgentServerIdentityError("registration_incomplete", `pending registration for "${alias}" does not have complete managed references`);
8021
8179
  const [agentKey, privateKeyState] = await Promise.all([secrets.read(config.agent_key_ref.key), secrets.probe(config.keys.private_key_ref.key)]);
8022
- if (!agentKey || privateKeyState !== "present") throw new ServeIdentityError("registration_incomplete", `pending registration for "${alias}" is missing persisted secret material`);
8180
+ if (!agentKey || privateKeyState !== "present") throw new AgentServerIdentityError("registration_incomplete", `pending registration for "${alias}" is missing persisted secret material`);
8023
8181
  const identity = identityFromConfig(config);
8024
8182
  const apiUrl = requireConfigApiUrl(config, store.agentPath(alias));
8025
8183
  const whoami = await callWhoami(connectAgent, {
@@ -8047,7 +8205,7 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
8047
8205
  const alias = assertStoreName("agent name", input.name);
8048
8206
  const releaseAlias = reserveAlias(store, alias);
8049
8207
  try {
8050
- if (!isAbsolute(input.configDir)) throw new ServeIdentityError("config_not_found", "external configDir must be an absolute path");
8208
+ if (!isAbsolute(input.configDir)) throw new AgentServerIdentityError("config_not_found", "external configDir must be an absolute path");
8051
8209
  const configPath = join(input.configDir, "moltnet.json");
8052
8210
  externalAgentLocation(configPath);
8053
8211
  const config = await readCurrentConfig(configPath);
@@ -8083,7 +8241,7 @@ async function verifyAgentActivation(store, alias, managedSecretProviders, exter
8083
8241
  const verified = activation.source === "managed" ? await verifyManagedActivation(store, activation, managedSecretProviders, connectAgent, signal) : await verifyExternalActivation(activation, externalSecretProviders, connectAgent, signal);
8084
8242
  assertIdentityMatches(verified.whoami, activation, "authenticated whoami", `agent "${activation.alias}" pinned activation`);
8085
8243
  const boundTeamId = boundTeamIdFromWhoami(verified.whoami);
8086
- if (activation.boundTeamId !== boundTeamId) throw new ServeIdentityError("verification_failed", `authenticated whoami team binding does not match agent "${activation.alias}" pinned activation`);
8244
+ if (activation.boundTeamId !== boundTeamId) throw new AgentServerIdentityError("verification_failed", `authenticated whoami team binding does not match agent "${activation.alias}" pinned activation`);
8087
8245
  return {
8088
8246
  activation,
8089
8247
  config: verified.config,
@@ -8100,7 +8258,7 @@ async function verifyManagedActivation(store, activation, secretProviders, conne
8100
8258
  } catch (cause) {
8101
8259
  throw verificationError(`could not resolve the managed agent key for "${activation.alias}"`, cause);
8102
8260
  }
8103
- if (!agentKey) throw new ServeIdentityError("verification_failed", `managed config for "${activation.alias}" has no agent_key_ref`);
8261
+ if (!agentKey) throw new AgentServerIdentityError("verification_failed", `managed config for "${activation.alias}" has no agent_key_ref`);
8104
8262
  return {
8105
8263
  config,
8106
8264
  whoami: await callWhoami(connectAgent, {
@@ -8123,16 +8281,16 @@ async function verifyExternalActivation(activation, secretProviders, connectAgen
8123
8281
  /** Never let request or activation metadata redirect persisted credentials. */
8124
8282
  function requireTrustedApiOverride(override, configApiUrl, configPath) {
8125
8283
  if (!override) return configApiUrl;
8126
- if (override !== configApiUrl) throw new ServeIdentityError("verification_failed", `API override for ${configPath} does not match its configured endpoint`);
8284
+ if (override !== configApiUrl) throw new AgentServerIdentityError("verification_failed", `API override for ${configPath} does not match its configured endpoint`);
8127
8285
  return configApiUrl;
8128
8286
  }
8129
8287
  function assertActivatedConfig(config, activation, configPath, currentApiUrl, pinnedApiUrl) {
8130
- if (currentApiUrl !== pinnedApiUrl) throw new ServeIdentityError("verification_failed", `agent config at ${configPath} API endpoint does not match its pinned activation`);
8288
+ if (currentApiUrl !== pinnedApiUrl) throw new AgentServerIdentityError("verification_failed", `agent config at ${configPath} API endpoint does not match its pinned activation`);
8131
8289
  assertIdentityMatches(identityFromConfig(config), activation, configPath, `agent "${activation.alias}" pinned activation`);
8132
8290
  }
8133
8291
  function requireConfigApiUrl(config, configPath) {
8134
8292
  const apiUrl = config?.endpoints?.api?.trim();
8135
- if (!apiUrl) throw new ServeIdentityError("verification_failed", `agent config at ${configPath} is missing endpoints.api`);
8293
+ if (!apiUrl) throw new AgentServerIdentityError("verification_failed", `agent config at ${configPath} is missing endpoints.api`);
8136
8294
  return apiUrl;
8137
8295
  }
8138
8296
  function requireTrustedConfigApiUrl(config, configPath) {
@@ -8149,14 +8307,14 @@ async function readCurrentConfig(configPath) {
8149
8307
  try {
8150
8308
  file = await open(configPath, "r");
8151
8309
  } catch (cause) {
8152
- throw new ServeIdentityError("config_not_found", `agent config is missing at ${configPath}`, { cause });
8310
+ throw new AgentServerIdentityError("config_not_found", `agent config is missing at ${configPath}`, { cause });
8153
8311
  }
8154
8312
  let raw;
8155
8313
  try {
8156
- if (!(await file.stat()).isFile()) throw new ServeIdentityError("verification_failed", `agent config at ${configPath} must be a regular file no larger than ${MAX_CONFIG_BYTES} bytes`);
8314
+ if (!(await file.stat()).isFile()) throw new AgentServerIdentityError("verification_failed", `agent config at ${configPath} must be a regular file no larger than ${MAX_CONFIG_BYTES} bytes`);
8157
8315
  const buffer = Buffer.alloc(MAX_CONFIG_BYTES + 1);
8158
8316
  const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
8159
- if (bytesRead > MAX_CONFIG_BYTES) throw new ServeIdentityError("verification_failed", `agent config at ${configPath} must be a regular file no larger than ${MAX_CONFIG_BYTES} bytes`);
8317
+ if (bytesRead > MAX_CONFIG_BYTES) throw new AgentServerIdentityError("verification_failed", `agent config at ${configPath} must be a regular file no larger than ${MAX_CONFIG_BYTES} bytes`);
8160
8318
  raw = buffer.toString("utf8", 0, bytesRead);
8161
8319
  } finally {
8162
8320
  await file.close();
@@ -8171,7 +8329,7 @@ async function readCurrentConfig(configPath) {
8171
8329
  function externalAgentLocation(configPath) {
8172
8330
  const configDir = dirname(configPath);
8173
8331
  const moltnetDir = dirname(configDir);
8174
- if (!isAbsolute(configPath) || basename(configPath) !== "moltnet.json" || basename(moltnetDir) !== ".moltnet") throw new ServeIdentityError("config_not_found", `external config must be at an absolute <agent-root>/.moltnet/<agent>/moltnet.json path`);
8332
+ if (!isAbsolute(configPath) || basename(configPath) !== "moltnet.json" || basename(moltnetDir) !== ".moltnet") throw new AgentServerIdentityError("config_not_found", `external config must be at an absolute <agent-root>/.moltnet/<agent>/moltnet.json path`);
8175
8333
  return {
8176
8334
  agentName: assertStoreName("external config agent name", basename(configDir)),
8177
8335
  agentRoot: dirname(moltnetDir)
@@ -8203,7 +8361,7 @@ function identityFromConfig(config) {
8203
8361
  const identityId = config?.identity_id?.trim();
8204
8362
  const publicKey = config?.keys?.public_key?.trim();
8205
8363
  const fingerprint = config?.keys?.fingerprint?.trim();
8206
- if (!identityId || !publicKey || !fingerprint) throw new ServeIdentityError("verification_failed", "agent config is missing canonical identity_id, keys.public_key, or keys.fingerprint");
8364
+ if (!identityId || !publicKey || !fingerprint) throw new AgentServerIdentityError("verification_failed", "agent config is missing canonical identity_id, keys.public_key, or keys.fingerprint");
8207
8365
  return {
8208
8366
  identityId,
8209
8367
  publicKey,
@@ -8212,10 +8370,10 @@ function identityFromConfig(config) {
8212
8370
  }
8213
8371
  function assertIdentityMatches(current, expected, currentLabel, expectedLabel) {
8214
8372
  const assessment = assessIdentityPin(current, expected);
8215
- if (!assessment.ok) throw new ServeIdentityError("verification_failed", `${currentLabel} ${assessment.label} does not match ${expectedLabel}`);
8373
+ if (!assessment.ok) throw new AgentServerIdentityError("verification_failed", `${currentLabel} ${assessment.label} does not match ${expectedLabel}`);
8216
8374
  }
8217
8375
  function verificationError(message, cause) {
8218
- return new ServeIdentityError("verification_failed", message, { cause });
8376
+ return new AgentServerIdentityError("verification_failed", message, { cause });
8219
8377
  }
8220
8378
  /** Non-secret projection preserving the existing `/v1` response shape. */
8221
8379
  function publicAgentView(store, activation) {
@@ -8249,11 +8407,11 @@ function boundTeamIdFromWhoami(whoami) {
8249
8407
  }
8250
8408
  function requireActivation(store, alias) {
8251
8409
  const activation = store.readActivation(alias);
8252
- if (!activation) throw new ServeStoreError("not_found", `agent "${alias}" is not configured`);
8410
+ if (!activation) throw new AgentServerStoreError("not_found", `agent "${alias}" is not configured`);
8253
8411
  return activation;
8254
8412
  }
8255
8413
  //#endregion
8256
- //#region src/lib/serve/runs.ts
8414
+ //#region src/lib/agent-server/runs.ts
8257
8415
  var STOP_GRACE_MS = 1e4;
8258
8416
  var STOP_FORCE_MS = 2e3;
8259
8417
  var DEFAULT_MAX_LOG_BYTES = 10 * 1024 * 1024;
@@ -8292,19 +8450,19 @@ var INHERITED_MOLTNET_ENV_NAMES = new Set([
8292
8450
  "MOLTNET_SIGNER_URL",
8293
8451
  "MOLTNET_TRACE_IDLE_POLLING"
8294
8452
  ]);
8295
- var ServeRunError = class extends Error {
8296
- name = "ServeRunError";
8453
+ var AgentServerRunError = class extends Error {
8454
+ name = "AgentServerRunError";
8297
8455
  constructor(code, message) {
8298
8456
  super(message);
8299
8457
  this.code = code;
8300
8458
  }
8301
8459
  };
8302
8460
  function validateRunSpec(spec) {
8303
- if (spec.mode !== "poll" && spec.mode !== "drain") throw new ServeRunError("invalid_spec", "mode must be poll or drain");
8304
- if (!spec.agent) throw new ServeRunError("invalid_spec", "agent is required");
8305
- if (!spec.teamId) throw new ServeRunError("invalid_spec", "teamId is required");
8306
- if (!Array.isArray(spec.profiles) || spec.profiles.length === 0) throw new ServeRunError("invalid_spec", "at least one profile is required");
8307
- if (!Array.isArray(spec.taskTypes) || spec.taskTypes.length === 0) throw new ServeRunError("invalid_spec", "at least one task type is required");
8461
+ if (spec.mode !== "poll" && spec.mode !== "drain") throw new AgentServerRunError("invalid_spec", "mode must be poll or drain");
8462
+ if (!spec.agent) throw new AgentServerRunError("invalid_spec", "agent is required");
8463
+ if (!spec.teamId) throw new AgentServerRunError("invalid_spec", "teamId is required");
8464
+ if (!Array.isArray(spec.profiles) || spec.profiles.length === 0) throw new AgentServerRunError("invalid_spec", "at least one profile is required");
8465
+ if (!Array.isArray(spec.taskTypes) || spec.taskTypes.length === 0) throw new AgentServerRunError("invalid_spec", "at least one task type is required");
8308
8466
  }
8309
8467
  var RunManager = class {
8310
8468
  active = /* @__PURE__ */ new Map();
@@ -8327,7 +8485,7 @@ var RunManager = class {
8327
8485
  };
8328
8486
  }
8329
8487
  /** Assemble child env + args for a run. Exposed for tests. */
8330
- async prepare(spec, agent, piDir, providers = this.store.readProviders()) {
8488
+ async prepare(spec, agent, piDir, providers = this.store.readProviders(), runtimeModule) {
8331
8489
  const { activation, config } = agent;
8332
8490
  const homeDir = join(dirname(piDir), "home");
8333
8491
  const env = {
@@ -8351,6 +8509,7 @@ var RunManager = class {
8351
8509
  };
8352
8510
  })();
8353
8511
  const args = [
8512
+ ...runtimeModule ? ["--runtime", runtimeModule] : [],
8354
8513
  spec.mode,
8355
8514
  "--agent",
8356
8515
  target.agentName,
@@ -8362,7 +8521,7 @@ var RunManager = class {
8362
8521
  ...target.extraArgs
8363
8522
  ];
8364
8523
  if (activation.source === "managed") {
8365
- if (!config.agent_key_ref || !config.keys.private_key_ref) throw new ServeRunError("invalid_spec", `managed config for "${activation.alias}" is missing canonical secret references`);
8524
+ if (!config.agent_key_ref || !config.keys.private_key_ref) throw new AgentServerRunError("invalid_spec", `managed config for "${activation.alias}" is missing canonical secret references`);
8366
8525
  env["MOLTNET_API_URL"] = activation.apiUrl;
8367
8526
  env["MOLTNET_AGENT_KEY_REF"] = formatSecretReferenceString(config.agent_key_ref);
8368
8527
  env["MOLTNET_PRIVATE_KEY_REF"] = formatSecretReferenceString(config.keys.private_key_ref);
@@ -8380,7 +8539,7 @@ var RunManager = class {
8380
8539
  }
8381
8540
  if (!agentKey && config.keys.private_key_ref) env["MOLTNET_PRIVATE_KEY"] = await resolveIdentitySeed(config, this.options.externalSecretProviders);
8382
8541
  } catch {
8383
- throw new ServeRunError("invalid_spec", `external credentials for "${activation.alias}" could not be projected`);
8542
+ throw new AgentServerRunError("invalid_spec", `external credentials for "${activation.alias}" could not be projected`);
8384
8543
  }
8385
8544
  }
8386
8545
  env["MOLTNET_EXPECTED_IDENTITY_ID"] = activation.identityId;
@@ -8393,7 +8552,7 @@ var RunManager = class {
8393
8552
  try {
8394
8553
  value = await this.options.secretProviders.resolve(parseSecretReferenceString(provider.apiKeyRef));
8395
8554
  } catch {
8396
- throw new ServeRunError("invalid_spec", `provider "${providerId}" API key could not be resolved`);
8555
+ throw new AgentServerRunError("invalid_spec", `provider "${providerId}" API key could not be resolved`);
8397
8556
  }
8398
8557
  env[provider.envName] = value;
8399
8558
  }
@@ -8416,12 +8575,13 @@ var RunManager = class {
8416
8575
  this.assertStartOpen(signal);
8417
8576
  const agent = await (this.options.verifyActivationImpl ?? verifyAgentActivation)(this.store, spec.agent, this.options.secretProviders, this.options.externalSecretProviders, void 0, signal);
8418
8577
  this.assertStartOpen(signal);
8419
- if (agent.boundTeamId && agent.boundTeamId !== spec.teamId) throw new ServeRunError("invalid_spec", `agent "${spec.agent}" has a key bound to team ${agent.boundTeamId}; start the run with that team, or create a new agent with an enrollment token from team ${spec.teamId}`);
8578
+ if (agent.boundTeamId && agent.boundTeamId !== spec.teamId) throw new AgentServerRunError("invalid_spec", `agent "${spec.agent}" has a key bound to team ${agent.boundTeamId}; start the run with that team, or create a new agent with an enrollment token from team ${spec.teamId}`);
8420
8579
  const id = `${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
8421
8580
  const runDir = this.store.runDir(id);
8422
8581
  const piDir = join(runDir, "pi");
8423
8582
  const providers = this.store.readProviders();
8424
- const { args, env, cwd } = await this.prepare(spec, agent, piDir, providers);
8583
+ const runtimeModule = this.options.resolveRuntimeModule ? await this.options.resolveRuntimeModule(spec, agent, dirname(piDir)) : this.options.runtimeRegistry ? await this.resolveRuntimeModule(spec, agent, dirname(piDir)) : void 0;
8584
+ const { args, env, cwd } = await this.prepare(spec, agent, piDir, providers, runtimeModule);
8425
8585
  this.assertStartOpen(signal);
8426
8586
  let child;
8427
8587
  let logStream;
@@ -8449,20 +8609,20 @@ var RunManager = class {
8449
8609
  try {
8450
8610
  (this.options.symlinkImpl ?? symlinkSync)(this.store.piAuthJsonPath, join(piDir, "auth.json"));
8451
8611
  } catch (cause) {
8452
- throw new ServeStoreError("io_error", "could not link subscription credentials into the run", { cause });
8612
+ throw new AgentServerStoreError("io_error", "could not link subscription credentials into the run", { cause });
8453
8613
  }
8454
8614
  const entry = this.entrypoint();
8455
8615
  logStream = createWriteStream(logPath, {
8456
8616
  fd: openSync(logPath, "a", 384),
8457
8617
  autoClose: true
8458
8618
  });
8459
- logLimiter = createByteLimitTransform(this.options.maxLogBytes ?? DEFAULT_MAX_LOG_BYTES, () => this.log("warn", "serve run log output truncated", {
8619
+ logLimiter = createByteLimitTransform(this.options.maxLogBytes ?? DEFAULT_MAX_LOG_BYTES, () => this.log("warn", "agent server run log output truncated", {
8460
8620
  ...runContext(id, spec.agent, child),
8461
8621
  transition: "log_truncated"
8462
8622
  }));
8463
8623
  logLimiter.pipe(logStream);
8464
8624
  logStream.on("error", (error) => {
8465
- this.log("error", "serve run log stream failed", {
8625
+ this.log("error", "agent server run log stream failed", {
8466
8626
  ...runContext(id, spec.agent, child),
8467
8627
  transition: "log_failed",
8468
8628
  ...safeRunError(error)
@@ -8508,7 +8668,7 @@ var RunManager = class {
8508
8668
  this.active.delete(id);
8509
8669
  logLimiter?.end();
8510
8670
  const status = activeRun?.stopRequested ? "stopped" : code === 0 ? "exited" : "failed";
8511
- this.log(status === "failed" ? "error" : "info", "serve run exited", {
8671
+ this.log(status === "failed" ? "error" : "info", "agent server run exited", {
8512
8672
  ...runContext(id, spec.agent, child),
8513
8673
  transition: status,
8514
8674
  exitCode: code,
@@ -8522,7 +8682,7 @@ var RunManager = class {
8522
8682
  child.once("error", (error) => {
8523
8683
  this.active.delete(id);
8524
8684
  logLimiter?.end();
8525
- this.log("error", "serve run child process failed", {
8685
+ this.log("error", "agent server run child process failed", {
8526
8686
  ...runContext(id, spec.agent, child),
8527
8687
  transition: "spawn_failed",
8528
8688
  ...safeRunError(error)
@@ -8530,7 +8690,7 @@ var RunManager = class {
8530
8690
  this.persistRunCompletion(id, spec.agent, { status: "failed" });
8531
8691
  });
8532
8692
  this.store.writeRun(record);
8533
- this.log("info", "serve run started", {
8693
+ this.log("info", "agent server run started", {
8534
8694
  ...runContext(id, spec.agent, child),
8535
8695
  transition: "running"
8536
8696
  });
@@ -8544,7 +8704,7 @@ var RunManager = class {
8544
8704
  recursive: true,
8545
8705
  force: true
8546
8706
  });
8547
- this.log("error", "serve run failed to start", {
8707
+ this.log("error", "agent server run failed to start", {
8548
8708
  ...runContext(id, spec.agent, child),
8549
8709
  transition: "start_failed",
8550
8710
  ...safeRunError(cause)
@@ -8552,13 +8712,53 @@ var RunManager = class {
8552
8712
  throw cause;
8553
8713
  }
8554
8714
  }
8715
+ async resolveRuntimeModule(spec, activated, cwd) {
8716
+ const profiles = await resolveRuntimeProfiles({
8717
+ agent: await this.connectActivatedAgent(activated),
8718
+ profiles: spec.profiles,
8719
+ teamId: spec.teamId,
8720
+ cwd
8721
+ });
8722
+ const kinds = [...new Set(profiles.map((profile) => profile.runtimeKind))];
8723
+ if (kinds.length !== 1) throw new AgentServerRunError("invalid_spec", "All profiles in a server-managed run must use the same runtime kind.");
8724
+ const kind = kinds[0];
8725
+ let registration;
8726
+ try {
8727
+ registration = this.options.runtimeRegistry?.resolve(kind);
8728
+ } catch (cause) {
8729
+ this.log("warn", "registered runtime unavailable locally", {
8730
+ runtimeKind: kind,
8731
+ ...runtimeRegistryFailure(cause)
8732
+ });
8733
+ throw new AgentServerRunError("invalid_spec", `Registered runtime "${kind}" is unavailable locally; re-register it before starting a run.`);
8734
+ }
8735
+ if (registration) return registration.moduleUrl;
8736
+ if (kind === "gondolin_pi") return void 0;
8737
+ throw new AgentServerRunError("invalid_spec", `No local runtime is registered for profile kind "${kind}".`);
8738
+ }
8739
+ async connectActivatedAgent(activated) {
8740
+ const { activation, config } = activated;
8741
+ if (activation.source === "managed") {
8742
+ const agentKey = await resolveAgentKey(config, this.options.secretProviders);
8743
+ if (!agentKey) throw new AgentServerRunError("invalid_spec", `managed agent "${activation.alias}" has no agent key`);
8744
+ return connect({
8745
+ agentKey,
8746
+ apiUrl: activation.apiUrl
8747
+ });
8748
+ }
8749
+ return connect({
8750
+ configDir: dirname(activation.configPath),
8751
+ apiUrl: activation.apiUrl ?? activation.configApiUrl,
8752
+ secretProviders: this.options.externalSecretProviders
8753
+ });
8754
+ }
8555
8755
  stop(id) {
8556
8756
  const record = this.store.readRun(id);
8557
- if (!record) throw new ServeRunError("run_not_found", `run "${id}" was not found`);
8757
+ if (!record) throw new AgentServerRunError("run_not_found", `run "${id}" was not found`);
8558
8758
  const activeRun = this.active.get(id);
8559
- if (!activeRun) throw new ServeRunError("run_not_active", `run "${id}" is not running`);
8759
+ if (!activeRun) throw new AgentServerRunError("run_not_active", `run "${id}" is not running`);
8560
8760
  activeRun.stopRequested = true;
8561
- this.log("info", "serve run stop requested", {
8761
+ this.log("info", "agent server run stop requested", {
8562
8762
  ...runContext(id, record.agent, activeRun.child),
8563
8763
  transition: "stopping",
8564
8764
  signal: "SIGTERM"
@@ -8572,7 +8772,7 @@ var RunManager = class {
8572
8772
  /** Live status merged over the persisted record. */
8573
8773
  status(id) {
8574
8774
  const record = this.store.readRun(id);
8575
- if (!record) throw new ServeStoreError("not_found", `run "${id}" was not found`);
8775
+ if (!record) throw new AgentServerStoreError("not_found", `run "${id}" was not found`);
8576
8776
  return record;
8577
8777
  }
8578
8778
  list(limit = Number.POSITIVE_INFINITY) {
@@ -8620,7 +8820,7 @@ var RunManager = class {
8620
8820
  this.closing = true;
8621
8821
  for (const [id, activeRun] of this.active) {
8622
8822
  activeRun.stopRequested = true;
8623
- this.log("warn", "serve run force stop requested", {
8823
+ this.log("warn", "agent server run force stop requested", {
8624
8824
  ...runContext(id, this.store.readRun(id)?.agent ?? "unknown", activeRun.child),
8625
8825
  transition: "force_stopping",
8626
8826
  signal: "SIGKILL"
@@ -8638,7 +8838,7 @@ var RunManager = class {
8638
8838
  exitCode: null,
8639
8839
  endedAt
8640
8840
  });
8641
- this.log("warn", "serve run interrupted by supervisor replacement", {
8841
+ this.log("warn", "agent server run interrupted by supervisor replacement", {
8642
8842
  runId: record.id,
8643
8843
  agent: record.agent,
8644
8844
  pid: record.pid,
@@ -8647,10 +8847,10 @@ var RunManager = class {
8647
8847
  }
8648
8848
  }
8649
8849
  reserveStart(agent) {
8650
- if (this.closing) throw new ServeRunError("invalid_spec", "serve is shutting down");
8850
+ if (this.closing) throw new AgentServerRunError("invalid_spec", "agent server is shutting down");
8651
8851
  const agentStarting = this.startingByAgent.get(agent) ?? 0;
8652
8852
  const agentActive = [...this.active.values()].filter((run) => run.agent === agent).length;
8653
- if (this.active.size + this.starting >= (this.options.maxActiveRuns ?? DEFAULT_MAX_ACTIVE_RUNS) || agentActive + agentStarting >= (this.options.maxActiveRunsPerAgent ?? DEFAULT_MAX_ACTIVE_RUNS_PER_AGENT)) throw new ServeRunError("invalid_spec", `active run limit reached for agent "${agent}"`);
8853
+ if (this.active.size + this.starting >= (this.options.maxActiveRuns ?? DEFAULT_MAX_ACTIVE_RUNS) || agentActive + agentStarting >= (this.options.maxActiveRunsPerAgent ?? DEFAULT_MAX_ACTIVE_RUNS_PER_AGENT)) throw new AgentServerRunError("invalid_spec", `active run limit reached for agent "${agent}"`);
8654
8854
  this.starting += 1;
8655
8855
  this.startingByAgent.set(agent, agentStarting + 1);
8656
8856
  return () => {
@@ -8661,7 +8861,7 @@ var RunManager = class {
8661
8861
  };
8662
8862
  }
8663
8863
  assertStartOpen(signal) {
8664
- if (this.closing || signal?.aborted) throw new ServeRunError("invalid_spec", "serve is shutting down");
8864
+ if (this.closing || signal?.aborted) throw new AgentServerRunError("invalid_spec", "agent server is shutting down");
8665
8865
  }
8666
8866
  pruneCompletedRuns() {
8667
8867
  const removed = this.store.pruneCompletedRuns({
@@ -8670,7 +8870,7 @@ var RunManager = class {
8670
8870
  maxBytes: this.options.maxRunStorageBytes ?? DEFAULT_MAX_RUN_STORAGE_BYTES,
8671
8871
  now: this.options.now?.() ?? /* @__PURE__ */ new Date()
8672
8872
  });
8673
- if (removed.length > 0) this.log("info", "serve run retention removed completed artifacts", {
8873
+ if (removed.length > 0) this.log("info", "agent server run retention removed completed artifacts", {
8674
8874
  transition: "pruned",
8675
8875
  removedCount: removed.length
8676
8876
  });
@@ -8685,7 +8885,7 @@ var RunManager = class {
8685
8885
  });
8686
8886
  this.pruneCompletedRuns();
8687
8887
  } catch (error) {
8688
- this.log("error", "serve run completion persistence failed", {
8888
+ this.log("error", "agent server run completion persistence failed", {
8689
8889
  runId: id,
8690
8890
  agent,
8691
8891
  transition: "persistence_failed",
@@ -8734,6 +8934,14 @@ function safeRunError(error) {
8734
8934
  ...typeof code === "string" ? { errorCode: code } : {}
8735
8935
  };
8736
8936
  }
8937
+ function runtimeRegistryFailure(error) {
8938
+ const code = error?.code;
8939
+ return {
8940
+ errorType: error instanceof Error ? error.name : typeof error,
8941
+ ...typeof code === "string" ? { errorCode: code } : {},
8942
+ ...error instanceof Error ? { errorMessage: error.message.slice(0, 512) } : {}
8943
+ };
8944
+ }
8737
8945
  function sanitizeChildBaseEnv(baseEnv) {
8738
8946
  return Object.fromEntries(Object.entries(baseEnv).filter(([name]) => INHERITED_ENV_NAMES.has(name) || INHERITED_MOLTNET_ENV_NAMES.has(name)));
8739
8947
  }
@@ -8750,63 +8958,169 @@ async function terminateChild(child) {
8750
8958
  });
8751
8959
  }
8752
8960
  //#endregion
8753
- //#region src/lib/serve/serve-lock.ts
8754
- var ServeLockError = class extends Error {
8755
- name = "ServeLockError";
8756
- constructor(code, message, options) {
8757
- super(message, options);
8758
- this.code = code;
8759
- }
8760
- };
8761
- /**
8762
- * Acquire the per-root singleton lock. `proper-lockfile` uses an atomic lock
8763
- * directory at exactly `<root>/serve.lock`, recovers stale owners, and keeps
8764
- * the mtime fresh while the supervisor is alive.
8765
- */
8766
- async function acquireServeLock(root, options = {}) {
8767
- const path = join(root, "serve.lock");
8768
- let releaseLock;
8769
- try {
8770
- releaseLock = await lock(root, {
8771
- lockfilePath: path,
8772
- realpath: false,
8773
- retries: 0,
8774
- ...options.staleMs === void 0 ? {} : { stale: options.staleMs },
8775
- ...options.updateMs === void 0 ? {} : { update: options.updateMs },
8776
- onCompromised: (cause) => {
8777
- const error = new ServeLockError("compromised", `serve lock ${path} was compromised: ${cause.message}`, { cause });
8778
- if (options.onCompromised) {
8779
- options.onCompromised(error);
8780
- return;
8781
- }
8782
- throw error;
8783
- }
8784
- });
8785
- } catch (cause) {
8786
- if (cause.code === "ELOCKED") throw new ServeLockError("held", `another moltnet-agent serve process already owns ${path}`, { cause });
8787
- throw new ServeLockError("failed", `could not acquire serve lock ${path}: ${cause.message}`, { cause });
8961
+ //#region src/runtime-loader.ts
8962
+ function extractRuntimeModule(argv) {
8963
+ const remaining = [];
8964
+ let specifier;
8965
+ let parsingOptions = true;
8966
+ for (let index = 0; index < argv.length; index += 1) {
8967
+ const argument = argv[index];
8968
+ if (argument === "--") {
8969
+ parsingOptions = false;
8970
+ remaining.push(argument);
8971
+ continue;
8972
+ }
8973
+ if (!parsingOptions) {
8974
+ remaining.push(argument);
8975
+ continue;
8976
+ }
8977
+ let candidate;
8978
+ if (argument === "--runtime") {
8979
+ candidate = argv[index + 1];
8980
+ if (!candidate) throw new Error("Missing value for --runtime");
8981
+ index += 1;
8982
+ } else if (argument.startsWith("--runtime=")) {
8983
+ candidate = argument.slice(10);
8984
+ if (!candidate) throw new Error("Missing value for --runtime");
8985
+ } else {
8986
+ remaining.push(argument);
8987
+ continue;
8988
+ }
8989
+ if (specifier) throw new Error("--runtime may be specified only once");
8990
+ specifier = candidate;
8788
8991
  }
8789
- let released = false;
8790
8992
  return {
8791
- path,
8792
- async release() {
8793
- if (released) return;
8794
- released = true;
8795
- await releaseLock();
8796
- }
8993
+ argv: remaining,
8994
+ specifier
8797
8995
  };
8798
8996
  }
8799
- /** Always release after normal completion or startup/runtime failure. */
8800
- async function withServeLock(root, work, options) {
8801
- const held = await acquireServeLock(root, options);
8997
+ function resolveRuntimeModuleUrl(specifier, cwd = process.cwd()) {
8998
+ if (specifier.startsWith("file:")) return new URL(specifier).href;
8999
+ if (isAbsolute(specifier) || specifier.startsWith("./") || specifier.startsWith("../")) return pathToFileURL(resolve(cwd, specifier)).href;
9000
+ return pathToFileURL(createRequire(resolve(cwd, "package.json")).resolve(specifier)).href;
9001
+ }
9002
+ async function loadDaemonRuntimeAdapter(specifier, options = {}) {
9003
+ let moduleUrl;
8802
9004
  try {
8803
- return await work();
8804
- } finally {
8805
- await held.release();
9005
+ moduleUrl = resolveRuntimeModuleUrl(specifier, options.cwd);
9006
+ } catch (cause) {
9007
+ throw new Error(`Unable to resolve runtime module "${specifier}" from ${options.cwd ?? process.cwd()}`, { cause });
9008
+ }
9009
+ let namespace;
9010
+ try {
9011
+ namespace = await import(moduleUrl);
9012
+ } catch (cause) {
9013
+ throw new Error(`Unable to load runtime module "${specifier}"`, { cause });
9014
+ }
9015
+ const adapter = namespace.default;
9016
+ if (!isDaemonRuntimeAdapter(adapter)) throw new Error(`Runtime module "${specifier}" must default-export a DaemonRuntimeAdapter`);
9017
+ return adapter;
9018
+ }
9019
+ function isDaemonRuntimeAdapter(value) {
9020
+ if (!value || typeof value !== "object") return false;
9021
+ const candidate = value;
9022
+ return typeof candidate.runtimeKind === "string" && candidate.runtimeKind.length > 0 && typeof candidate.prepare === "function";
9023
+ }
9024
+ //#endregion
9025
+ //#region src/lib/agent-server/runtime-registry.ts
9026
+ var LOCKFILE_NAMES = [
9027
+ "pnpm-lock.yaml",
9028
+ "package-lock.json",
9029
+ "yarn.lock",
9030
+ "bun.lockb"
9031
+ ];
9032
+ /** Local, operator-owned allowlist for executable daemon runtime modules. */
9033
+ var RuntimeRegistry = class {
9034
+ constructor(root) {
9035
+ this.root = root;
9036
+ }
9037
+ get path() {
9038
+ return join(this.root, "runtime-registry.json");
8806
9039
  }
9040
+ list() {
9041
+ if (!existsSync(this.path)) return [];
9042
+ const value = JSON.parse(readFileSync(this.path, "utf8"));
9043
+ if (!Array.isArray(value) || !value.every(isRegistration)) throw new AgentServerStoreError("invalid_state", "runtime registry contains an invalid registration");
9044
+ return value;
9045
+ }
9046
+ async register(kind, specifier, cwd = process.cwd()) {
9047
+ kind = assertStoreName("runtime kind", kind);
9048
+ mkdirSync(this.root, {
9049
+ recursive: true,
9050
+ mode: 448
9051
+ });
9052
+ const moduleUrl = resolveRuntimeModuleUrl(specifier, cwd);
9053
+ const adapter = await loadDaemonRuntimeAdapter(specifier, { cwd });
9054
+ if (adapter.runtimeKind !== kind) throw new Error(`Runtime module provides "${adapter.runtimeKind}", not registered kind "${kind}".`);
9055
+ if (!moduleUrl.startsWith("file:")) throw new Error("Runtime registration must resolve to a local file URL.");
9056
+ const entryHash = hashFile(new URL(moduleUrl));
9057
+ const lockfilePath = isPackageSpecifier(specifier) ? findLockfile(cwd) : void 0;
9058
+ if (isPackageSpecifier(specifier) && !lockfilePath) throw new Error("Package runtime registration requires a pnpm, npm, Yarn, or Bun lockfile in the current project or a parent directory.");
9059
+ const entry = {
9060
+ kind,
9061
+ moduleUrl,
9062
+ entryHash,
9063
+ ...lockfilePath ? {
9064
+ lockfilePath,
9065
+ lockfileHash: hashPath(lockfilePath)
9066
+ } : {},
9067
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
9068
+ };
9069
+ const entries = this.list().filter((candidate) => candidate.kind !== kind);
9070
+ entries.push(entry);
9071
+ writeRegistry(this.path, entries);
9072
+ return entry;
9073
+ }
9074
+ unregister(kind) {
9075
+ kind = assertStoreName("runtime kind", kind);
9076
+ const entries = this.list();
9077
+ const next = entries.filter((entry) => entry.kind !== kind);
9078
+ if (next.length === entries.length) return false;
9079
+ writeRegistry(this.path, next);
9080
+ return true;
9081
+ }
9082
+ resolve(kind) {
9083
+ kind = assertStoreName("runtime kind", kind);
9084
+ const entry = this.list().find((candidate) => candidate.kind === kind);
9085
+ if (!entry) return void 0;
9086
+ if (hashFile(new URL(entry.moduleUrl)) !== entry.entryHash) throw new Error(`Registered runtime "${kind}" has changed; re-register it before starting a run.`);
9087
+ if (entry.lockfilePath && (!existsSync(entry.lockfilePath) || hashPath(entry.lockfilePath) !== entry.lockfileHash)) throw new Error(`Registered runtime "${kind}" lockfile has changed; re-register it before starting a run.`);
9088
+ return entry;
9089
+ }
9090
+ };
9091
+ function hashFile(url) {
9092
+ return createHash("sha256").update(readFileSync(url)).digest("hex");
8807
9093
  }
8808
- var ServeModelDiscoveryError = class extends Error {
8809
- name = "ServeModelDiscoveryError";
9094
+ function hashPath(path) {
9095
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
9096
+ }
9097
+ function isPackageSpecifier(specifier) {
9098
+ return !specifier.startsWith("file:") && !specifier.startsWith(".") && !specifier.startsWith("/");
9099
+ }
9100
+ function findLockfile(cwd) {
9101
+ let current = resolve(cwd);
9102
+ for (;;) {
9103
+ for (const name of LOCKFILE_NAMES) {
9104
+ const candidate = join(current, name);
9105
+ if (existsSync(candidate)) return candidate;
9106
+ }
9107
+ const parent = dirname(current);
9108
+ if (parent === current) return void 0;
9109
+ current = parent;
9110
+ }
9111
+ }
9112
+ function isRegistration(value) {
9113
+ if (!value || typeof value !== "object") return false;
9114
+ const entry = value;
9115
+ return typeof entry.kind === "string" && typeof entry.moduleUrl === "string" && entry.moduleUrl.startsWith("file:") && typeof entry.entryHash === "string" && typeof entry.registeredAt === "string" && (entry.lockfilePath === void 0 || typeof entry.lockfilePath === "string") && (entry.lockfileHash === void 0 || typeof entry.lockfileHash === "string") && entry.lockfilePath === void 0 === (entry.lockfileHash === void 0);
9116
+ }
9117
+ function writeRegistry(path, entries) {
9118
+ const temp = `${path}.${process.pid}.tmp`;
9119
+ writeFileSync(temp, `${JSON.stringify(entries, null, 2)}\n`, { mode: 384 });
9120
+ renameSync(temp, path);
9121
+ }
9122
+ var AgentServerModelDiscoveryError = class extends Error {
9123
+ name = "AgentServerModelDiscoveryError";
8810
9124
  constructor(code, message, statusCode, options) {
8811
9125
  super(message, options);
8812
9126
  this.code = code;
@@ -8847,32 +9161,352 @@ function parseProviderBaseUrl(value, providerId) {
8847
9161
  try {
8848
9162
  parsed = new URL(value);
8849
9163
  } catch (cause) {
8850
- throw new ServeModelDiscoveryError("invalid_provider", `provider "${providerId}" has an invalid base URL`, 400, { cause });
9164
+ throw new AgentServerModelDiscoveryError("invalid_provider", `provider "${providerId}" has an invalid base URL`, 400, { cause });
8851
9165
  }
8852
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) throw new ServeModelDiscoveryError("invalid_provider", `provider "${providerId}" base URL must be HTTP(S) without credentials, query, or fragment`, 400);
9166
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) throw new AgentServerModelDiscoveryError("invalid_provider", `provider "${providerId}" base URL must be HTTP(S) without credentials, query, or fragment`, 400);
9167
+ if (isNonLoopbackPrivateAddress(parsed.hostname)) throw new AgentServerModelDiscoveryError("invalid_provider", `provider "${providerId}" base URL must not target a private network address`, 400);
8853
9168
  return parsed;
8854
9169
  }
9170
+ function isNonLoopbackPrivateAddress(hostname) {
9171
+ if (isIP(hostname) !== 4) return false;
9172
+ const [first, second] = hostname.split(".").map(Number);
9173
+ if (first === 127) return false;
9174
+ return first === 10 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168 || first === 169 && second === 254;
9175
+ }
8855
9176
  function discoveryFailure(providerId, failures) {
8856
- if (failures.some((failure) => failure.kind === "http" && (failure.status === 401 || failure.status === 403))) return new ServeModelDiscoveryError("discovery_unauthorized", `provider "${providerId}" rejected model discovery; check its API key`, 502);
8857
- if (failures.some((failure) => failure.kind === "network")) return new ServeModelDiscoveryError("discovery_unavailable", `provider "${providerId}" could not be reached for model discovery`, 502);
8858
- if (failures.some((failure) => failure.kind === "invalid_response")) return new ServeModelDiscoveryError("discovery_invalid_response", `provider "${providerId}" returned an invalid model response`, 502);
8859
- return new ServeModelDiscoveryError("discovery_failed", `no models discovered for provider "${providerId}"`, 502);
9177
+ if (failures.some((failure) => failure.kind === "http" && (failure.status === 401 || failure.status === 403))) return new AgentServerModelDiscoveryError("discovery_unauthorized", `provider "${providerId}" rejected model discovery; check its API key`, 502);
9178
+ if (failures.some((failure) => failure.kind === "network")) return new AgentServerModelDiscoveryError("discovery_unavailable", `provider "${providerId}" could not be reached for model discovery`, 502);
9179
+ if (failures.some((failure) => failure.kind === "invalid_response")) return new AgentServerModelDiscoveryError("discovery_invalid_response", `provider "${providerId}" returned an invalid model response`, 502);
9180
+ return new AgentServerModelDiscoveryError("discovery_failed", `no models discovered for provider "${providerId}"`, 502);
8860
9181
  }
8861
9182
  function isRecord(value) {
8862
9183
  return typeof value === "object" && value !== null && !Array.isArray(value);
8863
9184
  }
8864
9185
  //#endregion
8865
- //#region src/lib/serve/server.ts
9186
+ //#region src/lib/agent-server/protocol.ts
9187
+ var DateTime = Type.String({ format: "date-time" });
9188
+ var StringList = Type.Array(Type.String());
9189
+ function schemaRef(schema) {
9190
+ const id = schema.$id;
9191
+ if (typeof id !== "string" || id.length === 0) throw new Error("Agent Server protocol schemas must have an identifier");
9192
+ return Type.Ref(id);
9193
+ }
9194
+ var AgentServerHealthSchema = Type.Object({ status: Type.Literal("ok") }, { $id: "AgentServerHealth" });
9195
+ var AgentServerProblemSchema = Type.Object({
9196
+ code: Type.String(),
9197
+ message: Type.String()
9198
+ }, { $id: "AgentServerProblem" });
9199
+ var AgentServerAgentSchema = Type.Object({
9200
+ kind: Type.Union([Type.Literal("managed"), Type.Literal("external")]),
9201
+ agentName: Type.String(),
9202
+ identityId: Type.Optional(Type.String()),
9203
+ fingerprint: Type.Optional(Type.String()),
9204
+ apiUrl: Type.Optional(Type.String()),
9205
+ teamId: Type.Optional(Type.String()),
9206
+ configDir: Type.Optional(Type.String()),
9207
+ createdAt: DateTime,
9208
+ hasAgentKey: Type.Optional(Type.Boolean()),
9209
+ hasPrivateKey: Type.Optional(Type.Boolean())
9210
+ }, { $id: "AgentServerAgent" });
9211
+ var AgentServerProviderSchema = Type.Object({
9212
+ api: Type.String(),
9213
+ baseUrl: Type.String({ format: "uri" }),
9214
+ envName: Type.String(),
9215
+ models: StringList,
9216
+ hasApiKey: Type.Boolean()
9217
+ }, { $id: "AgentServerProvider" });
9218
+ var AgentServerRunRecordSchema = Type.Object({
9219
+ id: Type.String(),
9220
+ agent: Type.String(),
9221
+ teamId: Type.String(),
9222
+ profiles: StringList,
9223
+ taskTypes: StringList,
9224
+ mode: Type.Union([Type.Literal("poll"), Type.Literal("drain")]),
9225
+ status: Type.Union([
9226
+ Type.Literal("running"),
9227
+ Type.Literal("exited"),
9228
+ Type.Literal("stopped"),
9229
+ Type.Literal("failed")
9230
+ ]),
9231
+ pid: Type.Optional(Type.Number()),
9232
+ exitCode: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
9233
+ startedAt: DateTime,
9234
+ endedAt: Type.Optional(DateTime)
9235
+ }, { $id: "AgentServerRunRecord" });
9236
+ var AgentServerRunSchema = Type.Intersect([schemaRef(AgentServerRunRecordSchema), Type.Object({ active: Type.Boolean() })], { $id: "AgentServerRun" });
9237
+ var AgentServerSubscriptionSchema = Type.Object({
9238
+ id: Type.String(),
9239
+ name: Type.String(),
9240
+ connected: Type.Boolean()
9241
+ }, { $id: "AgentServerSubscription" });
9242
+ var AgentServerSubscriptionLoginSchema = Type.Object({
9243
+ providerId: Type.String(),
9244
+ status: Type.Union([
9245
+ Type.Literal("pending"),
9246
+ Type.Literal("completed"),
9247
+ Type.Literal("failed")
9248
+ ]),
9249
+ authUrl: Type.Optional(Type.String({ format: "uri" })),
9250
+ instructions: Type.Optional(Type.String()),
9251
+ userCode: Type.Optional(Type.String()),
9252
+ verificationUri: Type.Optional(Type.String({ format: "uri" })),
9253
+ error: Type.Optional(Type.String())
9254
+ }, { $id: "AgentServerSubscriptionLogin" });
9255
+ var AgentServerStatusSchema = Type.Object({
9256
+ version: Type.String(),
9257
+ platform: Type.String(),
9258
+ subscriptions: Type.Array(schemaRef(AgentServerSubscriptionSchema)),
9259
+ agents: Type.Array(schemaRef(AgentServerAgentSchema)),
9260
+ providers: Type.Record(Type.String(), schemaRef(AgentServerProviderSchema)),
9261
+ runs: Type.Array(schemaRef(AgentServerRunSchema))
9262
+ }, { $id: "AgentServerStatus" });
9263
+ var PairingStartedSchema = Type.Object({
9264
+ pairingId: Type.String(),
9265
+ approvalPath: Type.String()
9266
+ }, { $id: "PairingStarted" });
9267
+ var PairingClaimedSchema = Type.Object({ token: Type.String() }, { $id: "PairingClaimed" });
9268
+ var PairingParamsSchema = Type.Object({ pairingId: Type.String() });
9269
+ var ProviderParamsSchema = Type.Object({ providerId: Type.String() });
9270
+ var AgentParamsSchema = Type.Object({ agentName: Type.String() });
9271
+ var RunParamsSchema = Type.Object({ runId: Type.String() });
9272
+ var CreateAgentSchema = Type.Object({
9273
+ kind: Type.Union([Type.Literal("managed"), Type.Literal("external")]),
9274
+ name: Type.String()
9275
+ }, { anyOf: [Type.Object({
9276
+ kind: Type.Literal("managed"),
9277
+ enrollmentToken: Type.String()
9278
+ }), Type.Object({
9279
+ kind: Type.Literal("external"),
9280
+ configDir: Type.String(),
9281
+ apiUrl: Type.Optional(Type.String({ format: "uri" }))
9282
+ })] });
9283
+ var ReconcileAgentSchema = Type.Object({ action: Type.Union([Type.Literal("resume"), Type.Literal("abandon")]) });
9284
+ var ReconcileAgentResultSchema = Type.Union([schemaRef(AgentServerAgentSchema), Type.Object({ abandoned: Type.Literal(true) })], { $id: "ReconcileAgentResult" });
9285
+ var PutProviderSchema = Type.Object({
9286
+ api: Type.String(),
9287
+ baseUrl: Type.String({ format: "uri" }),
9288
+ envName: Type.String(),
9289
+ models: StringList,
9290
+ apiKey: Type.Optional(Type.String())
9291
+ });
9292
+ var DiscoverModelsSchema = Type.Object({ models: StringList }, { $id: "DiscoveredModels" });
9293
+ var StartRunSchema = Type.Object({
9294
+ agent: Type.String(),
9295
+ teamId: Type.String(),
9296
+ profiles: StringList,
9297
+ taskTypes: StringList,
9298
+ mode: Type.Union([Type.Literal("poll"), Type.Literal("drain")])
9299
+ });
9300
+ var CancelledSubscriptionSchema = Type.Object({
9301
+ providerId: Type.String(),
9302
+ status: Type.Literal("cancelled")
9303
+ }, { $id: "CancelledSubscription" });
9304
+ var LogStreamSchema = Type.String({
9305
+ $id: "AgentServerLogStream",
9306
+ contentMediaType: "text/event-stream"
9307
+ });
9308
+ var AGENT_SERVER_SCHEMAS = [
9309
+ AgentServerHealthSchema,
9310
+ AgentServerProblemSchema,
9311
+ AgentServerAgentSchema,
9312
+ AgentServerProviderSchema,
9313
+ AgentServerRunRecordSchema,
9314
+ AgentServerRunSchema,
9315
+ AgentServerSubscriptionSchema,
9316
+ AgentServerSubscriptionLoginSchema,
9317
+ AgentServerStatusSchema,
9318
+ PairingStartedSchema,
9319
+ PairingClaimedSchema,
9320
+ ReconcileAgentResultSchema,
9321
+ DiscoverModelsSchema,
9322
+ CancelledSubscriptionSchema,
9323
+ LogStreamSchema
9324
+ ];
9325
+ var pairedSecurity = [{ agentServerToken: [] }];
9326
+ var problemResponse = { default: schemaRef(AgentServerProblemSchema) };
9327
+ var AgentServerRouteSchemas = {
9328
+ health: {
9329
+ operationId: "getAgentServerHealth",
9330
+ tags: ["system"],
9331
+ response: { 200: schemaRef(AgentServerHealthSchema) }
9332
+ },
9333
+ startPairing: {
9334
+ operationId: "startAgentServerPairing",
9335
+ tags: ["pairing"],
9336
+ response: {
9337
+ 201: schemaRef(PairingStartedSchema),
9338
+ ...problemResponse
9339
+ }
9340
+ },
9341
+ claimPairing: {
9342
+ operationId: "claimAgentServerPairing",
9343
+ tags: ["pairing"],
9344
+ params: PairingParamsSchema,
9345
+ response: {
9346
+ 200: schemaRef(PairingClaimedSchema),
9347
+ ...problemResponse
9348
+ }
9349
+ },
9350
+ status: {
9351
+ operationId: "getAgentServerStatus",
9352
+ tags: ["system"],
9353
+ security: pairedSecurity,
9354
+ response: {
9355
+ 200: schemaRef(AgentServerStatusSchema),
9356
+ ...problemResponse
9357
+ }
9358
+ },
9359
+ listAgents: {
9360
+ operationId: "listAgentServerAgents",
9361
+ tags: ["agents"],
9362
+ security: pairedSecurity,
9363
+ response: {
9364
+ 200: Type.Array(schemaRef(AgentServerAgentSchema)),
9365
+ ...problemResponse
9366
+ }
9367
+ },
9368
+ createAgent: {
9369
+ operationId: "createAgentServerAgent",
9370
+ tags: ["agents"],
9371
+ security: pairedSecurity,
9372
+ body: CreateAgentSchema,
9373
+ response: {
9374
+ 201: schemaRef(AgentServerAgentSchema),
9375
+ ...problemResponse
9376
+ }
9377
+ },
9378
+ reconcileAgent: {
9379
+ operationId: "reconcileAgentServerAgent",
9380
+ tags: ["agents"],
9381
+ security: pairedSecurity,
9382
+ params: AgentParamsSchema,
9383
+ body: ReconcileAgentSchema,
9384
+ response: {
9385
+ 200: schemaRef(ReconcileAgentResultSchema),
9386
+ ...problemResponse
9387
+ }
9388
+ },
9389
+ listProviders: {
9390
+ operationId: "listAgentServerProviders",
9391
+ tags: ["providers"],
9392
+ security: pairedSecurity,
9393
+ response: {
9394
+ 200: Type.Record(Type.String(), schemaRef(AgentServerProviderSchema)),
9395
+ ...problemResponse
9396
+ }
9397
+ },
9398
+ discoverModels: {
9399
+ operationId: "discoverAgentServerProviderModels",
9400
+ tags: ["providers"],
9401
+ security: pairedSecurity,
9402
+ params: ProviderParamsSchema,
9403
+ response: {
9404
+ 200: schemaRef(DiscoverModelsSchema),
9405
+ ...problemResponse
9406
+ }
9407
+ },
9408
+ putProvider: {
9409
+ operationId: "putAgentServerProvider",
9410
+ tags: ["providers"],
9411
+ security: pairedSecurity,
9412
+ params: ProviderParamsSchema,
9413
+ body: PutProviderSchema,
9414
+ response: {
9415
+ 200: schemaRef(AgentServerProviderSchema),
9416
+ ...problemResponse
9417
+ }
9418
+ },
9419
+ listSubscriptions: {
9420
+ operationId: "listAgentServerSubscriptions",
9421
+ tags: ["subscriptions"],
9422
+ security: pairedSecurity,
9423
+ response: {
9424
+ 200: Type.Array(schemaRef(AgentServerSubscriptionSchema)),
9425
+ ...problemResponse
9426
+ }
9427
+ },
9428
+ startSubscriptionLogin: {
9429
+ operationId: "startAgentServerSubscriptionLogin",
9430
+ tags: ["subscriptions"],
9431
+ security: pairedSecurity,
9432
+ params: ProviderParamsSchema,
9433
+ response: {
9434
+ 201: schemaRef(AgentServerSubscriptionLoginSchema),
9435
+ ...problemResponse
9436
+ }
9437
+ },
9438
+ getSubscriptionLogin: {
9439
+ operationId: "getAgentServerSubscriptionLogin",
9440
+ tags: ["subscriptions"],
9441
+ security: pairedSecurity,
9442
+ params: ProviderParamsSchema,
9443
+ response: {
9444
+ 200: schemaRef(AgentServerSubscriptionLoginSchema),
9445
+ ...problemResponse
9446
+ }
9447
+ },
9448
+ cancelSubscriptionLogin: {
9449
+ operationId: "cancelAgentServerSubscriptionLogin",
9450
+ tags: ["subscriptions"],
9451
+ security: pairedSecurity,
9452
+ params: ProviderParamsSchema,
9453
+ response: {
9454
+ 200: schemaRef(CancelledSubscriptionSchema),
9455
+ ...problemResponse
9456
+ }
9457
+ },
9458
+ listRuns: {
9459
+ operationId: "listAgentServerRuns",
9460
+ tags: ["runs"],
9461
+ security: pairedSecurity,
9462
+ response: {
9463
+ 200: Type.Array(schemaRef(AgentServerRunSchema)),
9464
+ ...problemResponse
9465
+ }
9466
+ },
9467
+ startRun: {
9468
+ operationId: "startAgentServerRun",
9469
+ tags: ["runs"],
9470
+ security: pairedSecurity,
9471
+ body: StartRunSchema,
9472
+ response: {
9473
+ 201: schemaRef(AgentServerRunSchema),
9474
+ ...problemResponse
9475
+ }
9476
+ },
9477
+ stopRun: {
9478
+ operationId: "stopAgentServerRun",
9479
+ tags: ["runs"],
9480
+ security: pairedSecurity,
9481
+ params: RunParamsSchema,
9482
+ response: {
9483
+ 200: schemaRef(AgentServerRunRecordSchema),
9484
+ ...problemResponse
9485
+ }
9486
+ },
9487
+ streamRunLogs: {
9488
+ operationId: "streamAgentServerRunLogs",
9489
+ tags: ["runs"],
9490
+ security: pairedSecurity,
9491
+ params: RunParamsSchema,
9492
+ response: {
9493
+ 200: schemaRef(LogStreamSchema),
9494
+ ...problemResponse
9495
+ }
9496
+ }
9497
+ };
9498
+ //#endregion
9499
+ //#region src/lib/agent-server/server.ts
8866
9500
  /**
8867
- * HTTP surface of `moltnet-agent serve` (#2061), built on the shared
9501
+ * HTTP surface of `moltnet-agent server` (#2061), built on the shared
8868
9502
  * loopback-companion security profile (#2066): loopback Host enforcement,
8869
9503
  * exact-origin CORS, Fetch-Metadata guards, strict JSON parsing.
8870
9504
  *
8871
9505
  * Everything under `/v1` except the pairing bootstrap requires a paired
8872
- * origin: the `x-moltnet-serve-token` header must verify against the
9506
+ * origin: the `x-moltnet-agent-server-token` header must verify against the
8873
9507
  * origin-bound token issued by the one-click pairing ceremony.
8874
9508
  */
8875
- var SERVE_TOKEN_HEADER = "x-moltnet-serve-token";
9509
+ var AGENT_SERVER_TOKEN_HEADER = "x-moltnet-agent-server-token";
8876
9510
  var BODY_LIMIT = 64 * 1024;
8877
9511
  var LOG_POLL_INTERVAL_MS = 500;
8878
9512
  var LOG_STREAM_MAX_DURATION_MS = 3600 * 1e3;
@@ -8881,7 +9515,7 @@ var LOG_READ_LIMIT_BYTES = 256 * 1024;
8881
9515
  var RUN_HISTORY_LIMIT = 100;
8882
9516
  var RATE_LIMIT_MAX = 120;
8883
9517
  var RATE_LIMIT_WINDOW_MS = 6e4;
8884
- async function readServeLogDelta(handle, state, limit = LOG_READ_LIMIT_BYTES) {
9518
+ async function readAgentServerLogDelta(handle, state, limit = LOG_READ_LIMIT_BYTES) {
8885
9519
  const info = await handle.stat();
8886
9520
  if (!info.isFile() || info.size <= state.offset) return {
8887
9521
  lines: [],
@@ -8929,8 +9563,8 @@ async function readServeLogDelta(handle, state, limit = LOG_READ_LIMIT_BYTES) {
8929
9563
  omitted
8930
9564
  };
8931
9565
  }
8932
- var ServeHttpError = class extends Error {
8933
- name = "ServeHttpError";
9566
+ var AgentServerHttpError = class extends Error {
9567
+ name = "AgentServerHttpError";
8934
9568
  constructor(statusCode, code, message) {
8935
9569
  super(message);
8936
9570
  this.statusCode = statusCode;
@@ -8939,23 +9573,23 @@ var ServeHttpError = class extends Error {
8939
9573
  };
8940
9574
  function requireBody(request) {
8941
9575
  const body = request.body;
8942
- if (typeof body !== "object" || body === null || Array.isArray(body)) throw new ServeHttpError(400, "invalid_body", "JSON object body required");
9576
+ if (typeof body !== "object" || body === null || Array.isArray(body)) throw new AgentServerHttpError(400, "invalid_body", "JSON object body required");
8943
9577
  return body;
8944
9578
  }
8945
9579
  function requireString(body, field) {
8946
9580
  const value = body[field];
8947
- if (typeof value !== "string" || value.trim().length === 0) throw new ServeHttpError(400, "invalid_body", `"${field}" must be a non-empty string`);
9581
+ if (typeof value !== "string" || value.trim().length === 0) throw new AgentServerHttpError(400, "invalid_body", `"${field}" must be a non-empty string`);
8948
9582
  return value.trim();
8949
9583
  }
8950
9584
  function optionalString(body, field) {
8951
9585
  const value = body[field];
8952
9586
  if (value === void 0 || value === null) return void 0;
8953
- if (typeof value !== "string" || value.trim().length === 0) throw new ServeHttpError(400, "invalid_body", `"${field}" must be a non-empty string when present`);
9587
+ if (typeof value !== "string" || value.trim().length === 0) throw new AgentServerHttpError(400, "invalid_body", `"${field}" must be a non-empty string when present`);
8954
9588
  return value.trim();
8955
9589
  }
8956
9590
  function stringArray(body, field, options = {}) {
8957
9591
  const value = body[field];
8958
- if (!Array.isArray(value) || !options.allowEmpty && value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) throw new ServeHttpError(400, "invalid_body", `"${field}" must be ${options.allowEmpty ? "a" : "a non-empty"} string array`);
9592
+ if (!Array.isArray(value) || !options.allowEmpty && value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) throw new AgentServerHttpError(400, "invalid_body", `"${field}" must be ${options.allowEmpty ? "a" : "a non-empty"} string array`);
8959
9593
  return value;
8960
9594
  }
8961
9595
  function requestOperationSignal(request, shutdownSignal) {
@@ -8964,16 +9598,18 @@ function requestOperationSignal(request, shutdownSignal) {
8964
9598
  else request.raw.once("aborted", () => disconnected.abort());
8965
9599
  return shutdownSignal ? AbortSignal.any([disconnected.signal, shutdownSignal]) : disconnected.signal;
8966
9600
  }
8967
- function buildServeServer(options) {
9601
+ function buildAgentServer(options) {
8968
9602
  const { pairing } = options;
8969
9603
  const app = options.logger ? Fastify({
8970
9604
  bodyLimit: BODY_LIMIT,
8971
9605
  loggerInstance: options.logger
8972
9606
  }) : Fastify({ bodyLimit: BODY_LIMIT });
9607
+ options.registerOpenApi?.(app);
9608
+ for (const schema of AGENT_SERVER_SCHEMAS) app.addSchema(schema);
8973
9609
  registerLoopbackSecurity(app, {
8974
9610
  allowedOrigins: options.allowedOrigins,
8975
9611
  ...options.selfOrigin ? { selfOrigins: [options.selfOrigin] } : {},
8976
- allowedHeaders: [SERVE_TOKEN_HEADER],
9612
+ allowedHeaders: [AGENT_SERVER_TOKEN_HEADER],
8977
9613
  methods: [
8978
9614
  "GET",
8979
9615
  "POST",
@@ -8986,35 +9622,37 @@ function buildServeServer(options) {
8986
9622
  global: false,
8987
9623
  max: options.rateLimitMax ?? RATE_LIMIT_MAX,
8988
9624
  timeWindow: RATE_LIMIT_WINDOW_MS,
8989
- errorResponseBuilder: () => new ServeHttpError(429, "rate_limited", "Too many requests"),
9625
+ errorResponseBuilder: () => new AgentServerHttpError(429, "rate_limited", "Too many requests"),
8990
9626
  keyGenerator: (request) => {
8991
9627
  const origin = request.headers.origin;
8992
9628
  return isConfiguredOrigin(origin, options) ? `origin:${origin}` : `ip:${request.ip}`;
8993
9629
  }
8994
9630
  });
8995
- app.after(() => app.addHook("onRequest", app.rateLimit()));
8996
9631
  app.addContentTypeParser("application/x-www-form-urlencoded", { parseAs: "buffer" }, (_request, body, done) => {
8997
9632
  try {
8998
9633
  const form = new TextDecoder("utf-8", { fatal: true }).decode(typeof body === "string" ? Buffer.from(body) : body);
8999
9634
  done(null, new URLSearchParams(form));
9000
9635
  } catch {
9001
- done(new ServeHttpError(400, "invalid_body", "Form body is invalid"), void 0);
9636
+ done(new AgentServerHttpError(400, "invalid_body", "Form body is invalid"), void 0);
9002
9637
  }
9003
9638
  });
9004
9639
  const requirePairedOrigin = (request) => {
9005
9640
  const origin = requireOriginHeader(request.headers);
9006
- const token = request.headers[SERVE_TOKEN_HEADER];
9007
- if (typeof token !== "string" || token.length === 0) throw new ServeHttpError(401, "pairing_required", "Pairing token is required");
9641
+ const token = request.headers[AGENT_SERVER_TOKEN_HEADER];
9642
+ if (typeof token !== "string" || token.length === 0) throw new AgentServerHttpError(401, "pairing_required", "Pairing token is required");
9008
9643
  pairing.verify(origin, token);
9009
9644
  return origin;
9010
9645
  };
9011
- app.get("/health", async () => ({ status: "ok" }));
9012
- registerPairingRoutes(app, pairing);
9013
- registerStatusRoute(app, options, requirePairedOrigin);
9014
- registerAgentRoutes(app, options, requirePairedOrigin);
9015
- registerProviderRoutes(app, options, requirePairedOrigin);
9016
- registerSubscriptionRoutes(app, options, requirePairedOrigin);
9017
- registerRunRoutes(app, options, requirePairedOrigin);
9646
+ app.after(() => {
9647
+ app.addHook("onRequest", app.rateLimit());
9648
+ app.get("/health", { schema: AgentServerRouteSchemas.health }, async () => ({ status: "ok" }));
9649
+ registerPairingRoutes(app, pairing);
9650
+ registerStatusRoute(app, options, requirePairedOrigin);
9651
+ registerAgentRoutes(app, options, requirePairedOrigin);
9652
+ registerProviderRoutes(app, options, requirePairedOrigin);
9653
+ registerSubscriptionRoutes(app, options, requirePairedOrigin);
9654
+ registerRunRoutes(app, options, requirePairedOrigin);
9655
+ });
9018
9656
  app.addHook("onClose", () => {
9019
9657
  options.subscriptions.close();
9020
9658
  });
@@ -9023,13 +9661,13 @@ function buildServeServer(options) {
9023
9661
  message: "Route is not available"
9024
9662
  }));
9025
9663
  app.setErrorHandler(async (error, request, reply) => {
9026
- const { statusCode, code, message } = normalizeServeError(error);
9664
+ const { statusCode, code, message } = normalizeAgentServerError(error);
9027
9665
  if (statusCode === 500) request.log.error({
9028
9666
  ...safeErrorContext(error),
9029
- code: "serve_request_failed",
9667
+ code: "agent_server_request_failed",
9030
9668
  method: request.method,
9031
9669
  route: request.routeOptions.url
9032
- }, "Serve request failed");
9670
+ }, "AgentServer request failed");
9033
9671
  return reply.code(statusCode).send({
9034
9672
  code,
9035
9673
  message
@@ -9038,11 +9676,11 @@ function buildServeServer(options) {
9038
9676
  return app;
9039
9677
  }
9040
9678
  function registerPairingRoutes(app, pairing) {
9041
- app.post("/v1/pairings", async (request, reply) => {
9679
+ app.post("/v1/pairings", { schema: AgentServerRouteSchemas.startPairing }, async (request, reply) => {
9042
9680
  const origin = requireOriginHeader(request.headers);
9043
9681
  return reply.code(201).send(pairing.start(origin));
9044
9682
  });
9045
- app.get("/pairings/:pairingId", async (request, reply) => {
9683
+ app.get("/pairings/:pairingId", { schema: { hide: true } }, async (request, reply) => {
9046
9684
  assertNavigationRequest(request.headers);
9047
9685
  const { pairingId } = request.params;
9048
9686
  const approval = pairing.approval(pairingId);
@@ -9052,17 +9690,17 @@ function registerPairingRoutes(app, pairing) {
9052
9690
  confirmToken: approval.confirmToken
9053
9691
  }));
9054
9692
  });
9055
- app.post("/pairings/:pairingId/confirm", async (request, reply) => {
9693
+ app.post("/pairings/:pairingId/confirm", { schema: { hide: true } }, async (request, reply) => {
9056
9694
  rejectExplicitCrossSite(request.headers);
9057
9695
  const { pairingId } = request.params;
9058
- if (!(request.body instanceof URLSearchParams)) throw new ServeHttpError(400, "invalid_body", "Confirmation form is invalid");
9696
+ if (!(request.body instanceof URLSearchParams)) throw new AgentServerHttpError(400, "invalid_body", "Confirmation form is invalid");
9059
9697
  const { origin } = pairing.confirm(pairingId, request.body.get("confirmToken") ?? "");
9060
9698
  return reply.type("text/html; charset=utf-8").send(renderPairingResultPage({
9061
9699
  title: "Connection approved",
9062
9700
  message: `${origin} can now manage local MoltNet agents on this machine.`
9063
9701
  }));
9064
9702
  });
9065
- app.post("/v1/pairings/:pairingId/claim", async (request) => {
9703
+ app.post("/v1/pairings/:pairingId/claim", { schema: AgentServerRouteSchemas.claimPairing }, async (request) => {
9066
9704
  const origin = requireOriginHeader(request.headers);
9067
9705
  const { pairingId } = request.params;
9068
9706
  return pairing.claim(pairingId, origin);
@@ -9070,7 +9708,7 @@ function registerPairingRoutes(app, pairing) {
9070
9708
  }
9071
9709
  function registerStatusRoute(app, options, requirePairedOrigin) {
9072
9710
  const { store, runs } = options;
9073
- app.get("/v1/status", async (request) => {
9711
+ app.get("/v1/status", { schema: AgentServerRouteSchemas.status }, async (request) => {
9074
9712
  requirePairedOrigin(request);
9075
9713
  return {
9076
9714
  version: options.version,
@@ -9084,17 +9722,20 @@ function registerStatusRoute(app, options, requirePairedOrigin) {
9084
9722
  }
9085
9723
  function registerAgentRoutes(app, options, requirePairedOrigin) {
9086
9724
  const { store } = options;
9087
- app.get("/v1/agents", async (request) => {
9725
+ app.get("/v1/agents", { schema: AgentServerRouteSchemas.listAgents }, async (request) => {
9088
9726
  requirePairedOrigin(request);
9089
9727
  return store.listActivations().map((activation) => publicAgentView(store, activation));
9090
9728
  });
9091
- app.post("/v1/agents", async (request, reply) => {
9729
+ app.post("/v1/agents", {
9730
+ schema: AgentServerRouteSchemas.createAgent,
9731
+ attachValidation: true
9732
+ }, async (request, reply) => {
9092
9733
  requirePairedOrigin(request);
9093
9734
  const body = requireBody(request);
9094
9735
  const signal = requestOperationSignal(request, options.shutdownSignal);
9095
9736
  const kind = requireString(body, "kind");
9096
9737
  if (kind === "managed") {
9097
- if (body["apiUrl"] !== void 0) throw new ServeHttpError(400, "invalid_body", "managed agent registration uses the configured MoltNet API URL; apiUrl cannot be overridden");
9738
+ if (body["apiUrl"] !== void 0) throw new AgentServerHttpError(400, "invalid_body", "managed agent registration uses the configured MoltNet API URL; apiUrl cannot be overridden");
9098
9739
  const entry = await createManagedAgent(store, options.secrets, {
9099
9740
  name: requireString(body, "name"),
9100
9741
  apiUrl: options.defaultApiUrl,
@@ -9113,13 +9754,16 @@ function registerAgentRoutes(app, options, requirePairedOrigin) {
9113
9754
  });
9114
9755
  return reply.code(201).send(publicAgentView(store, entry.activation));
9115
9756
  }
9116
- throw new ServeHttpError(400, "invalid_body", "\"kind\" must be \"managed\" or \"external\"");
9757
+ throw new AgentServerHttpError(400, "invalid_body", "\"kind\" must be \"managed\" or \"external\"");
9117
9758
  });
9118
- app.post("/v1/agents/:agentName/reconcile", async (request) => {
9759
+ app.post("/v1/agents/:agentName/reconcile", {
9760
+ schema: AgentServerRouteSchemas.reconcileAgent,
9761
+ attachValidation: true
9762
+ }, async (request) => {
9119
9763
  requirePairedOrigin(request);
9120
9764
  const { agentName } = request.params;
9121
9765
  const action = requireString(requireBody(request), "action");
9122
- if (action !== "resume" && action !== "abandon") throw new ServeHttpError(400, "invalid_body", "\"action\" must be \"resume\" or \"abandon\"");
9766
+ if (action !== "resume" && action !== "abandon") throw new AgentServerHttpError(400, "invalid_body", "\"action\" must be \"resume\" or \"abandon\"");
9123
9767
  const reconciled = await reconcileManagedRegistration(store, options.secrets, agentName, action, void 0, requestOperationSignal(request, options.shutdownSignal));
9124
9768
  return reconciled ? publicAgentView(store, reconciled.activation) : { abandoned: true };
9125
9769
  });
@@ -9132,16 +9776,16 @@ function registerProviderRoutes(app, options, requirePairedOrigin) {
9132
9776
  mutationQueue = result.then(() => void 0, () => void 0);
9133
9777
  return result;
9134
9778
  };
9135
- app.get("/v1/providers", async (request) => {
9779
+ app.get("/v1/providers", { schema: AgentServerRouteSchemas.listProviders }, async (request) => {
9136
9780
  requirePairedOrigin(request);
9137
9781
  return Object.fromEntries(Object.entries(store.readProviders()).map(([id, provider]) => [id, providerView(provider)]));
9138
9782
  });
9139
- app.post("/v1/providers/:providerId/discover-models", async (request) => {
9783
+ app.post("/v1/providers/:providerId/discover-models", { schema: AgentServerRouteSchemas.discoverModels }, async (request) => {
9140
9784
  requirePairedOrigin(request);
9141
9785
  const { providerId: rawProviderId } = request.params;
9142
9786
  const providerId = assertProviderId(rawProviderId);
9143
9787
  const provider = store.readProviders()[providerId];
9144
- if (!provider) throw new ServeHttpError(404, "provider_not_found", `provider "${providerId}" was not found`);
9788
+ if (!provider) throw new AgentServerHttpError(404, "provider_not_found", `provider "${providerId}" was not found`);
9145
9789
  const parsed = parseProviderBaseUrl(provider.baseUrl, providerId);
9146
9790
  const baseUrl = parsed.href.replace(/\/$/u, "");
9147
9791
  let apiKey;
@@ -9150,10 +9794,10 @@ function registerProviderRoutes(app, options, requirePairedOrigin) {
9150
9794
  } catch (error) {
9151
9795
  request.log.warn({
9152
9796
  ...safeErrorContext(error),
9153
- code: "serve_provider_secret_unavailable",
9797
+ code: "agent_server_provider_secret_unavailable",
9154
9798
  providerId
9155
9799
  }, "Provider API key could not be resolved for model discovery");
9156
- throw new ServeHttpError(400, "provider_secret_unavailable", `provider "${providerId}" API key could not be resolved`);
9800
+ throw new AgentServerHttpError(400, "provider_secret_unavailable", `provider "${providerId}" API key could not be resolved`);
9157
9801
  }
9158
9802
  const headers = apiKey ? { authorization: `Bearer ${apiKey}` } : {};
9159
9803
  const fetchImpl = options.discoverFetch ?? fetch;
@@ -9174,7 +9818,7 @@ function registerProviderRoutes(app, options, requirePairedOrigin) {
9174
9818
  errorType
9175
9819
  });
9176
9820
  request.log.warn({
9177
- code: "serve_provider_discovery_request_failed",
9821
+ code: "agent_server_provider_discovery_request_failed",
9178
9822
  endpoint,
9179
9823
  errorType,
9180
9824
  providerId
@@ -9187,7 +9831,7 @@ function registerProviderRoutes(app, options, requirePairedOrigin) {
9187
9831
  status: response.status
9188
9832
  });
9189
9833
  const context = {
9190
- code: "serve_provider_discovery_upstream_error",
9834
+ code: "agent_server_provider_discovery_upstream_error",
9191
9835
  endpoint,
9192
9836
  providerId,
9193
9837
  statusCode: response.status
@@ -9201,7 +9845,7 @@ function registerProviderRoutes(app, options, requirePairedOrigin) {
9201
9845
  } catch {
9202
9846
  failures.push({ kind: "invalid_response" });
9203
9847
  request.log.warn({
9204
- code: "serve_provider_discovery_invalid_json",
9848
+ code: "agent_server_provider_discovery_invalid_json",
9205
9849
  endpoint,
9206
9850
  providerId
9207
9851
  }, "Provider model discovery returned invalid JSON");
@@ -9212,19 +9856,22 @@ function registerProviderRoutes(app, options, requirePairedOrigin) {
9212
9856
  if (collector.size === 0) collector.addOllamaResponse(await tryJson("ollama_tags", `${parsed.origin}/api/tags`));
9213
9857
  const result = collector.result(providerId, failures);
9214
9858
  if (result.discoveredCount > result.models.length) request.log.warn({
9215
- code: "serve_provider_discovery_truncated",
9859
+ code: "agent_server_provider_discovery_truncated",
9216
9860
  discoveredCount: result.discoveredCount,
9217
9861
  providerId,
9218
9862
  returnedCount: 500
9219
9863
  }, "Provider model discovery result was truncated");
9220
9864
  request.log.info({
9221
- code: "serve_provider_discovery_completed",
9865
+ code: "agent_server_provider_discovery_completed",
9222
9866
  modelCount: result.models.length,
9223
9867
  providerId
9224
9868
  }, "Provider model discovery completed");
9225
9869
  return { models: result.models };
9226
9870
  });
9227
- app.put("/v1/providers/:providerId", async (request, reply) => {
9871
+ app.put("/v1/providers/:providerId", {
9872
+ schema: AgentServerRouteSchemas.putProvider,
9873
+ attachValidation: true
9874
+ }, async (request, reply) => {
9228
9875
  requirePairedOrigin(request);
9229
9876
  const { providerId: rawProviderId } = request.params;
9230
9877
  const providerId = assertProviderId(rawProviderId);
@@ -9261,22 +9908,22 @@ function runViews(runs) {
9261
9908
  }));
9262
9909
  }
9263
9910
  function registerSubscriptionRoutes(app, options, requirePairedOrigin) {
9264
- app.get("/v1/subscriptions", async (request) => {
9911
+ app.get("/v1/subscriptions", { schema: AgentServerRouteSchemas.listSubscriptions }, async (request) => {
9265
9912
  requirePairedOrigin(request);
9266
9913
  return options.subscriptions.list();
9267
9914
  });
9268
- app.post("/v1/subscriptions/:providerId/login", async (request, reply) => {
9915
+ app.post("/v1/subscriptions/:providerId/login", { schema: AgentServerRouteSchemas.startSubscriptionLogin }, async (request, reply) => {
9269
9916
  requirePairedOrigin(request);
9270
9917
  const { providerId } = request.params;
9271
9918
  const login = await options.subscriptions.start(providerId);
9272
9919
  return reply.code(201).send(login);
9273
9920
  });
9274
- app.get("/v1/subscriptions/:providerId/login", async (request) => {
9921
+ app.get("/v1/subscriptions/:providerId/login", { schema: AgentServerRouteSchemas.getSubscriptionLogin }, async (request) => {
9275
9922
  requirePairedOrigin(request);
9276
9923
  const { providerId } = request.params;
9277
9924
  return options.subscriptions.status(providerId);
9278
9925
  });
9279
- app.delete("/v1/subscriptions/:providerId/login", async (request) => {
9926
+ app.delete("/v1/subscriptions/:providerId/login", { schema: AgentServerRouteSchemas.cancelSubscriptionLogin }, async (request) => {
9280
9927
  requirePairedOrigin(request);
9281
9928
  const { providerId } = request.params;
9282
9929
  return options.subscriptions.cancel(providerId);
@@ -9284,11 +9931,14 @@ function registerSubscriptionRoutes(app, options, requirePairedOrigin) {
9284
9931
  }
9285
9932
  function registerRunRoutes(app, options, requirePairedOrigin) {
9286
9933
  const { runs } = options;
9287
- app.get("/v1/runs", async (request) => {
9934
+ app.get("/v1/runs", { schema: AgentServerRouteSchemas.listRuns }, async (request) => {
9288
9935
  requirePairedOrigin(request);
9289
9936
  return runViews(runs);
9290
9937
  });
9291
- app.post("/v1/runs", async (request, reply) => {
9938
+ app.post("/v1/runs", {
9939
+ schema: AgentServerRouteSchemas.startRun,
9940
+ attachValidation: true
9941
+ }, async (request, reply) => {
9292
9942
  requirePairedOrigin(request);
9293
9943
  const body = requireBody(request);
9294
9944
  const record = await runs.start({
@@ -9298,9 +9948,12 @@ function registerRunRoutes(app, options, requirePairedOrigin) {
9298
9948
  taskTypes: stringArray(body, "taskTypes"),
9299
9949
  mode: requireString(body, "mode")
9300
9950
  }, requestOperationSignal(request, options.shutdownSignal));
9301
- return reply.code(201).send(record);
9951
+ return reply.code(201).send({
9952
+ ...record,
9953
+ active: runs.isActive(record.id)
9954
+ });
9302
9955
  });
9303
- app.delete("/v1/runs/:runId", async (request) => {
9956
+ app.delete("/v1/runs/:runId", { schema: AgentServerRouteSchemas.stopRun }, async (request) => {
9304
9957
  requirePairedOrigin(request);
9305
9958
  const { runId } = request.params;
9306
9959
  return runs.stop(runId);
@@ -9310,13 +9963,14 @@ function registerRunRoutes(app, options, requirePairedOrigin) {
9310
9963
  function registerRunLogRoute(app, options, requirePairedOrigin) {
9311
9964
  const { runs, store } = options;
9312
9965
  let openStreams = 0;
9313
- app.get("/v1/runs/:runId/logs", async (request, reply) => {
9966
+ app.get("/v1/runs/:runId/logs", { schema: AgentServerRouteSchemas.streamRunLogs }, async (request, reply) => {
9314
9967
  requirePairedOrigin(request);
9315
9968
  const { runId } = request.params;
9316
9969
  const record = runs.status(runId);
9317
9970
  store.resolveRunLogPath(record.id);
9318
- if (openStreams >= MAX_LOG_STREAMS) throw new ServeHttpError(429, "rate_limited", "Too many concurrent log streams");
9971
+ if (openStreams >= MAX_LOG_STREAMS) throw new AgentServerHttpError(429, "rate_limited", "Too many concurrent log streams");
9319
9972
  openStreams += 1;
9973
+ reply.hijack();
9320
9974
  reply.raw.writeHead(200, {
9321
9975
  "content-type": "text/event-stream",
9322
9976
  "cache-control": "no-store",
@@ -9355,7 +10009,7 @@ function registerRunLogRoute(app, options, requirePairedOrigin) {
9355
10009
  const push = async () => {
9356
10010
  const handle = await open(store.resolveRunLogPath(record.id), constants.O_RDONLY | constants.O_NOFOLLOW);
9357
10011
  try {
9358
- const { lines, omitted } = await readServeLogDelta(handle, readState);
10012
+ const { lines, omitted } = await readAgentServerLogDelta(handle, readState);
9359
10013
  if (omitted) await writeData("[older log output omitted]");
9360
10014
  for (const line of lines) await writeData(line);
9361
10015
  } finally {
@@ -9366,9 +10020,9 @@ function registerRunLogRoute(app, options, requirePairedOrigin) {
9366
10020
  if (closed) return;
9367
10021
  request.log.warn({
9368
10022
  ...safeErrorContext(error),
9369
- code: "serve_log_tail_failed",
10023
+ code: "agent_server_log_tail_failed",
9370
10024
  runId
9371
- }, "serve log tail failed");
10025
+ }, "Agent Server log tail failed");
9372
10026
  finish(true);
9373
10027
  };
9374
10028
  const schedule = () => {
@@ -9381,7 +10035,7 @@ function registerRunLogRoute(app, options, requirePairedOrigin) {
9381
10035
  }, LOG_POLL_INTERVAL_MS);
9382
10036
  pollTimer.unref();
9383
10037
  };
9384
- request.raw.on("close", () => finish());
10038
+ reply.raw.on("close", () => finish());
9385
10039
  try {
9386
10040
  await push();
9387
10041
  if (runs.isActive(record.id)) schedule();
@@ -9426,8 +10080,8 @@ function corsHeadersFor(request, options) {
9426
10080
  function isConfiguredOrigin(origin, options) {
9427
10081
  return typeof origin === "string" && (options.allowedOrigins.includes(origin) || origin === options.selfOrigin);
9428
10082
  }
9429
- function normalizeServeError(error) {
9430
- if (error instanceof ServeHttpError) return {
10083
+ function normalizeAgentServerError(error) {
10084
+ if (error instanceof AgentServerHttpError) return {
9431
10085
  statusCode: error.statusCode,
9432
10086
  code: error.code,
9433
10087
  message: error.message
@@ -9437,32 +10091,32 @@ function normalizeServeError(error) {
9437
10091
  code: error.kind,
9438
10092
  message: error.message
9439
10093
  };
9440
- if (error instanceof ServePairingError) return {
10094
+ if (error instanceof AgentServerPairingError) return {
9441
10095
  statusCode: error.code === "pairing_not_found" ? 404 : error.code === "pairing_token_invalid" || error.code === "pairing_not_approved" ? 401 : 403,
9442
10096
  code: error.code,
9443
10097
  message: error.message
9444
10098
  };
9445
- if (error instanceof ServeStoreError) return {
10099
+ if (error instanceof AgentServerStoreError) return {
9446
10100
  statusCode: error.code === "not_found" ? 404 : error.code === "io_error" ? 500 : 400,
9447
10101
  code: error.code,
9448
10102
  message: error.message
9449
10103
  };
9450
- if (error instanceof ServeRunError) return {
10104
+ if (error instanceof AgentServerRunError) return {
9451
10105
  statusCode: error.code === "run_not_found" ? 404 : 400,
9452
10106
  code: error.code,
9453
10107
  message: error.message
9454
10108
  };
9455
- if (error instanceof ServeSubscriptionError) return {
10109
+ if (error instanceof AgentServerSubscriptionError) return {
9456
10110
  statusCode: error.code === "login_not_found" ? 404 : error.code === "provider_unknown" ? 404 : 400,
9457
10111
  code: error.code,
9458
10112
  message: error.message
9459
10113
  };
9460
- if (error instanceof ServeModelDiscoveryError) return {
10114
+ if (error instanceof AgentServerModelDiscoveryError) return {
9461
10115
  statusCode: error.statusCode,
9462
10116
  code: error.code,
9463
10117
  message: error.message
9464
10118
  };
9465
- if (error instanceof ServeIdentityError) return {
10119
+ if (error instanceof AgentServerIdentityError) return {
9466
10120
  statusCode: error.code === "agent_exists" ? 409 : 400,
9467
10121
  code: error.code,
9468
10122
  message: error.message
@@ -9474,9 +10128,9 @@ function normalizeServeError(error) {
9474
10128
  };
9475
10129
  }
9476
10130
  //#endregion
9477
- //#region src/cli/serve.ts
10131
+ //#region src/cli/server.ts
9478
10132
  /**
9479
- * `moltnet-agent serve` — per-user loopback supervisor (#2061).
10133
+ * `moltnet-agent server` — per-user loopback supervisor (#2061).
9480
10134
  *
9481
10135
  * Starts nothing on its own: it binds 127.0.0.1 and waits for a paired
9482
10136
  * Console origin to configure agents/providers and start/stop runs.
@@ -9485,12 +10139,12 @@ var DEFAULT_PORT = 17374;
9485
10139
  var DEFAULT_ALLOWED_ORIGINS = "https://console.themolt.net";
9486
10140
  var DEFAULT_API_URL = "https://api.themolt.net";
9487
10141
  var SHUTDOWN_TIMEOUT_MS = 15e3;
9488
- async function runServe(argv) {
10142
+ async function runAgentServer(argv) {
9489
10143
  if (isHelpFlag(argv)) {
9490
- console.log(SERVE_HELP);
10144
+ console.log(AGENT_SERVER_HELP);
9491
10145
  return 0;
9492
10146
  }
9493
- const envConfig = loadServeEnvConfig();
10147
+ const envConfig = loadAgentServerEnvConfig();
9494
10148
  const { values } = parseArgs({
9495
10149
  args: argv,
9496
10150
  options: {
@@ -9506,19 +10160,19 @@ async function runServe(argv) {
9506
10160
  return 1;
9507
10161
  }
9508
10162
  const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
9509
- const root = values.root ?? resolveServeRoot({
10163
+ const root = values.root ?? resolveAgentServerRoot({
9510
10164
  root: envConfig.root,
9511
10165
  xdgConfigHome: envConfig.xdgConfigHome
9512
10166
  });
9513
10167
  const defaultApiUrl = values["api-url"] ?? (envConfig.apiUrl || DEFAULT_API_URL);
9514
- const store = new ServeStore(root).ensure();
10168
+ const store = new AgentServerStore(root).ensure();
9515
10169
  const { logger, shutdown: shutdownLogger } = createRootLogger({
9516
- name: "agent-daemon.serve",
10170
+ name: "agent-daemon.server",
9517
10171
  level: envConfig.logLevel || "info"
9518
10172
  });
9519
10173
  try {
9520
10174
  try {
9521
- return await withServeLock(root, async () => {
10175
+ return await withAgentServerLock(root, async () => {
9522
10176
  const secrets = new FileSecretProvider({
9523
10177
  root: store.secretsDir,
9524
10178
  writable: true
@@ -9527,7 +10181,7 @@ async function runServe(argv) {
9527
10181
  const externalSecretProviders = createNodeSecretProviderRegistry();
9528
10182
  const pairing = new PairingService();
9529
10183
  const shutdownController = new AbortController();
9530
- const subscriptions = new ProviderLoginService({
10184
+ const subscriptions = await ProviderLoginService.create({
9531
10185
  authPath: store.piAuthJsonPath,
9532
10186
  logger
9533
10187
  });
@@ -9536,9 +10190,10 @@ async function runServe(argv) {
9536
10190
  secretProviders,
9537
10191
  externalSecretProviders,
9538
10192
  baseEnv: processEnvSnapshot(),
9539
- logger
10193
+ logger,
10194
+ runtimeRegistry: new RuntimeRegistry(store.root)
9540
10195
  });
9541
- const app = buildServeServer({
10196
+ const app = buildAgentServer({
9542
10197
  store,
9543
10198
  secrets,
9544
10199
  secretProviders,
@@ -9558,11 +10213,11 @@ async function runServe(argv) {
9558
10213
  host: "127.0.0.1",
9559
10214
  port
9560
10215
  });
9561
- console.error(`moltnet-agent serve listening on ${address}`);
10216
+ console.error(`moltnet-agent server listening on ${address}`);
9562
10217
  console.error(`config root: ${root}`);
9563
10218
  console.error(`allowed origins: ${allowedOrigins.join(", ")}`);
9564
10219
  console.error("Pair from the Console \"Local runtime\" page; approve the one-click prompt this server opens.");
9565
- return await waitForServeShutdown(runs, app, shutdownController);
10220
+ return await waitForAgentServerShutdown(runs, app, shutdownController);
9566
10221
  } catch (cause) {
9567
10222
  await app.close().catch(() => void 0);
9568
10223
  throw cause;
@@ -9573,7 +10228,7 @@ async function runServe(argv) {
9573
10228
  process.kill(process.pid, "SIGTERM");
9574
10229
  } });
9575
10230
  } catch (cause) {
9576
- if (cause instanceof ServeLockError) {
10231
+ if (cause instanceof AgentServerLockError) {
9577
10232
  console.error(cause.message);
9578
10233
  return 1;
9579
10234
  }
@@ -9583,7 +10238,7 @@ async function runServe(argv) {
9583
10238
  await shutdownLogger();
9584
10239
  }
9585
10240
  }
9586
- function waitForServeShutdown(runs, app, shutdownController) {
10241
+ function waitForAgentServerShutdown(runs, app, shutdownController) {
9587
10242
  return new Promise((resolvePromise) => {
9588
10243
  let shuttingDown = false;
9589
10244
  const shutdown = () => {
@@ -9801,15 +10456,128 @@ function parseLimit(raw) {
9801
10456
  return value;
9802
10457
  }
9803
10458
  //#endregion
10459
+ //#region src/lib/update.ts
10460
+ var UPDATE_MANIFEST_URL = "https://themolt.net/download/manifest.json";
10461
+ var UPDATE_CACHE_TTL_MS = 1440 * 60 * 1e3;
10462
+ var UPDATE_ERROR_CACHE_TTL_MS = 300 * 1e3;
10463
+ function detectDaemonInstallMethod(executable = process.argv[1] ?? "") {
10464
+ const path = executable.replaceAll("\\", "/");
10465
+ if (path.includes("/node_modules/@themoltnet/agent-daemon/")) return "npm";
10466
+ if (path.includes("/.local/share/moltnet/") || path.includes("/opt/moltnet/")) return "bundle";
10467
+ return "direct";
10468
+ }
10469
+ function daemonUpdateCommand(method) {
10470
+ if (method === "npm") return "npm install -g @themoltnet/agent-daemon@latest";
10471
+ return "curl -fsSL https://themolt.net/install/agent | sh";
10472
+ }
10473
+ async function checkDaemonUpdate(input) {
10474
+ const installMethod = detectDaemonInstallMethod(input.executable ?? process.argv[1] ?? "moltnet-agent");
10475
+ const result = {
10476
+ currentVersion: normalizeVersion(input.currentVersion),
10477
+ updateAvailable: false,
10478
+ installMethod,
10479
+ releaseUrl: "https://themolt.net/download",
10480
+ command: daemonUpdateCommand(installMethod)
10481
+ };
10482
+ const now = input.now ?? /* @__PURE__ */ new Date();
10483
+ if (!input.force) {
10484
+ const cached = await readCache();
10485
+ const ttl = cached?.latest ? UPDATE_CACHE_TTL_MS : UPDATE_ERROR_CACHE_TTL_MS;
10486
+ if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttl) {
10487
+ result.latestVersion = cached.latest;
10488
+ result.updateAvailable = compareVersions(cached.latest, result.currentVersion) > 0;
10489
+ return result;
10490
+ }
10491
+ }
10492
+ try {
10493
+ const response = await (input.fetchFn ?? fetch)(UPDATE_MANIFEST_URL, { signal: AbortSignal.timeout(5e3) });
10494
+ if (!response.ok) throw new Error(`manifest returned HTTP ${response.status}`);
10495
+ const latest = manifestVersion(await response.json());
10496
+ if (!latest) throw new Error("manifest has no valid agent version");
10497
+ await writeCache({
10498
+ checkedAt: now.toISOString(),
10499
+ latest
10500
+ }).catch(() => void 0);
10501
+ result.latestVersion = latest;
10502
+ result.updateAvailable = compareVersions(latest, result.currentVersion) > 0;
10503
+ return result;
10504
+ } catch (error) {
10505
+ await writeCache({
10506
+ checkedAt: now.toISOString(),
10507
+ error: error instanceof Error ? error.message : String(error)
10508
+ }).catch(() => void 0);
10509
+ throw new Error(`could not check for MoltNet agent updates: ${error instanceof Error ? error.message : String(error)}`);
10510
+ }
10511
+ }
10512
+ function manifestVersion(value) {
10513
+ if (!value || typeof value !== "object") return void 0;
10514
+ const agent = value.agent;
10515
+ if (!agent || typeof agent !== "object") return void 0;
10516
+ const version = agent.version;
10517
+ return typeof version === "string" && validVersion(version) ? normalizeVersion(version) : void 0;
10518
+ }
10519
+ function normalizeVersion(value) {
10520
+ return value.trim().replace(/^v/, "");
10521
+ }
10522
+ function validVersion(value) {
10523
+ return /^v?\d+\.\d+\.\d+$/.test(value);
10524
+ }
10525
+ function compareVersions(a, b) {
10526
+ if (!a || !validVersion(a) || !validVersion(b)) return 0;
10527
+ const left = normalizeVersion(a).split(".").map(Number);
10528
+ const right = normalizeVersion(b).split(".").map(Number);
10529
+ for (let i = 0; i < 3; i += 1) {
10530
+ const leftPart = left[i] ?? 0;
10531
+ const rightPart = right[i] ?? 0;
10532
+ if (leftPart > rightPart) return 1;
10533
+ if (leftPart < rightPart) return -1;
10534
+ }
10535
+ return 0;
10536
+ }
10537
+ function cachePath() {
10538
+ const env = loadUpdateEnvConfig();
10539
+ return join(env.xdgCacheHome || (platform() === "win32" ? env.localAppData || join(homedir(), "AppData", "Local") : join(homedir(), ".cache")), "moltnet", "updates", "agent.json");
10540
+ }
10541
+ async function readCache() {
10542
+ try {
10543
+ return JSON.parse(await readFile(cachePath(), "utf8"));
10544
+ } catch {
10545
+ return;
10546
+ }
10547
+ }
10548
+ async function writeCache(cache) {
10549
+ const path = cachePath();
10550
+ await mkdir(dirname(path), {
10551
+ recursive: true,
10552
+ mode: 448
10553
+ });
10554
+ await writeFile(path, JSON.stringify(cache), { mode: 384 });
10555
+ }
10556
+ //#endregion
10557
+ //#region src/version.ts
10558
+ var DAEMON_VERSION = "0.51.0";
10559
+ //#endregion
9804
10560
  //#region src/cli.ts
9805
10561
  async function runAgentDaemonCli(options) {
9806
10562
  const [subcommand, ...rest] = options.argv ?? process.argv.slice(2);
10563
+ if ([
10564
+ "poll",
10565
+ "drain",
10566
+ "serve"
10567
+ ].includes(subcommand ?? "") && !isWorkspaceInvocation()) checkDaemonUpdate({ currentVersion: DAEMON_VERSION }).then((update) => {
10568
+ if (update.updateAvailable) pino({ name: "moltnet.agent-daemon" }).info({
10569
+ event: "agent-daemon.update_available",
10570
+ update
10571
+ }, "A MoltNet agent update is available");
10572
+ }).catch(() => void 0);
9807
10573
  switch (subcommand) {
9808
10574
  case "poll": return runPoll(rest, options.runtime);
9809
10575
  case "once": return runOnce(rest, options.runtime);
9810
10576
  case "drain": return runDrain(rest, options.runtime);
9811
- case "serve": return runServe(rest);
10577
+ case "server": return runAgentServer(rest);
9812
10578
  case "sync-sessions": return runSyncSessions(rest);
10579
+ case "update": return runUpdate(rest);
10580
+ case "runtime": return runRuntime(rest);
9813
10581
  case "-h":
9814
10582
  case "--help":
9815
10583
  console.log(ROOT_USAGE);
@@ -9822,5 +10590,49 @@ async function runAgentDaemonCli(options) {
9822
10590
  return 1;
9823
10591
  }
9824
10592
  }
10593
+ function isWorkspaceInvocation() {
10594
+ return (process.argv[1] ?? "").replaceAll("\\", "/").includes("/apps/agent-daemon/src/");
10595
+ }
10596
+ async function runUpdate(argv) {
10597
+ if (argv[0] !== "check" || argv.slice(1).some((arg) => arg !== "--json")) {
10598
+ console.error("Usage: moltnet-agent update check [--json]");
10599
+ return 1;
10600
+ }
10601
+ try {
10602
+ const update = await checkDaemonUpdate({
10603
+ currentVersion: DAEMON_VERSION,
10604
+ force: true
10605
+ });
10606
+ if (argv.includes("--json")) console.log(JSON.stringify(update));
10607
+ else if (update.updateAvailable) console.log(`MoltNet agent ${update.latestVersion} is available (you have ${update.currentVersion}). Run: ${update.command}`);
10608
+ else console.log(`MoltNet agent ${update.currentVersion} is up to date.`);
10609
+ return 0;
10610
+ } catch (error) {
10611
+ console.error(error instanceof Error ? error.message : String(error));
10612
+ return 1;
10613
+ }
10614
+ }
10615
+ async function runRuntime(argv) {
10616
+ const config = loadAgentServerEnvConfig();
10617
+ const registry = new RuntimeRegistry(resolveAgentServerRoot({
10618
+ root: config.root,
10619
+ xdgConfigHome: config.xdgConfigHome
10620
+ }));
10621
+ const [command, ...rest] = argv;
10622
+ if (command === "list") {
10623
+ console.log(JSON.stringify(registry.list(), null, 2));
10624
+ return 0;
10625
+ }
10626
+ if (command === "unregister" && rest.length === 1) {
10627
+ if (!registry.unregister(rest[0])) throw new Error(`No runtime registered for "${rest[0]}".`);
10628
+ return 0;
10629
+ }
10630
+ if (command === "register" && rest.length === 2) {
10631
+ console.log(JSON.stringify(await registry.register(rest[0], rest[1]), null, 2));
10632
+ return 0;
10633
+ }
10634
+ console.error("Usage: moltnet-agent runtime <list|register <kind> <module>|unregister <kind>>");
10635
+ return 1;
10636
+ }
9825
10637
  //#endregion
9826
- export { runAgentDaemonCli };
10638
+ export { loadDaemonRuntimeAdapter as n, runAgentDaemonCli, extractRuntimeModule as t };