@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.d.ts.map +1 -1
- package/dist/cli.js +1173 -361
- package/dist/main.js +2 -69
- package/package.json +16 -9
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 {
|
|
34
|
-
import {
|
|
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 {
|
|
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$
|
|
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$
|
|
829
|
-
y: schemaRef$
|
|
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$
|
|
839
|
-
y: schemaRef$
|
|
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$
|
|
849
|
-
y: schemaRef$
|
|
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$
|
|
859
|
-
kemKey: schemaRef$
|
|
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$
|
|
867
|
-
outerPublicKey: schemaRef$
|
|
868
|
-
previewKeyHandle: schemaRef$
|
|
869
|
-
seedPublicKey: schemaRef$
|
|
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$
|
|
878
|
-
digest: schemaRef$
|
|
879
|
-
additionalArguments: schemaRef$
|
|
880
|
-
outerCredentialId: schemaRef$
|
|
881
|
-
outerPublicKey: schemaRef$
|
|
882
|
-
previewKeyHandle: schemaRef$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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
|
|
3565
|
-
agent-daemon
|
|
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:
|
|
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:
|
|
3580
|
+
Env: MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS.
|
|
3577
3581
|
--root <path> Config root. Default: ~/.config/moltnet
|
|
3578
|
-
(or
|
|
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
|
|
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
|
|
3791
|
+
function loadAgentServerEnvConfig() {
|
|
3788
3792
|
return {
|
|
3789
|
-
port: process.env["
|
|
3790
|
-
allowedOrigins: process.env["
|
|
3791
|
-
root: process.env["
|
|
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
|
|
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
|
|
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 =
|
|
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 (
|
|
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/
|
|
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
|
-
*
|
|
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
|
|
7197
|
-
name = "
|
|
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
|
|
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
|
|
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
|
|
7261
|
-
if (!pairing.approved || !pairing.bearerToken) throw new
|
|
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
|
|
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
|
|
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/
|
|
7393
|
+
//#region src/lib/agent-server/provider-login.ts
|
|
7325
7394
|
/**
|
|
7326
|
-
* Subscription-provider OAuth brokering for
|
|
7395
|
+
* Subscription-provider OAuth brokering for Agent Server (#2061 slice 4).
|
|
7327
7396
|
*
|
|
7328
|
-
* The console clicks "Connect";
|
|
7329
|
-
* `
|
|
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
|
|
7334
|
-
*
|
|
7335
|
-
*
|
|
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
|
|
7341
|
-
|
|
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
|
|
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.
|
|
7451
|
+
return readStoredCredential(providerId, this.options.authPath) !== void 0;
|
|
7374
7452
|
} catch (error) {
|
|
7375
7453
|
this.logger.warn({
|
|
7376
|
-
event: "
|
|
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
|
-
|
|
7400
|
-
|
|
7481
|
+
restoreStoredCredential(this.options.authPath, login.providerId, login.previousCredential);
|
|
7482
|
+
return true;
|
|
7401
7483
|
} catch (error) {
|
|
7402
7484
|
this.logger.error({
|
|
7403
|
-
event: "
|
|
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: "
|
|
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
|
|
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
|
|
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.
|
|
7536
|
+
previousCredential: readStoredCredential(providerId, this.options.authPath)
|
|
7453
7537
|
};
|
|
7454
7538
|
this.logins.set(providerId, login);
|
|
7455
7539
|
this.logger.info({
|
|
7456
|
-
event: "
|
|
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.
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
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: "
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7586
|
-
name = "
|
|
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
|
-
/** `
|
|
7593
|
-
function
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7664
|
-
return join(this.root, "
|
|
7821
|
+
get statePath() {
|
|
7822
|
+
return join(this.root, "agent-server.json");
|
|
7665
7823
|
}
|
|
7666
|
-
|
|
7667
|
-
const state = readJson(this.
|
|
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
|
|
7674
|
-
if ("pairedOrigins" in state) throw new
|
|
7675
|
-
if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new
|
|
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
|
|
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
|
-
|
|
7688
|
-
writeJsonAtomic(this.
|
|
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.
|
|
7861
|
+
return this.readAgentServerState().activations[assertStoreName("agent name", alias)] ?? null;
|
|
7704
7862
|
}
|
|
7705
7863
|
hasPendingRegistration(alias) {
|
|
7706
|
-
return Boolean(this.
|
|
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.
|
|
7711
|
-
if (state.activations[name] || state.pendingRegistrations[name]) throw new
|
|
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.
|
|
7874
|
+
this.writeAgentServerState(state);
|
|
7717
7875
|
}
|
|
7718
7876
|
clearPendingRegistration(alias) {
|
|
7719
7877
|
const name = assertStoreName("agent name", alias);
|
|
7720
|
-
const state = this.
|
|
7878
|
+
const state = this.readAgentServerState();
|
|
7721
7879
|
delete state.pendingRegistrations[name];
|
|
7722
|
-
this.
|
|
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.
|
|
7885
|
+
const state = this.readAgentServerState();
|
|
7728
7886
|
state.activations[alias] = activation;
|
|
7729
7887
|
if (activation.source === "managed") delete state.pendingRegistrations[alias];
|
|
7730
|
-
this.
|
|
7888
|
+
this.writeAgentServerState(state);
|
|
7731
7889
|
}
|
|
7732
7890
|
listActivations() {
|
|
7733
|
-
return Object.values(this.
|
|
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
|
|
7921
|
+
throw new AgentServerStoreError("io_error", "could not resolve run directory", { cause });
|
|
7764
7922
|
}
|
|
7765
|
-
if (!isStrictDescendant(root, runDir)) throw new
|
|
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
|
|
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
|
|
7928
|
+
if (!isStrictDescendant(runDir, resolvedLog)) throw new AgentServerStoreError("invalid_state", "run log escapes its store");
|
|
7771
7929
|
} catch (cause) {
|
|
7772
|
-
if (cause instanceof
|
|
7773
|
-
if (cause.code !== "ENOENT") throw new
|
|
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
|
|
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
|
|
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/
|
|
8045
|
+
//#region src/lib/agent-server/identity.ts
|
|
7888
8046
|
/**
|
|
7889
|
-
*
|
|
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 `
|
|
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
|
|
7900
|
-
name = "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8151
|
+
throw new AgentServerIdentityError("registration_failed", `registration for "${alias}" was rejected`, { cause });
|
|
7994
8152
|
}
|
|
7995
|
-
if (store.hasPendingRegistration(alias)) throw new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
8296
|
-
name = "
|
|
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
|
|
8304
|
-
if (!spec.agent) throw new
|
|
8305
|
-
if (!spec.teamId) throw new
|
|
8306
|
-
if (!Array.isArray(spec.profiles) || spec.profiles.length === 0) throw new
|
|
8307
|
-
if (!Array.isArray(spec.taskTypes) || spec.taskTypes.length === 0) throw new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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
|
|
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
|
|
8759
|
+
if (!activeRun) throw new AgentServerRunError("run_not_active", `run "${id}" is not running`);
|
|
8560
8760
|
activeRun.stopRequested = true;
|
|
8561
|
-
this.log("info", "
|
|
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
|
|
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", "
|
|
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", "
|
|
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
|
|
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
|
|
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
|
|
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", "
|
|
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", "
|
|
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/
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
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
|
-
|
|
8792
|
-
|
|
8793
|
-
if (released) return;
|
|
8794
|
-
released = true;
|
|
8795
|
-
await releaseLock();
|
|
8796
|
-
}
|
|
8993
|
+
argv: remaining,
|
|
8994
|
+
specifier
|
|
8797
8995
|
};
|
|
8798
8996
|
}
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
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
|
-
|
|
8804
|
-
}
|
|
8805
|
-
|
|
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
|
-
|
|
8809
|
-
|
|
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
|
|
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
|
|
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
|
|
8857
|
-
if (failures.some((failure) => failure.kind === "network")) return new
|
|
8858
|
-
if (failures.some((failure) => failure.kind === "invalid_response")) return new
|
|
8859
|
-
return new
|
|
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/
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
8933
|
-
name = "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: [
|
|
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
|
|
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
|
|
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[
|
|
9007
|
-
if (typeof token !== "string" || token.length === 0) throw new
|
|
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.
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
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 } =
|
|
9664
|
+
const { statusCode, code, message } = normalizeAgentServerError(error);
|
|
9027
9665
|
if (statusCode === 500) request.log.error({
|
|
9028
9666
|
...safeErrorContext(error),
|
|
9029
|
-
code: "
|
|
9667
|
+
code: "agent_server_request_failed",
|
|
9030
9668
|
method: request.method,
|
|
9031
9669
|
route: request.routeOptions.url
|
|
9032
|
-
}, "
|
|
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
|
|
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",
|
|
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
|
|
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
|
|
9757
|
+
throw new AgentServerHttpError(400, "invalid_body", "\"kind\" must be \"managed\" or \"external\"");
|
|
9117
9758
|
});
|
|
9118
|
-
app.post("/v1/agents/:agentName/reconcile",
|
|
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
|
|
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
|
|
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: "
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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",
|
|
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",
|
|
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(
|
|
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
|
|
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
|
|
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: "
|
|
10023
|
+
code: "agent_server_log_tail_failed",
|
|
9370
10024
|
runId
|
|
9371
|
-
}, "
|
|
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
|
-
|
|
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
|
|
9430
|
-
if (error instanceof
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
10131
|
+
//#region src/cli/server.ts
|
|
9478
10132
|
/**
|
|
9479
|
-
* `moltnet-agent
|
|
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
|
|
10142
|
+
async function runAgentServer(argv) {
|
|
9489
10143
|
if (isHelpFlag(argv)) {
|
|
9490
|
-
console.log(
|
|
10144
|
+
console.log(AGENT_SERVER_HELP);
|
|
9491
10145
|
return 0;
|
|
9492
10146
|
}
|
|
9493
|
-
const envConfig =
|
|
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 ??
|
|
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
|
|
10168
|
+
const store = new AgentServerStore(root).ensure();
|
|
9515
10169
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
9516
|
-
name: "agent-daemon.
|
|
10170
|
+
name: "agent-daemon.server",
|
|
9517
10171
|
level: envConfig.logLevel || "info"
|
|
9518
10172
|
});
|
|
9519
10173
|
try {
|
|
9520
10174
|
try {
|
|
9521
|
-
return await
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 "
|
|
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 };
|