@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
package/dist/mcp/config.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The MCP servers an instance opens (agent-artifact.md §3.3, §4.2): the program's, the `mcpServers` map of its
|
|
3
|
+
* `context.json`, and what the instance adds, the `mcpServers` map of its `fastagent.json`. One list, read once, that
|
|
4
|
+
* every reader (the open, `inspectMcp`, sign-in, the secrets list, the build's warning) is given.
|
|
5
|
+
*/
|
|
2
6
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
7
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
8
|
import * as Schema from "effect/Schema";
|
|
5
9
|
import * as SchemaIssue from "effect/SchemaIssue";
|
|
10
|
+
import { readProgramMcpServers } from "../content/file.js";
|
|
6
11
|
import { isEnvName } from "../declared-secrets.js";
|
|
7
12
|
import { McpError } from "../mcp-api.js";
|
|
8
|
-
|
|
13
|
+
import { CONTEXT_FILE } from "../paths.js";
|
|
9
14
|
const Strings = Schema.Record(Schema.String, Schema.String);
|
|
10
15
|
const Stdio = Schema.Struct({
|
|
11
16
|
type: Schema.optional(Schema.Literal("stdio")),
|
|
@@ -18,21 +23,15 @@ const Http = Schema.Struct({
|
|
|
18
23
|
url: Schema.String,
|
|
19
24
|
headers: Schema.optional(Strings),
|
|
20
25
|
});
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
const Declaration = Schema.Union([Stdio, Http]);
|
|
27
|
+
const Servers = Schema.Record(Schema.String, Declaration);
|
|
28
|
+
/** The `mcpServers` map `raw` holds (undefined is none), checked; every refusal names `file` and, where it can, the server. */
|
|
29
|
+
function declareServers(raw, file) {
|
|
30
|
+
if (raw === undefined)
|
|
31
|
+
return {};
|
|
32
|
+
let servers;
|
|
25
33
|
try {
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
if (error.code === "ENOENT")
|
|
30
|
-
return { mcpServers: {} };
|
|
31
|
-
throw error;
|
|
32
|
-
}
|
|
33
|
-
let config;
|
|
34
|
-
try {
|
|
35
|
-
config = Schema.decodeUnknownSync(Schema.fromJsonString(Configuration))(text, { onExcessProperty: "error" });
|
|
34
|
+
servers = Schema.decodeUnknownSync(Servers)(raw, { onExcessProperty: "error" });
|
|
36
35
|
}
|
|
37
36
|
catch (error) {
|
|
38
37
|
const paths = error instanceof Error && SchemaIssue.isIssue(error.cause)
|
|
@@ -40,43 +39,133 @@ export function readMcpConfiguration(dir) {
|
|
|
40
39
|
: [];
|
|
41
40
|
throw new McpError("configuration", `${file}: invalid MCP configuration at ${JSON.stringify(paths)}; expected stdio command/args/env or http url/headers; unknown fields are refused`);
|
|
42
41
|
}
|
|
43
|
-
for (const [name, server] of Object.entries(
|
|
42
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
44
43
|
if (!/^[a-zA-Z0-9_-]+$/.test(name) || name.includes("__") || name.length > 40) {
|
|
45
44
|
throw new McpError("configuration", `${file}: server names must be 1–40 letters, digits, hyphens or underscores, without __`);
|
|
46
45
|
}
|
|
47
46
|
if (server.type === "http")
|
|
48
|
-
checkMcpUrl(server.url, name);
|
|
47
|
+
checkMcpUrl(server.url, name, file);
|
|
49
48
|
else
|
|
50
49
|
for (const key of Object.keys(server.env ?? {})) {
|
|
51
50
|
if (!isEnvName(key))
|
|
52
51
|
throw new McpError("configuration", `${file}: invalid environment variable name for ${name}`, name);
|
|
53
52
|
}
|
|
54
53
|
for (const value of configStrings(server))
|
|
55
|
-
variables(value, name);
|
|
54
|
+
variables(value, name, file);
|
|
55
|
+
}
|
|
56
|
+
return servers;
|
|
57
|
+
}
|
|
58
|
+
/** An instance's servers, checked: what `resolveConfig` asks of a configuration, so its refusal is the file's. */
|
|
59
|
+
export function checkInstanceServers(instance) {
|
|
60
|
+
declareServers(mapOf(instance.values), instance.label);
|
|
61
|
+
}
|
|
62
|
+
const mapOf = (values) => typeof values === "object" && values !== null ? values.mcpServers : undefined;
|
|
63
|
+
/**
|
|
64
|
+
* The servers of the program in `dir` and of `instance`. An instance only adds: a name both declare is refused, naming
|
|
65
|
+
* both, so nothing is shadowed.
|
|
66
|
+
*/
|
|
67
|
+
export function declareMcp(dir, instance) {
|
|
68
|
+
const own = readProgramMcpServers(dir);
|
|
69
|
+
const program = Object.entries(declareServers(own.values, own.path)).map(([name, server]) => ({ name, server, file: CONTEXT_FILE, base: resolve(dir), origin: "program" }));
|
|
70
|
+
if (instance === undefined)
|
|
71
|
+
return program;
|
|
72
|
+
const added = Object.entries(declareServers(mapOf(instance.values), instance.label)).map(([name, server]) => ({
|
|
73
|
+
name,
|
|
74
|
+
server,
|
|
75
|
+
file: instance.label,
|
|
76
|
+
...(instance.directory === undefined ? {} : { base: instance.directory }),
|
|
77
|
+
origin: "instance",
|
|
78
|
+
}));
|
|
79
|
+
for (const { name } of added) {
|
|
80
|
+
if (program.some((declared) => declared.name === name)) {
|
|
81
|
+
throw new McpError("configuration", `MCP server "${name}" is declared by the program (${CONTEXT_FILE}) and by the instance (${instance.label}): an ` +
|
|
82
|
+
`instance adds to the program's servers and does not replace them — name it otherwise`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return [...program, ...added];
|
|
86
|
+
}
|
|
87
|
+
/** An instance configuration given to `inspectMcp` or `signInMcp`: an object, or the absolute path of a `fastagent.json`. */
|
|
88
|
+
export function mcpInstance(config) {
|
|
89
|
+
if (config === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
if (typeof config !== "string")
|
|
92
|
+
return { values: config, label: "the config object" };
|
|
93
|
+
if (!isAbsolute(config))
|
|
94
|
+
throw new McpError("configuration", `config must be an absolute path, and is ${JSON.stringify(config)}`);
|
|
95
|
+
let text;
|
|
96
|
+
try {
|
|
97
|
+
text = readFileSync(config, "utf8");
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new McpError("configuration", `cannot read the instance configuration ${config}: ${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return { values: JSON.parse(text), label: config, directory: dirname(config) };
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
throw new McpError("configuration", `${config} is not JSON: ${error.message}`);
|
|
56
107
|
}
|
|
57
|
-
return config;
|
|
58
108
|
}
|
|
59
109
|
function configStrings(server) {
|
|
60
110
|
return server.type === "http"
|
|
61
111
|
? Object.values(server.headers ?? {})
|
|
62
112
|
: [server.command, ...(server.args ?? []), ...Object.values(server.env ?? {})];
|
|
63
113
|
}
|
|
64
|
-
function variables(value, server) {
|
|
114
|
+
function variables(value, server, file) {
|
|
65
115
|
const names = [];
|
|
66
116
|
const rest = value.replace(/\$\{([^}]*)\}/g, (_match, name) => {
|
|
67
117
|
if (!isEnvName(name))
|
|
68
|
-
throw new McpError("configuration", `${
|
|
118
|
+
throw new McpError("configuration", `${file}: ${server} supports only \${NAME} substitutions`, server);
|
|
69
119
|
names.push(name);
|
|
70
120
|
return "";
|
|
71
121
|
});
|
|
72
122
|
if (rest.includes("${") || rest.includes("$("))
|
|
73
|
-
throw new McpError("configuration", `${
|
|
123
|
+
throw new McpError("configuration", `${file}: unsupported substitution in ${server}`, server);
|
|
74
124
|
return names;
|
|
75
125
|
}
|
|
76
|
-
|
|
77
|
-
|
|
126
|
+
/** The secrets the servers' `${NAME}` references need: each required, declared where the server is. */
|
|
127
|
+
export function mcpSecrets(declarations) {
|
|
128
|
+
return declarations.flatMap(({ name, server, file }) => configStrings(server).flatMap((value) => variables(value, name, file).map((secret) => ({
|
|
129
|
+
name: secret,
|
|
130
|
+
declaredIn: `${file} mcpServers.${name}`,
|
|
131
|
+
optional: false,
|
|
132
|
+
}))));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The header and environment values with no `${VAR}` in them: the format allows a literal credential, and where the
|
|
136
|
+
* file travels (a build publishes it, a deploy bakes or delivers it) each is said. FastAgent cannot tell a secret from a
|
|
137
|
+
* setting, so a caller warns and never refuses.
|
|
138
|
+
*/
|
|
139
|
+
export function literalCredentials(declarations) {
|
|
140
|
+
return declarations.flatMap(({ name, server, file }) => {
|
|
141
|
+
const [field, entries] = server.type === "http" ? ["headers", server.headers ?? {}] : ["env", server.env ?? {}];
|
|
142
|
+
return Object.entries(entries)
|
|
143
|
+
.filter(([, value]) => value !== "" && !value.includes("${"))
|
|
144
|
+
.map(([key]) => ({ file, server: name, field, key }));
|
|
145
|
+
});
|
|
78
146
|
}
|
|
79
|
-
|
|
147
|
+
/**
|
|
148
|
+
* The stdio servers whose command is a path relative to the file that declares it, as it is written: what a build or a
|
|
149
|
+
* deploy has to be able to deliver, since a script is not found on a `PATH`. A command that starts with a `${VAR}` is
|
|
150
|
+
* left to the variable, and `args` are never read as paths, since FastAgent cannot know which strings are.
|
|
151
|
+
*/
|
|
152
|
+
export function relativeCommands(declarations) {
|
|
153
|
+
return declarations.flatMap((declaration) => {
|
|
154
|
+
const { server } = declaration;
|
|
155
|
+
const command = server.type === "http" ? undefined : server.command;
|
|
156
|
+
return command !== undefined && /[/\\]/.test(command) && !isAbsolute(command) && !command.startsWith("${")
|
|
157
|
+
? [{ ...declaration, command }]
|
|
158
|
+
: [];
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** What to write instead of a literal credential, which a header's value usually carries a scheme in front of. */
|
|
162
|
+
export function literalCredentialAdvice({ field, key }) {
|
|
163
|
+
const name = key.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
164
|
+
return field === "headers"
|
|
165
|
+
? `replace the secret in it with \${${name}} (a scheme stays: \`Bearer \${${name}}\`)`
|
|
166
|
+
: `write \${${name}}`;
|
|
167
|
+
}
|
|
168
|
+
export function checkMcpUrl(value, server, file = "mcpServers") {
|
|
80
169
|
const url = URL.parse(value);
|
|
81
170
|
if (!url ||
|
|
82
171
|
value.includes("${") ||
|
|
@@ -86,32 +175,22 @@ export function checkMcpUrl(value, server) {
|
|
|
86
175
|
url.hash ||
|
|
87
176
|
(url.protocol !== "https:" &&
|
|
88
177
|
!(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))) {
|
|
89
|
-
throw new McpError("configuration", `${
|
|
178
|
+
throw new McpError("configuration", `${file}: MCP endpoints require HTTPS (HTTP is allowed only on loopback), without userinfo or fragments`, server);
|
|
90
179
|
}
|
|
91
180
|
return url;
|
|
92
181
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"TMP",
|
|
102
|
-
"TMPDIR",
|
|
103
|
-
"LANG",
|
|
104
|
-
"LC_ALL",
|
|
105
|
-
];
|
|
106
|
-
export function resolveMcpServers(dir, env, only) {
|
|
107
|
-
const config = readMcpConfiguration(dir);
|
|
108
|
-
return Object.entries(config.mcpServers)
|
|
109
|
-
.filter(([name]) => only === undefined || name === only)
|
|
110
|
-
.map(([name, server]) => {
|
|
182
|
+
export function resolveMcpServers(declarations, env, only,
|
|
183
|
+
/** Where a stdio server runs: the instance's workspace, whoever declared it. */
|
|
184
|
+
cwd,
|
|
185
|
+
/** What every process the instance starts gets (child-env.ts), under a stdio server's own `env`. */
|
|
186
|
+
processEnv) {
|
|
187
|
+
return declarations
|
|
188
|
+
.filter(({ name }) => only === undefined || name === only)
|
|
189
|
+
.map(({ name, server, file, base }) => {
|
|
111
190
|
const expand = (value) => value.replace(/\$\{([^}]*)\}/g, (_match, key) => {
|
|
112
191
|
const result = env[key];
|
|
113
192
|
if (!result)
|
|
114
|
-
throw new McpError("configuration", `${
|
|
193
|
+
throw new McpError("configuration", `${file}: ${name} requires ${key}`, name);
|
|
115
194
|
return result;
|
|
116
195
|
});
|
|
117
196
|
if (server.type === "http") {
|
|
@@ -120,21 +199,25 @@ export function resolveMcpServers(dir, env, only) {
|
|
|
120
199
|
new Headers(headers);
|
|
121
200
|
}
|
|
122
201
|
catch {
|
|
123
|
-
throw new McpError("configuration", `${
|
|
202
|
+
throw new McpError("configuration", `${file}: invalid HTTP headers for ${name}`, name);
|
|
124
203
|
}
|
|
125
|
-
return { name, type: "http", url: checkMcpUrl(server.url, name).href, headers };
|
|
204
|
+
return { name, type: "http", url: checkMcpUrl(server.url, name, file).href, headers };
|
|
126
205
|
}
|
|
127
206
|
let command = expand(server.command);
|
|
128
|
-
if (!isAbsolute(command) && /[/\\]/.test(command))
|
|
129
|
-
|
|
207
|
+
if (!isAbsolute(command) && /[/\\]/.test(command)) {
|
|
208
|
+
if (base === undefined) {
|
|
209
|
+
throw new McpError("configuration", `${file}: the command "${command}" has a path and no file to be relative to: write an absolute path or a bare command`, name);
|
|
210
|
+
}
|
|
211
|
+
command = resolve(base, command);
|
|
212
|
+
}
|
|
130
213
|
return {
|
|
131
214
|
name,
|
|
132
215
|
type: "stdio",
|
|
133
216
|
command,
|
|
134
217
|
args: (server.args ?? []).map(expand),
|
|
135
|
-
cwd: resolve(
|
|
218
|
+
cwd: resolve(cwd),
|
|
136
219
|
env: {
|
|
137
|
-
...
|
|
220
|
+
...processEnv,
|
|
138
221
|
...Object.fromEntries(Object.entries(server.env ?? {}).map(([key, value]) => [key, expand(value)])),
|
|
139
222
|
},
|
|
140
223
|
};
|
package/dist/mcp/host.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { type McpAuthorization, type McpOptions, type McpServerSummary } from "../mcp-api.ts";
|
|
1
|
+
import { type McpAuthorization, type McpInspectOptions, type McpOptions, type McpServerSummary } from "../mcp-api.ts";
|
|
2
2
|
import { type McpServer } from "./config.ts";
|
|
3
|
+
/** The project's `.secrets/`: where the CLI keeps them. A write first makes the directory ignored by git. */
|
|
3
4
|
export declare function mcpAuthorization(dir: string, env: NodeJS.ProcessEnv, stateRoot: string, options?: McpOptions): McpAuthorization;
|
|
5
|
+
/** The instance's state directory: where an app that is given no store keeps them, beside its other state. */
|
|
6
|
+
export declare function mcpAuthorizationInState(stateRoot: string, options?: McpOptions): McpAuthorization;
|
|
4
7
|
export declare function writeMcpBinding(stateRoot: string, server: string, id: string | undefined): Promise<void>;
|
|
5
8
|
export declare function mcpContext(dir: string, options?: McpOptions): {
|
|
6
9
|
agentDir: string;
|
|
@@ -13,4 +16,4 @@ export declare function mcpContext(dir: string, options?: McpOptions): {
|
|
|
13
16
|
export declare function httpServer(servers: readonly McpServer[], name: string): Extract<McpServer, {
|
|
14
17
|
type: "http";
|
|
15
18
|
}>;
|
|
16
|
-
export declare function inspectMcp(dir: string, options?:
|
|
19
|
+
export declare function inspectMcp(dir: string, options?: McpInspectOptions): Promise<McpServerSummary[]>;
|
package/dist/mcp/host.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import * as Schema from "effect/Schema";
|
|
5
|
-
import { McpError } from "../mcp-api.js";
|
|
5
|
+
import { McpError, } from "../mcp-api.js";
|
|
6
6
|
import { resolveSecretsDir, resolveStateRoot } from "../paths.js";
|
|
7
7
|
import { agentValues, prepareSecretsDirectory, unprotectedSecretsNote } from "../value-file.js";
|
|
8
8
|
import { log } from "../log.js";
|
|
9
9
|
import { withLockedFile } from "../locked-file.js";
|
|
10
10
|
import { checkedGrant, mcpGrantFile } from "./grants.js";
|
|
11
|
-
import {
|
|
11
|
+
import { checkMcpUrl, declareMcp, mcpInstance } from "./config.js";
|
|
12
12
|
const Bindings = Schema.Record(Schema.String, Schema.String);
|
|
13
13
|
function decodeBindings(raw) {
|
|
14
14
|
if (raw === undefined)
|
|
@@ -20,7 +20,16 @@ function decodeBindings(raw) {
|
|
|
20
20
|
throw new McpError("storage", "Invalid MCP bindings file; refusing to overwrite it");
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
/** The project's `.secrets/`: where the CLI keeps them. A write first makes the directory ignored by git. */
|
|
23
24
|
export function mcpAuthorization(dir, env, stateRoot, options = {}) {
|
|
25
|
+
const secretsDir = resolveSecretsDir(dir, env);
|
|
26
|
+
return authorizationIn({ file: join(secretsDir, "mcp-auth.json"), prepare: () => prepareSecretsDirectory(dir, secretsDir) }, stateRoot, options);
|
|
27
|
+
}
|
|
28
|
+
/** The instance's state directory: where an app that is given no store keeps them, beside its other state. */
|
|
29
|
+
export function mcpAuthorizationInState(stateRoot, options = {}) {
|
|
30
|
+
return authorizationIn({ file: join(stateRoot, "mcp-auth.json") }, stateRoot, options);
|
|
31
|
+
}
|
|
32
|
+
function authorizationIn(home, stateRoot, options) {
|
|
24
33
|
if ((options.store === undefined) !== (options.bindings === undefined))
|
|
25
34
|
throw new McpError("configuration", "MCP store and bindings must be supplied together");
|
|
26
35
|
if (options.store && options.bindings)
|
|
@@ -34,10 +43,9 @@ export function mcpAuthorization(dir, env, stateRoot, options = {}) {
|
|
|
34
43
|
if (error.code !== "ENOENT")
|
|
35
44
|
throw error;
|
|
36
45
|
}
|
|
37
|
-
const
|
|
38
|
-
const file = mcpGrantFile(join(secretsDir, "mcp-auth.json"));
|
|
46
|
+
const file = mcpGrantFile(home.file);
|
|
39
47
|
const protectedWrite = async (write) => {
|
|
40
|
-
const unprotected = await
|
|
48
|
+
const unprotected = await home.prepare?.();
|
|
41
49
|
await write();
|
|
42
50
|
if (unprotected !== undefined)
|
|
43
51
|
log.warn(`[fastagent] ${unprotectedSecretsNote(unprotected)}`);
|
|
@@ -85,7 +93,7 @@ export function httpServer(servers, name) {
|
|
|
85
93
|
}
|
|
86
94
|
export async function inspectMcp(dir, options = {}) {
|
|
87
95
|
const { agentDir, auth } = mcpContext(dir, options);
|
|
88
|
-
return Promise.all(
|
|
96
|
+
return Promise.all(declareMcp(agentDir, mcpInstance(options.config)).map(async ({ name, server: declaration }) => {
|
|
89
97
|
const server = { ...declaration, name, type: declaration.type ?? "stdio" };
|
|
90
98
|
const id = Object.hasOwn(auth.bindings, server.name) ? auth.bindings[server.name] : undefined;
|
|
91
99
|
const raw = id === undefined ? undefined : await auth.store.read(id);
|
package/dist/mcp/sign-in.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { agentValues } from "../value-file.js";
|
|
6
|
-
import { resolveMcpServers } from "./config.js";
|
|
6
|
+
import { declareMcp, mcpInstance, resolveMcpServers } from "./config.js";
|
|
7
7
|
import { registerClient, startAuthorization, exchangeAuthorization, selectClientAuthMethod, validateAuthorizationResponseIssuer, } from "@modelcontextprotocol/client";
|
|
8
8
|
import * as Effect from "effect/Effect";
|
|
9
9
|
import * as Deferred from "effect/Deferred";
|
|
@@ -24,7 +24,7 @@ export async function signInMcp(request) {
|
|
|
24
24
|
throw new McpError("configuration", "Invalid MCP sign-in timeout or callback port", request.server);
|
|
25
25
|
}
|
|
26
26
|
const dir = resolve(request.agentDir);
|
|
27
|
-
const server = httpServer(resolveMcpServers(dir, { ...process.env, ...agentValues(dir), ...request.variables }, request.server), request.server);
|
|
27
|
+
const server = httpServer(resolveMcpServers(declareMcp(dir, mcpInstance(request.config)), { ...process.env, ...agentValues(dir), ...request.variables }, request.server, dir, {}), request.server);
|
|
28
28
|
let saved;
|
|
29
29
|
const work = Effect.gen(function* () {
|
|
30
30
|
const network = yield* Effect.acquireRelease(port(() => openMcpNetwork(server.url, request.fetch)), (value) => portCleanup("MCP network", () => value.close()));
|
package/dist/mcp-api.d.ts
CHANGED
|
@@ -54,6 +54,18 @@ export interface McpOptions {
|
|
|
54
54
|
/** Trusted transport override: the host must enforce connection-level DNS/IP and redirect safety itself. */
|
|
55
55
|
fetch?: typeof globalThis.fetch;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The instance configuration as far as MCP reads it: an object, or the absolute path of a `fastagent.json`. Its
|
|
59
|
+
* `mcpServers` add to the program's (`context.json`), and a name in both refuses.
|
|
60
|
+
*/
|
|
61
|
+
export type McpConfigInput = {
|
|
62
|
+
mcpServers?: Readonly<Record<string, unknown>>;
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
} | string;
|
|
65
|
+
/** What `inspectMcp` is given beside the grants: the instance configuration whose servers it reads with the program's. */
|
|
66
|
+
export interface McpInspectOptions extends McpOptions {
|
|
67
|
+
config?: McpConfigInput;
|
|
68
|
+
}
|
|
57
69
|
export interface McpServerSummary {
|
|
58
70
|
name: string;
|
|
59
71
|
transport: "stdio" | "http";
|
|
@@ -90,4 +102,6 @@ export interface McpSignInRequest {
|
|
|
90
102
|
timeoutSeconds?: number;
|
|
91
103
|
variables?: Readonly<Record<string, string>>;
|
|
92
104
|
fetch?: McpOptions["fetch"];
|
|
105
|
+
/** The instance configuration whose servers are read with the program's. */
|
|
106
|
+
config?: McpConfigInput;
|
|
93
107
|
}
|
package/dist/paths.d.ts
CHANGED
|
@@ -14,20 +14,20 @@ export declare function selectAgentEnvironment(environment: AgentEnvironment, en
|
|
|
14
14
|
/** The state segment inside an agent dir — same rule and same template caveat as {@link SECRETS_DIRNAME}. */
|
|
15
15
|
export declare const STATE_DIRNAME = ".state";
|
|
16
16
|
/**
|
|
17
|
-
* Where fastagent clones the repositories
|
|
18
|
-
* override: a machine that has a checkout of its own
|
|
17
|
+
* Where fastagent clones the repositories an agent's content declares, `content/<name>` in the instance's working
|
|
18
|
+
* directory. Never an env override: a machine that has a checkout of its own adds it as a directory instead.
|
|
19
19
|
*/
|
|
20
20
|
export declare const CONTENT_DIRNAME = "content";
|
|
21
21
|
/** The agent's context declaration (`context.json`), beside its config: shared, so only what every instance reaches. */
|
|
22
22
|
export declare const CONTEXT_FILE = "context.json";
|
|
23
|
-
/** The directories of THIS machine the agent is bound to: never committed, never deployed. */
|
|
24
|
-
export declare const LOCAL_CONTEXT_FILE = "context.local.json";
|
|
25
23
|
/**
|
|
26
24
|
* THE config filename. One spelling, not a family: fastagent generates this file, so a choice of extension buys
|
|
27
25
|
* an author nothing and costs a precedence order plus a "you have two of them" failure path. Everything else an
|
|
28
26
|
* author writes (`tools/`, `channels/`) still accepts `.ts`/`.js`/`.mjs` — that is THEIR code.
|
|
29
27
|
*/
|
|
30
28
|
export declare const AGENT_CONFIG_FILE = "fastagent.config.ts";
|
|
29
|
+
/** The instance configuration a development project holds beside its `fastagent.config.ts`. */
|
|
30
|
+
export declare const INSTANCE_CONFIG_FILE = "fastagent.json";
|
|
31
31
|
/** The agent `dir` sits INSIDE (the nearest proper ancestor that IS an agent dir), or undefined. */
|
|
32
32
|
export declare function enclosingAgentDir(dir: string): string | undefined;
|
|
33
33
|
/**
|
|
@@ -50,10 +50,24 @@ export declare function readTextIfExists(p: string): Promise<string | undefined>
|
|
|
50
50
|
*/
|
|
51
51
|
export declare function resolveOverridePath(raw: string | undefined): string | undefined;
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* A path as an instance configuration writes it, absolute: `~/` is the home directory, a relative one is `base`'s (the
|
|
54
|
+
* configuration file's directory). Undefined for a relative one when there is no file to anchor it.
|
|
55
|
+
*/
|
|
56
|
+
export declare function configurationPath(path: string, base: string | undefined): string | undefined;
|
|
57
|
+
/** Where an instance is, from the environment: the root, or its state and its workspace apart (agent-artifact.md §4.1). */
|
|
58
|
+
export declare const INSTANCE_ROOT_ENV = "FASTAGENT_ROOT";
|
|
59
|
+
export declare const INSTANCE_WORKDIR_ENV = "FASTAGENT_WORKDIR";
|
|
60
|
+
export declare const INSTANCE_STATE_ENV = "FASTAGENT_STATE_DIR";
|
|
61
|
+
export declare const INSTANCE_CONFIG_ENV = "FASTAGENT_CONFIG";
|
|
62
|
+
/** Where fastagent keeps what it makes for a local instance of the project in `dir`, one directory per environment. */
|
|
63
|
+
export declare const INSTANCES_DIRNAME = ".fastagent";
|
|
64
|
+
/**
|
|
65
|
+
* The resolved state root — the durable machine-state home (sessions/, channels/<kind>/, schedule/): the state
|
|
66
|
+
* directory, else `.state` in the instance root, which is the project's default one when none is given.
|
|
55
67
|
*/
|
|
56
68
|
export declare function resolveStateRoot(dir: string, env?: NodeJS.ProcessEnv): string;
|
|
69
|
+
/** The agent's working directory: the workspace directory, else the instance root, which is the project's default one. */
|
|
70
|
+
export declare function resolveWorkdir(dir: string, env?: NodeJS.ProcessEnv): string;
|
|
57
71
|
/** Where fastagent clones the content entry `name` of the agent in `dir`. */
|
|
58
72
|
export declare function contentEntryPath(dir: string, name: string): string;
|
|
59
73
|
/**
|
|
@@ -63,9 +77,9 @@ export declare function contentEntryPath(dir: string, name: string): string;
|
|
|
63
77
|
export declare function resolveSecretsDir(dir: string, env?: NodeJS.ProcessEnv): string;
|
|
64
78
|
/** Is this process the deployed container the generated artifacts describe? ONE reading of the
|
|
65
79
|
* environment fact the image states, so a second spelling cannot drift from it. */
|
|
66
|
-
export declare function isDeployedWorkspace(): boolean;
|
|
80
|
+
export declare function isDeployedWorkspace(env?: NodeJS.ProcessEnv): boolean;
|
|
67
81
|
/** Is this process running inside the AgentCore Runtime? Set by the generated deploy artifacts. */
|
|
68
|
-
export declare function isAgentcoreRuntime(): boolean;
|
|
82
|
+
export declare function isAgentcoreRuntime(env?: NodeJS.ProcessEnv): boolean;
|
|
69
83
|
/**
|
|
70
84
|
* Which host this deployed box runs on, for the command that logs it in (`login --deployment <host>`). Read from what
|
|
71
85
|
* each platform puts in a box's environment (`FLY_APP_NAME`, `RAILWAY_SERVICE_ID`) or what our AgentCore template
|
package/dist/paths.js
CHANGED
|
@@ -29,20 +29,20 @@ function environmentPath(dir, segment, env) {
|
|
|
29
29
|
/** The state segment inside an agent dir — same rule and same template caveat as {@link SECRETS_DIRNAME}. */
|
|
30
30
|
export const STATE_DIRNAME = ".state";
|
|
31
31
|
/**
|
|
32
|
-
* Where fastagent clones the repositories
|
|
33
|
-
* override: a machine that has a checkout of its own
|
|
32
|
+
* Where fastagent clones the repositories an agent's content declares, `content/<name>` in the instance's working
|
|
33
|
+
* directory. Never an env override: a machine that has a checkout of its own adds it as a directory instead.
|
|
34
34
|
*/
|
|
35
35
|
export const CONTENT_DIRNAME = "content";
|
|
36
36
|
/** The agent's context declaration (`context.json`), beside its config: shared, so only what every instance reaches. */
|
|
37
37
|
export const CONTEXT_FILE = "context.json";
|
|
38
|
-
/** The directories of THIS machine the agent is bound to: never committed, never deployed. */
|
|
39
|
-
export const LOCAL_CONTEXT_FILE = "context.local.json";
|
|
40
38
|
/**
|
|
41
39
|
* THE config filename. One spelling, not a family: fastagent generates this file, so a choice of extension buys
|
|
42
40
|
* an author nothing and costs a precedence order plus a "you have two of them" failure path. Everything else an
|
|
43
41
|
* author writes (`tools/`, `channels/`) still accepts `.ts`/`.js`/`.mjs` — that is THEIR code.
|
|
44
42
|
*/
|
|
45
43
|
export const AGENT_CONFIG_FILE = "fastagent.config.ts";
|
|
44
|
+
/** The instance configuration a development project holds beside its `fastagent.config.ts`. */
|
|
45
|
+
export const INSTANCE_CONFIG_FILE = "fastagent.json";
|
|
46
46
|
/**
|
|
47
47
|
* "Not there" and "could not look" are different answers, and only the first may read as an absence: an agent
|
|
48
48
|
* directory the caller cannot enter (EACCES) reported as no-agent-here ends in `run \`fastagent init\`` — over a
|
|
@@ -131,11 +131,44 @@ export function resolveOverridePath(raw) {
|
|
|
131
131
|
return resolve(expanded);
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
134
|
+
* A path as an instance configuration writes it, absolute: `~/` is the home directory, a relative one is `base`'s (the
|
|
135
|
+
* configuration file's directory). Undefined for a relative one when there is no file to anchor it.
|
|
136
|
+
*/
|
|
137
|
+
export function configurationPath(path, base) {
|
|
138
|
+
if (path === "~" || path.startsWith("~/"))
|
|
139
|
+
return join(homedir(), path.slice(1));
|
|
140
|
+
if (isAbsolute(path))
|
|
141
|
+
return resolve(path);
|
|
142
|
+
return base === undefined ? undefined : resolve(base, path);
|
|
143
|
+
}
|
|
144
|
+
/** Where an instance is, from the environment: the root, or its state and its workspace apart (agent-artifact.md §4.1). */
|
|
145
|
+
export const INSTANCE_ROOT_ENV = "FASTAGENT_ROOT";
|
|
146
|
+
export const INSTANCE_WORKDIR_ENV = "FASTAGENT_WORKDIR";
|
|
147
|
+
export const INSTANCE_STATE_ENV = "FASTAGENT_STATE_DIR";
|
|
148
|
+
export const INSTANCE_CONFIG_ENV = "FASTAGENT_CONFIG";
|
|
149
|
+
/** Where fastagent keeps what it makes for a local instance of the project in `dir`, one directory per environment. */
|
|
150
|
+
export const INSTANCES_DIRNAME = ".fastagent";
|
|
151
|
+
/**
|
|
152
|
+
* The instance root of the project in `dir` when none is given (agent-artifact.md §4.1): `.fastagent/dev/` for `dev`,
|
|
153
|
+
* and `.fastagent/start/` for the commands that run what `start` runs, kept apart so a trial of the production build
|
|
154
|
+
* never reads or writes the development sessions and clones.
|
|
155
|
+
*/
|
|
156
|
+
function defaultInstanceRoot(dir, env) {
|
|
157
|
+
return join(resolve(dir), INSTANCES_DIRNAME, env[AGENT_ENVIRONMENT_ENV] === "production" ? "start" : "dev");
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The resolved state root — the durable machine-state home (sessions/, channels/<kind>/, schedule/): the state
|
|
161
|
+
* directory, else `.state` in the instance root, which is the project's default one when none is given.
|
|
136
162
|
*/
|
|
137
163
|
export function resolveStateRoot(dir, env = process.env) {
|
|
138
|
-
return resolveOverridePath(env
|
|
164
|
+
return (resolveOverridePath(env[INSTANCE_STATE_ENV]) ??
|
|
165
|
+
join(resolveOverridePath(env[INSTANCE_ROOT_ENV]) ?? defaultInstanceRoot(dir, env), STATE_DIRNAME));
|
|
166
|
+
}
|
|
167
|
+
/** The agent's working directory: the workspace directory, else the instance root, which is the project's default one. */
|
|
168
|
+
export function resolveWorkdir(dir, env = process.env) {
|
|
169
|
+
return (resolveOverridePath(env[INSTANCE_WORKDIR_ENV]) ??
|
|
170
|
+
resolveOverridePath(env[INSTANCE_ROOT_ENV]) ??
|
|
171
|
+
defaultInstanceRoot(dir, env));
|
|
139
172
|
}
|
|
140
173
|
/** Where fastagent clones the content entry `name` of the agent in `dir`. */
|
|
141
174
|
export function contentEntryPath(dir, name) {
|
|
@@ -150,12 +183,12 @@ export function resolveSecretsDir(dir, env = process.env) {
|
|
|
150
183
|
}
|
|
151
184
|
/** Is this process the deployed container the generated artifacts describe? ONE reading of the
|
|
152
185
|
* environment fact the image states, so a second spelling cannot drift from it. */
|
|
153
|
-
export function isDeployedWorkspace() {
|
|
154
|
-
return Boolean(
|
|
186
|
+
export function isDeployedWorkspace(env = process.env) {
|
|
187
|
+
return Boolean(env.FASTAGENT_RELEASE_FILE);
|
|
155
188
|
}
|
|
156
189
|
/** Is this process running inside the AgentCore Runtime? Set by the generated deploy artifacts. */
|
|
157
|
-
export function isAgentcoreRuntime() {
|
|
158
|
-
return
|
|
190
|
+
export function isAgentcoreRuntime(env = process.env) {
|
|
191
|
+
return env.FASTAGENT_AGENTCORE === "1";
|
|
159
192
|
}
|
|
160
193
|
/**
|
|
161
194
|
* Which host this deployed box runs on, for the command that logs it in (`login --deployment <host>`). Read from what
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as undici from "undici";
|
|
2
|
+
export declare const RuntimeWebSocket: {
|
|
3
|
+
new (url: string | URL, protocols?: string | string[] | undici.WebSocketInit): undici.WebSocket;
|
|
4
|
+
prototype: undici.WebSocket;
|
|
5
|
+
readonly CLOSED: number;
|
|
6
|
+
readonly CLOSING: number;
|
|
7
|
+
readonly CONNECTING: number;
|
|
8
|
+
readonly OPEN: number;
|
|
9
|
+
};
|
|
10
|
+
export type RuntimeWebSocket = InstanceType<typeof RuntimeWebSocket>;
|
|
11
|
+
/** Send a ping on `socket`, or `undefined` when this runtime cannot. */
|
|
12
|
+
export declare function pinger(socket: RuntimeWebSocket): (() => void) | undefined;
|
|
13
|
+
/** Call `onPong` for each pong `socket` receives. Returns what stops it. */
|
|
14
|
+
export declare function watchPongs(socket: RuntimeWebSocket, onPong: () => void): () => void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime's own WebSocket and the pings it can send, for the two places that hold a WebSocket open against a
|
|
3
|
+
* service that drops one nobody pings: Slack's Socket Mode and AgentCore's command shell.
|
|
4
|
+
*
|
|
5
|
+
* Node's undici sends a ping with `undici.ping` and reports the pong on a diagnostics channel; Bun 1.4.4 and later has
|
|
6
|
+
* both in its `undici` shim. Before that the shim has no `ping` and its WebSocket is the runtime's own, which pings with
|
|
7
|
+
* its `ping()` and reports a `pong` event. A runtime with neither has no pings: `pinger` says so, and the caller decides.
|
|
8
|
+
*
|
|
9
|
+
* A namespace import, because Bun's `undici` is a shim that leaves out what it does not have, and a named import of a
|
|
10
|
+
* missing export fails the whole module's link.
|
|
11
|
+
*/
|
|
12
|
+
import { channel } from "node:diagnostics_channel";
|
|
13
|
+
import * as undici from "undici";
|
|
14
|
+
export const RuntimeWebSocket = undici.WebSocket ?? globalThis.WebSocket;
|
|
15
|
+
const undiciPings = undici.WebSocket !== undefined && typeof undici.ping === "function";
|
|
16
|
+
const nativePing = (socket) => socket.ping;
|
|
17
|
+
/** Send a ping on `socket`, or `undefined` when this runtime cannot. */
|
|
18
|
+
export function pinger(socket) {
|
|
19
|
+
if (undiciPings)
|
|
20
|
+
return () => undici.ping(socket);
|
|
21
|
+
return typeof nativePing(socket) === "function" ? () => nativePing(socket)?.call(socket) : undefined;
|
|
22
|
+
}
|
|
23
|
+
/** Call `onPong` for each pong `socket` receives. Returns what stops it. */
|
|
24
|
+
export function watchPongs(socket, onPong) {
|
|
25
|
+
if (undiciPings) {
|
|
26
|
+
const pongs = channel("undici:websocket:pong");
|
|
27
|
+
const listener = (message) => {
|
|
28
|
+
if (message.websocket === socket)
|
|
29
|
+
onPong();
|
|
30
|
+
};
|
|
31
|
+
pongs.subscribe(listener);
|
|
32
|
+
return () => pongs.unsubscribe(listener);
|
|
33
|
+
}
|
|
34
|
+
if (typeof nativePing(socket) !== "function")
|
|
35
|
+
return () => { };
|
|
36
|
+
const listener = () => onPong();
|
|
37
|
+
socket.addEventListener("pong", listener);
|
|
38
|
+
return () => socket.removeEventListener("pong", listener);
|
|
39
|
+
}
|
|
@@ -1,34 +1,26 @@
|
|
|
1
|
-
import { type ValueWriteResult } from "../value-file.ts";
|
|
2
|
-
import { type FeishuSubscriptionMode } from "../channels/feishu/setup-mode.ts";
|
|
3
1
|
export type ChannelKind = "telegram" | "slack" | "feishu" | "lark";
|
|
4
2
|
/** An env var a scaffolded channel reads. */
|
|
5
|
-
|
|
3
|
+
interface ChannelEnv {
|
|
6
4
|
name: string;
|
|
7
5
|
hint: string;
|
|
8
6
|
/** Required for the channel to run. */
|
|
9
7
|
required: boolean;
|
|
10
|
-
generate?: boolean;
|
|
11
8
|
}
|
|
12
|
-
|
|
13
|
-
export declare const CHANNEL_KINDS: ChannelKind[];
|
|
14
|
-
/** The mode-specific env vars + next-step lines a scaffolded channel needs. */
|
|
15
|
-
export declare function channelSetup(kind: ChannelKind, ingress?: FeishuSubscriptionMode,
|
|
16
|
-
/** Whether `<PREFIX>_INGRESS` is explicitly set in this environment. */
|
|
17
|
-
pinned?: boolean): {
|
|
9
|
+
interface ChannelScaffold {
|
|
18
10
|
env: ChannelEnv[];
|
|
11
|
+
/** Channel instructions with paths and the serving command filled by the CLI. */
|
|
19
12
|
steps: string[];
|
|
20
|
-
}
|
|
13
|
+
}
|
|
14
|
+
/** The channel kinds `fastagent add <kind>` can scaffold. */
|
|
15
|
+
export declare const CHANNEL_KINDS: ChannelKind[];
|
|
16
|
+
/** The env vars and next-step lines a scaffolded channel has. */
|
|
17
|
+
export declare function channelSetup(kind: ChannelKind): ChannelScaffold;
|
|
21
18
|
/**
|
|
22
19
|
* Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who copies it to
|
|
23
20
|
* `.env` finds the vars already there. Every var the channel can declare, whichever way this machine receives it:
|
|
24
21
|
* the file is what a collaborator or CI fills for a deployment, which receives Feishu/Lark by webhook.
|
|
25
22
|
*/
|
|
26
23
|
export declare function appendChannelEnv(dir: string, kind: ChannelKind): Promise<boolean>;
|
|
27
|
-
/**
|
|
28
|
-
* Append generated channel secrets to the selected environment's value file, and a placeholder for each the channel
|
|
29
|
-
* reads that nothing generated. Existing non-empty values are kept.
|
|
30
|
-
*/
|
|
31
|
-
export declare function appendChannelDotEnv(dir: string, kind: ChannelKind, generated: Record<string, string>, overwrite?: readonly string[], ingress?: FeishuSubscriptionMode): Promise<ValueWriteResult>;
|
|
32
24
|
/** Whether a channel file already exists — checked before any mutation, so a no-clobber re-add is side-effect-free. */
|
|
33
25
|
export declare function channelExists(dir: string, kind: ChannelKind): Promise<boolean>;
|
|
34
26
|
/** Scaffold `src/channels/<kind>.ts` into {@link dir}. */
|
|
@@ -40,3 +32,4 @@ export declare function scaffoldCompanionTools(dir: string, kind: ChannelKind):
|
|
|
40
32
|
* channel file imports it).
|
|
41
33
|
*/
|
|
42
34
|
export declare function assertChannelReady(dir: string): Promise<void>;
|
|
35
|
+
export {};
|