@fastagent-sh/fastagent 0.24.3 → 0.25.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 +53 -82
- package/dist/agent-api.d.ts +452 -0
- package/dist/agent-api.js +201 -0
- package/dist/answer-text.d.ts +8 -0
- package/dist/answer-text.js +18 -0
- package/dist/atomic-write.d.ts +2 -2
- package/dist/atomic-write.js +2 -2
- package/dist/authoring.d.ts +77 -0
- package/dist/authoring.js +238 -0
- package/dist/channel.d.ts +29 -15
- package/dist/channel.js +4 -1
- package/dist/channels/agentcore-protocol.d.ts +36 -50
- package/dist/channels/agentcore-protocol.js +3 -3
- package/dist/channels/agentcore-service.d.ts +8 -18
- package/dist/channels/agentcore-service.js +108 -127
- package/dist/channels/agentcore.d.ts +8 -24
- package/dist/channels/agentcore.js +32 -65
- package/dist/channels/body.d.ts +0 -16
- package/dist/channels/body.js +0 -25
- package/dist/channels/busy.js +6 -1
- package/dist/channels/context.d.ts +4 -0
- package/dist/channels/context.js +35 -0
- package/dist/channels/discover.d.ts +18 -20
- package/dist/channels/discover.js +41 -99
- package/dist/channels/feishu/feishu-api.d.ts +54 -4
- package/dist/channels/feishu/feishu-api.js +192 -149
- package/dist/channels/feishu/feishu.d.ts +3 -0
- package/dist/channels/feishu/feishu.js +117 -106
- package/dist/channels/feishu/history.d.ts +55 -0
- package/dist/channels/feishu/history.js +199 -0
- package/dist/channels/feishu/invoke-turn.d.ts +13 -11
- package/dist/channels/feishu/invoke-turn.js +9 -10
- package/dist/channels/feishu/model.d.ts +2 -1
- package/dist/channels/feishu/normalize.js +100 -0
- package/dist/channels/feishu/preview.d.ts +2 -2
- package/dist/channels/feishu/register-webhook.d.ts +1 -0
- package/dist/channels/feishu/register-webhook.js +3 -2
- package/dist/channels/feishu/scaffold/channel.ts +65 -46
- package/dist/channels/feishu/scaffold/feishu-send.ts +38 -33
- package/dist/channels/feishu/scaffold/feishu-threads.ts +25 -0
- package/dist/channels/feishu/setup-mode.d.ts +45 -25
- package/dist/channels/feishu/setup-mode.js +74 -23
- package/dist/channels/feishu/shared-api.d.ts +24 -6
- package/dist/channels/feishu/shared-api.js +30 -24
- package/dist/channels/kit/context-buffer.d.ts +11 -2
- package/dist/channels/kit/context-buffer.js +7 -4
- package/dist/channels/kit/delivery.d.ts +19 -3
- package/dist/channels/kit/delivery.js +74 -15
- package/dist/channels/kit/event-stream.js +2 -1
- package/dist/channels/kit/invoke-turn-kit.d.ts +41 -41
- package/dist/channels/kit/invoke-turn-kit.js +62 -71
- package/dist/channels/kit/place-history.d.ts +107 -0
- package/dist/channels/kit/place-history.js +182 -0
- package/dist/channels/kit/preview-kit.d.ts +9 -5
- package/dist/channels/kit/preview-kit.js +36 -15
- package/dist/channels/kit/room-threads.d.ts +46 -0
- package/dist/channels/kit/room-threads.js +81 -0
- package/dist/channels/kit/seen.js +1 -1
- package/dist/channels/kit/stop-command.d.ts +6 -3
- package/dist/channels/kit/stop-command.js +20 -18
- package/dist/channels/kit/thread-fork.d.ts +17 -0
- package/dist/channels/kit/thread-fork.js +43 -0
- package/dist/channels/kit/thread-participants.js +1 -1
- package/dist/channels/kit/transport.d.ts +5 -0
- package/dist/channels/kit/transport.js +5 -0
- package/dist/channels/kit/turn-runner.d.ts +10 -9
- package/dist/channels/kit/turn-runner.js +17 -19
- package/dist/channels/kit/turn-store.d.ts +5 -4
- package/dist/channels/kit/turn-store.js +5 -5
- package/dist/channels/lark/lark.d.ts +3 -0
- package/dist/channels/lark/lark.js +5 -0
- package/dist/channels/lark/onboard.d.ts +5 -3
- package/dist/channels/lark/onboard.js +14 -14
- package/dist/channels/lark/scaffold/channel.ts +67 -44
- package/dist/channels/lark/scaffold/lark-send.ts +38 -33
- package/dist/channels/lark/scaffold/lark-threads.ts +25 -0
- package/dist/channels/respond.d.ts +0 -4
- package/dist/channels/respond.js +1 -1
- package/dist/channels/router.d.ts +18 -0
- package/dist/channels/router.js +68 -0
- package/dist/channels/serve.d.ts +5 -70
- package/dist/channels/serve.js +105 -287
- package/dist/channels/slack/history.d.ts +42 -0
- package/dist/channels/slack/history.js +282 -0
- package/dist/channels/slack/invoke-turn.d.ts +12 -5
- package/dist/channels/slack/invoke-turn.js +7 -5
- package/dist/channels/slack/manifest.d.ts +1 -4
- package/dist/channels/slack/manifest.js +22 -10
- package/dist/channels/slack/model.d.ts +2 -1
- package/dist/channels/slack/onboard.d.ts +0 -2
- package/dist/channels/slack/onboard.js +2 -4
- package/dist/channels/slack/onboarding-state.d.ts +0 -2
- package/dist/channels/slack/onboarding-state.js +0 -1
- package/dist/channels/slack/parse.d.ts +0 -5
- package/dist/channels/slack/parse.js +1 -16
- package/dist/channels/slack/preview.d.ts +2 -3
- package/dist/channels/slack/preview.js +78 -52
- package/dist/channels/slack/register-webhook.d.ts +1 -0
- package/dist/channels/slack/register-webhook.js +6 -5
- package/dist/channels/slack/scaffold/channel.ts +23 -24
- package/dist/channels/slack/scaffold/slack-send.ts +50 -43
- package/dist/channels/slack/scaffold/slack-threads.ts +29 -0
- package/dist/channels/slack/setup-server.d.ts +19 -0
- package/dist/channels/slack/setup-server.js +113 -11
- package/dist/channels/slack/shared-api.d.ts +15 -4
- package/dist/channels/slack/shared-api.js +19 -14
- package/dist/channels/slack/slack-api.d.ts +61 -2
- package/dist/channels/slack/slack-api.js +28 -5
- package/dist/channels/slack/slack.js +97 -52
- package/dist/channels/slack/welcomed.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +6 -1
- package/dist/channels/telegram/context-buffer.js +19 -0
- package/dist/channels/telegram/invoke-turn.d.ts +8 -4
- package/dist/channels/telegram/invoke-turn.js +7 -7
- package/dist/channels/telegram/parse.d.ts +7 -2
- package/dist/channels/telegram/parse.js +7 -0
- package/dist/channels/telegram/preview.d.ts +2 -2
- package/dist/channels/telegram/register-webhook.d.ts +1 -0
- package/dist/channels/telegram/register-webhook.js +3 -2
- package/dist/channels/telegram/scaffold/channel.ts +27 -28
- package/dist/channels/telegram/scaffold/telegram-send.ts +61 -108
- package/dist/channels/telegram/shared-api.d.ts +34 -0
- package/dist/channels/telegram/shared-api.js +79 -0
- package/dist/channels/telegram/telegram-api.d.ts +15 -5
- package/dist/channels/telegram/telegram-api.js +10 -6
- package/dist/channels/telegram/telegram.js +27 -21
- package/dist/channels/telegram/turn-store.js +1 -1
- package/dist/channels/{kit/turn-queue.d.ts → turn-queue.d.ts} +2 -2
- package/dist/channels/{kit/turn-queue.js → turn-queue.js} +10 -10
- package/dist/cli/add-feishu.d.ts +42 -12
- package/dist/cli/add-feishu.js +177 -124
- package/dist/cli/add-slack.d.ts +8 -3
- package/dist/cli/add-slack.js +115 -63
- package/dist/cli/auth-view.d.ts +10 -3
- package/dist/cli/auth-view.js +8 -6
- package/dist/cli/box-login.d.ts +17 -7
- package/dist/cli/box-login.js +46 -12
- package/dist/cli/commands/add.d.ts +15 -4
- package/dist/cli/commands/add.js +252 -143
- package/dist/cli/commands/auth.d.ts +6 -0
- package/dist/cli/commands/auth.js +50 -0
- package/dist/cli/commands/content.d.ts +6 -0
- package/dist/cli/commands/content.js +45 -0
- package/dist/cli/commands/deploy/agentcore.js +50 -77
- package/dist/cli/commands/deploy/docker.js +38 -39
- package/dist/cli/commands/deploy/fly.js +21 -18
- package/dist/cli/commands/deploy/host-login.d.ts +26 -0
- package/dist/cli/commands/deploy/host-login.js +90 -0
- package/dist/cli/commands/deploy/railway.js +21 -26
- package/dist/cli/commands/deploy/shared.d.ts +45 -17
- package/dist/cli/commands/deploy/shared.js +34 -15
- package/dist/cli/commands/deploy.d.ts +0 -4
- package/dist/cli/commands/deploy.js +237 -50
- package/dist/cli/commands/destroy.js +8 -6
- package/dist/cli/commands/dev.d.ts +0 -2
- package/dist/cli/commands/dev.js +20 -19
- package/dist/cli/commands/env.d.ts +12 -0
- package/dist/cli/commands/env.js +190 -0
- package/dist/cli/commands/info.js +95 -156
- package/dist/cli/commands/init.d.ts +2 -2
- package/dist/cli/commands/init.js +35 -33
- package/dist/cli/commands/invoke.d.ts +6 -0
- package/dist/cli/commands/invoke.js +131 -16
- package/dist/cli/commands/login.d.ts +31 -2
- package/dist/cli/commands/login.js +413 -64
- package/dist/cli/commands/logout.d.ts +4 -0
- package/dist/cli/commands/logout.js +53 -0
- package/dist/cli/commands/logs.js +8 -6
- package/dist/cli/commands/models.d.ts +2 -1
- package/dist/cli/commands/models.js +39 -37
- package/dist/cli/commands/schedules.d.ts +21 -0
- package/dist/cli/commands/schedules.js +143 -0
- package/dist/cli/commands/start.d.ts +11 -10
- package/dist/cli/commands/start.js +35 -62
- package/dist/cli/content-view.d.ts +9 -0
- package/dist/cli/content-view.js +26 -0
- package/dist/cli/fail.d.ts +9 -12
- package/dist/cli/fail.js +17 -14
- package/dist/cli/invoke-stream.d.ts +12 -3
- package/dist/cli/invoke-stream.js +85 -23
- package/dist/cli/kernel.d.ts +9 -2
- package/dist/cli/kernel.js +15 -6
- package/dist/cli/login-relay.d.ts +3 -2
- package/dist/cli/login-relay.js +18 -6
- package/dist/cli/models-view.d.ts +5 -1
- package/dist/cli/models-view.js +1 -1
- package/dist/cli/person.d.ts +10 -0
- package/dist/cli/person.js +23 -0
- package/dist/cli/program.js +421 -203
- package/dist/cli/serve.d.ts +13 -63
- package/dist/cli/serve.js +31 -121
- package/dist/cli/shared.d.ts +21 -48
- package/dist/cli/shared.js +68 -89
- package/dist/cli/ticket.d.ts +136 -0
- package/dist/cli/ticket.js +476 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +2 -0
- package/dist/content/declare.d.ts +70 -0
- package/dist/content/declare.js +163 -0
- package/dist/content/file.d.ts +32 -0
- package/dist/content/file.js +119 -0
- package/dist/content/git.d.ts +41 -0
- package/dist/content/git.js +217 -0
- package/dist/content/mount.d.ts +2 -0
- package/dist/content/mount.js +15 -0
- package/dist/content/resolve.d.ts +52 -0
- package/dist/content/resolve.js +151 -0
- package/dist/content/source.d.ts +27 -0
- package/dist/content/source.js +53 -0
- package/dist/conversation-sessions.d.ts +26 -0
- package/dist/conversation-sessions.js +99 -0
- package/dist/declared-secrets.d.ts +19 -37
- package/dist/declared-secrets.js +16 -58
- package/dist/deploy/agentcore/destroy.d.ts +3 -3
- package/dist/deploy/agentcore/destroy.js +46 -28
- package/dist/deploy/agentcore/forwarder.js +105 -22
- package/dist/deploy/agentcore/logs.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +10 -24
- package/dist/deploy/agentcore/plan.js +31 -135
- package/dist/deploy/agentcore/run.d.ts +1 -1
- package/dist/deploy/agentcore/run.js +8 -5
- package/dist/deploy/agentcore/shell.d.ts +1 -1
- package/dist/deploy/agentcore/shell.js +5 -5
- package/dist/deploy/api.d.ts +34 -0
- package/dist/deploy/api.js +50 -0
- package/dist/deploy/box-shell.d.ts +6 -0
- package/dist/deploy/box-shell.js +7 -0
- package/dist/deploy/build-context.d.ts +7 -5
- package/dist/deploy/build-context.js +41 -35
- package/dist/deploy/channel-ingress.d.ts +6 -1
- package/dist/deploy/channel-ingress.js +18 -5
- package/dist/deploy/container.d.ts +22 -9
- package/dist/deploy/container.js +96 -55
- package/dist/deploy/docker/plan.d.ts +3 -2
- package/dist/deploy/docker/plan.js +13 -14
- package/dist/deploy/docker/run.d.ts +2 -2
- package/dist/deploy/docker/run.js +8 -4
- package/dist/deploy/fly/plan.d.ts +1 -1
- package/dist/deploy/fly/plan.js +12 -21
- package/dist/deploy/fly/run.d.ts +10 -6
- package/dist/deploy/fly/run.js +11 -6
- package/dist/deploy/hosts.d.ts +2 -1
- package/dist/deploy/hosts.js +2 -1
- package/dist/deploy/preflight.d.ts +21 -23
- package/dist/deploy/preflight.js +222 -201
- package/dist/deploy/railway/plan.d.ts +8 -6
- package/dist/deploy/railway/plan.js +15 -58
- package/dist/deploy/railway/run.d.ts +8 -3
- package/dist/deploy/railway/run.js +12 -8
- package/dist/deploy/residency.d.ts +5 -11
- package/dist/deploy/residency.js +0 -6
- package/dist/deploy/runner.d.ts +1 -1
- package/dist/deploy/runner.js +1 -1
- package/dist/deploy/secrets.d.ts +16 -2
- package/dist/deploy/secrets.js +33 -6
- package/dist/deploy/workspace.d.ts +11 -6
- package/dist/deploy/workspace.js +55 -38
- package/dist/dev-supervisor.d.ts +11 -2
- package/dist/dev-supervisor.js +51 -26
- package/dist/effect-port.d.ts +4 -4
- package/dist/effect-port.js +4 -4
- package/dist/environment/apt.d.ts +4 -0
- package/dist/environment/apt.js +32 -0
- package/dist/environment/declare.d.ts +59 -0
- package/dist/environment/declare.js +210 -0
- package/dist/environment/mise.d.ts +50 -0
- package/dist/environment/mise.js +268 -0
- package/dist/feishu.d.ts +2 -2
- package/dist/feishu.js +2 -2
- package/dist/git.d.ts +13 -0
- package/dist/git.js +55 -0
- package/dist/harnesses/models/catalog.d.ts +20 -0
- package/dist/harnesses/models/catalog.js +254 -0
- package/dist/{engines/pi/auth.d.ts → harnesses/models/credentials.d.ts} +3 -3
- package/dist/{engines/pi/auth.js → harnesses/models/credentials.js} +3 -3
- package/dist/harnesses/models/environment.d.ts +43 -0
- package/dist/harnesses/models/environment.js +82 -0
- package/dist/{engines/pi → harnesses/models}/login.d.ts +57 -26
- package/dist/{engines/pi → harnesses/models}/login.js +187 -79
- package/dist/harnesses/models/models.d.ts +83 -0
- package/dist/harnesses/models/models.js +158 -0
- package/dist/harnesses/pi-durable/agent.d.ts +42 -0
- package/dist/harnesses/pi-durable/agent.js +579 -0
- package/dist/harnesses/pi-durable/defaults.d.ts +28 -0
- package/dist/harnesses/pi-durable/defaults.js +26 -0
- package/dist/harnesses/pi-durable/define.d.ts +77 -0
- package/dist/harnesses/pi-durable/define.js +28 -0
- package/dist/harnesses/pi-durable/entries.d.ts +32 -0
- package/dist/harnesses/pi-durable/entries.js +126 -0
- package/dist/harnesses/pi-durable/events.d.ts +25 -0
- package/dist/harnesses/pi-durable/events.js +238 -0
- package/dist/harnesses/pi-durable/ids.d.ts +16 -0
- package/dist/harnesses/pi-durable/ids.js +55 -0
- package/dist/harnesses/pi-durable/messages.d.ts +41 -0
- package/dist/harnesses/pi-durable/messages.js +116 -0
- package/dist/harnesses/pi-durable/open.d.ts +88 -0
- package/dist/harnesses/pi-durable/open.js +329 -0
- package/dist/harnesses/pi-durable/reads.d.ts +30 -0
- package/dist/harnesses/pi-durable/reads.js +94 -0
- package/dist/harnesses/pi-durable/registry.d.ts +18 -0
- package/dist/harnesses/pi-durable/registry.js +56 -0
- package/dist/harnesses/pi-durable/release.d.ts +13 -0
- package/dist/harnesses/pi-durable/release.js +40 -0
- package/dist/harnesses/pi-durable/serve.d.ts +80 -0
- package/dist/harnesses/pi-durable/serve.js +156 -0
- package/dist/harnesses/pi-durable/sessions.d.ts +27 -0
- package/dist/harnesses/pi-durable/sessions.js +47 -0
- package/dist/harnesses/pi-durable/storage.d.ts +4 -0
- package/dist/harnesses/pi-durable/storage.js +49 -0
- package/dist/harnesses/pi-durable/stream.d.ts +20 -0
- package/dist/harnesses/pi-durable/stream.js +221 -0
- package/dist/harnesses/pi-durable/text.d.ts +30 -0
- package/dist/harnesses/pi-durable/text.js +292 -0
- package/dist/harnesses/pi-durable/wake.d.ts +4 -0
- package/dist/harnesses/pi-durable/wake.js +81 -0
- package/dist/http/client.d.ts +6 -0
- package/dist/http/client.js +256 -0
- package/dist/http/contract.d.ts +541 -0
- package/dist/http/contract.js +179 -0
- package/dist/http/server.d.ts +34 -0
- package/dist/http/server.js +194 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +10 -5
- package/dist/lark.d.ts +2 -2
- package/dist/lark.js +2 -2
- package/dist/locked-file.d.ts +17 -0
- package/dist/{engines/pi/locked-file.js → locked-file.js} +16 -10
- package/dist/once.d.ts +6 -0
- package/dist/once.js +23 -0
- package/dist/paths.d.ts +36 -52
- package/dist/paths.js +66 -182
- package/dist/runtime.d.ts +25 -2
- package/dist/runtime.js +27 -0
- package/dist/scaffold/add-channel.d.ts +14 -27
- package/dist/scaffold/add-channel.js +82 -180
- package/dist/scaffold/init.d.ts +17 -18
- package/dist/scaffold/init.js +87 -99
- package/dist/scaffold/templates/SYSTEM.md +12 -0
- package/dist/scaffold/templates/env.example +4 -10
- package/dist/scaffold/templates/fastagent.config.ts +21 -33
- package/dist/scaffold/templates/gitignore +8 -1
- package/dist/scaffold/templates/skills/writing-great-skills/SKILL.md +1 -2
- package/dist/scaffold/templates.d.ts +9 -4
- package/dist/scaffold/templates.js +7 -5
- package/dist/scaffold/vendor-skill.d.ts +1 -4
- package/dist/scaffold/vendor-skill.js +5 -6
- package/dist/schedule/cron.d.ts +13 -0
- package/dist/schedule/cron.js +52 -0
- package/dist/schedule/eventbridge-cron.d.ts +17 -0
- package/dist/schedule/eventbridge-cron.js +88 -0
- package/dist/schedule/schedule.d.ts +23 -0
- package/dist/schedule/schedule.js +34 -0
- package/dist/schedule/scheduler.d.ts +34 -33
- package/dist/schedule/scheduler.js +251 -137
- package/dist/schedule/state.d.ts +4 -14
- package/dist/schedule/state.js +7 -7
- package/dist/schedule/wake-alarm.d.ts +22 -2
- package/dist/schedule/wake-alarm.js +66 -20
- package/dist/schedule/wakeups.d.ts +16 -15
- package/dist/schedule/wakeups.js +47 -45
- package/dist/service.d.ts +56 -169
- package/dist/service.js +91 -187
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +16 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/{channels/kit/state.js → state-file.js} +6 -6
- package/dist/telegram.d.ts +1 -0
- package/dist/telegram.js +1 -0
- package/dist/tunnel.d.ts +3 -0
- package/dist/tunnel.js +12 -4
- package/dist/value-file.d.ts +21 -0
- package/dist/value-file.js +108 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +14 -1
- package/package.json +18 -28
- package/dist/agent.d.ts +0 -106
- package/dist/agent.js +0 -21
- package/dist/channels/control.d.ts +0 -31
- package/dist/channels/control.js +0 -329
- package/dist/channels/define-channel.d.ts +0 -39
- package/dist/channels/define-channel.js +0 -9
- package/dist/channels/feishu/context-buffer.d.ts +0 -35
- package/dist/channels/feishu/context-buffer.js +0 -65
- package/dist/channels/feishu/scaffold/channel.websocket.ts +0 -31
- package/dist/channels/http.d.ts +0 -23
- package/dist/channels/http.js +0 -57
- package/dist/channels/lark/scaffold/channel.websocket.ts +0 -31
- package/dist/channels/slack/context-buffer.d.ts +0 -23
- package/dist/channels/slack/context-buffer.js +0 -37
- package/dist/channels/sse.d.ts +0 -4
- package/dist/channels/sse.js +0 -66
- package/dist/cli/commands/chat.d.ts +0 -3
- package/dist/cli/commands/chat.js +0 -14
- package/dist/cli/commands/routine-run.d.ts +0 -6
- package/dist/cli/commands/routine-run.js +0 -46
- package/dist/cli/commands/routine.d.ts +0 -25
- package/dist/cli/commands/routine.js +0 -122
- package/dist/cli/commands/tool.d.ts +0 -1
- package/dist/cli/commands/tool.js +0 -57
- package/dist/collect.d.ts +0 -63
- package/dist/collect.js +0 -90
- package/dist/core.d.ts +0 -13
- package/dist/core.js +0 -15
- package/dist/engines/pi/agent-models.d.ts +0 -83
- package/dist/engines/pi/agent-models.js +0 -120
- package/dist/engines/pi/agent-session-factory.d.ts +0 -121
- package/dist/engines/pi/agent-session-factory.js +0 -517
- package/dist/engines/pi/chat.d.ts +0 -3
- package/dist/engines/pi/chat.js +0 -8
- package/dist/engines/pi/config.d.ts +0 -71
- package/dist/engines/pi/config.js +0 -199
- package/dist/engines/pi/create.d.ts +0 -170
- package/dist/engines/pi/create.js +0 -343
- package/dist/engines/pi/definition.d.ts +0 -74
- package/dist/engines/pi/definition.js +0 -236
- package/dist/engines/pi/entry-images.d.ts +0 -14
- package/dist/engines/pi/entry-images.js +0 -23
- package/dist/engines/pi/invoke-session.d.ts +0 -18
- package/dist/engines/pi/invoke-session.js +0 -403
- package/dist/engines/pi/locked-file.d.ts +0 -9
- package/dist/engines/pi/machine.d.ts +0 -53
- package/dist/engines/pi/machine.js +0 -137
- package/dist/engines/pi/models.d.ts +0 -200
- package/dist/engines/pi/models.js +0 -533
- package/dist/engines/pi/open.d.ts +0 -180
- package/dist/engines/pi/open.js +0 -270
- package/dist/engines/pi/openai-account-models.d.ts +0 -35
- package/dist/engines/pi/openai-account-models.js +0 -116
- package/dist/engines/pi/report.d.ts +0 -18
- package/dist/engines/pi/report.js +0 -59
- package/dist/engines/pi/retry-event.d.ts +0 -6
- package/dist/engines/pi/retry-event.js +0 -15
- package/dist/engines/pi/service.d.ts +0 -9
- package/dist/engines/pi/service.js +0 -14
- package/dist/engines/pi/session-builder.d.ts +0 -13
- package/dist/engines/pi/session-builder.js +0 -117
- package/dist/engines/pi/session-control.d.ts +0 -77
- package/dist/engines/pi/session-control.js +0 -908
- package/dist/engines/pi/session-effects.d.ts +0 -14
- package/dist/engines/pi/session-effects.js +0 -30
- package/dist/engines/pi/session-inheritance.d.ts +0 -21
- package/dist/engines/pi/session-inheritance.js +0 -204
- package/dist/engines/pi/session-markers.d.ts +0 -49
- package/dist/engines/pi/session-markers.js +0 -60
- package/dist/engines/pi/session-settings.d.ts +0 -67
- package/dist/engines/pi/session-settings.js +0 -119
- package/dist/engines/pi/session-store.d.ts +0 -69
- package/dist/engines/pi/session-store.js +0 -430
- package/dist/engines/pi/tool-context.d.ts +0 -45
- package/dist/engines/pi/tool-context.js +0 -31
- package/dist/engines/pi/tool.d.ts +0 -99
- package/dist/engines/pi/tool.js +0 -118
- package/dist/engines/pi/turn-kit.d.ts +0 -83
- package/dist/engines/pi/turn-kit.js +0 -275
- package/dist/engines/pi/wake-tool.d.ts +0 -9
- package/dist/engines/pi/wake-tool.js +0 -92
- package/dist/loader.d.ts +0 -41
- package/dist/loader.js +0 -116
- package/dist/node.d.ts +0 -3
- package/dist/node.js +0 -4
- package/dist/observe.d.ts +0 -6
- package/dist/observe.js +0 -47
- package/dist/pi.d.ts +0 -25
- package/dist/pi.js +0 -15
- package/dist/scaffold/templates/APPEND_SYSTEM.md +0 -12
- package/dist/scaffold/templates/tools/fetch-url.ts +0 -25
- package/dist/schedule/discover.d.ts +0 -17
- package/dist/schedule/discover.js +0 -61
- package/dist/schedule/routine.d.ts +0 -43
- package/dist/schedule/routine.js +0 -32
- package/dist/schedule/run.d.ts +0 -55
- package/dist/schedule/run.js +0 -146
- package/dist/secrets-gate.d.ts +0 -32
- package/dist/secrets-gate.js +0 -33
- package/dist/session-remote.d.ts +0 -47
- package/dist/session-remote.js +0 -460
- package/dist/session.d.ts +0 -466
- package/dist/session.js +0 -57
- /package/dist/{channels/kit/state.d.ts → state-file.d.ts} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { answerText } from "../../answer-text.js";
|
|
1
2
|
import * as Effect from "effect/Effect";
|
|
2
3
|
import * as Clock from "effect/Clock";
|
|
3
4
|
import * as Stream from "effect/Stream";
|
|
4
|
-
import { previewPump, renderReply, serialWriter } from "../kit/delivery.js";
|
|
5
|
+
import { previewPump, renderReply, serialWriter, turnEnd, unfinishedTurn } from "../kit/delivery.js";
|
|
5
6
|
import { PortFailure, portJoin } from "../../effect-port.js";
|
|
6
7
|
import { log } from "../../log.js";
|
|
7
8
|
import { RETRY_NOTICE, THINKING_PLACEHOLDER, applyTurnEvent, composeTurnBody, createTurnView, defaultErrorMessage, humanizeToolName, revealedAnswer, summarizeToolArgs, toolLines, } from "../kit/preview-kit.js";
|
|
@@ -45,7 +46,7 @@ function boldText(value) {
|
|
|
45
46
|
/**
|
|
46
47
|
* One trace line: the tool, what it was called on, and — on the failure line — that it failed.
|
|
47
48
|
*
|
|
48
|
-
* Tool OUTPUT never leaves the process, failed or not: the channel would have to guess the
|
|
49
|
+
* Tool OUTPUT never leaves the process, failed or not: the channel would have to guess the harness's
|
|
49
50
|
* result shape to read it, and the agent already explains a failure it recovered from in its answer.
|
|
50
51
|
* The failure line therefore repeats the operation instead of adding one: it states WHICH call failed
|
|
51
52
|
* (six `Bash` calls in a turn are otherwise indistinguishable) without exposing anything the start
|
|
@@ -105,6 +106,8 @@ export async function settleSlackPreview(api, target, previewTs, text) {
|
|
|
105
106
|
}
|
|
106
107
|
await api.postMessage(target, text);
|
|
107
108
|
}
|
|
109
|
+
/** Whether an event carries answer text: a text delta, or the settled message it adds up to. */
|
|
110
|
+
const isAnswerText = (event) => (event.type === "delta" && event.channel === "text") || (event.type === "entry" && event.entry.kind === "assistant");
|
|
108
111
|
function streamClassicSlackReply(events, api, target, formatError, initialPreviewTs, disclaimer, label, onAnswered) {
|
|
109
112
|
return Effect.gen(function* () {
|
|
110
113
|
const clock = yield* Clock.Clock;
|
|
@@ -176,7 +179,7 @@ function streamClassicSlackReply(events, api, target, formatError, initialPrevie
|
|
|
176
179
|
onEvent: (event) => {
|
|
177
180
|
const changed = applyTurnEvent(turn, event, now());
|
|
178
181
|
// A hidden answer must not open a placeholder followed by a rate-limited edit on a fast turn.
|
|
179
|
-
if (changed && (event
|
|
182
|
+
if (changed && (!isAnswerText(event) || revealedAnswer(turn, CLASSIC_UPDATE_INTERVAL_MS, now()) !== ""))
|
|
180
183
|
touch();
|
|
181
184
|
},
|
|
182
185
|
});
|
|
@@ -206,7 +209,9 @@ function streamNativeSlackReply(events, api, target, formatError, initialPreview
|
|
|
206
209
|
yield* Effect.addFinalizer(() => Effect.gen(function* () {
|
|
207
210
|
if (target.channelId.startsWith("D"))
|
|
208
211
|
setStatus("");
|
|
209
|
-
|
|
212
|
+
// A finalizer does not fail: its defect would make the turn's typed failure one too. Unreachable while
|
|
213
|
+
// `setStatus` catches its own failures, which no test can make it stop doing.
|
|
214
|
+
yield* statuses.finish.pipe(Effect.catchTag("PortFailure", (error) => Effect.sync(() => log.warn(`${label} could not finish the Slack Agent status: ${String(error.cause)}`))));
|
|
210
215
|
}));
|
|
211
216
|
let stopAttempted = false;
|
|
212
217
|
const stop = (ts, markdown) => {
|
|
@@ -350,6 +355,32 @@ function streamNativeSlackReply(events, api, target, formatError, initialPreview
|
|
|
350
355
|
]);
|
|
351
356
|
}
|
|
352
357
|
});
|
|
358
|
+
let messageId;
|
|
359
|
+
let streamError;
|
|
360
|
+
const textOf = answerText();
|
|
361
|
+
/** An ending that is a failure: its notice woven into the stream, then the turn fails with it. */
|
|
362
|
+
const failWith = (failure) => Effect.gen(function* () {
|
|
363
|
+
finalized = true;
|
|
364
|
+
yield* Effect.gen(function* () {
|
|
365
|
+
const notice = yield* Effect.try({
|
|
366
|
+
try: () => formatError(failure) ?? "",
|
|
367
|
+
catch: (cause) => new PortFailure(cause),
|
|
368
|
+
});
|
|
369
|
+
if (notice) {
|
|
370
|
+
pendingText += `${fullAnswer.trim() ? "\n\n" : ""}${notice}`;
|
|
371
|
+
fullAnswer += `${fullAnswer.trim() ? "\n\n" : ""}${notice}`;
|
|
372
|
+
}
|
|
373
|
+
yield* settleNative(fullAnswer.trim() || GENERIC_FAILURE);
|
|
374
|
+
}).pipe(Effect.catchTag("PortFailure", (error) => Effect.sync(() => log.error(`${label} failed to deliver the agent-failure stream: ${String(error.cause)}`))));
|
|
375
|
+
return yield* Effect.fail(new PortFailure(new Error(`agent failed: ${failure.details} (retryable=${failure.retryable})`)));
|
|
376
|
+
});
|
|
377
|
+
const appendAnswer = (text) => {
|
|
378
|
+
if (!text)
|
|
379
|
+
return;
|
|
380
|
+
pendingText += text;
|
|
381
|
+
fullAnswer += text;
|
|
382
|
+
scheduleText();
|
|
383
|
+
};
|
|
353
384
|
yield* Stream.runForEachWhile(events, (event) => Effect.gen(function* () {
|
|
354
385
|
if (event.type !== "retrying" && retryStatusShown) {
|
|
355
386
|
// Progress after a retry notice: restore the normal working status so the stale line doesn't
|
|
@@ -357,34 +388,43 @@ function streamNativeSlackReply(events, api, target, formatError, initialPreview
|
|
|
357
388
|
retryStatusShown = false;
|
|
358
389
|
setStatus(WORKING_STATUS);
|
|
359
390
|
}
|
|
360
|
-
if (event.type === "
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
// Slack's native loading status represents private reasoning without exposing it.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
391
|
+
if (event.type === "message")
|
|
392
|
+
messageId = event.message.id;
|
|
393
|
+
if (event.type === "stream_error")
|
|
394
|
+
streamError = event.error.message;
|
|
395
|
+
const end = turnEnd(event, messageId);
|
|
396
|
+
if (end === undefined) {
|
|
397
|
+
// Slack's native loading status represents private reasoning (`thinking` deltas) without exposing it.
|
|
398
|
+
if (event.type === "delta" && event.channel === "text")
|
|
399
|
+
appendAnswer(textOf(event));
|
|
400
|
+
else if (event.type === "retrying") {
|
|
401
|
+
// A summarization retry backoff pauses the stream (~14s worst case). Channels have no per-message
|
|
402
|
+
// status surface in native mode; DMs get the explicit Agent status line, restored on progress.
|
|
403
|
+
if (target.channelId.startsWith("D")) {
|
|
404
|
+
retryStatusShown = true;
|
|
405
|
+
setStatus("hit a temporary problem — retrying…");
|
|
406
|
+
}
|
|
374
407
|
}
|
|
408
|
+
else if (event.type === "tool_started") {
|
|
409
|
+
const trace = nativeToolTrace(event.name, event.args);
|
|
410
|
+
toolTraces.set(event.toolCallId, trace);
|
|
411
|
+
sendToolTrace(nativeToolLine(trace));
|
|
412
|
+
}
|
|
413
|
+
else if (event.type === "entry") {
|
|
414
|
+
if (event.entry.kind === "assistant")
|
|
415
|
+
appendAnswer(textOf(event));
|
|
416
|
+
if (event.entry.kind === "tool_result") {
|
|
417
|
+
const data = event.entry.data;
|
|
418
|
+
const trace = (data.toolCallId && toolTraces.get(data.toolCallId)) || { label: "Tool" };
|
|
419
|
+
if (data.toolCallId)
|
|
420
|
+
toolTraces.delete(data.toolCallId);
|
|
421
|
+
if (data.isError === true)
|
|
422
|
+
sendToolTrace(nativeToolLine(trace, " failed"));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return true;
|
|
375
426
|
}
|
|
376
|
-
|
|
377
|
-
const trace = nativeToolTrace(event.name, event.args);
|
|
378
|
-
toolTraces.set(event.id, trace);
|
|
379
|
-
sendToolTrace(nativeToolLine(trace));
|
|
380
|
-
}
|
|
381
|
-
else if (event.type === "tool_ended") {
|
|
382
|
-
const trace = toolTraces.get(event.id) ?? { label: "Tool" };
|
|
383
|
-
toolTraces.delete(event.id);
|
|
384
|
-
if (event.isError)
|
|
385
|
-
sendToolTrace(nativeToolLine(trace, " failed"));
|
|
386
|
-
}
|
|
387
|
-
else if (event.type === "completed") {
|
|
427
|
+
if ("answered" in end) {
|
|
388
428
|
finalized = true;
|
|
389
429
|
const finalAnswer = withDisclaimer(fullAnswer, disclaimer);
|
|
390
430
|
const footer = finalAnswer.slice(fullAnswer.trim().length);
|
|
@@ -396,29 +436,15 @@ function streamNativeSlackReply(events, api, target, formatError, initialPreview
|
|
|
396
436
|
yield* settleNative(finalAnswer);
|
|
397
437
|
return false;
|
|
398
438
|
}
|
|
399
|
-
|
|
400
|
-
finalized = true;
|
|
401
|
-
yield* Effect.gen(function* () {
|
|
402
|
-
const notice = yield* Effect.try({
|
|
403
|
-
try: () => formatError({
|
|
404
|
-
details: event.details,
|
|
405
|
-
retryable: event.retryable,
|
|
406
|
-
...(event.code !== undefined ? { code: event.code } : {}),
|
|
407
|
-
}) ?? "",
|
|
408
|
-
catch: (cause) => new PortFailure(cause),
|
|
409
|
-
});
|
|
410
|
-
if (notice) {
|
|
411
|
-
pendingText += `${fullAnswer.trim() ? "\n\n" : ""}${notice}`;
|
|
412
|
-
fullAnswer += `${fullAnswer.trim() ? "\n\n" : ""}${notice}`;
|
|
413
|
-
}
|
|
414
|
-
yield* settleNative(fullAnswer.trim() || GENERIC_FAILURE);
|
|
415
|
-
}).pipe(Effect.catchTag("PortFailure", (error) => Effect.sync(() => log.error(`${label} failed to deliver the agent-failure stream: ${String(error.cause)}`))));
|
|
416
|
-
return yield* Effect.fail(new PortFailure(new Error(`agent failed: ${event.details} (retryable=${event.retryable})`)));
|
|
417
|
-
}
|
|
418
|
-
return true;
|
|
439
|
+
return yield* failWith(end.failure);
|
|
419
440
|
}).pipe(Effect.uninterruptible));
|
|
420
|
-
if (
|
|
421
|
-
|
|
441
|
+
if (finalized)
|
|
442
|
+
return;
|
|
443
|
+
const unfinished = unfinishedTurn(streamError);
|
|
444
|
+
if (unfinished === undefined) {
|
|
445
|
+
return yield* Effect.fail(new PortFailure(new Error("stream ended without a terminal event")));
|
|
446
|
+
}
|
|
447
|
+
yield* failWith(unfinished).pipe(Effect.uninterruptible);
|
|
422
448
|
});
|
|
423
449
|
}
|
|
424
450
|
export function slackReply(events, api, target, formatError, options = {}) {
|
|
@@ -6,6 +6,8 @@ import { currentSlackConfigToken, readSlackOnboardingState } from "./onboarding-
|
|
|
6
6
|
export async function registerSlackWebhook(baseUrl, options) {
|
|
7
7
|
const note = options.log ?? ((message) => console.error(message));
|
|
8
8
|
const publicBaseUrl = baseUrl.replace(/\/$/, "");
|
|
9
|
+
const add = `fastagent add slack${options.environment === "production" ? " --env production" : ""}`;
|
|
10
|
+
const repair = `${add} --replace-config`;
|
|
9
11
|
let state;
|
|
10
12
|
try {
|
|
11
13
|
state = readSlackOnboardingState(options.stateRoot);
|
|
@@ -15,9 +17,9 @@ export async function registerSlackWebhook(baseUrl, options) {
|
|
|
15
17
|
return "failed";
|
|
16
18
|
}
|
|
17
19
|
if (!state?.appId || !state.installedAt) {
|
|
18
|
-
note(`[fastagent] slack: no completed local onboarding state on this machine — the config credential lives only where
|
|
20
|
+
note(`[fastagent] slack: no completed local onboarding state on this machine — the config credential lives only where \`${add}\` ran. ` +
|
|
19
21
|
`Set Event Subscriptions → Request URL = ${publicBaseUrl}/slack manually in the Slack console, or re-run this command from the onboarding machine ` +
|
|
20
|
-
`(repair its expired/revoked tokens with \`
|
|
22
|
+
`(repair its expired/revoked tokens with \`${repair}\`)`);
|
|
21
23
|
return "manual";
|
|
22
24
|
}
|
|
23
25
|
const consoleFallback = `set ${publicBaseUrl}/slack as the Event Subscriptions Request URL in the Slack console`;
|
|
@@ -31,14 +33,13 @@ export async function registerSlackWebhook(baseUrl, options) {
|
|
|
31
33
|
}
|
|
32
34
|
catch (error) {
|
|
33
35
|
note(`[fastagent] slack: could not refresh the App Configuration token: ${String(error)} — ` +
|
|
34
|
-
`re-run \`
|
|
36
|
+
`re-run \`${repair}\` to repair it, or ${consoleFallback}`);
|
|
35
37
|
return "failed";
|
|
36
38
|
}
|
|
37
39
|
// Slack verifies the new request_url with a challenge DURING apps.manifest.update, from Slack's own network.
|
|
38
40
|
try {
|
|
39
41
|
await retryWhile(() => updateSlackAppManifest(current.token, current.state.appId, buildSlackManifest({
|
|
40
42
|
name: current.state.appName,
|
|
41
|
-
groupBehavior: current.state.groupBehavior,
|
|
42
43
|
requestUrl: `${publicBaseUrl}/slack`,
|
|
43
44
|
// A manifest update replaces the whole manifest and redirect_urls may not be empty, so a placeholder is
|
|
44
45
|
// declared.
|
|
@@ -60,7 +61,7 @@ export async function registerSlackWebhook(baseUrl, options) {
|
|
|
60
61
|
? `[fastagent] slack: Slack could not verify ${publicBaseUrl}/slack after retries (last error: ${String(error)}) — ` +
|
|
61
62
|
`once the app is up, ${consoleFallback}`
|
|
62
63
|
: `[fastagent] slack: automatic Request URL registration failed: ${String(error)} — ` +
|
|
63
|
-
`re-run \`
|
|
64
|
+
`re-run \`${repair}\` to repair the configuration tokens, or ${consoleFallback}`);
|
|
64
65
|
return "failed";
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -1,33 +1,32 @@
|
|
|
1
|
+
import { needs } from "@fastagent-sh/fastagent";
|
|
1
2
|
import { slackChannel } from "@fastagent-sh/fastagent/slack";
|
|
2
|
-
import { defineChannel } from "@fastagent-sh/fastagent";
|
|
3
3
|
|
|
4
4
|
// Slack HTTP Events API channel. Setup:
|
|
5
5
|
// 1. Create a Slack app at https://api.slack.com/apps and add a bot user.
|
|
6
6
|
// 2. Bot Token Scopes: app_mentions:read, assistant:write, chat:write, im:history,
|
|
7
|
-
// files:read, files:write, channels:history, groups:history,
|
|
8
|
-
//
|
|
7
|
+
// files:read, files:write, reactions:write, channels:history, groups:history, mpim:history,
|
|
8
|
+
// and users:read.
|
|
9
9
|
// 3. Event Subscriptions: app_home_opened, app_context_changed, app_mention, message.im,
|
|
10
|
-
// message.channels, message.groups, and message.mpim.
|
|
10
|
+
// message.channels, message.groups, and message.mpim.
|
|
11
11
|
// Set Request URL to https://<host>/slack.
|
|
12
12
|
// 4. Install the app (reinstall after changing scopes) and leave token rotation OFF: it cannot be
|
|
13
13
|
// turned off again, and this channel takes one long-lived Bot User OAuth Token.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
14
|
+
//
|
|
15
|
+
// `needs` declares the env vars this channel reads: fastagent carries them to a deployed box and
|
|
16
|
+
// refuses to serve while one is unset.
|
|
17
|
+
export default needs(["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], (secrets) =>
|
|
18
|
+
slackChannel({
|
|
19
|
+
botToken: secrets.SLACK_BOT_TOKEN, // OAuth & Permissions → Bot User OAuth Token (xoxb-…)
|
|
20
|
+
signingSecret: secrets.SLACK_SIGNING_SECRET, // Basic Information → App Credentials
|
|
21
|
+
// Slack Agent stream; its inline tool traces show each call's first argument and stay in the
|
|
22
|
+
// delivered message. "classic" settles into the answer alone (and gives up native streaming).
|
|
23
|
+
rendering: "native",
|
|
24
|
+
// Optional per-reply footer, if your policy requires one: aiDisclaimer: "AI-generated; verify important information.",
|
|
25
|
+
// welcome: "Custom first-run DM greeting", // sent once on first DM open; false disables (default: a generic greeting)
|
|
26
|
+
// reactionAck: false, // disable the 👀→✅ ack on the user's message (default on; needs reactions:write)
|
|
27
|
+
// An answer attaches to its question with a thread (Slack has no quote primitive), and that thread is
|
|
28
|
+
// one conversation, with a session of its own: see docs/design/participant-model.md.
|
|
29
|
+
// Dev/personal bot: surface raw errors. Remove this for a customer-facing bot; details remain in logs.
|
|
30
|
+
onError: (failed) => `⚠️ ${failed.details}`,
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
@@ -1,48 +1,55 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
2
|
+
import { defineTool } from "@earendil-works/pi-durable";
|
|
3
|
+
import { needs } from "@fastagent-sh/fastagent";
|
|
2
4
|
import { slackTransport } from "@fastagent-sh/fastagent/slack";
|
|
3
5
|
|
|
4
6
|
// Send a message or upload a local file to Slack. In a CHAT turn the channel delivers the reply
|
|
5
|
-
// itself
|
|
6
|
-
// for file uploads, and for
|
|
7
|
-
// self-scheduled wake-up, whose plain reply is not delivered anywhere. The channelId/threadTs come
|
|
8
|
-
// from the [slack: …] context line in a chat turn; a scheduled
|
|
9
|
-
// prompt must name the target channel id.
|
|
7
|
+
// itself: do NOT call this to answer a normal chat turn (that posts the message twice). This tool is
|
|
8
|
+
// for file uploads, and for messages NO channel is carrying: a schedule's (src/schedules/) or a
|
|
9
|
+
// self-scheduled wake-up's, whose plain reply is not delivered anywhere. The channelId/threadTs come
|
|
10
|
+
// from the [slack: …] context line in a chat turn; a scheduled or woken message has no such line, so
|
|
11
|
+
// its prompt must name the target channel id.
|
|
10
12
|
//
|
|
11
|
-
// Delivery rides the channel's own transport (slackTransport): its token, API base, Markdown
|
|
12
|
-
// and rate-limit handling. With no channel mounted
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
13
|
+
// Delivery rides the channel's own transport (slackTransport): its token, API base, Markdown
|
|
14
|
+
// splitting and rate-limit handling. With no channel mounted, it is built from SLACK_BOT_TOKEN.
|
|
15
|
+
export default needs(["SLACK_BOT_TOKEN"], ({ SLACK_BOT_TOKEN }) =>
|
|
16
|
+
defineTool({
|
|
17
|
+
name: "slack_send",
|
|
18
|
+
description:
|
|
19
|
+
"Upload one local file to Slack (`path`), or send a message (`text`) for a message NO channel is " +
|
|
20
|
+
"carrying: a scheduled or self-scheduled one. In a normal chat turn the channel already " +
|
|
21
|
+
"delivers your reply, so do NOT call this to answer (it would post the message twice). Pass exactly " +
|
|
22
|
+
"one of `text`/`path`. channelId/threadTs come from the [slack: …] context line in a chat turn; a " +
|
|
23
|
+
"scheduled or woken message has no context line, so name the destination in your instruction. A user ID " +
|
|
24
|
+
"(U…) as channelId messages that user's DM; a file upload needs the DM channel ID (D…) the text send reports.",
|
|
25
|
+
parameters: Type.Object({
|
|
26
|
+
channelId: Type.String({ description: "target Slack channel ID, or a user ID (U…) to message that user's DM" }),
|
|
27
|
+
text: Type.Optional(Type.String({ description: "standard Markdown message text" })),
|
|
28
|
+
path: Type.Optional(Type.String({ description: "absolute path of a local file to upload" })),
|
|
29
|
+
title: Type.Optional(Type.String({ description: "file title (file mode only)" })),
|
|
30
|
+
initialComment: Type.Optional(Type.String({ description: "message posted with the file (file mode only)" })),
|
|
31
|
+
threadTs: Type.Optional(Type.String({ description: "thread parent timestamp, if replying in a thread" })),
|
|
32
|
+
}),
|
|
33
|
+
execute: async ({ channelId, text, path, title, initialComment, threadTs }) => {
|
|
34
|
+
if ((text === undefined) === (path === undefined)) throw new Error("pass exactly one of `text` or `path`");
|
|
35
|
+
const slack = slackTransport(SLACK_BOT_TOKEN);
|
|
36
|
+
const target = { channelId, threadTs };
|
|
37
|
+
let said: string;
|
|
38
|
+
if (text !== undefined) {
|
|
39
|
+
if (title !== undefined || initialComment !== undefined) {
|
|
40
|
+
throw new Error("`title`/`initialComment` are file-mode only");
|
|
41
|
+
}
|
|
42
|
+
const sent = await slack.sendMarkdown(target, text);
|
|
43
|
+
// A user-id target reports the DM channel Slack resolved it to: the id a file upload needs.
|
|
44
|
+
said =
|
|
45
|
+
sent.channelId === channelId
|
|
46
|
+
? `sent message to Slack channel ${channelId} (ts ${sent.ts})`
|
|
47
|
+
: `sent message to Slack user ${channelId} in DM channel ${sent.channelId} (ts ${sent.ts})`;
|
|
48
|
+
} else {
|
|
49
|
+
const file = await slack.uploadFile(target, path as string, { title, initialComment });
|
|
50
|
+
said = `uploaded ${file.name} to Slack channel ${channelId} (file ${file.id})`;
|
|
38
51
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
: `sent message to Slack user ${channelId} in DM channel ${sent.channelId} (ts ${sent.ts})`;
|
|
44
|
-
}
|
|
45
|
-
const file = await slack.uploadFile(target, path as string, { title, initialComment });
|
|
46
|
-
return `uploaded ${file.name} to Slack channel ${channelId} (file ${file.id})`;
|
|
47
|
-
},
|
|
48
|
-
});
|
|
52
|
+
return { output: [{ type: "text", text: said }] };
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
2
|
+
import { defineTool } from "@earendil-works/pi-durable";
|
|
3
|
+
import { sessionIdOf } from "@fastagent-sh/fastagent";
|
|
4
|
+
import { slackThreads } from "@fastagent-sh/fastagent/slack";
|
|
5
|
+
|
|
6
|
+
// Reads the threads of the Slack channel this message was asked in, and no other channel's: the
|
|
7
|
+
// channel names where the message runs, so the tool takes no channel id. It works in a channel turn
|
|
8
|
+
// served by `fastagent dev`/`start`.
|
|
9
|
+
export default defineTool({
|
|
10
|
+
name: "slack_threads",
|
|
11
|
+
description:
|
|
12
|
+
"Read the threads of the Slack channel you are answering in. Without `threadId`, list its recent threads, most " +
|
|
13
|
+
"recently active first, each with its id; with a `threadId` from that list, read that thread. Use it when a " +
|
|
14
|
+
"question here concerns something discussed in another thread, such as whether a problem raised there was " +
|
|
15
|
+
"resolved: the answer is often what a person wrote in the thread. Only this channel's threads can be read, and " +
|
|
16
|
+
"only in a channel turn.",
|
|
17
|
+
parameters: Type.Object({
|
|
18
|
+
threadId: Type.Optional(
|
|
19
|
+
Type.String({
|
|
20
|
+
description: "a thread id from the list (its first message's ts, like 1712345678.123456); omit it to list",
|
|
21
|
+
}),
|
|
22
|
+
),
|
|
23
|
+
}),
|
|
24
|
+
execute: async ({ threadId }, api, context) => {
|
|
25
|
+
const threads = slackThreads(await sessionIdOf(api, context));
|
|
26
|
+
const text = threadId === undefined ? await threads.list() : await threads.read(threadId);
|
|
27
|
+
return { output: [{ type: "text", text }] };
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -2,6 +2,25 @@ export interface SlackSetupServer {
|
|
|
2
2
|
port: number;
|
|
3
3
|
requestPath: string;
|
|
4
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>;
|
|
5
24
|
waitForOAuth(timeoutMs?: number): Promise<{
|
|
6
25
|
code?: string;
|
|
7
26
|
state?: string;
|
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
|
+
const escapeHtml = (text) => text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
4
|
+
const page = (body, head = "") => `<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width">${head}` +
|
|
5
|
+
`<title>FastAgent Slack setup</title><body style="font:16px system-ui;max-width:40em;margin:3em auto;padding:0 1em">${body}`;
|
|
6
|
+
function tokensForm(error) {
|
|
7
|
+
return page(`<h1>Slack App Configuration token</h1>` +
|
|
8
|
+
`<ol><li>Open <a href="https://api.slack.com/apps" target=_blank rel=noopener>api.slack.com/apps</a>, then ` +
|
|
9
|
+
`<b>Your App Configuration Tokens</b> → <b>Generate Token</b> for the workspace the agent joins.</li>` +
|
|
10
|
+
`<li>Paste both tokens here.</li></ol>` +
|
|
11
|
+
`<p>FastAgent keeps them on this machine only (owner-readable), uses them to create the app and update its ` +
|
|
12
|
+
`Request URL, and never deploys them.</p>` +
|
|
13
|
+
(error ? `<p style="color:#b00"><b>${escapeHtml(error)}</b></p>` : "") +
|
|
14
|
+
`<form method=post><p><label>Access token<br><input name=access type=password required ` +
|
|
15
|
+
`placeholder="xoxe.xoxp-…" style="width:100%"></label></p><p><label>Refresh token<br><input name=refresh ` +
|
|
16
|
+
`type=password required placeholder="xoxe-…" style="width:100%"></label></p>` +
|
|
17
|
+
`<p><button>Create the Slack app</button></p></form>`);
|
|
18
|
+
}
|
|
3
19
|
/** Temporary onboarding-only responder. */
|
|
4
20
|
export async function startSlackSetupServer() {
|
|
5
21
|
const nonce = randomBytes(24).toString("hex");
|
|
6
22
|
const requestPath = `/slack/setup/${nonce}`;
|
|
7
23
|
const redirectPath = `/slack/oauth/${nonce}`;
|
|
24
|
+
const tokensPath = `/slack/tokens/${nonce}`;
|
|
25
|
+
const nextPath = `/slack/next/${nonce}`;
|
|
26
|
+
let takeTokens;
|
|
27
|
+
let pasted = false;
|
|
28
|
+
let after;
|
|
29
|
+
let lastShown;
|
|
30
|
+
/** The last page a browser waiting after the pair sees, and when it has seen it (bounded by one refresh window). */
|
|
31
|
+
const lastPage = (said, failed) => {
|
|
32
|
+
if (!pasted || after !== undefined)
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
after = { said, failed };
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const timer = setTimeout(resolve, 5_000);
|
|
37
|
+
lastShown = () => {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
resolve();
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
};
|
|
8
43
|
let settleOAuth;
|
|
9
44
|
const oauth = new Promise((resolve) => {
|
|
10
45
|
settleOAuth = resolve;
|
|
@@ -24,21 +59,61 @@ export async function startSlackSetupServer() {
|
|
|
24
59
|
settleOAuth = undefined;
|
|
25
60
|
response.statusCode = 200;
|
|
26
61
|
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
27
|
-
response.end("
|
|
62
|
+
response.end(page("<p>Slack authorization received: FastAgent is finishing the install. You can close this page.</p>"));
|
|
28
63
|
return;
|
|
29
64
|
}
|
|
30
|
-
if (request.method === "
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
65
|
+
if (url.pathname === tokensPath && (request.method === "GET" || request.method === "POST")) {
|
|
66
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
67
|
+
if (takeTokens === undefined) {
|
|
68
|
+
// Taken already, or not asked for: the page is one-time.
|
|
69
|
+
response.statusCode = 404;
|
|
70
|
+
response.end(page("<p>This page is no longer in use.</p>"));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (request.method === "GET") {
|
|
74
|
+
response.end(tokensForm());
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
readBody(request, (body) => {
|
|
78
|
+
if (body === undefined) {
|
|
79
|
+
response.statusCode = 413;
|
|
80
|
+
response.end(page("<p>Too large.</p>"));
|
|
35
81
|
return;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
82
|
+
}
|
|
83
|
+
const form = new URLSearchParams(body);
|
|
84
|
+
try {
|
|
85
|
+
takeTokens?.({ access: (form.get("access") ?? "").trim(), refresh: (form.get("refresh") ?? "").trim() });
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
response.end(tokensForm(error.message));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
response.statusCode = 303;
|
|
92
|
+
response.setHeader("location", nextPath);
|
|
93
|
+
response.end();
|
|
39
94
|
});
|
|
40
|
-
|
|
41
|
-
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (request.method === "GET" && url.pathname === nextPath) {
|
|
98
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
99
|
+
if (after === undefined) {
|
|
100
|
+
response.end(page("<p>Creating the Slack app…</p>", '<meta http-equiv="refresh" content="2">'));
|
|
101
|
+
}
|
|
102
|
+
else if ("url" in after) {
|
|
103
|
+
response.statusCode = 303;
|
|
104
|
+
response.setHeader("location", after.url);
|
|
105
|
+
response.end();
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const style = after.failed ? ' style="color:#b00"' : "";
|
|
109
|
+
response.end(page(`<p${style}><b>${escapeHtml(after.said)}</b></p><p>Return to your agent.</p>`));
|
|
110
|
+
lastShown?.();
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (request.method === "POST" && url.pathname === requestPath) {
|
|
115
|
+
readBody(request, (body) => {
|
|
116
|
+
if (body === undefined) {
|
|
42
117
|
response.statusCode = 413;
|
|
43
118
|
response.end("payload too large\n");
|
|
44
119
|
return;
|
|
@@ -78,6 +153,20 @@ export async function startSlackSetupServer() {
|
|
|
78
153
|
port: address.port,
|
|
79
154
|
requestPath,
|
|
80
155
|
redirectPath,
|
|
156
|
+
tokensPath,
|
|
157
|
+
waitForTokens: (check) => new Promise((resolve) => {
|
|
158
|
+
takeTokens = (pair) => {
|
|
159
|
+
const taken = check(pair);
|
|
160
|
+
takeTokens = undefined;
|
|
161
|
+
pasted = true;
|
|
162
|
+
resolve(taken);
|
|
163
|
+
};
|
|
164
|
+
}),
|
|
165
|
+
next(url) {
|
|
166
|
+
after = { url };
|
|
167
|
+
},
|
|
168
|
+
failed: (message) => lastPage(message, true),
|
|
169
|
+
done: (message) => lastPage(message, false),
|
|
81
170
|
async waitForOAuth(timeoutMs = 10 * 60_000) {
|
|
82
171
|
let timer;
|
|
83
172
|
const timeout = new Promise((_, reject) => {
|
|
@@ -97,3 +186,16 @@ export async function startSlackSetupServer() {
|
|
|
97
186
|
}),
|
|
98
187
|
};
|
|
99
188
|
}
|
|
189
|
+
/** A request's body, or `undefined` past 64 KB: setup traffic is small, and anything larger is not ours. */
|
|
190
|
+
function readBody(request, then) {
|
|
191
|
+
let body = "";
|
|
192
|
+
let tooLarge = false;
|
|
193
|
+
request.on("data", (chunk) => {
|
|
194
|
+
if (tooLarge)
|
|
195
|
+
return;
|
|
196
|
+
body += String(chunk);
|
|
197
|
+
if (Buffer.byteLength(body) > 64 * 1024)
|
|
198
|
+
tooLarge = true;
|
|
199
|
+
});
|
|
200
|
+
request.on("end", () => then(tooLarge ? undefined : body));
|
|
201
|
+
}
|