@fastagent-sh/fastagent 0.21.1 → 0.22.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 -12
- package/dist/agent.d.ts +24 -37
- package/dist/agent.js +7 -15
- package/dist/atomic-write.d.ts +13 -17
- package/dist/atomic-write.js +18 -29
- package/dist/bind.d.ts +12 -22
- package/dist/bind.js +18 -27
- package/dist/channel.d.ts +11 -33
- package/dist/channels/agentcore-limits.d.ts +2 -13
- package/dist/channels/agentcore-limits.js +2 -13
- package/dist/channels/agentcore-protocol.d.ts +67 -58
- package/dist/channels/agentcore-protocol.js +23 -15
- package/dist/channels/agentcore-service.d.ts +19 -33
- package/dist/channels/agentcore-service.js +176 -59
- package/dist/channels/agentcore.d.ts +39 -33
- package/dist/channels/agentcore.js +90 -224
- package/dist/channels/body.d.ts +15 -2
- package/dist/channels/body.js +24 -2
- package/dist/channels/busy.d.ts +1 -21
- package/dist/channels/busy.js +2 -39
- package/dist/channels/control.d.ts +17 -62
- package/dist/channels/control.js +74 -182
- package/dist/channels/define-channel.d.ts +39 -0
- package/dist/channels/define-channel.js +9 -0
- package/dist/channels/discover.d.ts +10 -31
- package/dist/channels/discover.js +37 -31
- package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
- package/dist/channels/feishu/bootstrap-token.js +7 -23
- package/dist/channels/feishu/card.d.ts +14 -28
- package/dist/channels/feishu/card.js +16 -30
- package/dist/channels/feishu/cloud.d.ts +1 -5
- package/dist/channels/feishu/context-buffer.d.ts +4 -23
- package/dist/channels/feishu/context-buffer.js +8 -31
- package/dist/channels/feishu/crypto.d.ts +10 -5
- package/dist/channels/feishu/crypto.js +12 -19
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu-api.js +5 -4
- package/dist/channels/feishu/feishu.d.ts +4 -10
- package/dist/channels/feishu/feishu.js +113 -227
- package/dist/channels/feishu/invoke-turn.d.ts +5 -4
- package/dist/channels/feishu/invoke-turn.js +36 -54
- package/dist/channels/feishu/model.d.ts +4 -21
- package/dist/channels/feishu/model.js +1 -8
- package/dist/channels/feishu/normalize.d.ts +2 -10
- package/dist/channels/feishu/normalize.js +7 -42
- package/dist/channels/feishu/parse.d.ts +9 -29
- package/dist/channels/feishu/parse.js +8 -26
- package/dist/channels/feishu/preview.d.ts +12 -14
- package/dist/channels/feishu/preview.js +108 -207
- package/dist/channels/feishu/register-app.d.ts +8 -23
- package/dist/channels/feishu/register-app.js +7 -20
- package/dist/channels/feishu/register-webhook.d.ts +2 -9
- package/dist/channels/feishu/register-webhook.js +9 -33
- package/dist/channels/feishu/scaffold/channel.ts +27 -19
- package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/feishu/setup-mode.d.ts +11 -36
- package/dist/channels/feishu/setup-mode.js +7 -26
- package/dist/channels/feishu/ws-ingress.d.ts +1 -2
- package/dist/channels/feishu/ws-ingress.js +6 -8
- package/dist/channels/http.d.ts +15 -15
- package/dist/channels/http.js +19 -9
- package/dist/channels/kit/attachment-path.d.ts +4 -5
- package/dist/channels/kit/attachment-path.js +10 -34
- package/dist/channels/kit/context-buffer.d.ts +14 -21
- package/dist/channels/kit/context-buffer.js +25 -38
- package/dist/channels/kit/delivery.d.ts +35 -0
- package/dist/channels/kit/delivery.js +129 -0
- package/dist/channels/kit/event-stream.d.ts +5 -0
- package/dist/channels/kit/event-stream.js +29 -0
- package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
- package/dist/channels/kit/invoke-turn-kit.js +89 -32
- package/dist/channels/kit/preview-kit.d.ts +18 -72
- package/dist/channels/kit/preview-kit.js +22 -88
- package/dist/channels/kit/signature.d.ts +3 -9
- package/dist/channels/kit/signature.js +3 -9
- package/dist/channels/kit/state.d.ts +1 -2
- package/dist/channels/kit/state.js +5 -14
- package/dist/channels/kit/stop-command.d.ts +1 -2
- package/dist/channels/kit/stop-command.js +6 -9
- package/dist/channels/kit/tasks.d.ts +1 -1
- package/dist/channels/kit/tasks.js +8 -21
- package/dist/channels/kit/text.d.ts +2 -16
- package/dist/channels/kit/text.js +23 -31
- package/dist/channels/kit/thread-participants.d.ts +4 -17
- package/dist/channels/kit/thread-participants.js +8 -55
- package/dist/channels/kit/transport.d.ts +26 -0
- package/dist/channels/kit/transport.js +20 -0
- package/dist/channels/kit/turn-queue.d.ts +7 -11
- package/dist/channels/kit/turn-queue.js +21 -39
- package/dist/channels/kit/turn-runner.d.ts +32 -18
- package/dist/channels/kit/turn-runner.js +98 -53
- package/dist/channels/kit/turn-store.d.ts +32 -32
- package/dist/channels/kit/turn-store.js +45 -88
- package/dist/channels/lark/lark.d.ts +1 -5
- package/dist/channels/lark/onboard.d.ts +3 -12
- package/dist/channels/lark/onboard.js +3 -13
- package/dist/channels/lark/scaffold/channel.ts +27 -19
- package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/registration.d.ts +9 -35
- package/dist/channels/registration.js +6 -37
- package/dist/channels/secret.js +2 -5
- package/dist/channels/serve.d.ts +47 -44
- package/dist/channels/serve.js +165 -101
- package/dist/channels/slack/config-api.d.ts +4 -27
- package/dist/channels/slack/config-api.js +5 -34
- package/dist/channels/slack/context-buffer.d.ts +2 -3
- package/dist/channels/slack/context-buffer.js +5 -5
- package/dist/channels/slack/invoke-turn.d.ts +3 -1
- package/dist/channels/slack/invoke-turn.js +25 -34
- package/dist/channels/slack/manifest.js +1 -3
- package/dist/channels/slack/model.d.ts +1 -2
- package/dist/channels/slack/model.js +1 -1
- package/dist/channels/slack/onboard.d.ts +4 -3
- package/dist/channels/slack/onboard.js +8 -11
- package/dist/channels/slack/onboarding-state.d.ts +3 -5
- package/dist/channels/slack/onboarding-state.js +9 -6
- package/dist/channels/slack/parse.d.ts +4 -13
- package/dist/channels/slack/parse.js +7 -28
- package/dist/channels/slack/preview.d.ts +12 -2
- package/dist/channels/slack/preview.js +292 -269
- package/dist/channels/slack/reaction.d.ts +11 -3
- package/dist/channels/slack/reaction.js +19 -8
- package/dist/channels/slack/register-webhook.js +4 -7
- package/dist/channels/slack/scaffold/channel.ts +20 -13
- package/dist/channels/slack/scaffold/slack-send.ts +2 -2
- package/dist/channels/slack/setup-server.d.ts +1 -5
- package/dist/channels/slack/setup-server.js +1 -5
- package/dist/channels/slack/shared-api.d.ts +1 -4
- package/dist/channels/slack/shared-api.js +2 -13
- package/dist/channels/slack/slack-api.d.ts +2 -1
- package/dist/channels/slack/slack-api.js +6 -4
- package/dist/channels/slack/slack.d.ts +11 -14
- package/dist/channels/slack/slack.js +121 -122
- package/dist/channels/sse.d.ts +1 -1
- package/dist/channels/sse.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +7 -22
- package/dist/channels/telegram/context-buffer.js +6 -23
- package/dist/channels/telegram/invoke-turn.d.ts +7 -7
- package/dist/channels/telegram/invoke-turn.js +24 -43
- package/dist/channels/telegram/parse.d.ts +24 -51
- package/dist/channels/telegram/parse.js +32 -84
- package/dist/channels/telegram/preview.d.ts +10 -1
- package/dist/channels/telegram/preview.js +79 -100
- package/dist/channels/telegram/register-webhook.js +5 -23
- package/dist/channels/telegram/scaffold/channel.ts +25 -18
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
- package/dist/channels/telegram/telegram-api.d.ts +4 -3
- package/dist/channels/telegram/telegram-api.js +9 -7
- package/dist/channels/telegram/telegram.d.ts +4 -18
- package/dist/channels/telegram/telegram.js +38 -94
- package/dist/channels/telegram/turn-store.d.ts +11 -14
- package/dist/channels/telegram/turn-store.js +8 -6
- package/dist/channels/wait-health.d.ts +4 -6
- package/dist/channels/wait-health.js +10 -14
- package/dist/cli/add-feishu.d.ts +2 -12
- package/dist/cli/add-feishu.js +24 -71
- package/dist/cli/add-slack.d.ts +5 -3
- package/dist/cli/add-slack.js +53 -76
- package/dist/cli/auth-view.d.ts +2 -16
- package/dist/cli/auth-view.js +2 -16
- package/dist/cli/commands/add.js +25 -40
- package/dist/cli/commands/chat.d.ts +0 -1
- package/dist/cli/commands/chat.js +5 -6
- package/dist/cli/commands/deploy/agentcore.js +60 -65
- package/dist/cli/commands/deploy/docker.js +50 -23
- package/dist/cli/commands/deploy/fly.js +23 -61
- package/dist/cli/commands/deploy/railway.js +23 -18
- package/dist/cli/commands/deploy/shared.d.ts +38 -57
- package/dist/cli/commands/deploy/shared.js +40 -59
- package/dist/cli/commands/deploy.d.ts +10 -27
- package/dist/cli/commands/deploy.js +38 -60
- package/dist/cli/commands/destroy.d.ts +4 -0
- package/dist/cli/commands/destroy.js +56 -0
- package/dist/cli/commands/dev.d.ts +2 -1
- package/dist/cli/commands/dev.js +20 -23
- package/dist/cli/commands/info.d.ts +0 -2
- package/dist/cli/commands/info.js +83 -49
- package/dist/cli/commands/init.d.ts +1 -2
- package/dist/cli/commands/init.js +16 -55
- package/dist/cli/commands/invoke.d.ts +0 -1
- package/dist/cli/commands/invoke.js +5 -7
- package/dist/cli/commands/login.d.ts +2 -1
- package/dist/cli/commands/login.js +33 -38
- package/dist/cli/commands/logs.js +3 -4
- package/dist/cli/commands/models.js +1 -5
- package/dist/cli/commands/routine-run.d.ts +6 -0
- package/dist/cli/commands/routine-run.js +47 -0
- package/dist/cli/commands/routine.d.ts +25 -0
- package/dist/cli/commands/routine.js +122 -0
- package/dist/cli/commands/start.d.ts +31 -3
- package/dist/cli/commands/start.js +131 -81
- package/dist/cli/commands/tool.js +26 -12
- package/dist/cli/fail.d.ts +13 -23
- package/dist/cli/fail.js +20 -23
- package/dist/cli/invoke-stream.d.ts +1 -8
- package/dist/cli/kernel.d.ts +6 -38
- package/dist/cli/kernel.js +14 -77
- package/dist/cli/models-view.d.ts +1 -9
- package/dist/cli/models-view.js +11 -22
- package/dist/cli/program.d.ts +5 -6
- package/dist/cli/program.js +120 -157
- package/dist/cli/serve.d.ts +57 -41
- package/dist/cli/serve.js +94 -121
- package/dist/cli/shared.d.ts +15 -61
- package/dist/cli/shared.js +55 -181
- package/dist/cli.js +1 -8
- package/dist/collect.d.ts +6 -0
- package/dist/collect.js +6 -0
- package/dist/core.d.ts +4 -1
- package/dist/core.js +9 -7
- package/dist/declared-secrets.d.ts +55 -0
- package/dist/declared-secrets.js +88 -0
- package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
- package/dist/deploy/agentcore/aws-cli.js +64 -0
- package/dist/deploy/agentcore/destroy.d.ts +51 -0
- package/dist/deploy/agentcore/destroy.js +273 -0
- package/dist/deploy/agentcore/forwarder.js +28 -79
- package/dist/deploy/agentcore/logs.d.ts +2 -18
- package/dist/deploy/agentcore/logs.js +27 -51
- package/dist/deploy/agentcore/plan.d.ts +85 -105
- package/dist/deploy/agentcore/plan.js +152 -314
- package/dist/deploy/agentcore/run.d.ts +28 -28
- package/dist/deploy/agentcore/run.js +227 -306
- package/dist/deploy/agentcore/zip.d.ts +2 -15
- package/dist/deploy/agentcore/zip.js +3 -17
- package/dist/deploy/build-context.d.ts +21 -0
- package/dist/deploy/build-context.js +137 -0
- package/dist/deploy/channel-ingress.d.ts +26 -39
- package/dist/deploy/channel-ingress.js +57 -28
- package/dist/deploy/container.d.ts +20 -34
- package/dist/deploy/container.js +49 -100
- package/dist/deploy/docker/plan.d.ts +12 -11
- package/dist/deploy/docker/plan.js +43 -24
- package/dist/deploy/docker/run.d.ts +22 -25
- package/dist/deploy/docker/run.js +39 -37
- package/dist/deploy/fly/plan.d.ts +15 -44
- package/dist/deploy/fly/plan.js +41 -73
- package/dist/deploy/fly/run.d.ts +16 -51
- package/dist/deploy/fly/run.js +42 -95
- package/dist/deploy/hosts.d.ts +24 -3
- package/dist/deploy/hosts.js +24 -3
- package/dist/deploy/preflight.d.ts +47 -41
- package/dist/deploy/preflight.js +282 -383
- package/dist/deploy/railway/plan.d.ts +16 -42
- package/dist/deploy/railway/plan.js +42 -60
- package/dist/deploy/railway/run.d.ts +30 -68
- package/dist/deploy/railway/run.js +86 -100
- package/dist/deploy/registration-gate.d.ts +6 -13
- package/dist/deploy/residency.d.ts +37 -0
- package/dist/deploy/residency.js +24 -0
- package/dist/deploy/runner.d.ts +10 -9
- package/dist/deploy/runner.js +7 -18
- package/dist/deploy/secrets.d.ts +40 -41
- package/dist/deploy/secrets.js +117 -103
- package/dist/deploy/workspace.d.ts +50 -0
- package/dist/deploy/workspace.js +197 -0
- package/dist/dev-supervisor.d.ts +1 -9
- package/dist/dev-supervisor.js +21 -64
- package/dist/effect-port.d.ts +50 -0
- package/dist/effect-port.js +77 -0
- package/dist/engines/pi/agent-session-factory.d.ts +50 -69
- package/dist/engines/pi/agent-session-factory.js +249 -146
- package/dist/engines/pi/auth.d.ts +15 -11
- package/dist/engines/pi/auth.js +96 -99
- package/dist/engines/pi/chat.d.ts +1 -5
- package/dist/engines/pi/chat.js +2 -14
- package/dist/engines/pi/config.d.ts +53 -51
- package/dist/engines/pi/config.js +83 -92
- package/dist/engines/pi/create.d.ts +60 -75
- package/dist/engines/pi/create.js +108 -141
- package/dist/engines/pi/definition.d.ts +14 -58
- package/dist/engines/pi/definition.js +21 -85
- package/dist/engines/pi/invoke-session.d.ts +5 -25
- package/dist/engines/pi/invoke-session.js +209 -244
- package/dist/engines/pi/login.d.ts +95 -35
- package/dist/engines/pi/login.js +259 -107
- package/dist/engines/pi/machine.d.ts +31 -0
- package/dist/engines/pi/machine.js +106 -0
- package/dist/engines/pi/models.d.ts +70 -89
- package/dist/engines/pi/models.js +188 -81
- package/dist/engines/pi/open.d.ts +65 -53
- package/dist/engines/pi/open.js +129 -69
- package/dist/engines/pi/report.d.ts +3 -12
- package/dist/engines/pi/report.js +2 -16
- package/dist/engines/pi/search-tools.d.ts +1 -9
- package/dist/engines/pi/search-tools.js +26 -57
- package/dist/engines/pi/service.d.ts +2 -16
- package/dist/engines/pi/service.js +2 -16
- package/dist/engines/pi/session-builder.d.ts +5 -9
- package/dist/engines/pi/session-builder.js +44 -82
- package/dist/engines/pi/session-control.js +286 -269
- package/dist/engines/pi/session-effects.d.ts +14 -0
- package/dist/engines/pi/session-effects.js +30 -0
- package/dist/engines/pi/session-inheritance.d.ts +9 -18
- package/dist/engines/pi/session-inheritance.js +103 -160
- package/dist/engines/pi/session-markers.d.ts +31 -30
- package/dist/engines/pi/session-markers.js +26 -25
- package/dist/engines/pi/session-settings.d.ts +6 -22
- package/dist/engines/pi/session-settings.js +10 -21
- package/dist/engines/pi/session-store.d.ts +26 -92
- package/dist/engines/pi/session-store.js +96 -310
- package/dist/engines/pi/tool-context.d.ts +17 -45
- package/dist/engines/pi/tool-context.js +7 -26
- package/dist/engines/pi/tool.d.ts +45 -36
- package/dist/engines/pi/tool.js +43 -57
- package/dist/engines/pi/turn-kit.d.ts +14 -52
- package/dist/engines/pi/turn-kit.js +18 -67
- package/dist/engines/pi/wake-tool.d.ts +2 -10
- package/dist/engines/pi/wake-tool.js +7 -27
- package/dist/env.d.ts +27 -28
- package/dist/env.js +45 -50
- package/dist/index.js +0 -2
- package/dist/lark.d.ts +1 -2
- package/dist/lark.js +1 -2
- package/dist/loader.d.ts +14 -55
- package/dist/loader.js +24 -43
- package/dist/log.d.ts +2 -12
- package/dist/log.js +3 -24
- package/dist/node.d.ts +2 -15
- package/dist/node.js +2 -18
- package/dist/observe.d.ts +2 -8
- package/dist/open-url.d.ts +1 -1
- package/dist/open-url.js +1 -1
- package/dist/paths.d.ts +70 -134
- package/dist/paths.js +165 -232
- package/dist/pi.d.ts +4 -6
- package/dist/pi.js +3 -3
- package/dist/proxy.d.ts +16 -6
- package/dist/proxy.js +52 -16
- package/dist/runtime.d.ts +18 -12
- package/dist/runtime.js +18 -11
- package/dist/scaffold/add-channel.d.ts +14 -31
- package/dist/scaffold/add-channel.js +48 -112
- package/dist/scaffold/init.d.ts +9 -31
- package/dist/scaffold/init.js +45 -138
- package/dist/scaffold/templates/env.example +6 -4
- package/dist/scaffold/templates/fastagent.config.ts +36 -0
- package/dist/scaffold/templates.d.ts +3 -3
- package/dist/scaffold/templates.js +5 -8
- package/dist/scaffold/vendor-skill.d.ts +2 -5
- package/dist/scaffold/vendor-skill.js +19 -29
- package/dist/schedule/cron.d.ts +9 -5
- package/dist/schedule/cron.js +9 -11
- package/dist/schedule/discover.d.ts +12 -12
- package/dist/schedule/discover.js +43 -34
- package/dist/schedule/routine.d.ts +43 -0
- package/dist/schedule/routine.js +32 -0
- package/dist/schedule/run.d.ts +55 -0
- package/dist/schedule/run.js +146 -0
- package/dist/schedule/scheduler.d.ts +44 -55
- package/dist/schedule/scheduler.js +291 -265
- package/dist/schedule/state.d.ts +105 -10
- package/dist/schedule/state.js +293 -19
- package/dist/schedule/wake-alarm.d.ts +10 -23
- package/dist/schedule/wake-alarm.js +91 -121
- package/dist/schedule/wakeups.d.ts +23 -39
- package/dist/schedule/wakeups.js +33 -75
- package/dist/secrets-gate.d.ts +32 -0
- package/dist/secrets-gate.js +33 -0
- package/dist/service.d.ts +137 -98
- package/dist/service.js +250 -252
- package/dist/session-remote.d.ts +33 -44
- package/dist/session-remote.js +203 -192
- package/dist/session.d.ts +153 -228
- package/dist/session.js +33 -55
- package/dist/tunnel.d.ts +8 -36
- package/dist/tunnel.js +24 -65
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +10 -16
- package/dist/channels/agentcore-state.d.ts +0 -77
- package/dist/channels/agentcore-state.js +0 -276
- package/dist/channels/github/github.d.ts +0 -31
- package/dist/channels/github/github.js +0 -89
- package/dist/channels/github/scaffold/channel.ts +0 -25
- package/dist/cli/commands/attach.d.ts +0 -108
- package/dist/cli/commands/attach.js +0 -637
- package/dist/cli/commands/fire.d.ts +0 -7
- package/dist/cli/commands/fire.js +0 -38
- package/dist/cli/commands/schedule.d.ts +0 -12
- package/dist/cli/commands/schedule.js +0 -87
- package/dist/github.d.ts +0 -3
- package/dist/github.js +0 -2
- package/dist/scaffold/templates/fastagent.config.mjs +0 -23
- package/dist/schedule/audit.d.ts +0 -18
- package/dist/schedule/audit.js +0 -57
- package/dist/schedule/schedule.d.ts +0 -35
- package/dist/schedule/schedule.js +0 -23
|
@@ -1,40 +1,21 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `deploy fly`: fly.toml + a state volume. KEEP (no --force): an existing fly.toml is authoritative;
|
|
3
|
-
* --force: the template is. `--run` drives flyctl to completion (idempotent, resumable).
|
|
4
|
-
*/
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
/** `deploy fly`: fly.toml + a state volume. */
|
|
6
2
|
import { basename, join } from "node:path";
|
|
7
|
-
import { isGeneratedFlyToml, parseFlyAppName, parseFlyMinMachines,
|
|
3
|
+
import { isGeneratedFlyToml, parseFlyAppName, parseFlyMinMachines, planFlyDeploy, toFlyAppName, } from "../../../deploy/fly/plan.js";
|
|
8
4
|
import { deployFlyRun } from "../../../deploy/fly/run.js";
|
|
9
5
|
import { spawnRunner } from "../../../deploy/runner.js";
|
|
10
6
|
import { readTextIfExists } from "../../../paths.js";
|
|
7
|
+
import { residencyFor } from "../../../deploy/residency.js";
|
|
11
8
|
import { failStartup } from "../../fail.js";
|
|
12
9
|
import { carryCredentials, gateOnModelCredential, registrarsFor } from "./shared.js";
|
|
13
10
|
export const flyHost = {
|
|
14
11
|
isOurs: (path, content) => path.endsWith("fly.toml") && isGeneratedFlyToml(content),
|
|
15
12
|
async deploy(ctx) {
|
|
16
|
-
const { opts, agentDir, workspace, channels,
|
|
17
|
-
const {
|
|
18
|
-
//
|
|
19
|
-
// are fire-and-forget (no replay), so the generated fly.toml keeps one machine running for them —
|
|
20
|
-
// a note, not a warn, since the plan already did the safe thing (definition-aware autostop).
|
|
21
|
-
if (channels.some((channel) => channel.name === "github")) {
|
|
22
|
-
console.error(`[fastagent] note: github turns have no replay — the generated fly.toml uses min_machines_running=1 ` +
|
|
23
|
-
`(no scale-to-zero) so autostop can't drop an in-flight review. Set it to 0 to accept that trade.`);
|
|
24
|
-
}
|
|
25
|
-
// Two consistent modes. KEEP (no --force): an existing fly.toml is authoritative — not rewritten,
|
|
26
|
-
// and the runbook reads its `app=` (Fly app names are globally unique, so the basename guess may be
|
|
27
|
-
// taken and the user renamed it). --force: the template is authoritative — the WHOLE fly.toml resets
|
|
28
|
-
// (app→basename, region→iad, vm→defaults), so we do NOT round-trip `app` and warn that hand edits go.
|
|
29
|
-
// fly.toml lives in the agent dir (fastagent/fly.toml) — the workspace's own
|
|
30
|
-
// fly.toml (if any) belongs to the host's product deploy and is never read or written here.
|
|
13
|
+
const { opts, agentDir, workspace, channels, pre, write } = ctx;
|
|
14
|
+
const { hasCron, modelAuth, modelKeyInDefinition, authPath, container, port, declaredSecrets, values, valueFile } = pre;
|
|
15
|
+
// Two consistent modes.
|
|
31
16
|
const flyTomlPath = join(agentDir, "fly.toml");
|
|
32
17
|
const flyToml = await readTextIfExists(flyTomlPath).catch(failStartup);
|
|
33
|
-
// Every decision below turns on ONE question — will `writeArtifacts` keep this file?
|
|
34
|
-
// OWNERSHIP, not the flag: `--force` resets a fly.toml we generated and leaves a hand-written one alone.
|
|
35
|
-
// Keying these on `--force` alone meant a forced deploy of a hand-owned fly.toml took the app name from
|
|
36
|
-
// the directory basename (shipping that file at a DIFFERENT app than it declares) and skipped the
|
|
37
|
-
// scale-to-zero gate below (a schedules deploy that silently sleeps). Read once, decide once.
|
|
18
|
+
// Every decision below turns on ONE question — will `writeArtifacts` keep this file?
|
|
38
19
|
const flyTomlKept = flyToml !== undefined && (!opts.force || !isGeneratedFlyToml(flyToml));
|
|
39
20
|
const keptApp = flyTomlKept ? parseFlyAppName(flyToml) : undefined;
|
|
40
21
|
const appName = keptApp ?? toFlyAppName(basename(workspace));
|
|
@@ -43,28 +24,17 @@ export const flyHost = {
|
|
|
43
24
|
if (flyToml !== undefined && !flyTomlKept) {
|
|
44
25
|
console.error(`[fastagent] warn: --force resets fly.toml to defaults (app, region, vm) — re-apply any hand edits`);
|
|
45
26
|
}
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
// KEEP mode + time triggers: the kept fly.toml may still scale to zero — which would sleep through every
|
|
53
|
-
// cron instant / wake-up. The generated plan can't fix a kept file, so surface it instead of the preflight
|
|
54
|
-
// note silently not applying (the author who deployed FIRST and added schedules LATER hits exactly this).
|
|
55
|
-
// Under `--run` this is a GATE (same discipline as the model-travel gate): a full deploy whose schedules
|
|
56
|
-
// silently never fire is worse than a crash-loop — nothing fails visibly when a cron instant passes on a
|
|
57
|
-
// sleeping machine, and unlike github's min=0 there is no legitimate trade to accept here.
|
|
58
|
-
if (flyTomlKept && (hasTimeTriggers || longConnectionChannels.length > 0)) {
|
|
27
|
+
// KEEP mode: the kept fly.toml may still scale to zero — which would sleep through the very thing the
|
|
28
|
+
// GENERATED one keeps a machine up for. Same question, same answer: `residencyFor` decides, here too, so the
|
|
29
|
+
// kept-file gate and the generated setting cannot disagree.
|
|
30
|
+
const residency = residencyFor({ channels, hasCron });
|
|
31
|
+
if (flyTomlKept && residency) {
|
|
59
32
|
const min = parseFlyMinMachines(flyToml);
|
|
60
33
|
if ((min ?? 0) === 0) {
|
|
61
|
-
// undefined = the line is absent — Fly's platform default for min_machines_running is 0, so a
|
|
62
|
-
//
|
|
63
|
-
const reason = hasTimeTriggers
|
|
64
|
-
? `schedules/self-scheduling need a running machine (no external wake-up)`
|
|
65
|
-
: `long-connection channel (${longConnectionChannels.map((c) => c.name).join(", ")}) needs an always-on outbound connection`;
|
|
34
|
+
// undefined = the line is absent — Fly's platform default for min_machines_running is 0, so a hand-written
|
|
35
|
+
// fly.toml without the line scales to zero exactly like an explicit 0.
|
|
66
36
|
const msg = `your kept fly.toml scales to zero (min_machines_running = ${min ?? "absent → platform default 0"}), but ` +
|
|
67
|
-
`${
|
|
37
|
+
`${residency.why}. Set min_machines_running = 1, or pass --force to regenerate.`;
|
|
68
38
|
if (opts.run)
|
|
69
39
|
failStartup(new Error(`deploy stopped: ${msg}`));
|
|
70
40
|
console.error(`[fastagent] warn: ${msg}`);
|
|
@@ -75,11 +45,9 @@ export const flyHost = {
|
|
|
75
45
|
port,
|
|
76
46
|
modelAuth,
|
|
77
47
|
channels,
|
|
78
|
-
|
|
79
|
-
|
|
48
|
+
secrets: pre.secrets,
|
|
49
|
+
hasCron,
|
|
80
50
|
...container,
|
|
81
|
-
autostop: opts.stop ? "stop" : "suspend",
|
|
82
|
-
scaleToZero: opts.scaleToZero !== false,
|
|
83
51
|
});
|
|
84
52
|
await write(plan.artifacts, { force: !!opts.force });
|
|
85
53
|
if (opts.run) {
|
|
@@ -92,35 +60,29 @@ export const flyHost = {
|
|
|
92
60
|
modelKeyInDefinition,
|
|
93
61
|
authPath,
|
|
94
62
|
channels,
|
|
95
|
-
|
|
96
|
-
|
|
63
|
+
declaredSecrets,
|
|
64
|
+
values,
|
|
65
|
+
valueFile,
|
|
97
66
|
});
|
|
98
67
|
}
|
|
99
68
|
console.log(plan.runbook.join("\n"));
|
|
100
69
|
},
|
|
101
70
|
};
|
|
102
|
-
/**
|
|
103
|
-
* `deploy fly --run`: drive flyctl to completion (idempotent, resumable). Gathers the secret VALUES
|
|
104
|
-
* from the local env — the model key (env auth) or the whole auth.json as a `FASTAGENT_AUTH_SEED` seed
|
|
105
|
-
* (OAuth/stored auth: the deployed box materializes it onto the /data volume on first boot, so a
|
|
106
|
-
* personal deploy runs on the SAME subscription) plus channel secrets — then runs the flyctl steps
|
|
107
|
-
* behind the shared {@link spawnRunner} seam (spawned `fly`, cwd = the workspace so the build context is the whole workspace).
|
|
108
|
-
*/
|
|
71
|
+
/** `deploy fly --run`: drive flyctl to completion (idempotent, resumable). */
|
|
109
72
|
async function runDeployFly(params) {
|
|
110
|
-
const { agentDir, workspace, agentPrefix, appName, channels
|
|
73
|
+
const { agentDir, workspace, agentPrefix, appName, channels } = params;
|
|
111
74
|
const fly = spawnRunner("fly", workspace);
|
|
112
75
|
// Fail fast if flyctl is absent (spawn ENOENT → 127), with the install link — not a confusing auth gate.
|
|
113
76
|
if ((await fly(["version"], { capture: true })).code === 127) {
|
|
114
77
|
failStartup(new Error(`flyctl not found — install it: https://fly.io/docs/flyctl/install, then re-run`));
|
|
115
78
|
}
|
|
116
|
-
const region = parseFlyRegion(await readFile(flyTomlPath, "utf8")) ?? "iad";
|
|
117
79
|
const { secrets, missingSecrets, needsModelCredential } = await carryCredentials(params);
|
|
118
80
|
gateOnModelCredential(needsModelCredential);
|
|
119
81
|
const outcome = await deployFlyRun({
|
|
120
82
|
appName,
|
|
121
|
-
region,
|
|
122
83
|
secrets,
|
|
123
84
|
missingSecrets,
|
|
85
|
+
valueFile: params.valueFile,
|
|
124
86
|
channels,
|
|
125
87
|
flyConfig: `${agentPrefix}fly.toml`,
|
|
126
88
|
dockerfile: `${agentPrefix}Dockerfile`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `deploy railway`: a thin config file, scale-to-zero is a manual dashboard step, the URL is minted
|
|
3
|
-
*
|
|
2
|
+
* `deploy railway`: a thin config file, scale-to-zero is a manual dashboard step, the URL is minted (see
|
|
3
|
+
* planRailwayDeploy).
|
|
4
4
|
*/
|
|
5
5
|
import { basename } from "node:path";
|
|
6
6
|
import { dockerfilePathVar, isGeneratedRailwayJson, planRailwayDeploy, toRailwayName, } from "../../../deploy/railway/plan.js";
|
|
@@ -12,22 +12,22 @@ export const railwayHost = {
|
|
|
12
12
|
isOurs: (path, content) => path.endsWith("railway.json") && isGeneratedRailwayJson(content),
|
|
13
13
|
async deploy(ctx) {
|
|
14
14
|
const { opts, agentDir, workspace, pre, channels, write } = ctx;
|
|
15
|
-
const {
|
|
15
|
+
const { hasCron, modelAuth, modelKeyInDefinition, authPath, container, declaredSecrets, values, valueFile } = pre;
|
|
16
16
|
const serviceName = toRailwayName(basename(workspace));
|
|
17
17
|
const plan = planRailwayDeploy({
|
|
18
18
|
serviceName,
|
|
19
19
|
modelAuth,
|
|
20
20
|
channels,
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
secrets: pre.secrets,
|
|
22
|
+
hasCron,
|
|
23
23
|
...container,
|
|
24
24
|
});
|
|
25
25
|
await write(plan.artifacts, { force: !!opts.force });
|
|
26
26
|
if (opts.run) {
|
|
27
|
-
// The BUILD entry is guaranteed by the RAILWAY_DOCKERFILE_PATH service variable the runner sets
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
27
|
+
// The BUILD entry is guaranteed by the RAILWAY_DOCKERFILE_PATH service variable the runner sets (Railway's
|
|
28
|
+
// documented non-root-Dockerfile route), and Railway's default restart policy already equals the file's
|
|
29
|
+
// ON_FAILURE — the dashboard-only Config-as-code pointer only adds the /health deploy gate (boot-crash
|
|
30
|
+
// visibility), so it is an OPTIONAL note, not a gate.
|
|
31
31
|
console.error(`[fastagent] note: optional — point the service at fastagent/railway.json (Service → Settings → ` +
|
|
32
32
|
`Config-as-code, dashboard-only) so the /health healthcheck marks a boot-crashing deploy as FAILED; ` +
|
|
33
33
|
`the build already uses fastagent/Dockerfile via the RAILWAY_DOCKERFILE_PATH variable`);
|
|
@@ -39,7 +39,9 @@ export const railwayHost = {
|
|
|
39
39
|
modelKeyInDefinition,
|
|
40
40
|
authPath,
|
|
41
41
|
channels,
|
|
42
|
-
|
|
42
|
+
declaredSecrets,
|
|
43
|
+
values,
|
|
44
|
+
valueFile,
|
|
43
45
|
intoLinked: !!opts.intoLinked,
|
|
44
46
|
dockerfilePath: dockerfilePathVar(pre.container.agentPrefix),
|
|
45
47
|
});
|
|
@@ -48,13 +50,7 @@ export const railwayHost = {
|
|
|
48
50
|
return;
|
|
49
51
|
},
|
|
50
52
|
};
|
|
51
|
-
/**
|
|
52
|
-
* `deploy railway --run`: drive the railway CLI to completion. Mirrors the fly driver — same
|
|
53
|
-
* credential carry (env key OR the OAuth auth.json as `FASTAGENT_AUTH_SEED`) via {@link carryCredentials},
|
|
54
|
-
* same runner seam (spawned `railway`, cwd = the workspace so `railway up`'s upload is the whole workspace). The
|
|
55
|
-
* Railway-specific sequence (linked-check → init/add/volume when fresh → variables → up → domain →
|
|
56
|
-
* webhook) lives in {@link deployRailwayRun}; see there for why Railway differs from Fly.
|
|
57
|
-
*/
|
|
53
|
+
/** `deploy railway --run`: drive the railway CLI to completion. */
|
|
58
54
|
async function runDeployRailway(params) {
|
|
59
55
|
const { agentDir, workspace, name, channels, intoLinked, dockerfilePath } = params;
|
|
60
56
|
const railway = spawnRunner("railway", workspace);
|
|
@@ -64,7 +60,16 @@ async function runDeployRailway(params) {
|
|
|
64
60
|
}
|
|
65
61
|
const { secrets, missingSecrets, needsModelCredential } = await carryCredentials(params);
|
|
66
62
|
gateOnModelCredential(needsModelCredential);
|
|
67
|
-
const outcome = await deployRailwayRun({
|
|
63
|
+
const outcome = await deployRailwayRun({
|
|
64
|
+
name,
|
|
65
|
+
mountPath: "/data",
|
|
66
|
+
secrets,
|
|
67
|
+
missingSecrets,
|
|
68
|
+
valueFile: params.valueFile,
|
|
69
|
+
channels,
|
|
70
|
+
intoLinked,
|
|
71
|
+
dockerfilePath,
|
|
72
|
+
}, railway, (m) => console.error(`[fastagent] ${m}`), registrarsFor(agentDir));
|
|
68
73
|
if (!outcome.ok)
|
|
69
74
|
failStartup(new Error(`deploy stopped: ${outcome.gate}`));
|
|
70
75
|
console.error(`[fastagent] deployed → ${outcome.url}`);
|
|
@@ -2,21 +2,19 @@ import type { DeclaredChannel } from "../../../channels/discover.ts";
|
|
|
2
2
|
import type { Registrars } from "../../../deploy/channel-ingress.ts";
|
|
3
3
|
import type { DeployPreflight } from "../../../deploy/preflight.ts";
|
|
4
4
|
import type { FastagentConfig } from "../../../engines/pi/config.ts";
|
|
5
|
+
import type { DeclaredSecret } from "../../../declared-secrets.ts";
|
|
5
6
|
export interface DeployOptions {
|
|
6
7
|
run?: boolean;
|
|
7
8
|
tunnel?: boolean;
|
|
8
9
|
force?: boolean;
|
|
9
|
-
stop?: boolean;
|
|
10
|
-
/** false ⇔ `--no-scale-to-zero`. */
|
|
11
|
-
scaleToZero?: boolean;
|
|
12
10
|
intoLinked?: boolean;
|
|
13
|
-
model?: string;
|
|
14
|
-
authPath?: string;
|
|
15
11
|
/** false ⇔ `--no-input`. */
|
|
16
12
|
input?: boolean;
|
|
17
13
|
}
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
14
|
+
/**
|
|
15
|
+
* What the dispatcher resolved before handing off: the placement, the flags, the config and the host-neutral
|
|
16
|
+
* pre-flight, plus the channel lists every host asks about.
|
|
17
|
+
*/
|
|
20
18
|
interface DeployContext {
|
|
21
19
|
opts: DeployOptions;
|
|
22
20
|
agentDir: string;
|
|
@@ -28,10 +26,7 @@ interface DeployContext {
|
|
|
28
26
|
channels: readonly DeclaredChannel[];
|
|
29
27
|
webhookChannels: readonly DeclaredChannel[];
|
|
30
28
|
longConnectionChannels: readonly DeclaredChannel[];
|
|
31
|
-
/** Write this host's planned artifacts into the workspace under the ownership rule.
|
|
32
|
-
* binds {@link HostDeploy.isOurs} — a host cannot name a predicate here, because every host's has
|
|
33
|
-
* the same type: passing a neighbour's, or forgetting a new host's, type-checks and then silently
|
|
34
|
-
* turns `--force` into a no-op for that host's own artifact. */
|
|
29
|
+
/** Write this host's planned artifacts into the workspace under the ownership rule. */
|
|
35
30
|
write(artifacts: {
|
|
36
31
|
path: string;
|
|
37
32
|
content: string;
|
|
@@ -40,75 +35,61 @@ interface DeployContext {
|
|
|
40
35
|
alwaysWrite?: string[];
|
|
41
36
|
}): Promise<void>;
|
|
42
37
|
}
|
|
43
|
-
/** One deploy target, as the dispatcher sees it.
|
|
44
|
-
* plus one of these beside it, and a row in `HOSTS` (cli/commands/deploy.ts). */
|
|
38
|
+
/** One deploy target, as the dispatcher sees it. */
|
|
45
39
|
export interface HostDeploy {
|
|
46
|
-
/** Did this host generate the file at `path`?
|
|
47
|
-
* (Dockerfile, .dockerignore) are answered by {@link writeArtifacts} itself. Read only through the
|
|
48
|
-
* {@link DeployContext.write} the dispatcher binds from it. */
|
|
40
|
+
/** Did this host generate the file at `path`? */
|
|
49
41
|
isOurs(path: string, content: string): boolean;
|
|
50
|
-
/**
|
|
51
|
-
*
|
|
42
|
+
/**
|
|
43
|
+
* Plan the artifacts from the pre-flight facts and what is on disk, write them, then either drive the host CLI
|
|
44
|
+
* (`--run`) or print the runbook.
|
|
45
|
+
*/
|
|
52
46
|
deploy(ctx: DeployContext): Promise<void>;
|
|
53
47
|
}
|
|
54
|
-
/** The registrars every host driver gets.
|
|
55
|
-
* the host's, so which of them can run end-to-end never varies by deployment target.
|
|
56
|
-
*
|
|
57
|
-
* All three get {@link DEPLOY_REGISTRATION_ATTEMPTS} rather than the default: a host CLI returns
|
|
58
|
-
* before the deployment answers, and a registration that gives up first GATES the deploy — reporting
|
|
59
|
-
* a working deployment as one to re-run. `dev --tunnel` keeps the shorter default; it is a resident
|
|
60
|
-
* process whose URL is live when it is printed, as does `deploy docker --run` — it reuses that same
|
|
61
|
-
* announcer, having already health-probed the container and waited for the Quick Tunnel URL.
|
|
62
|
-
*
|
|
63
|
-
* Accepted cost: pointChannelsAt registers serially, so a deployment whose channels are ALL
|
|
64
|
-
* unreachable spends the budget once per channel (3 x 180s) before it gates. Registering in parallel
|
|
65
|
-
* would not shorten a single failing channel, and the failing case is the one nobody is waiting on. */
|
|
48
|
+
/** The registrars every host driver gets. */
|
|
66
49
|
export declare function registrarsFor(agentDir: string): Registrars;
|
|
67
50
|
/**
|
|
68
|
-
* The `--run`
|
|
69
|
-
* auth.json as a `FASTAGENT_AUTH_SEED`)
|
|
51
|
+
* The `--run` carry, for every host: the value file's variables plus the local model credential (an env key, or the
|
|
52
|
+
* whole auth.json as a `FASTAGENT_AUTH_SEED`).
|
|
70
53
|
*/
|
|
71
54
|
export declare function carryCredentials(params: {
|
|
72
55
|
modelAuth: string | undefined;
|
|
73
56
|
modelKeyInDefinition: boolean;
|
|
74
57
|
authPath: string;
|
|
75
|
-
|
|
76
|
-
|
|
58
|
+
declaredSecrets: readonly DeclaredSecret[];
|
|
59
|
+
/** The deployed environment's declaration, from the pre-flight's single read. */
|
|
60
|
+
values: ReadonlyMap<string, string>;
|
|
77
61
|
}): Promise<{
|
|
78
62
|
secrets: Record<string, string>;
|
|
79
63
|
missingSecrets: string[];
|
|
80
64
|
needsModelCredential: boolean;
|
|
81
65
|
}>;
|
|
82
|
-
/** Gate a `--run` that has no model credential to carry.
|
|
83
|
-
* is why it is separate from `missingSecrets` (a `.env` fix) rather than folded into it. Docker states
|
|
84
|
-
* the same gate inside its driver, where it belongs after the daemon check. */
|
|
66
|
+
/** Gate a `--run` that has no model credential to carry. */
|
|
85
67
|
export declare function gateOnModelCredential(needsModelCredential: boolean): void;
|
|
68
|
+
/** One artifact's verdict under the ownership rule: the bytes to write, or `undefined` to keep what is there. */
|
|
69
|
+
type ArtifactStep = {
|
|
70
|
+
abs: string;
|
|
71
|
+
content: string | undefined;
|
|
72
|
+
message: string;
|
|
73
|
+
};
|
|
74
|
+
/** What `target` would become, and which generated artifacts have drifted from this definition. */
|
|
75
|
+
export type ArtifactPlan = {
|
|
76
|
+
steps: ArtifactStep[];
|
|
77
|
+
stale: string[];
|
|
78
|
+
};
|
|
86
79
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* That single rule replaced a second mechanism (a per-host list of paths exempt from `--force`), which
|
|
93
|
-
* only described one shape: with the agent inside the workspace, the root `.dockerignore` is the
|
|
94
|
-
* WORKSPACE's file and was listed; with the agent flat, the list was empty — so `--force` would have
|
|
95
|
-
* overwritten a hand-written root `Dockerfile` in a repository `init --flat` had explicitly adopted.
|
|
96
|
-
* Ownership is a property of the file, not of where the agent sits.
|
|
97
|
-
*
|
|
98
|
-
* A KEPT file of OURS that no longer matches what deploy would generate now (config/channel/lockfile/
|
|
99
|
-
* version drift — or the author's edits, which we cannot tell apart) is flagged stale; `--force`
|
|
100
|
-
* regenerates that one.
|
|
101
|
-
*
|
|
102
|
-
* Exported for its own test — this is a four-branch state machine over (exists, ours, force) that used
|
|
103
|
-
* to be proven by spawning the CLI eight times, which is command LOGIC re-run through a subprocess
|
|
104
|
-
* (see vitest.config.ts) and the suite's slowest test.
|
|
80
|
+
* Decide what happens to each generated artifact under `target`, under ONE ownership rule — reading only, so a
|
|
81
|
+
* caller can refuse a run BEFORE anything is on disk. `stale` names the paths that would be kept while no longer
|
|
82
|
+
* matching what this definition generates: a fact, not a verdict, because only the caller knows whether this run is
|
|
83
|
+
* about to deploy from one.
|
|
105
84
|
*/
|
|
106
|
-
export declare function
|
|
85
|
+
export declare function planArtifacts(target: string, artifacts: {
|
|
107
86
|
path: string;
|
|
108
87
|
content: string;
|
|
109
88
|
}[], options: {
|
|
110
89
|
force: boolean;
|
|
111
90
|
alwaysWrite?: string[];
|
|
112
91
|
isOurs: HostDeploy["isOurs"];
|
|
113
|
-
}): Promise<
|
|
92
|
+
}): Promise<ArtifactPlan>;
|
|
93
|
+
/** Carry out a {@link planArtifacts} decision. Reports each path, written or kept. */
|
|
94
|
+
export declare function applyArtifactPlan(plan: ArtifactPlan): Promise<void>;
|
|
114
95
|
export {};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What every host's deploy command shares: the context the dispatcher hands a host, the artifact
|
|
3
|
-
*
|
|
4
|
-
* `cli/commands/deploy/<host>.ts`; this file holds nothing that names one.
|
|
2
|
+
* What every host's deploy command shares: the context the dispatcher hands a host, the artifact writer with its
|
|
3
|
+
* ownership rule, and the `--run` credential carry.
|
|
5
4
|
*/
|
|
6
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
6
|
import { dirname, join } from "node:path";
|
|
@@ -10,21 +9,11 @@ import { DEPLOY_REGISTRATION_ATTEMPTS } from "../../../channels/registration.js"
|
|
|
10
9
|
import { registerSlackWebhook } from "../../../channels/slack/register-webhook.js";
|
|
11
10
|
import { registerTelegramWebhook } from "../../../channels/telegram/register-webhook.js";
|
|
12
11
|
import { isGeneratedDockerfile, isGeneratedDockerignore } from "../../../deploy/container.js";
|
|
12
|
+
import { RELEASE_FILE } from "../../../deploy/workspace.js";
|
|
13
13
|
import { assembleSecrets } from "../../../deploy/secrets.js";
|
|
14
14
|
import { exists, resolveStateRoot } from "../../../paths.js";
|
|
15
15
|
import { failStartup } from "../../fail.js";
|
|
16
|
-
/** The registrars every host driver gets.
|
|
17
|
-
* the host's, so which of them can run end-to-end never varies by deployment target.
|
|
18
|
-
*
|
|
19
|
-
* All three get {@link DEPLOY_REGISTRATION_ATTEMPTS} rather than the default: a host CLI returns
|
|
20
|
-
* before the deployment answers, and a registration that gives up first GATES the deploy — reporting
|
|
21
|
-
* a working deployment as one to re-run. `dev --tunnel` keeps the shorter default; it is a resident
|
|
22
|
-
* process whose URL is live when it is printed, as does `deploy docker --run` — it reuses that same
|
|
23
|
-
* announcer, having already health-probed the container and waited for the Quick Tunnel URL.
|
|
24
|
-
*
|
|
25
|
-
* Accepted cost: pointChannelsAt registers serially, so a deployment whose channels are ALL
|
|
26
|
-
* unreachable spends the budget once per channel (3 x 180s) before it gates. Registering in parallel
|
|
27
|
-
* would not shorten a single failing channel, and the failing case is the one nobody is waiting on. */
|
|
16
|
+
/** The registrars every host driver gets. */
|
|
28
17
|
export function registrarsFor(agentDir) {
|
|
29
18
|
const attempts = DEPLOY_REGISTRATION_ATTEMPTS;
|
|
30
19
|
return {
|
|
@@ -34,64 +23,47 @@ export function registrarsFor(agentDir) {
|
|
|
34
23
|
};
|
|
35
24
|
}
|
|
36
25
|
/**
|
|
37
|
-
* The `--run`
|
|
38
|
-
* auth.json as a `FASTAGENT_AUTH_SEED`)
|
|
26
|
+
* The `--run` carry, for every host: the value file's variables plus the local model credential (an env key, or the
|
|
27
|
+
* whole auth.json as a `FASTAGENT_AUTH_SEED`).
|
|
39
28
|
*/
|
|
40
29
|
export async function carryCredentials(params) {
|
|
41
|
-
const { modelAuth, modelKeyInDefinition, authPath,
|
|
30
|
+
const { modelAuth, modelKeyInDefinition, authPath, declaredSecrets, values } = params;
|
|
42
31
|
return assembleSecrets({
|
|
43
32
|
modelAuth,
|
|
44
33
|
modelKeyInDefinition,
|
|
45
34
|
authFile: (await exists(authPath)) ? await readFile(authPath) : undefined,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
env: process.env,
|
|
35
|
+
declared: declaredSecrets,
|
|
36
|
+
values,
|
|
49
37
|
});
|
|
50
38
|
}
|
|
51
|
-
/** Gate a `--run` that has no model credential to carry.
|
|
52
|
-
* is why it is separate from `missingSecrets` (a `.env` fix) rather than folded into it. Docker states
|
|
53
|
-
* the same gate inside its driver, where it belongs after the daemon check. */
|
|
39
|
+
/** Gate a `--run` that has no model credential to carry. */
|
|
54
40
|
export function gateOnModelCredential(needsModelCredential) {
|
|
55
41
|
if (!needsModelCredential)
|
|
56
42
|
return;
|
|
57
43
|
failStartup(new Error(`deploy stopped: no model credential — run \`fastagent login\`, or set a provider API key in .env, then re-run`));
|
|
58
44
|
}
|
|
59
45
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* That single rule replaced a second mechanism (a per-host list of paths exempt from `--force`), which
|
|
66
|
-
* only described one shape: with the agent inside the workspace, the root `.dockerignore` is the
|
|
67
|
-
* WORKSPACE's file and was listed; with the agent flat, the list was empty — so `--force` would have
|
|
68
|
-
* overwritten a hand-written root `Dockerfile` in a repository `init --flat` had explicitly adopted.
|
|
69
|
-
* Ownership is a property of the file, not of where the agent sits.
|
|
70
|
-
*
|
|
71
|
-
* A KEPT file of OURS that no longer matches what deploy would generate now (config/channel/lockfile/
|
|
72
|
-
* version drift — or the author's edits, which we cannot tell apart) is flagged stale; `--force`
|
|
73
|
-
* regenerates that one.
|
|
74
|
-
*
|
|
75
|
-
* Exported for its own test — this is a four-branch state machine over (exists, ours, force) that used
|
|
76
|
-
* to be proven by spawning the CLI eight times, which is command LOGIC re-run through a subprocess
|
|
77
|
-
* (see vitest.config.ts) and the suite's slowest test.
|
|
46
|
+
* Decide what happens to each generated artifact under `target`, under ONE ownership rule — reading only, so a
|
|
47
|
+
* caller can refuse a run BEFORE anything is on disk. `stale` names the paths that would be kept while no longer
|
|
48
|
+
* matching what this definition generates: a fact, not a verdict, because only the caller knows whether this run is
|
|
49
|
+
* about to deploy from one.
|
|
78
50
|
*/
|
|
79
|
-
export async function
|
|
51
|
+
export async function planArtifacts(target, artifacts, options) {
|
|
52
|
+
const plan = { steps: [], stale: [] };
|
|
53
|
+
const write = (path, abs, content) => plan.steps.push({ abs, content, message: `[fastagent] wrote ${path}` });
|
|
54
|
+
const keep = (abs, message) => plan.steps.push({ abs, content: undefined, message });
|
|
80
55
|
for (const a of artifacts) {
|
|
81
56
|
const abs = join(target, a.path);
|
|
82
57
|
// Pure build output, not operator-owned configuration. It must track the generated template/runbook.
|
|
83
|
-
if (options.alwaysWrite?.includes(a.path)) {
|
|
84
|
-
|
|
85
|
-
await writeFile(abs, a.content);
|
|
86
|
-
console.error(`[fastagent] wrote ${a.path}`);
|
|
58
|
+
if (a.path.endsWith(`/${RELEASE_FILE}`) || options.alwaysWrite?.includes(a.path)) {
|
|
59
|
+
write(a.path, abs, a.content);
|
|
87
60
|
continue;
|
|
88
61
|
}
|
|
89
62
|
const existing = (await exists(abs)) ? await readFile(abs, "utf8") : undefined;
|
|
90
63
|
const ours = existing !== undefined && isOurArtifact(a.path, existing, options.isOurs);
|
|
91
64
|
if (existing !== undefined && !ours) {
|
|
92
|
-
// Only the `.dockerignore` has content checks in preflight
|
|
93
|
-
|
|
94
|
-
console.error(`[fastagent] kept ${a.path} — not generated by fastagent, so --force does not touch it ` +
|
|
65
|
+
// Only the `.dockerignore` has content checks in preflight.
|
|
66
|
+
keep(abs, `[fastagent] kept ${a.path} — not generated by fastagent, so --force does not touch it ` +
|
|
95
67
|
`(delete it to let deploy own the path)` +
|
|
96
68
|
(a.path.endsWith(".dockerignore")
|
|
97
69
|
? `; see the preflight warnings for what it must exclude`
|
|
@@ -101,20 +73,29 @@ export async function writeArtifacts(target, artifacts, options) {
|
|
|
101
73
|
continue;
|
|
102
74
|
}
|
|
103
75
|
if (existing !== undefined && !options.force) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
76
|
+
if (existing !== a.content)
|
|
77
|
+
plan.stale.push(a.path);
|
|
78
|
+
keep(abs, existing !== a.content
|
|
79
|
+
? `[fastagent] kept ${a.path} — it no longer matches what deploy would generate; pass --force to ` +
|
|
80
|
+
`regenerate.`
|
|
107
81
|
: `[fastagent] kept ${a.path} (unchanged)`);
|
|
108
82
|
continue;
|
|
109
83
|
}
|
|
110
|
-
|
|
111
|
-
await writeFile(abs, a.content);
|
|
112
|
-
console.error(`[fastagent] wrote ${a.path}`);
|
|
84
|
+
write(a.path, abs, a.content);
|
|
113
85
|
}
|
|
86
|
+
return plan;
|
|
114
87
|
}
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
|
|
88
|
+
/** Carry out a {@link planArtifacts} decision. Reports each path, written or kept. */
|
|
89
|
+
export async function applyArtifactPlan(plan) {
|
|
90
|
+
for (const step of plan.steps) {
|
|
91
|
+
if (step.content !== undefined) {
|
|
92
|
+
await mkdir(dirname(step.abs), { recursive: true }); // artifacts live under fastagent/
|
|
93
|
+
await writeFile(step.abs, step.content);
|
|
94
|
+
}
|
|
95
|
+
console.error(step.message);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Did fastagent generate the file at `path`? */
|
|
118
99
|
function isOurArtifact(path, content, host) {
|
|
119
100
|
if (path.endsWith("Dockerfile"))
|
|
120
101
|
return isGeneratedDockerfile(content);
|
|
@@ -1,41 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `fastagent deploy <host> [dir]`: generate host artifacts from the resolved definition and print an
|
|
3
|
-
*
|
|
4
|
-
* run the host CLI by default: fastagent owns the definition-aware artifacts and precise runbook;
|
|
5
|
-
* Docker may opt into a generated ephemeral tunnel, while durable ingress stays operator-owned. The pre-flight
|
|
6
|
-
* (config/model/channels/container facts) is host-neutral; each host's module (`./deploy/<host>.ts`)
|
|
7
|
-
* adds its artifacts, its kept-file semantics, its gates and its `--run` drive over the neutral
|
|
8
|
-
* `deploy/<host>/` plan + driver. Read-only on the definition; the only writes are generated
|
|
9
|
-
* artifacts (never clobbered without --force). `--run` drives the target CLI instead of printing.
|
|
2
|
+
* `fastagent deploy <host> [dir]`: generate host artifacts from the resolved definition and print an ordered deploy
|
|
3
|
+
* runbook.
|
|
10
4
|
*/
|
|
11
5
|
import type { DeployHost } from "../../deploy/hosts.ts";
|
|
12
|
-
import { type DeployOptions, type HostDeploy,
|
|
13
|
-
export {
|
|
14
|
-
/** Every host, by the name the CLI takes.
|
|
15
|
-
* without a module here fails to compile. */
|
|
6
|
+
import { type DeployOptions, type HostDeploy, applyArtifactPlan, planArtifacts } from "./deploy/shared.ts";
|
|
7
|
+
export { applyArtifactPlan, planArtifacts };
|
|
8
|
+
/** Every host, by the name the CLI takes. */
|
|
16
9
|
export declare const HOSTS: Record<DeployHost, HostDeploy>;
|
|
17
|
-
/** A flag exactly one host honours, and what the OTHERS do instead.
|
|
18
|
-
* non-owners, so a host added to `DEPLOY_HOSTS` cannot silently lose its line. */
|
|
10
|
+
/** A flag exactly one host honours, and what the OTHERS do instead. */
|
|
19
11
|
interface HostOnlyFlag<Owner extends DeployHost> {
|
|
20
12
|
flag: string;
|
|
21
13
|
owner: Owner;
|
|
22
14
|
passed: (opts: DeployOptions) => boolean;
|
|
23
15
|
instead: Record<Exclude<DeployHost, Owner>, string>;
|
|
24
16
|
}
|
|
17
|
+
/** ONE table for "this flag belongs to that host", because the fact is symmetric and was not stored that way. */
|
|
18
|
+
export declare const HOST_ONLY_FLAGS: HostOnlyFlag<"railway">[];
|
|
25
19
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* existed three times per flag and had already drifted (`Railway-only` twice, `railway-only` once).
|
|
29
|
-
*
|
|
30
|
-
* Every row is a flag that only WARNS elsewhere. `--tunnel` is host-only too and is deliberately not
|
|
31
|
-
* here: it is a usage GATE (`failUsage`, exit 2) raised in `runDeploy`, not a warning — moving it in
|
|
32
|
-
* would downgrade a refusal to a line of advice.
|
|
33
|
-
*
|
|
34
|
-
* Exported for the exhaustiveness test: the type stops a missing `instead` line, and the test stops it
|
|
35
|
-
* from being typed away.
|
|
20
|
+
* Exported for its own test: nothing covered these six sentences, which is how three of them came to disagree about
|
|
21
|
+
* the spelling of a host name.
|
|
36
22
|
*/
|
|
37
|
-
export declare const HOST_ONLY_FLAGS: (HostOnlyFlag<"fly"> | HostOnlyFlag<"railway">)[];
|
|
38
|
-
/** Exported for its own test: nothing covered these six sentences, which is how three of them came to
|
|
39
|
-
* disagree about the spelling of a host name. */
|
|
40
23
|
export declare function warnHostOnlyFlags(host: DeployHost, opts: DeployOptions): void;
|
|
41
24
|
export declare function runDeploy(host: DeployHost, dirArg: string, opts: DeployOptions): Promise<void>;
|