@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { feishuChannel } from "@fastagent-sh/fastagent/feishu";
|
|
2
|
+
import { defineChannel } from "@fastagent-sh/fastagent";
|
|
2
3
|
|
|
3
4
|
// feishuChannel is fastagent's canonical Feishu adapter (verify + run + reply), configured with YOUR
|
|
4
5
|
// policy. fastagent discovers this file, mounts POST /feishu, and pipes the agent + state home to it.
|
|
@@ -20,23 +21,30 @@ import { feishuChannel } from "@fastagent-sh/fastagent/feishu";
|
|
|
20
21
|
// 5. create a version and publish the app (a Feishu admin approves it), then add the bot to a chat
|
|
21
22
|
// `fastagent add feishu` already did steps 1-3 for you (scan-to-create); this walkthrough is for a
|
|
22
23
|
// hand-made app or for auditing what the scan configured.
|
|
23
|
-
export default
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
24
|
+
export default defineChannel({
|
|
25
|
+
// The env vars this channel needs. fastagent carries them to a deployed box and refuses to serve
|
|
26
|
+
// while one is unset — a bare process.env read gets neither guarantee. FEISHU_ENCRYPT_KEY is
|
|
27
|
+
// optional, so it is NOT declared here: an undeclared read stays undeclared on purpose.
|
|
28
|
+
secrets: ["FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_VERIFICATION_TOKEN"],
|
|
29
|
+
channel: (secrets) =>
|
|
30
|
+
feishuChannel({
|
|
31
|
+
appId: secrets.FEISHU_APP_ID,
|
|
32
|
+
appSecret: secrets.FEISHU_APP_SECRET,
|
|
33
|
+
verificationToken: secrets.FEISHU_VERIFICATION_TOKEN, // authenticates inbound events
|
|
34
|
+
encryptKey: process.env.FEISHU_ENCRYPT_KEY || undefined, // optional; when set, plaintext events are refused
|
|
35
|
+
// No session modes: a chat is one session and a thread is another, and where the answer goes follows
|
|
36
|
+
// from that (docs/design/participant-model.md).
|
|
37
|
+
// Dev/personal bot: surface raw errors to the chat so you (and your AI agent) can act on them. The
|
|
38
|
+
// chat is customer-facing by default — for a public bot, drop this or return a neutral string;
|
|
39
|
+
// full details always go to the server log regardless.
|
|
40
|
+
onError: (failed) => `⚠️ ${failed.details}`,
|
|
41
|
+
// The channel owns transport + format (markdown card) + attachments (image→vision, file→disk) +
|
|
42
|
+
// the live streaming preview. `route` (POLICY) is OPTIONAL — omitted, it uses defaultFeishuRoute:
|
|
43
|
+
// p2p chats always answer; groups answer on @this-bot, plus bare messages in a thread where the
|
|
44
|
+
// Agent takes part and exactly ONE human does. Other human group/thread discussion buffers until
|
|
45
|
+
// that place's next answered turn; @other-only messages buffer rather than triggering the Agent.
|
|
46
|
+
// Override to customise explicit routing, reusing the export:
|
|
47
|
+
// route: (e) => defaultFeishuRoute(e, { botOpenId: "ou_xxx" }) && { session: `user:${e.sender?.sender_id?.open_id}` },
|
|
48
|
+
// route: (e) => defaultFeishuRoute(e, { botOpenId: "ou_xxx" }) && { text: `${feishuEnvelope(e)}\n[extra]` },
|
|
49
|
+
}),
|
|
42
50
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { feishuWebSocketChannel } from "@fastagent-sh/fastagent/feishu";
|
|
2
|
+
import { defineChannel } from "@fastagent-sh/fastagent";
|
|
3
|
+
|
|
4
|
+
// Feishu WebSocket long connection: the process connects OUT to the platform, so no public URL,
|
|
5
|
+
// Verification Token, Encrypt Key, or --tunnel is needed. In Events & Callbacks choose long
|
|
6
|
+
// connection, subscribe im.message.receive_v1, then publish the app version. Keep one process
|
|
7
|
+
// running in production: scale-to-zero/App Sleeping would disconnect ingress.
|
|
8
|
+
export default defineChannel({
|
|
9
|
+
// The env vars this channel needs. fastagent carries them to a deployed box and refuses to serve
|
|
10
|
+
// while one is unset — a bare process.env read gets neither guarantee.
|
|
11
|
+
secrets: ["FEISHU_APP_ID", "FEISHU_APP_SECRET"],
|
|
12
|
+
channel: (secrets) =>
|
|
13
|
+
feishuWebSocketChannel({
|
|
14
|
+
appId: secrets.FEISHU_APP_ID,
|
|
15
|
+
appSecret: secrets.FEISHU_APP_SECRET,
|
|
16
|
+
// No session modes: a chat is one session and a thread is another, and where the answer goes follows
|
|
17
|
+
// from that (docs/design/participant-model.md).
|
|
18
|
+
// Dev/personal bot: surface raw errors to the chat so you (and your AI agent) can act on them. The
|
|
19
|
+
// chat is customer-facing by default — for a public bot, drop this or return a neutral string;
|
|
20
|
+
// full details always go to the server log regardless.
|
|
21
|
+
onError: (failed) => `⚠️ ${failed.details}`,
|
|
22
|
+
// The channel owns transport + format (markdown card) + attachments (image→vision, file→disk) +
|
|
23
|
+
// the live streaming preview. `route` (POLICY) is OPTIONAL — omitted, it uses defaultFeishuRoute:
|
|
24
|
+
// p2p chats always answer; groups answer on @this-bot, plus bare messages in a thread where the
|
|
25
|
+
// Agent takes part and exactly ONE human does. Other human group/thread discussion buffers until
|
|
26
|
+
// that place's next answered turn; @other-only messages buffer rather than triggering the Agent.
|
|
27
|
+
// Override to customise explicit routing, reusing the export:
|
|
28
|
+
// route: (e) => defaultFeishuRoute(e, { botOpenId: "ou_xxx" }) && { session: `user:${e.sender?.sender_id?.open_id}` },
|
|
29
|
+
// route: (e) => defaultFeishuRoute(e, { botOpenId: "ou_xxx" }) && { text: `${feishuEnvelope(e)}\n[extra]` },
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
/** Feishu/Lark app-level event subscription choice used by onboarding and scaffolding. */
|
|
2
2
|
export type FeishuSubscriptionMode = "webhook" | "websocket";
|
|
3
|
-
/** Onboarding choice for group visibility.
|
|
4
|
-
* runtime remains capability-driven because the platform, not channel source, decides which events
|
|
5
|
-
* are delivered. */
|
|
3
|
+
/** Onboarding choice for group visibility. */
|
|
6
4
|
export type FeishuGroupBehavior = "context" | "mentions";
|
|
7
5
|
/**
|
|
8
|
-
* Configuring THIS app: what every v7 config PATCH needs — registering a webhook Request URL, and
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Requested at creation for BOTH ingress modes, though only webhook uses it on day one. A WebSocket
|
|
12
|
-
* app that later wants webhook delivery cannot acquire it in passing: changing mode is a migration
|
|
13
|
-
* the CLI deliberately refuses to perform (`resolveIngress`), so the app would have to be recreated
|
|
14
|
-
* or the scope added by hand in the console after a failed PATCH. It is not a data scope — it reads
|
|
15
|
-
* and writes this app's own configuration — so carrying it costs an app nothing and keeps the one
|
|
16
|
-
* door open that the alternative nails shut.
|
|
6
|
+
* Configuring THIS app: what every v7 config PATCH needs — registering a webhook Request URL, and adding a scope to
|
|
7
|
+
* the app draft during context-aware group setup.
|
|
17
8
|
*/
|
|
18
9
|
export declare const FEISHU_APP_CONFIG_SCOPE = "application:application:patch";
|
|
19
10
|
/** The one event an agent app cannot serve without: an inbound message. */
|
|
20
11
|
export declare const FEISHU_MESSAGE_RECEIVE_EVENT = "im.message.receive_v1";
|
|
21
12
|
/**
|
|
22
|
-
* What a created app carries on top of the platform's agent template, for EITHER ingress — the
|
|
23
|
-
*
|
|
24
|
-
* policy decision (which capabilities an app is born with), and the CLI layer that requests it is
|
|
25
|
-
* terminal wiring with no seam to test through.
|
|
13
|
+
* What a created app carries on top of the platform's agent template, for EITHER ingress — the `addons` merged onto
|
|
14
|
+
* the confirm page.
|
|
26
15
|
*/
|
|
27
16
|
export declare function feishuAppAddons(): {
|
|
28
17
|
scopes: {
|
|
@@ -36,32 +25,18 @@ export declare function feishuAppAddons(): {
|
|
|
36
25
|
};
|
|
37
26
|
/** The sensitive tenant scope behind both bare replies in the agent's threads and group context buffering. */
|
|
38
27
|
export declare const FEISHU_GROUP_CONTEXT_SCOPE = "im:message.group_msg";
|
|
39
|
-
/** Reading a QUOTED message by id, so an ask carries what it replies to (participant-model.md §8).
|
|
40
|
-
* INDEPENDENT of the delivery scope above and of the group posture: the read runs in every chat type
|
|
41
|
-
* (a p2p thread's opening ask, any quoted @mention in a group). It is also a softer dependency —
|
|
42
|
-
* without it everything still works, and an unreadable quote degrades to a marker in the prompt.
|
|
43
|
-
* Bundled into the context-aware request only because that path already needs an approval round; a
|
|
44
|
-
* mention-only app wanting referents must add it by hand. */
|
|
28
|
+
/** Reading a QUOTED message by id, so an ask carries what it replies to (participant-model.md §8). */
|
|
45
29
|
export declare const FEISHU_MESSAGE_READ_SCOPE = "im:message:readonly";
|
|
46
|
-
/** A scope the onboarding asks for, plus any BROADER spelling that already satisfies it.
|
|
47
|
-
* so adding a superset means editing the entry — not every call site that tests a scope. */
|
|
30
|
+
/** A scope the onboarding asks for, plus any BROADER spelling that already satisfies it. */
|
|
48
31
|
export interface FeishuScopeRequest {
|
|
49
|
-
/** What to add to the app draft when nothing satisfies it.
|
|
32
|
+
/** What to add to the app draft when nothing satisfies it. */
|
|
50
33
|
request: string;
|
|
51
|
-
/** EXTRA spellings that also count — supersets.
|
|
52
|
-
* so a request that can never be satisfied (a list omitting its own `request`) cannot be
|
|
53
|
-
* written: onboarding would add the scope, the tenant would grant it, and it would still read as
|
|
54
|
-
* missing forever. */
|
|
34
|
+
/** EXTRA spellings that also count — supersets. */
|
|
55
35
|
supersets?: string[];
|
|
56
36
|
}
|
|
57
37
|
/** Whether `predicate` holds for any spelling that satisfies this request. */
|
|
58
38
|
export declare function scopeSatisfied(entry: FeishuScopeRequest, predicate: (name: string) => boolean): boolean;
|
|
59
|
-
/** Reading a quoted message: `im:message` is the read/write superset, so an app holding it can already
|
|
60
|
-
* do so — checking only the readonly spelling would warn a correctly configured app forever and push
|
|
61
|
-
* its author into a redundant approval round. Exported on its own because the serving-time capability
|
|
62
|
-
* report asks the same question, and a second mechanism there is what this type exists to prevent. */
|
|
39
|
+
/** Reading a quoted message: `im:message` is the read/write superset, so an app holding it can already do so. */
|
|
63
40
|
export declare const FEISHU_MESSAGE_READ_REQUEST: FeishuScopeRequest;
|
|
64
|
-
/** What `--group-behavior context` REQUESTS in one approval round — not a dependency set.
|
|
65
|
-
* delivery scope is required for the context path; the read scope rides along because it shares the
|
|
66
|
-
* round and its absence merely degrades quoted messages to a marker. */
|
|
41
|
+
/** What `--group-behavior context` REQUESTS in one approval round — not a dependency set. */
|
|
67
42
|
export declare const FEISHU_CONTEXT_ONBOARDING_SCOPES: FeishuScopeRequest[];
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Configuring THIS app: what every v7 config PATCH needs — registering a webhook Request URL, and
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Requested at creation for BOTH ingress modes, though only webhook uses it on day one. A WebSocket
|
|
6
|
-
* app that later wants webhook delivery cannot acquire it in passing: changing mode is a migration
|
|
7
|
-
* the CLI deliberately refuses to perform (`resolveIngress`), so the app would have to be recreated
|
|
8
|
-
* or the scope added by hand in the console after a failed PATCH. It is not a data scope — it reads
|
|
9
|
-
* and writes this app's own configuration — so carrying it costs an app nothing and keeps the one
|
|
10
|
-
* door open that the alternative nails shut.
|
|
2
|
+
* Configuring THIS app: what every v7 config PATCH needs — registering a webhook Request URL, and adding a scope to
|
|
3
|
+
* the app draft during context-aware group setup.
|
|
11
4
|
*/
|
|
12
5
|
export const FEISHU_APP_CONFIG_SCOPE = "application:application:patch";
|
|
13
6
|
/** The one event an agent app cannot serve without: an inbound message. */
|
|
14
7
|
export const FEISHU_MESSAGE_RECEIVE_EVENT = "im.message.receive_v1";
|
|
15
8
|
/**
|
|
16
|
-
* What a created app carries on top of the platform's agent template, for EITHER ingress — the
|
|
17
|
-
*
|
|
18
|
-
* policy decision (which capabilities an app is born with), and the CLI layer that requests it is
|
|
19
|
-
* terminal wiring with no seam to test through.
|
|
9
|
+
* What a created app carries on top of the platform's agent template, for EITHER ingress — the `addons` merged onto
|
|
10
|
+
* the confirm page.
|
|
20
11
|
*/
|
|
21
12
|
export function feishuAppAddons() {
|
|
22
13
|
return {
|
|
@@ -26,28 +17,18 @@ export function feishuAppAddons() {
|
|
|
26
17
|
}
|
|
27
18
|
/** The sensitive tenant scope behind both bare replies in the agent's threads and group context buffering. */
|
|
28
19
|
export const FEISHU_GROUP_CONTEXT_SCOPE = "im:message.group_msg";
|
|
29
|
-
/** Reading a QUOTED message by id, so an ask carries what it replies to (participant-model.md §8).
|
|
30
|
-
* INDEPENDENT of the delivery scope above and of the group posture: the read runs in every chat type
|
|
31
|
-
* (a p2p thread's opening ask, any quoted @mention in a group). It is also a softer dependency —
|
|
32
|
-
* without it everything still works, and an unreadable quote degrades to a marker in the prompt.
|
|
33
|
-
* Bundled into the context-aware request only because that path already needs an approval round; a
|
|
34
|
-
* mention-only app wanting referents must add it by hand. */
|
|
20
|
+
/** Reading a QUOTED message by id, so an ask carries what it replies to (participant-model.md §8). */
|
|
35
21
|
export const FEISHU_MESSAGE_READ_SCOPE = "im:message:readonly";
|
|
36
22
|
/** Whether `predicate` holds for any spelling that satisfies this request. */
|
|
37
23
|
export function scopeSatisfied(entry, predicate) {
|
|
38
24
|
return [entry.request, ...(entry.supersets ?? [])].some(predicate);
|
|
39
25
|
}
|
|
40
|
-
/** Reading a quoted message: `im:message` is the read/write superset, so an app holding it can already
|
|
41
|
-
* do so — checking only the readonly spelling would warn a correctly configured app forever and push
|
|
42
|
-
* its author into a redundant approval round. Exported on its own because the serving-time capability
|
|
43
|
-
* report asks the same question, and a second mechanism there is what this type exists to prevent. */
|
|
26
|
+
/** Reading a quoted message: `im:message` is the read/write superset, so an app holding it can already do so. */
|
|
44
27
|
export const FEISHU_MESSAGE_READ_REQUEST = {
|
|
45
28
|
request: FEISHU_MESSAGE_READ_SCOPE,
|
|
46
29
|
supersets: ["im:message"],
|
|
47
30
|
};
|
|
48
|
-
/** What `--group-behavior context` REQUESTS in one approval round — not a dependency set.
|
|
49
|
-
* delivery scope is required for the context path; the read scope rides along because it shares the
|
|
50
|
-
* round and its absence merely degrades quoted messages to a marker. */
|
|
31
|
+
/** What `--group-behavior context` REQUESTS in one approval round — not a dependency set. */
|
|
51
32
|
export const FEISHU_CONTEXT_ONBOARDING_SCOPES = [
|
|
52
33
|
{ request: FEISHU_GROUP_CONTEXT_SCOPE },
|
|
53
34
|
FEISHU_MESSAGE_READ_REQUEST,
|
|
@@ -22,7 +22,6 @@ export interface FeishuWsConnectionOptions {
|
|
|
22
22
|
/** Internal SDK seam for deterministic tests. */
|
|
23
23
|
createClient?: CreateFeishuWsClient;
|
|
24
24
|
}
|
|
25
|
-
/** Open Feishu/Lark's official-SDK WebSocket connection.
|
|
26
|
-
* a persistence throw therefore becomes a 500 response frame and the platform re-pushes the event. */
|
|
25
|
+
/** Open Feishu/Lark's official-SDK WebSocket connection. */
|
|
27
26
|
export declare function connectFeishuWs(options: FeishuWsConnectionOptions, signal: AbortSignal): LongConnection;
|
|
28
27
|
export {};
|
|
@@ -9,8 +9,8 @@ function sdkLogger(label) {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
async function productionClient(options, callbacks) {
|
|
12
|
-
// Webhook-only users stay on the lightweight fetch path; load the proprietary-protocol SDK only
|
|
13
|
-
//
|
|
12
|
+
// Webhook-only users stay on the lightweight fetch path; load the proprietary-protocol SDK only when a WebSocket
|
|
13
|
+
// connection is actually opened.
|
|
14
14
|
const { EventDispatcher, LoggerLevel, WSClient } = await import("@larksuiteoapi/node-sdk");
|
|
15
15
|
const label = `[${options.kind}:ws]`;
|
|
16
16
|
const logger = sdkLogger(label);
|
|
@@ -36,8 +36,7 @@ async function productionClient(options, callbacks) {
|
|
|
36
36
|
close: () => client.close(),
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
/** Open Feishu/Lark's official-SDK WebSocket connection.
|
|
40
|
-
* a persistence throw therefore becomes a 500 response frame and the platform re-pushes the event. */
|
|
39
|
+
/** Open Feishu/Lark's official-SDK WebSocket connection. */
|
|
41
40
|
export function connectFeishuWs(options, signal) {
|
|
42
41
|
const label = `[${options.kind}:ws]`;
|
|
43
42
|
// The SDK only logs + returns for this case (no onError), which would leave readiness pending forever.
|
|
@@ -63,8 +62,7 @@ export function connectFeishuWs(options, signal) {
|
|
|
63
62
|
return;
|
|
64
63
|
// Settle-then-close: closedSettled first makes a close()-triggered SDK callback re-entry a no-op.
|
|
65
64
|
closedSettled = true;
|
|
66
|
-
// Terminal failure must release the transport here
|
|
67
|
-
// closedSettled is set, and only close() destroys SDK-held resources (e.g. its cache sweep timer).
|
|
65
|
+
// Terminal failure must release the transport here.
|
|
68
66
|
if (client)
|
|
69
67
|
closeClient(client);
|
|
70
68
|
const failure = error instanceof Error ? error : new Error(String(error));
|
|
@@ -112,8 +110,8 @@ export function connectFeishuWs(options, signal) {
|
|
|
112
110
|
if (client)
|
|
113
111
|
closeClient(client);
|
|
114
112
|
if (!readySettled) {
|
|
115
|
-
// Abort before the first connection: `ready` still settles, and resolution here means
|
|
116
|
-
//
|
|
113
|
+
// Abort before the first connection: `ready` still settles, and resolution here means cancellation, not
|
|
114
|
+
// readiness (the LongConnection contract; serve skips ready-side effects).
|
|
117
115
|
readySettled = true;
|
|
118
116
|
resolveReady();
|
|
119
117
|
}
|
package/dist/channels/http.d.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP/SSE channel: fan one invoke stream out to Server-Sent Events.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* embedding host speaks, so it mounts inside an existing app's own route. It is path-agnostic. The
|
|
6
|
-
* web stream primitives give cancellation (consumer disconnect → cancel() → iterator.return() →
|
|
7
|
-
* invoke cancellation, SPEC MUST 3), backpressure (pull-based), and the body cap natively.
|
|
8
|
-
*
|
|
9
|
-
* Serving it is somebody else's job: the node:http bridge and the route table belong to the HOST
|
|
10
|
-
* (`./serve.ts`). This file knows only the Agent contract and the wire shape of one stream.
|
|
2
|
+
* HTTP/SSE channel: fan one invoke stream out to Server-Sent Events. The web stream primitives supply cancellation
|
|
3
|
+
* natively (consumer disconnect → `cancel()` → `iterator.return()` → invoke cancellation, SPEC MUST 3), backpressure,
|
|
4
|
+
* and the body cap.
|
|
11
5
|
*/
|
|
12
6
|
import type { Agent } from "../agent.ts";
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
7
|
+
/**
|
|
8
|
+
* Request body cap (1 MiB) — shared by every Prompt-bearing wire surface (the control plane's dispatch imports it), so
|
|
9
|
+
* the two caps cannot drift apart.
|
|
10
|
+
*/
|
|
15
11
|
export declare const MAX_BODY_BYTES: number;
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
12
|
+
/**
|
|
13
|
+
* A valid example request body for the invoke handler — lives HERE, next to the shape check it must satisfy, so the
|
|
14
|
+
* CLI's "try it" hint can't drift from the protocol.
|
|
15
|
+
*/
|
|
18
16
|
export declare const INVOKE_EXAMPLE_BODY = "{\"session\":\"dev\",\"text\":\"hello\"}";
|
|
19
17
|
/**
|
|
20
|
-
* Fetch-shaped invoke handler.
|
|
21
|
-
*
|
|
18
|
+
* Fetch-shaped invoke handler.
|
|
19
|
+
*
|
|
20
|
+
* No CORS here: who may call this from a browser is decided once, before dispatch, in `channels/serve.ts`'s router
|
|
21
|
+
* — which is also the only layer that can answer the `OPTIONS` preflight, since this route registers only `POST`.
|
|
22
22
|
*/
|
|
23
23
|
export declare function createInvokeHandler(agent: Agent): (req: Request) => Promise<Response>;
|
package/dist/channels/http.js
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
|
-
import { readBodyCapped } from "./body.js";
|
|
1
|
+
import { readBodyCapped, refuseNonJsonBody } from "./body.js";
|
|
2
2
|
import { text } from "./respond.js";
|
|
3
3
|
import { sseResponse } from "./sse.js";
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
4
|
+
/**
|
|
5
|
+
* Request body cap (1 MiB) — shared by every Prompt-bearing wire surface (the control plane's dispatch imports it), so
|
|
6
|
+
* the two caps cannot drift apart.
|
|
7
|
+
*/
|
|
6
8
|
export const MAX_BODY_BYTES = 1 << 20;
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
9
|
+
/**
|
|
10
|
+
* A valid example request body for the invoke handler — lives HERE, next to the shape check it must satisfy, so the
|
|
11
|
+
* CLI's "try it" hint can't drift from the protocol.
|
|
12
|
+
*/
|
|
9
13
|
export const INVOKE_EXAMPLE_BODY = '{"session":"dev","text":"hello"}';
|
|
10
14
|
/**
|
|
11
|
-
* Fetch-shaped invoke handler.
|
|
12
|
-
*
|
|
15
|
+
* Fetch-shaped invoke handler.
|
|
16
|
+
*
|
|
17
|
+
* No CORS here: who may call this from a browser is decided once, before dispatch, in `channels/serve.ts`'s router
|
|
18
|
+
* — which is also the only layer that can answer the `OPTIONS` preflight, since this route registers only `POST`.
|
|
13
19
|
*/
|
|
14
20
|
export function createInvokeHandler(agent) {
|
|
15
21
|
return async (req) => {
|
|
16
22
|
if (req.method !== "POST")
|
|
17
23
|
return text("POST only\n", 405);
|
|
24
|
+
// Also applied by the host router over every unguarded route — kept HERE too because this handler is a
|
|
25
|
+
// PUBLIC standalone artifact (docs/overview.md mounts it as a Next.js route), and a handler you can mount
|
|
26
|
+
// anywhere has to be safe anywhere. The router's copy is what stops a route added later from forgetting.
|
|
27
|
+
const wrongType = refuseNonJsonBody(req);
|
|
28
|
+
if (wrongType)
|
|
29
|
+
return wrongType;
|
|
18
30
|
const body = await readBodyCapped(req, MAX_BODY_BYTES);
|
|
19
31
|
if ("tooLarge" in body)
|
|
20
32
|
return text("body too large\n", 413);
|
|
@@ -30,8 +42,6 @@ export function createInvokeHandler(agent) {
|
|
|
30
42
|
return text('need { "session": string, "text": string }\n', 400);
|
|
31
43
|
}
|
|
32
44
|
// INVOKE_EXAMPLE_BODY must keep satisfying this request shape.
|
|
33
|
-
// The OPTIONAL lineage extension (Scope): malformed values are a 400, not a silent drop — a
|
|
34
|
-
// caller that sent them meant them.
|
|
35
45
|
if (parentSession !== undefined && typeof parentSession !== "string") {
|
|
36
46
|
return text('"parentSession" must be a string\n', 400);
|
|
37
47
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `ENAMETOOLONG`. Left to fail there rather than truncated here: a digest suffix would buy a length
|
|
6
|
-
* no platform id approaches, at the cost of the readability this encoding exists to keep.
|
|
2
|
+
* THE inbound-attachment root inside a channel's state home. One spelling because three channels write here and a
|
|
3
|
+
* conversation id becomes a directory under it; nothing else reads it, and nothing removes it — an attachment is
|
|
4
|
+
* durable conversation data, and this directory grows with the agent's inbound traffic (docs/design/core.md §9).
|
|
7
5
|
*/
|
|
6
|
+
export declare const attachmentsDir: (stateHome: string) => string;
|
|
8
7
|
export declare function attachmentPath(filesDir: string, conversationId: string | number, fileName: string): {
|
|
9
8
|
dir: string;
|
|
10
9
|
name: string;
|
|
@@ -1,42 +1,18 @@
|
|
|
1
|
+
/** Where an inbound attachment lands: `<stateHome>/files/<conversation>/<file>`. */
|
|
2
|
+
import { join, resolve, sep } from "node:path";
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - The conversation is an id, and turning an arbitrary caller id into a storage name is a job this
|
|
7
|
-
* repo already answers by ENCODING it, never by rejecting it (`piSessionId` in
|
|
8
|
-
* engines/pi/session-store.ts, same shape: a character whitelist under a fixed prefix). An id is
|
|
9
|
-
* the caller's — a route returning `../..` gets an odd directory, not a failed turn, because the
|
|
10
|
-
* place to notice that id is broken is the reply it cannot deliver, not the disk.
|
|
11
|
-
* - The file name is not an id: it is read by a human and by the model, in a path this puts into the
|
|
12
|
-
* prompt. Encoding it would cost that, so it is reduced instead — losing the odd character rather
|
|
13
|
-
* than the readability. A leading dot survives: `.gitignore` is a name, not an escape, and nothing
|
|
14
|
-
* lists this directory, so hiding is not a property anyone here depends on.
|
|
15
|
-
*
|
|
16
|
-
* The encoding makes containment structural rather than checked: `encodeURIComponent` emits no path
|
|
17
|
-
* separator (`/` → `%2F`, `\` → `%5C`, `:` → `%3A`), and the prefix leaves no way to spell `.`, `..`
|
|
18
|
-
* or the empty string.
|
|
19
|
-
*
|
|
20
|
-
* It is lossless for a well-formed id, so `a/b` and `a_b` are different places. Two exceptions, both
|
|
21
|
-
* outside what a platform hands over: ids differing only in malformed UTF-16 (see the substitution
|
|
22
|
-
* below), and ids differing only in case on a case-insensitive filesystem.
|
|
23
|
-
*/
|
|
24
|
-
import { resolve, sep } from "node:path";
|
|
25
|
-
/**
|
|
26
|
-
* Attachment destination for one conversation. Never rejects an id — but the path it returns is not
|
|
27
|
-
* promised to be one the filesystem accepts: percent-encoding spends up to 9 bytes per character, so
|
|
28
|
-
* an id of ~29 CJK characters exceeds a 255-byte `NAME_MAX` and the caller's `mkdir` fails with
|
|
29
|
-
* `ENAMETOOLONG`. Left to fail there rather than truncated here: a digest suffix would buy a length
|
|
30
|
-
* no platform id approaches, at the cost of the readability this encoding exists to keep.
|
|
4
|
+
* THE inbound-attachment root inside a channel's state home. One spelling because three channels write here and a
|
|
5
|
+
* conversation id becomes a directory under it; nothing else reads it, and nothing removes it — an attachment is
|
|
6
|
+
* durable conversation data, and this directory grows with the agent's inbound traffic (docs/design/core.md §9).
|
|
31
7
|
*/
|
|
8
|
+
export const attachmentsDir = (stateHome) => join(stateHome, "files");
|
|
32
9
|
export function attachmentPath(filesDir, conversationId, fileName) {
|
|
33
|
-
// A lone surrogate makes `encodeURIComponent` throw `URIError`, and this function rejecting an id
|
|
34
|
-
//
|
|
35
|
-
// The `u` flag matches by code point, so a valid pair (an emoji in a route's id) is left alone.
|
|
10
|
+
// A lone surrogate makes `encodeURIComponent` throw `URIError`, and this function rejecting an id is the one thing
|
|
11
|
+
// it must not do.
|
|
36
12
|
const id = String(conversationId).replace(/\p{Surrogate}/gu, "\uFFFD");
|
|
37
13
|
const dir = resolve(filesDir, `c-${encodeURIComponent(id)}`);
|
|
38
|
-
// The name keeps its own check because it is NOT encoded: separators go, and `resolve` settles
|
|
39
|
-
//
|
|
14
|
+
// The name keeps its own check because it is NOT encoded: separators go, and `resolve` settles whether what is left
|
|
15
|
+
// (`..`, a Windows `D:foo`) still lands in `dir`.
|
|
40
16
|
const cleaned = fileName.replace(/[/\\]/g, "_");
|
|
41
17
|
const name = resolve(dir, cleaned).startsWith(dir + sep) ? cleaned : "file";
|
|
42
18
|
return { dir, name, path: resolve(dir, name) };
|
|
@@ -1,36 +1,29 @@
|
|
|
1
|
-
/** Per-message bound INSIDE that budget.
|
|
2
|
-
* allowance, so the job here is fairness, not fidelity: one rambler must not price out everyone who
|
|
3
|
-
* spoke after them. 280 against 4000 keeps at least ~14 messages in a full buffer. A referent — the
|
|
4
|
-
* single message the asker points at — is the opposite job and takes REFERENT_MAX_CODE_POINTS. */
|
|
1
|
+
/** Per-message bound INSIDE that budget. */
|
|
5
2
|
export declare const BUFFER_LINE_MAX_CHARS = 280;
|
|
6
|
-
/** How many buffered files and images (each, most recent first) a summon pulls in with the folded
|
|
7
|
-
* discussion — bounds the latency/token cost of "summarize the file from earlier" against a chatty
|
|
8
|
-
* group posting many attachments between summons. Skipped ones must be counted into the prompt
|
|
9
|
-
* note, so the model never sees an attachment reference it silently cannot open. Shared policy:
|
|
10
|
-
* each channel's attachment collector caps against this. */
|
|
3
|
+
/** How many buffered files and images (each, most recent first) a summon pulls in with the folded discussion. */
|
|
11
4
|
export declare const BUFFER_ATTACH_MAX = 3;
|
|
12
5
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* not depend on which chat platform delivered it, and three copies of the literal is how that drifts.
|
|
16
|
-
*
|
|
17
|
-
* A channel that folds a SECOND source (feishu's originating room) labels that one itself — it is a
|
|
18
|
-
* different claim about a different place, not this block with another name.
|
|
6
|
+
* Keep the newest {@link BUFFER_ATTACH_MAX} refs of one kind. The overflow is COUNTED, not silently dropped: the
|
|
7
|
+
* prompt note tells the model what it is not holding, so it cannot pretend to have read it.
|
|
19
8
|
*/
|
|
9
|
+
export declare function capBufferedRefs<R>(refs: R[]): {
|
|
10
|
+
kept: R[];
|
|
11
|
+
skipped: number;
|
|
12
|
+
};
|
|
13
|
+
/** The folded discussion as it reaches the model — the prompt block, or nothing when the buffer is empty. */
|
|
20
14
|
export declare function discussionBlock(text: string): string;
|
|
21
15
|
export interface ContextBuffer<E> {
|
|
22
|
-
/** Record an un-summoned message.
|
|
16
|
+
/** Record an un-summoned message. */
|
|
23
17
|
push(placeKey: string, entry: E): void;
|
|
24
18
|
/** Render the fold text and snapshot the consumed entries (see the module header's consume protocol). */
|
|
25
19
|
peek(placeKey: string): {
|
|
26
20
|
text: string;
|
|
27
21
|
consumed: E[];
|
|
28
22
|
};
|
|
29
|
-
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
* it. Post-ACK: a failed write is logged, never thrown (it must not abort the turn's delivery). */
|
|
23
|
+
/**
|
|
24
|
+
* Remove exactly `consumed` (by identity) — call on the turn's `completed` event, when the folded discussion
|
|
25
|
+
* provably lives in the durable session.
|
|
26
|
+
*/
|
|
34
27
|
commit(placeKey: string, consumed: E[]): void;
|
|
35
28
|
}
|
|
36
29
|
export declare function createContextBuffer<E>(options: {
|
|
@@ -1,52 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generic durable context buffer — the SHARED mechanics behind each stateful channel's
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* answered turn in that place, so a summoned agent has the discussion it didn't see turn-by-turn.
|
|
2
|
+
* Generic durable context buffer — the SHARED mechanics behind each stateful channel's "un-summoned group discussion"
|
|
3
|
+
* module (telegram/feishu/slack `context-buffer.ts`). Generic over the entry shape: the channel supplies its entry
|
|
4
|
+
* type, the shape validator, the fold-line renderer and its log label.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* fold-line renderer, and its log label. What stays per channel: the entry type itself, place-key
|
|
11
|
-
* derivation, and buffered-attachment selection (platform resource shapes are real differences).
|
|
12
|
-
*
|
|
13
|
-
* DURABLE, with the consume protocol every channel inherits:
|
|
14
|
-
* - `push` persists synchronously BEFORE the transport ACK (an ACKed delivery is not redelivered,
|
|
15
|
-
* so ACK-then-persist would be a silent-loss window): a throw becomes the webhook's 500 and the
|
|
16
|
-
* platform redelivers once the disk recovers — staged on a copy and rolled back on a failed
|
|
17
|
-
* write, so the redelivery does not double-append the entry already in memory.
|
|
6
|
+
* The consume protocol every channel inherits:
|
|
7
|
+
* - `push` persists synchronously BEFORE the transport ACK (an ACKed delivery is not redelivered, so ACK-then-persist
|
|
8
|
+
* would be a silent-loss window): a throw becomes the webhook's 500 and the platform redelivers.
|
|
18
9
|
* - `peek` renders WITHOUT clearing and snapshots exactly which entries it consumed.
|
|
19
|
-
* - `commit` removes only that snapshot, by object identity, on the turn's `completed` — so a
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
10
|
+
* - `commit` removes only that snapshot, by object identity, on the turn's `completed` — so a failure or crash before
|
|
11
|
+
* `completed` leaves the discussion intact, and a message arriving mid-turn survives for the next answered turn.
|
|
12
|
+
*
|
|
13
|
+
* State files are an IO boundary: valid JSON of the WRONG shape degrades exactly like a corrupt file (warn + empty),
|
|
14
|
+
* never flowing in as trusted data.
|
|
23
15
|
*/
|
|
24
16
|
import { log } from "../../log.js";
|
|
25
17
|
import { loadStateFile, saveStateFile } from "./state.js";
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Char budget for the per-place buffer — bounds the cost of folding it into a prompt; when exceeded the OLDEST
|
|
20
|
+
* un-summoned messages are dropped. Not a time window: a quiet group keeps its sparse-but-relevant lines.
|
|
21
|
+
*/
|
|
30
22
|
const BUFFER_MAX_CHARS = 4000;
|
|
31
|
-
/** Per-message bound INSIDE that budget.
|
|
32
|
-
* allowance, so the job here is fairness, not fidelity: one rambler must not price out everyone who
|
|
33
|
-
* spoke after them. 280 against 4000 keeps at least ~14 messages in a full buffer. A referent — the
|
|
34
|
-
* single message the asker points at — is the opposite job and takes REFERENT_MAX_CODE_POINTS. */
|
|
23
|
+
/** Per-message bound INSIDE that budget. */
|
|
35
24
|
export const BUFFER_LINE_MAX_CHARS = 280;
|
|
36
|
-
/** How many buffered files and images (each, most recent first) a summon pulls in with the folded
|
|
37
|
-
* discussion — bounds the latency/token cost of "summarize the file from earlier" against a chatty
|
|
38
|
-
* group posting many attachments between summons. Skipped ones must be counted into the prompt
|
|
39
|
-
* note, so the model never sees an attachment reference it silently cannot open. Shared policy:
|
|
40
|
-
* each channel's attachment collector caps against this. */
|
|
25
|
+
/** How many buffered files and images (each, most recent first) a summon pulls in with the folded discussion. */
|
|
41
26
|
export const BUFFER_ATTACH_MAX = 3;
|
|
42
27
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* not depend on which chat platform delivered it, and three copies of the literal is how that drifts.
|
|
46
|
-
*
|
|
47
|
-
* A channel that folds a SECOND source (feishu's originating room) labels that one itself — it is a
|
|
48
|
-
* different claim about a different place, not this block with another name.
|
|
28
|
+
* Keep the newest {@link BUFFER_ATTACH_MAX} refs of one kind. The overflow is COUNTED, not silently dropped: the
|
|
29
|
+
* prompt note tells the model what it is not holding, so it cannot pretend to have read it.
|
|
49
30
|
*/
|
|
31
|
+
export function capBufferedRefs(refs) {
|
|
32
|
+
return { kept: refs.slice(-BUFFER_ATTACH_MAX), skipped: Math.max(0, refs.length - BUFFER_ATTACH_MAX) };
|
|
33
|
+
}
|
|
34
|
+
/** The folded discussion as it reaches the model — the prompt block, or nothing when the buffer is empty. */
|
|
50
35
|
export function discussionBlock(text) {
|
|
51
36
|
return text ? `[recent group discussion:\n${text}\n]\n\n` : "";
|
|
52
37
|
}
|
|
@@ -83,6 +68,8 @@ export function createContextBuffer(options) {
|
|
|
83
68
|
persist();
|
|
84
69
|
}
|
|
85
70
|
catch (error) {
|
|
71
|
+
// Staged on a copy, rolled back on a failed write, then rethrown: the throw becomes the webhook's 500 and the
|
|
72
|
+
// platform redelivers, so an entry left in memory here would be appended a second time.
|
|
86
73
|
if (previous)
|
|
87
74
|
buffers.set(placeKey, previous);
|
|
88
75
|
else
|