@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
package/dist/cli/commands/add.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `fastagent add <channel>|skill` — scaffold channel glue (`channels/<kind>.ts`) or vendor an Agent
|
|
3
|
-
*
|
|
2
|
+
* `fastagent add <channel>|skill` — scaffold channel glue (`channels/<kind>.ts`) or vendor an Agent Skills skill.
|
|
3
|
+
* slack/feishu/lark additionally CREATE OR RESUME the platform app.
|
|
4
4
|
*/
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
7
|
import { join, relative, resolve } from "node:path";
|
|
8
8
|
import { isCancel, select } from "@clack/prompts";
|
|
9
9
|
import { onboardFeishuCloudApp } from "../add-feishu.js";
|
|
10
|
-
import { dotEnvPath,
|
|
10
|
+
import { dotEnvPath, enterAgentEnv } from "../../env.js";
|
|
11
11
|
import { resolveStateRoot, SECRETS_DIRNAME, isUnderDir, displayPath } from "../../paths.js";
|
|
12
12
|
import { detectRuntime, readPackageJson } from "../../runtime.js";
|
|
13
13
|
import { appendChannelDotEnv, appendChannelEnv, assertChannelReady, channelExists, channelSetup, scaffoldChannel, scaffoldCompanionTools, } from "../../scaffold/add-channel.js";
|
|
@@ -15,27 +15,20 @@ import { vendorSkill } from "../../scaffold/vendor-skill.js";
|
|
|
15
15
|
import { failStartup, failUsage, placementOrExit } from "../fail.js";
|
|
16
16
|
/** `fastagent add <kind> [dir]`: scaffold `channels/<kind>.ts` — the adapter import plus a starter `on()`. */
|
|
17
17
|
export async function runAddChannel(channelKind, dirArg, opts) {
|
|
18
|
-
// The channel (glue + companion tool + secrets) is agent surface — everything lands in the
|
|
19
|
-
//
|
|
20
|
-
// Flag conflicts first: a bad combination is a USAGE error (exit 2), and reporting it must not depend
|
|
21
|
-
// on the directory being an agent (a runtime/environment failure, exit 1) — the same order start/tool
|
|
22
|
-
// follow.
|
|
18
|
+
// The channel (glue + companion tool + secrets) is agent surface — everything lands in the AGENT DIR
|
|
19
|
+
// (`fastagent/`), the same place dev/start discover channels/.
|
|
23
20
|
if (opts.replaceConfig && opts.onboard === false) {
|
|
24
21
|
failUsage("--replace-config replaces onboarding credentials; it cannot be combined with --no-onboard");
|
|
25
22
|
}
|
|
26
23
|
const { agentDir: target } = placementOrExit(resolve(dirArg));
|
|
27
|
-
|
|
28
|
-
// Paths are printed to someone standing in their CWD, usually the workspace, while every file
|
|
29
|
-
//
|
|
30
|
-
// relative-vs-absolute policy (shared with `init`). The `.env` label names the file actually
|
|
31
|
-
// WRITTEN (FASTAGENT_SECRETS_DIR relocates it, possibly out of the agent), never the default spelling.
|
|
24
|
+
enterAgentEnv(target); // onboarding state follows the same FASTAGENT_STATE_DIR as serving/deploy
|
|
25
|
+
// Paths are printed to someone standing in their CWD, usually the workspace, while every file belongs to the AGENT
|
|
26
|
+
// dir — prefix them, or they point at nothing.
|
|
32
27
|
const agentFromCwd = displayPath(process.cwd(), target);
|
|
33
28
|
const inAgent = (p) => (agentFromCwd === undefined ? p : join(agentFromCwd, p));
|
|
34
29
|
const envPath = dotEnvPath(target);
|
|
35
30
|
const envLabel = isUnderDir(envPath, target) ? inAgent(relative(target, envPath)) : envPath;
|
|
36
|
-
// An existing channel file is authored glue: kept, never rewritten.
|
|
37
|
-
// companion tools below are refreshed, and slack/feishu/lark resume a failed/cancelled app or OAuth
|
|
38
|
-
// flow — so re-running `add <kind>` never requires hand-deleting the glue.
|
|
31
|
+
// An existing channel file is authored glue: kept, never rewritten.
|
|
39
32
|
const file = join(target, "channels", `${channelKind}.ts`);
|
|
40
33
|
const existsAlready = await channelExists(target, channelKind).catch(failStartup);
|
|
41
34
|
const ingress = await resolveIngress(channelKind, file, existsAlready, opts.ingress);
|
|
@@ -48,16 +41,14 @@ export async function runAddChannel(channelKind, dirArg, opts) {
|
|
|
48
41
|
await scaffoldChannel(target, channelKind, { ingress }).catch(failStartup);
|
|
49
42
|
console.error(`[fastagent] created ${relative(target, file)}`);
|
|
50
43
|
}
|
|
51
|
-
// Companion tools are the package's, not authored glue
|
|
52
|
-
// package reaches an existing agent by re-running `add <kind>` (slack: `--no-onboard` skips the prompts).
|
|
44
|
+
// Companion tools are the package's, not authored glue.
|
|
53
45
|
for (const tool of await scaffoldCompanionTools(target, channelKind).catch(failStartup)) {
|
|
54
46
|
console.error(`[fastagent] wrote ${relative(target, tool)}`);
|
|
55
47
|
}
|
|
56
48
|
if (await appendChannelEnv(target, channelKind, ingress).catch(failStartup)) {
|
|
57
49
|
console.error(`[fastagent] added ${channelKind} env vars to ${inAgent(join(SECRETS_DIRNAME, ".env.example"))}`);
|
|
58
50
|
}
|
|
59
|
-
// Stateful app onboarding is re-runnable after the scaffold boundary.
|
|
60
|
-
// persists its manifest/OAuth recovery state separately and writes runtime secrets directly.
|
|
51
|
+
// Stateful app onboarding is re-runnable after the scaffold boundary.
|
|
61
52
|
let created;
|
|
62
53
|
if (channelKind === "slack" && opts.onboard !== false) {
|
|
63
54
|
const { onboardSlackInternalApp } = await import("../add-slack.js");
|
|
@@ -77,16 +68,14 @@ export async function runAddChannel(channelKind, dirArg, opts) {
|
|
|
77
68
|
const env = setup.env;
|
|
78
69
|
const steps = channelKind === "slack" && opts.onboard !== false
|
|
79
70
|
? [
|
|
80
|
-
// No line about the Request URL: the `dev --tunnel` line below is the whole instruction, and
|
|
81
|
-
//
|
|
71
|
+
// No line about the Request URL: the `dev --tunnel` line below is the whole instruction, and FastAgent sets
|
|
72
|
+
// that URL itself when the agent first runs.
|
|
82
73
|
"invite the app to each channel it should read",
|
|
83
74
|
"the agent can send messages or files by calling the scaffolded {tools}/slack-send.ts tool",
|
|
84
75
|
]
|
|
85
76
|
: setup.steps;
|
|
86
77
|
const generated = Object.fromEntries(env.filter((e) => e.generate).map((e) => [e.name, randomBytes(24).toString("hex")]));
|
|
87
|
-
// Kind-neutral: every channel's generated secrets get the same treatment
|
|
88
|
-
// the same class of value as telegram's); guided Lark credentials ride the same write as overwrites.
|
|
89
|
-
// Feishu's irreversible credentials were already staged inside add-feishu.ts before bootstrap.
|
|
78
|
+
// Kind-neutral: every channel's generated secrets get the same treatment.
|
|
90
79
|
const dotEnv = await appendChannelDotEnv(target, channelKind, { ...generated, ...created }, Object.keys(created ?? {}), ingress).catch(failStartup);
|
|
91
80
|
if (dotEnv.unprotectedSecretsDir) {
|
|
92
81
|
console.error(`[fastagent] note: ${dotEnv.unprotectedSecretsDir} (FASTAGENT_SECRETS_DIR) now holds a secret and has ` +
|
|
@@ -96,15 +85,16 @@ export async function runAddChannel(channelKind, dirArg, opts) {
|
|
|
96
85
|
if (dotEnv.written.length > 0) {
|
|
97
86
|
console.error(`[fastagent] wrote ${dotEnv.written.join(", ")} to ${envLabel}`);
|
|
98
87
|
}
|
|
99
|
-
const install = detectRuntime(target, await readPackageJson(target)).runtime === "bun"
|
|
88
|
+
const install = detectRuntime(target, await readPackageJson(target).catch(failStartup)).runtime === "bun"
|
|
89
|
+
? "bun install"
|
|
90
|
+
: "npm install";
|
|
100
91
|
console.error(` next steps:`);
|
|
101
92
|
console.error(` ${agentFromCwd === undefined ? install : `(cd ${agentFromCwd} && ${install})`} # if @fastagent-sh/fastagent is not installed yet`);
|
|
102
93
|
for (const e of env) {
|
|
103
94
|
if (dotEnv.alreadySet.includes(e.name))
|
|
104
95
|
continue; // the user already has it — nothing to do
|
|
105
96
|
if (dotEnv.written.includes(e.name)) {
|
|
106
|
-
// Written, but its hint may still carry an action
|
|
107
|
-
// UI) — keep the variable visible instead of silently absorbing it.
|
|
97
|
+
// Written, but its hint may still carry an action — keep the variable visible instead of silently absorbing it.
|
|
108
98
|
console.error(` ${e.name} — ${e.generate ? "generated and " : ""}written to ${envLabel} # ${e.hint}`);
|
|
109
99
|
continue;
|
|
110
100
|
}
|
|
@@ -112,8 +102,8 @@ export async function runAddChannel(channelKind, dirArg, opts) {
|
|
|
112
102
|
const action = e.required ? "set" : "optionally set";
|
|
113
103
|
console.error(` ${action} ${e.name}${value} in ${envLabel} # ${e.hint}`);
|
|
114
104
|
}
|
|
115
|
-
// Steps carry `{channel}`/`{tools}` path placeholders (their filenames are the scaffold's private
|
|
116
|
-
//
|
|
105
|
+
// Steps carry `{channel}`/`{tools}` path placeholders (their filenames are the scaffold's private knowledge) —
|
|
106
|
+
// resolve them to the real agent-dir-relative locations here.
|
|
117
107
|
for (const s of steps) {
|
|
118
108
|
console.error(` ${s.replace("{channel}", inAgent(relative(target, file))).replace("{tools}", inAgent("tools"))}`);
|
|
119
109
|
}
|
|
@@ -123,14 +113,11 @@ export async function runAddChannel(channelKind, dirArg, opts) {
|
|
|
123
113
|
else if (channelKind === "slack") {
|
|
124
114
|
console.error(` fastagent dev --tunnel # ${opts.onboard === false ? "serve locally + print the URL to paste into the Slack app's Event Subscriptions" : "start the agent — then message it in Slack"}`);
|
|
125
115
|
}
|
|
126
|
-
else if (channelKind === "github") {
|
|
127
|
-
console.error(` fastagent dev --tunnel # serve locally + print the URL for manual GitHub webhook setup`);
|
|
128
|
-
}
|
|
129
116
|
else if (channelKind !== "lark") {
|
|
130
117
|
console.error(` fastagent dev --tunnel # serve locally + a public URL, auto-registering the webhook`);
|
|
131
118
|
}
|
|
132
|
-
// App-creation flows leave platform/tunnel sockets behind that would otherwise hold the one-shot
|
|
133
|
-
//
|
|
119
|
+
// App-creation flows leave platform/tunnel sockets behind that would otherwise hold the one-shot scaffold command
|
|
120
|
+
// open after all durable boundaries have completed.
|
|
134
121
|
process.exit(0);
|
|
135
122
|
}
|
|
136
123
|
async function resolveIngress(kind, file, existsAlready, raw) {
|
|
@@ -167,9 +154,7 @@ async function resolveIngress(kind, file, existsAlready, raw) {
|
|
|
167
154
|
}
|
|
168
155
|
const answer = await select({
|
|
169
156
|
message: `How should ${kind === "feishu" ? "Feishu" : "Lark"} deliver events?`,
|
|
170
|
-
// The default must match the non-interactive branch above.
|
|
171
|
-
// wrong on: its credentials are a superset of websocket's (App ID/Secret plus the Verification
|
|
172
|
-
// Token), and that token has no read API — a websocket app moving to webhook must re-acquire it.
|
|
157
|
+
// The default must match the non-interactive branch above.
|
|
173
158
|
initialValue: "webhook",
|
|
174
159
|
options: [
|
|
175
160
|
{
|
|
@@ -228,8 +213,7 @@ async function resolveGroupBehavior(kind, raw) {
|
|
|
228
213
|
export async function runAddSkill(source, dirArg, opts) {
|
|
229
214
|
const { agentDir: target } = placementOrExit(resolve(dirArg));
|
|
230
215
|
if (!source) {
|
|
231
|
-
// A missing source is a usage error (exit 2), but the guide is worth more than a bare
|
|
232
|
-
// missing-argument line — the common path (writing your own skill) needs no command at all.
|
|
216
|
+
// A missing source is a usage error (exit 2), but the guide is worth more than a bare missing-argument line.
|
|
233
217
|
failUsage(`add a skill — two ways:\n` +
|
|
234
218
|
` 1. write your own (vibe): create skills/<name>/SKILL.md with name + description\n` +
|
|
235
219
|
` frontmatter; it's auto-discovered. No command needed — this is the common path.\n` +
|
|
@@ -240,6 +224,7 @@ export async function runAddSkill(source, dirArg, opts) {
|
|
|
240
224
|
` --update overwrites an existing skill (re-fetch from source); review with git diff`);
|
|
241
225
|
}
|
|
242
226
|
// Skills are agent surface — vendored into the agent dir's `skills/`.
|
|
227
|
+
enterAgentEnv(target); // a git-ref source is a network fetch (giget uses global fetch)
|
|
243
228
|
const { name, description, dest, hasScripts, diagnostics, overwritten } = await vendorSkill(target, source, {
|
|
244
229
|
update: opts.update ?? false,
|
|
245
230
|
}).catch(failStartup);
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
import { failStartup } from "../fail.js";
|
|
3
3
|
import { enterAgentCommand } from "../shared.js";
|
|
4
4
|
export async function runChat(dirArg, opts) {
|
|
5
|
-
// Chat authenticates through fastagent's credential store like every other command (the shared
|
|
6
|
-
//
|
|
7
|
-
// inline login apply here too.
|
|
5
|
+
// Chat authenticates through fastagent's credential store like every other command (the shared session builder
|
|
6
|
+
// injects it — see engines/pi/session-builder.ts), so the first-run picker and its inline login apply here too.
|
|
8
7
|
const placement = await enterAgentCommand(dirArg, opts);
|
|
9
|
-
// Run the chat process AT the workspace: pi resolves a session's cwd as `header.cwd ?? process.cwd()`,
|
|
10
|
-
//
|
|
8
|
+
// Run the chat process AT the workspace: pi resolves a session's cwd as `header.cwd ?? process.cwd()`, so aligning
|
|
9
|
+
// process.cwd() with the workspace keeps a cwd-less session on it.
|
|
11
10
|
process.chdir(placement.workspace);
|
|
12
11
|
// Lazy-import: chat pulls pi's interactive TUI module graph; headless start/dev never need it.
|
|
13
12
|
const { runPiChat } = await import("../../engines/pi/chat.js");
|
|
14
|
-
await runPiChat(placement.
|
|
13
|
+
await runPiChat(placement.agentDir, { model: opts.model }).catch(failStartup);
|
|
15
14
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `deploy agentcore`: one CloudFormation stack (runtime + forwarder Lambda + EventBridge
|
|
3
|
-
*
|
|
2
|
+
* `deploy agentcore`: one CloudFormation stack (runtime + forwarder Lambda behind a Function URL + EventBridge
|
|
3
|
+
* schedules); no resident process.
|
|
4
4
|
*/
|
|
5
5
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { basename, join } from "node:path";
|
|
8
|
-
import {
|
|
9
|
-
import { FORWARDER_FILE, TEMPLATE_FILE, agentcoreName, isGeneratedAgentcoreTemplate, planAgentcoreDeploy, } from "../../../deploy/agentcore/plan.js";
|
|
8
|
+
import { FORWARDER_FILE, TEMPLATE_FILE, agentcoreName, forwarderLogGroup, ingressSessionId, isGeneratedAgentcoreTemplate, planAgentcoreDeploy, } from "../../../deploy/agentcore/plan.js";
|
|
10
9
|
import { deployAgentcoreRun } from "../../../deploy/agentcore/run.js";
|
|
11
10
|
import { spawnRunner } from "../../../deploy/runner.js";
|
|
12
|
-
import { exists } from "../../../paths.js";
|
|
13
|
-
import {
|
|
11
|
+
import { SECRET_FILE_MODE, exists } from "../../../paths.js";
|
|
12
|
+
import { loadRoutines } from "../../../schedule/discover.js";
|
|
14
13
|
import { failStartup } from "../../fail.js";
|
|
15
14
|
import { carryCredentials, gateOnModelCredential, registrarsFor } from "./shared.js";
|
|
16
15
|
/** A copy/paste-safe POSIX shell argument for the command hints deploy prints. */
|
|
@@ -21,10 +20,9 @@ export const agentcoreHost = {
|
|
|
21
20
|
isOurs: (path, content) => path.endsWith(TEMPLATE_FILE) && isGeneratedAgentcoreTemplate(content),
|
|
22
21
|
async deploy(ctx) {
|
|
23
22
|
const { opts, agentDir, workspace, config, channels, longConnectionChannels, pre, write } = ctx;
|
|
24
|
-
const { modelAuth, modelKeyInDefinition, authPath, container,
|
|
25
|
-
// Long-connection channels are STRUCTURALLY unsupported: the connection is the ingress, and a
|
|
26
|
-
//
|
|
27
|
-
// (deploying a channel that can't connect); generate-only warns and prints the runbook.
|
|
23
|
+
const { modelAuth, modelKeyInDefinition, authPath, container, declaredSecrets, values, valueFile } = pre;
|
|
24
|
+
// Long-connection channels are STRUCTURALLY unsupported: the connection is the ingress, and a reclaimed session
|
|
25
|
+
// has nothing to wake it.
|
|
28
26
|
if (longConnectionChannels.length > 0) {
|
|
29
27
|
const msg = `long-connection channel (${longConnectionChannels.map((c) => c.name).join(", ")}) cannot run on AgentCore — there is no ` +
|
|
30
28
|
`resident process to hold the connection, and nothing wakes a reclaimed session. Switch the channel ` +
|
|
@@ -33,18 +31,23 @@ export const agentcoreHost = {
|
|
|
33
31
|
failStartup(new Error(`deploy stopped: ${msg}`));
|
|
34
32
|
console.error(`[fastagent] warn: ${msg}`);
|
|
35
33
|
}
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
// Host capability limit, stated before the image is built rather than left to a line in CloudWatch.
|
|
35
|
+
if (config.sessionControl === true) {
|
|
36
|
+
console.error(`[fastagent] warn: sessionControl: true has no effect on AgentCore — /control/* is not served here. This ` +
|
|
37
|
+
`host's only public ingress relays anonymous traffic to the container as trusted (the forwarder holds the ` +
|
|
38
|
+
`ingress secret and attaches it), and the plane has nothing of its own to verify. Steer sessions from a ` +
|
|
39
|
+
`host that serves it behind your own auth (docs/design/session-control.md §14).`);
|
|
40
|
+
}
|
|
41
|
+
// Wake-ups are a default capability here as everywhere: pending ones are mirrored into one-shot EventBridge
|
|
42
|
+
// schedules via the forwarder (the wake-alarm mechanism — see deploy/agentcore/plan.ts), which is why every stack
|
|
43
|
+
// has one.
|
|
44
|
+
const loaded = await loadRoutines(agentDir).catch(failStartup);
|
|
41
45
|
if (loaded.failures.length > 0) {
|
|
42
|
-
failStartup(new Error(`deploy stopped: cannot load
|
|
46
|
+
failStartup(new Error(`deploy stopped: cannot load routines: ${loaded.failures.map((x) => `${x.label}: ${x.message}`).join("; ")}`));
|
|
43
47
|
}
|
|
44
48
|
const acName = agentcoreName(basename(workspace));
|
|
45
49
|
// Every derived AWS name embeds acName; the tightest ceiling is the Lambda function name
|
|
46
|
-
// (`fastagent-<name>-forwarder` ≤ 64 chars).
|
|
47
|
-
// truncation would break the redeploy identity (a renamed stack starts blank state).
|
|
50
|
+
// (`fastagent-<name>-forwarder` ≤ 64 chars).
|
|
48
51
|
if (acName.length > 40) {
|
|
49
52
|
failStartup(new Error(`deploy stopped: the directory name maps to "${acName}" (${acName.length} chars) — AWS resource ` +
|
|
50
53
|
`names derived from it exceed their limits past 40 chars. Deploy from a shorter directory name.`));
|
|
@@ -53,42 +56,35 @@ export const agentcoreHost = {
|
|
|
53
56
|
name: acName,
|
|
54
57
|
modelAuth,
|
|
55
58
|
channels,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
secrets: pre.secrets,
|
|
60
|
+
// ONLY THE ONES WITH A CRON become rules. A routine without one is reached by NAME, through this host's
|
|
61
|
+
// IAM-gated `routine-run` envelope (channels/agentcore.ts) rather than a route — so it needs no rule and
|
|
62
|
+
// loses nothing by not having one.
|
|
63
|
+
schedules: loaded.routines.flatMap((r) => r.cron === undefined ? [] : [{ name: r.name, cron: r.cron, ...(r.tz !== undefined ? { tz: r.tz } : {}) }]),
|
|
64
|
+
idleTimeoutSeconds: config.deploy?.agentcore?.idleTimeoutSeconds,
|
|
59
65
|
...container,
|
|
60
66
|
});
|
|
61
67
|
for (const u of plan.untranslatableSchedules) {
|
|
62
|
-
// Same discipline as Fly's kept-toml time-trigger gate: a deploy whose schedule silently never
|
|
63
|
-
//
|
|
68
|
+
// Same discipline as Fly's kept-toml time-trigger gate: a deploy whose schedule silently never fires is worse
|
|
69
|
+
// than a stopped deploy.
|
|
64
70
|
const msg = `schedule "${u.name}" cannot be expressed as an EventBridge rule — ${u.reason}`;
|
|
65
71
|
if (opts.run)
|
|
66
72
|
failStartup(new Error(`deploy stopped: ${msg}`));
|
|
67
73
|
console.error(`[fastagent] warn: ${msg} — it will NOT fire on this deployment`);
|
|
68
74
|
}
|
|
69
|
-
//
|
|
70
|
-
// Lambda Function URL request caps at 6 MB — so on this host a large payload cannot arrive at
|
|
71
|
-
// all. Better a sentence here than an opaque 502 the first time someone pushes a big diff.
|
|
72
|
-
if (channels.some((channel) => channel.name === "github")) {
|
|
73
|
-
console.error(`[fastagent] note: on AgentCore a webhook body is capped at ~${Math.round(MAX_WEBHOOK_BODY_BYTES / (1 << 20))} MiB ` +
|
|
74
|
-
`(the forwarder's Function URL limit); the GitHub channel accepts 25 MiB on a resident host, so the largest ` +
|
|
75
|
-
`payloads are rejected here rather than delivered`);
|
|
76
|
-
}
|
|
77
|
-
// The template IS the topology (EventBridge rules, wake wiring, secrets) — a kept generated
|
|
78
|
-
// template that no longer matches the definition would deploy a stack silently missing the
|
|
79
|
-
// difference (a new schedule with no rule never fires: the exact miss the gate above stops).
|
|
80
|
-
// A hand-written template (marker removed) is the operator's own — kept, never gated.
|
|
75
|
+
// The template IS the topology (EventBridge rules, wake wiring, secrets).
|
|
81
76
|
const templateArtifact = plan.artifacts.find((a) => a.path.endsWith(TEMPLATE_FILE));
|
|
82
77
|
const templateHome = join(workspace, templateArtifact?.path ?? TEMPLATE_FILE);
|
|
83
78
|
if (!opts.force && templateArtifact && (await exists(templateHome))) {
|
|
84
79
|
const existing = await readFile(templateHome, "utf8");
|
|
85
80
|
if (isGeneratedAgentcoreTemplate(existing) && existing !== templateArtifact.content) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
// WHY it matters here, not WHETHER to stop: the template IS the topology, so a kept one drops the
|
|
82
|
+
// difference rather than degrading visibly. Stopping a `--run` on a stale artifact is the dispatcher's
|
|
83
|
+
// rule (deploy.ts) for every host and every path — stating it a second time here is how the two
|
|
84
|
+
// wordings drift apart.
|
|
85
|
+
console.error(`[fastagent] warn: ${templateArtifact.path} no longer matches this definition ` +
|
|
86
|
+
`(channels/routines or a deploy.agentcore setting changed) — the kept template would ` +
|
|
87
|
+
`silently drop the difference.`);
|
|
92
88
|
}
|
|
93
89
|
}
|
|
94
90
|
await write(plan.artifacts, {
|
|
@@ -105,7 +101,9 @@ export const agentcoreHost = {
|
|
|
105
101
|
modelKeyInDefinition,
|
|
106
102
|
authPath,
|
|
107
103
|
channels,
|
|
108
|
-
|
|
104
|
+
declaredSecrets,
|
|
105
|
+
values,
|
|
106
|
+
valueFile,
|
|
109
107
|
topology: plan.topology,
|
|
110
108
|
});
|
|
111
109
|
}
|
|
@@ -113,33 +111,23 @@ export const agentcoreHost = {
|
|
|
113
111
|
return;
|
|
114
112
|
},
|
|
115
113
|
};
|
|
116
|
-
/**
|
|
117
|
-
* `deploy agentcore --run`: drive aws + docker to completion. Mirrors the fly driver — same
|
|
118
|
-
* credential carry via {@link carryCredentials}, same runner seam (spawned `aws` + `docker`, cwd = the
|
|
119
|
-
* workspace so the build context is the agent). The AgentCore-specific sequence (identity → buildx →
|
|
120
|
-
* ECR → CloudFormation → outputs → webhooks) lives in {@link deployAgentcoreRun}; the params temp
|
|
121
|
-
* file (secret values off argv) is created here — 0600, removed after the run either way.
|
|
122
|
-
*/
|
|
114
|
+
/** `deploy agentcore --run`: drive aws + docker to completion. */
|
|
123
115
|
async function runDeployAgentcore(params) {
|
|
124
116
|
const { agentDir, workspace, agentPrefix, name, channels, topology } = params;
|
|
125
117
|
const { secrets, missingSecrets, needsModelCredential } = await carryCredentials(params);
|
|
126
|
-
// The wake-alarm shared secret (container ↔ forwarder).
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// The forwarder→runtime ingress secret: what makes an envelope the forwarder's rather than any IAM
|
|
131
|
-
// principal's. Minted fresh each run — both sides receive the SAME parameter, so rotation is atomic.
|
|
132
|
-
if (topology.forwarder)
|
|
133
|
-
secrets.FASTAGENT_INGRESS_SECRET = crypto.randomUUID();
|
|
118
|
+
// The wake-alarm shared secret (container ↔ forwarder).
|
|
119
|
+
secrets.FASTAGENT_WAKE_SECRET = crypto.randomUUID();
|
|
120
|
+
// The forwarder→runtime ingress secret: what makes an envelope the forwarder's rather than any IAM principal's.
|
|
121
|
+
secrets.FASTAGENT_INGRESS_SECRET = crypto.randomUUID();
|
|
134
122
|
gateOnModelCredential(needsModelCredential);
|
|
135
|
-
// The params temp dir holds the ONE file carrying secret values (file:// parameter-overrides —
|
|
136
|
-
//
|
|
123
|
+
// The params temp dir holds the ONE file carrying secret values (file:// parameter-overrides — never argv);
|
|
124
|
+
// 0700/0600 and removed after the run, success or gate.
|
|
137
125
|
const paramsDir = await mkdtemp(join(tmpdir(), "fastagent-agentcore-"));
|
|
138
126
|
try {
|
|
139
127
|
const outcome = await deployAgentcoreRun({
|
|
140
128
|
name,
|
|
141
129
|
templatePath: `${agentPrefix}${TEMPLATE_FILE}`,
|
|
142
|
-
dockerfilePath:
|
|
130
|
+
dockerfilePath: `${agentPrefix}Dockerfile`,
|
|
143
131
|
tag: new Date()
|
|
144
132
|
.toISOString()
|
|
145
133
|
.replace(/[-:.TZ]/g, "")
|
|
@@ -147,11 +135,12 @@ async function runDeployAgentcore(params) {
|
|
|
147
135
|
region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION,
|
|
148
136
|
secrets,
|
|
149
137
|
missingSecrets,
|
|
138
|
+
valueFile: params.valueFile,
|
|
150
139
|
channels,
|
|
151
140
|
topology,
|
|
152
141
|
}, spawnRunner("aws", workspace), spawnRunner("docker", workspace), (m) => console.error(`[fastagent] ${m}`), async (content) => {
|
|
153
142
|
const path = join(paramsDir, "params.json");
|
|
154
|
-
await writeFile(path, content, { mode:
|
|
143
|
+
await writeFile(path, content, { mode: SECRET_FILE_MODE });
|
|
155
144
|
return path;
|
|
156
145
|
}, async (bytes) => {
|
|
157
146
|
const path = join(paramsDir, "forwarder.zip");
|
|
@@ -165,11 +154,17 @@ async function runDeployAgentcore(params) {
|
|
|
165
154
|
console.error(`[fastagent] webhook ingress → ${outcome.url}`);
|
|
166
155
|
const logsDir = shellArg(workspace);
|
|
167
156
|
console.error(`[fastagent] runtime logs → fastagent logs agentcore ${logsDir} --follow`);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
157
|
+
console.error(`[fastagent] forwarder logs → fastagent logs agentcore ${logsDir} --source forwarder --follow`);
|
|
158
|
+
// `--run` never prints the runbook, and this is the ONE step in it that nothing else will remind anyone of: a
|
|
159
|
+
// log group is created by whatever writes it, CloudWatch keeps log data indefinitely, and these logs are where
|
|
160
|
+
// a failed turn's reason lives. The runtime's group name is only known after discovery, so it is named by the command that
|
|
161
|
+
// resolves it — spelled out, because the line above it may be the forwarder's.
|
|
162
|
+
console.error(`[fastagent] logs are kept FOREVER until you say otherwise: aws logs put-retention-policy ` +
|
|
163
|
+
`--log-group-name <the group \`fastagent logs agentcore ${logsDir} --follow\` resolves> --retention-in-days 14`);
|
|
164
|
+
console.error(`[fastagent] ...and for the forwarder: aws logs put-retention-policy ` +
|
|
165
|
+
`--log-group-name ${forwarderLogGroup(name)} --retention-in-days 14`);
|
|
171
166
|
console.error(`[fastagent] invoke: aws bedrock-agentcore invoke-agent-runtime --agent-runtime-arn ${outcome.runtimeArn} \\\n` +
|
|
172
|
-
` --runtime-session-id "
|
|
167
|
+
` --runtime-session-id "${ingressSessionId(name)}" \\\n` +
|
|
173
168
|
` --payload '{"kind":"invoke","session":"cli","text":"hello"}' --cli-binary-format raw-in-base64-out /dev/stdout`);
|
|
174
169
|
}
|
|
175
170
|
finally {
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* decides whether Docker receives side effects.
|
|
5
|
-
*/
|
|
6
|
-
import { basename, join } from "node:path";
|
|
1
|
+
/** `deploy docker`: one app service + loopback port + state volume, as a user-owned Compose file. */
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
7
4
|
import { webhookPaths } from "../../../deploy/channel-ingress.js";
|
|
8
5
|
import { composeHasTunnelService, isGeneratedCompose, planDockerDeploy, toDockerProjectName, } from "../../../deploy/docker/plan.js";
|
|
9
6
|
import { deployDockerRun } from "../../../deploy/docker/run.js";
|
|
10
7
|
import { spawnRunner } from "../../../deploy/runner.js";
|
|
11
8
|
import { openExternalUrl } from "../../../open-url.js";
|
|
12
|
-
import { readTextIfExists, resolveStateRoot } from "../../../paths.js";
|
|
9
|
+
import { SECRETS_DIRNAME, SECRET_FILE_MODE, exists, readTextIfExists, resolveStateRoot, } from "../../../paths.js";
|
|
10
|
+
import { dotEnvPath } from "../../../env.js";
|
|
13
11
|
import { announceWebhooks } from "../../../tunnel.js";
|
|
14
12
|
import { failStartup } from "../../fail.js";
|
|
15
13
|
import { carryCredentials } from "./shared.js";
|
|
@@ -17,7 +15,37 @@ export const dockerHost = {
|
|
|
17
15
|
isOurs: (path, content) => path.endsWith("fastagent.compose.yml") && isGeneratedCompose(content),
|
|
18
16
|
async deploy(ctx) {
|
|
19
17
|
const { opts, agentDir, workspace, channels, webhookChannels, pre, write } = ctx;
|
|
20
|
-
const { modelAuth, modelKeyInDefinition, authPath, container, port,
|
|
18
|
+
const { modelAuth, modelKeyInDefinition, authPath, container, port, declaredSecrets, values, valueFile } = pre;
|
|
19
|
+
// The generated Compose names `<agent>/.secrets/.env` unconditionally, so it has to be there — Compose refuses
|
|
20
|
+
// an `env_file` entry pointing at a missing path, and this floor predates `required: false` (Compose 2.24).
|
|
21
|
+
// Creating it empty is honest: a deployment that declares nothing declares it in an empty file.
|
|
22
|
+
const composeValueFile = join(agentDir, SECRETS_DIRNAME, ".env");
|
|
23
|
+
await mkdir(dirname(composeValueFile), { recursive: true });
|
|
24
|
+
if (!(await exists(composeValueFile)))
|
|
25
|
+
await writeFile(composeValueFile, "", { mode: SECRET_FILE_MODE });
|
|
26
|
+
// The pre-flight read whatever `FASTAGENT_SECRETS_DIR` resolved to; the committed Compose cannot, because a
|
|
27
|
+
// builder's path would not mean the same thing anywhere else. Under `--run` the two files disagreeing is a
|
|
28
|
+
// DETERMINISTIC failure and gates like every other one: the missing-values gate would pass on the file this
|
|
29
|
+
// machine reads while `compose up` starts a container whose declared secrets and model are all absent.
|
|
30
|
+
if (resolve(dotEnvPath(agentDir)) !== resolve(composeValueFile)) {
|
|
31
|
+
const issue = `FASTAGENT_SECRETS_DIR points this run's values at ${valueFile}, but the generated Compose reads ` +
|
|
32
|
+
`${relative(workspace, composeValueFile)} (a committed artifact cannot carry this machine's path), so the ` +
|
|
33
|
+
`container would start with none of them. Unset FASTAGENT_SECRETS_DIR, or put the values in that file.`;
|
|
34
|
+
if (opts.run)
|
|
35
|
+
failStartup(new Error(`deploy stopped: ${issue}`));
|
|
36
|
+
console.error(`[fastagent] warn: ${issue}`);
|
|
37
|
+
}
|
|
38
|
+
// Compose interpolates `$VAR` INSIDE env_file values (`format: raw` needs Compose 2.30), so a credential
|
|
39
|
+
// containing `$` reaches the container rewritten — silently, and differently from what this pre-flight read.
|
|
40
|
+
// No escaping advice: this ONE file is also read literally by `dev`/`start` (`parseEnvContent`) and pushed
|
|
41
|
+
// as-is by every other host, so `$$` would fix Docker by corrupting all of them.
|
|
42
|
+
const dollarValues = [...values].filter(([, value]) => value.includes("$")).map(([name]) => name);
|
|
43
|
+
if (dollarValues.length > 0) {
|
|
44
|
+
console.error(`[fastagent] warn: ${dollarValues.join(", ")} contain "$", which Compose expands when reading ${valueFile} ` +
|
|
45
|
+
`(an undefined name becomes empty), so the container sees a different value. "$$" escapes it for Compose ` +
|
|
46
|
+
`ONLY — \`fastagent dev\`/\`start\` and the other hosts read this file literally and would keep the extra ` +
|
|
47
|
+
`"$". Prefer a value without "$".`);
|
|
48
|
+
}
|
|
21
49
|
const hasDeclaredChannels = channels.length > 0;
|
|
22
50
|
const projectName = toDockerProjectName(basename(workspace));
|
|
23
51
|
const dockerPlan = (tunnel) => planDockerDeploy({
|
|
@@ -26,7 +54,8 @@ export const dockerHost = {
|
|
|
26
54
|
modelAuth,
|
|
27
55
|
channels,
|
|
28
56
|
tunnel,
|
|
29
|
-
|
|
57
|
+
secrets: pre.secrets,
|
|
58
|
+
valueFile,
|
|
30
59
|
...container,
|
|
31
60
|
});
|
|
32
61
|
const requestedTunnel = !!opts.tunnel && (!hasDeclaredChannels || webhookChannels.length > 0);
|
|
@@ -34,12 +63,12 @@ export const dockerHost = {
|
|
|
34
63
|
console.error(`[fastagent] note: --tunnel skipped — every channel uses a long connection`);
|
|
35
64
|
}
|
|
36
65
|
let plan = dockerPlan(requestedTunnel);
|
|
37
|
-
// An existing Compose file is authoritative: shape its comparison/runbook from the topology on disk,
|
|
38
|
-
//
|
|
66
|
+
// An existing Compose file is authoritative: shape its comparison/runbook from the topology on disk, regardless
|
|
67
|
+
// of the current flag.
|
|
39
68
|
const composeFile = join(workspace, plan.composePath);
|
|
40
69
|
let keptWithoutRequestedTunnel = false;
|
|
41
|
-
// Same ownership question as fly.toml: a hand-owned compose file survives --force, so the plan must
|
|
42
|
-
//
|
|
70
|
+
// Same ownership question as fly.toml: a hand-owned compose file survives --force, so the plan must describe the
|
|
71
|
+
// topology that will actually be there.
|
|
43
72
|
const composeText = await readTextIfExists(composeFile).catch(failStartup);
|
|
44
73
|
if (composeText !== undefined && (!opts.force || !isGeneratedCompose(composeText))) {
|
|
45
74
|
const existingHasTunnel = composeHasTunnelService(composeText);
|
|
@@ -58,7 +87,9 @@ export const dockerHost = {
|
|
|
58
87
|
modelKeyInDefinition,
|
|
59
88
|
authPath,
|
|
60
89
|
channels,
|
|
61
|
-
|
|
90
|
+
declaredSecrets,
|
|
91
|
+
values,
|
|
92
|
+
valueFile,
|
|
62
93
|
});
|
|
63
94
|
}
|
|
64
95
|
if (keptWithoutRequestedTunnel) {
|
|
@@ -70,9 +101,8 @@ export const dockerHost = {
|
|
|
70
101
|
},
|
|
71
102
|
};
|
|
72
103
|
/**
|
|
73
|
-
* `deploy docker --run`: carry local credentials into Compose's child environment, then reconcile the
|
|
74
|
-
*
|
|
75
|
-
* when present, yields an ephemeral URL that reuses the same webhook announcer as `dev --tunnel`.
|
|
104
|
+
* `deploy docker --run`: carry local credentials into Compose's child environment, then reconcile the user-owned local
|
|
105
|
+
* topology.
|
|
76
106
|
*/
|
|
77
107
|
async function runDeployDocker(params) {
|
|
78
108
|
const { agentDir, workspace, composeFile, port, requireTunnel, channels } = params;
|
|
@@ -82,6 +112,7 @@ async function runDeployDocker(params) {
|
|
|
82
112
|
port,
|
|
83
113
|
secrets,
|
|
84
114
|
missingSecrets,
|
|
115
|
+
valueFile: params.valueFile,
|
|
85
116
|
needsModelCredential,
|
|
86
117
|
requireTunnel,
|
|
87
118
|
announce: (tunnelUrl) => announceWebhooks(agentDir, tunnelUrl, channels, {
|
|
@@ -90,9 +121,7 @@ async function runDeployDocker(params) {
|
|
|
90
121
|
}),
|
|
91
122
|
}, spawnRunner("docker", workspace), (message) => console.error(`[fastagent] ${message}`));
|
|
92
123
|
const compose = `docker compose -f ${composeFile}`;
|
|
93
|
-
// Compose reached "up" iff the driver could report where it is.
|
|
94
|
-
// (no Docker CLI, no daemon, missing secrets) must not be preceded by `docker compose logs`
|
|
95
|
-
// instructions for containers that do not exist; the health-check gate names its own logs command.
|
|
124
|
+
// Compose reached "up" iff the driver could report where it is.
|
|
96
125
|
const isUp = outcome.ok || outcome.url !== undefined || outcome.tunnelUrl !== undefined;
|
|
97
126
|
if (outcome.url)
|
|
98
127
|
console.error(`[fastagent] running → ${outcome.url}`);
|
|
@@ -100,9 +129,7 @@ async function runDeployDocker(params) {
|
|
|
100
129
|
console.error(`[fastagent] logs: ${compose} logs -f agent`);
|
|
101
130
|
console.error(`[fastagent] stop: ${compose} down (state volume is kept)`);
|
|
102
131
|
}
|
|
103
|
-
// BEFORE failStartup: a registration gate says "re-run this deploy", and a re-run rebuilds the
|
|
104
|
-
// tunnel service — the operator needs to know the URL will be a different one, or they will read
|
|
105
|
-
// the retry as re-registering the same address.
|
|
132
|
+
// BEFORE failStartup: a registration gate says "re-run this deploy", and a re-run rebuilds the tunnel service.
|
|
106
133
|
if (outcome.tunnelUrl) {
|
|
107
134
|
console.error(`[fastagent] note: Quick Tunnel URLs are ephemeral — after the tunnel container/Docker daemon ` +
|
|
108
135
|
`restarts, re-run this deploy so webhooks receive the new URL`);
|