@fastagent-sh/fastagent 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/atomic-write.d.ts +4 -5
- package/dist/atomic-write.js +4 -5
- package/dist/authoring.d.ts +32 -15
- package/dist/authoring.js +147 -127
- package/dist/channels/agentcore-protocol.d.ts +8 -3
- package/dist/channels/agentcore-protocol.js +3 -1
- package/dist/channels/agentcore-service.d.ts +1 -0
- package/dist/channels/agentcore-service.js +14 -4
- package/dist/channels/agentcore.d.ts +9 -0
- package/dist/channels/agentcore.js +29 -4
- package/dist/channels/context.d.ts +2 -0
- package/dist/channels/context.js +5 -1
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu.d.ts +0 -3
- package/dist/channels/feishu/feishu.js +10 -10
- package/dist/channels/feishu/register-app.js +3 -5
- package/dist/channels/feishu/scaffold/channel.ts +32 -34
- package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
- package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
- package/dist/channels/feishu/setup-mode.d.ts +2 -12
- package/dist/channels/feishu/setup-mode.js +2 -22
- package/dist/channels/feishu/setup.d.ts +12 -0
- package/dist/channels/feishu/setup.js +197 -0
- package/dist/channels/kit/abortable.d.ts +8 -0
- package/dist/channels/kit/abortable.js +22 -0
- package/dist/channels/lark/lark.d.ts +0 -3
- package/dist/channels/lark/lark.js +0 -5
- package/dist/channels/lark/scaffold/channel.ts +33 -35
- package/dist/channels/lark/scaffold/lark-send.ts +5 -5
- package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
- package/dist/channels/lark/setup.d.ts +9 -0
- package/dist/channels/lark/setup.js +57 -0
- package/dist/channels/setup.d.ts +112 -0
- package/dist/channels/setup.js +31 -0
- package/dist/channels/slack/history.js +1 -1
- package/dist/channels/slack/manifest.d.ts +17 -7
- package/dist/channels/slack/manifest.js +12 -16
- package/dist/channels/slack/scaffold/channel.ts +44 -30
- package/dist/channels/slack/scaffold/slack-send.ts +8 -8
- package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
- package/dist/channels/slack/setup.d.ts +7 -0
- package/dist/channels/slack/setup.js +119 -0
- package/dist/channels/slack/slack-api.d.ts +2 -0
- package/dist/channels/slack/slack-api.js +4 -0
- package/dist/channels/slack/slack.d.ts +21 -2
- package/dist/channels/slack/slack.js +398 -338
- package/dist/channels/slack/socket.d.ts +57 -0
- package/dist/channels/slack/socket.js +305 -0
- package/dist/channels/telegram/polling.d.ts +27 -0
- package/dist/channels/telegram/polling.js +105 -0
- package/dist/channels/telegram/scaffold/channel.ts +52 -32
- package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
- package/dist/channels/telegram/setup.d.ts +2 -0
- package/dist/channels/telegram/setup.js +42 -0
- package/dist/channels/telegram/telegram-api.d.ts +27 -1
- package/dist/channels/telegram/telegram-api.js +7 -6
- package/dist/channels/telegram/telegram.d.ts +11 -3
- package/dist/channels/telegram/telegram.js +184 -157
- package/dist/channels/webhook-evidence.d.ts +11 -0
- package/dist/channels/webhook-evidence.js +71 -0
- package/dist/channels/webhooks.d.ts +37 -0
- package/dist/channels/webhooks.js +70 -0
- package/dist/child-env.d.ts +16 -0
- package/dist/child-env.js +30 -0
- package/dist/cli/box-login.js +4 -1
- package/dist/cli/channel-app.d.ts +3 -0
- package/dist/cli/channel-app.js +35 -0
- package/dist/cli/channel-setup.d.ts +38 -0
- package/dist/cli/channel-setup.js +195 -0
- package/dist/cli/commands/add.d.ts +6 -14
- package/dist/cli/commands/add.js +45 -303
- package/dist/cli/commands/auth.js +8 -2
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +16 -0
- package/dist/cli/commands/channels.d.ts +19 -0
- package/dist/cli/commands/channels.js +230 -0
- package/dist/cli/commands/content.d.ts +7 -3
- package/dist/cli/commands/content.js +16 -10
- package/dist/cli/commands/deploy/agentcore.js +55 -23
- package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
- package/dist/cli/commands/deploy/channel-apps.js +30 -0
- package/dist/cli/commands/deploy/docker.js +32 -65
- package/dist/cli/commands/deploy/fly.js +28 -12
- package/dist/cli/commands/deploy/railway.js +38 -13
- package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
- package/dist/cli/commands/deploy/secrets-step.js +66 -0
- package/dist/cli/commands/deploy/shared.d.ts +16 -5
- package/dist/cli/commands/deploy/shared.js +25 -8
- package/dist/cli/commands/deploy.js +53 -60
- package/dist/cli/commands/dev.d.ts +2 -0
- package/dist/cli/commands/dev.js +15 -3
- package/dist/cli/commands/env.js +1 -1
- package/dist/cli/commands/info.d.ts +2 -0
- package/dist/cli/commands/info.js +60 -25
- package/dist/cli/commands/init.js +3 -1
- package/dist/cli/commands/invoke.d.ts +2 -0
- package/dist/cli/commands/invoke.js +20 -7
- package/dist/cli/commands/login.d.ts +0 -7
- package/dist/cli/commands/login.js +22 -10
- package/dist/cli/commands/logout.js +1 -1
- package/dist/cli/commands/mcp.js +14 -6
- package/dist/cli/commands/models.js +3 -2
- package/dist/cli/commands/schedules.d.ts +4 -2
- package/dist/cli/commands/schedules.js +14 -10
- package/dist/cli/commands/secrets.d.ts +14 -0
- package/dist/cli/commands/secrets.js +213 -0
- package/dist/cli/commands/start.d.ts +14 -14
- package/dist/cli/commands/start.js +46 -60
- package/dist/cli/content-view.d.ts +4 -6
- package/dist/cli/content-view.js +5 -11
- package/dist/cli/first-run-channels.d.ts +3 -0
- package/dist/cli/first-run-channels.js +63 -0
- package/dist/cli/instance.d.ts +41 -0
- package/dist/cli/instance.js +155 -0
- package/dist/cli/invoke-stream.d.ts +5 -2
- package/dist/cli/invoke-stream.js +7 -2
- package/dist/cli/program.js +282 -110
- package/dist/cli/serve.d.ts +4 -5
- package/dist/cli/serve.js +4 -7
- package/dist/cli/shared.d.ts +11 -2
- package/dist/cli/shared.js +32 -19
- package/dist/cli/ticket.js +50 -6
- package/dist/content/declare.d.ts +31 -37
- package/dist/content/declare.js +59 -60
- package/dist/content/file.d.ts +23 -12
- package/dist/content/file.js +48 -35
- package/dist/content/git.d.ts +0 -10
- package/dist/content/git.js +5 -39
- package/dist/content/resolve.d.ts +18 -22
- package/dist/content/resolve.js +32 -66
- package/dist/content/source.d.ts +7 -8
- package/dist/content/source.js +10 -28
- package/dist/credentials-page.d.ts +26 -0
- package/dist/credentials-page.js +185 -0
- package/dist/declared-secrets.d.ts +10 -3
- package/dist/declared-secrets.js +8 -4
- package/dist/deploy/agentcore/destroy.d.ts +4 -1
- package/dist/deploy/agentcore/destroy.js +25 -1
- package/dist/deploy/agentcore/forwarder.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +7 -21
- package/dist/deploy/agentcore/plan.js +47 -49
- package/dist/deploy/agentcore/run.d.ts +18 -10
- package/dist/deploy/agentcore/run.js +93 -70
- package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
- package/dist/deploy/agentcore/runtime-secrets.js +108 -0
- package/dist/deploy/agentcore/shell.js +12 -19
- package/dist/deploy/agentcore/sigv4.d.ts +7 -0
- package/dist/deploy/agentcore/sigv4.js +15 -0
- package/dist/deploy/agentcore/slr.d.ts +18 -0
- package/dist/deploy/agentcore/slr.js +27 -0
- package/dist/deploy/agentcore/store.d.ts +7 -0
- package/dist/deploy/agentcore/store.js +115 -0
- package/dist/deploy/api.d.ts +14 -14
- package/dist/deploy/api.js +26 -24
- package/dist/deploy/box-shell.d.ts +3 -1
- package/dist/deploy/build-context.d.ts +4 -4
- package/dist/deploy/build-context.js +11 -15
- package/dist/deploy/channel-ingress.d.ts +35 -9
- package/dist/deploy/channel-ingress.js +119 -22
- package/dist/deploy/container.d.ts +35 -10
- package/dist/deploy/container.js +79 -37
- package/dist/deploy/delivered-text.d.ts +13 -0
- package/dist/deploy/delivered-text.js +86 -0
- package/dist/deploy/docker/plan.d.ts +7 -8
- package/dist/deploy/docker/plan.js +31 -52
- package/dist/deploy/docker/run.d.ts +5 -43
- package/dist/deploy/docker/run.js +9 -84
- package/dist/deploy/file-store.d.ts +2 -0
- package/dist/deploy/file-store.js +17 -0
- package/dist/deploy/fly/plan.d.ts +3 -2
- package/dist/deploy/fly/plan.js +20 -9
- package/dist/deploy/fly/run.d.ts +18 -8
- package/dist/deploy/fly/run.js +24 -42
- package/dist/deploy/fly/store.d.ts +12 -0
- package/dist/deploy/fly/store.js +83 -0
- package/dist/deploy/hosts.d.ts +1 -2
- package/dist/deploy/hosts.js +1 -2
- package/dist/deploy/preflight.d.ts +18 -5
- package/dist/deploy/preflight.js +135 -63
- package/dist/deploy/railway/ignore.d.ts +8 -0
- package/dist/deploy/railway/ignore.js +57 -0
- package/dist/deploy/railway/plan.d.ts +1 -2
- package/dist/deploy/railway/plan.js +8 -6
- package/dist/deploy/railway/run.d.ts +21 -15
- package/dist/deploy/railway/run.js +32 -78
- package/dist/deploy/railway/store.d.ts +29 -0
- package/dist/deploy/railway/store.js +143 -0
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +4 -3
- package/dist/deploy/secret-store.d.ts +63 -0
- package/dist/deploy/secret-store.js +98 -0
- package/dist/deploy/secrets.d.ts +29 -46
- package/dist/deploy/secrets.js +49 -122
- package/dist/deploy/workspace.d.ts +6 -12
- package/dist/deploy/workspace.js +18 -94
- package/dist/dev-supervisor.js +6 -8
- package/dist/environment/declare.js +3 -2
- package/dist/environment/mise.d.ts +3 -1
- package/dist/environment/mise.js +4 -2
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/harnesses/models/catalog.d.ts +1 -0
- package/dist/harnesses/models/catalog.js +1 -2
- package/dist/harnesses/models/environment.d.ts +7 -2
- package/dist/harnesses/models/environment.js +9 -5
- package/dist/harnesses/models/host.d.ts +0 -7
- package/dist/harnesses/models/host.js +10 -1
- package/dist/harnesses/models/login.d.ts +0 -8
- package/dist/harnesses/models/login.js +5 -4
- package/dist/harnesses/models/models.d.ts +7 -2
- package/dist/harnesses/models/models.js +28 -4
- package/dist/harnesses/pi-durable/agent.js +59 -38
- package/dist/harnesses/pi-durable/build.d.ts +12 -0
- package/dist/harnesses/pi-durable/build.js +270 -0
- package/dist/harnesses/pi-durable/built.d.ts +24 -0
- package/dist/harnesses/pi-durable/built.js +136 -0
- package/dist/harnesses/pi-durable/config.d.ts +81 -0
- package/dist/harnesses/pi-durable/config.js +266 -0
- package/dist/harnesses/pi-durable/define.d.ts +63 -13
- package/dist/harnesses/pi-durable/define.js +23 -2
- package/dist/harnesses/pi-durable/definition.d.ts +72 -0
- package/dist/harnesses/pi-durable/definition.js +143 -0
- package/dist/harnesses/pi-durable/events.d.ts +4 -3
- package/dist/harnesses/pi-durable/events.js +179 -136
- package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
- package/dist/harnesses/pi-durable/execution-env.js +11 -0
- package/dist/harnesses/pi-durable/mcp.js +1 -3
- package/dist/harnesses/pi-durable/open.d.ts +35 -37
- package/dist/harnesses/pi-durable/open.js +91 -153
- package/dist/harnesses/pi-durable/reads.d.ts +0 -1
- package/dist/harnesses/pi-durable/reads.js +1 -3
- package/dist/harnesses/pi-durable/registry.d.ts +4 -6
- package/dist/harnesses/pi-durable/registry.js +29 -26
- package/dist/harnesses/pi-durable/release.d.ts +2 -2
- package/dist/harnesses/pi-durable/release.js +10 -7
- package/dist/harnesses/pi-durable/serve.d.ts +91 -15
- package/dist/harnesses/pi-durable/serve.js +109 -47
- package/dist/harnesses/pi-durable/sessions.js +15 -12
- package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
- package/dist/harnesses/pi-durable/text-sources.js +55 -0
- package/dist/harnesses/pi-durable/text.d.ts +16 -4
- package/dist/harnesses/pi-durable/text.js +115 -59
- package/dist/harnesses/pi-durable/wake.js +8 -8
- package/dist/http/client.js +9 -3
- package/dist/http/contract.d.ts +3 -3
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -1
- package/dist/instance-name.d.ts +7 -0
- package/dist/instance-name.js +26 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/mcp/client.js +11 -2
- package/dist/mcp/config.d.ts +70 -18
- package/dist/mcp/config.js +136 -53
- package/dist/mcp/host.d.ts +5 -2
- package/dist/mcp/host.js +14 -6
- package/dist/mcp/sign-in.js +2 -2
- package/dist/mcp-api.d.ts +14 -0
- package/dist/paths.d.ts +22 -8
- package/dist/paths.js +44 -11
- package/dist/runtime-websocket.d.ts +14 -0
- package/dist/runtime-websocket.js +39 -0
- package/dist/scaffold/add-channel.d.ts +9 -16
- package/dist/scaffold/add-channel.js +13 -83
- package/dist/scaffold/templates/env.example +4 -5
- package/dist/scaffold/templates/fastagent.config.ts +20 -14
- package/dist/scaffold/templates/gitignore +9 -6
- package/dist/scaffold/templates.d.ts +6 -2
- package/dist/scaffold/templates.js +7 -2
- package/dist/scaffold/vendor-skill.js +4 -2
- package/dist/service.d.ts +44 -6
- package/dist/service.js +110 -10
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +1 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/strip-edges.d.ts +10 -0
- package/dist/strip-edges.js +22 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +0 -1
- package/dist/tunnel.js +2 -8
- package/dist/value-file.d.ts +11 -6
- package/dist/value-file.js +21 -9
- package/dist/version.d.ts +7 -1
- package/dist/version.js +9 -3
- package/package.json +6 -5
- package/dist/channels/lark/credentials-page.d.ts +0 -25
- package/dist/channels/lark/credentials-page.js +0 -206
- package/dist/channels/lark/onboard.d.ts +0 -52
- package/dist/channels/lark/onboard.js +0 -86
- package/dist/channels/slack/config-api.d.ts +0 -69
- package/dist/channels/slack/config-api.js +0 -162
- package/dist/channels/slack/onboard.d.ts +0 -40
- package/dist/channels/slack/onboard.js +0 -136
- package/dist/channels/slack/onboarding-state.d.ts +0 -31
- package/dist/channels/slack/onboarding-state.js +0 -64
- package/dist/channels/slack/register-webhook.d.ts +0 -12
- package/dist/channels/slack/register-webhook.js +0 -67
- package/dist/channels/slack/setup-server.d.ts +0 -32
- package/dist/channels/slack/setup-server.js +0 -246
- package/dist/cli/add-feishu.d.ts +0 -41
- package/dist/cli/add-feishu.js +0 -391
- package/dist/cli/add-slack.d.ts +0 -18
- package/dist/cli/add-slack.js +0 -216
- package/dist/harnesses/pi-durable/parked.d.ts +0 -18
- package/dist/harnesses/pi-durable/parked.js +0 -27
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/** `deploy docker`: one app service + loopback port + state volume, as a user-owned Compose file. */
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { DOCKER_COMPOSE_FILE, DOCKER_VALUE_FILE,
|
|
4
|
+
import { ownPublicUrl } from "../../../deploy/container.js";
|
|
5
|
+
import { DOCKER_COMPOSE_FILE, DOCKER_VALUE_FILE, ingressNotes, isGeneratedCompose, planDockerDeploy, toDockerProjectName, } from "../../../deploy/docker/plan.js";
|
|
6
6
|
import { deployDockerRun } from "../../../deploy/docker/run.js";
|
|
7
7
|
import { spawnRunner } from "../../../deploy/runner.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { SECRET_FILE_MODE, exists } from "../../../paths.js";
|
|
9
|
+
import { fileStore } from "../../../deploy/file-store.js";
|
|
10
|
+
import { dotEnvPath } from "../../../value-file.js";
|
|
11
|
+
import { deploySecrets } from "./secrets-step.js";
|
|
12
12
|
import { processShell } from "../../../deploy/box-shell.js";
|
|
13
13
|
import { failStartup } from "../../fail.js";
|
|
14
14
|
import { boxLoginStep, leftToPerson } from "./shared.js";
|
|
@@ -26,7 +26,7 @@ export const dockerHost = {
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
async deploy(ctx) {
|
|
29
|
-
const { opts, agentDir, channels,
|
|
29
|
+
const { opts, agentDir, channels, pre, write } = ctx;
|
|
30
30
|
const { modelAuth, boxLogin, container, port, declaredSecrets, values, valueFile } = pre;
|
|
31
31
|
// Compose requires its committed env_file to exist, even when no values are declared.
|
|
32
32
|
const composeValueFile = join(agentDir, DOCKER_VALUE_FILE);
|
|
@@ -35,8 +35,8 @@ export const dockerHost = {
|
|
|
35
35
|
await writeFile(composeValueFile, "", { mode: SECRET_FILE_MODE });
|
|
36
36
|
// Compose interpolates `$VAR` INSIDE env_file values (`format: raw` needs Compose 2.30), so a credential
|
|
37
37
|
// containing `$` reaches the container rewritten — silently, and differently from what this pre-flight read.
|
|
38
|
-
// No escaping advice: this ONE file is also read literally by `dev`/`start` (`parseEnvContent`) and
|
|
39
|
-
//
|
|
38
|
+
// No escaping advice: this ONE file is also read literally by `dev`/`start` (`parseEnvContent`) and is the source
|
|
39
|
+
// `secrets push` sets every other host's values from, so `$$` would fix Docker by corrupting all of them.
|
|
40
40
|
const dollarValues = [...values].filter(([, value]) => value.includes("$")).map(([name]) => name);
|
|
41
41
|
if (dollarValues.length > 0) {
|
|
42
42
|
console.error(`[fastagent] warn: ${dollarValues.join(", ")} contain "$", which Compose expands when reading ${valueFile} ` +
|
|
@@ -44,109 +44,76 @@ export const dockerHost = {
|
|
|
44
44
|
`ONLY — \`fastagent dev\`/\`start\` and the other hosts read this file literally and would keep the extra ` +
|
|
45
45
|
`"$". Prefer a value without "$".`);
|
|
46
46
|
}
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const dockerPlan = (tunnel) => planDockerDeploy({
|
|
50
|
-
projectName,
|
|
47
|
+
const plan = planDockerDeploy({
|
|
48
|
+
projectName: toDockerProjectName(basename(agentDir)),
|
|
51
49
|
port,
|
|
52
50
|
boxLogin,
|
|
53
51
|
channels,
|
|
54
|
-
tunnel,
|
|
55
52
|
secrets: pre.secrets,
|
|
56
53
|
valueFile,
|
|
57
54
|
...container,
|
|
58
55
|
});
|
|
59
|
-
const requestedTunnel = !!opts.tunnel && (!hasDeclaredChannels || webhookChannels.length > 0);
|
|
60
|
-
if (opts.tunnel && hasDeclaredChannels && webhookChannels.length === 0) {
|
|
61
|
-
console.error(`[fastagent] note: --tunnel skipped — every channel uses a long connection`);
|
|
62
|
-
}
|
|
63
|
-
let plan = dockerPlan(requestedTunnel);
|
|
64
|
-
// An existing Compose file is authoritative: shape its comparison/runbook from the topology on disk, regardless
|
|
65
|
-
// of the current flag.
|
|
66
|
-
const composeFile = join(agentDir, plan.composePath);
|
|
67
|
-
let keptWithoutRequestedTunnel = false;
|
|
68
|
-
// Same ownership question as fly.toml: a hand-owned compose file survives --force, so the plan must describe the
|
|
69
|
-
// topology that will actually be there.
|
|
70
|
-
const composeText = await readTextIfExists(composeFile).catch(failStartup);
|
|
71
|
-
if (composeText !== undefined && (!opts.force || !isGeneratedCompose(composeText))) {
|
|
72
|
-
const existingHasTunnel = composeHasTunnelService(composeText);
|
|
73
|
-
plan = dockerPlan(existingHasTunnel);
|
|
74
|
-
keptWithoutRequestedTunnel = requestedTunnel && !existingHasTunnel;
|
|
75
|
-
}
|
|
76
56
|
await write(plan.artifacts, { force: !!opts.force });
|
|
77
57
|
if (opts.run) {
|
|
78
58
|
return runDeployDocker({
|
|
79
59
|
agentDir,
|
|
80
60
|
composeFile: plan.composePath,
|
|
81
61
|
port,
|
|
82
|
-
requireTunnel: requestedTunnel,
|
|
83
62
|
modelAuth,
|
|
63
|
+
modelKey: pre.modelKey,
|
|
84
64
|
boxLogin,
|
|
85
65
|
answerer: ctx.answerer,
|
|
66
|
+
api: ctx.api,
|
|
86
67
|
channels,
|
|
87
68
|
declaredSecrets,
|
|
88
69
|
values,
|
|
89
70
|
valueFile,
|
|
71
|
+
publicUrl: ownPublicUrl(container.config),
|
|
72
|
+
config: opts.config,
|
|
90
73
|
});
|
|
91
74
|
}
|
|
92
|
-
if (keptWithoutRequestedTunnel) {
|
|
93
|
-
console.error(`[fastagent] warn: --tunnel was requested but kept ${plan.composePath} has no "tunnel" service — ` +
|
|
94
|
-
`edit it, delete it and regenerate, or pass --force`);
|
|
95
|
-
}
|
|
96
75
|
console.log(plan.runbook.join("\n"));
|
|
97
76
|
return `http://127.0.0.1:${port}`;
|
|
98
77
|
},
|
|
99
78
|
};
|
|
100
79
|
/** `deploy docker --run`: reconcile the user-owned local topology, then log the box in if it must. */
|
|
101
80
|
async function runDeployDocker(params) {
|
|
102
|
-
const { agentDir, composeFile, port,
|
|
81
|
+
const { agentDir, composeFile, port, channels } = params;
|
|
103
82
|
const leave = leftToPerson(params.answerer);
|
|
104
|
-
const
|
|
83
|
+
const valueFile = dotEnvPath(agentDir);
|
|
84
|
+
const provide = deploySecrets({
|
|
85
|
+
host: "docker",
|
|
86
|
+
label: "Docker",
|
|
87
|
+
agentDir,
|
|
88
|
+
opts: { config: params.config },
|
|
89
|
+
answerer: params.answerer,
|
|
90
|
+
api: params.api,
|
|
105
91
|
modelAuth: params.modelAuth,
|
|
106
|
-
|
|
92
|
+
modelKey: params.modelKey,
|
|
93
|
+
declaredSecrets: params.declaredSecrets,
|
|
94
|
+
valueFile: params.valueFile,
|
|
107
95
|
values: params.values,
|
|
108
96
|
});
|
|
109
97
|
const outcome = await deployDockerRun({
|
|
110
98
|
composeFile,
|
|
111
99
|
port,
|
|
112
|
-
|
|
113
|
-
missingSecrets,
|
|
114
|
-
valueFile: params.valueFile,
|
|
115
|
-
requireTunnel,
|
|
116
|
-
channels,
|
|
100
|
+
provideSecrets: () => provide(fileStore(agentDir, valueFile)),
|
|
117
101
|
...boxLoginStep("docker", params, () => composeShell(composeFile, agentDir)),
|
|
118
|
-
announce: (tunnelUrl) => announceWebhooks(agentDir, tunnelUrl, channels, {
|
|
119
|
-
// A ticket's process opens no browser: its person is told, through `leave`.
|
|
120
|
-
...(leave === undefined ? { openUrl: openExternalUrl } : { leave }),
|
|
121
|
-
stateRoot: resolveStateRoot(agentDir),
|
|
122
|
-
env: Object.fromEntries(params.values),
|
|
123
|
-
}),
|
|
124
102
|
}, spawnRunner("docker", agentDir), (message) => console.error(`[fastagent] ${message}`));
|
|
125
103
|
const compose = `docker compose -f ${composeFile}`;
|
|
126
104
|
// Compose reached "up" iff the driver could report where it is.
|
|
127
|
-
const isUp = outcome.ok || outcome.url !== undefined
|
|
105
|
+
const isUp = outcome.ok || outcome.url !== undefined;
|
|
128
106
|
if (outcome.url)
|
|
129
107
|
console.error(`[fastagent] running → ${outcome.url}`);
|
|
130
108
|
if (isUp) {
|
|
131
109
|
console.error(`[fastagent] logs: ${compose} logs -f agent`);
|
|
132
110
|
console.error(`[fastagent] stop: ${compose} down (state volume is kept)`);
|
|
133
111
|
}
|
|
134
|
-
// BEFORE failStartup: a registration gate says "re-run this deploy", and a re-run rebuilds the tunnel service.
|
|
135
|
-
if (outcome.tunnelUrl) {
|
|
136
|
-
console.error(`[fastagent] note: Quick Tunnel URLs are ephemeral — after the tunnel container/Docker daemon ` +
|
|
137
|
-
`restarts, re-run this deploy so webhooks receive the new URL`);
|
|
138
|
-
}
|
|
139
112
|
if (!outcome.ok)
|
|
140
113
|
failStartup(new Error(`deploy stopped: ${outcome.gate}`));
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (paths.length > 0) {
|
|
145
|
-
const unreached = `${paths.join(", ")} receive nothing until a public URL reaches this container — re-run with --tunnel for a ` +
|
|
146
|
-
`Quick Tunnel, or configure your own tunnel/proxy and point those webhook path(s) (or your remapped channel ` +
|
|
147
|
-
`routes) at it`;
|
|
148
|
-
console.error(`[fastagent] note: ${unreached}`);
|
|
149
|
-
leave?.(unreached);
|
|
114
|
+
for (const note of ingressNotes(channels, params.publicUrl, outcome.url && `${outcome.url}/health`)) {
|
|
115
|
+
console.error(`[fastagent] note: ${note}`);
|
|
116
|
+
leave?.(note);
|
|
150
117
|
}
|
|
151
118
|
return outcome.url;
|
|
152
119
|
}
|
|
@@ -5,11 +5,12 @@ import { deployFlyRun } from "../../../deploy/fly/run.js";
|
|
|
5
5
|
import { spawnRunner } from "../../../deploy/runner.js";
|
|
6
6
|
import { readTextIfExists } from "../../../paths.js";
|
|
7
7
|
import { residencyFor } from "../../../deploy/residency.js";
|
|
8
|
-
import { assembleSecrets } from "../../../deploy/secrets.js";
|
|
9
8
|
import { processShell } from "../../../deploy/box-shell.js";
|
|
9
|
+
import { withPublicUrl } from "../../../deploy/container.js";
|
|
10
10
|
import { failStartup } from "../../fail.js";
|
|
11
|
+
import { deploySecrets } from "./secrets-step.js";
|
|
11
12
|
import { FLY_LOGIN, hostLogin } from "./host-login.js";
|
|
12
|
-
import { boxLoginStep, registrarsFor } from "./shared.js";
|
|
13
|
+
import { boxLoginStep, registrarsFor, urlRecorder } from "./shared.js";
|
|
13
14
|
export const flyHost = {
|
|
14
15
|
isOurs: (path, content) => path.endsWith("fly.toml") && isGeneratedFlyToml(content),
|
|
15
16
|
async shell(agentDir) {
|
|
@@ -47,6 +48,8 @@ export const flyHost = {
|
|
|
47
48
|
console.error(`[fastagent] warn: ${msg}`);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
const reachedAt = `https://${appName}.fly.dev`;
|
|
52
|
+
const delivered = withPublicUrl(container.config, reachedAt);
|
|
50
53
|
const plan = planFlyDeploy({
|
|
51
54
|
appName,
|
|
52
55
|
port,
|
|
@@ -55,21 +58,27 @@ export const flyHost = {
|
|
|
55
58
|
secrets: pre.secrets,
|
|
56
59
|
hasCron,
|
|
57
60
|
...container,
|
|
61
|
+
// The app's URL is known before the build, so the image names it: the box points its own channels there.
|
|
62
|
+
config: delivered.config,
|
|
63
|
+
publicUrl: delivered.publicUrl,
|
|
58
64
|
});
|
|
59
65
|
await write(plan.artifacts, { force: !!opts.force });
|
|
60
|
-
const reachedAt = `https://${appName}.fly.dev`;
|
|
61
66
|
if (opts.run) {
|
|
62
67
|
await runDeployFly({
|
|
63
68
|
agentDir,
|
|
64
69
|
appName,
|
|
65
70
|
modelAuth,
|
|
71
|
+
modelKey: pre.modelKey,
|
|
66
72
|
boxLogin,
|
|
67
73
|
answerer: ctx.answerer,
|
|
74
|
+
api: ctx.api,
|
|
68
75
|
rerun: ctx.rerun,
|
|
69
76
|
channels,
|
|
70
77
|
declaredSecrets,
|
|
71
78
|
values,
|
|
72
79
|
valueFile,
|
|
80
|
+
publicUrl: delivered.publicUrl,
|
|
81
|
+
config: opts.config,
|
|
73
82
|
});
|
|
74
83
|
return reachedAt;
|
|
75
84
|
}
|
|
@@ -85,23 +94,30 @@ async function runDeployFly(params) {
|
|
|
85
94
|
if ((await fly(["version"], { capture: true })).code === 127) {
|
|
86
95
|
failStartup(new Error(`flyctl not found — install it: https://fly.io/docs/flyctl/install, then re-run`));
|
|
87
96
|
}
|
|
88
|
-
const { secrets, missingSecrets } = assembleSecrets({
|
|
89
|
-
modelAuth: params.modelAuth,
|
|
90
|
-
declared: params.declaredSecrets,
|
|
91
|
-
values: params.values,
|
|
92
|
-
});
|
|
93
97
|
const outcome = await deployFlyRun({
|
|
94
98
|
appName,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
provideSecrets: deploySecrets({
|
|
100
|
+
host: "fly",
|
|
101
|
+
label: "Fly",
|
|
102
|
+
agentDir,
|
|
103
|
+
opts: { config: params.config },
|
|
104
|
+
answerer: params.answerer,
|
|
105
|
+
api: params.api,
|
|
106
|
+
modelAuth: params.modelAuth,
|
|
107
|
+
modelKey: params.modelKey,
|
|
108
|
+
declaredSecrets: params.declaredSecrets,
|
|
109
|
+
valueFile: params.valueFile,
|
|
110
|
+
values: params.values,
|
|
111
|
+
}),
|
|
98
112
|
channels,
|
|
99
113
|
flyConfig: "fly.toml",
|
|
100
114
|
dockerfile: "Dockerfile",
|
|
101
115
|
...boxLoginStep("fly", params, () => flyShell(appName, agentDir)),
|
|
102
116
|
...hostLogin(FLY_LOGIN, params.answerer, agentDir),
|
|
103
117
|
forAgent: `${params.rerun} --json`,
|
|
104
|
-
|
|
118
|
+
publicUrl: params.publicUrl,
|
|
119
|
+
...urlRecorder(agentDir, params.config, params.values, params.answerer),
|
|
120
|
+
}, fly, (m) => console.error(`[fastagent] ${m}`), registrarsFor(params.values, params.answerer));
|
|
105
121
|
if (!outcome.ok)
|
|
106
122
|
failStartup(new Error(`deploy stopped: ${outcome.gate}`));
|
|
107
123
|
console.error(`[fastagent] deployed → https://${appName}.fly.dev`);
|
|
@@ -3,18 +3,27 @@
|
|
|
3
3
|
* planRailwayDeploy).
|
|
4
4
|
*/
|
|
5
5
|
import { basename } from "node:path";
|
|
6
|
+
import { RAILWAYIGNORE_FILE, isGeneratedRailwayignore, keptRailwayignoreProblem, } from "../../../deploy/railway/ignore.js";
|
|
6
7
|
import { planRailwayDeploy, toRailwayName } from "../../../deploy/railway/plan.js";
|
|
7
8
|
import { deployRailwayRun } from "../../../deploy/railway/run.js";
|
|
8
9
|
import { spawnRunner } from "../../../deploy/runner.js";
|
|
9
|
-
import { assembleSecrets } from "../../../deploy/secrets.js";
|
|
10
10
|
import { processShell } from "../../../deploy/box-shell.js";
|
|
11
|
+
import { deliveredConfigArtifact, withPublicUrl } from "../../../deploy/container.js";
|
|
11
12
|
import { failStartup } from "../../fail.js";
|
|
13
|
+
import { deploySecrets } from "./secrets-step.js";
|
|
12
14
|
import { RAILWAY_LOGIN, hostLogin } from "./host-login.js";
|
|
13
|
-
import { boxLoginStep, registrarsFor } from "./shared.js";
|
|
15
|
+
import { boxLoginStep, registrarsFor, urlRecorder } from "./shared.js";
|
|
14
16
|
export const railwayHost = {
|
|
15
|
-
|
|
16
|
-
isOurs: () => false,
|
|
17
|
+
isOurs: (path, content) => path === RAILWAYIGNORE_FILE && isGeneratedRailwayignore(content),
|
|
17
18
|
shell: async (agentDir) => railwayShell(toRailwayName(basename(agentDir)), agentDir),
|
|
19
|
+
validate({ opts, agentDir }) {
|
|
20
|
+
const problem = keptRailwayignoreProblem(agentDir);
|
|
21
|
+
if (problem === undefined)
|
|
22
|
+
return;
|
|
23
|
+
if (opts.run)
|
|
24
|
+
failStartup(new Error(`deploy stopped: ${problem}`));
|
|
25
|
+
console.error(`[fastagent] warn: ${problem}`);
|
|
26
|
+
},
|
|
18
27
|
async deploy(ctx) {
|
|
19
28
|
const { opts, agentDir, pre, channels, write } = ctx;
|
|
20
29
|
const { hasCron, modelAuth, boxLogin, container, declaredSecrets, values, valueFile } = pre;
|
|
@@ -31,16 +40,25 @@ export const railwayHost = {
|
|
|
31
40
|
if (opts.run) {
|
|
32
41
|
return runDeployRailway({
|
|
33
42
|
agentDir,
|
|
43
|
+
// The URL Railway mints is read before the upload, and the image delivers it as `http.publicUrl`.
|
|
44
|
+
deliverPublicUrl: async (url) => {
|
|
45
|
+
const delivered = withPublicUrl(container.config, url);
|
|
46
|
+
await write([deliveredConfigArtifact(delivered.config)], { force: true });
|
|
47
|
+
return delivered.publicUrl;
|
|
48
|
+
},
|
|
34
49
|
name: serviceName,
|
|
35
50
|
modelAuth,
|
|
51
|
+
modelKey: pre.modelKey,
|
|
36
52
|
boxLogin,
|
|
37
53
|
answerer: ctx.answerer,
|
|
54
|
+
api: ctx.api,
|
|
38
55
|
rerun: ctx.rerun,
|
|
39
56
|
channels,
|
|
40
57
|
declaredSecrets,
|
|
41
58
|
values,
|
|
42
59
|
valueFile,
|
|
43
60
|
intoLinked: !!opts.intoLinked,
|
|
61
|
+
config: opts.config,
|
|
44
62
|
});
|
|
45
63
|
}
|
|
46
64
|
console.log(plan.runbook.join("\n"));
|
|
@@ -56,23 +74,30 @@ async function runDeployRailway(params) {
|
|
|
56
74
|
if ((await railway(["--version"], { capture: true })).code === 127) {
|
|
57
75
|
failStartup(new Error(`railway CLI not found — install it: https://docs.railway.com/guides/cli, then re-run`));
|
|
58
76
|
}
|
|
59
|
-
const { secrets, missingSecrets } = assembleSecrets({
|
|
60
|
-
modelAuth: params.modelAuth,
|
|
61
|
-
declared: params.declaredSecrets,
|
|
62
|
-
values: params.values,
|
|
63
|
-
});
|
|
64
77
|
const outcome = await deployRailwayRun({
|
|
65
78
|
name,
|
|
66
79
|
mountPath: "/data",
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
provideSecrets: deploySecrets({
|
|
81
|
+
host: "railway",
|
|
82
|
+
label: "Railway",
|
|
83
|
+
agentDir,
|
|
84
|
+
opts: { config: params.config },
|
|
85
|
+
answerer: params.answerer,
|
|
86
|
+
api: params.api,
|
|
87
|
+
modelAuth: params.modelAuth,
|
|
88
|
+
modelKey: params.modelKey,
|
|
89
|
+
declaredSecrets: params.declaredSecrets,
|
|
90
|
+
valueFile: params.valueFile,
|
|
91
|
+
values: params.values,
|
|
92
|
+
}),
|
|
70
93
|
channels,
|
|
71
94
|
intoLinked,
|
|
95
|
+
deliverPublicUrl: params.deliverPublicUrl,
|
|
96
|
+
...urlRecorder(agentDir, params.config, params.values, params.answerer),
|
|
72
97
|
...boxLoginStep("railway", params, () => railwayShell(name, agentDir)),
|
|
73
98
|
...hostLogin(RAILWAY_LOGIN, params.answerer, agentDir),
|
|
74
99
|
forAgent: `${params.rerun} --json`,
|
|
75
|
-
}, railway, (m) => console.error(`[fastagent] ${m}`), registrarsFor(
|
|
100
|
+
}, railway, (m) => console.error(`[fastagent] ${m}`), registrarsFor(params.values, params.answerer));
|
|
76
101
|
if (!outcome.ok)
|
|
77
102
|
failStartup(new Error(`deploy stopped: ${outcome.gate}`));
|
|
78
103
|
console.error(`[fastagent] deployed → ${outcome.url}`);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DeclaredSecret } from "../../../declared-secrets.ts";
|
|
2
|
+
import { type SecretStore } from "../../../deploy/secret-store.ts";
|
|
3
|
+
import { type DeploymentApi } from "../../../deploy/api.ts";
|
|
4
|
+
import { type ModelKey } from "../../../deploy/secrets.ts";
|
|
5
|
+
import type { Answerer, DeployOptions } from "./shared.ts";
|
|
6
|
+
export declare function deploySecrets(input: {
|
|
7
|
+
host: string;
|
|
8
|
+
label: string;
|
|
9
|
+
agentDir: string;
|
|
10
|
+
opts: Pick<DeployOptions, "config">;
|
|
11
|
+
answerer: Answerer;
|
|
12
|
+
api: DeploymentApi;
|
|
13
|
+
modelAuth: string | undefined;
|
|
14
|
+
modelKey: ModelKey;
|
|
15
|
+
declaredSecrets: readonly DeclaredSecret[];
|
|
16
|
+
/** The production value file this deploy loaded, and its values. */
|
|
17
|
+
valueFile: string;
|
|
18
|
+
values: ReadonlyMap<string, string>;
|
|
19
|
+
}): (store: SecretStore) => Promise<string | undefined>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deploy's step that makes the target hold every value the deployment needs (secret-store.ts), written once for the
|
|
3
|
+
* hosts that keep their values in a store of their own: who is asked is the deploy's answerer, and the file a source
|
|
4
|
+
* reads is the production value file the deploy already loaded.
|
|
5
|
+
*/
|
|
6
|
+
import { confirm, isCancel, password } from "@clack/prompts";
|
|
7
|
+
import { provideSecrets, } from "../../../deploy/secret-store.js";
|
|
8
|
+
import { mintApiKey } from "../../../deploy/api.js";
|
|
9
|
+
import { declaredNames, secretNeeds } from "../../../deploy/secrets.js";
|
|
10
|
+
import { API_KEY_VARIABLE } from "../../../http/server.js";
|
|
11
|
+
import { unprotectedSecretsNote, writeValues } from "../../../value-file.js";
|
|
12
|
+
import { displayPath } from "../../../paths.js";
|
|
13
|
+
import { shellArg } from "../../shared.js";
|
|
14
|
+
/** The questions of a person at this terminal; a cancel stops the deploy. */
|
|
15
|
+
const terminalAsker = {
|
|
16
|
+
async confirm(question) {
|
|
17
|
+
const answer = await confirm({ message: question, initialValue: true });
|
|
18
|
+
if (isCancel(answer))
|
|
19
|
+
throw new Error("cancelled");
|
|
20
|
+
return answer;
|
|
21
|
+
},
|
|
22
|
+
async secret(need) {
|
|
23
|
+
const answer = await password({ message: `${need.name} (${need.hint})` });
|
|
24
|
+
if (isCancel(answer))
|
|
25
|
+
throw new Error("cancelled");
|
|
26
|
+
const value = String(answer);
|
|
27
|
+
if (value.trim() === "")
|
|
28
|
+
throw new Error(`${need.name} is empty`);
|
|
29
|
+
return value;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
/** The `fastagent secrets` commands for `host` as this deploy would have them run: its agent directory and configuration. */
|
|
33
|
+
function secretsCommands(host, agentDir, config) {
|
|
34
|
+
const agent = displayPath(process.cwd(), agentDir);
|
|
35
|
+
const rest = `${agent === undefined ? "" : ` ${shellArg(agent)}`}${config === undefined ? "" : ` --config ${shellArg(config)}`}`;
|
|
36
|
+
return {
|
|
37
|
+
push: `fastagent secrets push ${host}${rest}`,
|
|
38
|
+
set: (name) => `fastagent secrets set ${host} ${name}${rest}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function deploySecrets(input) {
|
|
42
|
+
const { host, label, agentDir, opts, answerer, api, modelAuth, modelKey, declaredSecrets, valueFile, values } = input;
|
|
43
|
+
return (store) => provideSecrets({
|
|
44
|
+
host: label,
|
|
45
|
+
store,
|
|
46
|
+
needs: secretNeeds(modelAuth, declaredSecrets, modelKey),
|
|
47
|
+
known: [...declaredNames(modelAuth, declaredSecrets, modelKey), ...(api.served ? [API_KEY_VARIABLE] : [])],
|
|
48
|
+
...(values.size === 0 ? {} : { source: { file: valueFile, values } }),
|
|
49
|
+
...(answerer.kind === "terminal" ? { asker: terminalAsker } : {}),
|
|
50
|
+
commands: secretsCommands(host, agentDir, opts.config),
|
|
51
|
+
say: (line) => {
|
|
52
|
+
console.error(`[fastagent] note: ${line}`);
|
|
53
|
+
if (answerer.kind === "ticket")
|
|
54
|
+
answerer.step.person.afterwards(line);
|
|
55
|
+
},
|
|
56
|
+
inform: (line) => console.error(`[fastagent] note: ${line}`),
|
|
57
|
+
...(api.served ? { mint: [mintApiKey()] } : {}),
|
|
58
|
+
onMinted: async (minted) => {
|
|
59
|
+
const names = Object.keys(minted);
|
|
60
|
+
console.error(`[fastagent] made ${names.join(", ")} for the Agent API, set on ${label} and kept in ${valueFile}`);
|
|
61
|
+
const { unprotectedSecretsDir } = await writeValues(agentDir, "Agent API", names.map((name) => ({ name, value: minted[name] })), names);
|
|
62
|
+
if (unprotectedSecretsDir !== undefined)
|
|
63
|
+
console.error(`[fastagent] note: ${unprotectedSecretsNote(unprotectedSecretsDir)}`);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { DeclaredChannel } from "../../../channels/discover.ts";
|
|
2
|
-
import type { Registrars } from "../../../deploy/channel-ingress.ts";
|
|
2
|
+
import type { Registrars, UrlRecorder } from "../../../deploy/channel-ingress.ts";
|
|
3
3
|
import type { DeployPreflight } from "../../../deploy/preflight.ts";
|
|
4
|
-
import type {
|
|
4
|
+
import type { DeploymentApi } from "../../../deploy/api.ts";
|
|
5
|
+
import type { EffectiveConfig } from "../../../harnesses/pi-durable/config.ts";
|
|
5
6
|
import { type BoxLoginStep, type BoxShell } from "../../../deploy/box-shell.ts";
|
|
6
7
|
import type { TicketStep } from "../../ticket.ts";
|
|
7
8
|
import type { DeployHost } from "../../../deploy/hosts.ts";
|
|
8
9
|
export interface DeployOptions {
|
|
9
10
|
run?: boolean;
|
|
10
|
-
tunnel?: boolean;
|
|
11
11
|
force?: boolean;
|
|
12
12
|
intoLinked?: boolean;
|
|
13
|
+
/** `--config <file>`: the instance configuration the deployment delivers. */
|
|
14
|
+
config?: string;
|
|
13
15
|
/** false ⇔ `--no-input`. */
|
|
14
16
|
input?: boolean;
|
|
15
17
|
/** `--json`: a coding agent's `--run`, run in a ticket that hands each step only a person can take to its user. */
|
|
@@ -48,10 +50,12 @@ interface DeployContext {
|
|
|
48
50
|
/** The command that runs this deploy again, from where it ran: its agent directory and flags. */
|
|
49
51
|
rerun: string;
|
|
50
52
|
agentDir: string;
|
|
51
|
-
config:
|
|
53
|
+
config: EffectiveConfig;
|
|
52
54
|
pre: Extract<DeployPreflight, {
|
|
53
55
|
ok: true;
|
|
54
56
|
}>;
|
|
57
|
+
/** Whether the deployment serves the Agent API, so whether the deploy mints its key. */
|
|
58
|
+
api: DeploymentApi;
|
|
55
59
|
channels: readonly DeclaredChannel[];
|
|
56
60
|
webhookChannels: readonly DeclaredChannel[];
|
|
57
61
|
/** Write this host's planned artifacts into the agent directory under the ownership rule. */
|
|
@@ -78,8 +82,15 @@ export interface HostDeploy {
|
|
|
78
82
|
}
|
|
79
83
|
/** Where a step left to the person goes for `answerer`: a ticket's `afterwards`; nowhere else but the log. */
|
|
80
84
|
export declare function leftToPerson(answerer: Answerer): ((step: string) => void) | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* At a terminal, with a `--config` file to write to: record each URL a person sets on a platform's page once the box
|
|
87
|
+
* confirms it, so later deploys of that configuration, CI included, report it `registered` from the start.
|
|
88
|
+
*/
|
|
89
|
+
export declare function urlRecorder(agentDir: string, config: string | undefined, values: ReadonlyMap<string, string>, answerer: Answerer): {
|
|
90
|
+
recordUrl?: UrlRecorder;
|
|
91
|
+
};
|
|
81
92
|
/** The registrars every host driver gets; a channel's manual step also goes to `answerer`'s person. */
|
|
82
|
-
export declare function registrarsFor(
|
|
93
|
+
export declare function registrarsFor(values: ReadonlyMap<string, string>, answerer: Answerer): Registrars;
|
|
83
94
|
/**
|
|
84
95
|
* The login `--run` starts on the box it just deployed, before any webhook is pointed at it (a host driver's `boxLogin`): for
|
|
85
96
|
* the model's provider, keeping a credential the box already holds (a redeploy), and asking only when a person can
|
|
@@ -3,28 +3,45 @@
|
|
|
3
3
|
* ownership rule.
|
|
4
4
|
*/
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
-
import {
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
7
|
import { registerFeishuWebhook } from "../../../channels/feishu/register-webhook.js";
|
|
8
8
|
import { DEPLOY_REGISTRATION_ATTEMPTS } from "../../../channels/registration.js";
|
|
9
|
-
import { registerSlackWebhook } from "../../../channels/slack/register-webhook.js";
|
|
10
9
|
import { registerTelegramWebhook } from "../../../channels/telegram/register-webhook.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { recordChannelUrl } from "../../channel-app.js";
|
|
11
|
+
import { BUILD_OUTPUTS, IMAGE_APP_DIR, isGeneratedDockerfile, isGeneratedDockerignore, } from "../../../deploy/container.js";
|
|
12
|
+
import { exists } from "../../../paths.js";
|
|
13
13
|
import { deploymentLoginCommand } from "../../../deploy/box-shell.js";
|
|
14
14
|
import { loginOnBox, ticketLoginIO } from "../../box-login.js";
|
|
15
15
|
/** Where a step left to the person goes for `answerer`: a ticket's `afterwards`; nowhere else but the log. */
|
|
16
16
|
export function leftToPerson(answerer) {
|
|
17
17
|
return answerer.kind === "ticket" ? (step) => answerer.step.person.afterwards(step) : undefined;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* At a terminal, with a `--config` file to write to: record each URL a person sets on a platform's page once the box
|
|
21
|
+
* confirms it, so later deploys of that configuration, CI included, report it `registered` from the start.
|
|
22
|
+
*/
|
|
23
|
+
export function urlRecorder(agentDir, config, values, answerer) {
|
|
24
|
+
if (answerer.kind !== "terminal" || config === undefined)
|
|
25
|
+
return {};
|
|
26
|
+
const file = resolve(config);
|
|
27
|
+
return {
|
|
28
|
+
recordUrl: {
|
|
29
|
+
waitMs: 10 * 60_000,
|
|
30
|
+
write: async (kind, url) => {
|
|
31
|
+
await recordChannelUrl(agentDir, file, kind, url, values);
|
|
32
|
+
return file;
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
19
37
|
/** The registrars every host driver gets; a channel's manual step also goes to `answerer`'s person. */
|
|
20
|
-
export function registrarsFor(
|
|
38
|
+
export function registrarsFor(values, answerer) {
|
|
21
39
|
const attempts = DEPLOY_REGISTRATION_ATTEMPTS;
|
|
22
40
|
const env = Object.fromEntries(values);
|
|
23
41
|
const leave = leftToPerson(answerer);
|
|
24
42
|
return {
|
|
25
43
|
...(leave === undefined ? {} : { leave }),
|
|
26
44
|
telegram: (baseUrl) => registerTelegramWebhook(baseUrl, { attempts, env }),
|
|
27
|
-
slack: (baseUrl) => registerSlackWebhook(baseUrl, { stateRoot: resolveStateRoot(agentDir), environment: "production", attempts }),
|
|
28
45
|
feishu: (baseUrl, kind) => registerFeishuWebhook(baseUrl, kind, { attempts, env }),
|
|
29
46
|
};
|
|
30
47
|
}
|
|
@@ -73,7 +90,7 @@ export async function planArtifacts(target, artifacts, options) {
|
|
|
73
90
|
for (const a of artifacts) {
|
|
74
91
|
const abs = join(target, a.path);
|
|
75
92
|
// Pure build output, not operator-owned configuration. It must track the generated template/runbook.
|
|
76
|
-
if (BUILD_OUTPUTS.includes(
|
|
93
|
+
if (BUILD_OUTPUTS.includes(a.path) || options.alwaysWrite?.includes(a.path)) {
|
|
77
94
|
write(a.path, abs, a.content);
|
|
78
95
|
continue;
|
|
79
96
|
}
|
|
@@ -86,7 +103,7 @@ export async function planArtifacts(target, artifacts, options) {
|
|
|
86
103
|
(a.path.endsWith(".dockerignore")
|
|
87
104
|
? `; see the preflight warnings for what it must exclude`
|
|
88
105
|
: a.path.endsWith("Dockerfile")
|
|
89
|
-
? `; deploy still assumes it listens on $PORT and runs \`fastagent start ${
|
|
106
|
+
? `; deploy still assumes it listens on $PORT and runs \`fastagent start ${IMAGE_APP_DIR}\``
|
|
90
107
|
: ``));
|
|
91
108
|
continue;
|
|
92
109
|
}
|