@fastagent-sh/fastagent 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -12
- package/dist/agent.d.ts +24 -37
- package/dist/agent.js +7 -15
- package/dist/atomic-write.d.ts +13 -17
- package/dist/atomic-write.js +18 -29
- package/dist/bind.d.ts +12 -22
- package/dist/bind.js +18 -27
- package/dist/channel.d.ts +11 -33
- package/dist/channels/agentcore-limits.d.ts +2 -13
- package/dist/channels/agentcore-limits.js +2 -13
- package/dist/channels/agentcore-protocol.d.ts +67 -58
- package/dist/channels/agentcore-protocol.js +23 -15
- package/dist/channels/agentcore-service.d.ts +19 -33
- package/dist/channels/agentcore-service.js +176 -59
- package/dist/channels/agentcore.d.ts +39 -33
- package/dist/channels/agentcore.js +90 -224
- package/dist/channels/body.d.ts +15 -2
- package/dist/channels/body.js +24 -2
- package/dist/channels/busy.d.ts +1 -21
- package/dist/channels/busy.js +2 -39
- package/dist/channels/control.d.ts +17 -62
- package/dist/channels/control.js +74 -182
- package/dist/channels/define-channel.d.ts +39 -0
- package/dist/channels/define-channel.js +9 -0
- package/dist/channels/discover.d.ts +10 -31
- package/dist/channels/discover.js +37 -31
- package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
- package/dist/channels/feishu/bootstrap-token.js +7 -23
- package/dist/channels/feishu/card.d.ts +14 -28
- package/dist/channels/feishu/card.js +16 -30
- package/dist/channels/feishu/cloud.d.ts +1 -5
- package/dist/channels/feishu/context-buffer.d.ts +4 -23
- package/dist/channels/feishu/context-buffer.js +8 -31
- package/dist/channels/feishu/crypto.d.ts +10 -5
- package/dist/channels/feishu/crypto.js +12 -19
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu-api.js +5 -4
- package/dist/channels/feishu/feishu.d.ts +4 -10
- package/dist/channels/feishu/feishu.js +113 -227
- package/dist/channels/feishu/invoke-turn.d.ts +5 -4
- package/dist/channels/feishu/invoke-turn.js +36 -54
- package/dist/channels/feishu/model.d.ts +4 -21
- package/dist/channels/feishu/model.js +1 -8
- package/dist/channels/feishu/normalize.d.ts +2 -10
- package/dist/channels/feishu/normalize.js +7 -42
- package/dist/channels/feishu/parse.d.ts +9 -29
- package/dist/channels/feishu/parse.js +8 -26
- package/dist/channels/feishu/preview.d.ts +12 -14
- package/dist/channels/feishu/preview.js +108 -207
- package/dist/channels/feishu/register-app.d.ts +8 -23
- package/dist/channels/feishu/register-app.js +7 -20
- package/dist/channels/feishu/register-webhook.d.ts +2 -9
- package/dist/channels/feishu/register-webhook.js +9 -33
- package/dist/channels/feishu/scaffold/channel.ts +27 -19
- package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/feishu/setup-mode.d.ts +11 -36
- package/dist/channels/feishu/setup-mode.js +7 -26
- package/dist/channels/feishu/ws-ingress.d.ts +1 -2
- package/dist/channels/feishu/ws-ingress.js +6 -8
- package/dist/channels/http.d.ts +15 -15
- package/dist/channels/http.js +19 -9
- package/dist/channels/kit/attachment-path.d.ts +4 -5
- package/dist/channels/kit/attachment-path.js +10 -34
- package/dist/channels/kit/context-buffer.d.ts +14 -21
- package/dist/channels/kit/context-buffer.js +25 -38
- package/dist/channels/kit/delivery.d.ts +35 -0
- package/dist/channels/kit/delivery.js +129 -0
- package/dist/channels/kit/event-stream.d.ts +5 -0
- package/dist/channels/kit/event-stream.js +29 -0
- package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
- package/dist/channels/kit/invoke-turn-kit.js +89 -32
- package/dist/channels/kit/preview-kit.d.ts +18 -72
- package/dist/channels/kit/preview-kit.js +22 -88
- package/dist/channels/kit/signature.d.ts +3 -9
- package/dist/channels/kit/signature.js +3 -9
- package/dist/channels/kit/state.d.ts +1 -2
- package/dist/channels/kit/state.js +5 -14
- package/dist/channels/kit/stop-command.d.ts +1 -2
- package/dist/channels/kit/stop-command.js +6 -9
- package/dist/channels/kit/tasks.d.ts +1 -1
- package/dist/channels/kit/tasks.js +8 -21
- package/dist/channels/kit/text.d.ts +2 -16
- package/dist/channels/kit/text.js +23 -31
- package/dist/channels/kit/thread-participants.d.ts +4 -17
- package/dist/channels/kit/thread-participants.js +8 -55
- package/dist/channels/kit/transport.d.ts +26 -0
- package/dist/channels/kit/transport.js +20 -0
- package/dist/channels/kit/turn-queue.d.ts +7 -11
- package/dist/channels/kit/turn-queue.js +21 -39
- package/dist/channels/kit/turn-runner.d.ts +32 -18
- package/dist/channels/kit/turn-runner.js +98 -53
- package/dist/channels/kit/turn-store.d.ts +32 -32
- package/dist/channels/kit/turn-store.js +45 -88
- package/dist/channels/lark/lark.d.ts +1 -5
- package/dist/channels/lark/onboard.d.ts +3 -12
- package/dist/channels/lark/onboard.js +3 -13
- package/dist/channels/lark/scaffold/channel.ts +27 -19
- package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/registration.d.ts +9 -35
- package/dist/channels/registration.js +6 -37
- package/dist/channels/secret.js +2 -5
- package/dist/channels/serve.d.ts +47 -44
- package/dist/channels/serve.js +165 -101
- package/dist/channels/slack/config-api.d.ts +4 -27
- package/dist/channels/slack/config-api.js +5 -34
- package/dist/channels/slack/context-buffer.d.ts +2 -3
- package/dist/channels/slack/context-buffer.js +5 -5
- package/dist/channels/slack/invoke-turn.d.ts +3 -1
- package/dist/channels/slack/invoke-turn.js +25 -34
- package/dist/channels/slack/manifest.js +1 -3
- package/dist/channels/slack/model.d.ts +1 -2
- package/dist/channels/slack/model.js +1 -1
- package/dist/channels/slack/onboard.d.ts +4 -3
- package/dist/channels/slack/onboard.js +8 -11
- package/dist/channels/slack/onboarding-state.d.ts +3 -5
- package/dist/channels/slack/onboarding-state.js +9 -6
- package/dist/channels/slack/parse.d.ts +4 -13
- package/dist/channels/slack/parse.js +7 -28
- package/dist/channels/slack/preview.d.ts +12 -2
- package/dist/channels/slack/preview.js +292 -269
- package/dist/channels/slack/reaction.d.ts +11 -3
- package/dist/channels/slack/reaction.js +19 -8
- package/dist/channels/slack/register-webhook.js +4 -7
- package/dist/channels/slack/scaffold/channel.ts +20 -13
- package/dist/channels/slack/scaffold/slack-send.ts +2 -2
- package/dist/channels/slack/setup-server.d.ts +1 -5
- package/dist/channels/slack/setup-server.js +1 -5
- package/dist/channels/slack/shared-api.d.ts +1 -4
- package/dist/channels/slack/shared-api.js +2 -13
- package/dist/channels/slack/slack-api.d.ts +2 -1
- package/dist/channels/slack/slack-api.js +6 -4
- package/dist/channels/slack/slack.d.ts +11 -14
- package/dist/channels/slack/slack.js +121 -122
- package/dist/channels/sse.d.ts +1 -1
- package/dist/channels/sse.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +7 -22
- package/dist/channels/telegram/context-buffer.js +6 -23
- package/dist/channels/telegram/invoke-turn.d.ts +7 -7
- package/dist/channels/telegram/invoke-turn.js +24 -43
- package/dist/channels/telegram/parse.d.ts +24 -51
- package/dist/channels/telegram/parse.js +32 -84
- package/dist/channels/telegram/preview.d.ts +10 -1
- package/dist/channels/telegram/preview.js +79 -100
- package/dist/channels/telegram/register-webhook.js +5 -23
- package/dist/channels/telegram/scaffold/channel.ts +25 -18
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
- package/dist/channels/telegram/telegram-api.d.ts +4 -3
- package/dist/channels/telegram/telegram-api.js +9 -7
- package/dist/channels/telegram/telegram.d.ts +4 -18
- package/dist/channels/telegram/telegram.js +38 -94
- package/dist/channels/telegram/turn-store.d.ts +11 -14
- package/dist/channels/telegram/turn-store.js +8 -6
- package/dist/channels/wait-health.d.ts +4 -6
- package/dist/channels/wait-health.js +10 -14
- package/dist/cli/add-feishu.d.ts +2 -12
- package/dist/cli/add-feishu.js +24 -71
- package/dist/cli/add-slack.d.ts +5 -3
- package/dist/cli/add-slack.js +53 -76
- package/dist/cli/auth-view.d.ts +2 -16
- package/dist/cli/auth-view.js +2 -16
- package/dist/cli/commands/add.js +25 -43
- package/dist/cli/commands/chat.d.ts +0 -1
- package/dist/cli/commands/chat.js +5 -6
- package/dist/cli/commands/deploy/agentcore.js +60 -65
- package/dist/cli/commands/deploy/docker.js +50 -23
- package/dist/cli/commands/deploy/fly.js +23 -61
- package/dist/cli/commands/deploy/railway.js +23 -18
- package/dist/cli/commands/deploy/shared.d.ts +38 -57
- package/dist/cli/commands/deploy/shared.js +40 -59
- package/dist/cli/commands/deploy.d.ts +10 -27
- package/dist/cli/commands/deploy.js +38 -60
- package/dist/cli/commands/destroy.d.ts +4 -0
- package/dist/cli/commands/destroy.js +56 -0
- package/dist/cli/commands/dev.d.ts +2 -1
- package/dist/cli/commands/dev.js +20 -23
- package/dist/cli/commands/info.d.ts +0 -2
- package/dist/cli/commands/info.js +83 -49
- package/dist/cli/commands/init.d.ts +1 -2
- package/dist/cli/commands/init.js +16 -55
- package/dist/cli/commands/invoke.d.ts +0 -1
- package/dist/cli/commands/invoke.js +5 -7
- package/dist/cli/commands/login.d.ts +2 -1
- package/dist/cli/commands/login.js +33 -38
- package/dist/cli/commands/logs.js +3 -4
- package/dist/cli/commands/models.js +1 -5
- package/dist/cli/commands/routine-run.d.ts +6 -0
- package/dist/cli/commands/routine-run.js +47 -0
- package/dist/cli/commands/routine.d.ts +25 -0
- package/dist/cli/commands/routine.js +122 -0
- package/dist/cli/commands/start.d.ts +31 -3
- package/dist/cli/commands/start.js +131 -81
- package/dist/cli/commands/tool.js +26 -12
- package/dist/cli/fail.d.ts +13 -23
- package/dist/cli/fail.js +20 -23
- package/dist/cli/invoke-stream.d.ts +1 -8
- package/dist/cli/kernel.d.ts +6 -38
- package/dist/cli/kernel.js +14 -77
- package/dist/cli/models-view.d.ts +1 -9
- package/dist/cli/models-view.js +11 -22
- package/dist/cli/program.d.ts +5 -6
- package/dist/cli/program.js +120 -157
- package/dist/cli/serve.d.ts +57 -41
- package/dist/cli/serve.js +94 -121
- package/dist/cli/shared.d.ts +15 -61
- package/dist/cli/shared.js +55 -181
- package/dist/cli.js +1 -8
- package/dist/collect.d.ts +6 -0
- package/dist/collect.js +6 -0
- package/dist/core.d.ts +4 -1
- package/dist/core.js +9 -7
- package/dist/declared-secrets.d.ts +55 -0
- package/dist/declared-secrets.js +88 -0
- package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
- package/dist/deploy/agentcore/aws-cli.js +64 -0
- package/dist/deploy/agentcore/destroy.d.ts +51 -0
- package/dist/deploy/agentcore/destroy.js +273 -0
- package/dist/deploy/agentcore/forwarder.js +28 -79
- package/dist/deploy/agentcore/logs.d.ts +2 -18
- package/dist/deploy/agentcore/logs.js +27 -51
- package/dist/deploy/agentcore/plan.d.ts +85 -105
- package/dist/deploy/agentcore/plan.js +152 -314
- package/dist/deploy/agentcore/run.d.ts +28 -28
- package/dist/deploy/agentcore/run.js +227 -306
- package/dist/deploy/agentcore/zip.d.ts +2 -15
- package/dist/deploy/agentcore/zip.js +3 -17
- package/dist/deploy/build-context.d.ts +21 -0
- package/dist/deploy/build-context.js +137 -0
- package/dist/deploy/channel-ingress.d.ts +26 -39
- package/dist/deploy/channel-ingress.js +57 -28
- package/dist/deploy/container.d.ts +20 -34
- package/dist/deploy/container.js +49 -100
- package/dist/deploy/docker/plan.d.ts +12 -11
- package/dist/deploy/docker/plan.js +43 -24
- package/dist/deploy/docker/run.d.ts +22 -25
- package/dist/deploy/docker/run.js +39 -37
- package/dist/deploy/fly/plan.d.ts +15 -44
- package/dist/deploy/fly/plan.js +41 -73
- package/dist/deploy/fly/run.d.ts +16 -51
- package/dist/deploy/fly/run.js +42 -95
- package/dist/deploy/hosts.d.ts +24 -3
- package/dist/deploy/hosts.js +24 -3
- package/dist/deploy/preflight.d.ts +47 -41
- package/dist/deploy/preflight.js +282 -383
- package/dist/deploy/railway/plan.d.ts +16 -42
- package/dist/deploy/railway/plan.js +42 -60
- package/dist/deploy/railway/run.d.ts +30 -68
- package/dist/deploy/railway/run.js +86 -100
- package/dist/deploy/registration-gate.d.ts +6 -13
- package/dist/deploy/residency.d.ts +37 -0
- package/dist/deploy/residency.js +24 -0
- package/dist/deploy/runner.d.ts +10 -9
- package/dist/deploy/runner.js +7 -18
- package/dist/deploy/secrets.d.ts +40 -41
- package/dist/deploy/secrets.js +117 -103
- package/dist/deploy/workspace.d.ts +50 -0
- package/dist/deploy/workspace.js +197 -0
- package/dist/dev-supervisor.d.ts +1 -9
- package/dist/dev-supervisor.js +21 -64
- package/dist/effect-port.d.ts +50 -0
- package/dist/effect-port.js +77 -0
- package/dist/engines/pi/agent-session-factory.d.ts +50 -69
- package/dist/engines/pi/agent-session-factory.js +249 -146
- package/dist/engines/pi/auth.d.ts +15 -11
- package/dist/engines/pi/auth.js +96 -99
- package/dist/engines/pi/chat.d.ts +1 -5
- package/dist/engines/pi/chat.js +2 -14
- package/dist/engines/pi/config.d.ts +53 -51
- package/dist/engines/pi/config.js +83 -92
- package/dist/engines/pi/create.d.ts +60 -75
- package/dist/engines/pi/create.js +108 -141
- package/dist/engines/pi/definition.d.ts +14 -58
- package/dist/engines/pi/definition.js +21 -85
- package/dist/engines/pi/invoke-session.d.ts +5 -25
- package/dist/engines/pi/invoke-session.js +209 -244
- package/dist/engines/pi/login.d.ts +95 -35
- package/dist/engines/pi/login.js +259 -107
- package/dist/engines/pi/machine.d.ts +31 -0
- package/dist/engines/pi/machine.js +106 -0
- package/dist/engines/pi/models.d.ts +70 -89
- package/dist/engines/pi/models.js +188 -81
- package/dist/engines/pi/open.d.ts +65 -53
- package/dist/engines/pi/open.js +129 -69
- package/dist/engines/pi/report.d.ts +3 -12
- package/dist/engines/pi/report.js +2 -16
- package/dist/engines/pi/search-tools.d.ts +1 -9
- package/dist/engines/pi/search-tools.js +26 -57
- package/dist/engines/pi/service.d.ts +2 -16
- package/dist/engines/pi/service.js +2 -16
- package/dist/engines/pi/session-builder.d.ts +5 -9
- package/dist/engines/pi/session-builder.js +44 -82
- package/dist/engines/pi/session-control.js +286 -269
- package/dist/engines/pi/session-effects.d.ts +14 -0
- package/dist/engines/pi/session-effects.js +30 -0
- package/dist/engines/pi/session-inheritance.d.ts +9 -18
- package/dist/engines/pi/session-inheritance.js +103 -160
- package/dist/engines/pi/session-markers.d.ts +31 -30
- package/dist/engines/pi/session-markers.js +26 -25
- package/dist/engines/pi/session-settings.d.ts +6 -22
- package/dist/engines/pi/session-settings.js +10 -21
- package/dist/engines/pi/session-store.d.ts +26 -92
- package/dist/engines/pi/session-store.js +96 -310
- package/dist/engines/pi/tool-context.d.ts +17 -45
- package/dist/engines/pi/tool-context.js +7 -26
- package/dist/engines/pi/tool.d.ts +45 -36
- package/dist/engines/pi/tool.js +43 -57
- package/dist/engines/pi/turn-kit.d.ts +14 -52
- package/dist/engines/pi/turn-kit.js +18 -67
- package/dist/engines/pi/wake-tool.d.ts +2 -10
- package/dist/engines/pi/wake-tool.js +7 -27
- package/dist/env.d.ts +27 -28
- package/dist/env.js +45 -50
- package/dist/index.js +0 -2
- package/dist/lark.d.ts +1 -2
- package/dist/lark.js +1 -2
- package/dist/loader.d.ts +14 -55
- package/dist/loader.js +24 -43
- package/dist/log.d.ts +2 -12
- package/dist/log.js +3 -24
- package/dist/node.d.ts +2 -15
- package/dist/node.js +2 -18
- package/dist/observe.d.ts +2 -8
- package/dist/open-url.d.ts +1 -1
- package/dist/open-url.js +1 -1
- package/dist/paths.d.ts +70 -134
- package/dist/paths.js +165 -232
- package/dist/pi.d.ts +4 -6
- package/dist/pi.js +3 -3
- package/dist/proxy.d.ts +16 -6
- package/dist/proxy.js +52 -16
- package/dist/runtime.d.ts +18 -12
- package/dist/runtime.js +18 -11
- package/dist/scaffold/add-channel.d.ts +14 -31
- package/dist/scaffold/add-channel.js +48 -112
- package/dist/scaffold/init.d.ts +9 -31
- package/dist/scaffold/init.js +45 -138
- package/dist/scaffold/templates/env.example +6 -4
- package/dist/scaffold/templates/fastagent.config.ts +36 -0
- package/dist/scaffold/templates.d.ts +3 -3
- package/dist/scaffold/templates.js +5 -8
- package/dist/scaffold/vendor-skill.d.ts +2 -5
- package/dist/scaffold/vendor-skill.js +19 -29
- package/dist/schedule/cron.d.ts +9 -5
- package/dist/schedule/cron.js +9 -11
- package/dist/schedule/discover.d.ts +12 -12
- package/dist/schedule/discover.js +43 -34
- package/dist/schedule/routine.d.ts +43 -0
- package/dist/schedule/routine.js +32 -0
- package/dist/schedule/run.d.ts +55 -0
- package/dist/schedule/run.js +146 -0
- package/dist/schedule/scheduler.d.ts +44 -55
- package/dist/schedule/scheduler.js +291 -265
- package/dist/schedule/state.d.ts +105 -10
- package/dist/schedule/state.js +293 -19
- package/dist/schedule/wake-alarm.d.ts +10 -23
- package/dist/schedule/wake-alarm.js +91 -121
- package/dist/schedule/wakeups.d.ts +23 -39
- package/dist/schedule/wakeups.js +33 -75
- package/dist/secrets-gate.d.ts +32 -0
- package/dist/secrets-gate.js +33 -0
- package/dist/service.d.ts +137 -98
- package/dist/service.js +250 -252
- package/dist/session-remote.d.ts +33 -44
- package/dist/session-remote.js +203 -192
- package/dist/session.d.ts +153 -228
- package/dist/session.js +33 -55
- package/dist/tunnel.d.ts +8 -36
- package/dist/tunnel.js +24 -65
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +10 -16
- package/dist/channels/agentcore-state.d.ts +0 -77
- package/dist/channels/agentcore-state.js +0 -276
- package/dist/channels/github/github.d.ts +0 -31
- package/dist/channels/github/github.js +0 -89
- package/dist/channels/github/scaffold/channel.ts +0 -25
- package/dist/cli/commands/attach.d.ts +0 -108
- package/dist/cli/commands/attach.js +0 -637
- package/dist/cli/commands/fire.d.ts +0 -7
- package/dist/cli/commands/fire.js +0 -38
- package/dist/cli/commands/schedule.d.ts +0 -12
- package/dist/cli/commands/schedule.js +0 -87
- package/dist/github.d.ts +0 -3
- package/dist/github.js +0 -2
- package/dist/scaffold/templates/fastagent.config.mjs +0 -23
- package/dist/schedule/audit.d.ts +0 -18
- package/dist/schedule/audit.js +0 -57
- package/dist/schedule/schedule.d.ts +0 -35
- package/dist/schedule/schedule.js +0 -23
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
export interface TelegramMessage {
|
|
3
3
|
message_id: number;
|
|
4
4
|
text?: string;
|
|
5
|
-
/** Entities Telegram's server parsed out of `text` (mentions, commands, URLs, code…).
|
|
6
|
-
* UTF-16 code units — exactly JS string indexing, so `text.slice(offset, offset + length)` is the
|
|
7
|
-
* entity's text verbatim. */
|
|
5
|
+
/** Entities Telegram's server parsed out of `text` (mentions, commands, URLs, code…). */
|
|
8
6
|
entities?: {
|
|
9
7
|
type: string;
|
|
10
8
|
offset: number;
|
|
@@ -20,7 +18,7 @@ export interface TelegramMessage {
|
|
|
20
18
|
length: number;
|
|
21
19
|
[k: string]: unknown;
|
|
22
20
|
}[];
|
|
23
|
-
/** Photo sizes, smallest → largest.
|
|
21
|
+
/** Photo sizes, smallest → largest. */
|
|
24
22
|
photo?: {
|
|
25
23
|
file_id: string;
|
|
26
24
|
file_unique_id: string;
|
|
@@ -85,16 +83,20 @@ export interface TelegramMessage {
|
|
|
85
83
|
};
|
|
86
84
|
[k: string]: unknown;
|
|
87
85
|
}
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
86
|
+
/**
|
|
87
|
+
* A Telegram update (the common subset the channel ACTS on — an update kind not listed here is ACKed and dropped
|
|
88
|
+
* before `route` sees it, so listing it would be a false promise; `[k]` keeps the raw payload reachable).
|
|
89
|
+
*/
|
|
91
90
|
export interface TelegramUpdate {
|
|
92
91
|
update_id: number;
|
|
93
92
|
message?: TelegramMessage;
|
|
94
93
|
channel_post?: TelegramMessage;
|
|
95
94
|
[k: string]: unknown;
|
|
96
95
|
}
|
|
97
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* What `route` returns: act with these (every field optional — omitted ones default from the message), or null to
|
|
98
|
+
* ignore.
|
|
99
|
+
*/
|
|
98
100
|
export interface TelegramRoute {
|
|
99
101
|
/** Conversation identity (default: `chat` or `chat:thread`). */
|
|
100
102
|
session?: string;
|
|
@@ -105,68 +107,39 @@ export interface TelegramRoute {
|
|
|
105
107
|
/** Base prompt (default: {@link telegramEnvelope}); the channel still appends attachments + the HTML hint. */
|
|
106
108
|
text?: string;
|
|
107
109
|
}
|
|
108
|
-
/** The actionable message in an update (a fresh message or channel post).
|
|
109
|
-
* `edited_channel_post`) are deliberately NOT actionable: answering them re-answers every typo fix (a
|
|
110
|
-
* duplicate reply per edit), so an edited message changes nothing — the standard bot behavior. The
|
|
111
|
-
* trade-off: editing a mention INTO an old message does not summon either; send a new message. */
|
|
110
|
+
/** The actionable message in an update (a fresh message or channel post). */
|
|
112
111
|
export declare function pickMessage(update: TelegramUpdate): TelegramMessage | undefined;
|
|
113
112
|
/** file_ids to send the model as vision images: this message's largest photo + a replied-to photo. */
|
|
114
113
|
export declare function extractImages(m: TelegramMessage): string[];
|
|
115
|
-
/** The message's OWN photo (largest size), without the replied-to message's
|
|
116
|
-
* this: each message is its own entry there, so counting the replied-to attachment again would
|
|
117
|
-
* duplicate it and squeeze the attachment cap (the reply relation is expressed by `replyTo` instead). */
|
|
114
|
+
/** The message's OWN photo (largest size), without the replied-to message's. */
|
|
118
115
|
export declare function ownImages(m: TelegramMessage): string[];
|
|
119
|
-
/** file_ids to download to disk
|
|
120
|
-
* (so "summarize this file" works when the user replies to a document with the mention). */
|
|
116
|
+
/** file_ids to download to disk: this message's files plus a replied-to message's ("summarize this file"). */
|
|
121
117
|
export declare function extractFiles(m: TelegramMessage): string[];
|
|
122
|
-
/** The message's OWN files, without the replied-to message's
|
|
118
|
+
/** The message's OWN files, without the replied-to message's. */
|
|
123
119
|
export declare function ownFiles(m: TelegramMessage): string[];
|
|
124
|
-
/** A stable sender label for attribution.
|
|
125
|
-
* who across turns; a username-less user still gets a name + id rather than vanishing. */
|
|
120
|
+
/** A stable sender label for attribution. */
|
|
126
121
|
export declare function fromLabel(from: TelegramMessage["from"]): string | undefined;
|
|
127
|
-
/**
|
|
128
|
-
*
|
|
122
|
+
/**
|
|
123
|
+
* A one-line description of a message's attachment, so the envelope names what was sent even before the agent opens it
|
|
124
|
+
* (and so a media-only message isn't blank).
|
|
125
|
+
*/
|
|
129
126
|
export declare function attachmentSummary(m: TelegramMessage): string | undefined;
|
|
130
127
|
/** A one-line, length-capped rendering of a message's content for the context buffer. */
|
|
131
128
|
export declare function messageText(m: TelegramMessage): string;
|
|
132
129
|
/**
|
|
133
|
-
* The default base prompt: a context envelope (chat/thread/sender + a group note + reply) then the
|
|
134
|
-
*
|
|
135
|
-
* sender is named on every message and a group chat is flagged — in a shared multi-user session that is
|
|
136
|
-
* how the model tells participants apart and knows it is not a 1:1. The reply
|
|
137
|
-
* block carries the replied-to sender, message id, and text/caption or an attachment summary (and the
|
|
138
|
-
* channel downloads a replied-to file/photo too). Exported so a custom `route` can reuse it, e.g.
|
|
139
|
-
* `text: `${telegramEnvelope(m)}\n\n[extra]``. The channel still appends downloaded attachments.
|
|
130
|
+
* The default base prompt: a context envelope (chat/thread/sender + a group note + reply) then the user's text/caption
|
|
131
|
+
* and a compact rendering of structured payloads (location/contact/poll).
|
|
140
132
|
*/
|
|
141
133
|
export declare function telegramEnvelope(m: TelegramMessage): string;
|
|
142
134
|
/**
|
|
143
|
-
* Whether the update is a `/stop` ADDRESSED TO THIS BOT — the one question the channel asks about a
|
|
144
|
-
*
|
|
145
|
-
* equivalent: a bare `/stop` in a private chat (no one else is there), `/stop@thisbot`, an `@thisbot`
|
|
146
|
-
* mention beside the command, and a bare `/stop` replying to one of this bot's messages.
|
|
147
|
-
*
|
|
148
|
-
* A BARE `/stop` in a group is deliberately NOT one of them. Telegram hands it to every bot in the
|
|
149
|
-
* chat — and only promises to deliver it at all when this bot spoke last — so it names no one: acting
|
|
150
|
-
* on it lets a bystander's ask for a different bot abort this bot's run, and answering it lets that
|
|
151
|
-
* bystander make this bot talk. It stays ordinary discussion, which is what the group sees anyway.
|
|
152
|
-
*
|
|
153
|
-
* Takes the UPDATE, like `route` itself, so a custom route can ask it directly — that route is the
|
|
154
|
-
* gate a stop must pass, and one that refuses this update silences the command.
|
|
135
|
+
* Whether the update is a `/stop` ADDRESSED TO THIS BOT — the one question the channel asks about a stop, because an
|
|
136
|
+
* unaddressed one is not a command it may act on.
|
|
155
137
|
*/
|
|
156
138
|
export declare function telegramStop(update: TelegramUpdate, options?: {
|
|
157
139
|
botUsername?: string;
|
|
158
140
|
botId?: number;
|
|
159
141
|
}): boolean;
|
|
160
|
-
/**
|
|
161
|
-
* The default routing policy (used when `route` is omitted; exported so a custom route can reuse it):
|
|
162
|
-
* answer private chats always; a group only on a reply to THIS bot (by `botId`) or a `mention` entity
|
|
163
|
-
* naming it (when `botUsername` is supplied — telegramChannel parses the id from the token and resolves
|
|
164
|
-
* the username via getMe). A bare or directed slash command does NOT summon in a group (that was noisy;
|
|
165
|
-
* a bot author who wants commands adds a custom route) — except a `/stop` ADDRESSED to this bot, which
|
|
166
|
-
* must reach the run it stops rather than sit in the buffer behind it (see {@link telegramStop}; a
|
|
167
|
-
* bare group `/stop` addresses no one and stays ordinary discussion). Returns `{}` (act; the channel fills
|
|
168
|
-
* session/target/prompt from the message) or `null` (ignore).
|
|
169
|
-
*/
|
|
142
|
+
/** The default routing policy (used when `route` is omitted; exported so a custom route can reuse it). */
|
|
170
143
|
export declare function defaultTelegramRoute(update: TelegramUpdate, options?: {
|
|
171
144
|
botUsername?: string;
|
|
172
145
|
botId?: number;
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Telegram protocol parsing — PURE: message field extraction, the prompt envelope, and the summon/route
|
|
3
|
-
* policy. The defining invariant is purity: no state, no IO, no Bot API calls — plain data-in → data-out.
|
|
4
|
-
* In telegram.ts's pipeline (verify → decide via `route` → run the turn → stream reply), this is the
|
|
5
|
-
* "decide" and prompt-building half; telegram.ts wires it in and owns the stateful lifecycle. Kept
|
|
6
|
-
* separate so this layer tests as plain functions and reads without the factory's noise.
|
|
7
|
-
*/
|
|
1
|
+
/** Telegram protocol parsing — PURE: message field extraction, the prompt envelope, and the summon/route policy. */
|
|
8
2
|
import { BUFFER_LINE_MAX_CHARS } from "../kit/context-buffer.js";
|
|
9
3
|
import { REFERENT_MAX_CODE_POINTS, truncateCodePointPrefix } from "../kit/text.js";
|
|
10
|
-
/** The actionable message in an update (a fresh message or channel post).
|
|
11
|
-
* `edited_channel_post`) are deliberately NOT actionable: answering them re-answers every typo fix (a
|
|
12
|
-
* duplicate reply per edit), so an edited message changes nothing — the standard bot behavior. The
|
|
13
|
-
* trade-off: editing a mention INTO an old message does not summon either; send a new message. */
|
|
4
|
+
/** The actionable message in an update (a fresh message or channel post). */
|
|
14
5
|
export function pickMessage(update) {
|
|
15
6
|
return update.message ?? update.channel_post;
|
|
16
7
|
}
|
|
@@ -18,30 +9,28 @@ export function pickMessage(update) {
|
|
|
18
9
|
export function extractImages(m) {
|
|
19
10
|
return [...ownImages(m), ...(m.reply_to_message ? ownImages(m.reply_to_message) : [])];
|
|
20
11
|
}
|
|
21
|
-
/** The message's OWN photo (largest size), without the replied-to message's
|
|
22
|
-
* this: each message is its own entry there, so counting the replied-to attachment again would
|
|
23
|
-
* duplicate it and squeeze the attachment cap (the reply relation is expressed by `replyTo` instead). */
|
|
12
|
+
/** The message's OWN photo (largest size), without the replied-to message's. */
|
|
24
13
|
export function ownImages(m) {
|
|
25
14
|
return [m.photo?.at(-1)?.file_id].filter((id) => Boolean(id));
|
|
26
15
|
}
|
|
27
|
-
/** file_ids to download to disk
|
|
28
|
-
* (so "summarize this file" works when the user replies to a document with the mention). */
|
|
16
|
+
/** file_ids to download to disk: this message's files plus a replied-to message's ("summarize this file"). */
|
|
29
17
|
export function extractFiles(m) {
|
|
30
18
|
return [...ownFiles(m), ...(m.reply_to_message ? ownFiles(m.reply_to_message) : [])];
|
|
31
19
|
}
|
|
32
|
-
/** The message's OWN files, without the replied-to message's
|
|
20
|
+
/** The message's OWN files, without the replied-to message's. */
|
|
33
21
|
export function ownFiles(m) {
|
|
34
22
|
return [m.document?.file_id, m.voice?.file_id, m.video?.file_id, m.audio?.file_id].filter((id) => Boolean(id));
|
|
35
23
|
}
|
|
36
|
-
/** A stable sender label for attribution.
|
|
37
|
-
* who across turns; a username-less user still gets a name + id rather than vanishing. */
|
|
24
|
+
/** A stable sender label for attribution. */
|
|
38
25
|
export function fromLabel(from) {
|
|
39
26
|
if (!from)
|
|
40
27
|
return undefined;
|
|
41
28
|
return from.username ? `@${from.username}` : `${from.first_name ?? "user"} (id ${from.id})`;
|
|
42
29
|
}
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
30
|
+
/**
|
|
31
|
+
* A one-line description of a message's attachment, so the envelope names what was sent even before the agent opens it
|
|
32
|
+
* (and so a media-only message isn't blank).
|
|
33
|
+
*/
|
|
45
34
|
export function attachmentSummary(m) {
|
|
46
35
|
if (m.photo?.length)
|
|
47
36
|
return "[photo]";
|
|
@@ -56,9 +45,10 @@ export function attachmentSummary(m) {
|
|
|
56
45
|
return "[audio]";
|
|
57
46
|
return undefined;
|
|
58
47
|
}
|
|
59
|
-
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
48
|
+
/**
|
|
49
|
+
* A message's readable body: its text, else its caption, else a one-line attachment summary; undefined for an
|
|
50
|
+
* empty/service message.
|
|
51
|
+
*/
|
|
62
52
|
function bodyOf(m) {
|
|
63
53
|
return m.text ?? m.caption ?? attachmentSummary(m);
|
|
64
54
|
}
|
|
@@ -67,13 +57,8 @@ export function messageText(m) {
|
|
|
67
57
|
return truncateCodePointPrefix((bodyOf(m) ?? "").replace(/\s+/g, " ").trim(), BUFFER_LINE_MAX_CHARS);
|
|
68
58
|
}
|
|
69
59
|
/**
|
|
70
|
-
* The default base prompt: a context envelope (chat/thread/sender + a group note + reply) then the
|
|
71
|
-
*
|
|
72
|
-
* sender is named on every message and a group chat is flagged — in a shared multi-user session that is
|
|
73
|
-
* how the model tells participants apart and knows it is not a 1:1. The reply
|
|
74
|
-
* block carries the replied-to sender, message id, and text/caption or an attachment summary (and the
|
|
75
|
-
* channel downloads a replied-to file/photo too). Exported so a custom `route` can reuse it, e.g.
|
|
76
|
-
* `text: `${telegramEnvelope(m)}\n\n[extra]``. The channel still appends downloaded attachments.
|
|
60
|
+
* The default base prompt: a context envelope (chat/thread/sender + a group note + reply) then the user's text/caption
|
|
61
|
+
* and a compact rendering of structured payloads (location/contact/poll).
|
|
77
62
|
*/
|
|
78
63
|
export function telegramEnvelope(m) {
|
|
79
64
|
const r = m.reply_to_message;
|
|
@@ -84,9 +69,7 @@ export function telegramEnvelope(m) {
|
|
|
84
69
|
]
|
|
85
70
|
.filter(Boolean)
|
|
86
71
|
.join(", ");
|
|
87
|
-
// In a shared group session the model sees turns from different people (each `from`-tagged)
|
|
88
|
-
// so it addresses participants by name and does not assume one continuous interlocutor. A 1:1 DM is
|
|
89
|
-
// self-evident, so no note there.
|
|
72
|
+
// In a shared group session the model sees turns from different people (each `from`-tagged).
|
|
90
73
|
const isGroup = m.chat.type === "group" || m.chat.type === "supergroup";
|
|
91
74
|
const scope = isGroup ? "\n[group chat — multiple people; each message is prefixed with its sender]" : "";
|
|
92
75
|
const replyTo = r
|
|
@@ -101,20 +84,14 @@ export function telegramEnvelope(m) {
|
|
|
101
84
|
parts.push(`[poll: ${m.poll.question} — ${(m.poll.options ?? []).map((o) => o.text).join(" / ")}]`);
|
|
102
85
|
return `[telegram: ${meta}]${scope}${replyTo}\n${parts.filter(Boolean).join("\n")}`;
|
|
103
86
|
}
|
|
104
|
-
/** Normalize a configured bot username: drop a leading `@`, trim, lowercase (usernames are case-
|
|
105
|
-
* insensitive). Undefined when unknown. */
|
|
87
|
+
/** Normalize a configured bot username: drop a leading `@`, trim, lowercase (usernames are case-insensitive). */
|
|
106
88
|
function botName(botUsername) {
|
|
107
89
|
const s = botUsername?.replace(/^@/, "").trim().toLowerCase();
|
|
108
90
|
return s || undefined;
|
|
109
91
|
}
|
|
110
92
|
/**
|
|
111
|
-
* The `mention` ENTITIES naming THIS bot — read from what Telegram's server already parsed, not a
|
|
112
|
-
*
|
|
113
|
-
* `@bot` inside a code block or a URL is not a `mention` entity, a glued `/cmd@bot` is a `bot_command`
|
|
114
|
-
* — and slicing the exact offset/length range makes `@fast` vs `@fastagent` confusion impossible.
|
|
115
|
-
* (Offsets are UTF-16 code units = native JS string indexing.) No text fallback: mention entities are
|
|
116
|
-
* produced server-side, so their absence means there is no mention. Empty when the bot does not know
|
|
117
|
-
* its own username — "which of these names me?" has no answer then, and guessing would mis-summon.
|
|
93
|
+
* The `mention` ENTITIES naming THIS bot — read from what Telegram's server already parsed, not a regex over the raw
|
|
94
|
+
* text.
|
|
118
95
|
*/
|
|
119
96
|
function botMentions(m, botUsername) {
|
|
120
97
|
const name = botName(botUsername);
|
|
@@ -124,14 +101,10 @@ function botMentions(m, botUsername) {
|
|
|
124
101
|
const entities = (m.text !== undefined ? m.entities : m.caption_entities) ?? [];
|
|
125
102
|
return entities.filter((e) => e.type === "mention" && text.slice(e.offset, e.offset + e.length).toLowerCase() === `@${name}`);
|
|
126
103
|
}
|
|
127
|
-
/** Whether the message @mentions the bot. */
|
|
128
104
|
function mentionsBot(m, botUsername) {
|
|
129
105
|
return botMentions(m, botUsername).length > 0;
|
|
130
106
|
}
|
|
131
|
-
/** The message text with THIS bot's mentions cut out, so a command addressed to it reads as the bare
|
|
132
|
-
* command. Only its own: a message that names someone else (`@otherbot /stop`) must keep that name,
|
|
133
|
-
* or cutting it would turn an ask aimed elsewhere into this bot's command. Cut from the end so the
|
|
134
|
-
* earlier offsets stay valid (they are UTF-16 code units = native JS indexing). */
|
|
107
|
+
/** The message text with THIS bot's mentions cut out, so a command addressed to it reads as the bare command. */
|
|
135
108
|
function textWithoutBotMentions(m, botUsername) {
|
|
136
109
|
const text = m.text ?? m.caption ?? "";
|
|
137
110
|
return botMentions(m, botUsername)
|
|
@@ -140,41 +113,27 @@ function textWithoutBotMentions(m, botUsername) {
|
|
|
140
113
|
.trim();
|
|
141
114
|
}
|
|
142
115
|
/**
|
|
143
|
-
* Whether the update is a `/stop` ADDRESSED TO THIS BOT — the one question the channel asks about a
|
|
144
|
-
*
|
|
145
|
-
* equivalent: a bare `/stop` in a private chat (no one else is there), `/stop@thisbot`, an `@thisbot`
|
|
146
|
-
* mention beside the command, and a bare `/stop` replying to one of this bot's messages.
|
|
147
|
-
*
|
|
148
|
-
* A BARE `/stop` in a group is deliberately NOT one of them. Telegram hands it to every bot in the
|
|
149
|
-
* chat — and only promises to deliver it at all when this bot spoke last — so it names no one: acting
|
|
150
|
-
* on it lets a bystander's ask for a different bot abort this bot's run, and answering it lets that
|
|
151
|
-
* bystander make this bot talk. It stays ordinary discussion, which is what the group sees anyway.
|
|
152
|
-
*
|
|
153
|
-
* Takes the UPDATE, like `route` itself, so a custom route can ask it directly — that route is the
|
|
154
|
-
* gate a stop must pass, and one that refuses this update silences the command.
|
|
116
|
+
* Whether the update is a `/stop` ADDRESSED TO THIS BOT — the one question the channel asks about a stop, because an
|
|
117
|
+
* unaddressed one is not a command it may act on.
|
|
155
118
|
*/
|
|
156
119
|
export function telegramStop(update, options) {
|
|
157
120
|
const m = pickMessage(update);
|
|
158
121
|
if (!m)
|
|
159
122
|
return false;
|
|
160
|
-
// This bot's own mentions are cut out before matching: `@thisbot /stop` is the same command as
|
|
161
|
-
// `/stop@thisbot`. Anyone else's stays in the text, so `@otherbot /stop` never reduces to a command
|
|
162
|
-
// here — not even when it also replies to this bot.
|
|
123
|
+
// This bot's own mentions are cut out before matching: `@thisbot /stop` is the same command as `/stop@thisbot`.
|
|
163
124
|
const match = /^\/stop(?:@([A-Za-z0-9_]+))?$/i.exec(textWithoutBotMentions(m, options?.botUsername));
|
|
164
125
|
if (!match)
|
|
165
126
|
return false;
|
|
166
|
-
// `/stop@name` states its addressee: ours only when the name is ours, and never when this bot does
|
|
167
|
-
//
|
|
127
|
+
// `/stop@name` states its addressee: ours only when the name is ours, and never when this bot does not know its own
|
|
128
|
+
// username (fail closed — the same rule reply/mention summon follows).
|
|
168
129
|
if (match[1] !== undefined)
|
|
169
130
|
return match[1].toLowerCase() === botName(options?.botUsername);
|
|
170
131
|
return m.chat.type === "private" || mentionsBot(m, options?.botUsername) || repliesToBot(m, options);
|
|
171
132
|
}
|
|
172
|
-
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
|
|
176
|
-
* to me?" with "I don't know who I am, so yes" would mis-summon in every multi-bot group — a caller
|
|
177
|
-
* reusing the route bare must supply an identity to get reply summon. */
|
|
133
|
+
/**
|
|
134
|
+
* Whether the message replies to THIS bot — not just any bot: in a multi-bot group, replying to another bot must not
|
|
135
|
+
* summon ours.
|
|
136
|
+
*/
|
|
178
137
|
function repliesToBot(m, options) {
|
|
179
138
|
const r = m.reply_to_message?.from;
|
|
180
139
|
if (r?.is_bot !== true)
|
|
@@ -184,16 +143,7 @@ function repliesToBot(m, options) {
|
|
|
184
143
|
const name = botName(options?.botUsername);
|
|
185
144
|
return name !== undefined && r.username?.toLowerCase() === name;
|
|
186
145
|
}
|
|
187
|
-
/**
|
|
188
|
-
* The default routing policy (used when `route` is omitted; exported so a custom route can reuse it):
|
|
189
|
-
* answer private chats always; a group only on a reply to THIS bot (by `botId`) or a `mention` entity
|
|
190
|
-
* naming it (when `botUsername` is supplied — telegramChannel parses the id from the token and resolves
|
|
191
|
-
* the username via getMe). A bare or directed slash command does NOT summon in a group (that was noisy;
|
|
192
|
-
* a bot author who wants commands adds a custom route) — except a `/stop` ADDRESSED to this bot, which
|
|
193
|
-
* must reach the run it stops rather than sit in the buffer behind it (see {@link telegramStop}; a
|
|
194
|
-
* bare group `/stop` addresses no one and stays ordinary discussion). Returns `{}` (act; the channel fills
|
|
195
|
-
* session/target/prompt from the message) or `null` (ignore).
|
|
196
|
-
*/
|
|
146
|
+
/** The default routing policy (used when `route` is omitted; exported so a custom route can reuse it). */
|
|
197
147
|
export function defaultTelegramRoute(update, options) {
|
|
198
148
|
const m = pickMessage(update);
|
|
199
149
|
if (!m)
|
|
@@ -201,9 +151,7 @@ export function defaultTelegramRoute(update, options) {
|
|
|
201
151
|
const summoned = m.chat.type === "private" ||
|
|
202
152
|
repliesToBot(m, options) ||
|
|
203
153
|
mentionsBot(m, options?.botUsername) ||
|
|
204
|
-
// Adds exactly ONE case to the three above: the `/stop@thisbot` suffix form.
|
|
205
|
-
// address a stop (private, mention, reply) is already a summon on its own — telegramStop repeats
|
|
206
|
-
// those three by design, since it must answer "addressed to me?" without a route to lean on.
|
|
154
|
+
// Adds exactly ONE case to the three above: the `/stop@thisbot` suffix form.
|
|
207
155
|
telegramStop(update, options);
|
|
208
156
|
return summoned ? {} : null;
|
|
209
157
|
}
|
|
@@ -6,14 +6,23 @@
|
|
|
6
6
|
* message, works in groups and private (unlike sendMessageDraft, which is private/forum-topic only).
|
|
7
7
|
*/
|
|
8
8
|
import type { AgentEvent } from "../../agent.ts";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import type * as Stream from "effect/Stream";
|
|
11
|
+
import { type PortFailure } from "../../effect-port.ts";
|
|
9
12
|
import { type ChannelFailure, defaultErrorMessage } from "../kit/preview-kit.ts";
|
|
10
13
|
import { type Target } from "./telegram-api.ts";
|
|
11
14
|
/** A terminal failure, as the channel hands it to `onError` — the shared channel shape. */
|
|
12
15
|
export type TelegramFailure = ChannelFailure;
|
|
13
16
|
export { defaultErrorMessage };
|
|
17
|
+
/**
|
|
18
|
+
* Deliver a reply this process did not generate: a recovered answer. The run that produced it is gone with its
|
|
19
|
+
* preview, but a "⏳ Queued" notice THIS process put up while the record waited its turn is still there — so the
|
|
20
|
+
* same terminal-write policy applies, taking that message over rather than leaving it pinned above the answer.
|
|
21
|
+
*/
|
|
22
|
+
export declare function deliverTelegramAnswer(api: string, botToken: string, target: Target, text: string, previewId?: number): Promise<void>;
|
|
14
23
|
/**
|
|
15
24
|
* Consume one turn's event stream into a Telegram chat, live (see the module header for the preview
|
|
16
25
|
* model). Preview edits are best-effort (logged once if they fail); the final write is authoritative
|
|
17
26
|
* and surfaces a real failure (bad token, etc.).
|
|
18
27
|
*/
|
|
19
|
-
export declare function
|
|
28
|
+
export declare function telegramReply(events: Stream.Stream<AgentEvent, PortFailure>, api: string, botToken: string, target: Target, formatError: (failed: TelegramFailure) => string | undefined, previewId?: number, onAnswered?: (answer: string) => void): Effect.Effect<void, PortFailure, import("effect/Scope").Scope>;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Clock from "effect/Clock";
|
|
3
|
+
import { previewPump, renderReply } from "../kit/delivery.js";
|
|
4
|
+
import { DROPPABLE_FRAME } from "../kit/transport.js";
|
|
5
|
+
import { portJoin } from "../../effect-port.js";
|
|
6
|
+
import { RETRY_NOTICE, THINKING_PLACEHOLDER, applyTurnEvent, composeTurnBody, createTurnView, defaultErrorMessage, revealedAnswer, thinkingLine, toolLines, } from "../kit/preview-kit.js";
|
|
2
7
|
import { log } from "../../log.js";
|
|
3
8
|
import { TELEGRAM_MAX_TEXT, callApi, editMessageText, sendMessage } from "./telegram-api.js";
|
|
4
9
|
export { defaultErrorMessage };
|
|
@@ -10,7 +15,7 @@ const EDIT_THROTTLE_MS = 1500;
|
|
|
10
15
|
/** How much of the (growing) reasoning to peek at in the live view — the most recent tail. */
|
|
11
16
|
const THINKING_PREVIEW = 280;
|
|
12
17
|
/**
|
|
13
|
-
* The terminal-write POLICY: resolve the single preview message into `text`.
|
|
18
|
+
* The terminal-write POLICY: resolve the single preview message into `text`. telegramReply owns the
|
|
14
19
|
* preview lifecycle, so this composition of transport primitives lives here, not in telegram-api. One
|
|
15
20
|
* message → edit the preview in place; if the edit fails (preview gone, or a persistent 429/5xx) fall
|
|
16
21
|
* back to deleting the placeholder and sending fresh, so no "Thinking…" is left pinned above the answer.
|
|
@@ -44,109 +49,83 @@ async function finalize(api, botToken, target, messageId, text, opts = {}) {
|
|
|
44
49
|
}
|
|
45
50
|
await sendMessage(api, botToken, target, text, { html });
|
|
46
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Deliver a reply this process did not generate: a recovered answer. The run that produced it is gone with its
|
|
54
|
+
* preview, but a "⏳ Queued" notice THIS process put up while the record waited its turn is still there — so the
|
|
55
|
+
* same terminal-write policy applies, taking that message over rather than leaving it pinned above the answer.
|
|
56
|
+
*/
|
|
57
|
+
export function deliverTelegramAnswer(api, botToken, target, text, previewId) {
|
|
58
|
+
return finalize(api, botToken, target, previewId, text);
|
|
59
|
+
}
|
|
47
60
|
/**
|
|
48
61
|
* Consume one turn's event stream into a Telegram chat, live (see the module header for the preview
|
|
49
62
|
* model). Preview edits are best-effort (logged once if they fail); the final write is authoritative
|
|
50
63
|
* and surfaces a real failure (bad token, etc.).
|
|
51
64
|
*/
|
|
52
|
-
export
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// frame). If Telegram returns ok WITHOUT a message_id (proxy / odd API base / unparseable body) we
|
|
85
|
-
// cannot edit — fail visibly and stop previewing (the final write still lands via finalize).
|
|
86
|
-
if (previewSent)
|
|
87
|
-
return;
|
|
88
|
-
previewSent = true;
|
|
89
|
-
messageId = await sendMessage(api, botToken, target, text, { html: false });
|
|
90
|
-
if (messageId === undefined)
|
|
91
|
-
throw new Error("telegram sendMessage returned ok without a message_id — live preview disabled for this turn");
|
|
92
|
-
};
|
|
93
|
-
// The shared single-writer pump (preview-kit) serializes edits to the one preview message. (No
|
|
94
|
-
// keepalive: a real message does not expire, unlike a Bot API `sendMessageDraft` (30s window).)
|
|
95
|
-
const { touch, finish } = createPreviewPump({
|
|
96
|
-
flush: flushPreview,
|
|
97
|
-
throttleMs: EDIT_THROTTLE_MS,
|
|
98
|
-
onError: (e) => log.warn(`[telegram] live preview failed (final reply still sends): ${String(e)}`),
|
|
99
|
-
});
|
|
100
|
-
touch(); // send the "💭 Thinking…" placeholder immediately
|
|
101
|
-
try {
|
|
102
|
-
for await (const e of events) {
|
|
103
|
-
if (e.type === "completed") {
|
|
104
|
-
await finish();
|
|
105
|
-
// Edit the preview into the final answer (HTML, plain fallback); the persisted message is the
|
|
106
|
-
// answer alone — the process (thinking/tools) was preview-only. Mark finalized BEFORE delivering:
|
|
107
|
-
// the terminal was reached, so a delivery failure here is a plain failure, not an "abnormal exit"
|
|
108
|
-
// (which would wrongly fire the finally's neutral-notice fallback = double delivery + wrong text).
|
|
109
|
-
finalized = true;
|
|
110
|
-
await finalize(api, botToken, target, messageId, turn.answer.trim() !== "" ? turn.answer : "(no reply)");
|
|
65
|
+
export function telegramReply(events, api, botToken, target, formatError, previewId, onAnswered) {
|
|
66
|
+
return Effect.gen(function* () {
|
|
67
|
+
const clock = yield* Clock.Clock;
|
|
68
|
+
const now = () => clock.currentTimeMillisUnsafe();
|
|
69
|
+
// Event → view-state reduction is the shared machine (preview-kit); this renderer owns the reveal
|
|
70
|
+
// policy, formatting, and delivery below.
|
|
71
|
+
const turn = createTurnView();
|
|
72
|
+
const view = () => {
|
|
73
|
+
const v = composeTurnBody([
|
|
74
|
+
thinkingLine(turn, THINKING_PREVIEW),
|
|
75
|
+
toolLines(turn),
|
|
76
|
+
turn.retrying ? RETRY_NOTICE : "",
|
|
77
|
+
revealedAnswer(turn, EDIT_THROTTLE_MS, now()),
|
|
78
|
+
]);
|
|
79
|
+
// Before any reasoning/tool/text arrives, show an explicit placeholder rather than an empty edit.
|
|
80
|
+
return v === "" ? THINKING_PLACEHOLDER : v;
|
|
81
|
+
};
|
|
82
|
+
// The live preview is ONE real message: sent once (capturing its id + threading under the asker),
|
|
83
|
+
// then edited in place. messageId/lastSent are shared with the final write on completion.
|
|
84
|
+
// `previewId`: an already-sent message to take over as the preview (the "⏳ queued" notice) — the
|
|
85
|
+
// pump edits it in place, so the queue notice morphs into the live view instead of leaving an orphan.
|
|
86
|
+
let messageId = previewId;
|
|
87
|
+
let previewSent = messageId !== undefined; // a placeholder send was attempted — guards against re-sending when no id came back
|
|
88
|
+
let lastSent = "";
|
|
89
|
+
const flushPreview = async () => {
|
|
90
|
+
const text = view();
|
|
91
|
+
if (text === lastSent)
|
|
92
|
+
return; // skip an unchanged edit (Telegram rejects "message is not modified")
|
|
93
|
+
lastSent = text;
|
|
94
|
+
if (messageId !== undefined) {
|
|
95
|
+
// plain — a partial answer may carry unbalanced HTML; droppable — see DROPPABLE_FRAME.
|
|
96
|
+
await editMessageText(api, botToken, target, messageId, text, DROPPABLE_FRAME);
|
|
111
97
|
return;
|
|
112
98
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// neutral "something went wrong" default rather than promising "try again" that may not help.
|
|
146
|
-
const notice = formatError({ details: "the turn ended without completing", retryable: false }) ?? "";
|
|
147
|
-
// finalize handles messageId===undefined (no preview reached) with a fresh send — so the user is
|
|
148
|
-
// told even when the turn died before any message.
|
|
149
|
-
await finalize(api, botToken, target, messageId, notice).catch(() => { });
|
|
150
|
-
}
|
|
151
|
-
}
|
|
99
|
+
// No preview message yet. Send the placeholder ONCE; never re-send (that would spam a new message per
|
|
100
|
+
// frame). If Telegram returns ok WITHOUT a message_id (proxy / odd API base / unparseable body) we
|
|
101
|
+
// cannot edit — fail visibly and stop previewing (the final write still lands via finalize).
|
|
102
|
+
//
|
|
103
|
+
// NOT droppable, unlike the frames above: this send happens once and every later frame depends on
|
|
104
|
+
// its id, so dropping it costs the whole turn's live preview rather than one redrawable view.
|
|
105
|
+
if (previewSent)
|
|
106
|
+
return;
|
|
107
|
+
previewSent = true;
|
|
108
|
+
messageId = await sendMessage(api, botToken, target, text, { html: false });
|
|
109
|
+
if (messageId === undefined)
|
|
110
|
+
throw new Error("telegram sendMessage returned ok without a message_id — live preview disabled for this turn");
|
|
111
|
+
};
|
|
112
|
+
const { touch, finish } = yield* previewPump({
|
|
113
|
+
flush: flushPreview,
|
|
114
|
+
throttleMs: EDIT_THROTTLE_MS,
|
|
115
|
+
onError: (e) => log.warn(`[telegram] live preview failed (final reply still sends): ${String(e)}`),
|
|
116
|
+
});
|
|
117
|
+
touch(); // send the "💭 Thinking…" placeholder immediately
|
|
118
|
+
yield* renderReply(events, {
|
|
119
|
+
label: "[telegram]",
|
|
120
|
+
finish,
|
|
121
|
+
formatError,
|
|
122
|
+
onEvent: (event) => {
|
|
123
|
+
if (applyTurnEvent(turn, event, now()))
|
|
124
|
+
touch();
|
|
125
|
+
},
|
|
126
|
+
answer: () => (turn.answer.trim() !== "" ? turn.answer : "(no reply)"),
|
|
127
|
+
...(onAnswered ? { onAnswered } : {}),
|
|
128
|
+
settle: (text) => portJoin(() => finalize(api, botToken, target, messageId, text)),
|
|
129
|
+
});
|
|
130
|
+
});
|
|
152
131
|
}
|