@fastagent-sh/fastagent 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -12
- package/dist/agent.d.ts +24 -37
- package/dist/agent.js +7 -15
- package/dist/atomic-write.d.ts +13 -17
- package/dist/atomic-write.js +18 -29
- package/dist/bind.d.ts +12 -22
- package/dist/bind.js +18 -27
- package/dist/channel.d.ts +11 -33
- package/dist/channels/agentcore-limits.d.ts +2 -13
- package/dist/channels/agentcore-limits.js +2 -13
- package/dist/channels/agentcore-protocol.d.ts +67 -58
- package/dist/channels/agentcore-protocol.js +23 -15
- package/dist/channels/agentcore-service.d.ts +19 -33
- package/dist/channels/agentcore-service.js +176 -59
- package/dist/channels/agentcore.d.ts +39 -33
- package/dist/channels/agentcore.js +90 -224
- package/dist/channels/body.d.ts +15 -2
- package/dist/channels/body.js +24 -2
- package/dist/channels/busy.d.ts +1 -21
- package/dist/channels/busy.js +2 -39
- package/dist/channels/control.d.ts +17 -62
- package/dist/channels/control.js +74 -182
- package/dist/channels/define-channel.d.ts +39 -0
- package/dist/channels/define-channel.js +9 -0
- package/dist/channels/discover.d.ts +10 -31
- package/dist/channels/discover.js +37 -31
- package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
- package/dist/channels/feishu/bootstrap-token.js +7 -23
- package/dist/channels/feishu/card.d.ts +14 -28
- package/dist/channels/feishu/card.js +16 -30
- package/dist/channels/feishu/cloud.d.ts +1 -5
- package/dist/channels/feishu/context-buffer.d.ts +4 -23
- package/dist/channels/feishu/context-buffer.js +8 -31
- package/dist/channels/feishu/crypto.d.ts +10 -5
- package/dist/channels/feishu/crypto.js +12 -19
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu-api.js +5 -4
- package/dist/channels/feishu/feishu.d.ts +4 -10
- package/dist/channels/feishu/feishu.js +113 -227
- package/dist/channels/feishu/invoke-turn.d.ts +5 -4
- package/dist/channels/feishu/invoke-turn.js +36 -54
- package/dist/channels/feishu/model.d.ts +4 -21
- package/dist/channels/feishu/model.js +1 -8
- package/dist/channels/feishu/normalize.d.ts +2 -10
- package/dist/channels/feishu/normalize.js +7 -42
- package/dist/channels/feishu/parse.d.ts +9 -29
- package/dist/channels/feishu/parse.js +8 -26
- package/dist/channels/feishu/preview.d.ts +12 -14
- package/dist/channels/feishu/preview.js +108 -207
- package/dist/channels/feishu/register-app.d.ts +8 -23
- package/dist/channels/feishu/register-app.js +7 -20
- package/dist/channels/feishu/register-webhook.d.ts +2 -9
- package/dist/channels/feishu/register-webhook.js +9 -33
- package/dist/channels/feishu/scaffold/channel.ts +27 -19
- package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/feishu/setup-mode.d.ts +11 -36
- package/dist/channels/feishu/setup-mode.js +7 -26
- package/dist/channels/feishu/ws-ingress.d.ts +1 -2
- package/dist/channels/feishu/ws-ingress.js +6 -8
- package/dist/channels/http.d.ts +15 -15
- package/dist/channels/http.js +19 -9
- package/dist/channels/kit/attachment-path.d.ts +4 -5
- package/dist/channels/kit/attachment-path.js +10 -34
- package/dist/channels/kit/context-buffer.d.ts +14 -21
- package/dist/channels/kit/context-buffer.js +25 -38
- package/dist/channels/kit/delivery.d.ts +35 -0
- package/dist/channels/kit/delivery.js +129 -0
- package/dist/channels/kit/event-stream.d.ts +5 -0
- package/dist/channels/kit/event-stream.js +29 -0
- package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
- package/dist/channels/kit/invoke-turn-kit.js +89 -32
- package/dist/channels/kit/preview-kit.d.ts +18 -72
- package/dist/channels/kit/preview-kit.js +22 -88
- package/dist/channels/kit/signature.d.ts +3 -9
- package/dist/channels/kit/signature.js +3 -9
- package/dist/channels/kit/state.d.ts +1 -2
- package/dist/channels/kit/state.js +5 -14
- package/dist/channels/kit/stop-command.d.ts +1 -2
- package/dist/channels/kit/stop-command.js +6 -9
- package/dist/channels/kit/tasks.d.ts +1 -1
- package/dist/channels/kit/tasks.js +8 -21
- package/dist/channels/kit/text.d.ts +2 -16
- package/dist/channels/kit/text.js +23 -31
- package/dist/channels/kit/thread-participants.d.ts +4 -17
- package/dist/channels/kit/thread-participants.js +8 -55
- package/dist/channels/kit/transport.d.ts +26 -0
- package/dist/channels/kit/transport.js +20 -0
- package/dist/channels/kit/turn-queue.d.ts +7 -11
- package/dist/channels/kit/turn-queue.js +21 -39
- package/dist/channels/kit/turn-runner.d.ts +32 -18
- package/dist/channels/kit/turn-runner.js +98 -53
- package/dist/channels/kit/turn-store.d.ts +32 -32
- package/dist/channels/kit/turn-store.js +45 -88
- package/dist/channels/lark/lark.d.ts +1 -5
- package/dist/channels/lark/onboard.d.ts +3 -12
- package/dist/channels/lark/onboard.js +3 -13
- package/dist/channels/lark/scaffold/channel.ts +27 -19
- package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/registration.d.ts +9 -35
- package/dist/channels/registration.js +6 -37
- package/dist/channels/secret.js +2 -5
- package/dist/channels/serve.d.ts +47 -44
- package/dist/channels/serve.js +165 -101
- package/dist/channels/slack/config-api.d.ts +4 -27
- package/dist/channels/slack/config-api.js +5 -34
- package/dist/channels/slack/context-buffer.d.ts +2 -3
- package/dist/channels/slack/context-buffer.js +5 -5
- package/dist/channels/slack/invoke-turn.d.ts +3 -1
- package/dist/channels/slack/invoke-turn.js +25 -34
- package/dist/channels/slack/manifest.js +1 -3
- package/dist/channels/slack/model.d.ts +1 -2
- package/dist/channels/slack/model.js +1 -1
- package/dist/channels/slack/onboard.d.ts +4 -3
- package/dist/channels/slack/onboard.js +8 -11
- package/dist/channels/slack/onboarding-state.d.ts +3 -5
- package/dist/channels/slack/onboarding-state.js +9 -6
- package/dist/channels/slack/parse.d.ts +4 -13
- package/dist/channels/slack/parse.js +7 -28
- package/dist/channels/slack/preview.d.ts +12 -2
- package/dist/channels/slack/preview.js +292 -269
- package/dist/channels/slack/reaction.d.ts +11 -3
- package/dist/channels/slack/reaction.js +19 -8
- package/dist/channels/slack/register-webhook.js +4 -7
- package/dist/channels/slack/scaffold/channel.ts +20 -13
- package/dist/channels/slack/scaffold/slack-send.ts +2 -2
- package/dist/channels/slack/setup-server.d.ts +1 -5
- package/dist/channels/slack/setup-server.js +1 -5
- package/dist/channels/slack/shared-api.d.ts +1 -4
- package/dist/channels/slack/shared-api.js +2 -13
- package/dist/channels/slack/slack-api.d.ts +2 -1
- package/dist/channels/slack/slack-api.js +6 -4
- package/dist/channels/slack/slack.d.ts +11 -14
- package/dist/channels/slack/slack.js +121 -122
- package/dist/channels/sse.d.ts +1 -1
- package/dist/channels/sse.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +7 -22
- package/dist/channels/telegram/context-buffer.js +6 -23
- package/dist/channels/telegram/invoke-turn.d.ts +7 -7
- package/dist/channels/telegram/invoke-turn.js +24 -43
- package/dist/channels/telegram/parse.d.ts +24 -51
- package/dist/channels/telegram/parse.js +32 -84
- package/dist/channels/telegram/preview.d.ts +10 -1
- package/dist/channels/telegram/preview.js +79 -100
- package/dist/channels/telegram/register-webhook.js +5 -23
- package/dist/channels/telegram/scaffold/channel.ts +25 -18
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
- package/dist/channels/telegram/telegram-api.d.ts +4 -3
- package/dist/channels/telegram/telegram-api.js +9 -7
- package/dist/channels/telegram/telegram.d.ts +4 -18
- package/dist/channels/telegram/telegram.js +38 -94
- package/dist/channels/telegram/turn-store.d.ts +11 -14
- package/dist/channels/telegram/turn-store.js +8 -6
- package/dist/channels/wait-health.d.ts +4 -6
- package/dist/channels/wait-health.js +10 -14
- package/dist/cli/add-feishu.d.ts +2 -12
- package/dist/cli/add-feishu.js +24 -71
- package/dist/cli/add-slack.d.ts +5 -3
- package/dist/cli/add-slack.js +53 -76
- package/dist/cli/auth-view.d.ts +2 -16
- package/dist/cli/auth-view.js +2 -16
- package/dist/cli/commands/add.js +25 -40
- package/dist/cli/commands/chat.d.ts +0 -1
- package/dist/cli/commands/chat.js +5 -6
- package/dist/cli/commands/deploy/agentcore.js +60 -65
- package/dist/cli/commands/deploy/docker.js +50 -23
- package/dist/cli/commands/deploy/fly.js +23 -61
- package/dist/cli/commands/deploy/railway.js +23 -18
- package/dist/cli/commands/deploy/shared.d.ts +38 -57
- package/dist/cli/commands/deploy/shared.js +40 -59
- package/dist/cli/commands/deploy.d.ts +10 -27
- package/dist/cli/commands/deploy.js +38 -60
- package/dist/cli/commands/destroy.d.ts +4 -0
- package/dist/cli/commands/destroy.js +56 -0
- package/dist/cli/commands/dev.d.ts +2 -1
- package/dist/cli/commands/dev.js +20 -23
- package/dist/cli/commands/info.d.ts +0 -2
- package/dist/cli/commands/info.js +83 -49
- package/dist/cli/commands/init.d.ts +1 -2
- package/dist/cli/commands/init.js +16 -55
- package/dist/cli/commands/invoke.d.ts +0 -1
- package/dist/cli/commands/invoke.js +5 -7
- package/dist/cli/commands/login.d.ts +2 -1
- package/dist/cli/commands/login.js +33 -38
- package/dist/cli/commands/logs.js +3 -4
- package/dist/cli/commands/models.js +1 -5
- package/dist/cli/commands/routine-run.d.ts +6 -0
- package/dist/cli/commands/routine-run.js +47 -0
- package/dist/cli/commands/routine.d.ts +25 -0
- package/dist/cli/commands/routine.js +122 -0
- package/dist/cli/commands/start.d.ts +31 -3
- package/dist/cli/commands/start.js +131 -81
- package/dist/cli/commands/tool.js +26 -12
- package/dist/cli/fail.d.ts +13 -23
- package/dist/cli/fail.js +20 -23
- package/dist/cli/invoke-stream.d.ts +1 -8
- package/dist/cli/kernel.d.ts +6 -38
- package/dist/cli/kernel.js +14 -77
- package/dist/cli/models-view.d.ts +1 -9
- package/dist/cli/models-view.js +11 -22
- package/dist/cli/program.d.ts +5 -6
- package/dist/cli/program.js +120 -157
- package/dist/cli/serve.d.ts +57 -41
- package/dist/cli/serve.js +94 -121
- package/dist/cli/shared.d.ts +15 -61
- package/dist/cli/shared.js +55 -181
- package/dist/cli.js +1 -8
- package/dist/collect.d.ts +6 -0
- package/dist/collect.js +6 -0
- package/dist/core.d.ts +4 -1
- package/dist/core.js +9 -7
- package/dist/declared-secrets.d.ts +55 -0
- package/dist/declared-secrets.js +88 -0
- package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
- package/dist/deploy/agentcore/aws-cli.js +64 -0
- package/dist/deploy/agentcore/destroy.d.ts +51 -0
- package/dist/deploy/agentcore/destroy.js +273 -0
- package/dist/deploy/agentcore/forwarder.js +28 -79
- package/dist/deploy/agentcore/logs.d.ts +2 -18
- package/dist/deploy/agentcore/logs.js +27 -51
- package/dist/deploy/agentcore/plan.d.ts +85 -105
- package/dist/deploy/agentcore/plan.js +152 -314
- package/dist/deploy/agentcore/run.d.ts +28 -28
- package/dist/deploy/agentcore/run.js +227 -306
- package/dist/deploy/agentcore/zip.d.ts +2 -15
- package/dist/deploy/agentcore/zip.js +3 -17
- package/dist/deploy/build-context.d.ts +21 -0
- package/dist/deploy/build-context.js +137 -0
- package/dist/deploy/channel-ingress.d.ts +26 -39
- package/dist/deploy/channel-ingress.js +57 -28
- package/dist/deploy/container.d.ts +20 -34
- package/dist/deploy/container.js +49 -100
- package/dist/deploy/docker/plan.d.ts +12 -11
- package/dist/deploy/docker/plan.js +43 -24
- package/dist/deploy/docker/run.d.ts +22 -25
- package/dist/deploy/docker/run.js +39 -37
- package/dist/deploy/fly/plan.d.ts +15 -44
- package/dist/deploy/fly/plan.js +41 -73
- package/dist/deploy/fly/run.d.ts +16 -51
- package/dist/deploy/fly/run.js +42 -95
- package/dist/deploy/hosts.d.ts +24 -3
- package/dist/deploy/hosts.js +24 -3
- package/dist/deploy/preflight.d.ts +47 -41
- package/dist/deploy/preflight.js +282 -383
- package/dist/deploy/railway/plan.d.ts +16 -42
- package/dist/deploy/railway/plan.js +42 -60
- package/dist/deploy/railway/run.d.ts +30 -68
- package/dist/deploy/railway/run.js +86 -100
- package/dist/deploy/registration-gate.d.ts +6 -13
- package/dist/deploy/residency.d.ts +37 -0
- package/dist/deploy/residency.js +24 -0
- package/dist/deploy/runner.d.ts +10 -9
- package/dist/deploy/runner.js +7 -18
- package/dist/deploy/secrets.d.ts +40 -41
- package/dist/deploy/secrets.js +117 -103
- package/dist/deploy/workspace.d.ts +50 -0
- package/dist/deploy/workspace.js +197 -0
- package/dist/dev-supervisor.d.ts +1 -9
- package/dist/dev-supervisor.js +21 -64
- package/dist/effect-port.d.ts +50 -0
- package/dist/effect-port.js +77 -0
- package/dist/engines/pi/agent-session-factory.d.ts +50 -69
- package/dist/engines/pi/agent-session-factory.js +249 -146
- package/dist/engines/pi/auth.d.ts +15 -11
- package/dist/engines/pi/auth.js +96 -99
- package/dist/engines/pi/chat.d.ts +1 -5
- package/dist/engines/pi/chat.js +2 -14
- package/dist/engines/pi/config.d.ts +53 -51
- package/dist/engines/pi/config.js +83 -92
- package/dist/engines/pi/create.d.ts +60 -75
- package/dist/engines/pi/create.js +108 -141
- package/dist/engines/pi/definition.d.ts +14 -58
- package/dist/engines/pi/definition.js +21 -85
- package/dist/engines/pi/invoke-session.d.ts +5 -25
- package/dist/engines/pi/invoke-session.js +209 -244
- package/dist/engines/pi/login.d.ts +95 -35
- package/dist/engines/pi/login.js +259 -107
- package/dist/engines/pi/machine.d.ts +31 -0
- package/dist/engines/pi/machine.js +106 -0
- package/dist/engines/pi/models.d.ts +70 -89
- package/dist/engines/pi/models.js +188 -81
- package/dist/engines/pi/open.d.ts +65 -53
- package/dist/engines/pi/open.js +129 -69
- package/dist/engines/pi/report.d.ts +3 -12
- package/dist/engines/pi/report.js +2 -16
- package/dist/engines/pi/search-tools.d.ts +1 -9
- package/dist/engines/pi/search-tools.js +26 -57
- package/dist/engines/pi/service.d.ts +2 -16
- package/dist/engines/pi/service.js +2 -16
- package/dist/engines/pi/session-builder.d.ts +5 -9
- package/dist/engines/pi/session-builder.js +44 -82
- package/dist/engines/pi/session-control.js +286 -269
- package/dist/engines/pi/session-effects.d.ts +14 -0
- package/dist/engines/pi/session-effects.js +30 -0
- package/dist/engines/pi/session-inheritance.d.ts +9 -18
- package/dist/engines/pi/session-inheritance.js +103 -160
- package/dist/engines/pi/session-markers.d.ts +31 -30
- package/dist/engines/pi/session-markers.js +26 -25
- package/dist/engines/pi/session-settings.d.ts +6 -22
- package/dist/engines/pi/session-settings.js +10 -21
- package/dist/engines/pi/session-store.d.ts +26 -92
- package/dist/engines/pi/session-store.js +96 -310
- package/dist/engines/pi/tool-context.d.ts +17 -45
- package/dist/engines/pi/tool-context.js +7 -26
- package/dist/engines/pi/tool.d.ts +45 -36
- package/dist/engines/pi/tool.js +43 -57
- package/dist/engines/pi/turn-kit.d.ts +14 -52
- package/dist/engines/pi/turn-kit.js +18 -67
- package/dist/engines/pi/wake-tool.d.ts +2 -10
- package/dist/engines/pi/wake-tool.js +7 -27
- package/dist/env.d.ts +27 -28
- package/dist/env.js +45 -50
- package/dist/index.js +0 -2
- package/dist/lark.d.ts +1 -2
- package/dist/lark.js +1 -2
- package/dist/loader.d.ts +14 -55
- package/dist/loader.js +24 -43
- package/dist/log.d.ts +2 -12
- package/dist/log.js +3 -24
- package/dist/node.d.ts +2 -15
- package/dist/node.js +2 -18
- package/dist/observe.d.ts +2 -8
- package/dist/open-url.d.ts +1 -1
- package/dist/open-url.js +1 -1
- package/dist/paths.d.ts +70 -134
- package/dist/paths.js +165 -232
- package/dist/pi.d.ts +4 -6
- package/dist/pi.js +3 -3
- package/dist/proxy.d.ts +16 -6
- package/dist/proxy.js +52 -16
- package/dist/runtime.d.ts +18 -12
- package/dist/runtime.js +18 -11
- package/dist/scaffold/add-channel.d.ts +14 -31
- package/dist/scaffold/add-channel.js +48 -112
- package/dist/scaffold/init.d.ts +9 -31
- package/dist/scaffold/init.js +45 -138
- package/dist/scaffold/templates/env.example +6 -4
- package/dist/scaffold/templates/fastagent.config.ts +36 -0
- package/dist/scaffold/templates.d.ts +3 -3
- package/dist/scaffold/templates.js +5 -8
- package/dist/scaffold/vendor-skill.d.ts +2 -5
- package/dist/scaffold/vendor-skill.js +19 -29
- package/dist/schedule/cron.d.ts +9 -5
- package/dist/schedule/cron.js +9 -11
- package/dist/schedule/discover.d.ts +12 -12
- package/dist/schedule/discover.js +43 -34
- package/dist/schedule/routine.d.ts +43 -0
- package/dist/schedule/routine.js +32 -0
- package/dist/schedule/run.d.ts +55 -0
- package/dist/schedule/run.js +146 -0
- package/dist/schedule/scheduler.d.ts +44 -55
- package/dist/schedule/scheduler.js +291 -265
- package/dist/schedule/state.d.ts +105 -10
- package/dist/schedule/state.js +293 -19
- package/dist/schedule/wake-alarm.d.ts +10 -23
- package/dist/schedule/wake-alarm.js +91 -121
- package/dist/schedule/wakeups.d.ts +23 -39
- package/dist/schedule/wakeups.js +33 -75
- package/dist/secrets-gate.d.ts +32 -0
- package/dist/secrets-gate.js +33 -0
- package/dist/service.d.ts +137 -98
- package/dist/service.js +250 -252
- package/dist/session-remote.d.ts +33 -44
- package/dist/session-remote.js +203 -192
- package/dist/session.d.ts +153 -228
- package/dist/session.js +33 -55
- package/dist/tunnel.d.ts +8 -36
- package/dist/tunnel.js +24 -65
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +10 -16
- package/dist/channels/agentcore-state.d.ts +0 -77
- package/dist/channels/agentcore-state.js +0 -276
- package/dist/channels/github/github.d.ts +0 -31
- package/dist/channels/github/github.js +0 -89
- package/dist/channels/github/scaffold/channel.ts +0 -25
- package/dist/cli/commands/attach.d.ts +0 -108
- package/dist/cli/commands/attach.js +0 -637
- package/dist/cli/commands/fire.d.ts +0 -7
- package/dist/cli/commands/fire.js +0 -38
- package/dist/cli/commands/schedule.d.ts +0 -12
- package/dist/cli/commands/schedule.js +0 -87
- package/dist/github.d.ts +0 -3
- package/dist/github.js +0 -2
- package/dist/scaffold/templates/fastagent.config.mjs +0 -23
- package/dist/schedule/audit.d.ts +0 -18
- package/dist/schedule/audit.js +0 -57
- package/dist/schedule/schedule.d.ts +0 -35
- package/dist/schedule/schedule.js +0 -23
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Readiness probe for a server THIS process can reach
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* NOT a probe for a public URL a platform must reach: a freshly minted hostname (a quick tunnel, a
|
|
6
|
-
* fresh deploy) is routinely unreachable from here for a minute or more while the platform reaches it
|
|
7
|
-
* fine, and polling it hard from t+0 makes that worse (#421). The webhook registrars therefore let the
|
|
8
|
-
* platform's own URL verification be the probe, retried, instead of gating on this.
|
|
2
|
+
* Readiness probe for a server THIS process can reach over HTTP — the local port `deploy docker --run` just
|
|
3
|
+
* published, the live probes' own origins, and the public URL a host deploy must clear before it registers a webhook
|
|
4
|
+
* (`publicHealthGate` in deploy/channel-ingress.ts).
|
|
9
5
|
*/
|
|
10
6
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*/
|
|
16
|
-
export async function waitForHealth(healthUrl, timeoutMs, intervalMs) {
|
|
7
|
+
/** Poll `healthUrl` until it responds 200, or the timeout elapses. */
|
|
8
|
+
export async function waitForHealth(healthUrl, timeoutMs, intervalMs,
|
|
9
|
+
/** Optional liveness answer from whoever runs the server. */
|
|
10
|
+
stillStarting) {
|
|
17
11
|
const deadline = Date.now() + timeoutMs;
|
|
18
12
|
for (;;) {
|
|
19
13
|
try {
|
|
@@ -21,10 +15,12 @@ export async function waitForHealth(healthUrl, timeoutMs, intervalMs) {
|
|
|
21
15
|
return true;
|
|
22
16
|
}
|
|
23
17
|
catch {
|
|
24
|
-
|
|
18
|
+
// not routable yet — keep polling until the deadline
|
|
25
19
|
}
|
|
26
20
|
if (Date.now() >= deadline)
|
|
27
21
|
return false;
|
|
22
|
+
if (stillStarting && !(await stillStarting()))
|
|
23
|
+
return false;
|
|
28
24
|
await sleep(intervalMs);
|
|
29
25
|
}
|
|
30
26
|
}
|
package/dist/cli/add-feishu.d.ts
CHANGED
|
@@ -11,20 +11,10 @@ export declare function configureGroupBehavior(input: {
|
|
|
11
11
|
apiBase: string;
|
|
12
12
|
api: Pick<FeishuApi, "listAppScopes" | "addAppScopes">;
|
|
13
13
|
behavior: FeishuGroupBehavior;
|
|
14
|
-
/** Whether the author chose the behavior (flag or prompt).
|
|
15
|
-
* reports only — it must never PATCH the sensitive scope into the app draft. */
|
|
14
|
+
/** Whether the author chose the behavior (flag or prompt). */
|
|
16
15
|
explicit: boolean;
|
|
17
16
|
note?: (message: string) => void;
|
|
18
17
|
openUrl?: (url: string) => void;
|
|
19
18
|
}): Promise<GroupBehaviorSetup>;
|
|
20
|
-
/**
|
|
21
|
-
* Create or resume the platform app behind `add feishu` / `add lark`. `target` is the AGENT DIR;
|
|
22
|
-
* credentials land in its `.env` — whose real path is {@link dotEnvPath}, printed rather than spelled,
|
|
23
|
-
* because `FASTAGENT_SECRETS_DIR` moves it and a hardcoded `.secrets/.env` would name a file this run
|
|
24
|
-
* did not write. Returns credentials for the
|
|
25
|
-
* caller's generic .env write (the guided Lark path), or undefined when nothing remains to write —
|
|
26
|
-
* the feishu path persists its own two credential stages internally (the App ID/Secret boundary is
|
|
27
|
-
* irreversible and must not wait for the caller). Throws on refusal (a non-interactive lark run);
|
|
28
|
-
* the caller surfaces that as a startup failure.
|
|
29
|
-
*/
|
|
19
|
+
/** Create or resume the platform app behind `add feishu` / `add lark`. */
|
|
30
20
|
export declare function onboardFeishuCloudApp(target: string, kind: "feishu" | "lark", ingress?: FeishuSubscriptionMode, groupBehavior?: GroupBehaviorChoice): Promise<Record<string, string> | undefined>;
|
package/dist/cli/add-feishu.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `fastagent add feishu|lark` app onboarding — the cloud-facing half of runAdd, kept out of cli.ts
|
|
3
|
-
*
|
|
4
|
-
* testable modules (register-app.ts, bootstrap-token.ts, lark/onboard.ts); this layer is the terminal
|
|
5
|
-
* wiring: clack prompts, .env staging, browser opens, progress lines.
|
|
6
|
-
*
|
|
7
|
-
* Feishu (scan-to-create): the device flow creates the app and persists App ID/Secret at the
|
|
8
|
-
* irreversible boundary. WebSocket stops there; webhook additionally captures the Verification Token
|
|
9
|
-
* over a throwaway tunnel. Lark uses the unbound launcher + credential validation, then either stops
|
|
10
|
-
* for WebSocket or probes the same webhook/token bootstrap with the config-route-404 manual fallback.
|
|
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.
|
|
11
4
|
*/
|
|
12
5
|
import { readFile } from "node:fs/promises";
|
|
13
6
|
import { isCancel, log as clackLog, password, text as clackText } from "@clack/prompts";
|
|
@@ -36,14 +29,11 @@ export async function configureGroupBehavior(input) {
|
|
|
36
29
|
scopes = [];
|
|
37
30
|
}
|
|
38
31
|
const granted = (name) => scopes.some((scope) => scope.name === name && scope.grantStatus === 1 && (scope.type === undefined || scope.type === "tenant"));
|
|
39
|
-
// Same type filter as `granted`: a user-type entry is not the tenant scope this path needs, so
|
|
40
|
-
//
|
|
41
|
-
// that would actually add it.
|
|
32
|
+
// Same type filter as `granted`: a user-type entry is not the tenant scope this path needs, so treating one as "on
|
|
33
|
+
// the app" would report an approval that can never arrive and skip the PATCH that would actually add it.
|
|
42
34
|
const onApp = (name) => scopes.some((scope) => scope.name === name && (scope.type === undefined || scope.type === "tenant"));
|
|
43
|
-
// Both halves of the recommended path: delivery (the platform pushes un-mentioned group messages)
|
|
44
|
-
//
|
|
45
|
-
// The read capability has two spellings and `im:message` is the superset, so an app holding it needs
|
|
46
|
-
// nothing added — asking anyway would cost the author a second tenant-admin approval round.
|
|
35
|
+
// Both halves of the recommended path: delivery (the platform pushes un-mentioned group messages) and reading a
|
|
36
|
+
// quoted message (so a thread's opening ask carries what it replies to).
|
|
47
37
|
const missing = FEISHU_CONTEXT_ONBOARDING_SCOPES.filter((entry) => !scopeSatisfied(entry, granted));
|
|
48
38
|
const missingNames = missing.map((entry) => entry.request);
|
|
49
39
|
if (behavior === "mentions") {
|
|
@@ -72,10 +62,7 @@ export async function configureGroupBehavior(input) {
|
|
|
72
62
|
return { publishReady: true };
|
|
73
63
|
}
|
|
74
64
|
const permissionUrl = `${apiBase}/app/${encodeURIComponent(appId)}/permission`;
|
|
75
|
-
// A missing scope is in one of two states, and they need different actions
|
|
76
|
-
// not yet approved (nothing to add — wait for the admin), or absent from the draft entirely.
|
|
77
|
-
// Same superset rule as `missing`: a draft already requesting `im:message` is awaiting approval, not
|
|
78
|
-
// missing something to add.
|
|
65
|
+
// A missing scope is in one of two states, and they need different actions.
|
|
79
66
|
const awaitingApproval = missing.filter((entry) => scopeSatisfied(entry, onApp)).map((entry) => entry.request);
|
|
80
67
|
const toRequest = missing.filter((entry) => !scopeSatisfied(entry, onApp)).map((entry) => entry.request);
|
|
81
68
|
if (toRequest.length === 0) {
|
|
@@ -84,11 +71,8 @@ export async function configureGroupBehavior(input) {
|
|
|
84
71
|
return { publishReady: false };
|
|
85
72
|
}
|
|
86
73
|
if (!explicit) {
|
|
87
|
-
// Defaulted, not chosen: report the gap and how to opt in, but leave the app's requested
|
|
88
|
-
//
|
|
89
|
-
// Name what is ACTUALLY missing: the common upgrade has the delivery scope already granted and
|
|
90
|
-
// only the read scope absent, and pointing at a granted permission sends the author looking in
|
|
91
|
-
// the wrong place.
|
|
74
|
+
// Defaulted, not chosen: report the gap and how to opt in, but leave the app's requested permission set untouched
|
|
75
|
+
// (a scripted re-run must not silently escalate a mention-only app).
|
|
92
76
|
note(`[fastagent] ${missingNames.join(" + ")} not granted (or could not be verified) — group behavior was ` +
|
|
93
77
|
`defaulted, so nothing was requested. Re-run with --group-behavior context to add ${missing.length > 1 ? "them" : "it"} ` +
|
|
94
78
|
`to the app draft, or --group-behavior mentions to stay least-privilege: ${permissionUrl}`);
|
|
@@ -104,16 +88,7 @@ export async function configureGroupBehavior(input) {
|
|
|
104
88
|
openUrl(permissionUrl);
|
|
105
89
|
return { publishReady: false };
|
|
106
90
|
}
|
|
107
|
-
/**
|
|
108
|
-
* Create or resume the platform app behind `add feishu` / `add lark`. `target` is the AGENT DIR;
|
|
109
|
-
* credentials land in its `.env` — whose real path is {@link dotEnvPath}, printed rather than spelled,
|
|
110
|
-
* because `FASTAGENT_SECRETS_DIR` moves it and a hardcoded `.secrets/.env` would name a file this run
|
|
111
|
-
* did not write. Returns credentials for the
|
|
112
|
-
* caller's generic .env write (the guided Lark path), or undefined when nothing remains to write —
|
|
113
|
-
* the feishu path persists its own two credential stages internally (the App ID/Secret boundary is
|
|
114
|
-
* irreversible and must not wait for the caller). Throws on refusal (a non-interactive lark run);
|
|
115
|
-
* the caller surfaces that as a startup failure.
|
|
116
|
-
*/
|
|
91
|
+
/** Create or resume the platform app behind `add feishu` / `add lark`. */
|
|
117
92
|
export async function onboardFeishuCloudApp(target, kind, ingress = "webhook", groupBehavior = { behavior: "context", explicit: false }) {
|
|
118
93
|
const env = dotEnvPath(target); // the file actually written — never the default spelling
|
|
119
94
|
const { envPrefix, apiBase, capabilities } = cloudFor(kind);
|
|
@@ -125,8 +100,7 @@ export async function onboardFeishuCloudApp(target, kind, ingress = "webhook", g
|
|
|
125
100
|
const existing = await activeDotEnvValues(target, requiredNames);
|
|
126
101
|
if (Object.keys(existing).length === requiredNames.length) {
|
|
127
102
|
console.error(`[fastagent] ${requiredNames.join("/")} already set in ${env} — keeping them`);
|
|
128
|
-
// WebSocket still needs its console mode/publish guidance.
|
|
129
|
-
// onboarding, but group visibility must still be inspected/configured on every explicit re-run.
|
|
103
|
+
// WebSocket still needs its console mode/publish guidance.
|
|
130
104
|
if (ingress === "webhook") {
|
|
131
105
|
const appId = existing[`${envPrefix}_APP_ID`];
|
|
132
106
|
const appSecret = existing[`${envPrefix}_APP_SECRET`];
|
|
@@ -176,8 +150,7 @@ export async function onboardFeishuCloudApp(target, kind, ingress = "webhook", g
|
|
|
176
150
|
startTunnel: (port) => startCloudflareTunnel(port),
|
|
177
151
|
onTunnelReady: (url) => console.error(`[fastagent] temporary tunnel ready → ${url}; registering webhook mode now…`),
|
|
178
152
|
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…`),
|
|
179
|
-
// A route-level 404 is definitive, not edge weather: fall back immediately.
|
|
180
|
-
// actual edge/network weather; scope/auth/config failures remain immediate.
|
|
153
|
+
// A route-level 404 is definitive, not edge weather: fall back immediately.
|
|
181
154
|
shouldRetryPatch: (error) => !isFeishuConfigApiMissing(error) && isTransientFeishuRegistrationError(error),
|
|
182
155
|
});
|
|
183
156
|
console.error(`[fastagent] Lark Verification Token captured; Subscription mode changed to webhook in the app draft`);
|
|
@@ -207,17 +180,7 @@ export async function onboardFeishuCloudApp(target, kind, ingress = "webhook", g
|
|
|
207
180
|
});
|
|
208
181
|
return Object.fromEntries(Object.entries(credentials).filter((entry) => typeof entry[1] === "string"));
|
|
209
182
|
}
|
|
210
|
-
/**
|
|
211
|
-
* The scan-to-create flow `add feishu` runs by default. The device-authorization grant
|
|
212
|
-
* creates a pre-configured agent app (bot capability, messaging scopes, event subscriptions) when the
|
|
213
|
-
* user confirms a link, then persists App ID/Secret at the irreversible boundary. That completes
|
|
214
|
-
* WebSocket credentials; webhook continues through challenge-captured Token persistence. The throwaway
|
|
215
|
-
* Request URL is later replaced by `dev --tunnel` / `deploy --run`.
|
|
216
|
-
*
|
|
217
|
-
* Feishu is the reference cloud and the only kind that runs this BOUND device flow. Lark is an explicit
|
|
218
|
-
* compatibility profile: its lagging control plane uses the unbound launcher + guided credentials,
|
|
219
|
-
* then probes the canonical token/mode bootstrap with a manual fallback.
|
|
220
|
-
*/
|
|
183
|
+
/** The scan-to-create flow `add feishu` runs by default. */
|
|
221
184
|
async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
222
185
|
const env = dotEnvPath(target); // the file actually written — never the default spelling
|
|
223
186
|
const { apiBase } = cloudFor("feishu");
|
|
@@ -233,8 +196,7 @@ async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
|
233
196
|
const app = await registerFeishuApp({
|
|
234
197
|
name: "{user}'s agent", // the platform expands {user} to the confirming user's name; editable on the page
|
|
235
198
|
desc: "Served by fastagent",
|
|
236
|
-
// The agent template alone is not enough to SERVE (see feishuAppAddons).
|
|
237
|
-
// permission approval and version publishing remain explicit console work.
|
|
199
|
+
// The agent template alone is not enough to SERVE (see feishuAppAddons).
|
|
238
200
|
addons: feishuAppAddons(),
|
|
239
201
|
onVerificationUrl: ({ url, expiresInS }) => {
|
|
240
202
|
console.error(`\n Opening the confirmation link in your browser (or open it in Feishu / render it as a QR code) — valid for ${Math.round(expiresInS / 60)} minutes:\n\n ${url}\n\n waiting for confirmation… (keep this running — the credentials are delivered here)`);
|
|
@@ -242,17 +204,13 @@ async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
|
242
204
|
},
|
|
243
205
|
});
|
|
244
206
|
console.error(`[fastagent] app created: ${app.appId}${app.tenantBrand ? ` (${app.tenantBrand} tenant)` : ""}`);
|
|
245
|
-
// A cross-brand confirmation should be impossible (each confirm page refuses the other brand's
|
|
246
|
-
// code) — but if the platform ever reports one, the credentials would land in the WRONG kind's env
|
|
247
|
-
// namespace and serve the wrong cloud. Fail visibly instead of writing them.
|
|
207
|
+
// A cross-brand confirmation should be impossible (each confirm page refuses the other brand's code).
|
|
248
208
|
if (app.tenantBrand && app.tenantBrand !== "feishu") {
|
|
249
209
|
throw new Error(`the confirming account is a ${app.tenantBrand} tenant, but this is \`add feishu\` — run \`fastagent add ${app.tenantBrand}\` instead`);
|
|
250
210
|
}
|
|
251
211
|
appId = app.appId;
|
|
252
212
|
appSecret = app.appSecret;
|
|
253
|
-
// IRREVERSIBLE BOUNDARY: the remote app now exists and its one-time Secret is in memory.
|
|
254
|
-
// both before any config read, temporary tunnel, or Token bootstrap can be interrupted. Partial old
|
|
255
|
-
// lines are overwritten because these newly-minted credentials are authoritative as one pair.
|
|
213
|
+
// IRREVERSIBLE BOUNDARY: the remote app now exists and its one-time Secret is in memory.
|
|
256
214
|
const staged = {
|
|
257
215
|
FEISHU_APP_ID: appId,
|
|
258
216
|
FEISHU_APP_SECRET: appSecret,
|
|
@@ -285,10 +243,6 @@ async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
|
285
243
|
return;
|
|
286
244
|
}
|
|
287
245
|
// The webhook channel authenticates plaintext events by the platform-generated Verification Token.
|
|
288
|
-
// Try the cheap read first (the v6 detail MAY someday return `encryption`), then the real path: the
|
|
289
|
-
// token's only programmatic delivery is the url_verification challenge during registration — capture
|
|
290
|
-
// it over a throwaway tunnel (bootstrap-token.ts). Failing both is a one-line manual copy; the staged
|
|
291
|
-
// ID/Secret pair makes a re-run resume this App rather than mint another one.
|
|
292
246
|
const tokenVar = "FEISHU_VERIFICATION_TOKEN";
|
|
293
247
|
const api = createFeishuApi({ baseUrl: apiBase, appId, appSecret });
|
|
294
248
|
let token;
|
|
@@ -298,7 +252,7 @@ async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
|
298
252
|
token = cfg.verificationToken;
|
|
299
253
|
}
|
|
300
254
|
catch {
|
|
301
|
-
|
|
255
|
+
// the read surface is best-effort — the bootstrap below is the real path
|
|
302
256
|
}
|
|
303
257
|
if (!token) {
|
|
304
258
|
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)…`);
|
|
@@ -312,14 +266,12 @@ async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
|
312
266
|
console.error(`[fastagent] Verification Token captured`);
|
|
313
267
|
}
|
|
314
268
|
catch (e) {
|
|
315
|
-
// Transient tunnel weather is the usual cause.
|
|
316
|
-
// scan: the staged pair makes the re-run resume THIS app; manual copy completes it too.
|
|
269
|
+
// Transient tunnel weather is the usual cause.
|
|
317
270
|
console.error(`[fastagent] warn: could not capture the Verification Token: ${String(e)} — usually a transient tunnel issue; finish this app with the manual copy below`);
|
|
318
271
|
}
|
|
319
272
|
}
|
|
320
273
|
if (token) {
|
|
321
|
-
// Persist the second credential stage immediately too
|
|
322
|
-
// scaffold finalization happen only after the complete runtime credential set is durable.
|
|
274
|
+
// Persist the second credential stage immediately too.
|
|
323
275
|
const staged = await appendChannelDotEnv(target, "feishu", { [tokenVar]: token }, [tokenVar]);
|
|
324
276
|
console.error(`[fastagent] wrote ${staged.written.join(", ")} to ${env}`);
|
|
325
277
|
}
|
|
@@ -327,8 +279,7 @@ async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
|
327
279
|
console.error(`[fastagent] copy it manually: developer console → Events & Callbacks → Encryption Strategy → Verification Token → ${tokenVar} in ${env}`);
|
|
328
280
|
}
|
|
329
281
|
if (webhookModeChanged) {
|
|
330
|
-
// The bootstrap's PATCH flipped event mode in the DRAFT.
|
|
331
|
-
// publish, which has no API; later dev/deploy runs change only the Request URL immediately.
|
|
282
|
+
// The bootstrap's PATCH flipped event mode in the DRAFT.
|
|
332
283
|
const versionUrl = `${apiBase}/app/${appId}/version`;
|
|
333
284
|
if (groupSetup.publishReady) {
|
|
334
285
|
console.error(`[fastagent] one console click remains: CREATE + PUBLISH a version (self-approved) — the switch to webhook mode takes effect on publish. Opening ${versionUrl}`);
|
|
@@ -339,8 +290,10 @@ async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
|
339
290
|
}
|
|
340
291
|
}
|
|
341
292
|
}
|
|
342
|
-
/**
|
|
343
|
-
*
|
|
293
|
+
/**
|
|
294
|
+
* Active agent `.env` (`.secrets/.env`) values for the requested names — decided by THE .env parser, so this check can
|
|
295
|
+
* never disagree with what `loadDotEnv` reads.
|
|
296
|
+
*/
|
|
344
297
|
async function activeDotEnvValues(dir, names) {
|
|
345
298
|
let content;
|
|
346
299
|
try {
|
package/dist/cli/add-slack.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type GroupBehaviorChoice } from "../scaffold/add-channel.ts";
|
|
2
|
-
/** Interactive single-workspace internal-app creation + installation.
|
|
2
|
+
/** Interactive single-workspace internal-app creation + installation. */
|
|
3
3
|
export declare function onboardSlackInternalApp(input: {
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
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
|
+
*/
|
|
6
8
|
target: string;
|
|
7
9
|
stateRoot: string;
|
|
8
10
|
groupBehavior: GroupBehaviorChoice;
|
package/dist/cli/add-slack.js
CHANGED
|
@@ -3,10 +3,9 @@ import { basename } from "node:path";
|
|
|
3
3
|
import { isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
|
|
4
4
|
import { dotEnvPath, parseEnvContent } from "../env.js";
|
|
5
5
|
import { openExternalUrl } from "../open-url.js";
|
|
6
|
-
import { installProxyFetch } from "../proxy.js";
|
|
7
6
|
import { appendChannelDotEnv } from "../scaffold/add-channel.js";
|
|
8
7
|
import { newSlackOnboardingState, onboardSlackApp } from "../channels/slack/onboard.js";
|
|
9
|
-
import { readSlackOnboardingState, writeSlackOnboardingState } from "../channels/slack/onboarding-state.js";
|
|
8
|
+
import { configTokenExpiry, readSlackOnboardingState, writeSlackOnboardingState, } from "../channels/slack/onboarding-state.js";
|
|
10
9
|
import { startSlackSetupServer } from "../channels/slack/setup-server.js";
|
|
11
10
|
import { startCloudflareTunnel } from "../tunnel.js";
|
|
12
11
|
const CONFIG_TOKEN_URL = "https://api.slack.com/apps";
|
|
@@ -19,9 +18,39 @@ async function promptValue(message, hidden = false, initialValue) {
|
|
|
19
18
|
throw new Error(`${message}: value is required`);
|
|
20
19
|
return value;
|
|
21
20
|
}
|
|
22
|
-
/**
|
|
21
|
+
/** The one place an App Configuration token pair is asked for and validated. */
|
|
22
|
+
async function promptConfigTokens() {
|
|
23
|
+
console.error(`[fastagent] generate an App Configuration Token pair at ${CONFIG_TOKEN_URL}`);
|
|
24
|
+
openExternalUrl(CONFIG_TOKEN_URL);
|
|
25
|
+
const configToken = await promptValue("Slack configuration access token (xoxe.xoxp-…)", true);
|
|
26
|
+
const configRefreshToken = await promptValue("Slack configuration refresh token (xoxe-…)", true);
|
|
27
|
+
if (!configToken.startsWith("xoxe.") || !configRefreshToken.startsWith("xoxe-")) {
|
|
28
|
+
throw new Error("invalid Slack configuration token prefix (expected xoxe. access + xoxe- refresh)");
|
|
29
|
+
}
|
|
30
|
+
return { configToken, configRefreshToken, configTokenExpiresAt: configTokenExpiry() };
|
|
31
|
+
}
|
|
32
|
+
/** Keep the saved token pair, or paste a fresh one? `--replace-config` answers without asking. */
|
|
33
|
+
async function chooseTokenAction(prompt) {
|
|
34
|
+
if (prompt.forced)
|
|
35
|
+
return "replace-config";
|
|
36
|
+
const answer = await select({
|
|
37
|
+
message: prompt.message,
|
|
38
|
+
initialValue: "keep",
|
|
39
|
+
options: [
|
|
40
|
+
{ value: "keep", label: prompt.keepLabel },
|
|
41
|
+
{
|
|
42
|
+
value: "replace-config",
|
|
43
|
+
label: prompt.replaceLabel,
|
|
44
|
+
...(prompt.replaceHint ? { hint: prompt.replaceHint } : {}),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
if (isCancel(answer))
|
|
49
|
+
throw new Error("Slack onboarding cancelled");
|
|
50
|
+
return answer;
|
|
51
|
+
}
|
|
52
|
+
/** Interactive single-workspace internal-app creation + installation. */
|
|
23
53
|
export async function onboardSlackInternalApp(input) {
|
|
24
|
-
installProxyFetch();
|
|
25
54
|
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
26
55
|
throw new Error("`add slack` needs an interactive terminal for internal-app creation and OAuth — " +
|
|
27
56
|
"re-run in a terminal, or pass --no-onboard to scaffold only");
|
|
@@ -50,38 +79,15 @@ export async function onboardSlackInternalApp(input) {
|
|
|
50
79
|
throw new Error(`the onboarded Slack app uses group behavior ${state.groupBehavior}; changing an installed app's ` +
|
|
51
80
|
"OAuth scopes is a migration. Keep the existing choice, or remove the app + Slack onboarding state and create a new app");
|
|
52
81
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
value: "replace-config",
|
|
62
|
-
label: "Replace App Configuration tokens",
|
|
63
|
-
hint: "repair automatic dev/deploy Request URL updates",
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
});
|
|
67
|
-
if (isCancel(answer))
|
|
68
|
-
throw new Error("Slack onboarding cancelled");
|
|
69
|
-
action = answer;
|
|
70
|
-
}
|
|
82
|
+
const action = await chooseTokenAction({
|
|
83
|
+
forced: input.replaceConfig === true,
|
|
84
|
+
message: `Slack app ${state.appId ?? "(unknown)"} is already installed${state.teamName ? ` in ${state.teamName}` : ""}`,
|
|
85
|
+
keepLabel: "Keep the installed app",
|
|
86
|
+
replaceLabel: "Replace App Configuration tokens",
|
|
87
|
+
replaceHint: "repair automatic dev/deploy Request URL updates",
|
|
88
|
+
});
|
|
71
89
|
if (action === "replace-config") {
|
|
72
|
-
|
|
73
|
-
openExternalUrl(CONFIG_TOKEN_URL);
|
|
74
|
-
const configToken = await promptValue("Slack configuration access token (xoxe.xoxp-…)", true);
|
|
75
|
-
const configRefreshToken = await promptValue("Slack configuration refresh token (xoxe-…)", true);
|
|
76
|
-
if (!configToken.startsWith("xoxe.") || !configRefreshToken.startsWith("xoxe-")) {
|
|
77
|
-
throw new Error("invalid Slack configuration token prefix (expected xoxe. access + xoxe- refresh)");
|
|
78
|
-
}
|
|
79
|
-
writeSlackOnboardingState(input.stateRoot, {
|
|
80
|
-
...state,
|
|
81
|
-
configToken,
|
|
82
|
-
configRefreshToken,
|
|
83
|
-
configTokenExpiresAt: Date.now() + 11 * 60 * 60_000,
|
|
84
|
-
});
|
|
90
|
+
writeSlackOnboardingState(input.stateRoot, { ...state, ...(await promptConfigTokens()) });
|
|
85
91
|
console.error("[fastagent] replaced local Slack App Configuration tokens; runtime app credentials are unchanged");
|
|
86
92
|
}
|
|
87
93
|
else {
|
|
@@ -93,20 +99,10 @@ export async function onboardSlackInternalApp(input) {
|
|
|
93
99
|
const appName = await promptValue("Slack app name", false, `FastAgent ${basename(input.target)}`);
|
|
94
100
|
clackLog.info("Slack's configuration refresh token can manage apps owned by your user in this workspace. " +
|
|
95
101
|
"FastAgent stores it only in owner-readable local state; it is never deployed.");
|
|
96
|
-
console.error(`[fastagent] generate an App Configuration Token at ${CONFIG_TOKEN_URL}`);
|
|
97
|
-
openExternalUrl(CONFIG_TOKEN_URL);
|
|
98
|
-
const configToken = await promptValue("Slack configuration access token (xoxe.xoxp-…)", true);
|
|
99
|
-
const configRefreshToken = await promptValue("Slack configuration refresh token (xoxe-…)", true);
|
|
100
|
-
if (!configToken.startsWith("xoxe."))
|
|
101
|
-
throw new Error("Slack configuration access token must start with xoxe.");
|
|
102
|
-
if (!configRefreshToken.startsWith("xoxe-")) {
|
|
103
|
-
throw new Error("Slack configuration refresh token must start with xoxe-");
|
|
104
|
-
}
|
|
105
102
|
state = newSlackOnboardingState({
|
|
106
103
|
appName,
|
|
107
104
|
groupBehavior: input.groupBehavior.behavior,
|
|
108
|
-
|
|
109
|
-
configRefreshToken,
|
|
105
|
+
...(await promptConfigTokens()),
|
|
110
106
|
});
|
|
111
107
|
writeSlackOnboardingState(input.stateRoot, state);
|
|
112
108
|
}
|
|
@@ -118,35 +114,17 @@ export async function onboardSlackInternalApp(input) {
|
|
|
118
114
|
throw new Error(`a prior Slack app creation attempt at ${state.createAttemptedAt} returned no app ID — ` +
|
|
119
115
|
`inspect ${CONFIG_TOKEN_URL}; delete any incomplete app and ${input.stateRoot}/channels/slack/onboarding.json before retrying`);
|
|
120
116
|
}
|
|
121
|
-
// `--replace-config` also covers the created-but-not-installed state, where a revoked token would
|
|
122
|
-
//
|
|
117
|
+
// `--replace-config` also covers the created-but-not-installed state, where a revoked token would otherwise strand
|
|
118
|
+
// the resume (rotation fails and no menu offers replacement).
|
|
123
119
|
if (resumed && (!state.appId || input.replaceConfig)) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
{ value: "keep", label: "Use the saved token pair" },
|
|
131
|
-
{ value: "replace-config", label: "Paste a fresh token pair" },
|
|
132
|
-
],
|
|
133
|
-
});
|
|
134
|
-
if (isCancel(answer))
|
|
135
|
-
throw new Error("Slack onboarding cancelled");
|
|
136
|
-
action = answer;
|
|
137
|
-
}
|
|
120
|
+
const action = await chooseTokenAction({
|
|
121
|
+
forced: input.replaceConfig === true,
|
|
122
|
+
message: "Resume Slack onboarding with which App Configuration tokens?",
|
|
123
|
+
keepLabel: "Use the saved token pair",
|
|
124
|
+
replaceLabel: "Paste a fresh token pair",
|
|
125
|
+
});
|
|
138
126
|
if (action === "replace-config") {
|
|
139
|
-
|
|
140
|
-
openExternalUrl(CONFIG_TOKEN_URL);
|
|
141
|
-
state = {
|
|
142
|
-
...state,
|
|
143
|
-
configToken: await promptValue("Slack configuration access token (xoxe.xoxp-…)", true),
|
|
144
|
-
configRefreshToken: await promptValue("Slack configuration refresh token (xoxe-…)", true),
|
|
145
|
-
configTokenExpiresAt: Date.now() + 11 * 60 * 60_000,
|
|
146
|
-
};
|
|
147
|
-
if (!state.configToken.startsWith("xoxe.") || !state.configRefreshToken.startsWith("xoxe-")) {
|
|
148
|
-
throw new Error("invalid Slack configuration token prefix (expected xoxe. access + xoxe- refresh)");
|
|
149
|
-
}
|
|
127
|
+
state = { ...state, ...(await promptConfigTokens()) };
|
|
150
128
|
writeSlackOnboardingState(input.stateRoot, state);
|
|
151
129
|
}
|
|
152
130
|
}
|
|
@@ -160,9 +138,8 @@ export async function onboardSlackInternalApp(input) {
|
|
|
160
138
|
const redirectUrl = `${tunnel.url}${server.redirectPath}`;
|
|
161
139
|
console.error(`[fastagent] temporary Slack setup tunnel ready → ${tunnel.url}`);
|
|
162
140
|
try {
|
|
163
|
-
// No local readiness probe: Slack challenges requestUrl from ITS network during app creation, and
|
|
164
|
-
//
|
|
165
|
-
// hostname for a minute. onboardSlackApp retries the create while Slack cannot verify it yet.
|
|
141
|
+
// No local readiness probe: Slack challenges requestUrl from ITS network during app creation, and that is the
|
|
142
|
+
// reachability that matters (#421).
|
|
166
143
|
await onboardSlackApp({ stateRoot: input.stateRoot, state, requestUrl, redirectUrl }, {
|
|
167
144
|
note: (message) => clackLog.info(message),
|
|
168
145
|
openUrl: openExternalUrl,
|
package/dist/cli/auth-view.d.ts
CHANGED
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI presenter for the auth-status line `reportAuth` prints (invoke/dev/start). Kept out of cli.ts —
|
|
3
|
-
* which self-executes on import — so the expired-vs-missing DECISION is unit-testable without a real
|
|
4
|
-
* credential round-trip, mirroring models-view.ts.
|
|
5
|
-
*/
|
|
1
|
+
/** CLI presenter for the auth-status line `reportAuth` prints (invoke/dev/start). */
|
|
6
2
|
/** A stored credential as `reportAuth` needs it: just its kind, for the status line. */
|
|
7
3
|
export interface StoredCredentialInfo {
|
|
8
4
|
type: string;
|
|
9
5
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Format the auth status for `spec`'s provider. `source` is {@link probeAuthSource}'s label (an env-var
|
|
12
|
-
* name, "OAuth", a stored-key label) or undefined when nothing currently SATISFIES auth. Undefined has
|
|
13
|
-
* TWO causes probeAuthSource can't distinguish (it swallows the throw): nothing stored, OR a credential
|
|
14
|
-
* that IS stored but couldn't be made usable (an expired/revoked OAuth whose refresh failed). `stored`
|
|
15
|
-
* — a refresh-FREE store read the caller does only in that case — tells them apart, so an expired login
|
|
16
|
-
* reports "expired/unusable → `fastagent login`" instead of a misleading "(none found)" (which would then
|
|
17
|
-
* be contradicted by the actual "OAuth refresh failed"). A malformed/unreadable store also reads as
|
|
18
|
-
* undefined `stored` and lands in "(none found)" — but the store's own read warns about the corrupt file,
|
|
19
|
-
* so the real signal is surfaced there; this line stays about credential PRESENCE.
|
|
20
|
-
*/
|
|
6
|
+
/** Format the auth status for `spec`'s provider. */
|
|
21
7
|
export declare function formatAuthReport(provider: string, authPath: string, source: string | undefined, stored: StoredCredentialInfo | undefined): {
|
|
22
8
|
line: string;
|
|
23
9
|
warn?: string;
|
package/dist/cli/auth-view.js
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* which self-executes on import — so the expired-vs-missing DECISION is unit-testable without a real
|
|
4
|
-
* credential round-trip, mirroring models-view.ts.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Format the auth status for `spec`'s provider. `source` is {@link probeAuthSource}'s label (an env-var
|
|
8
|
-
* name, "OAuth", a stored-key label) or undefined when nothing currently SATISFIES auth. Undefined has
|
|
9
|
-
* TWO causes probeAuthSource can't distinguish (it swallows the throw): nothing stored, OR a credential
|
|
10
|
-
* that IS stored but couldn't be made usable (an expired/revoked OAuth whose refresh failed). `stored`
|
|
11
|
-
* — a refresh-FREE store read the caller does only in that case — tells them apart, so an expired login
|
|
12
|
-
* reports "expired/unusable → `fastagent login`" instead of a misleading "(none found)" (which would then
|
|
13
|
-
* be contradicted by the actual "OAuth refresh failed"). A malformed/unreadable store also reads as
|
|
14
|
-
* undefined `stored` and lands in "(none found)" — but the store's own read warns about the corrupt file,
|
|
15
|
-
* so the real signal is surfaced there; this line stays about credential PRESENCE.
|
|
16
|
-
*/
|
|
1
|
+
/** CLI presenter for the auth-status line `reportAuth` prints (invoke/dev/start). */
|
|
2
|
+
/** Format the auth status for `spec`'s provider. */
|
|
17
3
|
export function formatAuthReport(provider, authPath, source, stored) {
|
|
18
4
|
if (source !== undefined)
|
|
19
5
|
return { line: `auth: ${source} (${provider}) — ${authPath}` };
|