@fastagent-sh/fastagent 0.29.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/atomic-write.d.ts +4 -5
- package/dist/atomic-write.js +4 -5
- package/dist/authoring.d.ts +32 -15
- package/dist/authoring.js +147 -127
- package/dist/channels/agentcore-protocol.d.ts +8 -3
- package/dist/channels/agentcore-protocol.js +3 -1
- package/dist/channels/agentcore-service.d.ts +1 -0
- package/dist/channels/agentcore-service.js +14 -4
- package/dist/channels/agentcore.d.ts +9 -0
- package/dist/channels/agentcore.js +29 -4
- package/dist/channels/context.d.ts +2 -0
- package/dist/channels/context.js +5 -1
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu.d.ts +0 -3
- package/dist/channels/feishu/feishu.js +10 -10
- package/dist/channels/feishu/register-app.js +3 -5
- package/dist/channels/feishu/scaffold/channel.ts +32 -34
- package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
- package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
- package/dist/channels/feishu/setup-mode.d.ts +2 -12
- package/dist/channels/feishu/setup-mode.js +2 -22
- package/dist/channels/feishu/setup.d.ts +12 -0
- package/dist/channels/feishu/setup.js +197 -0
- package/dist/channels/kit/abortable.d.ts +8 -0
- package/dist/channels/kit/abortable.js +22 -0
- package/dist/channels/lark/lark.d.ts +0 -3
- package/dist/channels/lark/lark.js +0 -5
- package/dist/channels/lark/scaffold/channel.ts +33 -35
- package/dist/channels/lark/scaffold/lark-send.ts +5 -5
- package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
- package/dist/channels/lark/setup.d.ts +9 -0
- package/dist/channels/lark/setup.js +57 -0
- package/dist/channels/setup.d.ts +112 -0
- package/dist/channels/setup.js +31 -0
- package/dist/channels/slack/history.js +1 -1
- package/dist/channels/slack/manifest.d.ts +17 -7
- package/dist/channels/slack/manifest.js +12 -16
- package/dist/channels/slack/scaffold/channel.ts +44 -30
- package/dist/channels/slack/scaffold/slack-send.ts +8 -8
- package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
- package/dist/channels/slack/setup.d.ts +7 -0
- package/dist/channels/slack/setup.js +119 -0
- package/dist/channels/slack/slack-api.d.ts +2 -0
- package/dist/channels/slack/slack-api.js +4 -0
- package/dist/channels/slack/slack.d.ts +21 -2
- package/dist/channels/slack/slack.js +398 -338
- package/dist/channels/slack/socket.d.ts +57 -0
- package/dist/channels/slack/socket.js +305 -0
- package/dist/channels/telegram/polling.d.ts +27 -0
- package/dist/channels/telegram/polling.js +105 -0
- package/dist/channels/telegram/scaffold/channel.ts +52 -32
- package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
- package/dist/channels/telegram/setup.d.ts +2 -0
- package/dist/channels/telegram/setup.js +42 -0
- package/dist/channels/telegram/telegram-api.d.ts +27 -1
- package/dist/channels/telegram/telegram-api.js +7 -6
- package/dist/channels/telegram/telegram.d.ts +11 -3
- package/dist/channels/telegram/telegram.js +184 -157
- package/dist/channels/webhook-evidence.d.ts +11 -0
- package/dist/channels/webhook-evidence.js +71 -0
- package/dist/channels/webhooks.d.ts +37 -0
- package/dist/channels/webhooks.js +70 -0
- package/dist/child-env.d.ts +16 -0
- package/dist/child-env.js +30 -0
- package/dist/cli/box-login.js +4 -1
- package/dist/cli/channel-app.d.ts +3 -0
- package/dist/cli/channel-app.js +35 -0
- package/dist/cli/channel-setup.d.ts +38 -0
- package/dist/cli/channel-setup.js +195 -0
- package/dist/cli/commands/add.d.ts +6 -14
- package/dist/cli/commands/add.js +45 -303
- package/dist/cli/commands/auth.js +8 -2
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +16 -0
- package/dist/cli/commands/channels.d.ts +19 -0
- package/dist/cli/commands/channels.js +230 -0
- package/dist/cli/commands/content.d.ts +7 -3
- package/dist/cli/commands/content.js +16 -10
- package/dist/cli/commands/deploy/agentcore.js +55 -23
- package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
- package/dist/cli/commands/deploy/channel-apps.js +30 -0
- package/dist/cli/commands/deploy/docker.js +32 -65
- package/dist/cli/commands/deploy/fly.js +28 -12
- package/dist/cli/commands/deploy/railway.js +38 -13
- package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
- package/dist/cli/commands/deploy/secrets-step.js +66 -0
- package/dist/cli/commands/deploy/shared.d.ts +16 -5
- package/dist/cli/commands/deploy/shared.js +25 -8
- package/dist/cli/commands/deploy.js +53 -60
- package/dist/cli/commands/dev.d.ts +2 -0
- package/dist/cli/commands/dev.js +15 -3
- package/dist/cli/commands/env.js +1 -1
- package/dist/cli/commands/info.d.ts +2 -0
- package/dist/cli/commands/info.js +60 -25
- package/dist/cli/commands/init.js +3 -1
- package/dist/cli/commands/invoke.d.ts +2 -0
- package/dist/cli/commands/invoke.js +20 -7
- package/dist/cli/commands/login.d.ts +0 -7
- package/dist/cli/commands/login.js +22 -10
- package/dist/cli/commands/logout.js +1 -1
- package/dist/cli/commands/mcp.js +14 -6
- package/dist/cli/commands/models.js +3 -2
- package/dist/cli/commands/schedules.d.ts +4 -2
- package/dist/cli/commands/schedules.js +14 -10
- package/dist/cli/commands/secrets.d.ts +14 -0
- package/dist/cli/commands/secrets.js +213 -0
- package/dist/cli/commands/start.d.ts +14 -14
- package/dist/cli/commands/start.js +46 -60
- package/dist/cli/content-view.d.ts +4 -6
- package/dist/cli/content-view.js +5 -11
- package/dist/cli/first-run-channels.d.ts +3 -0
- package/dist/cli/first-run-channels.js +63 -0
- package/dist/cli/instance.d.ts +41 -0
- package/dist/cli/instance.js +155 -0
- package/dist/cli/invoke-stream.d.ts +5 -2
- package/dist/cli/invoke-stream.js +7 -2
- package/dist/cli/kernel.js +3 -0
- package/dist/cli/program.js +282 -110
- package/dist/cli/serve.d.ts +4 -5
- package/dist/cli/serve.js +4 -7
- package/dist/cli/shared.d.ts +11 -2
- package/dist/cli/shared.js +32 -19
- package/dist/cli/ticket.d.ts +7 -0
- package/dist/cli/ticket.js +106 -30
- package/dist/content/declare.d.ts +31 -37
- package/dist/content/declare.js +59 -60
- package/dist/content/file.d.ts +23 -12
- package/dist/content/file.js +48 -35
- package/dist/content/git.d.ts +0 -10
- package/dist/content/git.js +5 -39
- package/dist/content/resolve.d.ts +18 -22
- package/dist/content/resolve.js +32 -66
- package/dist/content/source.d.ts +7 -8
- package/dist/content/source.js +10 -28
- package/dist/credentials-page.d.ts +26 -0
- package/dist/credentials-page.js +185 -0
- package/dist/declared-secrets.d.ts +10 -3
- package/dist/declared-secrets.js +8 -4
- package/dist/deploy/agentcore/destroy.d.ts +4 -1
- package/dist/deploy/agentcore/destroy.js +25 -1
- package/dist/deploy/agentcore/forwarder.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +7 -21
- package/dist/deploy/agentcore/plan.js +47 -49
- package/dist/deploy/agentcore/run.d.ts +18 -10
- package/dist/deploy/agentcore/run.js +93 -70
- package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
- package/dist/deploy/agentcore/runtime-secrets.js +108 -0
- package/dist/deploy/agentcore/shell.js +12 -19
- package/dist/deploy/agentcore/sigv4.d.ts +7 -0
- package/dist/deploy/agentcore/sigv4.js +15 -0
- package/dist/deploy/agentcore/slr.d.ts +18 -0
- package/dist/deploy/agentcore/slr.js +27 -0
- package/dist/deploy/agentcore/store.d.ts +7 -0
- package/dist/deploy/agentcore/store.js +115 -0
- package/dist/deploy/api.d.ts +14 -14
- package/dist/deploy/api.js +26 -24
- package/dist/deploy/box-shell.d.ts +3 -1
- package/dist/deploy/build-context.d.ts +4 -4
- package/dist/deploy/build-context.js +11 -15
- package/dist/deploy/channel-ingress.d.ts +35 -9
- package/dist/deploy/channel-ingress.js +119 -22
- package/dist/deploy/container.d.ts +35 -10
- package/dist/deploy/container.js +79 -37
- package/dist/deploy/delivered-text.d.ts +13 -0
- package/dist/deploy/delivered-text.js +86 -0
- package/dist/deploy/docker/plan.d.ts +7 -8
- package/dist/deploy/docker/plan.js +31 -52
- package/dist/deploy/docker/run.d.ts +5 -43
- package/dist/deploy/docker/run.js +9 -84
- package/dist/deploy/file-store.d.ts +2 -0
- package/dist/deploy/file-store.js +17 -0
- package/dist/deploy/fly/plan.d.ts +3 -2
- package/dist/deploy/fly/plan.js +20 -9
- package/dist/deploy/fly/run.d.ts +18 -8
- package/dist/deploy/fly/run.js +24 -42
- package/dist/deploy/fly/store.d.ts +12 -0
- package/dist/deploy/fly/store.js +83 -0
- package/dist/deploy/hosts.d.ts +1 -2
- package/dist/deploy/hosts.js +1 -2
- package/dist/deploy/preflight.d.ts +18 -5
- package/dist/deploy/preflight.js +135 -63
- package/dist/deploy/railway/ignore.d.ts +8 -0
- package/dist/deploy/railway/ignore.js +57 -0
- package/dist/deploy/railway/plan.d.ts +1 -2
- package/dist/deploy/railway/plan.js +8 -6
- package/dist/deploy/railway/run.d.ts +21 -15
- package/dist/deploy/railway/run.js +32 -78
- package/dist/deploy/railway/store.d.ts +29 -0
- package/dist/deploy/railway/store.js +143 -0
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +4 -3
- package/dist/deploy/secret-store.d.ts +63 -0
- package/dist/deploy/secret-store.js +98 -0
- package/dist/deploy/secrets.d.ts +29 -46
- package/dist/deploy/secrets.js +49 -122
- package/dist/deploy/workspace.d.ts +6 -12
- package/dist/deploy/workspace.js +18 -94
- package/dist/dev-supervisor.js +6 -8
- package/dist/environment/declare.js +3 -2
- package/dist/environment/mise.d.ts +3 -1
- package/dist/environment/mise.js +4 -2
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/harnesses/models/catalog.d.ts +1 -0
- package/dist/harnesses/models/catalog.js +1 -2
- package/dist/harnesses/models/environment.d.ts +7 -2
- package/dist/harnesses/models/environment.js +9 -5
- package/dist/harnesses/models/host.d.ts +0 -7
- package/dist/harnesses/models/host.js +10 -1
- package/dist/harnesses/models/login.d.ts +0 -8
- package/dist/harnesses/models/login.js +5 -4
- package/dist/harnesses/models/models.d.ts +7 -2
- package/dist/harnesses/models/models.js +28 -4
- package/dist/harnesses/pi-durable/agent.js +59 -38
- package/dist/harnesses/pi-durable/build.d.ts +12 -0
- package/dist/harnesses/pi-durable/build.js +270 -0
- package/dist/harnesses/pi-durable/built.d.ts +24 -0
- package/dist/harnesses/pi-durable/built.js +136 -0
- package/dist/harnesses/pi-durable/config.d.ts +81 -0
- package/dist/harnesses/pi-durable/config.js +266 -0
- package/dist/harnesses/pi-durable/define.d.ts +63 -13
- package/dist/harnesses/pi-durable/define.js +23 -2
- package/dist/harnesses/pi-durable/definition.d.ts +72 -0
- package/dist/harnesses/pi-durable/definition.js +143 -0
- package/dist/harnesses/pi-durable/events.d.ts +4 -3
- package/dist/harnesses/pi-durable/events.js +179 -136
- package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
- package/dist/harnesses/pi-durable/execution-env.js +11 -0
- package/dist/harnesses/pi-durable/mcp.js +1 -3
- package/dist/harnesses/pi-durable/open.d.ts +35 -37
- package/dist/harnesses/pi-durable/open.js +91 -153
- package/dist/harnesses/pi-durable/reads.d.ts +0 -1
- package/dist/harnesses/pi-durable/reads.js +1 -3
- package/dist/harnesses/pi-durable/registry.d.ts +4 -6
- package/dist/harnesses/pi-durable/registry.js +29 -26
- package/dist/harnesses/pi-durable/release.d.ts +2 -2
- package/dist/harnesses/pi-durable/release.js +10 -7
- package/dist/harnesses/pi-durable/serve.d.ts +91 -15
- package/dist/harnesses/pi-durable/serve.js +109 -47
- package/dist/harnesses/pi-durable/sessions.js +15 -12
- package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
- package/dist/harnesses/pi-durable/text-sources.js +55 -0
- package/dist/harnesses/pi-durable/text.d.ts +16 -4
- package/dist/harnesses/pi-durable/text.js +115 -59
- package/dist/harnesses/pi-durable/wake.js +8 -8
- package/dist/http/client.js +9 -3
- package/dist/http/contract.d.ts +3 -3
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -1
- package/dist/instance-name.d.ts +7 -0
- package/dist/instance-name.js +26 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/mcp/client.js +11 -2
- package/dist/mcp/config.d.ts +70 -18
- package/dist/mcp/config.js +136 -53
- package/dist/mcp/host.d.ts +5 -2
- package/dist/mcp/host.js +14 -6
- package/dist/mcp/sign-in.js +2 -2
- package/dist/mcp-api.d.ts +14 -0
- package/dist/paths.d.ts +22 -8
- package/dist/paths.js +44 -11
- package/dist/runtime-websocket.d.ts +14 -0
- package/dist/runtime-websocket.js +39 -0
- package/dist/scaffold/add-channel.d.ts +9 -16
- package/dist/scaffold/add-channel.js +13 -83
- package/dist/scaffold/templates/env.example +4 -5
- package/dist/scaffold/templates/fastagent.config.ts +20 -14
- package/dist/scaffold/templates/gitignore +9 -6
- package/dist/scaffold/templates.d.ts +6 -2
- package/dist/scaffold/templates.js +7 -2
- package/dist/scaffold/vendor-skill.js +4 -2
- package/dist/service.d.ts +44 -6
- package/dist/service.js +110 -10
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +1 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/strip-edges.d.ts +10 -0
- package/dist/strip-edges.js +22 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +0 -1
- package/dist/tunnel.js +2 -8
- package/dist/value-file.d.ts +11 -6
- package/dist/value-file.js +21 -9
- package/dist/version.d.ts +7 -1
- package/dist/version.js +9 -3
- package/package.json +6 -5
- package/dist/channels/lark/credentials-page.d.ts +0 -25
- package/dist/channels/lark/credentials-page.js +0 -206
- package/dist/channels/lark/onboard.d.ts +0 -52
- package/dist/channels/lark/onboard.js +0 -86
- package/dist/channels/slack/config-api.d.ts +0 -69
- package/dist/channels/slack/config-api.js +0 -162
- package/dist/channels/slack/onboard.d.ts +0 -40
- package/dist/channels/slack/onboard.js +0 -136
- package/dist/channels/slack/onboarding-state.d.ts +0 -31
- package/dist/channels/slack/onboarding-state.js +0 -64
- package/dist/channels/slack/register-webhook.d.ts +0 -12
- package/dist/channels/slack/register-webhook.js +0 -67
- package/dist/channels/slack/setup-server.d.ts +0 -32
- package/dist/channels/slack/setup-server.js +0 -246
- package/dist/cli/add-feishu.d.ts +0 -41
- package/dist/cli/add-feishu.js +0 -391
- package/dist/cli/add-slack.d.ts +0 -18
- package/dist/cli/add-slack.js +0 -216
- package/dist/harnesses/pi-durable/parked.d.ts +0 -18
- package/dist/harnesses/pi-durable/parked.js +0 -27
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { SlackAppManifest } from "./manifest.ts";
|
|
2
|
-
interface SlackErrorShape {
|
|
3
|
-
ok?: boolean;
|
|
4
|
-
error?: string;
|
|
5
|
-
errors?: {
|
|
6
|
-
message?: string;
|
|
7
|
-
pointer?: string;
|
|
8
|
-
}[];
|
|
9
|
-
}
|
|
10
|
-
export declare class SlackConfigApiError extends Error {
|
|
11
|
-
readonly code: string;
|
|
12
|
-
readonly status: number;
|
|
13
|
-
/** Kept, not just formatted into the message. */
|
|
14
|
-
readonly errors: {
|
|
15
|
-
message?: string;
|
|
16
|
-
pointer?: string;
|
|
17
|
-
}[];
|
|
18
|
-
constructor(method: string, status: number, data: SlackErrorShape, fallback: string);
|
|
19
|
-
}
|
|
20
|
-
/** Whether a manifest call failed because Slack could not verify the `request_url` it carries. */
|
|
21
|
-
export declare function isSlackRequestUrlUnverified(error: unknown): boolean;
|
|
22
|
-
/** Whether a manifest write was refused because the app has token rotation on, which Slack never lets off again. */
|
|
23
|
-
export declare function isSlackRotationLocked(error: unknown): boolean;
|
|
24
|
-
/** The remedy every rotating-app refusal points at. */
|
|
25
|
-
export declare const SLACK_ROTATING_APP_REMEDY = "this app has token rotation on, which FastAgent does not support and Slack cannot turn off \u2014 create a new app (docs/slack.md \u2192 \"If token rotation is on\")";
|
|
26
|
-
export interface SlackAppCredentials {
|
|
27
|
-
appId: string;
|
|
28
|
-
clientId: string;
|
|
29
|
-
clientSecret: string;
|
|
30
|
-
signingSecret: string;
|
|
31
|
-
oauthAuthorizeUrl?: string;
|
|
32
|
-
}
|
|
33
|
-
/** Single-attempt BY DESIGN: an ambiguous response may already have created the app, so this never retries itself. */
|
|
34
|
-
export declare function createSlackApp(configToken: string, manifest: SlackAppManifest, options?: {
|
|
35
|
-
apiBaseUrl?: string;
|
|
36
|
-
fetch?: typeof fetch;
|
|
37
|
-
}): Promise<SlackAppCredentials>;
|
|
38
|
-
export declare function updateSlackAppManifest(configToken: string, appId: string, manifest: SlackAppManifest, options?: {
|
|
39
|
-
apiBaseUrl?: string;
|
|
40
|
-
fetch?: typeof fetch;
|
|
41
|
-
}): Promise<void>;
|
|
42
|
-
export interface RotatedSlackConfigToken {
|
|
43
|
-
token: string;
|
|
44
|
-
refreshToken: string;
|
|
45
|
-
expiresAt: number;
|
|
46
|
-
teamId?: string;
|
|
47
|
-
}
|
|
48
|
-
export declare function rotateSlackConfigToken(refreshToken: string, options?: {
|
|
49
|
-
apiBaseUrl?: string;
|
|
50
|
-
fetch?: typeof fetch;
|
|
51
|
-
}): Promise<RotatedSlackConfigToken>;
|
|
52
|
-
export interface SlackOAuthResult {
|
|
53
|
-
botToken: string;
|
|
54
|
-
appId: string;
|
|
55
|
-
teamId: string;
|
|
56
|
-
teamName?: string;
|
|
57
|
-
botUserId?: string;
|
|
58
|
-
scopes: string[];
|
|
59
|
-
}
|
|
60
|
-
export declare function exchangeSlackOAuthCode(input: {
|
|
61
|
-
clientId: string;
|
|
62
|
-
clientSecret: string;
|
|
63
|
-
code: string;
|
|
64
|
-
redirectUrl: string;
|
|
65
|
-
}, options?: {
|
|
66
|
-
apiBaseUrl?: string;
|
|
67
|
-
fetch?: typeof fetch;
|
|
68
|
-
}): Promise<SlackOAuthResult>;
|
|
69
|
-
export {};
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
const SLACK_API = "https://slack.com/api";
|
|
2
|
-
const REQUEST_TIMEOUT_MS = 30_000;
|
|
3
|
-
/** One `errors[]` entry that means "Slack could not verify this URL", by either signal it may carry. */
|
|
4
|
-
const rejectsRequestUrl = (e) => e.pointer?.endsWith("/request_url") === true ||
|
|
5
|
-
(/request.?url/i.test(e.message ?? "") && /verif|challenge/i.test(e.message ?? ""));
|
|
6
|
-
export class SlackConfigApiError extends Error {
|
|
7
|
-
code;
|
|
8
|
-
status;
|
|
9
|
-
/** Kept, not just formatted into the message. */
|
|
10
|
-
errors;
|
|
11
|
-
constructor(method, status, data, fallback) {
|
|
12
|
-
const code = data.error ?? fallback;
|
|
13
|
-
const fields = data.errors
|
|
14
|
-
?.map((error) => `${error.pointer ?? "manifest"}: ${error.message ?? "invalid"}`)
|
|
15
|
-
.join("; ");
|
|
16
|
-
super(`Slack ${method} failed: ${code}${fields ? ` — ${fields}` : ""}`);
|
|
17
|
-
this.name = "SlackConfigApiError";
|
|
18
|
-
this.code = code;
|
|
19
|
-
this.status = status;
|
|
20
|
-
this.errors = data.errors ?? [];
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
/** Whether a manifest call failed because Slack could not verify the `request_url` it carries. */
|
|
24
|
-
export function isSlackRequestUrlUnverified(error) {
|
|
25
|
-
return (error instanceof SlackConfigApiError && error.code === "invalid_manifest" && error.errors.some(rejectsRequestUrl));
|
|
26
|
-
}
|
|
27
|
-
/** Whether a manifest write was refused because the app has token rotation on, which Slack never lets off again. */
|
|
28
|
-
export function isSlackRotationLocked(error) {
|
|
29
|
-
if (!(error instanceof SlackConfigApiError))
|
|
30
|
-
return false;
|
|
31
|
-
return (error.code === "cannot_disable_once_enabled" ||
|
|
32
|
-
error.errors.some((e) => e.message?.includes("cannot_disable_once_enabled") || e.pointer?.endsWith("token_rotation_enabled")));
|
|
33
|
-
}
|
|
34
|
-
/** The remedy every rotating-app refusal points at. */
|
|
35
|
-
export const SLACK_ROTATING_APP_REMEDY = 'this app has token rotation on, which FastAgent does not support and Slack cannot turn off — create a new app (docs/slack.md → "If token rotation is on")';
|
|
36
|
-
async function slackJson(method, body, options = {}) {
|
|
37
|
-
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
38
|
-
let response;
|
|
39
|
-
let raw;
|
|
40
|
-
try {
|
|
41
|
-
response = await fetchFn(`${options.apiBaseUrl ?? SLACK_API}/${method}`, {
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: {
|
|
44
|
-
"content-type": "application/json; charset=utf-8",
|
|
45
|
-
...(options.token ? { authorization: `Bearer ${options.token}` } : {}),
|
|
46
|
-
},
|
|
47
|
-
body: JSON.stringify(body),
|
|
48
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
49
|
-
});
|
|
50
|
-
raw = await response.text();
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
throw new Error(`Slack ${method} request failed before receiving a response`, { cause: error });
|
|
54
|
-
}
|
|
55
|
-
let data;
|
|
56
|
-
try {
|
|
57
|
-
data = JSON.parse(raw);
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
throw new Error(`Slack ${method} failed: HTTP ${response.status} returned non-JSON`);
|
|
61
|
-
}
|
|
62
|
-
if (!response.ok || data.ok !== true) {
|
|
63
|
-
throw new SlackConfigApiError(method, response.status, data, raw.trim().slice(0, 200) || "unknown_error");
|
|
64
|
-
}
|
|
65
|
-
return data;
|
|
66
|
-
}
|
|
67
|
-
/** Single-attempt BY DESIGN: an ambiguous response may already have created the app, so this never retries itself. */
|
|
68
|
-
export async function createSlackApp(configToken, manifest, options = {}) {
|
|
69
|
-
const data = await slackJson("apps.manifest.create", { manifest: JSON.stringify(manifest) }, { ...options, token: configToken });
|
|
70
|
-
const credentials = data.credentials;
|
|
71
|
-
if (!data.app_id || !credentials?.client_id || !credentials.client_secret || !credentials.signing_secret) {
|
|
72
|
-
throw new Error("Slack apps.manifest.create succeeded but returned incomplete app credentials");
|
|
73
|
-
}
|
|
74
|
-
return {
|
|
75
|
-
appId: data.app_id,
|
|
76
|
-
clientId: credentials.client_id,
|
|
77
|
-
clientSecret: credentials.client_secret,
|
|
78
|
-
signingSecret: credentials.signing_secret,
|
|
79
|
-
oauthAuthorizeUrl: data.oauth_authorize_url,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
export async function updateSlackAppManifest(configToken, appId, manifest, options = {}) {
|
|
83
|
-
await slackJson("apps.manifest.update", { app_id: appId, manifest: JSON.stringify(manifest) }, { ...options, token: configToken });
|
|
84
|
-
}
|
|
85
|
-
export async function rotateSlackConfigToken(refreshToken, options = {}) {
|
|
86
|
-
const data = await slackJson("tooling.tokens.rotate", { refresh_token: refreshToken }, options);
|
|
87
|
-
if (!data.token || !data.refresh_token || typeof data.exp !== "number") {
|
|
88
|
-
throw new Error("Slack tooling.tokens.rotate succeeded but returned incomplete token credentials");
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
token: data.token,
|
|
92
|
-
refreshToken: data.refresh_token,
|
|
93
|
-
expiresAt: data.exp * 1000,
|
|
94
|
-
teamId: data.team_id,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function slackOAuthFailure(status, code) {
|
|
98
|
-
const reason = (() => {
|
|
99
|
-
switch (code) {
|
|
100
|
-
case "bad_client_secret":
|
|
101
|
-
return "Slack rejected the app client secret";
|
|
102
|
-
case "invalid_client_id":
|
|
103
|
-
return "Slack rejected the app client ID";
|
|
104
|
-
case "bad_redirect_uri":
|
|
105
|
-
case "invalid_redirect_uri":
|
|
106
|
-
return "Slack rejected the temporary OAuth redirect URL";
|
|
107
|
-
case "invalid_code":
|
|
108
|
-
return "Slack rejected the authorization code";
|
|
109
|
-
case "code_already_used":
|
|
110
|
-
return "the Slack authorization code was already used";
|
|
111
|
-
case "access_denied":
|
|
112
|
-
return "the Slack installation was not approved";
|
|
113
|
-
default:
|
|
114
|
-
return "Slack rejected the OAuth exchange";
|
|
115
|
-
}
|
|
116
|
-
})();
|
|
117
|
-
return new Error(`${reason} (HTTP ${status}); re-run fastagent add slack to resume installation`);
|
|
118
|
-
}
|
|
119
|
-
export async function exchangeSlackOAuthCode(input, options = {}) {
|
|
120
|
-
const authorization = Buffer.from(`${input.clientId}:${input.clientSecret}`, "utf8").toString("base64");
|
|
121
|
-
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
122
|
-
let response;
|
|
123
|
-
let raw;
|
|
124
|
-
try {
|
|
125
|
-
response = await fetchFn(`${options.apiBaseUrl ?? SLACK_API}/oauth.v2.access`, {
|
|
126
|
-
method: "POST",
|
|
127
|
-
headers: {
|
|
128
|
-
authorization: `Basic ${authorization}`,
|
|
129
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
130
|
-
},
|
|
131
|
-
body: new URLSearchParams({ code: input.code, redirect_uri: input.redirectUrl }).toString(),
|
|
132
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
133
|
-
});
|
|
134
|
-
raw = await response.text();
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
throw new Error("Slack oauth.v2.access request failed before receiving a response", { cause: error });
|
|
138
|
-
}
|
|
139
|
-
let data;
|
|
140
|
-
try {
|
|
141
|
-
data = JSON.parse(raw);
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
throw new Error(`Slack oauth.v2.access failed: HTTP ${response.status} returned non-JSON`);
|
|
145
|
-
}
|
|
146
|
-
if (!response.ok || data.ok !== true)
|
|
147
|
-
throw slackOAuthFailure(response.status, data.error);
|
|
148
|
-
if (!data.access_token || !data.app_id || !data.team?.id) {
|
|
149
|
-
throw new Error("Slack oauth.v2.access succeeded but returned incomplete bot credentials/app identity");
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
botToken: data.access_token,
|
|
153
|
-
appId: data.app_id,
|
|
154
|
-
teamId: data.team.id,
|
|
155
|
-
teamName: data.team.name,
|
|
156
|
-
botUserId: data.bot_user_id,
|
|
157
|
-
scopes: (data.scope ?? "")
|
|
158
|
-
.split(",")
|
|
159
|
-
.map((scope) => scope.trim())
|
|
160
|
-
.filter(Boolean),
|
|
161
|
-
};
|
|
162
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { createSlackApp, exchangeSlackOAuthCode, updateSlackAppManifest } from "./config-api.ts";
|
|
2
|
-
import { type SlackOnboardingState } from "./onboarding-state.ts";
|
|
3
|
-
export interface SlackOnboardIO {
|
|
4
|
-
note(message: string): void;
|
|
5
|
-
openUrl(url: string): void;
|
|
6
|
-
/** Wait for the one OAuth redirect. */
|
|
7
|
-
waitForOAuth(): Promise<{
|
|
8
|
-
code?: string;
|
|
9
|
-
state?: string;
|
|
10
|
-
error?: string;
|
|
11
|
-
}>;
|
|
12
|
-
/** Stage the runtime secrets into the gitignored .env. */
|
|
13
|
-
writeRuntimeSecrets(values: {
|
|
14
|
-
botToken?: string;
|
|
15
|
-
signingSecret?: string;
|
|
16
|
-
}): Promise<void>;
|
|
17
|
-
}
|
|
18
|
-
export interface SlackOnboardInput {
|
|
19
|
-
stateRoot: string;
|
|
20
|
-
state: SlackOnboardingState;
|
|
21
|
-
requestUrl: string;
|
|
22
|
-
redirectUrl: string;
|
|
23
|
-
}
|
|
24
|
-
/** Create/resume one internal Slack app and complete its workspace OAuth installation. */
|
|
25
|
-
export declare function onboardSlackApp(input: SlackOnboardInput, io: SlackOnboardIO, deps?: {
|
|
26
|
-
createApp?: typeof createSlackApp;
|
|
27
|
-
updateManifest?: typeof updateSlackAppManifest;
|
|
28
|
-
exchangeCode?: typeof exchangeSlackOAuthCode;
|
|
29
|
-
/** Attempts absorbing the setup tunnel's warm-up before Slack can verify its URL. */
|
|
30
|
-
attempts?: number;
|
|
31
|
-
retryMs?: number;
|
|
32
|
-
}): Promise<SlackOnboardingState>;
|
|
33
|
-
export declare function newSlackOnboardingState(input: {
|
|
34
|
-
appName: string;
|
|
35
|
-
configToken: string;
|
|
36
|
-
configRefreshToken: string;
|
|
37
|
-
/** When the pair was captured; pass it through rather than re-deriving the same expiry twice. */
|
|
38
|
-
configTokenExpiresAt?: number;
|
|
39
|
-
now?: number;
|
|
40
|
-
}): SlackOnboardingState;
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { retryWhile } from "../registration.js";
|
|
3
|
-
import { createSlackApp, exchangeSlackOAuthCode, isSlackRequestUrlUnverified, isSlackRotationLocked, SLACK_ROTATING_APP_REMEDY, SlackConfigApiError, updateSlackAppManifest, } from "./config-api.js";
|
|
4
|
-
import { buildSlackManifest, slackBotScopes } from "./manifest.js";
|
|
5
|
-
import { configTokenExpiry, currentSlackConfigToken, writeSlackOnboardingState, } from "./onboarding-state.js";
|
|
6
|
-
/** Create/resume one internal Slack app and complete its workspace OAuth installation. */
|
|
7
|
-
export async function onboardSlackApp(input, io, deps = {}) {
|
|
8
|
-
let state = input.state;
|
|
9
|
-
const current = await currentSlackConfigToken(input.stateRoot, state);
|
|
10
|
-
state = current.state;
|
|
11
|
-
/**
|
|
12
|
-
* Slack challenges request_url from ITS network during a manifest call and validates the whole manifest before
|
|
13
|
-
* acting, so an unverifiable URL created nothing and the call is safe to repeat.
|
|
14
|
-
*/
|
|
15
|
-
const whileUnverified = (call, onRetry) => retryWhile(call, isSlackRequestUrlUnverified, {
|
|
16
|
-
attempts: deps.attempts,
|
|
17
|
-
retryMs: deps.retryMs,
|
|
18
|
-
onRetry: ({ attempt, attempts }) => {
|
|
19
|
-
onRetry?.();
|
|
20
|
-
io.note(`Slack cannot reach the temporary setup URL yet (attempt ${attempt}/${attempts}); retrying…`);
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
const manifest = buildSlackManifest({
|
|
24
|
-
name: state.appName,
|
|
25
|
-
requestUrl: input.requestUrl,
|
|
26
|
-
redirectUrl: input.redirectUrl,
|
|
27
|
-
});
|
|
28
|
-
if (!state.appId) {
|
|
29
|
-
if (state.createAttemptedAt) {
|
|
30
|
-
throw new Error(`a Slack app creation request started at ${state.createAttemptedAt}, but no app ID was returned. ` +
|
|
31
|
-
"Inspect https://api.slack.com/apps before retrying; if an app exists, delete that incomplete app first, then remove the local Slack onboarding state and re-run");
|
|
32
|
-
}
|
|
33
|
-
io.note("Creating the internal Slack app from its FastAgent manifest…");
|
|
34
|
-
const setMarker = (value) => {
|
|
35
|
-
state = { ...state, createAttemptedAt: value };
|
|
36
|
-
writeSlackOnboardingState(input.stateRoot, state);
|
|
37
|
-
};
|
|
38
|
-
let created;
|
|
39
|
-
try {
|
|
40
|
-
created = await whileUnverified(() => {
|
|
41
|
-
// Record BEFORE the non-idempotent API call.
|
|
42
|
-
setMarker(new Date().toISOString());
|
|
43
|
-
return (deps.createApp ?? createSlackApp)(current.token, manifest);
|
|
44
|
-
}, () => setMarker(undefined));
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
const ambiguous = !(error instanceof SlackConfigApiError) ||
|
|
48
|
-
["fatal_error", "internal_error", "request_timeout", "service_unavailable", "failed_creating_app"].includes(error.code);
|
|
49
|
-
if (!ambiguous)
|
|
50
|
-
setMarker(undefined);
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
state = {
|
|
54
|
-
...state,
|
|
55
|
-
appId: created.appId,
|
|
56
|
-
createAttemptedAt: undefined,
|
|
57
|
-
clientId: created.clientId,
|
|
58
|
-
clientSecret: created.clientSecret,
|
|
59
|
-
signingSecret: created.signingSecret,
|
|
60
|
-
};
|
|
61
|
-
// Irreversible boundary first: a cancellation or .env write failure can resume without creating a duplicate.
|
|
62
|
-
writeSlackOnboardingState(input.stateRoot, state);
|
|
63
|
-
io.note(`Created Slack app ${created.appId}; credentials captured locally.`);
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
if (!state.clientId || !state.clientSecret) {
|
|
67
|
-
throw new Error(`Slack app ${state.appId} exists but OAuth client credentials are no longer available — ` +
|
|
68
|
-
"the app appears already installed; set SLACK_BOT_TOKEN/SLACK_SIGNING_SECRET in .env or remove the app and onboarding state to start over");
|
|
69
|
-
}
|
|
70
|
-
const appId = state.appId;
|
|
71
|
-
io.note(`Resuming Slack app ${appId}; refreshing its temporary setup URLs…`);
|
|
72
|
-
try {
|
|
73
|
-
await whileUnverified(() => (deps.updateManifest ?? updateSlackAppManifest)(current.token, appId, manifest));
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
// A resume of an app created with rotation on: the manifest is refused for good, so say what fixes it.
|
|
77
|
-
if (isSlackRotationLocked(error))
|
|
78
|
-
throw new Error(SLACK_ROTATING_APP_REMEDY, { cause: error });
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (state.signingSecret) {
|
|
83
|
-
await io.writeRuntimeSecrets({ signingSecret: state.signingSecret });
|
|
84
|
-
state = { ...state, signingSecret: undefined };
|
|
85
|
-
writeSlackOnboardingState(input.stateRoot, state);
|
|
86
|
-
}
|
|
87
|
-
if (!state.appId || !state.clientId || !state.clientSecret) {
|
|
88
|
-
throw new Error("Slack onboarding state lost app OAuth credentials before installation");
|
|
89
|
-
}
|
|
90
|
-
const oauthState = randomBytes(24).toString("hex");
|
|
91
|
-
const authorize = new URL("https://slack.com/oauth/v2/authorize");
|
|
92
|
-
authorize.searchParams.set("client_id", state.clientId);
|
|
93
|
-
authorize.searchParams.set("scope", slackBotScopes().join(","));
|
|
94
|
-
authorize.searchParams.set("redirect_uri", input.redirectUrl);
|
|
95
|
-
authorize.searchParams.set("state", oauthState);
|
|
96
|
-
// The redirect lands on the tunnel hostname, which THIS machine may not resolve yet (#421).
|
|
97
|
-
io.note(`Click Allow in Slack to install the app: ${authorize}\nIf the page it returns to does not load, reload it.`);
|
|
98
|
-
io.openUrl(authorize.toString());
|
|
99
|
-
const callback = await io.waitForOAuth();
|
|
100
|
-
if (callback.error)
|
|
101
|
-
throw new Error("Slack OAuth installation was not approved");
|
|
102
|
-
if (!callback.code)
|
|
103
|
-
throw new Error("Slack OAuth callback carried no authorization code");
|
|
104
|
-
if (!callback.state || callback.state !== oauthState)
|
|
105
|
-
throw new Error("Slack OAuth callback state mismatch");
|
|
106
|
-
const oauth = await (deps.exchangeCode ?? exchangeSlackOAuthCode)({
|
|
107
|
-
clientId: state.clientId,
|
|
108
|
-
clientSecret: state.clientSecret,
|
|
109
|
-
code: callback.code,
|
|
110
|
-
redirectUrl: input.redirectUrl,
|
|
111
|
-
});
|
|
112
|
-
if (oauth.appId !== state.appId)
|
|
113
|
-
throw new Error("Slack OAuth installed a different app than the manifest app");
|
|
114
|
-
if (slackBotScopes().some((scope) => !oauth.scopes.includes(scope))) {
|
|
115
|
-
throw new Error("Slack OAuth completed without all required bot scopes; re-run fastagent add slack to reinstall");
|
|
116
|
-
}
|
|
117
|
-
await io.writeRuntimeSecrets({ botToken: oauth.botToken });
|
|
118
|
-
state = {
|
|
119
|
-
...state,
|
|
120
|
-
clientSecret: undefined,
|
|
121
|
-
teamId: oauth.teamId,
|
|
122
|
-
teamName: oauth.teamName,
|
|
123
|
-
installedAt: new Date().toISOString(),
|
|
124
|
-
};
|
|
125
|
-
writeSlackOnboardingState(input.stateRoot, state);
|
|
126
|
-
return state;
|
|
127
|
-
}
|
|
128
|
-
export function newSlackOnboardingState(input) {
|
|
129
|
-
return {
|
|
130
|
-
version: 1,
|
|
131
|
-
appName: input.appName,
|
|
132
|
-
configToken: input.configToken,
|
|
133
|
-
configRefreshToken: input.configRefreshToken,
|
|
134
|
-
configTokenExpiresAt: input.configTokenExpiresAt ?? configTokenExpiry(input.now),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/** When a token pair captured `now` stops being usable — the ONE place that answers it. */
|
|
2
|
-
export declare function configTokenExpiry(now?: number): number;
|
|
3
|
-
export interface SlackOnboardingState {
|
|
4
|
-
version: 1;
|
|
5
|
-
appName: string;
|
|
6
|
-
appId?: string;
|
|
7
|
-
/** Set before apps.manifest.create; without an appId it blocks blind duplicate-creation retries. */
|
|
8
|
-
createAttemptedAt?: string;
|
|
9
|
-
clientId?: string;
|
|
10
|
-
/** Kept only while OAuth installation is unfinished, then deleted. */
|
|
11
|
-
clientSecret?: string;
|
|
12
|
-
/** Kept only until it has been staged into .env, then deleted. */
|
|
13
|
-
signingSecret?: string;
|
|
14
|
-
configToken: string;
|
|
15
|
-
configRefreshToken: string;
|
|
16
|
-
configTokenExpiresAt: number;
|
|
17
|
-
teamId?: string;
|
|
18
|
-
teamName?: string;
|
|
19
|
-
installedAt?: string;
|
|
20
|
-
}
|
|
21
|
-
export declare function readSlackOnboardingState(stateRoot: string): SlackOnboardingState | undefined;
|
|
22
|
-
/** Atomic replacement with owner-only permissions: this file carries a workspace-wide config refresh token. */
|
|
23
|
-
export declare function writeSlackOnboardingState(stateRoot: string, state: SlackOnboardingState): void;
|
|
24
|
-
export declare function currentSlackConfigToken(stateRoot: string, state: SlackOnboardingState, options?: {
|
|
25
|
-
now?: number;
|
|
26
|
-
apiBaseUrl?: string;
|
|
27
|
-
fetch?: typeof fetch;
|
|
28
|
-
}): Promise<{
|
|
29
|
-
token: string;
|
|
30
|
-
state: SlackOnboardingState;
|
|
31
|
-
}>;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { writeFileAtomic } from "../../atomic-write.js";
|
|
4
|
-
import { SECRET_FILE_MODE } from "../../paths.js";
|
|
5
|
-
import { rotateSlackConfigToken } from "./config-api.js";
|
|
6
|
-
/** Slack config access tokens expire in 12 hours; use 11h so registration rotates before the edge. */
|
|
7
|
-
const CONFIG_TOKEN_TTL_MS = 11 * 60 * 60_000;
|
|
8
|
-
/** When a token pair captured `now` stops being usable — the ONE place that answers it. */
|
|
9
|
-
export function configTokenExpiry(now = Date.now()) {
|
|
10
|
-
return now + CONFIG_TOKEN_TTL_MS;
|
|
11
|
-
}
|
|
12
|
-
function slackOnboardingStatePath(stateRoot) {
|
|
13
|
-
return join(stateRoot, "channels", "slack", "onboarding.json");
|
|
14
|
-
}
|
|
15
|
-
function validState(value) {
|
|
16
|
-
const state = value;
|
|
17
|
-
return (typeof state === "object" &&
|
|
18
|
-
state !== null &&
|
|
19
|
-
state.version === 1 &&
|
|
20
|
-
typeof state.appName === "string" &&
|
|
21
|
-
typeof state.configToken === "string" &&
|
|
22
|
-
typeof state.configRefreshToken === "string" &&
|
|
23
|
-
typeof state.configTokenExpiresAt === "number");
|
|
24
|
-
}
|
|
25
|
-
export function readSlackOnboardingState(stateRoot) {
|
|
26
|
-
const file = slackOnboardingStatePath(stateRoot);
|
|
27
|
-
let raw;
|
|
28
|
-
try {
|
|
29
|
-
raw = readFileSync(file, "utf8");
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
if (error.code === "ENOENT")
|
|
33
|
-
return undefined;
|
|
34
|
-
throw new Error(`cannot read Slack onboarding state ${file}: ${error.message}`);
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const value = JSON.parse(raw);
|
|
38
|
-
if (!validState(value))
|
|
39
|
-
throw new Error("unexpected shape/version");
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
throw new Error(`invalid Slack onboarding state ${file}: ${error.message}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/** Atomic replacement with owner-only permissions: this file carries a workspace-wide config refresh token. */
|
|
47
|
-
export function writeSlackOnboardingState(stateRoot, state) {
|
|
48
|
-
writeFileAtomic(slackOnboardingStatePath(stateRoot), `${JSON.stringify(state, null, 2)}\n`, SECRET_FILE_MODE);
|
|
49
|
-
}
|
|
50
|
-
export async function currentSlackConfigToken(stateRoot, state, options = {}) {
|
|
51
|
-
const now = options.now ?? Date.now();
|
|
52
|
-
if (state.configTokenExpiresAt > now + 5 * 60_000)
|
|
53
|
-
return { token: state.configToken, state };
|
|
54
|
-
const rotated = await rotateSlackConfigToken(state.configRefreshToken, options);
|
|
55
|
-
const next = {
|
|
56
|
-
...state,
|
|
57
|
-
configToken: rotated.token,
|
|
58
|
-
configRefreshToken: rotated.refreshToken,
|
|
59
|
-
configTokenExpiresAt: rotated.expiresAt,
|
|
60
|
-
teamId: state.teamId ?? rotated.teamId,
|
|
61
|
-
};
|
|
62
|
-
writeSlackOnboardingState(stateRoot, next);
|
|
63
|
-
return { token: next.configToken, state: next };
|
|
64
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { type RegistrationOutcome } from "../registration.ts";
|
|
2
|
-
export interface RegisterSlackWebhookOptions {
|
|
3
|
-
stateRoot: string;
|
|
4
|
-
environment?: "dev" | "production";
|
|
5
|
-
log?: (message: string) => void;
|
|
6
|
-
attempts?: number;
|
|
7
|
-
retryMs?: number;
|
|
8
|
-
apiBaseUrl?: string;
|
|
9
|
-
fetch?: typeof fetch;
|
|
10
|
-
}
|
|
11
|
-
/** Update one onboarded internal Slack app without sending its configuration credential to the host. */
|
|
12
|
-
export declare function registerSlackWebhook(baseUrl: string, options: RegisterSlackWebhookOptions): Promise<RegistrationOutcome>;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { retryWhile } from "../registration.js";
|
|
2
|
-
import { SLACK_ROTATING_APP_REMEDY, isSlackRequestUrlUnverified, isSlackRotationLocked, updateSlackAppManifest, } from "./config-api.js";
|
|
3
|
-
import { buildSlackManifest } from "./manifest.js";
|
|
4
|
-
import { currentSlackConfigToken, readSlackOnboardingState } from "./onboarding-state.js";
|
|
5
|
-
/** Update one onboarded internal Slack app without sending its configuration credential to the host. */
|
|
6
|
-
export async function registerSlackWebhook(baseUrl, options) {
|
|
7
|
-
const note = options.log ?? ((message) => console.error(message));
|
|
8
|
-
const publicBaseUrl = baseUrl.replace(/\/$/, "");
|
|
9
|
-
const add = `fastagent add slack${options.environment === "production" ? " --env production" : ""}`;
|
|
10
|
-
const repair = `${add} --replace-config`;
|
|
11
|
-
let state;
|
|
12
|
-
try {
|
|
13
|
-
state = readSlackOnboardingState(options.stateRoot);
|
|
14
|
-
}
|
|
15
|
-
catch (error) {
|
|
16
|
-
note(`[fastagent] slack: cannot read local onboarding state: ${String(error)}`);
|
|
17
|
-
return "failed";
|
|
18
|
-
}
|
|
19
|
-
if (!state?.appId || !state.installedAt) {
|
|
20
|
-
note(`[fastagent] slack: no completed local onboarding state on this machine — the config credential lives only where \`${add}\` ran. ` +
|
|
21
|
-
`Set Event Subscriptions → Request URL = ${publicBaseUrl}/slack manually in the Slack console, or re-run this command from the onboarding machine ` +
|
|
22
|
-
`(repair its expired/revoked tokens with \`${repair}\`)`);
|
|
23
|
-
return "manual";
|
|
24
|
-
}
|
|
25
|
-
const consoleFallback = `set ${publicBaseUrl}/slack as the Event Subscriptions Request URL in the Slack console`;
|
|
26
|
-
let current;
|
|
27
|
-
try {
|
|
28
|
-
// Rotation is not retried with the manifest below.
|
|
29
|
-
current = await currentSlackConfigToken(options.stateRoot, state, {
|
|
30
|
-
apiBaseUrl: options.apiBaseUrl,
|
|
31
|
-
fetch: options.fetch,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
note(`[fastagent] slack: could not refresh the App Configuration token: ${String(error)} — ` +
|
|
36
|
-
`re-run \`${repair}\` to repair it, or ${consoleFallback}`);
|
|
37
|
-
return "failed";
|
|
38
|
-
}
|
|
39
|
-
// Slack verifies the new request_url with a challenge DURING apps.manifest.update, from Slack's own network.
|
|
40
|
-
try {
|
|
41
|
-
await retryWhile(() => updateSlackAppManifest(current.token, current.state.appId, buildSlackManifest({
|
|
42
|
-
name: current.state.appName,
|
|
43
|
-
requestUrl: `${publicBaseUrl}/slack`,
|
|
44
|
-
// A manifest update replaces the whole manifest and redirect_urls may not be empty, so a placeholder is
|
|
45
|
-
// declared.
|
|
46
|
-
redirectUrl: `${publicBaseUrl}/slack/oauth/callback`,
|
|
47
|
-
}), { apiBaseUrl: options.apiBaseUrl, fetch: options.fetch }), isSlackRequestUrlUnverified, {
|
|
48
|
-
attempts: options.attempts,
|
|
49
|
-
retryMs: options.retryMs,
|
|
50
|
-
onRetry: ({ attempt, attempts }) => note(`[fastagent] slack: Slack cannot verify ${publicBaseUrl}/slack yet (attempt ${attempt}/${attempts}); retrying…`),
|
|
51
|
-
});
|
|
52
|
-
note(`[fastagent] slack: Event Subscriptions Request URL registered → ${publicBaseUrl}/slack`);
|
|
53
|
-
return "registered";
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
if (isSlackRotationLocked(error)) {
|
|
57
|
-
note(`[fastagent] slack: ${SLACK_ROTATING_APP_REMEDY}, or ${consoleFallback}`);
|
|
58
|
-
return "failed";
|
|
59
|
-
}
|
|
60
|
-
note(isSlackRequestUrlUnverified(error)
|
|
61
|
-
? `[fastagent] slack: Slack could not verify ${publicBaseUrl}/slack after retries (last error: ${String(error)}) — ` +
|
|
62
|
-
`once the app is up, ${consoleFallback}`
|
|
63
|
-
: `[fastagent] slack: automatic Request URL registration failed: ${String(error)} — ` +
|
|
64
|
-
`re-run \`${repair}\` to repair the configuration tokens, or ${consoleFallback}`);
|
|
65
|
-
return "failed";
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export interface SlackSetupServer {
|
|
2
|
-
port: number;
|
|
3
|
-
requestPath: string;
|
|
4
|
-
redirectPath: string;
|
|
5
|
-
/**
|
|
6
|
-
* The page a person pastes an App Configuration token pair on, for a setup a coding agent runs: the pair never
|
|
7
|
-
* passes through the agent's conversation or any file. Its path is the server's secret, like the OAuth redirect's.
|
|
8
|
-
*/
|
|
9
|
-
tokensPath: string;
|
|
10
|
-
/** The pair, once one `check` accepts is pasted; what `check` throws is shown on the page, which stays up. */
|
|
11
|
-
waitForTokens<T>(check: (pair: {
|
|
12
|
-
access: string;
|
|
13
|
-
refresh: string;
|
|
14
|
-
}) => T): Promise<T>;
|
|
15
|
-
/** Where the browser that pasted the pair goes next: Slack's install page. */
|
|
16
|
-
next(url: string): void;
|
|
17
|
-
/**
|
|
18
|
-
* Why the setup stopped, for a browser waiting on the page after the pair: resolves once it has been shown, at once
|
|
19
|
-
* when no browser waits there, or after one refresh window, so a setup can stop without leaving it on a dead page.
|
|
20
|
-
*/
|
|
21
|
-
failed(message: string): Promise<void>;
|
|
22
|
-
/** What the setup did, for a browser waiting on that page when the setup ends there (a saved pair replaced), as `failed`. */
|
|
23
|
-
done(message: string): Promise<void>;
|
|
24
|
-
waitForOAuth(timeoutMs?: number): Promise<{
|
|
25
|
-
code?: string;
|
|
26
|
-
state?: string;
|
|
27
|
-
error?: string;
|
|
28
|
-
}>;
|
|
29
|
-
close(): Promise<void>;
|
|
30
|
-
}
|
|
31
|
-
/** Temporary onboarding-only responder. */
|
|
32
|
-
export declare function startSlackSetupServer(): Promise<SlackSetupServer>;
|