@fastagent-sh/fastagent 0.21.1 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -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 +233 -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 +166 -229
- 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
|
@@ -8,7 +8,11 @@ import { secretEquals } from "../secret.js";
|
|
|
8
8
|
import { createSeenRing } from "../kit/seen.js";
|
|
9
9
|
import { signatureIsFresh } from "../kit/signature.js";
|
|
10
10
|
import { createThreadParticipants } from "../kit/thread-participants.js";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Exit from "effect/Exit";
|
|
13
|
+
import { portJoin } from "../../effect-port.js";
|
|
11
14
|
import { createTaskTracker } from "../kit/tasks.js";
|
|
15
|
+
import { attachmentsDir } from "../kit/attachment-path.js";
|
|
12
16
|
import { ensureStateHome } from "../kit/state.js";
|
|
13
17
|
import { dispatchStop, isStopText } from "../kit/stop-command.js";
|
|
14
18
|
import { codePointPrefix } from "../kit/text.js";
|
|
@@ -16,17 +20,18 @@ import { createTurnRunner } from "../kit/turn-runner.js";
|
|
|
16
20
|
import { createTurnStore } from "../kit/turn-store.js";
|
|
17
21
|
import { discussionBlock } from "../kit/context-buffer.js";
|
|
18
22
|
import { collectSlackBufferedFiles, createSlackContextBuffer } from "./context-buffer.js";
|
|
19
|
-
import {
|
|
23
|
+
import { slackTurnStream } from "./invoke-turn.js";
|
|
20
24
|
import { defaultSlackRoute, isSlackDirectMessage, isSlackGroupMessage, hasSlackMention, hasSlackUserMention, isSlackHumanMessage, mentionsSlackUser, stripSlackMentions, slackBufferText, slackEnvelope, slackFileIds, slackMessageText, slackPlaceKey, slackSenderLabel, slackTeamId, } from "./parse.js";
|
|
21
|
-
import { defaultErrorMessage, settleSlackPreview,
|
|
22
|
-
import { resolveReactionEmojis, startSlackReaction } from "./reaction.js";
|
|
25
|
+
import { defaultErrorMessage, deliverSlackAnswer, settleSlackPreview, slackReply, } from "./preview.js";
|
|
26
|
+
import { completeSlackReaction, resolveReactionEmojis, startSlackReaction } from "./reaction.js";
|
|
23
27
|
import { registerSlackApi } from "./shared-api.js";
|
|
24
28
|
import { createSlackApi } from "./slack-api.js";
|
|
25
29
|
import { createWelcomedUsers } from "./welcomed.js";
|
|
26
30
|
export { defaultSlackRoute, slackEnvelope };
|
|
27
31
|
const MAX_EVENT_BYTES = 1 << 20;
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
32
|
+
/**
|
|
33
|
+
* Slack's own documented window — it re-signs every redelivery with a current timestamp, so a tight one costs nothing.
|
|
34
|
+
*/
|
|
30
35
|
const MAX_SIGNATURE_AGE_S = 5 * 60;
|
|
31
36
|
const QUEUED_PLACEHOLDER = "⏳ Queued — I’ll start once the current task finishes.";
|
|
32
37
|
const DEFERRED_PLACEHOLDER = "⏳ Delayed by a temporary system issue — I’ll retry automatically.";
|
|
@@ -56,12 +61,50 @@ export function verifySlackSignature(signingSecret, timestamp, signature, rawBod
|
|
|
56
61
|
const expected = `v0=${createHmac("sha256", signingSecret).update(`v0:${timestamp}:${rawBody}`).digest("hex")}`;
|
|
57
62
|
return secretEquals(signature, expected);
|
|
58
63
|
}
|
|
59
|
-
/**
|
|
60
|
-
*
|
|
64
|
+
/**
|
|
65
|
+
* The (channel, ts) of the user's triggering message, encoded in the logical turn id `team:channel:ts` (Slack ids
|
|
66
|
+
* never contain a colon, so a 3-part split is exact).
|
|
67
|
+
*/
|
|
61
68
|
function messageRefOf(turnId) {
|
|
62
69
|
const parts = turnId.split(":");
|
|
63
70
|
return parts.length === 3 && parts[1] && parts[2] ? { channelId: parts[1], ts: parts[2] } : undefined;
|
|
64
71
|
}
|
|
72
|
+
/** How long an ingress may wait for `auth.test`: the Events API allows 3s to ACK, so this must fail well inside it. */
|
|
73
|
+
const AUTH_ACK_BUDGET_MS = 2_000;
|
|
74
|
+
function createSlackAuthentication(api, label) {
|
|
75
|
+
let teamId;
|
|
76
|
+
let botUserId;
|
|
77
|
+
let state = "pending";
|
|
78
|
+
const settled = api.authTest().then((identity) => {
|
|
79
|
+
teamId = identity.teamId;
|
|
80
|
+
botUserId = identity.userId;
|
|
81
|
+
state = "ready";
|
|
82
|
+
log.info(`${label} authenticated${identity.teamId ? ` for workspace ${identity.teamId}` : ""}`);
|
|
83
|
+
}, (error) => {
|
|
84
|
+
state = "failed";
|
|
85
|
+
throw new Error(`Slack auth.test failed — fix SLACK_BOT_TOKEN before accepting events: ${String(error)}`, {
|
|
86
|
+
cause: error,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
void settled.catch((error) => log.error(`${label} ${String(error)}`));
|
|
90
|
+
return {
|
|
91
|
+
settled,
|
|
92
|
+
teamId: () => teamId,
|
|
93
|
+
botUserId: () => botUserId,
|
|
94
|
+
ready: () => state !== "pending"
|
|
95
|
+
? settled
|
|
96
|
+
: new Promise((resolve, reject) => {
|
|
97
|
+
const timeout = setTimeout(() => reject(new Error("Slack auth.test did not finish inside the Events API ACK budget")), AUTH_ACK_BUDGET_MS);
|
|
98
|
+
void settled.then(() => {
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
resolve();
|
|
101
|
+
}, (error) => {
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
reject(error);
|
|
104
|
+
});
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
65
108
|
export function slackChannel(options) {
|
|
66
109
|
const { botToken, signingSecret, rendering = "native", aiDisclaimer, welcome = DEFAULT_WELCOME, reactionAck = {}, route, onError, apiBaseUrl = "https://slack.com/api", } = options;
|
|
67
110
|
if (!["native", "classic"].includes(rendering)) {
|
|
@@ -84,46 +127,15 @@ export function slackChannel(options) {
|
|
|
84
127
|
ensureStateHome(stateHome);
|
|
85
128
|
const api = createSlackApi({ botToken, baseUrl: apiBaseUrl });
|
|
86
129
|
registerSlackApi(stateRoot, api); // the send tool delivers through this one (shared-api.ts)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let authenticationState = "pending";
|
|
90
|
-
const authentication = api.authTest().then((identity) => {
|
|
91
|
-
authenticatedTeamId = identity.teamId;
|
|
92
|
-
botUserId = identity.userId;
|
|
93
|
-
authenticationState = "ready";
|
|
94
|
-
log.info(`${label} authenticated${identity.teamId ? ` for workspace ${identity.teamId}` : ""}`);
|
|
95
|
-
}, (error) => {
|
|
96
|
-
authenticationState = "failed";
|
|
97
|
-
throw new Error(`Slack auth.test failed — fix SLACK_BOT_TOKEN before accepting events: ${String(error)}`, {
|
|
98
|
-
cause: error,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
// Construction cannot await Web API IO. Keep the rejection observed; recovered turns and ingress
|
|
102
|
-
// both gate on the same promise/state below and surface the failure without ACKing new work.
|
|
103
|
-
void authentication.catch((error) => log.error(`${label} ${String(error)}`));
|
|
104
|
-
const waitForAuthentication = () => {
|
|
105
|
-
if (authenticationState !== "pending")
|
|
106
|
-
return authentication;
|
|
107
|
-
return new Promise((resolve, reject) => {
|
|
108
|
-
const timeout = setTimeout(() => reject(new Error("Slack auth.test did not finish inside the Events API ACK budget")), 2_000);
|
|
109
|
-
void authentication.then(() => {
|
|
110
|
-
clearTimeout(timeout);
|
|
111
|
-
resolve();
|
|
112
|
-
}, (error) => {
|
|
113
|
-
clearTimeout(timeout);
|
|
114
|
-
reject(error);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
// Side tasks (stop feedback, DM welcomes) run off the ACK path but drain in turnsIdle. Declared
|
|
119
|
-
// ahead of its consumers so no construction-time path can reach it in the temporal dead zone.
|
|
130
|
+
const auth = createSlackAuthentication(api, label);
|
|
131
|
+
// Side tasks (stop feedback, DM welcomes) run off the ACK path but drain in turnsIdle.
|
|
120
132
|
const sideTasks = createTaskTracker(label);
|
|
121
133
|
const seen = createSeenRing(join(stateHome, "seen.json"), label);
|
|
122
134
|
const threadParticipants = createThreadParticipants(join(stateHome, "thread-participants.json"), label);
|
|
123
|
-
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
|
|
135
|
+
/**
|
|
136
|
+
* A thread's participation is keyed by the SESSION it describes — "the agent answered here" is a claim about a
|
|
137
|
+
* memory, so the two must not be re-keyable independently.
|
|
138
|
+
*/
|
|
127
139
|
const threadKey = (teamId, channelId, threadTs) => `slack:${teamId}:${channelId}:${threadTs}`;
|
|
128
140
|
const welcomed = createWelcomedUsers(join(stateHome, "welcomed.json"), label);
|
|
129
141
|
const buffer = createSlackContextBuffer(join(stateHome, "buffers.json"), label);
|
|
@@ -159,7 +171,7 @@ export function slackChannel(options) {
|
|
|
159
171
|
if (nativeDmStatus)
|
|
160
172
|
turn.nativeQueueStatus = true;
|
|
161
173
|
return {
|
|
162
|
-
done:
|
|
174
|
+
done: auth.settled
|
|
163
175
|
.then(() => nativeDmStatus
|
|
164
176
|
? api.setThreadStatus(targetOf(turn), "is queued behind an earlier request…")
|
|
165
177
|
: api.postMessage(targetOf(turn), QUEUED_PLACEHOLDER).then((ts) => {
|
|
@@ -168,12 +180,10 @@ export function slackChannel(options) {
|
|
|
168
180
|
.catch((error) => log.warn(`${label} queue preview failed (the turn stays durable): ${String(error)}`)),
|
|
169
181
|
};
|
|
170
182
|
},
|
|
171
|
-
// Leave the intent untouched when Slack auth failed.
|
|
172
|
-
// running now would execute an Agent turn whose only customer-facing transport is known to be
|
|
173
|
-
// unavailable.
|
|
183
|
+
// Leave the intent untouched when Slack auth failed.
|
|
174
184
|
beforeRun: async (turn) => {
|
|
175
185
|
try {
|
|
176
|
-
await
|
|
186
|
+
await auth.settled;
|
|
177
187
|
return true;
|
|
178
188
|
}
|
|
179
189
|
catch (error) {
|
|
@@ -192,47 +202,67 @@ export function slackChannel(options) {
|
|
|
192
202
|
}
|
|
193
203
|
},
|
|
194
204
|
notifyDropped,
|
|
195
|
-
|
|
205
|
+
deliverAnswer: (turn, answer) => portJoin(async () => {
|
|
206
|
+
// A queue notice THIS process put up outlives the run the answer came from, so it is settled here as it
|
|
207
|
+
// would be by a turn that ran: the status line cleared, the compatibility message taken over.
|
|
208
|
+
if (turn.nativeQueueStatus) {
|
|
209
|
+
await api
|
|
210
|
+
.setThreadStatus(targetOf(turn), "")
|
|
211
|
+
.catch((error) => log.warn(`${label} could not clear a queued Agent status: ${String(error)}`));
|
|
212
|
+
}
|
|
213
|
+
await deliverSlackAnswer(api, targetOf(turn), answer, turn.previewTs);
|
|
214
|
+
// The 👀 on the asker's message was added by the run that produced this answer, and `execute`'s
|
|
215
|
+
// acquire/release — the only thing that turns it into ✅ — is not on this path.
|
|
196
216
|
const messageRef = messageRefOf(turn.id);
|
|
197
|
-
|
|
198
|
-
|
|
217
|
+
if (reactionEmojis && messageRef) {
|
|
218
|
+
await completeSlackReaction({
|
|
199
219
|
api,
|
|
200
220
|
channelId: messageRef.channelId,
|
|
201
221
|
ts: messageRef.ts,
|
|
202
222
|
emojis: reactionEmojis,
|
|
203
223
|
label,
|
|
204
|
-
})
|
|
205
|
-
: undefined;
|
|
206
|
-
try {
|
|
207
|
-
await streamSlackReply(invokeSlackTurn(agent, turn.session, `${discussionBlock(discussion.text)}${turn.baseText}`, { api, channelId: turn.channelId, filesDir: join(stateHome, "files"), label }, {
|
|
208
|
-
primaryFileIds: turn.fileIds,
|
|
209
|
-
buffered: collectSlackBufferedFiles(discussion.consumed, new Set(turn.fileIds)),
|
|
210
|
-
}, onCompleted), api, targetOf(turn), formatError, {
|
|
211
|
-
rendering,
|
|
212
|
-
initialPreviewTs: turn.previewTs,
|
|
213
|
-
threadTitle: turn.threadTitle,
|
|
214
|
-
disclaimer: aiDisclaimer,
|
|
215
|
-
label,
|
|
216
224
|
});
|
|
217
225
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
}),
|
|
227
|
+
execute: (turn, discussion, onAnswered) => {
|
|
228
|
+
const messageRef = messageRefOf(turn.id);
|
|
229
|
+
return Effect.acquireUseRelease(portJoin(async () => reactionEmojis && messageRef
|
|
230
|
+
? startSlackReaction({
|
|
231
|
+
api,
|
|
232
|
+
channelId: messageRef.channelId,
|
|
233
|
+
ts: messageRef.ts,
|
|
234
|
+
emojis: reactionEmojis,
|
|
235
|
+
label,
|
|
236
|
+
})
|
|
237
|
+
: undefined), () => Effect.scoped(slackReply(slackTurnStream(agent, turn.session, `${discussionBlock(discussion.text)}${turn.baseText}`, { api, channelId: turn.channelId, filesDir: attachmentsDir(stateHome), label }, {
|
|
238
|
+
primaryFileIds: turn.fileIds,
|
|
239
|
+
buffered: collectSlackBufferedFiles(discussion.consumed, new Set(turn.fileIds)),
|
|
240
|
+
}), api, targetOf(turn), formatError, {
|
|
241
|
+
rendering,
|
|
242
|
+
initialPreviewTs: turn.previewTs,
|
|
243
|
+
threadTitle: turn.threadTitle,
|
|
244
|
+
disclaimer: aiDisclaimer,
|
|
245
|
+
label,
|
|
246
|
+
onAnswered,
|
|
247
|
+
})), (reaction, exit) => portJoin(async () => {
|
|
248
|
+
if (Exit.isSuccess(exit))
|
|
249
|
+
await reaction?.complete();
|
|
250
|
+
else
|
|
251
|
+
await reaction?.remove();
|
|
252
|
+
}).pipe(Effect.orDie));
|
|
223
253
|
},
|
|
224
254
|
});
|
|
225
255
|
let seq = runner.recover().reduce((maximum, turn) => Math.max(maximum, turn.seq), 0);
|
|
226
|
-
// Acceptance touches no network, so it stays synchronous inside Slack's ACK window and the
|
|
227
|
-
//
|
|
228
|
-
// through. The minutes-long Agent turn remains fire-and-forget.
|
|
256
|
+
// Acceptance touches no network, so it stays synchronous inside Slack's ACK window and the delivery dedup ring
|
|
257
|
+
// alone is enough.
|
|
229
258
|
const acceptEvent = (envelope) => {
|
|
230
259
|
const event = envelope.event;
|
|
231
260
|
if (!isSlackHumanMessage(event))
|
|
232
261
|
return;
|
|
262
|
+
const botUserId = auth.botUserId();
|
|
233
263
|
if (botUserId && event.user === botUserId)
|
|
234
264
|
return;
|
|
235
|
-
const teamId = slackTeamId(envelope) ??
|
|
265
|
+
const teamId = slackTeamId(envelope) ?? auth.teamId();
|
|
236
266
|
if (!teamId) {
|
|
237
267
|
log.warn(`${label} ignored message ${event.ts}: event carried no workspace/enterprise identity`);
|
|
238
268
|
return;
|
|
@@ -246,37 +276,28 @@ export function slackChannel(options) {
|
|
|
246
276
|
const direct = isSlackDirectMessage(event);
|
|
247
277
|
const rootTs = event.thread_ts ?? event.ts;
|
|
248
278
|
const bufferKey = slackPlaceKey(teamId, event);
|
|
249
|
-
// Listening is not speaking: every message the channel can see refines who takes part in its
|
|
250
|
-
//
|
|
251
|
-
// are known — this channel answering.
|
|
252
|
-
// Structural facts only, never `route` — see thread-participants.ts. Slack
|
|
253
|
-
// adds no delta of its own here; its summon rule is the only consumer.
|
|
279
|
+
// Listening is not speaking: every message the channel can see refines who takes part in its thread, whether or
|
|
280
|
+
// not it is answered.
|
|
254
281
|
if (group && event.thread_ts !== undefined) {
|
|
255
282
|
threadParticipants.merge(threadKey(teamId, event.channel, event.thread_ts), { humans: [event.user] });
|
|
256
283
|
}
|
|
257
284
|
let routed = decide(envelope);
|
|
258
|
-
// Two different questions (see parse.ts).
|
|
259
|
-
// is discussion, never an ask" guard (§3) and counts broadcasts and user groups too;
|
|
260
|
-
// `mightBeTheBot` gates the identity-window deferral below, where only a USER mention qualifies.
|
|
285
|
+
// Two different questions (see parse.ts).
|
|
261
286
|
const addressesSomeone = hasSlackMention(event.text ?? "");
|
|
262
287
|
const mightBeTheBot = hasSlackUserMention(event.text ?? "");
|
|
263
288
|
const structurallyMentionsBot = botUserId !== undefined && mentionsSlackUser(event.text ?? "", botUserId);
|
|
264
|
-
// app_mention and message.* subscriptions can overlap.
|
|
265
|
-
// identity routes it now; while auth.test is still unresolved, defer any mentioned message rather
|
|
266
|
-
// than buffer+dedup it and accidentally suppress the later app_mention callback.
|
|
289
|
+
// app_mention and message.* subscriptions can overlap.
|
|
267
290
|
if (!routed && route === undefined && group && event.type === "message" && structurallyMentionsBot)
|
|
268
291
|
routed = {};
|
|
269
|
-
// The participant model's thread rule (§3): a bare message reaches the agent while it takes part
|
|
270
|
-
//
|
|
271
|
-
// structural: it reads who THIS message addresses, which is the one thing an observation-only
|
|
272
|
-
// store cannot know about someone it has never heard.
|
|
292
|
+
// The participant model's thread rule (§3): a bare message reaches the agent while it takes part and has not
|
|
293
|
+
// heard a second human.
|
|
273
294
|
if (!routed &&
|
|
274
295
|
route === undefined &&
|
|
275
296
|
group &&
|
|
276
297
|
event.thread_ts !== undefined &&
|
|
277
298
|
event.type !== "app_mention" &&
|
|
278
|
-
// Mentioning only other people is targeted discussion, never an ask (§3) — the same guard
|
|
279
|
-
//
|
|
299
|
+
// Mentioning only other people is targeted discussion, never an ask (§3) — the same guard Feishu applies with
|
|
300
|
+
// `hasMentions`.
|
|
280
301
|
!addressesSomeone &&
|
|
281
302
|
threadParticipants.admitsBareMessage(threadKey(teamId, event.channel, event.thread_ts))) {
|
|
282
303
|
routed = {};
|
|
@@ -303,16 +324,11 @@ export function slackChannel(options) {
|
|
|
303
324
|
}
|
|
304
325
|
const targetChannel = routed.channelId ?? event.channel;
|
|
305
326
|
const sameChannel = targetChannel === event.channel;
|
|
306
|
-
// Answer where asked (participant model §4).
|
|
307
|
-
// attach an answer to its question is a thread on it — which then IS the place, and carries the
|
|
308
|
-
// memory (§5). Native streaming additionally REQUIRES a thread, but the shape does not depend on
|
|
309
|
-
// the renderer: `classic` attaches its answer the same way.
|
|
327
|
+
// Answer where asked (participant model §4).
|
|
310
328
|
const defaultThread = event.thread_ts ?? event.ts;
|
|
311
329
|
const threadTs = routed.threadTs === null ? undefined : (routed.threadTs ?? (sameChannel ? defaultThread : undefined));
|
|
312
330
|
const defaultSession = threadKey(teamId, event.channel, rootTs);
|
|
313
|
-
// Explicit user stop: a control action, never a turn — it must not queue behind the run it
|
|
314
|
-
// stops. Match the bare word after stripping the bot mention; record the logical id so a Slack
|
|
315
|
-
// redelivery doesn't double-abort or double-notify.
|
|
331
|
+
// Explicit user stop: a control action, never a turn — it must not queue behind the run it stops.
|
|
316
332
|
if (isStopText(stripSlackMentions(event.text ?? ""))) {
|
|
317
333
|
seen.add(logicalId);
|
|
318
334
|
const target = { channelId: event.channel, threadTs: event.thread_ts };
|
|
@@ -341,32 +357,15 @@ export function slackChannel(options) {
|
|
|
341
357
|
threadTitle: threadTitle || undefined,
|
|
342
358
|
fileIds,
|
|
343
359
|
}, true);
|
|
344
|
-
// Answering inside a GROUP thread makes the agent a participant of it, which is what lets the
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
// `group` excludes DMs, whose `threadTs` is always defined (the answer opens its assistant
|
|
348
|
-
// thread) and which no rule could ever read — safe because it is structural: a channel never
|
|
349
|
-
// becomes a DM.
|
|
350
|
-
//
|
|
351
|
-
// The two `routed` conditions keep the record describing what it claims. `session` undefined: the
|
|
352
|
-
// flag asserts "the agent answered into THIS thread's session", so a route supplying its own
|
|
353
|
-
// would record participation in a memory that never held the turn. `threadTs` undefined: a route
|
|
354
|
-
// can send the answer to a DIFFERENT thread, where the asker never spoke — recording them there
|
|
355
|
-
// would invent a participant. Feishu carries the same two conditions for the same reasons.
|
|
356
|
-
//
|
|
357
|
-
// Recorded only once the intent is durable: `submit` can throw, and a redelivery must still see
|
|
358
|
-
// the thread as the agent has actually left it.
|
|
360
|
+
// Answering inside a GROUP thread makes the agent a participant of it, which is what lets the NEXT bare message
|
|
361
|
+
// address it without a mention.
|
|
359
362
|
if (group &&
|
|
360
363
|
threadTs !== undefined &&
|
|
361
364
|
sameChannel &&
|
|
362
365
|
routed.session === undefined &&
|
|
363
366
|
routed.threadTs === undefined) {
|
|
364
|
-
// The ASKER counts as heard in this thread too, and both halves are written together so the
|
|
365
|
-
//
|
|
366
|
-
// level, the answer is what CREATES the thread, so the observation above never ran for it (no
|
|
367
|
-
// `thread_ts` on the ask) — without this, a thread whose root is a human would not count them,
|
|
368
|
-
// and a stranger's first bare reply would read as a two-party exchange. `event.user` is
|
|
369
|
-
// guaranteed here (isSlackHumanMessage). Idempotent when the ask was already in the thread.
|
|
367
|
+
// The ASKER counts as heard in this thread too, and both halves are written together so the record can never
|
|
368
|
+
// say "the agent takes part and nobody has spoken".
|
|
370
369
|
threadParticipants.merge(threadKey(teamId, event.channel, threadTs), {
|
|
371
370
|
agentSpoke: true,
|
|
372
371
|
humans: [event.user],
|
|
@@ -390,14 +389,14 @@ export function slackChannel(options) {
|
|
|
390
389
|
const channelId = event.channel;
|
|
391
390
|
if (!userId || !channelId)
|
|
392
391
|
return;
|
|
393
|
-
const teamId = slackTeamId(envelope) ??
|
|
392
|
+
const teamId = slackTeamId(envelope) ?? auth.teamId();
|
|
394
393
|
if (!teamId)
|
|
395
394
|
return;
|
|
396
395
|
const id = `${teamId}:${userId}`;
|
|
397
396
|
if (welcomed.has(teamId, userId) || welcomeInFlight.has(id))
|
|
398
397
|
return;
|
|
399
|
-
// Reserve in-memory so rapid re-opens don't double-post; persist to the durable set only on a
|
|
400
|
-
//
|
|
398
|
+
// Reserve in-memory so rapid re-opens don't double-post; persist to the durable set only on a successful post,
|
|
399
|
+
// so a failed post simply retries on the next open.
|
|
401
400
|
welcomeInFlight.add(id);
|
|
402
401
|
sideTasks.track(api
|
|
403
402
|
.postMarkdown({ channelId }, body)
|
|
@@ -437,7 +436,7 @@ export function slackChannel(options) {
|
|
|
437
436
|
if (envelope.type !== "event_callback")
|
|
438
437
|
return new Response(null, { status: 200 });
|
|
439
438
|
try {
|
|
440
|
-
await
|
|
439
|
+
await auth.ready();
|
|
441
440
|
}
|
|
442
441
|
catch (error) {
|
|
443
442
|
log.error(`${label} refusing to ACK an event because Slack authentication is unavailable: ${String(error)}`);
|
package/dist/channels/sse.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Shared SSE response lifecycle for invoke and session observation.
|
|
1
|
+
/** Shared SSE response lifecycle for invoke and session observation. */
|
|
2
2
|
/** The remote client's idle watchdog allows three missed heartbeats. */
|
|
3
3
|
export declare const SSE_HEARTBEAT_MS = 30000;
|
|
4
4
|
export declare function sseResponse<T>(events: AsyncIterable<T>, project?: (event: T) => unknown): Response;
|
package/dist/channels/sse.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Shared SSE response lifecycle for invoke and session observation.
|
|
1
|
+
/** Shared SSE response lifecycle for invoke and session observation. */
|
|
2
2
|
/** The remote client's idle watchdog allows three missed heartbeats. */
|
|
3
3
|
export const SSE_HEARTBEAT_MS = 30_000;
|
|
4
4
|
const encoder = new TextEncoder();
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Telegram's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts):
|
|
3
|
-
* the entry shape, its fold-line rendering, and buffered-attachment selection. Bucketed by Telegram
|
|
4
|
-
* "place" (chat[:thread]), not session: an un-summoned message has no route session, and the flush
|
|
5
|
-
* feeds whatever turn answers that place.
|
|
6
|
-
*/
|
|
1
|
+
/** Telegram's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts). */
|
|
7
2
|
import { type ContextBuffer as GenericContextBuffer } from "../kit/context-buffer.ts";
|
|
8
|
-
/** One buffered un-summoned message (object identity is the commit key).
|
|
9
|
-
* one-line body, it carries what a LATER summon needs to resolve references into the discussion:
|
|
10
|
-
* message ids ("reply to the one Alex answered"), and attachment file_ids so "summarize the file from
|
|
11
|
-
* earlier" can actually open it — an un-summoned attachment otherwise surfaces only as its caption or
|
|
12
|
-
* a `[document: …]` label, never the bytes. */
|
|
3
|
+
/** One buffered un-summoned message (object identity is the commit key). */
|
|
13
4
|
export interface BufferEntry {
|
|
14
5
|
sender: string;
|
|
15
6
|
body: string;
|
|
@@ -22,22 +13,16 @@ export interface BufferEntry {
|
|
|
22
13
|
/** file_ids of photos (usable as vision inputs on a later summon). */
|
|
23
14
|
imageIds?: string[];
|
|
24
15
|
}
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
16
|
+
/**
|
|
17
|
+
* A buffered attachment reference: its file_id plus WHO posted it in WHICH message, so the manifest can attribute it
|
|
18
|
+
* ("the file Bob sent") the way the fold attributes text.
|
|
19
|
+
*/
|
|
27
20
|
export interface BufferedRef {
|
|
28
21
|
id: string;
|
|
29
22
|
from: string;
|
|
30
23
|
msg?: number;
|
|
31
24
|
}
|
|
32
|
-
/**
|
|
33
|
-
* The buffered attachment references a summoned turn pulls in with the fold — most recent
|
|
34
|
-
* BUFFER_ATTACH_MAX of each kind, MINUS the summoning message's own ids: replying to a still-buffered
|
|
35
|
-
* attachment puts its file_id in both sets, and without the filter the same file would download twice
|
|
36
|
-
* and appear in the manifest twice (primary wins — it is what the user pointed at this turn).
|
|
37
|
-
* Cap-skipped ones are COUNTED, not silently dropped: a fold line may show a [document: …] label, but
|
|
38
|
-
* a captioned attachment renders as its caption text alone — without a note, the model holds
|
|
39
|
-
* references it silently cannot open and may pretend it read them.
|
|
40
|
-
*/
|
|
25
|
+
/** The buffered attachment references a summoned turn pulls in with the fold. */
|
|
41
26
|
export declare function collectAttachments(consumed: BufferEntry[], primary: {
|
|
42
27
|
files: Set<string>;
|
|
43
28
|
images: Set<string>;
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* the entry shape, its fold-line rendering, and buffered-attachment selection. Bucketed by Telegram
|
|
4
|
-
* "place" (chat[:thread]), not session: an un-summoned message has no route session, and the flush
|
|
5
|
-
* feeds whatever turn answers that place.
|
|
6
|
-
*/
|
|
7
|
-
import { BUFFER_ATTACH_MAX, createContextBuffer as createGenericContextBuffer, } from "../kit/context-buffer.js";
|
|
1
|
+
/** Telegram's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts). */
|
|
2
|
+
import { capBufferedRefs, createContextBuffer as createGenericContextBuffer, } from "../kit/context-buffer.js";
|
|
8
3
|
function bufferLine(e) {
|
|
9
4
|
const meta = [
|
|
10
5
|
e.messageId !== undefined ? `msg ${e.messageId}` : undefined,
|
|
@@ -14,15 +9,7 @@ function bufferLine(e) {
|
|
|
14
9
|
.join(", ");
|
|
15
10
|
return `${e.sender}${meta ? ` (${meta})` : ""}: ${e.body}`;
|
|
16
11
|
}
|
|
17
|
-
/**
|
|
18
|
-
* The buffered attachment references a summoned turn pulls in with the fold — most recent
|
|
19
|
-
* BUFFER_ATTACH_MAX of each kind, MINUS the summoning message's own ids: replying to a still-buffered
|
|
20
|
-
* attachment puts its file_id in both sets, and without the filter the same file would download twice
|
|
21
|
-
* and appear in the manifest twice (primary wins — it is what the user pointed at this turn).
|
|
22
|
-
* Cap-skipped ones are COUNTED, not silently dropped: a fold line may show a [document: …] label, but
|
|
23
|
-
* a captioned attachment renders as its caption text alone — without a note, the model holds
|
|
24
|
-
* references it silently cannot open and may pretend it read them.
|
|
25
|
-
*/
|
|
12
|
+
/** The buffered attachment references a summoned turn pulls in with the fold. */
|
|
26
13
|
export function collectAttachments(consumed, primary) {
|
|
27
14
|
const refs = (pick, exclude) => {
|
|
28
15
|
const seen = new Set();
|
|
@@ -37,13 +24,9 @@ export function collectAttachments(consumed, primary) {
|
|
|
37
24
|
}
|
|
38
25
|
return out;
|
|
39
26
|
};
|
|
40
|
-
const files = refs((e) => e.fileIds, primary.files);
|
|
41
|
-
const images = refs((e) => e.imageIds, primary.images);
|
|
42
|
-
return {
|
|
43
|
-
files: files.slice(-BUFFER_ATTACH_MAX),
|
|
44
|
-
images: images.slice(-BUFFER_ATTACH_MAX),
|
|
45
|
-
skipped: Math.max(0, files.length - BUFFER_ATTACH_MAX) + Math.max(0, images.length - BUFFER_ATTACH_MAX),
|
|
46
|
-
};
|
|
27
|
+
const files = capBufferedRefs(refs((e) => e.fileIds, primary.files));
|
|
28
|
+
const images = capBufferedRefs(refs((e) => e.imageIds, primary.images));
|
|
29
|
+
return { files: files.kept, images: images.kept, skipped: files.skipped + images.skipped };
|
|
47
30
|
}
|
|
48
31
|
function isBufferEntry(e) {
|
|
49
32
|
const t = e;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run one turn (the IO half of Telegram→Agent translation): assemble its inputs — resolve attachments
|
|
3
3
|
* (download files to disk, load vision images) — and stream `agent.invoke` with the assembled prompt.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* wiring and the per-turn lifecycle.
|
|
4
|
+
* Split from parse.ts because this half touches the Bot API + disk; split from telegram.ts so the
|
|
5
|
+
* factory keeps only wiring and the per-turn lifecycle.
|
|
7
6
|
*/
|
|
8
7
|
import type { Agent, AgentEvent } from "../../agent.ts";
|
|
8
|
+
import type * as Stream from "effect/Stream";
|
|
9
|
+
import type { PortFailure } from "../../effect-port.ts";
|
|
9
10
|
import { type BusyRetry } from "../kit/invoke-turn-kit.ts";
|
|
10
11
|
import type { BufferedRef } from "./context-buffer.ts";
|
|
11
12
|
/** Everything the transport needs to fetch a turn's attachments. */
|
|
@@ -29,8 +30,7 @@ export interface TurnAttachments {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
32
|
-
* Run one turn: resolve its attachments, then
|
|
33
|
-
*
|
|
34
|
-
* primary-attachment failure surfaces as a `failed` event (never a silent drop).
|
|
33
|
+
* Run one turn: resolve its attachments, then ask the agent (invoke-turn-kit — a primary-attachment
|
|
34
|
+
* failure surfaces as a `failed` event, never a silent drop).
|
|
35
35
|
*/
|
|
36
|
-
export declare function
|
|
36
|
+
export declare function telegramTurnStream(agent: Agent, session: string, text: string, transport: TurnTransport, attachments: TurnAttachments, busyRetry?: BusyRetry): Stream.Stream<AgentEvent, PortFailure>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DEFAULT_BUSY_RETRY, attachedFilesManifest, attributedFileName, missingAttachmentsNote, streamTurnWithBusyRetry, } from "../kit/invoke-turn-kit.js";
|
|
1
|
+
import { DEFAULT_BUSY_RETRY, attachedFilesManifest, attributedFileName, loadBackground, missingAttachmentsNote, turnStream, } from "../kit/invoke-turn-kit.js";
|
|
3
2
|
import { resolveFiles, resolveImages } from "./telegram-api.js";
|
|
4
3
|
/** Appended to the prompt (not the system prompt): the channel owns Telegram-HTML formatting. */
|
|
5
4
|
const HTML_INSTRUCTION = "\n\n(Format your reply in Telegram-supported HTML — <b> <i> <u> <s> <code> <pre> <a href> — not Markdown.)";
|
|
@@ -19,33 +18,14 @@ async function resolveTurnAttachments(t, attachments) {
|
|
|
19
18
|
const { primary, buffered } = attachments;
|
|
20
19
|
const images = await resolveImages(api, botToken, primary.imageFileIds);
|
|
21
20
|
const files = await resolveFiles(api, botToken, primary.fileIds, chatId, filesDir);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
lost++;
|
|
31
|
-
log.warn(`[telegram] could not load an earlier (buffered) photo: ${String(r.reason)}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const fileResults = await Promise.allSettled(buffered.files.map(async (ref) => ({
|
|
35
|
-
ref,
|
|
36
|
-
files: (await resolveFiles(api, botToken, [ref.id], chatId, filesDir)) ?? [],
|
|
37
|
-
})));
|
|
38
|
-
for (const r of fileResults) {
|
|
39
|
-
if (r.status === "fulfilled") {
|
|
40
|
-
for (const file of r.value.files)
|
|
41
|
-
bufferedFiles.push({ file, ref: r.value.ref });
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
lost++;
|
|
45
|
-
log.warn(`[telegram] could not load an earlier (buffered) attachment: ${String(r.reason)}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const missingNote = missingAttachmentsNote(lost + buffered.skipped);
|
|
21
|
+
const backgroundImages = await loadBackground(buffered.images, (ref) => resolveImages(api, botToken, [ref.id]), {
|
|
22
|
+
label: "[telegram]",
|
|
23
|
+
what: "photo",
|
|
24
|
+
});
|
|
25
|
+
const backgroundFiles = await loadBackground(buffered.files, (ref) => resolveFiles(api, botToken, [ref.id], chatId, filesDir), { label: "[telegram]", what: "attachment" });
|
|
26
|
+
const bufferedImages = backgroundImages.loaded.flatMap(({ value }) => value ?? []);
|
|
27
|
+
const bufferedFiles = backgroundFiles.loaded.flatMap(({ ref, value }) => (value ?? []).map((file) => ({ file, ref })));
|
|
28
|
+
const missingNote = missingAttachmentsNote(backgroundImages.lost + backgroundFiles.lost + buffered.skipped);
|
|
49
29
|
// PRIMARY first, background after — consistent with "primary wins": what the user pointed at this
|
|
50
30
|
// turn leads. Buffered file entries are attributed like the fold's text lines ("the file Bob sent"
|
|
51
31
|
// resolves); buffered PHOTOS cannot be (ImageRef carries no label), so their attribution stops at
|
|
@@ -62,19 +42,20 @@ async function resolveTurnAttachments(t, attachments) {
|
|
|
62
42
|
};
|
|
63
43
|
}
|
|
64
44
|
/**
|
|
65
|
-
* Run one turn: resolve its attachments, then
|
|
66
|
-
*
|
|
67
|
-
* primary-attachment failure surfaces as a `failed` event (never a silent drop).
|
|
45
|
+
* Run one turn: resolve its attachments, then ask the agent (invoke-turn-kit — a primary-attachment
|
|
46
|
+
* failure surfaces as a `failed` event, never a silent drop).
|
|
68
47
|
*/
|
|
69
|
-
export
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
48
|
+
export function telegramTurnStream(agent, session, text, transport, attachments, busyRetry = DEFAULT_BUSY_RETRY) {
|
|
49
|
+
return turnStream({
|
|
50
|
+
agent,
|
|
51
|
+
label: "[telegram]",
|
|
52
|
+
busyRetry,
|
|
53
|
+
resolve: () => resolveTurnAttachments(transport, attachments),
|
|
54
|
+
turn: (resolved) => ({
|
|
55
|
+
scope: { session },
|
|
56
|
+
prompt: { text: `${text}${resolved.promptSuffix}${HTML_INSTRUCTION}`, images: resolved.images },
|
|
57
|
+
}),
|
|
58
|
+
// A Bot API download that failed may simply succeed on the redelivery.
|
|
59
|
+
retryableLoadFailure: () => true,
|
|
60
|
+
});
|
|
80
61
|
}
|