@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
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { createServer } from "node:http";
|
|
3
|
-
const escapeHtml = (text) => text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
4
|
-
const page = (body, head = "") => `<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width">${head}` +
|
|
5
|
-
`<title>FastAgent Slack setup</title><body style="font:16px system-ui;max-width:40em;margin:3em auto;padding:0 1em">${body}`;
|
|
6
|
-
function tokensForm(error) {
|
|
7
|
-
return page(`<h1>Slack App Configuration token</h1>` +
|
|
8
|
-
`<ol><li>Open <a href="https://api.slack.com/apps" target=_blank rel=noopener>api.slack.com/apps</a>, then ` +
|
|
9
|
-
`<b>Your App Configuration Tokens</b> → <b>Generate Token</b> for the workspace the agent joins.</li>` +
|
|
10
|
-
`<li>Paste both tokens here.</li></ol>` +
|
|
11
|
-
`<p>FastAgent keeps them on this machine only (owner-readable), uses them to create the app and update its ` +
|
|
12
|
-
`Request URL, and never deploys them.</p>` +
|
|
13
|
-
(error ? `<p style="color:#b00"><b>${escapeHtml(error)}</b></p>` : "") +
|
|
14
|
-
`<form method=post><p><label>Access token<br><input name=access type=password required ` +
|
|
15
|
-
`placeholder="xoxe.xoxp-…" style="width:100%"></label></p><p><label>Refresh token<br><input name=refresh ` +
|
|
16
|
-
`type=password required placeholder="xoxe-…" style="width:100%"></label></p>` +
|
|
17
|
-
`<p><button>Create the Slack app</button></p></form>`);
|
|
18
|
-
}
|
|
19
|
-
/** Temporary onboarding-only responder. */
|
|
20
|
-
export async function startSlackSetupServer() {
|
|
21
|
-
const nonce = randomBytes(24).toString("hex");
|
|
22
|
-
const requestPath = `/slack/setup/${nonce}`;
|
|
23
|
-
const redirectPath = `/slack/oauth/${nonce}`;
|
|
24
|
-
const tokensPath = `/slack/tokens/${nonce}`;
|
|
25
|
-
const nextPath = `/slack/next/${nonce}`;
|
|
26
|
-
let takeTokens;
|
|
27
|
-
let pasted = false;
|
|
28
|
-
let after;
|
|
29
|
-
let lastShown;
|
|
30
|
-
/** The last page a browser waiting after the pair sees, and when it has seen it (bounded by one refresh window). */
|
|
31
|
-
const lastPage = (said, failed) => {
|
|
32
|
-
if (!pasted || after !== undefined)
|
|
33
|
-
return Promise.resolve();
|
|
34
|
-
after = { said, failed };
|
|
35
|
-
return new Promise((resolve) => {
|
|
36
|
-
const timer = setTimeout(resolve, 5_000);
|
|
37
|
-
lastShown = () => {
|
|
38
|
-
clearTimeout(timer);
|
|
39
|
-
resolve();
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
};
|
|
43
|
-
let settleOAuth;
|
|
44
|
-
const oauth = new Promise((resolve) => {
|
|
45
|
-
settleOAuth = resolve;
|
|
46
|
-
});
|
|
47
|
-
const server = createServer((request, response) => {
|
|
48
|
-
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
49
|
-
if (request.method === "GET" && url.pathname === "/health") {
|
|
50
|
-
response.end("ok\n");
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (request.method === "GET" && url.pathname === redirectPath) {
|
|
54
|
-
settleOAuth?.({
|
|
55
|
-
code: url.searchParams.get("code") ?? undefined,
|
|
56
|
-
state: url.searchParams.get("state") ?? undefined,
|
|
57
|
-
error: url.searchParams.get("error") ?? undefined,
|
|
58
|
-
});
|
|
59
|
-
settleOAuth = undefined;
|
|
60
|
-
response.statusCode = 200;
|
|
61
|
-
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
62
|
-
response.end(page("<p>Slack authorization received: FastAgent is finishing the install. You can close this page.</p>"));
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (url.pathname === tokensPath && (request.method === "GET" || request.method === "POST")) {
|
|
66
|
-
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
67
|
-
if (takeTokens === undefined) {
|
|
68
|
-
// Taken already, or not asked for: the page is one-time.
|
|
69
|
-
response.statusCode = 404;
|
|
70
|
-
response.end(page("<p>This page is no longer in use.</p>"));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (request.method === "GET") {
|
|
74
|
-
response.end(tokensForm());
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
readBody(request, (body) => {
|
|
78
|
-
if (body === undefined) {
|
|
79
|
-
response.statusCode = 413;
|
|
80
|
-
response.end(page("<p>Too large.</p>"));
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const form = new URLSearchParams(body);
|
|
84
|
-
try {
|
|
85
|
-
takeTokens?.({ access: (form.get("access") ?? "").trim(), refresh: (form.get("refresh") ?? "").trim() });
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
response.end(tokensForm(error.message));
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
response.statusCode = 303;
|
|
92
|
-
response.setHeader("location", nextPath);
|
|
93
|
-
response.end();
|
|
94
|
-
});
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (request.method === "GET" && url.pathname === nextPath) {
|
|
98
|
-
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
99
|
-
if (after === undefined) {
|
|
100
|
-
response.end(page("<p>Creating the Slack app…</p>", '<meta http-equiv="refresh" content="2">'));
|
|
101
|
-
}
|
|
102
|
-
else if ("url" in after) {
|
|
103
|
-
response.statusCode = 303;
|
|
104
|
-
response.setHeader("location", after.url);
|
|
105
|
-
response.end();
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
const style = after.failed ? ' style="color:#b00"' : "";
|
|
109
|
-
response.end(page(`<p${style}><b>${escapeHtml(after.said)}</b></p><p>Return to your agent.</p>`));
|
|
110
|
-
lastShown?.();
|
|
111
|
-
}
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (request.method === "POST" && url.pathname === requestPath) {
|
|
115
|
-
readBody(request, (body) => {
|
|
116
|
-
if (body === undefined) {
|
|
117
|
-
response.statusCode = 413;
|
|
118
|
-
response.end("payload too large\n");
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
const payload = JSON.parse(body);
|
|
123
|
-
if (payload.type === "url_verification" && typeof payload.challenge === "string") {
|
|
124
|
-
response.setHeader("content-type", "application/json");
|
|
125
|
-
response.end(JSON.stringify({ challenge: payload.challenge }));
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
// Fall through: setup never acknowledges normal events or malformed traffic.
|
|
131
|
-
}
|
|
132
|
-
response.statusCode = 404;
|
|
133
|
-
response.end("not found\n");
|
|
134
|
-
});
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
response.statusCode = 404;
|
|
138
|
-
response.end("not found\n");
|
|
139
|
-
});
|
|
140
|
-
await new Promise((resolve, reject) => {
|
|
141
|
-
server.once("error", reject);
|
|
142
|
-
server.listen(0, "127.0.0.1", () => {
|
|
143
|
-
server.off("error", reject);
|
|
144
|
-
resolve();
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
const address = server.address();
|
|
148
|
-
if (!address || typeof address === "string") {
|
|
149
|
-
server.close();
|
|
150
|
-
throw new Error("Slack setup server did not receive a local TCP port");
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
port: address.port,
|
|
154
|
-
requestPath,
|
|
155
|
-
redirectPath,
|
|
156
|
-
tokensPath,
|
|
157
|
-
waitForTokens: (check) => new Promise((resolve) => {
|
|
158
|
-
takeTokens = (pair) => {
|
|
159
|
-
const taken = check(pair);
|
|
160
|
-
takeTokens = undefined;
|
|
161
|
-
pasted = true;
|
|
162
|
-
resolve(taken);
|
|
163
|
-
};
|
|
164
|
-
}),
|
|
165
|
-
next(url) {
|
|
166
|
-
after = { url };
|
|
167
|
-
},
|
|
168
|
-
failed: (message) => lastPage(message, true),
|
|
169
|
-
done: (message) => lastPage(message, false),
|
|
170
|
-
async waitForOAuth(timeoutMs = 10 * 60_000) {
|
|
171
|
-
let timer;
|
|
172
|
-
const timeout = new Promise((_, reject) => {
|
|
173
|
-
timer = setTimeout(() => reject(new Error("Slack OAuth timed out — re-run `fastagent add slack`")), timeoutMs);
|
|
174
|
-
timer.unref();
|
|
175
|
-
});
|
|
176
|
-
try {
|
|
177
|
-
return await Promise.race([oauth, timeout]);
|
|
178
|
-
}
|
|
179
|
-
finally {
|
|
180
|
-
if (timer)
|
|
181
|
-
clearTimeout(timer);
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
close: () => new Promise((resolve, reject) => {
|
|
185
|
-
server.close((error) => (error ? reject(error) : resolve()));
|
|
186
|
-
}),
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
/** A request's body, or `undefined` past 64 KB: setup traffic is small, and anything larger is not ours. */
|
|
190
|
-
function readBody(request, then) {
|
|
191
|
-
let body = "";
|
|
192
|
-
let tooLarge = false;
|
|
193
|
-
request.on("data", (chunk) => {
|
|
194
|
-
if (tooLarge)
|
|
195
|
-
return;
|
|
196
|
-
body += String(chunk);
|
|
197
|
-
if (Buffer.byteLength(body) > 64 * 1024)
|
|
198
|
-
tooLarge = true;
|
|
199
|
-
});
|
|
200
|
-
request.on("end", () => then(tooLarge ? undefined : body));
|
|
201
|
-
}
|
package/dist/cli/add-feishu.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { type FeishuSubscriptionMode } from "../channels/feishu/setup-mode.ts";
|
|
2
|
-
import { type FeishuApi } from "../channels/feishu/feishu-api.ts";
|
|
3
|
-
import type { DeclaredChannel } from "../channels/discover.ts";
|
|
4
|
-
import { type Person } from "./person.ts";
|
|
5
|
-
export interface AgentScopeCheck {
|
|
6
|
-
/** Safe to proceed to version publishing now; false means Permissions still needs console or admin work. */
|
|
7
|
-
publishReady: boolean;
|
|
8
|
-
/** The scopes found missing; empty when the list could not be read (`publishReady` is false then). */
|
|
9
|
-
missing: string[];
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Check that the app holds what an app of this ingress needs ({@link feishuAppScopes}). A created app asks for them on
|
|
13
|
-
* its confirm page, and a tenant may still withhold some (its approval policy, or an app made by hand): those are
|
|
14
|
-
* NAMED, with what each one costs, and the console page that requests them is opened pre-filled. Nothing is requested
|
|
15
|
-
* through the config API — that needs a scope of its own, which a tenant can withhold just the same.
|
|
16
|
-
*/
|
|
17
|
-
export declare function checkAgentScopes(input: {
|
|
18
|
-
kind: "feishu" | "lark";
|
|
19
|
-
appId: string;
|
|
20
|
-
apiBase: string;
|
|
21
|
-
ingress: FeishuSubscriptionMode;
|
|
22
|
-
api: Pick<FeishuApi, "listAppScopes">;
|
|
23
|
-
note?: (message: string) => void;
|
|
24
|
-
/** Who is sent to the console page that requests a missing permission. */
|
|
25
|
-
person?: Person;
|
|
26
|
-
}): Promise<AgentScopeCheck>;
|
|
27
|
-
/** Create or resume the platform app behind `add feishu` / `add lark`. */
|
|
28
|
-
export declare function onboardFeishuCloudApp(target: string, kind: "feishu" | "lark", ingress: FeishuSubscriptionMode,
|
|
29
|
-
/** The command that continues an interrupted setup: the one that started it. */
|
|
30
|
-
rerun?: string,
|
|
31
|
-
/** Who takes the steps only a person can: at this terminal, or a coding agent's person through a ticket. */
|
|
32
|
-
person?: Person): Promise<Record<string, string> | undefined>;
|
|
33
|
-
/**
|
|
34
|
-
* Lark's app credentials, which only the person can get, from the app they create in Lark's console: with no one at a
|
|
35
|
-
* terminal to type them, the report naming where they set them (the selected environment's file), when either is
|
|
36
|
-
* absent. `again` is the command that continues once they have.
|
|
37
|
-
*/
|
|
38
|
-
export declare function larkCredentialsNeeded(agentDir: string, again: string): Promise<{
|
|
39
|
-
json: Record<string, unknown>;
|
|
40
|
-
text: string[];
|
|
41
|
-
} | undefined>;
|
|
42
|
-
export declare function feishuAppSecretNames(kind: "feishu" | "lark", ingress: FeishuSubscriptionMode): string[];
|
|
43
|
-
interface FeishuAppSetup {
|
|
44
|
-
kind: "feishu" | "lark";
|
|
45
|
-
ingress: FeishuSubscriptionMode;
|
|
46
|
-
}
|
|
47
|
-
export declare function unpreparedFeishuApps(channels: readonly DeclaredChannel[], values: ReadonlyMap<string, string>): FeishuAppSetup[];
|
|
48
|
-
export declare function assertIndependentFeishuApps(agentDir: string, kinds: readonly ("feishu" | "lark")[], values: ReadonlyMap<string, string>): void;
|
|
49
|
-
export declare function prepareFeishuApps(agentDir: string, apps: readonly FeishuAppSetup[], rerun: string, person: Person, onboard?: typeof onboardFeishuCloudApp): Promise<void>;
|
|
50
|
-
export {};
|
package/dist/cli/add-feishu.js
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `fastagent add feishu|lark` app onboarding — the cloud-facing half of runAdd, kept out of cli.ts (which
|
|
3
|
-
* self-executes on import), mirroring models-view.ts/auth-view.ts.
|
|
4
|
-
*/
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { isCancel, log as clackLog, password, text as clackText } from "@clack/prompts";
|
|
7
|
-
import { bootstrapFeishuVerificationToken } from "../channels/feishu/bootstrap-token.js";
|
|
8
|
-
import { FEISHU_APP_CONFIG_SCOPE, feishuAppAddons, feishuAppScopes, feishuScopeRequestUrl, scopeSatisfied, } from "../channels/feishu/setup-mode.js";
|
|
9
|
-
import { cloudFor } from "../channels/feishu/cloud.js";
|
|
10
|
-
import { createFeishuApi, isFeishuConfigApiMissing, isTransientFeishuRegistrationError, } from "../channels/feishu/feishu-api.js";
|
|
11
|
-
import { registerFeishuApp } from "../channels/feishu/register-app.js";
|
|
12
|
-
import { LARK_CONSOLE_URL, onboardLarkApp } from "../channels/lark/onboard.js";
|
|
13
|
-
import { dotEnvPath, loadEnvValues, parseEnvContent } from "../value-file.js";
|
|
14
|
-
import { openExternalUrl } from "../open-url.js";
|
|
15
|
-
import { terminalPerson } from "./person.js";
|
|
16
|
-
import { AGENT_ENVIRONMENT_ENV } from "../paths.js";
|
|
17
|
-
import { appendChannelDotEnv } from "../scaffold/add-channel.js";
|
|
18
|
-
import { startCloudflareTunnel } from "../tunnel.js";
|
|
19
|
-
/**
|
|
20
|
-
* Check that the app holds what an app of this ingress needs ({@link feishuAppScopes}). A created app asks for them on
|
|
21
|
-
* its confirm page, and a tenant may still withhold some (its approval policy, or an app made by hand): those are
|
|
22
|
-
* NAMED, with what each one costs, and the console page that requests them is opened pre-filled. Nothing is requested
|
|
23
|
-
* through the config API — that needs a scope of its own, which a tenant can withhold just the same.
|
|
24
|
-
*/
|
|
25
|
-
export async function checkAgentScopes(input) {
|
|
26
|
-
const { kind, appId, apiBase, api } = input;
|
|
27
|
-
const note = input.note ?? ((message) => console.error(message));
|
|
28
|
-
const person = input.person ?? terminalPerson;
|
|
29
|
-
const needed = feishuAppScopes(input.ingress);
|
|
30
|
-
const requested = needed.map((entry) => entry.request);
|
|
31
|
-
let scopes;
|
|
32
|
-
try {
|
|
33
|
-
scopes = await api.listAppScopes();
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
const url = feishuScopeRequestUrl(apiBase, appId, requested);
|
|
37
|
-
note(`[fastagent] warn: could not read the ${kind} app's permissions: ${String(error)}`);
|
|
38
|
-
person.afterwards(`make sure the ${kind} app holds ${requested.join(", ")} before publishing`, url);
|
|
39
|
-
return { publishReady: false, missing: [] };
|
|
40
|
-
}
|
|
41
|
-
// A user-type entry is not the tenant scope the app acts with.
|
|
42
|
-
const tenant = scopes.filter((scope) => scope.type === undefined || scope.type === "tenant");
|
|
43
|
-
const granted = (name) => tenant.some((scope) => scope.name === name && scope.grantStatus === 1);
|
|
44
|
-
const missing = needed.filter((entry) => !scopeSatisfied(entry, granted));
|
|
45
|
-
if (missing.length === 0) {
|
|
46
|
-
note(`[fastagent] ${kind} app permissions: ${requested.join(", ")} granted`);
|
|
47
|
-
return { publishReady: true, missing: [] };
|
|
48
|
-
}
|
|
49
|
-
// "Not granted", not "not requested": the list omits a scope still under review (measured), so the two look alike.
|
|
50
|
-
const describe = (entry) => `\n ${entry.request}: without it ${entry.withoutIt}`;
|
|
51
|
-
const url = feishuScopeRequestUrl(apiBase, appId, missing.map((entry) => entry.request));
|
|
52
|
-
person.afterwards(`the ${kind} app is not granted:${missing.map(describe).join("")}\n` +
|
|
53
|
-
`Tick and enable them on that page (one may already be awaiting a tenant admin's approval), before publishing`, url);
|
|
54
|
-
return { publishReady: false, missing: missing.map((entry) => entry.request) };
|
|
55
|
-
}
|
|
56
|
-
/** Create or resume the platform app behind `add feishu` / `add lark`. */
|
|
57
|
-
export async function onboardFeishuCloudApp(target, kind, ingress,
|
|
58
|
-
/** The command that continues an interrupted setup: the one that started it. */
|
|
59
|
-
rerun = `fastagent add ${kind}${ingress === "webhook" ? " --ingress webhook" : ""}`,
|
|
60
|
-
/** Who takes the steps only a person can: at this terminal, or a coding agent's person through a ticket. */
|
|
61
|
-
person = terminalPerson) {
|
|
62
|
-
const env = dotEnvPath(target); // the file actually written — never the default spelling
|
|
63
|
-
const { envPrefix, apiBase, capabilities } = cloudFor(kind);
|
|
64
|
-
const requiredNames = feishuAppSecretNames(kind, ingress);
|
|
65
|
-
const existing = await activeDotEnvValues(target, requiredNames);
|
|
66
|
-
if (process.env[AGENT_ENVIRONMENT_ENV] === "production") {
|
|
67
|
-
assertIndependentFeishuApps(target, [kind], new Map(Object.entries(existing)));
|
|
68
|
-
}
|
|
69
|
-
if (Object.keys(existing).length === requiredNames.length) {
|
|
70
|
-
console.error(`[fastagent] ${requiredNames.join("/")} already set in ${env} — keeping them`);
|
|
71
|
-
// WebSocket still needs its console mode/publish guidance.
|
|
72
|
-
if (ingress === "webhook") {
|
|
73
|
-
const appId = existing[`${envPrefix}_APP_ID`];
|
|
74
|
-
const appSecret = existing[`${envPrefix}_APP_SECRET`];
|
|
75
|
-
await checkAgentScopes({
|
|
76
|
-
kind,
|
|
77
|
-
appId,
|
|
78
|
-
apiBase,
|
|
79
|
-
ingress,
|
|
80
|
-
api: createFeishuApi({ kind, baseUrl: apiBase, appId, appSecret }),
|
|
81
|
-
person,
|
|
82
|
-
});
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (capabilities.appCreation === "scan-to-create") {
|
|
87
|
-
await createFeishuAppFlow(target, existing, ingress, rerun, person);
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
// guided-console (lark): the intl cloud cannot complete the bound device flow — collect + validate.
|
|
91
|
-
const credentials = await onboardLarkApp(person === terminalPerson ? larkAtTerminal : larkThrough(person, env, rerun), {
|
|
92
|
-
existing,
|
|
93
|
-
ingress,
|
|
94
|
-
rerun,
|
|
95
|
-
verifyCredentials: async (appId, appSecret) => {
|
|
96
|
-
if (process.env[AGENT_ENVIRONMENT_ENV] === "production") {
|
|
97
|
-
assertIndependentFeishuApps(target, ["lark"], new Map([["LARK_APP_ID", appId]]));
|
|
98
|
-
}
|
|
99
|
-
await createFeishuApi({ kind: "lark", baseUrl: apiBase, appId, appSecret }).verifyCredentials();
|
|
100
|
-
console.error(`[fastagent] Lark App ID / Secret verified`);
|
|
101
|
-
},
|
|
102
|
-
bootstrapWebhook: async (appId, appSecret) => {
|
|
103
|
-
const api = createFeishuApi({ kind: "lark", baseUrl: apiBase, appId, appSecret });
|
|
104
|
-
console.error(`[fastagent] trying Lark's webhook-mode + Verification-Token bootstrap (temporary tunnel)…`);
|
|
105
|
-
try {
|
|
106
|
-
const token = await bootstrapFeishuVerificationToken({
|
|
107
|
-
api,
|
|
108
|
-
appId,
|
|
109
|
-
kind: "lark",
|
|
110
|
-
startTunnel: (port) => startCloudflareTunnel(port),
|
|
111
|
-
onTunnelReady: (url) => console.error(`[fastagent] temporary tunnel ready → ${url}; registering webhook mode now…`),
|
|
112
|
-
onPatchRetry: ({ error, attempt, attempts, retryMs }) => console.error(`[fastagent] Lark could not validate the fresh tunnel yet (${String(error)}); retrying PATCH ${attempt + 1}/${attempts} in ${Math.round(retryMs / 1000)}s…`),
|
|
113
|
-
// A route-level 404 is definitive, not edge weather: fall back immediately.
|
|
114
|
-
shouldRetryPatch: (error) => !isFeishuConfigApiMissing(error) && isTransientFeishuRegistrationError(error),
|
|
115
|
-
});
|
|
116
|
-
console.error(`[fastagent] Lark Verification Token captured; Subscription mode changed to webhook in the app draft`);
|
|
117
|
-
return { token };
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
if (!isFeishuConfigApiMissing(error))
|
|
121
|
-
throw error;
|
|
122
|
-
const manualReason = "This Lark app returned HTTP 404 for the application-config API, so automatic mode/token bootstrap is unavailable.";
|
|
123
|
-
console.error(`[fastagent] ${manualReason}`);
|
|
124
|
-
return { manualReason };
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
await checkAgentScopes({
|
|
129
|
-
kind: "lark",
|
|
130
|
-
appId: credentials.LARK_APP_ID,
|
|
131
|
-
apiBase,
|
|
132
|
-
ingress,
|
|
133
|
-
person,
|
|
134
|
-
api: createFeishuApi({
|
|
135
|
-
kind: "lark",
|
|
136
|
-
baseUrl: apiBase,
|
|
137
|
-
appId: credentials.LARK_APP_ID,
|
|
138
|
-
appSecret: credentials.LARK_APP_SECRET,
|
|
139
|
-
}),
|
|
140
|
-
});
|
|
141
|
-
return Object.fromEntries(Object.entries(credentials).filter((entry) => typeof entry[1] === "string"));
|
|
142
|
-
}
|
|
143
|
-
/** Lark's guided console at a terminal: what to open, and the values asked for there. */
|
|
144
|
-
const larkAtTerminal = {
|
|
145
|
-
openUrl(url, action) {
|
|
146
|
-
clackLog.info(`${action}. Opening ${url}`);
|
|
147
|
-
openExternalUrl(url);
|
|
148
|
-
},
|
|
149
|
-
note: (message) => clackLog.info(message),
|
|
150
|
-
async prompt(message, opts) {
|
|
151
|
-
const result = opts?.hidden ? await password({ message }) : await clackText({ message });
|
|
152
|
-
return isCancel(result) ? undefined : result;
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
/**
|
|
156
|
-
* Lark's guided console through a coding agent's person: a page is a step left to them, with what they do there; a
|
|
157
|
-
* value it would ask for is theirs to set in the value file, and the setup continues when run again.
|
|
158
|
-
*/
|
|
159
|
-
function larkThrough(person, env, rerun) {
|
|
160
|
-
return {
|
|
161
|
-
note: (message) => console.error(`[fastagent] ${message}`),
|
|
162
|
-
openUrl: (url, action) => person.afterwards(action, url),
|
|
163
|
-
async prompt(message) {
|
|
164
|
-
throw new Error(`${message} is needed: ask the user to set it in ${env}, then run \`${rerun}\` again`);
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
/** The scan-to-create flow `add feishu` runs by default. */
|
|
169
|
-
async function createFeishuAppFlow(target, existing, ingress, rerun, person) {
|
|
170
|
-
const env = dotEnvPath(target); // the file actually written — never the default spelling
|
|
171
|
-
const { apiBase } = cloudFor("feishu");
|
|
172
|
-
let appId = existing.FEISHU_APP_ID;
|
|
173
|
-
let appSecret = existing.FEISHU_APP_SECRET;
|
|
174
|
-
if (appId && appSecret) {
|
|
175
|
-
console.error(ingress === "webhook"
|
|
176
|
-
? `[fastagent] resuming Feishu app ${appId} from ${env} to capture its missing Verification Token`
|
|
177
|
-
: `[fastagent] reusing Feishu app ${appId} from ${env} for WebSocket ingress`);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
console.error(`[fastagent] creating the Feishu app (confirm in the app)…`);
|
|
181
|
-
const app = await registerFeishuApp({
|
|
182
|
-
name: process.env[AGENT_ENVIRONMENT_ENV] === "production" ? "{user}'s production agent" : "{user}'s agent",
|
|
183
|
-
desc: "Served by fastagent",
|
|
184
|
-
// The agent template alone is not enough to SERVE, nor to hear a group (see feishuAppAddons).
|
|
185
|
-
addons: feishuAppAddons(ingress),
|
|
186
|
-
onVerificationUrl: ({ url, expiresInS }) => person.waitsOn(url, "confirm the new Feishu app (in a browser, or in Feishu, or as a QR code)", Date.now() + expiresInS * 1000),
|
|
187
|
-
});
|
|
188
|
-
person.carriesOn();
|
|
189
|
-
console.error(`[fastagent] app created: ${app.appId}${app.tenantBrand ? ` (${app.tenantBrand} tenant)` : ""}`);
|
|
190
|
-
// A cross-brand confirmation should be impossible (each confirm page refuses the other brand's code).
|
|
191
|
-
if (app.tenantBrand && app.tenantBrand !== "feishu") {
|
|
192
|
-
throw new Error(`the confirming account is a ${app.tenantBrand} tenant, but this is \`add feishu\` — run \`fastagent add ${app.tenantBrand}\` instead`);
|
|
193
|
-
}
|
|
194
|
-
appId = app.appId;
|
|
195
|
-
appSecret = app.appSecret;
|
|
196
|
-
// IRREVERSIBLE BOUNDARY: the remote app now exists and its one-time Secret is in memory.
|
|
197
|
-
const staged = {
|
|
198
|
-
FEISHU_APP_ID: appId,
|
|
199
|
-
FEISHU_APP_SECRET: appSecret,
|
|
200
|
-
...(ingress === "webhook" ? { FEISHU_VERIFICATION_TOKEN: "" } : {}),
|
|
201
|
-
};
|
|
202
|
-
await appendChannelDotEnv(target, "feishu", staged, Object.keys(staged), ingress);
|
|
203
|
-
console.error(ingress === "webhook"
|
|
204
|
-
? `[fastagent] wrote FEISHU_APP_ID, FEISHU_APP_SECRET to ${env} before Token bootstrap`
|
|
205
|
-
: `[fastagent] wrote FEISHU_APP_ID, FEISHU_APP_SECRET to ${env}`);
|
|
206
|
-
}
|
|
207
|
-
const groupSetup = await checkAgentScopes({
|
|
208
|
-
kind: "feishu",
|
|
209
|
-
appId,
|
|
210
|
-
apiBase,
|
|
211
|
-
ingress,
|
|
212
|
-
api: createFeishuApi({ kind: "feishu", baseUrl: apiBase, appId, appSecret }),
|
|
213
|
-
person,
|
|
214
|
-
});
|
|
215
|
-
if (ingress === "websocket") {
|
|
216
|
-
const versionUrl = `${apiBase}/app/${appId}/version`;
|
|
217
|
-
const choose = "WebSocket ingress needs no Verification Token, Encrypt Key, Request URL, or tunnel: choose long connection in " +
|
|
218
|
-
"Events & Callbacks";
|
|
219
|
-
if (groupSetup.publishReady)
|
|
220
|
-
person.afterwards(`${choose}, then CREATE + PUBLISH a version`, versionUrl);
|
|
221
|
-
else
|
|
222
|
-
person.afterwards(`${choose}, finish the permission work above, then CREATE + PUBLISH a version: ${versionUrl}`);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
// The webhook channel authenticates plaintext events by the platform-generated Verification Token.
|
|
226
|
-
const tokenVar = "FEISHU_VERIFICATION_TOKEN";
|
|
227
|
-
const api = createFeishuApi({ baseUrl: apiBase, appId, appSecret });
|
|
228
|
-
let token;
|
|
229
|
-
let webhookModeChanged = false;
|
|
230
|
-
try {
|
|
231
|
-
const cfg = await api.getAppConfig(appId);
|
|
232
|
-
token = cfg.verificationToken;
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
// Best-effort read; the bootstrap below is the real path. Said, because that path can take minutes and the reason
|
|
236
|
-
// it was needed would otherwise be invisible.
|
|
237
|
-
console.error(`[fastagent] could not read the app's Verification Token directly: ${error.message}`);
|
|
238
|
-
}
|
|
239
|
-
// The bootstrap registers a Request URL through the config scope; without it every attempt is refused, so it is not
|
|
240
|
-
// tried, and the reason is the one the permission check above already named.
|
|
241
|
-
const configWithheld = groupSetup.missing.includes(FEISHU_APP_CONFIG_SCOPE);
|
|
242
|
-
if (!token && configWithheld) {
|
|
243
|
-
console.error(`[fastagent] not capturing the Verification Token: the app is not granted ${FEISHU_APP_CONFIG_SCOPE}. Choose webhook ` +
|
|
244
|
-
`in Events & Callbacks and set the Request URL there yourself (dev --tunnel and deploy --run cannot ` +
|
|
245
|
-
`either), or have the permission approved and re-run \`${rerun}\`.`);
|
|
246
|
-
}
|
|
247
|
-
else if (!token) {
|
|
248
|
-
console.error(`[fastagent] capturing the Verification Token — a throwaway webhook registration delivers it (spinning up a temporary tunnel; can take a few minutes on a slow edge)…`);
|
|
249
|
-
try {
|
|
250
|
-
token = await bootstrapFeishuVerificationToken({
|
|
251
|
-
api,
|
|
252
|
-
appId,
|
|
253
|
-
startTunnel: (port) => startCloudflareTunnel(port),
|
|
254
|
-
// Only edge weather is retried; a refusal (scope, auth, app under review) is reported as itself.
|
|
255
|
-
shouldRetryPatch: isTransientFeishuRegistrationError,
|
|
256
|
-
});
|
|
257
|
-
webhookModeChanged = true;
|
|
258
|
-
console.error(`[fastagent] Verification Token captured`);
|
|
259
|
-
}
|
|
260
|
-
catch (e) {
|
|
261
|
-
console.error(`[fastagent] warn: could not capture the Verification Token: ${String(e)} — finish this app with the manual copy below`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (token) {
|
|
265
|
-
// Persist the second credential stage immediately too.
|
|
266
|
-
const staged = await appendChannelDotEnv(target, "feishu", { [tokenVar]: token }, [tokenVar]);
|
|
267
|
-
console.error(`[fastagent] wrote ${staged.written.join(", ")} to ${env}`);
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
person.afterwards(`copy the Verification Token: developer console → Events & Callbacks → Encryption Strategy → Verification ` +
|
|
271
|
-
`Token → ${tokenVar} in ${env}`);
|
|
272
|
-
}
|
|
273
|
-
if (webhookModeChanged) {
|
|
274
|
-
// The bootstrap's PATCH flipped event mode in the DRAFT.
|
|
275
|
-
const versionUrl = `${apiBase}/app/${appId}/version`;
|
|
276
|
-
const publish = "CREATE + PUBLISH a version (self-approved): the switch to webhook mode takes effect on publish";
|
|
277
|
-
if (groupSetup.publishReady)
|
|
278
|
-
person.afterwards(publish, versionUrl);
|
|
279
|
-
else
|
|
280
|
-
person.afterwards(`after the permission work above, ${publish}: ${versionUrl}`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Lark's app credentials, which only the person can get, from the app they create in Lark's console: with no one at a
|
|
285
|
-
* terminal to type them, the report naming where they set them (the selected environment's file), when either is
|
|
286
|
-
* absent. `again` is the command that continues once they have.
|
|
287
|
-
*/
|
|
288
|
-
export async function larkCredentialsNeeded(agentDir, again) {
|
|
289
|
-
const held = await activeDotEnvValues(agentDir, ["LARK_APP_ID", "LARK_APP_SECRET"]);
|
|
290
|
-
if (held.LARK_APP_ID && held.LARK_APP_SECRET)
|
|
291
|
-
return undefined;
|
|
292
|
-
const valueFile = dotEnvPath(agentDir);
|
|
293
|
-
return {
|
|
294
|
-
json: {
|
|
295
|
-
status: "need_user_action",
|
|
296
|
-
url: LARK_CONSOLE_URL,
|
|
297
|
-
variables: ["LARK_APP_ID", "LARK_APP_SECRET"],
|
|
298
|
-
value_file: valueFile,
|
|
299
|
-
check_command: `${again} --json`,
|
|
300
|
-
agent_next_steps: "Give the URL to the user so they can create the Lark app there, and ask them to set LARK_APP_ID and " +
|
|
301
|
-
`LARK_APP_SECRET (from its Credentials & Basic Info) in ${valueFile} themselves, not in this ` +
|
|
302
|
-
"conversation. Once they say they have, run check_command.",
|
|
303
|
-
},
|
|
304
|
-
text: [
|
|
305
|
-
`need_user_action: create the Lark app at ${LARK_CONSOLE_URL}`,
|
|
306
|
-
` and set LARK_APP_ID and LARK_APP_SECRET in ${valueFile}`,
|
|
307
|
-
`then run: ${again}`,
|
|
308
|
-
],
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Active agent `.env` (`.secrets/.env`) values for the requested names — decided by THE .env parser, so this check can
|
|
313
|
-
* never disagree with what `loadDotEnv` reads.
|
|
314
|
-
*/
|
|
315
|
-
async function activeDotEnvValues(dir, names) {
|
|
316
|
-
let content;
|
|
317
|
-
try {
|
|
318
|
-
content = await readFile(dotEnvPath(dir), "utf8");
|
|
319
|
-
}
|
|
320
|
-
catch (e) {
|
|
321
|
-
if (e.code === "ENOENT")
|
|
322
|
-
return {};
|
|
323
|
-
throw e;
|
|
324
|
-
}
|
|
325
|
-
const parsed = parseEnvContent(content);
|
|
326
|
-
return Object.fromEntries(names.flatMap((name) => {
|
|
327
|
-
const value = parsed.get(name)?.trim();
|
|
328
|
-
return value ? [[name, value]] : [];
|
|
329
|
-
}));
|
|
330
|
-
}
|
|
331
|
-
export function feishuAppSecretNames(kind, ingress) {
|
|
332
|
-
const prefix = cloudFor(kind).envPrefix;
|
|
333
|
-
return [
|
|
334
|
-
`${prefix}_APP_ID`,
|
|
335
|
-
`${prefix}_APP_SECRET`,
|
|
336
|
-
...(ingress === "webhook" ? [`${prefix}_VERIFICATION_TOKEN`] : []),
|
|
337
|
-
];
|
|
338
|
-
}
|
|
339
|
-
export function unpreparedFeishuApps(channels, values) {
|
|
340
|
-
return ["feishu", "lark"].flatMap((kind) => {
|
|
341
|
-
const channel = channels.find((entry) => entry.name === kind);
|
|
342
|
-
if (!channel)
|
|
343
|
-
return [];
|
|
344
|
-
const ingress = channel.ingress === "webhook" ? "webhook" : "websocket";
|
|
345
|
-
return feishuAppSecretNames(kind, ingress).some((name) => !values.get(name)?.trim()) ? [{ kind, ingress }] : [];
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
export function assertIndependentFeishuApps(agentDir, kinds, values) {
|
|
349
|
-
const devFile = dotEnvPath(agentDir, {});
|
|
350
|
-
const devValues = loadEnvValues(devFile);
|
|
351
|
-
for (const kind of kinds) {
|
|
352
|
-
const name = `${cloudFor(kind).envPrefix}_APP_ID`;
|
|
353
|
-
const appId = values.get(name)?.trim();
|
|
354
|
-
if (appId && appId === devValues.get(name)?.trim()) {
|
|
355
|
-
throw new Error(`${kind}: dev and production use the same app (${name}) — create a separate production app in ` +
|
|
356
|
-
`${dotEnvPath(agentDir)}; copying dev credentials would take its messages away`);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
export async function prepareFeishuApps(agentDir, apps, rerun, person, onboard = onboardFeishuCloudApp) {
|
|
361
|
-
for (const { kind, ingress } of apps) {
|
|
362
|
-
console.error(`[fastagent] ${kind}: preparing the production app for ${ingress} in ${dotEnvPath(agentDir)}`);
|
|
363
|
-
const created = await onboard(agentDir, kind, ingress, rerun, person);
|
|
364
|
-
if (created)
|
|
365
|
-
await appendChannelDotEnv(agentDir, kind, created, Object.keys(created), ingress);
|
|
366
|
-
}
|
|
367
|
-
}
|
package/dist/cli/add-slack.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Person } from "./person.ts";
|
|
2
|
-
/** Single-workspace internal-app creation + installation. */
|
|
3
|
-
export declare function onboardSlackInternalApp(input: {
|
|
4
|
-
/**
|
|
5
|
-
* The AGENT DIR — credentials land in its `.env` ({@link dotEnvPath}: `FASTAGENT_SECRETS_DIR` moves it, so messages
|
|
6
|
-
* print the resolved path rather than the default spelling).
|
|
7
|
-
*/
|
|
8
|
-
target: string;
|
|
9
|
-
stateRoot: string;
|
|
10
|
-
/** `--replace-config`: go straight to replacing the local App Configuration token pair. */
|
|
11
|
-
replaceConfig?: boolean;
|
|
12
|
-
/**
|
|
13
|
-
* A coding agent's person, through a ticket: nothing is asked at a terminal. The token pair is pasted on a page the
|
|
14
|
-
* setup server shows, which then takes the browser on to Slack's install page; the app takes its default name, and
|
|
15
|
-
* a saved pair is kept. Absent: the terminal asks.
|
|
16
|
-
*/
|
|
17
|
-
person?: Person;
|
|
18
|
-
}): Promise<void>;
|