@fastagent-sh/fastagent 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/atomic-write.d.ts +4 -5
- package/dist/atomic-write.js +4 -5
- package/dist/authoring.d.ts +32 -15
- package/dist/authoring.js +147 -127
- package/dist/channels/agentcore-protocol.d.ts +8 -3
- package/dist/channels/agentcore-protocol.js +3 -1
- package/dist/channels/agentcore-service.d.ts +1 -0
- package/dist/channels/agentcore-service.js +14 -4
- package/dist/channels/agentcore.d.ts +9 -0
- package/dist/channels/agentcore.js +29 -4
- package/dist/channels/context.d.ts +2 -0
- package/dist/channels/context.js +5 -1
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu.d.ts +0 -3
- package/dist/channels/feishu/feishu.js +10 -10
- package/dist/channels/feishu/register-app.js +3 -5
- package/dist/channels/feishu/scaffold/channel.ts +32 -34
- package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
- package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
- package/dist/channels/feishu/setup-mode.d.ts +2 -12
- package/dist/channels/feishu/setup-mode.js +2 -22
- package/dist/channels/feishu/setup.d.ts +12 -0
- package/dist/channels/feishu/setup.js +197 -0
- package/dist/channels/kit/abortable.d.ts +8 -0
- package/dist/channels/kit/abortable.js +22 -0
- package/dist/channels/lark/lark.d.ts +0 -3
- package/dist/channels/lark/lark.js +0 -5
- package/dist/channels/lark/scaffold/channel.ts +33 -35
- package/dist/channels/lark/scaffold/lark-send.ts +5 -5
- package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
- package/dist/channels/lark/setup.d.ts +9 -0
- package/dist/channels/lark/setup.js +57 -0
- package/dist/channels/setup.d.ts +112 -0
- package/dist/channels/setup.js +31 -0
- package/dist/channels/slack/history.js +1 -1
- package/dist/channels/slack/manifest.d.ts +17 -7
- package/dist/channels/slack/manifest.js +12 -16
- package/dist/channels/slack/scaffold/channel.ts +44 -30
- package/dist/channels/slack/scaffold/slack-send.ts +8 -8
- package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
- package/dist/channels/slack/setup.d.ts +7 -0
- package/dist/channels/slack/setup.js +119 -0
- package/dist/channels/slack/slack-api.d.ts +2 -0
- package/dist/channels/slack/slack-api.js +4 -0
- package/dist/channels/slack/slack.d.ts +21 -2
- package/dist/channels/slack/slack.js +398 -338
- package/dist/channels/slack/socket.d.ts +57 -0
- package/dist/channels/slack/socket.js +305 -0
- package/dist/channels/telegram/polling.d.ts +27 -0
- package/dist/channels/telegram/polling.js +105 -0
- package/dist/channels/telegram/scaffold/channel.ts +52 -32
- package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
- package/dist/channels/telegram/setup.d.ts +2 -0
- package/dist/channels/telegram/setup.js +42 -0
- package/dist/channels/telegram/telegram-api.d.ts +27 -1
- package/dist/channels/telegram/telegram-api.js +7 -6
- package/dist/channels/telegram/telegram.d.ts +11 -3
- package/dist/channels/telegram/telegram.js +184 -157
- package/dist/channels/webhook-evidence.d.ts +11 -0
- package/dist/channels/webhook-evidence.js +71 -0
- package/dist/channels/webhooks.d.ts +37 -0
- package/dist/channels/webhooks.js +70 -0
- package/dist/child-env.d.ts +16 -0
- package/dist/child-env.js +30 -0
- package/dist/cli/box-login.js +4 -1
- package/dist/cli/channel-app.d.ts +3 -0
- package/dist/cli/channel-app.js +35 -0
- package/dist/cli/channel-setup.d.ts +38 -0
- package/dist/cli/channel-setup.js +195 -0
- package/dist/cli/commands/add.d.ts +6 -14
- package/dist/cli/commands/add.js +45 -303
- package/dist/cli/commands/auth.js +8 -2
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +16 -0
- package/dist/cli/commands/channels.d.ts +19 -0
- package/dist/cli/commands/channels.js +230 -0
- package/dist/cli/commands/content.d.ts +7 -3
- package/dist/cli/commands/content.js +16 -10
- package/dist/cli/commands/deploy/agentcore.js +55 -23
- package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
- package/dist/cli/commands/deploy/channel-apps.js +30 -0
- package/dist/cli/commands/deploy/docker.js +32 -65
- package/dist/cli/commands/deploy/fly.js +28 -12
- package/dist/cli/commands/deploy/railway.js +38 -13
- package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
- package/dist/cli/commands/deploy/secrets-step.js +66 -0
- package/dist/cli/commands/deploy/shared.d.ts +16 -5
- package/dist/cli/commands/deploy/shared.js +25 -8
- package/dist/cli/commands/deploy.js +53 -60
- package/dist/cli/commands/dev.d.ts +2 -0
- package/dist/cli/commands/dev.js +15 -3
- package/dist/cli/commands/env.js +1 -1
- package/dist/cli/commands/info.d.ts +2 -0
- package/dist/cli/commands/info.js +60 -25
- package/dist/cli/commands/init.js +3 -1
- package/dist/cli/commands/invoke.d.ts +2 -0
- package/dist/cli/commands/invoke.js +20 -7
- package/dist/cli/commands/login.d.ts +0 -7
- package/dist/cli/commands/login.js +22 -10
- package/dist/cli/commands/logout.js +1 -1
- package/dist/cli/commands/mcp.js +14 -6
- package/dist/cli/commands/models.js +3 -2
- package/dist/cli/commands/schedules.d.ts +4 -2
- package/dist/cli/commands/schedules.js +14 -10
- package/dist/cli/commands/secrets.d.ts +14 -0
- package/dist/cli/commands/secrets.js +213 -0
- package/dist/cli/commands/start.d.ts +14 -14
- package/dist/cli/commands/start.js +46 -60
- package/dist/cli/content-view.d.ts +4 -6
- package/dist/cli/content-view.js +5 -11
- package/dist/cli/first-run-channels.d.ts +3 -0
- package/dist/cli/first-run-channels.js +63 -0
- package/dist/cli/instance.d.ts +41 -0
- package/dist/cli/instance.js +155 -0
- package/dist/cli/invoke-stream.d.ts +5 -2
- package/dist/cli/invoke-stream.js +7 -2
- package/dist/cli/program.js +282 -110
- package/dist/cli/serve.d.ts +4 -5
- package/dist/cli/serve.js +4 -7
- package/dist/cli/shared.d.ts +11 -2
- package/dist/cli/shared.js +32 -19
- package/dist/cli/ticket.js +50 -6
- package/dist/content/declare.d.ts +31 -37
- package/dist/content/declare.js +59 -60
- package/dist/content/file.d.ts +23 -12
- package/dist/content/file.js +48 -35
- package/dist/content/git.d.ts +0 -10
- package/dist/content/git.js +5 -39
- package/dist/content/resolve.d.ts +18 -22
- package/dist/content/resolve.js +32 -66
- package/dist/content/source.d.ts +7 -8
- package/dist/content/source.js +10 -28
- package/dist/credentials-page.d.ts +26 -0
- package/dist/credentials-page.js +185 -0
- package/dist/declared-secrets.d.ts +10 -3
- package/dist/declared-secrets.js +8 -4
- package/dist/deploy/agentcore/destroy.d.ts +4 -1
- package/dist/deploy/agentcore/destroy.js +25 -1
- package/dist/deploy/agentcore/forwarder.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +7 -21
- package/dist/deploy/agentcore/plan.js +47 -49
- package/dist/deploy/agentcore/run.d.ts +18 -10
- package/dist/deploy/agentcore/run.js +93 -70
- package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
- package/dist/deploy/agentcore/runtime-secrets.js +108 -0
- package/dist/deploy/agentcore/shell.js +12 -19
- package/dist/deploy/agentcore/sigv4.d.ts +7 -0
- package/dist/deploy/agentcore/sigv4.js +15 -0
- package/dist/deploy/agentcore/slr.d.ts +18 -0
- package/dist/deploy/agentcore/slr.js +27 -0
- package/dist/deploy/agentcore/store.d.ts +7 -0
- package/dist/deploy/agentcore/store.js +115 -0
- package/dist/deploy/api.d.ts +14 -14
- package/dist/deploy/api.js +26 -24
- package/dist/deploy/box-shell.d.ts +3 -1
- package/dist/deploy/build-context.d.ts +4 -4
- package/dist/deploy/build-context.js +11 -15
- package/dist/deploy/channel-ingress.d.ts +35 -9
- package/dist/deploy/channel-ingress.js +119 -22
- package/dist/deploy/container.d.ts +35 -10
- package/dist/deploy/container.js +79 -37
- package/dist/deploy/delivered-text.d.ts +13 -0
- package/dist/deploy/delivered-text.js +86 -0
- package/dist/deploy/docker/plan.d.ts +7 -8
- package/dist/deploy/docker/plan.js +31 -52
- package/dist/deploy/docker/run.d.ts +5 -43
- package/dist/deploy/docker/run.js +9 -84
- package/dist/deploy/file-store.d.ts +2 -0
- package/dist/deploy/file-store.js +17 -0
- package/dist/deploy/fly/plan.d.ts +3 -2
- package/dist/deploy/fly/plan.js +20 -9
- package/dist/deploy/fly/run.d.ts +18 -8
- package/dist/deploy/fly/run.js +24 -42
- package/dist/deploy/fly/store.d.ts +12 -0
- package/dist/deploy/fly/store.js +83 -0
- package/dist/deploy/hosts.d.ts +1 -2
- package/dist/deploy/hosts.js +1 -2
- package/dist/deploy/preflight.d.ts +18 -5
- package/dist/deploy/preflight.js +135 -63
- package/dist/deploy/railway/ignore.d.ts +8 -0
- package/dist/deploy/railway/ignore.js +57 -0
- package/dist/deploy/railway/plan.d.ts +1 -2
- package/dist/deploy/railway/plan.js +8 -6
- package/dist/deploy/railway/run.d.ts +21 -15
- package/dist/deploy/railway/run.js +32 -78
- package/dist/deploy/railway/store.d.ts +29 -0
- package/dist/deploy/railway/store.js +143 -0
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +4 -3
- package/dist/deploy/secret-store.d.ts +63 -0
- package/dist/deploy/secret-store.js +98 -0
- package/dist/deploy/secrets.d.ts +29 -46
- package/dist/deploy/secrets.js +49 -122
- package/dist/deploy/workspace.d.ts +6 -12
- package/dist/deploy/workspace.js +18 -94
- package/dist/dev-supervisor.js +6 -8
- package/dist/environment/declare.js +3 -2
- package/dist/environment/mise.d.ts +3 -1
- package/dist/environment/mise.js +4 -2
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/harnesses/models/catalog.d.ts +1 -0
- package/dist/harnesses/models/catalog.js +1 -2
- package/dist/harnesses/models/environment.d.ts +7 -2
- package/dist/harnesses/models/environment.js +9 -5
- package/dist/harnesses/models/host.d.ts +0 -7
- package/dist/harnesses/models/host.js +10 -1
- package/dist/harnesses/models/login.d.ts +0 -8
- package/dist/harnesses/models/login.js +5 -4
- package/dist/harnesses/models/models.d.ts +7 -2
- package/dist/harnesses/models/models.js +28 -4
- package/dist/harnesses/pi-durable/agent.js +59 -38
- package/dist/harnesses/pi-durable/build.d.ts +12 -0
- package/dist/harnesses/pi-durable/build.js +270 -0
- package/dist/harnesses/pi-durable/built.d.ts +24 -0
- package/dist/harnesses/pi-durable/built.js +136 -0
- package/dist/harnesses/pi-durable/config.d.ts +81 -0
- package/dist/harnesses/pi-durable/config.js +266 -0
- package/dist/harnesses/pi-durable/define.d.ts +63 -13
- package/dist/harnesses/pi-durable/define.js +23 -2
- package/dist/harnesses/pi-durable/definition.d.ts +72 -0
- package/dist/harnesses/pi-durable/definition.js +143 -0
- package/dist/harnesses/pi-durable/events.d.ts +4 -3
- package/dist/harnesses/pi-durable/events.js +179 -136
- package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
- package/dist/harnesses/pi-durable/execution-env.js +11 -0
- package/dist/harnesses/pi-durable/mcp.js +1 -3
- package/dist/harnesses/pi-durable/open.d.ts +35 -37
- package/dist/harnesses/pi-durable/open.js +91 -153
- package/dist/harnesses/pi-durable/reads.d.ts +0 -1
- package/dist/harnesses/pi-durable/reads.js +1 -3
- package/dist/harnesses/pi-durable/registry.d.ts +4 -6
- package/dist/harnesses/pi-durable/registry.js +29 -26
- package/dist/harnesses/pi-durable/release.d.ts +2 -2
- package/dist/harnesses/pi-durable/release.js +10 -7
- package/dist/harnesses/pi-durable/serve.d.ts +91 -15
- package/dist/harnesses/pi-durable/serve.js +109 -47
- package/dist/harnesses/pi-durable/sessions.js +15 -12
- package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
- package/dist/harnesses/pi-durable/text-sources.js +55 -0
- package/dist/harnesses/pi-durable/text.d.ts +16 -4
- package/dist/harnesses/pi-durable/text.js +115 -59
- package/dist/harnesses/pi-durable/wake.js +8 -8
- package/dist/http/client.js +9 -3
- package/dist/http/contract.d.ts +3 -3
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -1
- package/dist/instance-name.d.ts +7 -0
- package/dist/instance-name.js +26 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/mcp/client.js +11 -2
- package/dist/mcp/config.d.ts +70 -18
- package/dist/mcp/config.js +136 -53
- package/dist/mcp/host.d.ts +5 -2
- package/dist/mcp/host.js +14 -6
- package/dist/mcp/sign-in.js +2 -2
- package/dist/mcp-api.d.ts +14 -0
- package/dist/paths.d.ts +22 -8
- package/dist/paths.js +44 -11
- package/dist/runtime-websocket.d.ts +14 -0
- package/dist/runtime-websocket.js +39 -0
- package/dist/scaffold/add-channel.d.ts +9 -16
- package/dist/scaffold/add-channel.js +13 -83
- package/dist/scaffold/templates/env.example +4 -5
- package/dist/scaffold/templates/fastagent.config.ts +20 -14
- package/dist/scaffold/templates/gitignore +9 -6
- package/dist/scaffold/templates.d.ts +6 -2
- package/dist/scaffold/templates.js +7 -2
- package/dist/scaffold/vendor-skill.js +4 -2
- package/dist/service.d.ts +44 -6
- package/dist/service.js +110 -10
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +1 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/strip-edges.d.ts +10 -0
- package/dist/strip-edges.js +22 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +0 -1
- package/dist/tunnel.js +2 -8
- package/dist/value-file.d.ts +11 -6
- package/dist/value-file.js +21 -9
- package/dist/version.d.ts +7 -1
- package/dist/version.js +9 -3
- package/package.json +6 -5
- package/dist/channels/lark/credentials-page.d.ts +0 -25
- package/dist/channels/lark/credentials-page.js +0 -206
- package/dist/channels/lark/onboard.d.ts +0 -52
- package/dist/channels/lark/onboard.js +0 -86
- package/dist/channels/slack/config-api.d.ts +0 -69
- package/dist/channels/slack/config-api.js +0 -162
- package/dist/channels/slack/onboard.d.ts +0 -40
- package/dist/channels/slack/onboard.js +0 -136
- package/dist/channels/slack/onboarding-state.d.ts +0 -31
- package/dist/channels/slack/onboarding-state.js +0 -64
- package/dist/channels/slack/register-webhook.d.ts +0 -12
- package/dist/channels/slack/register-webhook.js +0 -67
- package/dist/channels/slack/setup-server.d.ts +0 -32
- package/dist/channels/slack/setup-server.js +0 -246
- package/dist/cli/add-feishu.d.ts +0 -41
- package/dist/cli/add-feishu.js +0 -391
- package/dist/cli/add-slack.d.ts +0 -18
- package/dist/cli/add-slack.js +0 -216
- package/dist/harnesses/pi-durable/parked.d.ts +0 -18
- package/dist/harnesses/pi-durable/parked.js +0 -27
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type EffectiveConfig, type InstanceConfigSource } from "../harnesses/pi-durable/config.ts";
|
|
2
2
|
import { type DeclaredChannel } from "../channels/discover.ts";
|
|
3
3
|
import type { DeclaredSecret } from "../declared-secrets.ts";
|
|
4
4
|
import { type ContainerInput } from "./container.ts";
|
|
@@ -27,15 +27,23 @@ interface DeployFacts {
|
|
|
27
27
|
* ({@link credentialRoute}).
|
|
28
28
|
*/
|
|
29
29
|
modelAuth: string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* The variables the model's provider reads its key from, and whether the deployment needs one of them set on its
|
|
32
|
+
* target: when no key is in the value file and the provider has no login to run on the box, the key is the only way in.
|
|
33
|
+
*/
|
|
34
|
+
modelKey: {
|
|
35
|
+
variables: string[];
|
|
36
|
+
required: boolean;
|
|
37
|
+
};
|
|
30
38
|
/**
|
|
31
39
|
* The provider the deployment must log in to itself (`fastagent login --deployment`): the model's credential does
|
|
32
40
|
* not travel, because it is neither a variable in the value file nor resolved by the definition's own provider code.
|
|
33
41
|
* Nothing from this machine's credentials file is ever copied, so a login on the box is its only holder.
|
|
34
42
|
*/
|
|
35
43
|
boxLogin: string | undefined;
|
|
36
|
-
/** Every declaration the definition makes — the names the
|
|
37
|
-
declaredSecrets: DeclaredSecret[];
|
|
38
|
-
/** The runbook's variable list: the declared names,
|
|
44
|
+
/** Every declaration the definition makes — the names the target must hold, by declaring file. */
|
|
45
|
+
declaredSecrets: readonly DeclaredSecret[];
|
|
46
|
+
/** The runbook's variable list: the declared names, each hinted by the file that declared it. */
|
|
39
47
|
secrets: DeploymentSecret[];
|
|
40
48
|
/** Container facts shared by the plan and the generated Dockerfile — ONE source, so they can't drift. */
|
|
41
49
|
container: ContainerInput;
|
|
@@ -59,7 +67,12 @@ export interface DeployReport {
|
|
|
59
67
|
}
|
|
60
68
|
interface PreflightInput {
|
|
61
69
|
agentDir: string;
|
|
62
|
-
config:
|
|
70
|
+
config: EffectiveConfig;
|
|
71
|
+
/** The instance configuration this deployment delivers (`--config`): as read, and its file's text. */
|
|
72
|
+
delivered?: {
|
|
73
|
+
source: InstanceConfigSource;
|
|
74
|
+
text: string;
|
|
75
|
+
};
|
|
63
76
|
/** `--run` fully deploys, so a definition that resolves NO model is a GATE (a known crash-loop); else it warns. */
|
|
64
77
|
run: boolean;
|
|
65
78
|
/** `--force` regenerates the artifacts fastagent OWNS, so a kept `.dockerignore`'s content checks do not apply. */
|
package/dist/deploy/preflight.js
CHANGED
|
@@ -8,42 +8,78 @@ import { basename, join, relative } from "node:path";
|
|
|
8
8
|
import { isModelSpec, isReleaseAgentName } from "./workspace.js";
|
|
9
9
|
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
10
10
|
import { resolveAuthPath } from "../harnesses/models/credentials.js";
|
|
11
|
-
import { environmentAuth, interactiveLoginKind, loginProviders, modelsOver, providerOf, } from "../harnesses/models/models.js";
|
|
11
|
+
import { environmentAuth, interactiveLoginKind, keyVariables, listModels, loginProviders, modelHint, modelsOver, providerOf, } from "../harnesses/models/models.js";
|
|
12
12
|
import { modelEnvironment } from "../harnesses/models/environment.js";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { projectInstanceConfig, } from "../harnesses/pi-durable/config.js";
|
|
14
|
+
import { definitionSecrets } from "../harnesses/pi-durable/definition.js";
|
|
15
|
+
import { buildDefinitionShape, loadSourceDefinition } from "../harnesses/pi-durable/open.js";
|
|
16
|
+
import { INSTANCE_CONFIG_FILE, exists } from "../paths.js";
|
|
15
17
|
import { channelIngress } from "../channels/discover.js";
|
|
16
18
|
import { loadContent, loadEnvironment } from "../content/file.js";
|
|
17
19
|
import { CHANNEL_KINDS } from "../scaffold/add-channel.js";
|
|
18
20
|
import { RUNTIME_FLOOR, belowFloor, detectRuntime, listsFastagent, readPackageJson } from "../runtime.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
+
import { DELIVERED_CONFIG_DIR, isGeneratedDockerfile } from "./container.js";
|
|
22
|
+
import { deliveredTextProblem } from "./delivered-text.js";
|
|
23
|
+
import { declareMcp, literalCredentialAdvice, literalCredentials, relativeCommands, } from "../mcp/config.js";
|
|
21
24
|
import { buildContextPaths, checkKeptIgnoreFiles } from "./build-context.js";
|
|
22
25
|
import { dotEnvPath, loadEnvValues } from "../value-file.js";
|
|
23
26
|
import { deploymentSecrets, isEnvKey } from "./secrets.js";
|
|
24
27
|
import { DEFAULT_HTTP_PORT } from "../service.js";
|
|
25
28
|
import { declaredVariables, declaresTool, runtimeAdvice } from "../environment/declare.js";
|
|
26
29
|
import { checkPackages, MISE_PACKAGE_NAMES, miseBinary, packageSpecs } from "../environment/mise.js";
|
|
30
|
+
/** What a host's clone of a `github` entry reads; optional, since a public repository needs none. */
|
|
31
|
+
const GITHUB_TOKEN_SECRET = {
|
|
32
|
+
name: "GITHUB_TOKEN",
|
|
33
|
+
declaredIn: "context.json (a github content entry)",
|
|
34
|
+
description: "git's credential for a private repository, and for the agent to push",
|
|
35
|
+
optional: true,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The MCP servers the box will open: the program's, in the image, and the delivered configuration's. A name both
|
|
39
|
+
* declare, or one that cannot be read, is an issue (the box would refuse to open); a literal credential is a warning,
|
|
40
|
+
* since it ships in the image and FastAgent cannot tell a secret from a setting; a relative stdio command in the
|
|
41
|
+
* delivered configuration is an issue, since nothing delivers the script it names. (The program's own is relative to
|
|
42
|
+
* its package, and `build` refuses one that it does not copy.)
|
|
43
|
+
* `args` are not checked: FastAgent cannot know which strings are paths. Answers whether the list reads.
|
|
44
|
+
*/
|
|
45
|
+
function checkMcp(agentDir, delivered, report) {
|
|
46
|
+
let declarations;
|
|
47
|
+
try {
|
|
48
|
+
declarations = declareMcp(agentDir, delivered);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
report.issue(messageOf(error));
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
for (const literal of literalCredentials(declarations)) {
|
|
55
|
+
report.warn(`${literal.file} mcpServers.${literal.server}.${literal.field}.${literal.key} is a literal value, and this ` +
|
|
56
|
+
`deploy puts it in the image: ${literalCredentialAdvice(literal)} and set that name with ` +
|
|
57
|
+
`\`fastagent secrets set <host> <NAME>\``);
|
|
58
|
+
}
|
|
59
|
+
for (const { name, file, command } of relativeCommands(declarations).filter(({ origin }) => origin === "instance")) {
|
|
60
|
+
report.issue(`${file} mcpServers.${name}: the command "${command}" is a relative path, and a deploy delivers the configuration ` +
|
|
61
|
+
`but not the script it names: write an absolute path on the box, or a command found on its PATH`);
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
27
65
|
/**
|
|
28
66
|
* What each content entry becomes on the host, one line each, so none is missing there unsaid (agent-model.md §3). A
|
|
29
|
-
* repository
|
|
30
|
-
*
|
|
31
|
-
*
|
|
67
|
+
* repository, the program's or the delivered configuration's, is cloned there. A directory of this machine is not on
|
|
68
|
+
* the host, and FastAgent maps none onto it, so the delivered configuration naming one is an issue: the deployed agent
|
|
69
|
+
* would lack data it was configured to work on.
|
|
32
70
|
*/
|
|
33
|
-
function checkContent(
|
|
71
|
+
function checkContent(declared, delivered, storageResets, report) {
|
|
72
|
+
const fate = storageResets
|
|
73
|
+
? "cloned afresh on every deployment, since the host's storage starts over; what the agent did not push is lost"
|
|
74
|
+
: "cloned on the host, and brought up to date in place at each start";
|
|
34
75
|
for (const entry of declared) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
: "cloned on the host, and brought up to date in place at each start";
|
|
38
|
-
report.note(`works on ${entry.name}: github ${entry.repo}${entry.ref ? `@${entry.ref}` : ""}, ${fate}`);
|
|
39
|
-
}
|
|
40
|
-
for (const binding of bound) {
|
|
41
|
-
if (binding.kind !== "local")
|
|
76
|
+
if (entry.kind === "github") {
|
|
77
|
+
report.note(`works on ${entry.name}: github ${entry.repo}${entry.ref ? `@${entry.ref}` : ""}, ${fate}`);
|
|
42
78
|
continue;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
`
|
|
79
|
+
}
|
|
80
|
+
report.issue(`works on ${entry.name}: a directory of this machine (${entry.path}), in ${delivered}, which the host does not ` +
|
|
81
|
+
`have: declare a repository instead (github:owner/repo), or copy what the agent only needs to read into the ` +
|
|
82
|
+
`agent directory, which every release carries`);
|
|
47
83
|
}
|
|
48
84
|
}
|
|
49
85
|
/**
|
|
@@ -52,12 +88,12 @@ function checkContent({ declared, bound }, storageResets, report) {
|
|
|
52
88
|
* the agent's mise cannot check them, which is an issue (a warning without `--run`), as a code input that cannot load
|
|
53
89
|
* is.
|
|
54
90
|
*/
|
|
55
|
-
async function checkEnvironment(agentDir, packages,
|
|
91
|
+
async function checkEnvironment(agentDir, packages, pkg, report) {
|
|
56
92
|
if (Object.keys(packages).length === 0)
|
|
57
93
|
return;
|
|
58
94
|
const linux = MISE_PACKAGE_NAMES.filter((name) => name.includes("-linux-"));
|
|
59
95
|
const missing = linux.filter((name) => !(name in (pkg.optionalDependencies ?? {})));
|
|
60
|
-
if (
|
|
96
|
+
if (missing.length > 0) {
|
|
61
97
|
report.issue(`context.json declares packages, and the image would have no mise to install them with: the agent's ` +
|
|
62
98
|
`package.json does not list ${missing.join(", ")}. Run \`fastagent env install\` in the agent directory.`);
|
|
63
99
|
return;
|
|
@@ -103,7 +139,7 @@ export async function preflightDeploy(input) {
|
|
|
103
139
|
}
|
|
104
140
|
}
|
|
105
141
|
async function gatherFacts(input, report) {
|
|
106
|
-
const { agentDir, config, force, noResidentProcess, storageResets = false, servesApi = false } = input;
|
|
142
|
+
const { agentDir, config, delivered, force, noResidentProcess, storageResets = false, servesApi = false } = input;
|
|
107
143
|
// The release manifest carries this name into the container, where it is joined onto the storage root — so `init`'s
|
|
108
144
|
// "one path segment" is not enough here.
|
|
109
145
|
if (!isReleaseAgentName(basename(agentDir))) {
|
|
@@ -111,13 +147,28 @@ async function gatherFacts(input, report) {
|
|
|
111
147
|
`may use only letters, digits, "-" and "_"; rename it (the fastagent.config.ts inside is what ` +
|
|
112
148
|
`makes it an agent, never its name)`);
|
|
113
149
|
}
|
|
114
|
-
|
|
115
|
-
|
|
150
|
+
// A deployed agent is built (`start` opens `dist/`), and a build needs the project's own manifest and TypeScript.
|
|
151
|
+
if (!(await exists(join(agentDir, "package.json")))) {
|
|
152
|
+
throw new DeployGate(`the agent directory has no package.json, and a deployed agent is built: \`fastagent build\` needs one with ` +
|
|
153
|
+
`"type": "module" and typescript (\`fastagent init\` writes both)`);
|
|
154
|
+
}
|
|
155
|
+
const content = loadContent(agentDir, delivered?.source);
|
|
156
|
+
checkContent(content, delivered?.source.label, storageResets, report);
|
|
157
|
+
const textProblem = delivered === undefined ? undefined : deliveredTextProblem(agentDir, delivered.source);
|
|
158
|
+
if (textProblem !== undefined)
|
|
159
|
+
report.issue(textProblem);
|
|
160
|
+
const mcpOpens = checkMcp(agentDir, delivered?.source, report);
|
|
161
|
+
// The project's own, the development instance's: a deploy carries only the configuration it is given.
|
|
162
|
+
const own = await projectInstanceConfig(agentDir);
|
|
163
|
+
if (own !== undefined && own.label !== delivered?.source.label) {
|
|
164
|
+
report.note(`${INSTANCE_CONFIG_FILE} is the development instance's configuration and a deploy does not carry it` +
|
|
165
|
+
(delivered === undefined ? ": pass --config <file> to deliver one" : `; it carries ${delivered.source.label}`));
|
|
166
|
+
}
|
|
116
167
|
// The definition the box will load on every start, read here first: a refusal in it (a module that fails to import,
|
|
117
168
|
// two tools under one name) builds a perfectly good image that crash-loops on fly/railway/docker and fails every
|
|
118
169
|
// invocation on AgentCore. This is the boundary that turns that refusal into a deploy finding, so the error is
|
|
119
170
|
// carried whole into the issue rather than rethrown.
|
|
120
|
-
const definition = await
|
|
171
|
+
const definition = await loadSourceDefinition(agentDir).catch((error) => {
|
|
121
172
|
report.issue(`the definition does not load, so the deployed agent would not start: ${messageOf(error)}`);
|
|
122
173
|
return undefined;
|
|
123
174
|
});
|
|
@@ -125,11 +176,12 @@ async function gatherFacts(input, report) {
|
|
|
125
176
|
// cannot disagree about it.
|
|
126
177
|
const valueFile = relative(agentDir, dotEnvPath(agentDir));
|
|
127
178
|
const values = loadEnvValues(dotEnvPath(agentDir));
|
|
128
|
-
// A host has no credential of the author's: what it clones with is GITHUB_TOKEN,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
`
|
|
179
|
+
// A host has no credential of the author's: what it clones with is GITHUB_TOKEN, a value of the target like any other.
|
|
180
|
+
// This reads this machine's file only, so a target that already holds one is told as much.
|
|
181
|
+
if (content.length > 0 && !values.get("GITHUB_TOKEN")) {
|
|
182
|
+
report.note(`no GITHUB_TOKEN in ${valueFile}: unless the target holds one, the host clones without a credential, which ` +
|
|
183
|
+
`reaches public repositories only, and the agent cannot push from its clones. \`fastagent secrets set <host> ` +
|
|
184
|
+
`GITHUB_TOKEN\` gives it access`);
|
|
133
185
|
}
|
|
134
186
|
const model = resolveDeployModel(config, values, valueFile);
|
|
135
187
|
if (model.invalid !== undefined) {
|
|
@@ -138,7 +190,7 @@ async function gatherFacts(input, report) {
|
|
|
138
190
|
throw new DeployGate(`the model in ${model.source} is not a "provider/modelId" spec: ${JSON.stringify(model.invalid)}`);
|
|
139
191
|
}
|
|
140
192
|
if (!model.spec) {
|
|
141
|
-
const issue = `no model resolves for this deployment — set \`model: "provider/id"\` in fastagent.config.ts (it travels ` +
|
|
193
|
+
const issue = `no model resolves for this deployment — set \`defaults: { model: "provider/id" }\` in fastagent.config.ts (it travels ` +
|
|
142
194
|
`in the image), or FASTAGENT_MODEL in ${valueFile} (deploy records that one in the release manifest)` +
|
|
143
195
|
// Whoever has the variable set right here sees `fastagent info` report a model, so "no model resolves" reads
|
|
144
196
|
// like a bug until the message says which environment was read. It states that fact WITHOUT attributing the
|
|
@@ -154,8 +206,14 @@ async function gatherFacts(input, report) {
|
|
|
154
206
|
report.note(`model ${model.spec} (source: ${model.source})`);
|
|
155
207
|
}
|
|
156
208
|
const modelSpec = model.spec;
|
|
157
|
-
const declaredSecrets =
|
|
158
|
-
|
|
209
|
+
const declaredSecrets = [
|
|
210
|
+
...(definition
|
|
211
|
+
? definitionSecrets(definition, config, mcpOpens ? delivered?.source : undefined)
|
|
212
|
+
: declaredVariables(loadEnvironment(agentDir))),
|
|
213
|
+
// The host's clone of a github entry reads it, so `secrets list|push|set` and `deploy` know the name.
|
|
214
|
+
...(content.some((entry) => entry.kind === "github") ? [GITHUB_TOKEN_SECRET] : []),
|
|
215
|
+
];
|
|
216
|
+
const code = definition ? buildShapes(definition, values, config, report) : undefined;
|
|
159
217
|
const channels = [...(code?.channels ?? [])].map(([name, module]) => ({
|
|
160
218
|
name,
|
|
161
219
|
ingress: channelIngress(module),
|
|
@@ -163,7 +221,7 @@ async function gatherFacts(input, report) {
|
|
|
163
221
|
for (const { name, ingress } of channels) {
|
|
164
222
|
if (CHANNEL_KINDS.includes(name))
|
|
165
223
|
continue;
|
|
166
|
-
const secretsPart = `
|
|
224
|
+
const secretsPart = `the variables it declares are the target's to hold (\`fastagent secrets\`), like every other`;
|
|
167
225
|
report.note(ingress === "long-connection"
|
|
168
226
|
? `long-connection channel "${name}" is custom — ${secretsPart}; generated deploy plans keep the process running and skip webhook registration`
|
|
169
227
|
: `route channel "${name}" is custom — ${secretsPart}; configure its webhook yourself`);
|
|
@@ -195,19 +253,24 @@ async function gatherFacts(input, report) {
|
|
|
195
253
|
valueFile,
|
|
196
254
|
});
|
|
197
255
|
}
|
|
256
|
+
const modelProvider = modelSpec ? loginProviders(providers).find((p) => p.id === providerOf(modelSpec)) : undefined;
|
|
257
|
+
const modelKey = {
|
|
258
|
+
variables: modelProvider ? await keyVariables(modelProvider) : [],
|
|
259
|
+
required: boxLogin !== undefined && !hasLogin(boxLogin, providers),
|
|
260
|
+
};
|
|
198
261
|
if (boxLogin !== undefined) {
|
|
199
|
-
if (
|
|
200
|
-
report.
|
|
201
|
-
`
|
|
262
|
+
if (modelKey.required) {
|
|
263
|
+
report.note(`${modelSpec}: "${boxLogin}" has no login to run on the box, so its API key (${modelKey.variables.join(" or ")}) ` +
|
|
264
|
+
`must be set on the target`);
|
|
202
265
|
}
|
|
203
266
|
else {
|
|
204
267
|
report.note(`${modelSpec}: no credential ships with this deploy (this machine's logins and shell stay here) — once the ` +
|
|
205
268
|
`box is up, \`--run\` asks it: it keeps what it already authenticates ${boxLogin} with, else logs in ` +
|
|
206
|
-
`(\`fastagent login ${boxLogin} --deployment\`). Or set ${boxLogin}'s API key
|
|
269
|
+
`(\`fastagent login ${boxLogin} --deployment\`). Or set ${boxLogin}'s API key on the target ` +
|
|
270
|
+
`(\`fastagent secrets set <host> ${modelKey.variables[0] ?? "<KEY_NAME>"}\`)`);
|
|
207
271
|
}
|
|
208
272
|
}
|
|
209
273
|
// Container facts (shared by every host) + the warnings that follow.
|
|
210
|
-
const hasPackageJson = await exists(join(agentDir, "package.json"));
|
|
211
274
|
const pkg = await readPackageJson(agentDir);
|
|
212
275
|
const { runtime, bunVersion, hasLockfile } = detectRuntime(agentDir, pkg);
|
|
213
276
|
const kept = await keptDockerfile(agentDir);
|
|
@@ -226,18 +289,13 @@ async function gatherFacts(input, report) {
|
|
|
226
289
|
((await exists(join(agentDir, "pnpm-lock.yaml"))) || (await exists(join(agentDir, "yarn.lock"))));
|
|
227
290
|
// Does the baked agent directory ship a `.git`?
|
|
228
291
|
const shipsGit = await exists(join(agentDir, ".git"));
|
|
229
|
-
// After the facts: the deps sentence must match the agent's actual shape (a markdown-only agent has no package.json
|
|
230
|
-
// and installs nothing — the note must not point at a file that doesn't exist).
|
|
231
|
-
const deps = hasPackageJson
|
|
232
|
-
? `its package.json dependencies are installed in the image`
|
|
233
|
-
: `the agent has no package.json, so no deps are installed (the pinned global CLI serves the directory)`;
|
|
234
292
|
// What a RELEASE does — host-neutral, because how long the storage under it lives is the host's own answer and its
|
|
235
293
|
// runbook gives it (a Fly volume outlives every deploy; AgentCore's mount does not).
|
|
236
294
|
report.note(`the agent directory is baked as the definition (WYSIWYG — what you see is what ships, git or not, clean or ` +
|
|
237
|
-
`not);
|
|
238
|
-
`credentials in place — for how long, see this host's storage note below`);
|
|
295
|
+
`not); its package.json dependencies are installed in the image. Every release replaces the image and leaves the instance's working directory, state and ` +
|
|
296
|
+
`credentials in place on the storage — for how long, see this host's storage note below`);
|
|
239
297
|
// A code agent with no lockfile builds via a non-frozen install (ranges resolve at build time) — not reproducible.
|
|
240
|
-
if (
|
|
298
|
+
if (!hasLockfile) {
|
|
241
299
|
const lock = runtime === "bun" ? "bun.lock" : "package-lock.json";
|
|
242
300
|
report.warn(hasOtherLock
|
|
243
301
|
? `the generated Dockerfile is npm-based — your pnpm/yarn lockfile is NOT used (build runs ` +
|
|
@@ -246,7 +304,7 @@ async function gatherFacts(input, report) {
|
|
|
246
304
|
`Run \`${install}\` and commit the lockfile for pinned redeploys.`);
|
|
247
305
|
}
|
|
248
306
|
// The code-path Dockerfile runs `${runner}`.
|
|
249
|
-
if (
|
|
307
|
+
if (!listsFastagent(pkg)) {
|
|
250
308
|
report.warn(`package.json does not list @fastagent-sh/fastagent — the image's \`${runner}\` has no local bin to run, ` +
|
|
251
309
|
`so the container fails at start. Add it to dependencies and re-run \`${install}\`.`);
|
|
252
310
|
}
|
|
@@ -254,12 +312,12 @@ async function gatherFacts(input, report) {
|
|
|
254
312
|
await checkKeptIgnoreFiles({ agentDir, force, paths }, report);
|
|
255
313
|
const environment = loadEnvironment(agentDir);
|
|
256
314
|
const packages = environment?.packages ?? {};
|
|
257
|
-
await checkEnvironment(agentDir, packages,
|
|
315
|
+
await checkEnvironment(agentDir, packages, pkg, report);
|
|
258
316
|
const installsPackages = Object.keys(packages).length > 0;
|
|
259
317
|
// Write-back mechanics are fastagent's (the policy is the agent's prompt's). A repository content entry is cloned on the
|
|
260
318
|
// host, which takes git too. mise downloads over TLS, and the slim base image has no CA certificates; rustup, which
|
|
261
319
|
// mise installs rust with, downloads with curl. The agent's own apt packages join them, in one layer.
|
|
262
|
-
const needsGit = shipsGit || content.
|
|
320
|
+
const needsGit = shipsGit || content.length > 0;
|
|
263
321
|
const apt = [
|
|
264
322
|
...new Set([
|
|
265
323
|
...(needsGit ? ["git"] : []),
|
|
@@ -272,30 +330,30 @@ async function gatherFacts(input, report) {
|
|
|
272
330
|
releaseId: randomUUID(),
|
|
273
331
|
agent: basename(agentDir),
|
|
274
332
|
machineryPaths: paths.machineryPaths,
|
|
275
|
-
hasPackageJson,
|
|
276
333
|
runtime,
|
|
277
334
|
bunVersion,
|
|
278
335
|
hasLockfile,
|
|
279
|
-
version: await fastagentVersion(),
|
|
280
336
|
apt,
|
|
281
337
|
...(installsPackages ? { packages: packageSpecs(packages) } : {}),
|
|
282
338
|
...(model.envValue !== undefined ? { modelSpec: model.envValue } : {}),
|
|
339
|
+
...(delivered !== undefined ? { config: delivered.text } : {}),
|
|
283
340
|
shipsGit,
|
|
284
341
|
};
|
|
285
342
|
const port = config.http?.port ?? DEFAULT_HTTP_PORT;
|
|
286
343
|
if (kept !== undefined)
|
|
287
|
-
checkKeptDockerfile(kept, agentDir, model.envValue, valueFile, report);
|
|
344
|
+
checkKeptDockerfile(kept, agentDir, model.envValue, valueFile, delivered !== undefined, report);
|
|
288
345
|
return {
|
|
289
346
|
channels,
|
|
290
347
|
hasCron,
|
|
291
348
|
values,
|
|
292
349
|
valueFile,
|
|
293
350
|
modelAuth,
|
|
351
|
+
modelKey,
|
|
294
352
|
boxLogin,
|
|
295
353
|
container,
|
|
296
354
|
port,
|
|
297
355
|
declaredSecrets,
|
|
298
|
-
secrets: deploymentSecrets(modelAuth, declaredSecrets
|
|
356
|
+
secrets: deploymentSecrets(modelAuth, declaredSecrets),
|
|
299
357
|
};
|
|
300
358
|
}
|
|
301
359
|
/**
|
|
@@ -303,9 +361,9 @@ async function gatherFacts(input, report) {
|
|
|
303
361
|
* deploy runs on a machine that may hold none of them, and whether the value file supplies them is the values gate's
|
|
304
362
|
* question, asked by name. A build that fails is an issue, as a module that fails to load is.
|
|
305
363
|
*/
|
|
306
|
-
function buildShapes(definition, values, report) {
|
|
364
|
+
function buildShapes(definition, values, config, report) {
|
|
307
365
|
try {
|
|
308
|
-
return buildDefinitionShape(definition, Object.fromEntries(values));
|
|
366
|
+
return buildDefinitionShape(definition, Object.fromEntries(values), config);
|
|
309
367
|
}
|
|
310
368
|
catch (error) {
|
|
311
369
|
report.issue(`the definition does not build, so the deployed agent would not start: ${messageOf(error)}`);
|
|
@@ -343,8 +401,8 @@ async function checkModelExists(agentDir, spec, providers, report, box) {
|
|
|
343
401
|
`as on a first deploy, fetches it from pi.dev as it starts, and refuses to start on "${spec}" if it cannot`);
|
|
344
402
|
return;
|
|
345
403
|
}
|
|
346
|
-
report.issue(`no provider of the deployed agent offers "${spec}"
|
|
347
|
-
`
|
|
404
|
+
report.issue(`no provider of the deployed agent offers "${spec}": ${modelHint(listModels(models), spec)}. Its providers are ` +
|
|
405
|
+
`pi-ai's built-ins, pi's published catalog and the ones fastagent.config.ts declares`);
|
|
348
406
|
}
|
|
349
407
|
/**
|
|
350
408
|
* HOW THE MODEL'S CREDENTIAL REACHES THE BOX, from what the deploy ships and nothing else. The box is the one authority
|
|
@@ -391,14 +449,22 @@ async function keptDockerfile(agentDir) {
|
|
|
391
449
|
*/
|
|
392
450
|
function checkKeptDockerfile(dockerfileText, agentDir,
|
|
393
451
|
/** The model the release manifest carries (set only when the value file named it). */
|
|
394
|
-
modelFromValueFile, valueFile,
|
|
395
|
-
|
|
452
|
+
modelFromValueFile, valueFile,
|
|
453
|
+
/** The release delivers an instance configuration, which the image must copy and name. */
|
|
454
|
+
deliversConfig, report) {
|
|
455
|
+
// What the image installs: the packages and apt packages. Variables are the target's, whatever the image.
|
|
396
456
|
const environment = loadEnvironment(agentDir);
|
|
397
457
|
if (Object.keys(environment?.packages ?? {}).length > 0 || (environment?.aptPackages.length ?? 0) > 0) {
|
|
398
458
|
report.warn(`kept your hand-written Dockerfile — it installs the environment context.json declares only if it says so: ` +
|
|
399
459
|
`a generated Dockerfile shows the layers (the apt packages, then \`mise install --system\` of the packages ` +
|
|
400
460
|
`context.json declares).`);
|
|
401
461
|
}
|
|
462
|
+
// `start` opens dist/. Docker and Fly upload the directory as it is, but `railway up` follows .gitignore, which the
|
|
463
|
+
// scaffold gives `/dist`: without a build step of its own the image has none, and the box does not start.
|
|
464
|
+
if (!/\bfastagent(?:\s+run)?\s+build\b|\bnpm\s+run\s+build\b/.test(dockerfileText)) {
|
|
465
|
+
report.warn(`your Dockerfile does not run \`fastagent build\`: \`start\` opens dist/, which an upload that follows ` +
|
|
466
|
+
`.gitignore (Railway) leaves out. Add \`RUN ./node_modules/.bin/fastagent build\` after copying the agent.`);
|
|
467
|
+
}
|
|
402
468
|
// The INSTRUCTION is the question, not the file's authorship: `prepareStartWorkspace` returns early without
|
|
403
469
|
// FASTAGENT_RELEASE_FILE, so a Dockerfile that sets it reads the manifest whoever wrote it. This gates rather
|
|
404
470
|
// than warns because it is about FastAgent's OWN delivery arriving — the model would be reported here and
|
|
@@ -406,12 +472,18 @@ modelFromValueFile, valueFile, report) {
|
|
|
406
472
|
// Anywhere in an `ENV` instruction, not just first: `ENV A=1 FASTAGENT_RELEASE_FILE=/app/x` is ordinary
|
|
407
473
|
// Dockerfile style and hard-refusing it would be a false gate. A backslash continuation still reads as absent
|
|
408
474
|
// (covering it means joining lines first) — the remaining over-strict edge.
|
|
409
|
-
|
|
475
|
+
const readsManifest = /^\s*ENV\s[^\n]*\bFASTAGENT_RELEASE_FILE[=\s]/m.test(dockerfileText);
|
|
476
|
+
if (modelFromValueFile !== undefined && !readsManifest) {
|
|
410
477
|
const issue = `your Dockerfile does not set FASTAGENT_RELEASE_FILE, and the model comes from ${valueFile} — it travels ` +
|
|
411
478
|
`in the release manifest, which is only read when that ENV points at it. Add it (see a generated ` +
|
|
412
|
-
`Dockerfile), or set \`model\` in fastagent.config.ts so it ships in the config instead.`;
|
|
479
|
+
`Dockerfile), or set \`defaults.model\` in fastagent.config.ts so it ships in the config instead.`;
|
|
413
480
|
report.issue(issue);
|
|
414
481
|
}
|
|
482
|
+
if (deliversConfig && !/^\s*ENV\s[^\n]*\bFASTAGENT_CONFIG[=\s]/m.test(dockerfileText)) {
|
|
483
|
+
report.issue(`your Dockerfile does not set FASTAGENT_CONFIG, and --config delivers the instance configuration in the ` +
|
|
484
|
+
`image, which \`start\` reads only where that ENV points: copy ${DELIVERED_CONFIG_DIR}/ into the image and set it ` +
|
|
485
|
+
`(see a generated Dockerfile).`);
|
|
486
|
+
}
|
|
415
487
|
}
|
|
416
488
|
/**
|
|
417
489
|
* WHICH model this deployment runs on, and where that came from.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const RAILWAYIGNORE_FILE = ".railwayignore";
|
|
2
|
+
export declare function isGeneratedRailwayignore(content: string): boolean;
|
|
3
|
+
export declare function railwayignore(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Why a `.railwayignore` the author kept would leave the deploy's own directory out of the upload, so the image build
|
|
6
|
+
* could not copy it, or undefined when it lets it through (or there is none to keep).
|
|
7
|
+
*/
|
|
8
|
+
export declare function keptRailwayignoreProblem(agentDir: string): string | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `railway up` uploads. It follows `.gitignore`, and only inside a git repository, so a project without one would
|
|
3
|
+
* upload its credentials, and one with one would drop `.fastagent/deploy/`, which `.gitignore` ignores and the image is
|
|
4
|
+
* built from. `.railwayignore` outranks `.gitignore`; a parent that is ignored hides its children, hence the three
|
|
5
|
+
* lines for the one directory (verified against Railway, which reads the same patterns as ripgrep).
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import ignore from "ignore";
|
|
10
|
+
import { DELIVERED_CONFIG_DIR } from "../container.js";
|
|
11
|
+
export const RAILWAYIGNORE_FILE = ".railwayignore";
|
|
12
|
+
const MARKER = "# Generated by `fastagent deploy railway`";
|
|
13
|
+
export function isGeneratedRailwayignore(content) {
|
|
14
|
+
return content.startsWith(MARKER);
|
|
15
|
+
}
|
|
16
|
+
export function railwayignore() {
|
|
17
|
+
return `${MARKER}. Delete this line to take ownership (deploy then keeps your file).
|
|
18
|
+
# Credentials, state and clones never leave this machine, whatever .gitignore says.
|
|
19
|
+
.secrets
|
|
20
|
+
.state
|
|
21
|
+
/content
|
|
22
|
+
.env
|
|
23
|
+
.env.*
|
|
24
|
+
!.env.example
|
|
25
|
+
# The deploy's own directory is what the image is built from, and .gitignore ignores .fastagent.
|
|
26
|
+
!.fastagent
|
|
27
|
+
.fastagent/*
|
|
28
|
+
!${DELIVERED_CONFIG_DIR}
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
31
|
+
function readIfExists(path) {
|
|
32
|
+
try {
|
|
33
|
+
return readFileSync(path, "utf8");
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error.code === "ENOENT")
|
|
37
|
+
return "";
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Why a `.railwayignore` the author kept would leave the deploy's own directory out of the upload, so the image build
|
|
43
|
+
* could not copy it, or undefined when it lets it through (or there is none to keep).
|
|
44
|
+
*/
|
|
45
|
+
export function keptRailwayignoreProblem(agentDir) {
|
|
46
|
+
const kept = readIfExists(join(agentDir, RAILWAYIGNORE_FILE));
|
|
47
|
+
if (kept === "" || isGeneratedRailwayignore(kept))
|
|
48
|
+
return undefined;
|
|
49
|
+
const upload = ignore()
|
|
50
|
+
.add(readIfExists(join(agentDir, ".gitignore")))
|
|
51
|
+
.add(kept);
|
|
52
|
+
if (!upload.ignores(`${DELIVERED_CONFIG_DIR}/fastagent.json`))
|
|
53
|
+
return undefined;
|
|
54
|
+
return (`your ${RAILWAYIGNORE_FILE} (kept), with .gitignore, leaves ${DELIVERED_CONFIG_DIR}/ out of what \`railway up\` ` +
|
|
55
|
+
`uploads, and the image is built from it. Add \`!.fastagent\`, \`.fastagent/*\` and \`!${DELIVERED_CONFIG_DIR}\` ` +
|
|
56
|
+
`to it, or delete it to let deploy generate one.`);
|
|
57
|
+
}
|
|
@@ -16,8 +16,7 @@ export interface RailwayPlanInput extends ContainerInput {
|
|
|
16
16
|
*/
|
|
17
17
|
channels: readonly DeclaredChannel[];
|
|
18
18
|
/**
|
|
19
|
-
* The runbook's variable list (`deploymentSecrets`):
|
|
20
|
-
* carries.
|
|
19
|
+
* The runbook's variable list (`deploymentSecrets`): the declared names, each hinted by the file that declared it.
|
|
21
20
|
*/
|
|
22
21
|
secrets?: readonly DeploymentSecret[];
|
|
23
22
|
/** `schedules/` declares a schedule — one of the things that forbids App Sleeping (deploy/residency.ts). */
|
|
@@ -2,21 +2,23 @@ import { webhookRunbook } from "../channel-ingress.js";
|
|
|
2
2
|
import { containerArtifacts, imageHasGit } from "../container.js";
|
|
3
3
|
import { deploymentLoginCommand } from "../box-shell.js";
|
|
4
4
|
import { WAKEUPS_WHEN_ASLEEP, residencyFor } from "../residency.js";
|
|
5
|
+
import { RAILWAYIGNORE_FILE, railwayignore } from "./ignore.js";
|
|
6
|
+
import { stripEdges } from "../../strip-edges.js";
|
|
5
7
|
/** State root = the volume mount path, kept in lockstep. */
|
|
6
8
|
const MOUNT = "/data";
|
|
7
9
|
/** The name this tool gives BOTH the project and the service, derived from the agent directory's name. */
|
|
8
10
|
export function toRailwayName(basename) {
|
|
9
|
-
return basename.replace(/[^a-zA-Z0-9-]+/g, "-")
|
|
11
|
+
return stripEdges(basename.replace(/[^a-zA-Z0-9-]+/g, "-"), (char) => char === "-") || "agent";
|
|
10
12
|
}
|
|
11
13
|
/** Compute the Railway deploy plan from the resolved definition. */
|
|
12
14
|
export function planRailwayDeploy(input) {
|
|
13
15
|
const { serviceName, channels } = input;
|
|
14
|
-
const artifacts = containerArtifacts(input);
|
|
16
|
+
const artifacts = [...containerArtifacts(input), { path: RAILWAYIGNORE_FILE, content: railwayignore() }];
|
|
15
17
|
const secrets = input.secrets ?? [];
|
|
16
18
|
// Order matters, not cosmetics: `railway init` creates a PROJECT with no service, but the volume and variables are
|
|
17
19
|
// service-scoped and `railway up` deploys THE service.
|
|
18
20
|
const runbook = [
|
|
19
|
-
`# Deploy to Railway. Dockerfile(.dockerignore) are generated above.`,
|
|
21
|
+
`# Deploy to Railway. Dockerfile(.dockerignore, .railwayignore) are generated above.`,
|
|
20
22
|
`# Prereqs: the Railway CLI (https://docs.railway.com/guides/cli) and \`railway login\`.`,
|
|
21
23
|
``,
|
|
22
24
|
`# One-time setup (init → service → volume → variables). Skip it on redeploy: repeating it creates`,
|
|
@@ -38,14 +40,14 @@ export function planRailwayDeploy(input) {
|
|
|
38
40
|
`railway variables set FASTAGENT_STATE_DIR=${MOUNT}/.state FASTAGENT_SECRETS_DIR=${MOUNT}/.secrets`,
|
|
39
41
|
];
|
|
40
42
|
if (secrets.length > 0) {
|
|
41
|
-
runbook.push(`# Secrets:`, `# ${secrets.map((s) => `${s.name}: ${s.hint}`).join("\n# ")}`, `railway variables set ${secrets.map((s) => `${s.name}=<value>`).join(" ")}`);
|
|
43
|
+
runbook.push(`# Secrets the service needs a value for. \`fastagent secrets push railway\` sets the ones a value file holds and`, `# \`fastagent secrets set railway <NAME>\` one typed without echo; or with the railway CLI:`, `# ${secrets.map((s) => `${s.name}: ${s.hint}`).join("\n# ")}`, `railway variables set ${secrets.map((s) => `${s.name}=<value>`).join(" ")}`);
|
|
42
44
|
}
|
|
43
45
|
runbook.push(``, `# Before a new definition release, run \`fastagent deploy railway\` to refresh the release manifest.`, `# Upload the agent directory and build on Railway (no local Docker needed): Railway builds the`, `# Dockerfile at its root. It marks the deploy live once the container starts, without waiting for`, `# /health: \`railway logs\` shows a box that crashes on boot.`, `railway up`);
|
|
44
46
|
// The credential is created on the box, never carried: the box is then the only holder of its grant.
|
|
45
47
|
if (input.boxLogin) {
|
|
46
48
|
runbook.push(``, `# Model auth: once it is up, the deployment logs in to ${input.boxLogin} itself (from the agent directory):`, `${deploymentLoginCommand("railway", input.boxLogin)}`);
|
|
47
49
|
}
|
|
48
|
-
runbook.push(``, `# The volume keeps .state and .secrets across restarts and deploys
|
|
50
|
+
runbook.push(``, `# The volume is the agent's working directory: it keeps its work, .state and .secrets across restarts and deploys.`, input.shipsGit
|
|
49
51
|
? `# Railway uploads may strip .git, so the deployed definition may have no history.`
|
|
50
52
|
: imageHasGit(input)
|
|
51
53
|
? `# Git is installed (github content is cloned here, or context.json declares it in aptPackages).`
|
|
@@ -53,7 +55,7 @@ export function planRailwayDeploy(input) {
|
|
|
53
55
|
// The public URL is minted, not deterministic (unlike Fly's <app>.fly.dev).
|
|
54
56
|
const steps = webhookRunbook(`https://<your-domain>`, channels);
|
|
55
57
|
if (steps.length > 0) {
|
|
56
|
-
runbook.push(``, `# Public URL — Railway mints a *.up.railway.app domain (NOT deterministic). Generate it, then read`, `# the printed https URL and use it as <your-domain> in the webhook step(s) below:`, `railway domain`, ...steps);
|
|
58
|
+
runbook.push(``, `# Public URL — Railway mints a *.up.railway.app domain (NOT deterministic). Generate it, then read`, `# the printed https URL and use it as <your-domain> in the webhook step(s) below:`, `railway domain`, `# Or, before \`railway up\`, set it as http.publicUrl in the configuration \`deploy --config\` delivers, and`, `# re-run \`fastagent deploy railway\`: the box then points Telegram, Feishu and Lark there itself.`, ...steps);
|
|
57
59
|
}
|
|
58
60
|
// Scale-to-zero: App Sleeping is dashboard-only (no CLI/API) — a manual step, not a generated setting. WHY it is
|
|
59
61
|
// forbidden is residency.ts's to decide; the SETTING and the path through the dashboard are Railway's.
|