@fastagent-sh/fastagent 0.21.0 → 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 -43
- 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,41 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Railway's model forces differences from the Fly runner (fly/run.ts), all validated against CLI 5.15.0:
|
|
6
|
-
*
|
|
7
|
-
* - **`--run` PROVISIONS a project and only runs on an UNLINKED directory** — so it can never deploy
|
|
8
|
-
* into a project it didn't create. `railway init` isn't check-then-act (it ALWAYS makes a new project)
|
|
9
|
-
* and `railway status` exits 0 even when unlinked (linkedness is read from stdout: JSON when linked,
|
|
10
|
-
* empty when not). We do NOT track ownership: a pre-existing link is refused (Railway has no globally
|
|
11
|
-
* unique name to give free identity like Fly's app name, and synthesizing one — a machine-local marker
|
|
12
|
-
* — was a large, bug-prone premature optimization). The only "yes, this project" signal is the operator's
|
|
13
|
-
* explicit `--into-linked`, which provisions INTO the linked project; a routine redeploy is just
|
|
14
|
-
* `railway up`. The volume is check-then-act, so `--into-linked` (or a create that failed at volume-add) still gets it.
|
|
15
|
-
* - **Every command needs `--service` explicitly** to stay non-interactive (a bare command prompts to
|
|
16
|
-
* pick a service). `railway volume add` is the exception — it has NO `--service` flag and rides the
|
|
17
|
-
* linked service, so the service must be created (and thus linked) first.
|
|
18
|
-
* - **The public URL is minted, not deterministic** — bare `railway domain --json` returns the service
|
|
19
|
-
* domain (minting one if absent), read for the webhook. We avoid the newer `domain list` subcommand:
|
|
20
|
-
* on a CLI without it, `railway domain list` registers a bogus custom domain named "list" (destructive).
|
|
21
|
-
*
|
|
22
|
-
* Secrets go in one-per-`variable set --stdin` (value on stdin, never argv/process listing — Railway has
|
|
23
|
-
* no bulk stdin import like Fly's `secrets import`). Auth needs an ACCOUNT credential (login or
|
|
24
|
-
* `RAILWAY_API_TOKEN`), not the project-scoped `RAILWAY_TOKEN`: `init` creates a project that a project
|
|
25
|
-
* token can't predate.
|
|
26
|
-
*/
|
|
27
|
-
import { registerWebhooks } from "../channel-ingress.js";
|
|
28
|
-
/**
|
|
29
|
-
* Whether `railway status --json` shows a linked project: non-empty stdout. Unlinked prints its message
|
|
30
|
-
* to stderr and leaves stdout EMPTY (the exit code is 0 either way, so it can't be the signal). ANY
|
|
31
|
-
* non-empty output — parseable or not — counts as linked, so an unreadable shape is refused, never
|
|
32
|
-
* mistaken for unlinked (which would `init` a duplicate). Verified against CLI 5.15.0.
|
|
33
|
-
*/
|
|
1
|
+
/** `fastagent deploy railway --run` — drive the Railway CLI to completion. */
|
|
2
|
+
import { publicHealthGate, registerWebhooks } from "../channel-ingress.js";
|
|
3
|
+
import { missingValuesGate } from "../secrets.js";
|
|
4
|
+
/** Whether `railway status --json` shows a linked project: non-empty stdout. */
|
|
34
5
|
export function isLinked(stdout) {
|
|
35
6
|
return stdout.trim() !== "";
|
|
36
7
|
}
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
8
|
+
/**
|
|
9
|
+
* The linked project's name for the gate message, or undefined if it can't be read (still linked — `railway status
|
|
10
|
+
* --json` puts `name` at the top level on 5.15.0).
|
|
11
|
+
*/
|
|
39
12
|
export function linkedName(stdout) {
|
|
40
13
|
try {
|
|
41
14
|
const v = JSON.parse(stdout);
|
|
@@ -43,13 +16,11 @@ export function linkedName(stdout) {
|
|
|
43
16
|
return v.name;
|
|
44
17
|
}
|
|
45
18
|
catch {
|
|
46
|
-
|
|
19
|
+
// non-JSON but non-empty → still linked, just no name to show
|
|
47
20
|
}
|
|
48
21
|
return undefined;
|
|
49
22
|
}
|
|
50
|
-
/** Every string leaf of a parsed JSON value
|
|
51
|
-
* `--json` field names aren't guaranteed, so we match on VALUES, not paths. `stdout` that isn't JSON
|
|
52
|
-
* (an empty/errored query) yields []. */
|
|
23
|
+
/** Every string leaf of a parsed JSON value. */
|
|
53
24
|
function jsonStrings(stdout) {
|
|
54
25
|
const walk = (v) => typeof v === "string"
|
|
55
26
|
? [v]
|
|
@@ -65,12 +36,7 @@ function jsonStrings(stdout) {
|
|
|
65
36
|
return [];
|
|
66
37
|
}
|
|
67
38
|
}
|
|
68
|
-
/**
|
|
69
|
-
* The first Railway-provided domain as an https URL, or undefined if none is present — reads `railway
|
|
70
|
-
* domain --json` without assuming exact field names: any string value carrying a `*.railway.app` host
|
|
71
|
-
* wins (unanchored, so a scheme/port/path around it still yields the bare host). Undefined → the CLI
|
|
72
|
-
* gates (register manually).
|
|
73
|
-
*/
|
|
39
|
+
/** The first Railway-provided domain as an https URL, or undefined if none is present. */
|
|
74
40
|
export function parseDomainUrl(stdout) {
|
|
75
41
|
for (const s of jsonStrings(stdout)) {
|
|
76
42
|
const host = s.match(/[a-z0-9-]+(?:\.[a-z0-9-]+)*\.railway\.app/i)?.[0];
|
|
@@ -80,42 +46,50 @@ export function parseDomainUrl(stdout) {
|
|
|
80
46
|
return undefined;
|
|
81
47
|
}
|
|
82
48
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
49
|
+
* The status of the volume `railway volume list --json` shows mounted at `mountPath` ON `service`, or undefined when
|
|
50
|
+
* that service has none there. The list is the whole PROJECT's: another service's volume, or one a deleted service
|
|
51
|
+
* left behind (`serviceName: null` — deleting a service keeps its volume), sits at the same path and is not this
|
|
52
|
+
* one. Read from any object carrying `mountPath` and `serviceName`, so a wrapper key moving does not matter.
|
|
86
53
|
*/
|
|
87
|
-
export function
|
|
88
|
-
|
|
54
|
+
export function volumeOn(stdout, service, mountPath) {
|
|
55
|
+
const walk = (v) => {
|
|
56
|
+
if (Array.isArray(v))
|
|
57
|
+
return v.map(walk).find((status) => status !== undefined);
|
|
58
|
+
if (!v || typeof v !== "object")
|
|
59
|
+
return undefined;
|
|
60
|
+
const o = v;
|
|
61
|
+
if (o.mountPath === mountPath && o.serviceName === service)
|
|
62
|
+
return typeof o.status === "string" ? o.status : "";
|
|
63
|
+
return walk(Object.values(o));
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
return walk(JSON.parse(stdout));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
89
71
|
}
|
|
90
72
|
/**
|
|
91
|
-
*
|
|
92
|
-
* the
|
|
93
|
-
*
|
|
94
|
-
* fail-visible.
|
|
73
|
+
* How long `--run` waits for a new volume to reach its service before deploying. Railway's `volume add` returns
|
|
74
|
+
* before the volume is attached, and a deployment started in that window runs WITHOUT it — no `/data` at all —
|
|
75
|
+
* while a later one gets it (measured 2026-09-27: listed on the service as `Ready` 5s after the add).
|
|
95
76
|
*/
|
|
96
|
-
|
|
77
|
+
const VOLUME_ATTACH_TIMEOUT_MS = 60_000;
|
|
78
|
+
const VOLUME_POLL_MS = 2_000;
|
|
79
|
+
export async function deployRailwayRun(plan, railway, log, registrars, healthProbe, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
97
80
|
const gate = (g) => ({ ok: false, gate: g });
|
|
98
|
-
// Every --service below targets plan.name — the name this tool gives BOTH the project and the service
|
|
99
|
-
//
|
|
100
|
-
// project whose service is named differently, the FIRST --service command (`variables set`) gates —
|
|
101
|
-
// and it's ordered before the volume (which has no --service), so the mismatch fails visibly with no
|
|
102
|
-
// side effect. (Pre-checking the name means walking status's nested multi-service shape — not worth it.)
|
|
81
|
+
// Every --service below targets plan.name — the name this tool gives BOTH the project and the service (`init
|
|
82
|
+
// --name` + `add --service`).
|
|
103
83
|
const svc = ["--service", plan.name];
|
|
104
|
-
// 1.
|
|
105
|
-
// RAILWAY_TOKEN can't predate the project `init` creates. `whoami` succeeds with either.
|
|
84
|
+
// 1.
|
|
106
85
|
if ((await railway(["whoami"], { capture: true })).code !== 0) {
|
|
107
86
|
return gate("not logged in to Railway — run `railway login`, or set RAILWAY_API_TOKEN (an account token), then re-run");
|
|
108
87
|
}
|
|
109
88
|
// 2. Gate missing required secret VALUES before any side effect (no half-created infra).
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// 3.
|
|
114
|
-
// into a project it didn't create. A linked directory is REFUSED (naming the project so the operator
|
|
115
|
-
// sees what it is) UNLESS they pass --into-linked to provision INTO that project deliberately.
|
|
116
|
-
// Unlinked → create. No ownership is tracked — a pre-existing link is never assumed ours; the only
|
|
117
|
-
// "yes, this project" signal is the explicit --into-linked. Any non-empty status counts as linked (an
|
|
118
|
-
// unreadable shape is refused, not mistaken for unlinked, which would `init` a duplicate).
|
|
89
|
+
const missingValues = missingValuesGate(plan.missingSecrets, plan.valueFile);
|
|
90
|
+
if (missingValues)
|
|
91
|
+
return gate(missingValues);
|
|
92
|
+
// 3.
|
|
119
93
|
const status = await railway(["status", "--json"], { capture: true });
|
|
120
94
|
if (isLinked(status.stdout)) {
|
|
121
95
|
if (!plan.intoLinked) {
|
|
@@ -128,8 +102,7 @@ export async function deployRailwayRun(plan, railway, log, registrars) {
|
|
|
128
102
|
log(`provisioning into linked project ${linkedName(status.stdout) ?? plan.name} (--into-linked)`);
|
|
129
103
|
}
|
|
130
104
|
else {
|
|
131
|
-
// --into-linked means "provision into the project this dir is linked to" — but it isn't linked.
|
|
132
|
-
// swallow the flag silently: the operator expected an existing project; say we're creating a fresh one.
|
|
105
|
+
// --into-linked means "provision into the project this dir is linked to" — but it isn't linked.
|
|
133
106
|
if (plan.intoLinked) {
|
|
134
107
|
log("warn: --into-linked was passed but this directory isn't linked to any project — creating a fresh one");
|
|
135
108
|
}
|
|
@@ -137,29 +110,28 @@ export async function deployRailwayRun(plan, railway, log, registrars) {
|
|
|
137
110
|
if ((await railway(["init", "--name", plan.name])).code !== 0) {
|
|
138
111
|
return gate("`railway init` failed — if you have multiple workspaces, run it once interactively (or pass --workspace) to pick one, then re-run");
|
|
139
112
|
}
|
|
140
|
-
// Create + link the service (init makes only a project); it precedes the volume, which has no
|
|
141
|
-
//
|
|
113
|
+
// Create + link the service (init makes only a project); it precedes the volume, which has no --service flag and
|
|
114
|
+
// rides the linked service.
|
|
142
115
|
log(`creating service ${plan.name}…`);
|
|
143
116
|
if ((await railway(["add", "--service", plan.name])).code !== 0) {
|
|
144
|
-
// Precise recovery, not "fix and re-run": init already created + linked the project, so a plain
|
|
145
|
-
//
|
|
146
|
-
// service to ride). The clean paths are to create the service by hand then --into-linked, or unlink.
|
|
117
|
+
// Precise recovery, not "fix and re-run": init already created + linked the project, so a plain re-run hits the
|
|
118
|
+
// linked-gate, and --into-linked SKIPS `add` and then fails at the volume (no service to ride).
|
|
147
119
|
return gate(`\`railway add --service\` failed — \`railway init\` already created the project (this directory is now ` +
|
|
148
120
|
`linked) but not the service. Finish with \`railway add --service ${plan.name}\` here, then re-run with ` +
|
|
149
121
|
`--into-linked; or \`railway unlink\` to detach and start fresh.`);
|
|
150
122
|
}
|
|
151
123
|
}
|
|
152
|
-
// 3b.
|
|
153
|
-
// an --into-linked into a hand-made project whose service name ≠ plan.name, it gates HERE, before the
|
|
154
|
-
// volume (which has no --service and would otherwise attach to that service) — so the mismatch fails
|
|
155
|
-
// visibly with NO side effect. State root on argv (not secret); secrets one-per-`set --stdin` (value
|
|
156
|
-
// on stdin, never argv). Idempotent. (Order vs the volume is free — both just need to precede `up`.)
|
|
124
|
+
// 3b.
|
|
157
125
|
const machineryVars = [
|
|
158
126
|
`FASTAGENT_STATE_DIR=${plan.mountPath}/.state`,
|
|
159
127
|
`FASTAGENT_SECRETS_DIR=${plan.mountPath}/.secrets`,
|
|
160
128
|
`RAILWAY_DOCKERFILE_PATH=${plan.dockerfilePath}`,
|
|
161
129
|
];
|
|
162
|
-
|
|
130
|
+
// The secret NAMES, not a count: `--run` uploads the whole value file, so the operator has to be able to see the
|
|
131
|
+
// list on every host.
|
|
132
|
+
const secretNames = Object.keys(plan.secrets);
|
|
133
|
+
log(`setting ${machineryVars.map((v) => v.split("=")[0]).join("/")} + ${secretNames.length} secret(s)` +
|
|
134
|
+
`${secretNames.length > 0 ? `: ${secretNames.join(", ")}` : ""}…`);
|
|
163
135
|
if ((await railway(["variables", "set", ...machineryVars, ...svc])).code !== 0) {
|
|
164
136
|
return gate("`railway variables set` failed — see the railway output above");
|
|
165
137
|
}
|
|
@@ -168,39 +140,53 @@ export async function deployRailwayRun(plan, railway, log, registrars) {
|
|
|
168
140
|
return gate(`\`railway variables set ${k}\` failed — see the railway output above`);
|
|
169
141
|
}
|
|
170
142
|
}
|
|
171
|
-
// 3c.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// service (see above) has already gated — this step won't attach a volume to the wrong service.
|
|
176
|
-
const vols = await railway(["volume", "list", "--json"], { capture: true });
|
|
177
|
-
if (parseHasVolume(vols.stdout, plan.mountPath)) {
|
|
178
|
-
log(`volume at ${plan.mountPath} exists — skipping`);
|
|
143
|
+
// 3c.
|
|
144
|
+
const volumeStatus = async () => volumeOn((await railway(["volume", "list", "--json"], { capture: true })).stdout, plan.name, plan.mountPath);
|
|
145
|
+
if ((await volumeStatus()) !== undefined) {
|
|
146
|
+
log(`volume at ${plan.mountPath} exists on ${plan.name} — skipping`);
|
|
179
147
|
}
|
|
180
148
|
else {
|
|
181
149
|
log(`creating volume at ${plan.mountPath}…`);
|
|
182
150
|
if ((await railway(["volume", "add", "--mount-path", plan.mountPath])).code !== 0) {
|
|
183
151
|
return gate("`railway volume add` failed — see the railway output above");
|
|
184
152
|
}
|
|
153
|
+
// Deploying before the volume is attached boots a container with no volume, which `start` refuses (it must
|
|
154
|
+
// never seed a workspace onto a disk that vanishes on restart).
|
|
155
|
+
let status;
|
|
156
|
+
for (let waited = 0;; waited += VOLUME_POLL_MS) {
|
|
157
|
+
status = await volumeStatus();
|
|
158
|
+
if (status === "Ready" || waited >= VOLUME_ATTACH_TIMEOUT_MS)
|
|
159
|
+
break;
|
|
160
|
+
await sleep(VOLUME_POLL_MS);
|
|
161
|
+
}
|
|
162
|
+
if (status !== "Ready") {
|
|
163
|
+
return gate(`the volume at ${plan.mountPath} is not attached to service ${plan.name} after ` +
|
|
164
|
+
`${VOLUME_ATTACH_TIMEOUT_MS / 1000}s (${status === undefined ? "not listed on it" : `status ${status}`}) — ` +
|
|
165
|
+
"check `railway volume list`, then re-run with --into-linked once it shows Ready");
|
|
166
|
+
}
|
|
185
167
|
}
|
|
186
168
|
// 5. Deploy — CI mode streams build logs then exits (no interactive attach). Build runs on Railway.
|
|
187
169
|
log("deploying (railway up)…");
|
|
188
170
|
if ((await railway(["up", "--ci", ...svc])).code !== 0) {
|
|
189
171
|
return gate("`railway up` failed — see the railway output above; fix and re-run");
|
|
190
172
|
}
|
|
191
|
-
// 6.
|
|
192
|
-
// <app>.fly.dev), so every deploy needs it (the /invoke + /health surface), not just webhook channels.
|
|
193
|
-
// The top-level `railway domain` (no subcommand; --json/--service flags are fine) is the long-standing
|
|
194
|
-
// core command: it returns the service domain, minting one if absent. We do NOT pre-check with the
|
|
195
|
-
// `domain list` SUBCOMMAND — it's newer, and on a CLI without it `railway domain list` falls through
|
|
196
|
-
// to the [DOMAIN] positional and registers a bogus custom domain named "list" (destructive). Version-robust.
|
|
173
|
+
// 6.
|
|
197
174
|
log("getting the public domain…");
|
|
198
175
|
const url = parseDomainUrl((await railway(["domain", "--json", ...svc], { capture: true })).stdout);
|
|
199
176
|
if (!url) {
|
|
200
177
|
return gate("couldn't read a domain from `railway domain` — run `railway domain` manually, then set any webhook");
|
|
201
178
|
}
|
|
202
|
-
//
|
|
203
|
-
|
|
179
|
+
// 6b. `railway up --ci` exits 0 once the build is accepted, so readiness is asked here and not inferred.
|
|
180
|
+
const healthGate = await publicHealthGate({
|
|
181
|
+
baseUrl: url,
|
|
182
|
+
channels: plan.channels,
|
|
183
|
+
log,
|
|
184
|
+
inspectHint: "the service itself deployed — inspect `railway logs`, then re-run once it answers",
|
|
185
|
+
probe: healthProbe,
|
|
186
|
+
});
|
|
187
|
+
if (healthGate)
|
|
188
|
+
return gate(healthGate);
|
|
189
|
+
// 7.
|
|
204
190
|
const registrationGateMsg = await registerWebhooks({
|
|
205
191
|
baseUrl: url,
|
|
206
192
|
channels: plan.channels,
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host-NEUTRAL step-7 gate policy, shared by the host runners: the registrars report facts
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* - "failed" gates — exit 0 would tell a coding agent "done" while the deployed agent cannot receive
|
|
6
|
-
* messages, and the runners' earlier steps are idempotent so a re-run just retries registration.
|
|
7
|
-
* - "manual" does NOT gate — re-running can never change it (an unclearable gate would spin a coding
|
|
8
|
-
* agent forever) — but is re-surfaced after all registrar output (and before any gate message) so
|
|
9
|
-
* the operator cannot miss it under the registrar's earlier log lines.
|
|
10
|
-
*
|
|
11
|
-
* The runners attempt ALL channels first (one failure doesn't skip the rest), then apply this policy
|
|
12
|
-
* once. Only the retry remediation differs per host (`retryHint`).
|
|
2
|
+
* Host-NEUTRAL step-7 gate policy, shared by the host runners: the registrars report facts ({@link
|
|
3
|
+
* RegistrationOutcome}), this module owns what to do about them.
|
|
13
4
|
*/
|
|
14
5
|
import type { RegistrationOutcome } from "../channels/registration.ts";
|
|
15
6
|
export declare function registrationGate(log: (msg: string) => void, retryHint: string): {
|
|
16
7
|
track: (kind: string, outcome: RegistrationOutcome) => void;
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
8
|
+
/**
|
|
9
|
+
* Logs the manual notices; returns the gate message (composes with cli.ts's "deploy stopped:" prefix — it leads with
|
|
10
|
+
* the failure, not "the deploy succeeded"), or undefined for no gate.
|
|
11
|
+
*/
|
|
19
12
|
gate: () => string | undefined;
|
|
20
13
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHAT FORBIDS SCALING TO ZERO — one rule, read by every host that can scale.
|
|
3
|
+
*
|
|
4
|
+
* A host may only phrase its own REMEDY — the setting it owns (`min_machines_running`, App Sleeping) — never
|
|
5
|
+
* re-derive the reason.
|
|
6
|
+
*
|
|
7
|
+
* ORDER IS THE POINT, not just precedence: the reason reported has to be the one an operator cannot work around,
|
|
8
|
+
* because the message it produces differs. A declared schedule has a substitute, because the clock does not have to
|
|
9
|
+
* be ours — see {@link CRON_CAN_BE_EXTERNAL}; a long connection has none.
|
|
10
|
+
*
|
|
11
|
+
* A WAKE-UP IS NOT A REASON. Every serve mounts `wake`, so counting it would pin every deployment up. It does not
|
|
12
|
+
* need to: the wake-up store is on the volume and the poll drains what is due when the process starts, so a box that
|
|
13
|
+
* slept fires it late, when a request next wakes it — never loses it. {@link WAKEUPS_WHEN_ASLEEP} says so where
|
|
14
|
+
* scaling to zero is offered.
|
|
15
|
+
*/
|
|
16
|
+
import type { DeclaredChannel } from "../channels/discover.ts";
|
|
17
|
+
/** Why one machine has to stay up. Also the order they are checked in. */
|
|
18
|
+
export type ResidencyReason = "long-connection" | "cron";
|
|
19
|
+
export interface Residency {
|
|
20
|
+
reason: ResidencyReason;
|
|
21
|
+
/** The host-neutral cause, one clause, for a generated comment or a runbook line. */
|
|
22
|
+
why: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The ONE reason a caller may offer a way out of: a cron is a TIME, and a time can be kept elsewhere — a platform
|
|
26
|
+
* scheduler, a CI cron, a crontab — which then calls this agent. Nothing else here can be moved out: a long
|
|
27
|
+
* connection is one this process holds.
|
|
28
|
+
*/
|
|
29
|
+
export declare const CRON_CAN_BE_EXTERNAL: ResidencyReason;
|
|
30
|
+
/** What scaling to zero costs the agent's own wake-ups — said next to every setting that allows it. */
|
|
31
|
+
export declare const WAKEUPS_WHEN_ASLEEP = "the agent's own wake-ups then fire when a request next wakes the machine, not on time";
|
|
32
|
+
/** What forbids scale-to-zero for this deployment, or `undefined` when nothing does. */
|
|
33
|
+
export declare function residencyFor(facts: {
|
|
34
|
+
channels: readonly DeclaredChannel[];
|
|
35
|
+
/** `routines/` declares at least one cron. */
|
|
36
|
+
hasCron: boolean;
|
|
37
|
+
}): Residency | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE reason a caller may offer a way out of: a cron is a TIME, and a time can be kept elsewhere — a platform
|
|
3
|
+
* scheduler, a CI cron, a crontab — which then calls this agent. Nothing else here can be moved out: a long
|
|
4
|
+
* connection is one this process holds.
|
|
5
|
+
*/
|
|
6
|
+
export const CRON_CAN_BE_EXTERNAL = "cron";
|
|
7
|
+
/** What scaling to zero costs the agent's own wake-ups — said next to every setting that allows it. */
|
|
8
|
+
export const WAKEUPS_WHEN_ASLEEP = "the agent's own wake-ups then fire when a request next wakes the machine, not on time";
|
|
9
|
+
/** What forbids scale-to-zero for this deployment, or `undefined` when nothing does. */
|
|
10
|
+
export function residencyFor(facts) {
|
|
11
|
+
if (facts.channels.some((channel) => channel.ingress === "long-connection")) {
|
|
12
|
+
return {
|
|
13
|
+
reason: "long-connection",
|
|
14
|
+
why: "a long-connection channel must stay connected — it cannot wake from zero",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (facts.hasCron) {
|
|
18
|
+
return {
|
|
19
|
+
reason: "cron",
|
|
20
|
+
why: "a cron instant has no external wake-up here, so a sleeping box sleeps through it",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
package/dist/deploy/runner.d.ts
CHANGED
|
@@ -2,14 +2,16 @@ interface RunResult {
|
|
|
2
2
|
code: number;
|
|
3
3
|
/** Captured stdout (for `--json` queries); empty when the command streamed to the terminal. */
|
|
4
4
|
stdout: string;
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
5
|
+
/**
|
|
6
|
+
* Captured stderr — ONLY when `captureStderr` was set (a caller that must CLASSIFY a failure, e.g. "not found" vs
|
|
7
|
+
* "denied").
|
|
8
|
+
*/
|
|
7
9
|
stderr?: string;
|
|
8
10
|
}
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Run `bin args`: `capture` collects stdout (for `--json` queries), else the command streams to the terminal
|
|
13
|
+
* (create/deploy) and stdout is empty.
|
|
14
|
+
*/
|
|
13
15
|
export type CliRunner = (args: string[], opts?: {
|
|
14
16
|
capture?: boolean;
|
|
15
17
|
captureStderr?: boolean;
|
|
@@ -17,9 +19,8 @@ export type CliRunner = (args: string[], opts?: {
|
|
|
17
19
|
env?: NodeJS.ProcessEnv;
|
|
18
20
|
}) => Promise<RunResult>;
|
|
19
21
|
/**
|
|
20
|
-
* Production {@link CliRunner}: spawn `bin` in `cwd` (the workspace, so a build/upload context is the
|
|
21
|
-
*
|
|
22
|
-
* ENOENT (the CLI not on PATH) resolves to code 127 so the caller can gate with an install hint.
|
|
22
|
+
* Production {@link CliRunner}: spawn `bin` in `cwd` (the workspace, so a build/upload context is the agent). stderr
|
|
23
|
+
* is always inherited to the terminal.
|
|
23
24
|
*/
|
|
24
25
|
export declare function spawnRunner(bin: string, cwd: string): CliRunner;
|
|
25
26
|
export {};
|
package/dist/deploy/runner.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The host-CLI dispatcher seam, shared by every `deploy <host> --run` driver (fly/run.ts, railway/run.ts).
|
|
3
|
-
* WHAT it does — run a CLI, optionally capture stdout or feed stdin — is identical across hosts; only the
|
|
4
|
-
* binary and the command sequence differ. Tests inject a fake recorder; production spawns the real CLI.
|
|
5
|
-
*/
|
|
1
|
+
/** The host-CLI dispatcher seam, shared by every `deploy <host> --run` driver (fly/run.ts, railway/run.ts). */
|
|
6
2
|
import { spawn } from "node:child_process";
|
|
7
3
|
import { log } from "../log.js";
|
|
8
4
|
/**
|
|
9
|
-
* Production {@link CliRunner}: spawn `bin` in `cwd` (the workspace, so a build/upload context is the
|
|
10
|
-
*
|
|
11
|
-
* ENOENT (the CLI not on PATH) resolves to code 127 so the caller can gate with an install hint.
|
|
5
|
+
* Production {@link CliRunner}: spawn `bin` in `cwd` (the workspace, so a build/upload context is the agent). stderr
|
|
6
|
+
* is always inherited to the terminal.
|
|
12
7
|
*/
|
|
13
8
|
export function spawnRunner(bin, cwd) {
|
|
14
9
|
return (args, opts) => new Promise((res) => {
|
|
@@ -27,22 +22,16 @@ export function spawnRunner(bin, cwd) {
|
|
|
27
22
|
child.stdout?.on("data", (d) => (out += String(d)));
|
|
28
23
|
child.stderr?.on("data", (d) => (err += String(d)));
|
|
29
24
|
if (opts?.input) {
|
|
30
|
-
// A host CLI that rejects before reading (bad auth, a refused command) closes stdin under us
|
|
31
|
-
// the write then emits `error` on a stream with no listener, which Node turns into an uncaught
|
|
32
|
-
// exception — crashing the deploy and losing the gate message the exit code was about to carry.
|
|
33
|
-
// HELD, not dropped: the exit code says it only when the CLI exits non-zero (see below).
|
|
25
|
+
// A host CLI that rejects before reading (bad auth, a refused command) closes stdin under us.
|
|
34
26
|
child.stdin?.on("error", (error) => (stdinError ??= error));
|
|
35
27
|
child.stdin?.end(opts.input);
|
|
36
28
|
}
|
|
37
29
|
child.on("close", (code) => {
|
|
38
|
-
// A CLI that exits 0 having refused part of its stdin took TRUNCATED input
|
|
39
|
-
// well past the 64KB pipe buffer, so "success" here would deploy a half-written secret and say
|
|
40
|
-
// nothing. The exit code is what callers read, so the failure has to reach them as one.
|
|
30
|
+
// A CLI that exits 0 having refused part of its stdin took TRUNCATED input.
|
|
41
31
|
const truncated = stdinError !== undefined && (code ?? 1) === 0;
|
|
42
32
|
if (truncated) {
|
|
43
|
-
// `error`, not `warn`: this line is the ONLY evidence of the failure — the caller's gate says
|
|
44
|
-
//
|
|
45
|
-
// it (CI often sets `error`) leaves a stopped deploy with no diagnosable cause.
|
|
33
|
+
// `error`, not `warn`: this line is the ONLY evidence of the failure — the caller's gate says "see the
|
|
34
|
+
// output above", and above it the host CLI printed success.
|
|
46
35
|
log.error(`[fastagent] ${bin} exited 0 without reading all of its input — ${stdinError?.message}`);
|
|
47
36
|
}
|
|
48
37
|
res({ code: truncated ? 1 : (code ?? 1), stdout: out, stderr: opts?.captureStderr ? err : undefined });
|
package/dist/deploy/secrets.d.ts
CHANGED
|
@@ -1,63 +1,62 @@
|
|
|
1
|
-
import type { DeclaredChannel } from "../channels/discover.ts";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
2
|
+
* The environment a deployed agent runs with — host-neutral.
|
|
3
|
+
*
|
|
4
|
+
* The value file (`.secrets/.env`) IS the deployed environment: every variable in it travels, on every host. What the
|
|
5
|
+
* definition declares (`defineTool`/`defineChannel`/`defineRoutine({ secrets })`, plus the model's env key) only says
|
|
6
|
+
* which of those must have a value, so a missing one stops a deploy before its first side effect instead of a boot.
|
|
7
7
|
*/
|
|
8
|
+
import { type DeclaredSecret } from "../declared-secrets.ts";
|
|
9
|
+
/** Is this local auth source an env-var API key (→ becomes a deploy secret) vs OAuth / stored / none? */
|
|
8
10
|
export declare function isEnvKey(source: string | undefined): source is string;
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
* secrets, and config extras. Channel metadata keeps optional values visible without presenting them as
|
|
12
|
-
* deployment prerequisites. An OAuth/stored login has no env key here — it carries as
|
|
13
|
-
* `FASTAGENT_AUTH_SEED` on the `--run` path (see each host's run module), not as a named runbook secret.
|
|
14
|
-
*/
|
|
15
|
-
export declare function deploymentSecrets(modelAuth: string | undefined, channels: readonly DeclaredChannel[], extraSecrets?: string[]): {
|
|
11
|
+
/** One variable a runbook tells the operator to set, and where to look for its value. */
|
|
12
|
+
export interface DeploymentSecret {
|
|
16
13
|
name: string;
|
|
17
14
|
hint: string;
|
|
18
|
-
|
|
19
|
-
}[];
|
|
15
|
+
}
|
|
20
16
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
17
|
+
* What a runbook lists: the names that must have a value (the model's env key, every declaration — hinted by the file
|
|
18
|
+
* that declared it) and then every other variable the value file carries.
|
|
19
|
+
*/
|
|
20
|
+
export declare function deploymentSecrets(modelAuth: string | undefined, declared: readonly DeclaredSecret[], values: ReadonlyMap<string, string>, valueFile: string): DeploymentSecret[];
|
|
21
|
+
/**
|
|
22
|
+
* Assemble the VALUES a `--run` deploy sets on the host: every variable the value file carries, plus the local
|
|
23
|
+
* credential. Never minted here.
|
|
28
24
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* required value enters `missingSecrets`; an absent optional value is simply omitted.
|
|
25
|
+
* `values` is the selected value file, read as data — **the environment running `deploy` is not a source**. A
|
|
26
|
+
* deployment must be reproducible from what it carries, and a variable that happens to be exported on the builder is
|
|
27
|
+
* written down nowhere. CI supplies values by writing the file before running the command.
|
|
33
28
|
*/
|
|
34
29
|
export declare function assembleSecrets(input: {
|
|
35
30
|
modelAuth: string | undefined;
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
31
|
+
/**
|
|
32
|
+
* The definition carries the model key itself (a models.json literal `apiKey` / `!command`): there is no value to
|
|
33
|
+
* carry and no gate to raise.
|
|
34
|
+
*/
|
|
38
35
|
modelKeyInDefinition?: boolean;
|
|
39
36
|
authFile: Buffer | undefined;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
/** Every name the definition declared it needs — what must have a value. */
|
|
38
|
+
declared?: readonly DeclaredSecret[];
|
|
39
|
+
/** The selected value file's contents (`loadEnvValues`). The ONLY source of operator-supplied values. */
|
|
40
|
+
values: ReadonlyMap<string, string>;
|
|
44
41
|
}): {
|
|
45
42
|
secrets: Record<string, string>;
|
|
46
43
|
missingSecrets: string[];
|
|
47
44
|
needsModelCredential: boolean;
|
|
48
45
|
};
|
|
49
46
|
/**
|
|
50
|
-
* The
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* that ran its own OAuth refresh is not rolled back.
|
|
47
|
+
* The ONE refusal for declared names the deployed environment does not supply a value for — every host reaches it
|
|
48
|
+
* before its first side effect. Host-neutral because the reason is: the value file IS the deployed environment, so a
|
|
49
|
+
* name missing from it is missing from the deployment, whatever platform receives it.
|
|
54
50
|
*/
|
|
51
|
+
export declare function missingValuesGate(missing: readonly string[], valueFile: string): string | undefined;
|
|
52
|
+
/** The bytes to seed to the auth file, or undefined to leave it alone. */
|
|
55
53
|
export declare function authSeedBytes(seed: string | undefined, fileExists: boolean): Buffer | undefined;
|
|
54
|
+
/** `FASTAGENT_AUTH_SEED` (+ `_2`, …): the base64 auth.json a deploy carries. */
|
|
55
|
+
export declare function collectAuthSeed(env: NodeJS.ProcessEnv): string | undefined;
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* length (AgentCore: 2048 chars — a real OAuth auth.json's base64 exceeds it) split the seed across
|
|
60
|
-
* them at deploy time; single-var hosts (Fly/Railway) never set a continuation and are unchanged.
|
|
61
|
-
* Collection stops at the first absent/empty continuation — the writer fills them contiguously.
|
|
57
|
+
* Expand `FASTAGENT_ENV` into the process environment — what `start` does first on a host that carries the value file
|
|
58
|
+
* that way. The platform's own variables still win, as they do over a value file anywhere.
|
|
62
59
|
*/
|
|
63
|
-
export declare function
|
|
60
|
+
export declare function applyCarriedEnv(env?: NodeJS.ProcessEnv): void;
|
|
61
|
+
/** The carried variables as the base64 JSON {@link collectCarriedEnv} reads back; empty when nothing travels. */
|
|
62
|
+
export declare function encodeCarriedEnv(carried: Readonly<Record<string, string>>): string;
|