@fastagent-sh/fastagent 0.28.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 +10 -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 +3 -1
- package/dist/channels/feishu/feishu-api.js +4 -0
- package/dist/channels/feishu/feishu.d.ts +0 -3
- package/dist/channels/feishu/feishu.js +10 -10
- package/dist/channels/feishu/register-app.d.ts +2 -0
- package/dist/channels/feishu/register-app.js +4 -6
- 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/wait-health.js +1 -1
- 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 -310
- 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 -67
- 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 +63 -24
- 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.d.ts +12 -0
- package/dist/cli/commands/mcp.js +164 -0
- 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 +339 -108
- 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 +26 -2
- 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 -11
- 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 +80 -39
- 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 +18 -43
- package/dist/deploy/docker/run.js +57 -79
- 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 -22
- package/dist/deploy/fly/run.js +25 -44
- 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 -7
- 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.d.ts +3 -0
- package/dist/harnesses/pi-durable/mcp.js +32 -0
- package/dist/harnesses/pi-durable/open.d.ts +36 -38
- package/dist/harnesses/pi-durable/open.js +97 -150
- 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 +95 -15
- package/dist/harnesses/pi-durable/serve.js +151 -59
- 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/server.d.ts +1 -1
- package/dist/http/server.js +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.js +8 -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/locked-file.d.ts +2 -2
- package/dist/locked-file.js +8 -3
- package/dist/mcp/auth.d.ts +22 -0
- package/dist/mcp/auth.js +180 -0
- package/dist/mcp/client.d.ts +15 -0
- package/dist/mcp/client.js +179 -0
- package/dist/mcp/config.d.ts +86 -0
- package/dist/mcp/config.js +225 -0
- package/dist/mcp/grants.d.ts +4 -0
- package/dist/mcp/grants.js +132 -0
- package/dist/mcp/host.d.ts +19 -0
- package/dist/mcp/host.js +128 -0
- package/dist/mcp/network.d.ts +12 -0
- package/dist/mcp/network.js +150 -0
- package/dist/mcp/sign-in.d.ts +2 -0
- package/dist/mcp/sign-in.js +204 -0
- package/dist/mcp-api.d.ts +107 -0
- package/dist/mcp-api.js +10 -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/setup-page.d.ts +51 -0
- package/dist/setup-page.js +149 -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 +12 -6
- package/dist/value-file.js +36 -25
- package/dist/version.d.ts +7 -1
- package/dist/version.js +9 -3
- package/package.json +8 -6
- package/dist/channels/lark/onboard.d.ts +0 -36
- package/dist/channels/lark/onboard.js +0 -56
- 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 -201
- package/dist/cli/add-feishu.d.ts +0 -50
- package/dist/cli/add-feishu.js +0 -367
- 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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** Explicit browser OAuth for MCP; no auth URL, code or token passes through a model turn. */
|
|
2
|
+
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { agentValues } from "../value-file.js";
|
|
6
|
+
import { declareMcp, mcpInstance, resolveMcpServers } from "./config.js";
|
|
7
|
+
import { registerClient, startAuthorization, exchangeAuthorization, selectClientAuthMethod, validateAuthorizationResponseIssuer, } from "@modelcontextprotocol/client";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Deferred from "effect/Deferred";
|
|
10
|
+
import * as Exit from "effect/Exit";
|
|
11
|
+
import { port, portJoin, portCleanup, portError } from "../effect-port.js";
|
|
12
|
+
import { McpError } from "../mcp-api.js";
|
|
13
|
+
import { sendPage, setupRequestUrl, statusCard } from "../setup-page.js";
|
|
14
|
+
import { discoverMcp, mcpFailure } from "./auth.js";
|
|
15
|
+
import { mcpFetch, openMcpNetwork } from "./network.js";
|
|
16
|
+
import { httpServer } from "./host.js";
|
|
17
|
+
import { grantSummary } from "./grants.js";
|
|
18
|
+
export async function signInMcp(request) {
|
|
19
|
+
const timeout = request.timeoutSeconds ?? 900;
|
|
20
|
+
if (!Number.isInteger(timeout) ||
|
|
21
|
+
timeout <= 0 ||
|
|
22
|
+
(request.callbackPort !== undefined &&
|
|
23
|
+
(!Number.isInteger(request.callbackPort) || request.callbackPort < 0 || request.callbackPort > 65535))) {
|
|
24
|
+
throw new McpError("configuration", "Invalid MCP sign-in timeout or callback port", request.server);
|
|
25
|
+
}
|
|
26
|
+
const dir = resolve(request.agentDir);
|
|
27
|
+
const server = httpServer(resolveMcpServers(declareMcp(dir, mcpInstance(request.config)), { ...process.env, ...agentValues(dir), ...request.variables }, request.server, dir, {}), request.server);
|
|
28
|
+
let saved;
|
|
29
|
+
const work = Effect.gen(function* () {
|
|
30
|
+
const network = yield* Effect.acquireRelease(port(() => openMcpNetwork(server.url, request.fetch)), (value) => portCleanup("MCP network", () => value.close()));
|
|
31
|
+
const fetchImpl = mcpFetch(server.url, network.fetch);
|
|
32
|
+
const discovery = yield* port(() => discoverMcp(server.url, fetchImpl));
|
|
33
|
+
if (!discovery.metadata.code_challenge_methods_supported?.includes("S256")) {
|
|
34
|
+
return yield* Effect.fail(new McpError("protocol", "MCP authorization server does not advertise PKCE S256", server.name));
|
|
35
|
+
}
|
|
36
|
+
if (!request.clientId && !discovery.metadata.registration_endpoint)
|
|
37
|
+
return yield* Effect.fail(new McpError("configuration", "This MCP service requires a public clientId or client metadata URL; dynamic registration is unavailable", server.name));
|
|
38
|
+
const scopes = [...new Set(request.scopes ?? discovery.resourceMetadata.scopes_supported ?? [])];
|
|
39
|
+
if (scopes.some((scope) => !/^[\x21\x23-\x5B\x5D-\x7E]+$/.test(scope)))
|
|
40
|
+
return yield* Effect.fail(new McpError("configuration", "Invalid OAuth scope", server.name));
|
|
41
|
+
const approved = yield* port(() => request.interaction.confirmAuthorization({
|
|
42
|
+
server: server.name,
|
|
43
|
+
resource: discovery.resource,
|
|
44
|
+
issuer: discovery.issuer,
|
|
45
|
+
scopes,
|
|
46
|
+
}));
|
|
47
|
+
if (!approved)
|
|
48
|
+
return yield* Effect.fail(new McpError("canceled", "MCP authorization canceled", server.name));
|
|
49
|
+
const callback = yield* Deferred.make();
|
|
50
|
+
const state = randomBytes(32).toString("base64url");
|
|
51
|
+
const path = request.clientId ? "/mcp/oauth/callback" : `/mcp/oauth/${randomBytes(24).toString("hex")}`;
|
|
52
|
+
let portNumber = 0;
|
|
53
|
+
let consumed = false;
|
|
54
|
+
const listener = createServer((incoming, response) => {
|
|
55
|
+
// This one-shot listener closes after sign-in; a later flow may bind the same callback port.
|
|
56
|
+
response.setHeader("connection", "close");
|
|
57
|
+
const url = setupRequestUrl(incoming, response);
|
|
58
|
+
if (!url)
|
|
59
|
+
return;
|
|
60
|
+
if (incoming.headers.host !== `127.0.0.1:${portNumber}` ||
|
|
61
|
+
incoming.method !== "GET" ||
|
|
62
|
+
url.pathname !== path ||
|
|
63
|
+
consumed) {
|
|
64
|
+
response.writeHead(404).end("not found\n");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const received = url.searchParams.get("state") ?? "";
|
|
68
|
+
if (Buffer.byteLength(received) !== Buffer.byteLength(state) ||
|
|
69
|
+
!timingSafeEqual(Buffer.from(received), Buffer.from(state))) {
|
|
70
|
+
response.writeHead(400).end("invalid OAuth state\n");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let problem;
|
|
74
|
+
try {
|
|
75
|
+
validateAuthorizationResponseIssuer({
|
|
76
|
+
iss: url.searchParams.get("iss") ?? undefined,
|
|
77
|
+
expectedIssuer: discovery.issuer,
|
|
78
|
+
issParameterSupported: discovery.metadata.authorization_response_iss_parameter_supported === true,
|
|
79
|
+
});
|
|
80
|
+
if (url.searchParams.has("error"))
|
|
81
|
+
problem = new McpError("canceled", "MCP authorization was not approved", server.name);
|
|
82
|
+
else if (!url.searchParams.get("code"))
|
|
83
|
+
problem = new McpError("protocol", "MCP callback contained no authorization code", server.name);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
problem = new McpError("authorization_mismatch", "MCP OAuth callback issuer mismatch", server.name);
|
|
87
|
+
}
|
|
88
|
+
consumed = true;
|
|
89
|
+
const completed = problem ? Deferred.fail(callback, problem) : Deferred.succeed(callback, url.searchParams);
|
|
90
|
+
// Closing the scope tears down the listener; first flush the page or observe the browser disconnect.
|
|
91
|
+
if (response.destroyed)
|
|
92
|
+
Effect.runSync(completed);
|
|
93
|
+
else
|
|
94
|
+
response.once("close", () => Effect.runSync(completed));
|
|
95
|
+
sendPage(response, problem ? 400 : 200, {
|
|
96
|
+
title: "MCP authorization — FastAgent",
|
|
97
|
+
card: statusCard({
|
|
98
|
+
state: problem ? "failed" : "working",
|
|
99
|
+
heading: problem ? "Authorization stopped" : "Authorization response received",
|
|
100
|
+
lede: problem?.message ?? "FastAgent is verifying and saving the authorization.",
|
|
101
|
+
next: "Return to your application or coding agent. Do not copy this address.",
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
yield* Effect.acquireRelease(Effect.succeed(listener), (value) => portCleanup("MCP OAuth callback", () => new Promise((resolve, reject) => {
|
|
106
|
+
value.close((error) => (error ? reject(error) : resolve()));
|
|
107
|
+
value.closeAllConnections();
|
|
108
|
+
})));
|
|
109
|
+
yield* portJoin(() => new Promise((resolve, reject) => {
|
|
110
|
+
listener.once("error", reject);
|
|
111
|
+
listener.listen(request.callbackPort ?? 0, "127.0.0.1", () => {
|
|
112
|
+
listener.off("error", reject);
|
|
113
|
+
resolve();
|
|
114
|
+
});
|
|
115
|
+
}));
|
|
116
|
+
const address = listener.address();
|
|
117
|
+
if (!address || typeof address === "string")
|
|
118
|
+
return yield* Effect.fail(new McpError("connection", "MCP callback has no local port", server.name));
|
|
119
|
+
portNumber = address.port;
|
|
120
|
+
const redirectUrl = `http://127.0.0.1:${portNumber}${path}`;
|
|
121
|
+
const registration = request.clientId
|
|
122
|
+
? { client_id: request.clientId }
|
|
123
|
+
: yield* portJoin(() => registerClient(discovery.issuer, {
|
|
124
|
+
metadata: discovery.metadata,
|
|
125
|
+
clientMetadata: {
|
|
126
|
+
client_name: "FastAgent",
|
|
127
|
+
redirect_uris: [redirectUrl],
|
|
128
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
129
|
+
response_types: ["code"],
|
|
130
|
+
token_endpoint_auth_method: "none",
|
|
131
|
+
application_type: "native",
|
|
132
|
+
scope: scopes.join(" "),
|
|
133
|
+
},
|
|
134
|
+
scope: scopes.join(" "),
|
|
135
|
+
fetchFn: fetchImpl,
|
|
136
|
+
}));
|
|
137
|
+
const { authorizationUrl, codeVerifier } = yield* portJoin(() => startAuthorization(discovery.issuer, {
|
|
138
|
+
metadata: discovery.metadata,
|
|
139
|
+
clientInformation: registration,
|
|
140
|
+
state,
|
|
141
|
+
redirectUrl,
|
|
142
|
+
scope: scopes.join(" "),
|
|
143
|
+
resource: discovery.resource,
|
|
144
|
+
}));
|
|
145
|
+
request.interaction.notify({ type: "auth_url", url: authorizationUrl.href });
|
|
146
|
+
const params = yield* Deferred.await(callback);
|
|
147
|
+
// Redeeming a code can consume it. Finish the bounded exchange and durable save even if its waiter cancels.
|
|
148
|
+
return yield* Effect.uninterruptible(Effect.gen(function* () {
|
|
149
|
+
request.interaction.notify({ type: "progress", message: "Saving MCP authorization" });
|
|
150
|
+
const tokens = yield* portJoin(() => exchangeAuthorization(discovery.issuer, {
|
|
151
|
+
metadata: discovery.metadata,
|
|
152
|
+
clientInformation: registration,
|
|
153
|
+
authorizationCode: params.get("code"),
|
|
154
|
+
codeVerifier,
|
|
155
|
+
redirectUri: redirectUrl,
|
|
156
|
+
iss: params.get("iss") ?? undefined,
|
|
157
|
+
resource: discovery.resource,
|
|
158
|
+
fetchFn: fetchImpl,
|
|
159
|
+
}));
|
|
160
|
+
const granted = tokens.scope === undefined ? scopes : tokens.scope.split(/\s+/).filter(Boolean);
|
|
161
|
+
if (tokens.token_type.toLowerCase() !== "bearer" || granted.some((scope) => !scopes.includes(scope)))
|
|
162
|
+
return yield* Effect.fail(new McpError("insufficient_scope", "MCP authorization returned an unsupported token type or unapproved scopes", server.name));
|
|
163
|
+
const method = selectClientAuthMethod(registration, discovery.metadata.token_endpoint_auth_methods_supported ?? []);
|
|
164
|
+
if (method !== "none" && method !== "client_secret_post" && method !== "client_secret_basic")
|
|
165
|
+
return yield* Effect.fail(new McpError("protocol", "Unsupported MCP client authentication method", server.name));
|
|
166
|
+
const record = {
|
|
167
|
+
version: 1,
|
|
168
|
+
serverUrl: server.url,
|
|
169
|
+
resource: discovery.resource,
|
|
170
|
+
issuer: discovery.issuer,
|
|
171
|
+
client: {
|
|
172
|
+
id: registration.client_id,
|
|
173
|
+
tokenEndpointAuthMethod: method,
|
|
174
|
+
...("client_secret" in registration && registration.client_secret
|
|
175
|
+
? { secret: registration.client_secret }
|
|
176
|
+
: {}),
|
|
177
|
+
},
|
|
178
|
+
scopes: granted,
|
|
179
|
+
status: "ready",
|
|
180
|
+
generation: 0,
|
|
181
|
+
accessToken: tokens.access_token,
|
|
182
|
+
...(tokens.refresh_token ? { refreshToken: tokens.refresh_token } : {}),
|
|
183
|
+
...(tokens.expires_in === undefined ? {} : { expiresAt: Date.now() + tokens.expires_in * 1000 }),
|
|
184
|
+
};
|
|
185
|
+
const id = randomUUID();
|
|
186
|
+
yield* portJoin(() => request.store.withGrant(id, async (access) => {
|
|
187
|
+
if (access.read())
|
|
188
|
+
throw new McpError("storage", "MCP grant ID collision");
|
|
189
|
+
await access.write(record);
|
|
190
|
+
}));
|
|
191
|
+
saved = grantSummary(id, record);
|
|
192
|
+
return saved;
|
|
193
|
+
}));
|
|
194
|
+
}).pipe(Effect.timeout(timeout * 1000), Effect.scoped);
|
|
195
|
+
const result = await Effect.runPromiseExit(work, { signal: request.interaction.signal });
|
|
196
|
+
if (saved)
|
|
197
|
+
return saved;
|
|
198
|
+
if (Exit.isFailure(result)) {
|
|
199
|
+
if (Exit.hasInterrupts(result) || request.interaction.signal?.aborted)
|
|
200
|
+
throw new McpError("canceled", "MCP sign-in canceled", request.server);
|
|
201
|
+
throw mcpFailure(portError(result.cause), "MCP sign-in", request.server);
|
|
202
|
+
}
|
|
203
|
+
return result.value;
|
|
204
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** MCP authorization as an embedding host sees it. No SDK or harness types cross this boundary. */
|
|
2
|
+
export type McpErrorCode = "configuration" | "authorization_required" | "authorization_mismatch" | "insufficient_scope" | "refresh_uncertain" | "canceled" | "connection" | "protocol" | "storage";
|
|
3
|
+
export declare class McpError extends Error {
|
|
4
|
+
readonly code: McpErrorCode;
|
|
5
|
+
readonly server?: string;
|
|
6
|
+
constructor(code: McpErrorCode, message: string, server?: string);
|
|
7
|
+
}
|
|
8
|
+
export interface McpGrantRecord {
|
|
9
|
+
version: 1;
|
|
10
|
+
serverUrl: string;
|
|
11
|
+
resource: string;
|
|
12
|
+
issuer: string;
|
|
13
|
+
client: {
|
|
14
|
+
id: string;
|
|
15
|
+
secret?: string;
|
|
16
|
+
tokenEndpointAuthMethod: "none" | "client_secret_post" | "client_secret_basic";
|
|
17
|
+
};
|
|
18
|
+
scopes: readonly string[];
|
|
19
|
+
status: "ready" | "refreshing" | "reauthorization_required";
|
|
20
|
+
generation: number;
|
|
21
|
+
accessToken: string;
|
|
22
|
+
refreshToken?: string;
|
|
23
|
+
expiresAt?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface McpGrantSummary {
|
|
26
|
+
id: string;
|
|
27
|
+
serverUrl: string;
|
|
28
|
+
resource: string;
|
|
29
|
+
issuer: string;
|
|
30
|
+
scopes: readonly string[];
|
|
31
|
+
status: McpGrantRecord["status"];
|
|
32
|
+
expiresAt?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface McpGrantAccess {
|
|
35
|
+
read(): McpGrantRecord | undefined;
|
|
36
|
+
write(record: McpGrantRecord): Promise<void>;
|
|
37
|
+
remove(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export interface McpGrantStore {
|
|
40
|
+
read(grantId: string): Promise<McpGrantRecord | undefined>;
|
|
41
|
+
list(): Promise<readonly McpGrantSummary[]>;
|
|
42
|
+
/** Exclusive across all callers sharing storage. Each write is durable immediately, even if operation later throws. */
|
|
43
|
+
withGrant<T>(grantId: string, operation: (grant: McpGrantAccess) => Promise<T>): Promise<T>;
|
|
44
|
+
}
|
|
45
|
+
export interface McpAuthorization {
|
|
46
|
+
store: McpGrantStore;
|
|
47
|
+
bindings: Readonly<Record<string, string>>;
|
|
48
|
+
}
|
|
49
|
+
export interface McpOptions {
|
|
50
|
+
store?: McpGrantStore;
|
|
51
|
+
bindings?: Readonly<Record<string, string>>;
|
|
52
|
+
/** Per-instance interpolation values; never copied into process.env. */
|
|
53
|
+
variables?: Readonly<Record<string, string>>;
|
|
54
|
+
/** Trusted transport override: the host must enforce connection-level DNS/IP and redirect safety itself. */
|
|
55
|
+
fetch?: typeof globalThis.fetch;
|
|
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
|
+
}
|
|
69
|
+
export interface McpServerSummary {
|
|
70
|
+
name: string;
|
|
71
|
+
transport: "stdio" | "http";
|
|
72
|
+
url?: string;
|
|
73
|
+
authorization: "not_required" | "not_bound" | "ready" | "refresh_needed" | "reauthorization_required" | "binding_mismatch";
|
|
74
|
+
grantId?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface McpSignInInteraction {
|
|
77
|
+
signal?: AbortSignal;
|
|
78
|
+
confirmAuthorization(request: {
|
|
79
|
+
server: string;
|
|
80
|
+
resource: string;
|
|
81
|
+
issuer: string;
|
|
82
|
+
scopes: readonly string[];
|
|
83
|
+
}): Promise<boolean>;
|
|
84
|
+
notify(event: {
|
|
85
|
+
type: "auth_url";
|
|
86
|
+
url: string;
|
|
87
|
+
} | {
|
|
88
|
+
type: "progress";
|
|
89
|
+
message: string;
|
|
90
|
+
}): void;
|
|
91
|
+
}
|
|
92
|
+
export interface McpSignInRequest {
|
|
93
|
+
agentDir: string;
|
|
94
|
+
server: string;
|
|
95
|
+
store: McpGrantStore;
|
|
96
|
+
interaction: McpSignInInteraction;
|
|
97
|
+
scopes?: readonly string[];
|
|
98
|
+
/** Non-secret registration choices for servers that require a pre-registered public client. */
|
|
99
|
+
clientId?: string;
|
|
100
|
+
/** With clientId, register http://127.0.0.1:<port>/mcp/oauth/callback. */
|
|
101
|
+
callbackPort?: number;
|
|
102
|
+
timeoutSeconds?: number;
|
|
103
|
+
variables?: Readonly<Record<string, string>>;
|
|
104
|
+
fetch?: McpOptions["fetch"];
|
|
105
|
+
/** The instance configuration whose servers are read with the program's. */
|
|
106
|
+
config?: McpConfigInput;
|
|
107
|
+
}
|
package/dist/mcp-api.js
ADDED
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 {};
|