@fastagent-sh/fastagent 0.29.0 → 0.30.1
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/kernel.js +3 -0
- 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.d.ts +7 -0
- package/dist/cli/ticket.js +106 -30
- 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
package/dist/content/resolve.js
CHANGED
|
@@ -1,83 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WHERE each content entry is, for THIS instance: the one answer the prompt, the coding tools, authored tools, `info`
|
|
3
|
-
* and `deploy` all read (docs/design/core.md §2). A repository
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* and `deploy` all read (docs/design/core.md §2). A repository is fastagent's clone at `content/<name>` in the
|
|
4
|
+
* instance's working directory; a directory is where the instance configuration says. Reads the disk (git included);
|
|
5
|
+
* never the network, never a write. Making a clone real is `cloneContent`'s, and only a process that runs the agent
|
|
6
|
+
* asks for it.
|
|
7
7
|
*/
|
|
8
8
|
import { lstatSync, realpathSync, statSync } from "node:fs";
|
|
9
9
|
import { basename, dirname, join } from "node:path";
|
|
10
|
-
import {
|
|
10
|
+
import { contentEntryPath } from "../paths.js";
|
|
11
11
|
import { nestingError } from "./declare.js";
|
|
12
|
-
import {
|
|
12
|
+
import { refreshClone } from "./git.js";
|
|
13
13
|
import { ensureContentDir } from "./mount.js";
|
|
14
14
|
/** Resolve the agent's content for this instance. Refuses, naming the entry, what cannot be. */
|
|
15
|
-
export function resolveContent(
|
|
16
|
-
|
|
17
|
-
const local = bound
|
|
18
|
-
.filter((binding) => binding.kind === "local" && statOrMissing(binding.path) !== undefined)
|
|
19
|
-
.map((binding) => resolveLocal(agentDir, binding));
|
|
20
|
-
return [...shared, ...local];
|
|
15
|
+
export function resolveContent(declared, places) {
|
|
16
|
+
return declared.map((entry) => entry.kind === "github" ? resolveRepository(places, entry) : resolveLocal(places, entry));
|
|
21
17
|
}
|
|
22
|
-
|
|
23
|
-
* The bindings of a directory only this machine has that point at nothing here: the directory was moved or removed.
|
|
24
|
-
* Not an error, since the binding is this machine's alone; the agent is not told of it, and whoever opens the agent
|
|
25
|
-
* says so.
|
|
26
|
-
*/
|
|
27
|
-
export function contentAbsentHere({ bound }) {
|
|
28
|
-
return bound.filter((binding) => binding.kind === "local" && statOrMissing(binding.path) === undefined);
|
|
29
|
-
}
|
|
30
|
-
/** What is said of a binding {@link contentAbsentHere} returns, after the word "content". */
|
|
31
|
-
export function absentNotice(binding) {
|
|
32
|
-
return (`${binding.name}: bound in ${LOCAL_CONTEXT_FILE} to ${binding.path}, which does not exist here: the agent is ` +
|
|
33
|
-
`not told of it`);
|
|
34
|
-
}
|
|
35
|
-
function resolveRepository(agentDir, entry, checkout) {
|
|
18
|
+
function resolveRepository({ workdir }, entry) {
|
|
36
19
|
const { name, description } = entry;
|
|
37
|
-
const
|
|
38
|
-
name,
|
|
39
|
-
...(description !== undefined ? { description } : {}),
|
|
40
|
-
kind: "github",
|
|
41
|
-
repo: entry.repo,
|
|
42
|
-
...(entry.ref !== undefined ? { ref: entry.ref } : {}),
|
|
43
|
-
};
|
|
44
|
-
if (checkout !== undefined && statOrMissing(checkout.path) !== undefined) {
|
|
45
|
-
const location = realpathSync(checkout.path);
|
|
46
|
-
const notices = checkBinding(agentDir, name, location, entry);
|
|
47
|
-
return { ...repository, location, notices, clone: false };
|
|
48
|
-
}
|
|
49
|
-
const location = contentEntryPath(agentDir, name);
|
|
20
|
+
const location = contentEntryPath(workdir, name);
|
|
50
21
|
const found = lstatOrMissing(location);
|
|
51
22
|
if (found?.isSymbolicLink()) {
|
|
52
23
|
throw new Error(`content "${name}": content/${name} is a link, and content/ holds only the clones fastagent makes — remove ` +
|
|
53
|
-
`the link, and
|
|
24
|
+
`the link, and add the directory it points at as content of its own (fastagent content add <dir>)`);
|
|
54
25
|
}
|
|
55
26
|
if (found && !found.isDirectory())
|
|
56
27
|
throw new Error(`content "${name}": ${location} is not a directory`);
|
|
57
|
-
const notices = found ? [] : ["not cloned yet: it is cloned when the agent starts"];
|
|
58
|
-
if (checkout !== undefined) {
|
|
59
|
-
notices.unshift(`bound in ${LOCAL_CONTEXT_FILE} to ${checkout.path}, which does not exist here: the clone is used`);
|
|
60
|
-
}
|
|
61
|
-
return { ...repository, location, notices, clone: true };
|
|
62
|
-
}
|
|
63
|
-
function resolveLocal(agentDir, binding) {
|
|
64
|
-
const { name, description } = binding;
|
|
65
|
-
const location = realpathSync(binding.path);
|
|
66
|
-
checkBinding(agentDir, name, location);
|
|
67
28
|
return {
|
|
68
29
|
name,
|
|
69
30
|
...(description !== undefined ? { description } : {}),
|
|
31
|
+
kind: "github",
|
|
32
|
+
repo: entry.repo,
|
|
33
|
+
...(entry.ref !== undefined ? { ref: entry.ref } : {}),
|
|
70
34
|
location,
|
|
71
|
-
notices: [],
|
|
35
|
+
notices: found ? [] : ["not cloned yet: it is cloned when the agent starts"],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function resolveLocal(places, entry) {
|
|
39
|
+
const { name, description } = entry;
|
|
40
|
+
checkDirectory(places, name, entry.path);
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
...(description !== undefined ? { description } : {}),
|
|
72
44
|
kind: "local",
|
|
45
|
+
location: realpathSync(entry.path),
|
|
46
|
+
notices: [],
|
|
73
47
|
};
|
|
74
48
|
}
|
|
75
49
|
/**
|
|
76
|
-
* Whether the content `name` can be
|
|
77
|
-
* directory,
|
|
78
|
-
*
|
|
50
|
+
* Whether the content `name` can be the directory `target` of this machine: one that exists, and is apart from the
|
|
51
|
+
* definition's directory, since a definition is released, content is kept, and one directory cannot be both. The
|
|
52
|
+
* working directory is the instance's own writable place, so a directory inside it is only a directory the agent
|
|
53
|
+
* already reaches. Refuses, naming the entry, what cannot be.
|
|
79
54
|
*/
|
|
80
|
-
export function
|
|
55
|
+
export function checkDirectory({ dir }, name, target) {
|
|
81
56
|
const stat = statOrMissing(target);
|
|
82
57
|
if (!stat)
|
|
83
58
|
throw new Error(`content "${name}": ${target} does not exist`);
|
|
@@ -85,25 +60,16 @@ export function checkBinding(agentDir, name, target, repository) {
|
|
|
85
60
|
throw new Error(`content "${name}": ${target} is not a directory`);
|
|
86
61
|
// A symlink can still put one inside the other, so ask of the real paths — including for an agent directory `init`
|
|
87
62
|
// is about to create, which a symlinked ancestor still places.
|
|
88
|
-
const nested = nestingError(realPathOf(
|
|
63
|
+
const nested = nestingError(realPathOf(dir), realpathSync(target), name);
|
|
89
64
|
if (nested)
|
|
90
65
|
throw new Error(nested);
|
|
91
|
-
if (repository === undefined)
|
|
92
|
-
return [];
|
|
93
|
-
const problem = checkoutProblem(target, repository.repo);
|
|
94
|
-
if (problem)
|
|
95
|
-
throw new Error(`content "${name}": ${target} ${problem}`);
|
|
96
|
-
const offRef = repository.ref === undefined ? undefined : refNotice(target, repository.ref);
|
|
97
|
-
return offRef ? [offRef] : [];
|
|
98
66
|
}
|
|
99
67
|
/**
|
|
100
68
|
* Make a clone real, or bring it up to date at its `ref` in place, by git's rules, which never overwrite what the
|
|
101
69
|
* agent did in it (git.ts `refreshClone`): what git refuses, or a remote that cannot be reached, keeps it as it is,
|
|
102
|
-
* with the reason. Only a process that runs the agent calls this.
|
|
70
|
+
* with the reason. Only a process that runs the agent calls this.
|
|
103
71
|
*/
|
|
104
72
|
export async function cloneContent(entry, env = process.env) {
|
|
105
|
-
if (!entry.clone)
|
|
106
|
-
throw new Error(`content "${entry.name}" is bound to the checkout ${entry.location}, not a clone`);
|
|
107
73
|
ensureContentDir(dirname(entry.location));
|
|
108
74
|
return refreshClone(entry.repo, entry.ref, entry.location, env).catch((error) => {
|
|
109
75
|
throw new Error(`content "${entry.name}": ${error.message}`);
|
package/dist/content/source.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ContentEntry } from "./declare.ts";
|
|
2
2
|
export interface SourceOptions {
|
|
3
3
|
name?: string;
|
|
4
4
|
/** A github entry's branch, tag or commit. */
|
|
@@ -6,19 +6,18 @@ export interface SourceOptions {
|
|
|
6
6
|
description?: string;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* A content entry to add, by name
|
|
10
|
-
*
|
|
9
|
+
* A content entry to add, by name. It is written to the instance's `fastagent.json`, or, with `program`, to the
|
|
10
|
+
* program's `context.json`, which every instance of it reaches and so holds repositories only.
|
|
11
11
|
*/
|
|
12
12
|
export interface ContentAddition {
|
|
13
13
|
name: string;
|
|
14
|
-
entry
|
|
15
|
-
|
|
14
|
+
entry: ContentEntry;
|
|
15
|
+
program?: boolean;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* The addition `source` asks for, with what to tell the user about how it was read. `github:owner/repo` is a
|
|
19
|
-
* repository fastagent clones.
|
|
20
|
-
*
|
|
21
|
-
* bound on this machine alone. The name defaults to the repository's or the directory's.
|
|
19
|
+
* repository fastagent clones. Any other source is a directory the agent works in as it is, a checkout of a GitHub
|
|
20
|
+
* repository included, which is then said. The name defaults to the repository's or the directory's.
|
|
22
21
|
*/
|
|
23
22
|
export declare function readContentSource(source: string, cwd: string, options?: SourceOptions): {
|
|
24
23
|
addition: ContentAddition;
|
package/dist/content/source.js
CHANGED
|
@@ -1,52 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a command's `<source>` adds (`init --content`, `fastagent content add`; docs/design/agent-cli.md §3), read the
|
|
3
|
-
* same way by both: `github:owner/repo`,
|
|
3
|
+
* same way by both: `github:owner/repo`, or a directory.
|
|
4
4
|
*/
|
|
5
|
-
import { realpathSync } from "node:fs";
|
|
6
5
|
import { basename, resolve } from "node:path";
|
|
7
6
|
import { isGithubRepo } from "./declare.js";
|
|
8
7
|
import { checkoutOf, githubRepoOf } from "./git.js";
|
|
9
8
|
/**
|
|
10
9
|
* The addition `source` asks for, with what to tell the user about how it was read. `github:owner/repo` is a
|
|
11
|
-
* repository fastagent clones.
|
|
12
|
-
*
|
|
13
|
-
* bound on this machine alone. The name defaults to the repository's or the directory's.
|
|
10
|
+
* repository fastagent clones. Any other source is a directory the agent works in as it is, a checkout of a GitHub
|
|
11
|
+
* repository included, which is then said. The name defaults to the repository's or the directory's.
|
|
14
12
|
*/
|
|
15
13
|
export function readContentSource(source, cwd, options = {}) {
|
|
16
|
-
const treated = {
|
|
17
|
-
...(options.description !== undefined ? { description: options.description } : {}),
|
|
18
|
-
};
|
|
19
|
-
const ref = options.ref !== undefined ? { ref: options.ref } : {};
|
|
14
|
+
const treated = options.description !== undefined ? { description: options.description } : {};
|
|
20
15
|
const repoName = (repo) => repo.slice(repo.indexOf("/") + 1);
|
|
21
16
|
if (source.startsWith("github:")) {
|
|
22
17
|
const repo = source.slice("github:".length);
|
|
23
18
|
if (!isGithubRepo(repo))
|
|
24
19
|
throw new Error(`${source} names no repository: write github:owner/repo`);
|
|
20
|
+
const ref = options.ref !== undefined ? { ref: options.ref } : {};
|
|
25
21
|
return {
|
|
26
22
|
addition: { name: options.name ?? repoName(repo), entry: { github: repo, ...ref, ...treated } },
|
|
27
23
|
notes: [],
|
|
28
24
|
};
|
|
29
25
|
}
|
|
26
|
+
if (options.ref !== undefined) {
|
|
27
|
+
throw new Error(`--ref applies to a repository, and ${resolve(cwd, source)} is a directory`);
|
|
28
|
+
}
|
|
30
29
|
const dir = resolve(cwd, source);
|
|
31
30
|
const checkout = checkoutOf(dir);
|
|
32
31
|
const repo = checkout?.origin === undefined ? undefined : githubRepoOf(checkout.origin);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const root = checkout.root;
|
|
36
|
-
return {
|
|
37
|
-
addition: {
|
|
38
|
-
name: options.name ?? repoName(repo),
|
|
39
|
-
entry: { github: repo, ...ref, ...treated },
|
|
40
|
-
binding: { path: root },
|
|
41
|
-
},
|
|
42
|
-
notes: [],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
if (options.ref !== undefined) {
|
|
46
|
-
throw new Error(`--ref applies to a repository, and ${dir} is bound as a directory of this machine`);
|
|
47
|
-
}
|
|
48
|
-
const notes = repo !== undefined && !atRoot
|
|
49
|
-
? [`${dir} is in a checkout of github ${repo}: add github:${repo} for the whole repository`]
|
|
50
|
-
: [];
|
|
51
|
-
return { addition: { name: options.name ?? basename(dir), binding: { path: dir, ...treated } }, notes };
|
|
32
|
+
const notes = repo === undefined ? [] : [`${dir} is in a checkout of github ${repo}: add github:${repo} to have it cloned`];
|
|
33
|
+
return { addition: { name: options.name ?? basename(dir), entry: { path: dir, ...treated } }, notes };
|
|
52
34
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SetupGuide } from "./channels/setup.ts";
|
|
2
|
+
/** What the person pastes at one step: its fields are checked together. */
|
|
3
|
+
export interface CredentialsStep {
|
|
4
|
+
label: string;
|
|
5
|
+
guide: SetupGuide;
|
|
6
|
+
fields: readonly {
|
|
7
|
+
name: string;
|
|
8
|
+
label: string;
|
|
9
|
+
secret?: boolean;
|
|
10
|
+
}[];
|
|
11
|
+
}
|
|
12
|
+
export interface CredentialsPage {
|
|
13
|
+
url: string;
|
|
14
|
+
/** Settles once every value was accepted; rejects when `accept` failed for a reason other than a refusal. */
|
|
15
|
+
done: Promise<void>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare function startCredentialsPage(input: {
|
|
19
|
+
/** "Connect Slack". */
|
|
20
|
+
title: string;
|
|
21
|
+
/** What the done page says: "Slack connected". */
|
|
22
|
+
doneHeading: string;
|
|
23
|
+
steps: readonly CredentialsStep[];
|
|
24
|
+
/** Check a step's values and keep them. Throws {@link SetupRefused} to stay on the form with the reason; anything else ends the page. */
|
|
25
|
+
accept(step: number, values: Record<string, string>): Promise<void>;
|
|
26
|
+
}): Promise<CredentialsPage>;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page where a person pastes the values a channel's setup needs, for a setup that has no terminal (a coding agent's
|
|
3
|
+
* `--json`): loopback only, one use, and each value goes from the browser to this process and nowhere else (see
|
|
4
|
+
* setup-page.ts for why it is never the agent's conversation). Each value is checked as it is pasted and handed to
|
|
5
|
+
* `accept` at once, so a step that passed stays accepted when another is refused: the form then asks only for what is left.
|
|
6
|
+
*/
|
|
7
|
+
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { createServer } from "node:http";
|
|
9
|
+
import { SetupRefused } from "./channels/setup.js";
|
|
10
|
+
import { field, formCard, openButton, privacy, prose, refusal, sendPage, setupRequestUrl, statusCard, step, submit, } from "./setup-page.js";
|
|
11
|
+
function readBody(request) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
let body = "";
|
|
14
|
+
let tooLarge = false;
|
|
15
|
+
request.on("data", (chunk) => {
|
|
16
|
+
if (tooLarge)
|
|
17
|
+
return;
|
|
18
|
+
body += String(chunk);
|
|
19
|
+
if (Buffer.byteLength(body) > 16 * 1024)
|
|
20
|
+
tooLarge = true;
|
|
21
|
+
});
|
|
22
|
+
request.on("end", () => resolve(tooLarge ? undefined : body));
|
|
23
|
+
request.on("error", () => resolve(undefined));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// A browser can leave during validation, before either response event can be subscribed to.
|
|
27
|
+
function afterResponse(response, complete) {
|
|
28
|
+
if (response.destroyed || response.writableFinished)
|
|
29
|
+
complete();
|
|
30
|
+
else
|
|
31
|
+
response.once("close", complete);
|
|
32
|
+
}
|
|
33
|
+
export async function startCredentialsPage(input) {
|
|
34
|
+
const path = `/setup/${randomBytes(24).toString("hex")}`;
|
|
35
|
+
const { promise: done, resolve, reject } = Promise.withResolvers();
|
|
36
|
+
const accepted = new Set();
|
|
37
|
+
let checking = false;
|
|
38
|
+
let used = false;
|
|
39
|
+
let port = 0;
|
|
40
|
+
const form = (problem) => formCard(input.title, input.steps
|
|
41
|
+
.map(({ label, guide, fields }, index) => {
|
|
42
|
+
if (accepted.has(index))
|
|
43
|
+
return step(index + 1, label, prose(`Saved.`));
|
|
44
|
+
const here = problem?.step === index ? problem.refusal : undefined;
|
|
45
|
+
return step(index + 1, label, prose(guide.instructions) +
|
|
46
|
+
(guide.url === undefined ? "" : openButton("Open", guide.url)) +
|
|
47
|
+
(here === undefined
|
|
48
|
+
? ""
|
|
49
|
+
: refusal(here.message, here.fix === undefined ? undefined : ` ${here.fix}`, here.why)) +
|
|
50
|
+
fields
|
|
51
|
+
.map((one, position) => field({
|
|
52
|
+
id: one.name,
|
|
53
|
+
label: one.label,
|
|
54
|
+
secret: one.secret ?? true,
|
|
55
|
+
invalid: here !== undefined,
|
|
56
|
+
autofocus: here !== undefined && position === 0,
|
|
57
|
+
}))
|
|
58
|
+
.join(""));
|
|
59
|
+
})
|
|
60
|
+
.join("") +
|
|
61
|
+
submit("Save and check") +
|
|
62
|
+
privacy());
|
|
63
|
+
const server = createServer(async (request, response) => {
|
|
64
|
+
const url = setupRequestUrl(request, response);
|
|
65
|
+
if (url === undefined)
|
|
66
|
+
return;
|
|
67
|
+
const host = request.headers.host ?? "";
|
|
68
|
+
if (host !== `127.0.0.1:${port}` && host !== `localhost:${port}`) {
|
|
69
|
+
response.writeHead(421).end("misdirected request\n");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (url.pathname !== path || (request.method !== "GET" && request.method !== "POST")) {
|
|
73
|
+
response.writeHead(404).end("not found\n");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const expired = () => sendPage(response, 404, {
|
|
77
|
+
title: "FastAgent setup",
|
|
78
|
+
card: statusCard({
|
|
79
|
+
state: "failed",
|
|
80
|
+
heading: "This page is no longer in use",
|
|
81
|
+
next: "Go back to your coding agent.",
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
if (used) {
|
|
85
|
+
expired();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (request.method === "GET") {
|
|
89
|
+
sendPage(response, 200, { title: `${input.title} — FastAgent`, card: form() });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const site = request.headers["sec-fetch-site"];
|
|
93
|
+
if (site !== undefined && site !== "same-origin") {
|
|
94
|
+
response.writeHead(403).end("forbidden\n");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const body = await readBody(request);
|
|
98
|
+
if (body === undefined) {
|
|
99
|
+
response.writeHead(413).end("payload too large\n");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (used) {
|
|
103
|
+
expired();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (checking) {
|
|
107
|
+
response.writeHead(409).end("another submission is being checked\n");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
checking = true;
|
|
111
|
+
const pasted = new URLSearchParams(body);
|
|
112
|
+
try {
|
|
113
|
+
for (const [index, { fields }] of input.steps.entries()) {
|
|
114
|
+
if (accepted.has(index))
|
|
115
|
+
continue;
|
|
116
|
+
try {
|
|
117
|
+
const values = {};
|
|
118
|
+
for (const { name, label } of fields) {
|
|
119
|
+
const value = (pasted.get(name) ?? "").trim();
|
|
120
|
+
if (value === "")
|
|
121
|
+
throw new SetupRefused(`${label} is needed.`);
|
|
122
|
+
values[name] = value;
|
|
123
|
+
}
|
|
124
|
+
await input.accept(index, values);
|
|
125
|
+
accepted.add(index);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
// Only a refusal stays on the form; infrastructure and persistence errors end the setup.
|
|
129
|
+
if (!(error instanceof SetupRefused))
|
|
130
|
+
throw error;
|
|
131
|
+
checking = false;
|
|
132
|
+
sendPage(response, 200, { title: `${input.title} — FastAgent`, card: form({ step: index, refusal: error }) });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
checking = false;
|
|
139
|
+
used = true;
|
|
140
|
+
afterResponse(response, () => reject(error));
|
|
141
|
+
sendPage(response, 200, {
|
|
142
|
+
title: "Setup stopped — FastAgent",
|
|
143
|
+
card: statusCard({
|
|
144
|
+
state: "failed",
|
|
145
|
+
heading: "Setup stopped",
|
|
146
|
+
lede: error instanceof Error ? error.message : String(error),
|
|
147
|
+
next: "Go back to your coding agent.",
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
used = true;
|
|
153
|
+
afterResponse(response, resolve);
|
|
154
|
+
sendPage(response, 200, {
|
|
155
|
+
title: `${input.doneHeading} — FastAgent`,
|
|
156
|
+
card: statusCard({
|
|
157
|
+
state: "done",
|
|
158
|
+
heading: input.doneHeading,
|
|
159
|
+
lede: "Your coding agent will say if anything is left to do.",
|
|
160
|
+
next: "Go back to your coding agent.",
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
await new Promise((resolveListening, rejectListening) => {
|
|
165
|
+
server.once("error", rejectListening);
|
|
166
|
+
server.listen(0, "127.0.0.1", () => {
|
|
167
|
+
server.off("error", rejectListening);
|
|
168
|
+
resolveListening();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
const address = server.address();
|
|
172
|
+
if (!address || typeof address === "string") {
|
|
173
|
+
server.close();
|
|
174
|
+
throw new Error("the credentials page did not receive a local TCP port");
|
|
175
|
+
}
|
|
176
|
+
port = address.port;
|
|
177
|
+
return {
|
|
178
|
+
url: `http://127.0.0.1:${port}${path}`,
|
|
179
|
+
done,
|
|
180
|
+
close: () => new Promise((resolveClosed) => {
|
|
181
|
+
server.close(() => resolveClosed());
|
|
182
|
+
server.closeAllConnections();
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -19,19 +19,26 @@ export interface DeclaredSecret {
|
|
|
19
19
|
name: string;
|
|
20
20
|
/** Where it was declared: "src/tools/x-post.ts", "fastagent.config.ts providers[0]", "context.json" — printed in
|
|
21
21
|
* runbooks and failures, so it must name a place the author can open. */
|
|
22
|
-
|
|
22
|
+
declaredIn: string;
|
|
23
23
|
/** What it is for, when the declaration says (`context.json`'s variables do): printed beside it in runbooks. */
|
|
24
24
|
description?: string;
|
|
25
25
|
/** The code runs without it: no start is refused for its absence. */
|
|
26
|
-
optional
|
|
26
|
+
optional: boolean;
|
|
27
|
+
/** Declared by one way of a channel that receives either way (`byIngress`): read only when it receives that way. */
|
|
28
|
+
when?: {
|
|
29
|
+
channel: string;
|
|
30
|
+
ingress: "webhook" | "connection";
|
|
31
|
+
};
|
|
27
32
|
}
|
|
28
33
|
/** Whether `name` has the shape of an env-var name ({@link ENV_NAME}). */
|
|
29
34
|
export declare function isEnvName(name: string): boolean;
|
|
30
35
|
/** Declared names as a stable list, first declaration winning — the form a carrier iterates. */
|
|
31
36
|
export declare function dedupeSecrets(declared: readonly DeclaredSecret[]): DeclaredSecret[];
|
|
37
|
+
/** The values of `secrets` that are set: what an environment is made of, since a name given no value is not one. */
|
|
38
|
+
export declare function definedValues(secrets: Readonly<Record<string, string | undefined>>): Record<string, string>;
|
|
32
39
|
/** Required names with no value in `env` — empty string counts as missing (an unset `.env` line). A name some
|
|
33
40
|
* declaration requires is required, whatever another says. */
|
|
34
|
-
export declare function missingSecrets(declared: readonly DeclaredSecret[], env
|
|
41
|
+
export declare function missingSecrets(declared: readonly DeclaredSecret[], env: NodeJS.ProcessEnv): DeclaredSecret[];
|
|
35
42
|
/** "X_API_KEY, X_API_SECRET (src/tools/x-post.ts); SLACK_TOKEN (src/channels/slack.ts)" — grouped by the
|
|
36
43
|
* file to open, since that is the unit the author fixes. */
|
|
37
44
|
export declare function describeSecrets(declared: readonly DeclaredSecret[]): string;
|
package/dist/declared-secrets.js
CHANGED
|
@@ -30,17 +30,21 @@ export function dedupeSecrets(declared) {
|
|
|
30
30
|
const seen = new Set();
|
|
31
31
|
return declared.filter((s) => !seen.has(s.name) && seen.add(s.name));
|
|
32
32
|
}
|
|
33
|
+
/** The values of `secrets` that are set: what an environment is made of, since a name given no value is not one. */
|
|
34
|
+
export function definedValues(secrets) {
|
|
35
|
+
return Object.fromEntries(Object.entries(secrets).filter((entry) => entry[1] !== undefined));
|
|
36
|
+
}
|
|
33
37
|
/** Required names with no value in `env` — empty string counts as missing (an unset `.env` line). A name some
|
|
34
38
|
* declaration requires is required, whatever another says. */
|
|
35
|
-
export function missingSecrets(declared, env
|
|
36
|
-
return dedupeSecrets(declared.filter((s) => s.optional
|
|
39
|
+
export function missingSecrets(declared, env) {
|
|
40
|
+
return dedupeSecrets(declared.filter((s) => !s.optional)).filter((s) => !env[s.name]);
|
|
37
41
|
}
|
|
38
42
|
/** "X_API_KEY, X_API_SECRET (src/tools/x-post.ts); SLACK_TOKEN (src/channels/slack.ts)" — grouped by the
|
|
39
43
|
* file to open, since that is the unit the author fixes. */
|
|
40
44
|
export function describeSecrets(declared) {
|
|
41
45
|
const bySource = new Map();
|
|
42
|
-
for (const { name,
|
|
43
|
-
bySource.set(
|
|
46
|
+
for (const { name, declaredIn } of dedupeSecrets(declared)) {
|
|
47
|
+
bySource.set(declaredIn, [...(bySource.get(declaredIn) ?? []), name]);
|
|
44
48
|
}
|
|
45
49
|
return [...bySource].map(([source, names]) => `${names.join(", ")} (${source})`).join("; ");
|
|
46
50
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `destroy agentcore`: removing what a deploy created, INCLUDING the parts the stack does not own.
|
|
3
3
|
*
|
|
4
|
-
* `aws cloudformation delete-stack` is the obvious move and it leaves
|
|
4
|
+
* `aws cloudformation delete-stack` is the obvious move and it leaves most of them behind, each for its own
|
|
5
5
|
* reason:
|
|
6
6
|
*
|
|
7
7
|
* - the S3 artifact bucket and the ECR repository must exist BEFORE the stack (it reads the forwarder's zip and
|
|
8
8
|
* the runtime's image from them), so they cannot be stack resources;
|
|
9
|
+
* - the Secrets Manager secret holds the deployment's values, which must exist before the first deploy and survive a
|
|
10
|
+
* stack update, so it is no stack resource either; it is deleted without a recovery window, since a window would
|
|
11
|
+
* keep its name from the next deployment of the agent (its values are pushed again from their source);
|
|
9
12
|
* - the forwarder's log group is created by AWS on the Lambda's first WRITE, so no template ever mentions it —
|
|
10
13
|
* and the deploy put a 14-day retention on it, i.e. it holds data;
|
|
11
14
|
* - the EventBridge schedules are minted at RUNTIME by the container: a recurring one per `schedules/` file, which
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { awsCli, awsJson, parseLogGroupNames } from "./aws-cli.js";
|
|
2
|
-
import { agentcoreRepoName, agentcoreStackName, deploymentBucketName, forwarderLogGroup, runtimeLogGroupPrefix, recurringSchedulePrefix, toRuntimeName, wakeAlarmPrefix, } from "./plan.js";
|
|
2
|
+
import { agentcoreRepoName, agentcoreSecretName, agentcoreStackName, deploymentBucketName, forwarderLogGroup, runtimeLogGroupPrefix, recurringSchedulePrefix, toRuntimeName, wakeAlarmPrefix, } from "./plan.js";
|
|
3
3
|
/** S3 deletes at most 1000 keys per call. */
|
|
4
4
|
const DELETE_BATCH = 1000;
|
|
5
5
|
/**
|
|
@@ -109,6 +109,19 @@ export async function destroyAgentcoreDeployment(plan, runner, announce = () =>
|
|
|
109
109
|
return gate(`could not tell whether repository ${repo} exists in ${region}: ${repoRead.unreadable}`);
|
|
110
110
|
}
|
|
111
111
|
const repoExists = "ok" in repoRead;
|
|
112
|
+
const secretName = agentcoreSecretName(plan.name);
|
|
113
|
+
const secretRead = await aws.present([
|
|
114
|
+
"secretsmanager",
|
|
115
|
+
"describe-secret",
|
|
116
|
+
"--secret-id",
|
|
117
|
+
secretName,
|
|
118
|
+
"--output",
|
|
119
|
+
"json",
|
|
120
|
+
]);
|
|
121
|
+
if ("unreadable" in secretRead) {
|
|
122
|
+
return gate(`could not tell whether secret ${secretName} exists in ${region}: ${secretRead.unreadable}`);
|
|
123
|
+
}
|
|
124
|
+
const secretExists = "ok" in secretRead;
|
|
112
125
|
// BOTH LOG GROUPS. AWS creates each on the first WRITE, so neither is a stack resource — same reason, twice —
|
|
113
126
|
// and the runtime's holds the agent's own stdout/stderr, i.e. what it said in every conversation. `deploy`'s
|
|
114
127
|
// runbook already knows there are two: it sets a retention on each.
|
|
@@ -173,6 +186,8 @@ export async function destroyAgentcoreDeployment(plan, runner, announce = () =>
|
|
|
173
186
|
found.push(`bucket ${bucket} (${keys.length} object version(s))`);
|
|
174
187
|
if (repoExists)
|
|
175
188
|
found.push(`repository ${repo}`);
|
|
189
|
+
if (secretExists)
|
|
190
|
+
found.push(`secret ${secretName} (its values are deleted for good, with no recovery window)`);
|
|
176
191
|
for (const name of listedGroups)
|
|
177
192
|
found.push(`log group ${name}`);
|
|
178
193
|
// The one thing this command refuses to delete.
|
|
@@ -245,6 +260,15 @@ export async function destroyAgentcoreDeployment(plan, runner, announce = () =>
|
|
|
245
260
|
if (repoExists) {
|
|
246
261
|
await attempt(`repository ${repo}`, ["ecr", "delete-repository", "--repository-name", repo, "--force"]);
|
|
247
262
|
}
|
|
263
|
+
if (secretExists) {
|
|
264
|
+
await attempt(`secret ${secretName}`, [
|
|
265
|
+
"secretsmanager",
|
|
266
|
+
"delete-secret",
|
|
267
|
+
"--secret-id",
|
|
268
|
+
secretName,
|
|
269
|
+
"--force-delete-without-recovery",
|
|
270
|
+
]);
|
|
271
|
+
}
|
|
248
272
|
for (const name of logGroups) {
|
|
249
273
|
await attempt(`log group ${name}`, ["logs", "delete-log-group", "--log-group-name", name]);
|
|
250
274
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by `fastagent deploy agentcore` — the deployment's only ingress.
|
|
2
2
|
// (Inside fastagent this file IS the source; the sentence below applies to the copy it is emitted as,
|
|
3
|
-
// the agent directory's `
|
|
3
|
+
// the agent directory's `agentcore/forwarder/index.js`.)
|
|
4
4
|
// REGENERATED ON EVERY DEPLOY; edits here are overwritten and never deployed. `--run` builds the
|
|
5
5
|
// Lambda package from fastagent's own copy of this source, so this file is the readable MIRROR of
|
|
6
6
|
// what runs (and what the manual runbook zips) — not an input you can change.
|
|
@@ -10,8 +10,7 @@ export interface AgentcorePlanInput extends ContainerInput {
|
|
|
10
10
|
*/
|
|
11
11
|
channels: readonly DeclaredChannel[];
|
|
12
12
|
/**
|
|
13
|
-
* The runbook's variable list (`deploymentSecrets`):
|
|
14
|
-
* carries.
|
|
13
|
+
* The runbook's variable list (`deploymentSecrets`): the declared names, each hinted by the file that declared it.
|
|
15
14
|
*/
|
|
16
15
|
secrets?: readonly DeploymentSecret[];
|
|
17
16
|
/**
|
|
@@ -51,7 +50,7 @@ export declare const SECRETS_DIR = "/mnt/data/.secrets";
|
|
|
51
50
|
export declare const DEFAULT_IDLE_TIMEOUT_SECONDS = 180;
|
|
52
51
|
/** One session's compute lifetime — AWS's own default. Both lifecycle fields cap at 1209600 (14 d). */
|
|
53
52
|
export declare const MAX_LIFETIME_SECONDS = 28800;
|
|
54
|
-
export declare const FORWARDER_FILE = "
|
|
53
|
+
export declare const FORWARDER_FILE = "agentcore/forwarder/index.js";
|
|
55
54
|
/**
|
|
56
55
|
* Where the forwarder puts the one-shot schedule for a wake-up. The id inside the name is minted in the
|
|
57
56
|
* container, so this prefix is the only handle anything outside has on the alarms a deployment has pending.
|
|
@@ -64,23 +63,10 @@ export declare function wakeAlarmPrefix(name: string): string;
|
|
|
64
63
|
export declare function recurringSchedulePrefix(name: string): string;
|
|
65
64
|
/** Account-suffixed artifact bucket for S3's global namespace. */
|
|
66
65
|
export declare function deploymentBucketName(name: string, account: string): string;
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
export declare const CARRIER_CHUNK_SIZE = 2000;
|
|
72
|
-
export declare const CARRIER_MAX_CHUNKS = 4;
|
|
73
|
-
/** The template's chunked carrier: CloudFormation parameter prefix → container env-var name. */
|
|
74
|
-
export declare const CARRIERS: readonly [{
|
|
75
|
-
readonly param: "FastagentEnv";
|
|
76
|
-
readonly env: "FASTAGENT_ENV";
|
|
77
|
-
readonly what: "base64 JSON of the value file's variables";
|
|
78
|
-
}];
|
|
79
|
-
/** Chunk `i` (0-based) of a carrier: its parameter and env-var names. */
|
|
80
|
-
export declare function carrierChunk(carrier: (typeof CARRIERS)[number], i: number): {
|
|
81
|
-
param: string;
|
|
82
|
-
env: string;
|
|
83
|
-
};
|
|
66
|
+
/** The Secrets Manager secret holding a deployment's values: one JSON object of NAME → value (agent-artifact.md §6.2). */
|
|
67
|
+
export declare function agentcoreSecretName(name: string): string;
|
|
68
|
+
/** The environment variable that tells the container which secret to read at its first invocation. */
|
|
69
|
+
export declare const SECRET_ARN_ENV = "FASTAGENT_SECRET_ARN";
|
|
84
70
|
/** The generated template's filename (namespaced under the kit in the agentDir layout). */
|
|
85
71
|
export declare const TEMPLATE_FILE = "agentcore.template.yaml";
|
|
86
72
|
/** The generated template's first-line marker. */
|
|
@@ -122,7 +108,7 @@ export declare const PROBE_PAYLOAD: string;
|
|
|
122
108
|
export declare function ingressSessionId(name: string): string;
|
|
123
109
|
/**
|
|
124
110
|
* The forwarder Lambda source — `forwarder.js` beside this file, the ONE text both the deployment package and the
|
|
125
|
-
* readable `
|
|
111
|
+
* readable `agentcore/forwarder/index.js` artifact are generated from.
|
|
126
112
|
*/
|
|
127
113
|
export declare function forwarderSource(): string;
|
|
128
114
|
/** Compute the AgentCore deploy plan from the resolved definition. */
|