@fastagent-sh/fastagent 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -12
- package/dist/agent.d.ts +24 -37
- package/dist/agent.js +7 -15
- package/dist/atomic-write.d.ts +13 -17
- package/dist/atomic-write.js +18 -29
- package/dist/bind.d.ts +12 -22
- package/dist/bind.js +18 -27
- package/dist/channel.d.ts +11 -33
- package/dist/channels/agentcore-limits.d.ts +2 -13
- package/dist/channels/agentcore-limits.js +2 -13
- package/dist/channels/agentcore-protocol.d.ts +67 -58
- package/dist/channels/agentcore-protocol.js +23 -15
- package/dist/channels/agentcore-service.d.ts +19 -33
- package/dist/channels/agentcore-service.js +176 -59
- package/dist/channels/agentcore.d.ts +39 -33
- package/dist/channels/agentcore.js +90 -224
- package/dist/channels/body.d.ts +15 -2
- package/dist/channels/body.js +24 -2
- package/dist/channels/busy.d.ts +1 -21
- package/dist/channels/busy.js +2 -39
- package/dist/channels/control.d.ts +17 -62
- package/dist/channels/control.js +74 -182
- package/dist/channels/define-channel.d.ts +39 -0
- package/dist/channels/define-channel.js +9 -0
- package/dist/channels/discover.d.ts +10 -31
- package/dist/channels/discover.js +37 -31
- package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
- package/dist/channels/feishu/bootstrap-token.js +7 -23
- package/dist/channels/feishu/card.d.ts +14 -28
- package/dist/channels/feishu/card.js +16 -30
- package/dist/channels/feishu/cloud.d.ts +1 -5
- package/dist/channels/feishu/context-buffer.d.ts +4 -23
- package/dist/channels/feishu/context-buffer.js +8 -31
- package/dist/channels/feishu/crypto.d.ts +10 -5
- package/dist/channels/feishu/crypto.js +12 -19
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu-api.js +5 -4
- package/dist/channels/feishu/feishu.d.ts +4 -10
- package/dist/channels/feishu/feishu.js +113 -227
- package/dist/channels/feishu/invoke-turn.d.ts +5 -4
- package/dist/channels/feishu/invoke-turn.js +36 -54
- package/dist/channels/feishu/model.d.ts +4 -21
- package/dist/channels/feishu/model.js +1 -8
- package/dist/channels/feishu/normalize.d.ts +2 -10
- package/dist/channels/feishu/normalize.js +7 -42
- package/dist/channels/feishu/parse.d.ts +9 -29
- package/dist/channels/feishu/parse.js +8 -26
- package/dist/channels/feishu/preview.d.ts +12 -14
- package/dist/channels/feishu/preview.js +108 -207
- package/dist/channels/feishu/register-app.d.ts +8 -23
- package/dist/channels/feishu/register-app.js +7 -20
- package/dist/channels/feishu/register-webhook.d.ts +2 -9
- package/dist/channels/feishu/register-webhook.js +9 -33
- package/dist/channels/feishu/scaffold/channel.ts +27 -19
- package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/feishu/setup-mode.d.ts +11 -36
- package/dist/channels/feishu/setup-mode.js +7 -26
- package/dist/channels/feishu/ws-ingress.d.ts +1 -2
- package/dist/channels/feishu/ws-ingress.js +6 -8
- package/dist/channels/http.d.ts +15 -15
- package/dist/channels/http.js +19 -9
- package/dist/channels/kit/attachment-path.d.ts +4 -5
- package/dist/channels/kit/attachment-path.js +10 -34
- package/dist/channels/kit/context-buffer.d.ts +14 -21
- package/dist/channels/kit/context-buffer.js +25 -38
- package/dist/channels/kit/delivery.d.ts +35 -0
- package/dist/channels/kit/delivery.js +129 -0
- package/dist/channels/kit/event-stream.d.ts +5 -0
- package/dist/channels/kit/event-stream.js +29 -0
- package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
- package/dist/channels/kit/invoke-turn-kit.js +89 -32
- package/dist/channels/kit/preview-kit.d.ts +18 -72
- package/dist/channels/kit/preview-kit.js +22 -88
- package/dist/channels/kit/signature.d.ts +3 -9
- package/dist/channels/kit/signature.js +3 -9
- package/dist/channels/kit/state.d.ts +1 -2
- package/dist/channels/kit/state.js +5 -14
- package/dist/channels/kit/stop-command.d.ts +1 -2
- package/dist/channels/kit/stop-command.js +6 -9
- package/dist/channels/kit/tasks.d.ts +1 -1
- package/dist/channels/kit/tasks.js +8 -21
- package/dist/channels/kit/text.d.ts +2 -16
- package/dist/channels/kit/text.js +23 -31
- package/dist/channels/kit/thread-participants.d.ts +4 -17
- package/dist/channels/kit/thread-participants.js +8 -55
- package/dist/channels/kit/transport.d.ts +26 -0
- package/dist/channels/kit/transport.js +20 -0
- package/dist/channels/kit/turn-queue.d.ts +7 -11
- package/dist/channels/kit/turn-queue.js +21 -39
- package/dist/channels/kit/turn-runner.d.ts +32 -18
- package/dist/channels/kit/turn-runner.js +98 -53
- package/dist/channels/kit/turn-store.d.ts +32 -32
- package/dist/channels/kit/turn-store.js +45 -88
- package/dist/channels/lark/lark.d.ts +1 -5
- package/dist/channels/lark/onboard.d.ts +3 -12
- package/dist/channels/lark/onboard.js +3 -13
- package/dist/channels/lark/scaffold/channel.ts +27 -19
- package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/registration.d.ts +9 -35
- package/dist/channels/registration.js +6 -37
- package/dist/channels/secret.js +2 -5
- package/dist/channels/serve.d.ts +47 -44
- package/dist/channels/serve.js +165 -101
- package/dist/channels/slack/config-api.d.ts +4 -27
- package/dist/channels/slack/config-api.js +5 -34
- package/dist/channels/slack/context-buffer.d.ts +2 -3
- package/dist/channels/slack/context-buffer.js +5 -5
- package/dist/channels/slack/invoke-turn.d.ts +3 -1
- package/dist/channels/slack/invoke-turn.js +25 -34
- package/dist/channels/slack/manifest.js +1 -3
- package/dist/channels/slack/model.d.ts +1 -2
- package/dist/channels/slack/model.js +1 -1
- package/dist/channels/slack/onboard.d.ts +4 -3
- package/dist/channels/slack/onboard.js +8 -11
- package/dist/channels/slack/onboarding-state.d.ts +3 -5
- package/dist/channels/slack/onboarding-state.js +9 -6
- package/dist/channels/slack/parse.d.ts +4 -13
- package/dist/channels/slack/parse.js +7 -28
- package/dist/channels/slack/preview.d.ts +12 -2
- package/dist/channels/slack/preview.js +292 -269
- package/dist/channels/slack/reaction.d.ts +11 -3
- package/dist/channels/slack/reaction.js +19 -8
- package/dist/channels/slack/register-webhook.js +4 -7
- package/dist/channels/slack/scaffold/channel.ts +20 -13
- package/dist/channels/slack/scaffold/slack-send.ts +2 -2
- package/dist/channels/slack/setup-server.d.ts +1 -5
- package/dist/channels/slack/setup-server.js +1 -5
- package/dist/channels/slack/shared-api.d.ts +1 -4
- package/dist/channels/slack/shared-api.js +2 -13
- package/dist/channels/slack/slack-api.d.ts +2 -1
- package/dist/channels/slack/slack-api.js +6 -4
- package/dist/channels/slack/slack.d.ts +11 -14
- package/dist/channels/slack/slack.js +121 -122
- package/dist/channels/sse.d.ts +1 -1
- package/dist/channels/sse.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +7 -22
- package/dist/channels/telegram/context-buffer.js +6 -23
- package/dist/channels/telegram/invoke-turn.d.ts +7 -7
- package/dist/channels/telegram/invoke-turn.js +24 -43
- package/dist/channels/telegram/parse.d.ts +24 -51
- package/dist/channels/telegram/parse.js +32 -84
- package/dist/channels/telegram/preview.d.ts +10 -1
- package/dist/channels/telegram/preview.js +79 -100
- package/dist/channels/telegram/register-webhook.js +5 -23
- package/dist/channels/telegram/scaffold/channel.ts +25 -18
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
- package/dist/channels/telegram/telegram-api.d.ts +4 -3
- package/dist/channels/telegram/telegram-api.js +9 -7
- package/dist/channels/telegram/telegram.d.ts +4 -18
- package/dist/channels/telegram/telegram.js +38 -94
- package/dist/channels/telegram/turn-store.d.ts +11 -14
- package/dist/channels/telegram/turn-store.js +8 -6
- package/dist/channels/wait-health.d.ts +4 -6
- package/dist/channels/wait-health.js +10 -14
- package/dist/cli/add-feishu.d.ts +2 -12
- package/dist/cli/add-feishu.js +24 -71
- package/dist/cli/add-slack.d.ts +5 -3
- package/dist/cli/add-slack.js +53 -76
- package/dist/cli/auth-view.d.ts +2 -16
- package/dist/cli/auth-view.js +2 -16
- package/dist/cli/commands/add.js +25 -40
- package/dist/cli/commands/chat.d.ts +0 -1
- package/dist/cli/commands/chat.js +5 -6
- package/dist/cli/commands/deploy/agentcore.js +60 -65
- package/dist/cli/commands/deploy/docker.js +50 -23
- package/dist/cli/commands/deploy/fly.js +23 -61
- package/dist/cli/commands/deploy/railway.js +23 -18
- package/dist/cli/commands/deploy/shared.d.ts +38 -57
- package/dist/cli/commands/deploy/shared.js +40 -59
- package/dist/cli/commands/deploy.d.ts +10 -27
- package/dist/cli/commands/deploy.js +38 -60
- package/dist/cli/commands/destroy.d.ts +4 -0
- package/dist/cli/commands/destroy.js +56 -0
- package/dist/cli/commands/dev.d.ts +2 -1
- package/dist/cli/commands/dev.js +20 -23
- package/dist/cli/commands/info.d.ts +0 -2
- package/dist/cli/commands/info.js +83 -49
- package/dist/cli/commands/init.d.ts +1 -2
- package/dist/cli/commands/init.js +16 -55
- package/dist/cli/commands/invoke.d.ts +0 -1
- package/dist/cli/commands/invoke.js +5 -7
- package/dist/cli/commands/login.d.ts +2 -1
- package/dist/cli/commands/login.js +33 -38
- package/dist/cli/commands/logs.js +3 -4
- package/dist/cli/commands/models.js +1 -5
- package/dist/cli/commands/routine-run.d.ts +6 -0
- package/dist/cli/commands/routine-run.js +47 -0
- package/dist/cli/commands/routine.d.ts +25 -0
- package/dist/cli/commands/routine.js +122 -0
- package/dist/cli/commands/start.d.ts +31 -3
- package/dist/cli/commands/start.js +131 -81
- package/dist/cli/commands/tool.js +26 -12
- package/dist/cli/fail.d.ts +13 -23
- package/dist/cli/fail.js +20 -23
- package/dist/cli/invoke-stream.d.ts +1 -8
- package/dist/cli/kernel.d.ts +6 -38
- package/dist/cli/kernel.js +14 -77
- package/dist/cli/models-view.d.ts +1 -9
- package/dist/cli/models-view.js +11 -22
- package/dist/cli/program.d.ts +5 -6
- package/dist/cli/program.js +120 -157
- package/dist/cli/serve.d.ts +57 -41
- package/dist/cli/serve.js +94 -121
- package/dist/cli/shared.d.ts +15 -61
- package/dist/cli/shared.js +55 -181
- package/dist/cli.js +1 -8
- package/dist/collect.d.ts +6 -0
- package/dist/collect.js +6 -0
- package/dist/core.d.ts +4 -1
- package/dist/core.js +9 -7
- package/dist/declared-secrets.d.ts +55 -0
- package/dist/declared-secrets.js +88 -0
- package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
- package/dist/deploy/agentcore/aws-cli.js +64 -0
- package/dist/deploy/agentcore/destroy.d.ts +51 -0
- package/dist/deploy/agentcore/destroy.js +273 -0
- package/dist/deploy/agentcore/forwarder.js +28 -79
- package/dist/deploy/agentcore/logs.d.ts +2 -18
- package/dist/deploy/agentcore/logs.js +27 -51
- package/dist/deploy/agentcore/plan.d.ts +85 -105
- package/dist/deploy/agentcore/plan.js +152 -314
- package/dist/deploy/agentcore/run.d.ts +28 -28
- package/dist/deploy/agentcore/run.js +227 -306
- package/dist/deploy/agentcore/zip.d.ts +2 -15
- package/dist/deploy/agentcore/zip.js +3 -17
- package/dist/deploy/build-context.d.ts +21 -0
- package/dist/deploy/build-context.js +137 -0
- package/dist/deploy/channel-ingress.d.ts +26 -39
- package/dist/deploy/channel-ingress.js +57 -28
- package/dist/deploy/container.d.ts +20 -34
- package/dist/deploy/container.js +49 -100
- package/dist/deploy/docker/plan.d.ts +12 -11
- package/dist/deploy/docker/plan.js +43 -24
- package/dist/deploy/docker/run.d.ts +22 -25
- package/dist/deploy/docker/run.js +39 -37
- package/dist/deploy/fly/plan.d.ts +15 -44
- package/dist/deploy/fly/plan.js +41 -73
- package/dist/deploy/fly/run.d.ts +16 -51
- package/dist/deploy/fly/run.js +42 -95
- package/dist/deploy/hosts.d.ts +24 -3
- package/dist/deploy/hosts.js +24 -3
- package/dist/deploy/preflight.d.ts +47 -41
- package/dist/deploy/preflight.js +282 -383
- package/dist/deploy/railway/plan.d.ts +16 -42
- package/dist/deploy/railway/plan.js +42 -60
- package/dist/deploy/railway/run.d.ts +30 -68
- package/dist/deploy/railway/run.js +86 -100
- package/dist/deploy/registration-gate.d.ts +6 -13
- package/dist/deploy/residency.d.ts +37 -0
- package/dist/deploy/residency.js +24 -0
- package/dist/deploy/runner.d.ts +10 -9
- package/dist/deploy/runner.js +7 -18
- package/dist/deploy/secrets.d.ts +40 -41
- package/dist/deploy/secrets.js +117 -103
- package/dist/deploy/workspace.d.ts +50 -0
- package/dist/deploy/workspace.js +197 -0
- package/dist/dev-supervisor.d.ts +1 -9
- package/dist/dev-supervisor.js +21 -64
- package/dist/effect-port.d.ts +50 -0
- package/dist/effect-port.js +77 -0
- package/dist/engines/pi/agent-session-factory.d.ts +50 -69
- package/dist/engines/pi/agent-session-factory.js +249 -146
- package/dist/engines/pi/auth.d.ts +15 -11
- package/dist/engines/pi/auth.js +96 -99
- package/dist/engines/pi/chat.d.ts +1 -5
- package/dist/engines/pi/chat.js +2 -14
- package/dist/engines/pi/config.d.ts +53 -51
- package/dist/engines/pi/config.js +83 -92
- package/dist/engines/pi/create.d.ts +60 -75
- package/dist/engines/pi/create.js +108 -141
- package/dist/engines/pi/definition.d.ts +14 -58
- package/dist/engines/pi/definition.js +21 -85
- package/dist/engines/pi/invoke-session.d.ts +5 -25
- package/dist/engines/pi/invoke-session.js +209 -244
- package/dist/engines/pi/login.d.ts +95 -35
- package/dist/engines/pi/login.js +259 -107
- package/dist/engines/pi/machine.d.ts +31 -0
- package/dist/engines/pi/machine.js +106 -0
- package/dist/engines/pi/models.d.ts +70 -89
- package/dist/engines/pi/models.js +188 -81
- package/dist/engines/pi/open.d.ts +65 -53
- package/dist/engines/pi/open.js +129 -69
- package/dist/engines/pi/report.d.ts +3 -12
- package/dist/engines/pi/report.js +2 -16
- package/dist/engines/pi/search-tools.d.ts +1 -9
- package/dist/engines/pi/search-tools.js +26 -57
- package/dist/engines/pi/service.d.ts +2 -16
- package/dist/engines/pi/service.js +2 -16
- package/dist/engines/pi/session-builder.d.ts +5 -9
- package/dist/engines/pi/session-builder.js +44 -82
- package/dist/engines/pi/session-control.js +286 -269
- package/dist/engines/pi/session-effects.d.ts +14 -0
- package/dist/engines/pi/session-effects.js +30 -0
- package/dist/engines/pi/session-inheritance.d.ts +9 -18
- package/dist/engines/pi/session-inheritance.js +103 -160
- package/dist/engines/pi/session-markers.d.ts +31 -30
- package/dist/engines/pi/session-markers.js +26 -25
- package/dist/engines/pi/session-settings.d.ts +6 -22
- package/dist/engines/pi/session-settings.js +10 -21
- package/dist/engines/pi/session-store.d.ts +26 -92
- package/dist/engines/pi/session-store.js +96 -310
- package/dist/engines/pi/tool-context.d.ts +17 -45
- package/dist/engines/pi/tool-context.js +7 -26
- package/dist/engines/pi/tool.d.ts +45 -36
- package/dist/engines/pi/tool.js +43 -57
- package/dist/engines/pi/turn-kit.d.ts +14 -52
- package/dist/engines/pi/turn-kit.js +18 -67
- package/dist/engines/pi/wake-tool.d.ts +2 -10
- package/dist/engines/pi/wake-tool.js +7 -27
- package/dist/env.d.ts +27 -28
- package/dist/env.js +45 -50
- package/dist/index.js +0 -2
- package/dist/lark.d.ts +1 -2
- package/dist/lark.js +1 -2
- package/dist/loader.d.ts +14 -55
- package/dist/loader.js +24 -43
- package/dist/log.d.ts +2 -12
- package/dist/log.js +3 -24
- package/dist/node.d.ts +2 -15
- package/dist/node.js +2 -18
- package/dist/observe.d.ts +2 -8
- package/dist/open-url.d.ts +1 -1
- package/dist/open-url.js +1 -1
- package/dist/paths.d.ts +70 -134
- package/dist/paths.js +165 -232
- package/dist/pi.d.ts +4 -6
- package/dist/pi.js +3 -3
- package/dist/proxy.d.ts +16 -6
- package/dist/proxy.js +52 -16
- package/dist/runtime.d.ts +18 -12
- package/dist/runtime.js +18 -11
- package/dist/scaffold/add-channel.d.ts +14 -31
- package/dist/scaffold/add-channel.js +48 -112
- package/dist/scaffold/init.d.ts +9 -31
- package/dist/scaffold/init.js +45 -138
- package/dist/scaffold/templates/env.example +6 -4
- package/dist/scaffold/templates/fastagent.config.ts +36 -0
- package/dist/scaffold/templates.d.ts +3 -3
- package/dist/scaffold/templates.js +5 -8
- package/dist/scaffold/vendor-skill.d.ts +2 -5
- package/dist/scaffold/vendor-skill.js +19 -29
- package/dist/schedule/cron.d.ts +9 -5
- package/dist/schedule/cron.js +9 -11
- package/dist/schedule/discover.d.ts +12 -12
- package/dist/schedule/discover.js +43 -34
- package/dist/schedule/routine.d.ts +43 -0
- package/dist/schedule/routine.js +32 -0
- package/dist/schedule/run.d.ts +55 -0
- package/dist/schedule/run.js +146 -0
- package/dist/schedule/scheduler.d.ts +44 -55
- package/dist/schedule/scheduler.js +291 -265
- package/dist/schedule/state.d.ts +105 -10
- package/dist/schedule/state.js +293 -19
- package/dist/schedule/wake-alarm.d.ts +10 -23
- package/dist/schedule/wake-alarm.js +91 -121
- package/dist/schedule/wakeups.d.ts +23 -39
- package/dist/schedule/wakeups.js +33 -75
- package/dist/secrets-gate.d.ts +32 -0
- package/dist/secrets-gate.js +33 -0
- package/dist/service.d.ts +137 -98
- package/dist/service.js +250 -252
- package/dist/session-remote.d.ts +33 -44
- package/dist/session-remote.js +203 -192
- package/dist/session.d.ts +153 -228
- package/dist/session.js +33 -55
- package/dist/tunnel.d.ts +8 -36
- package/dist/tunnel.js +24 -65
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +10 -16
- package/dist/channels/agentcore-state.d.ts +0 -77
- package/dist/channels/agentcore-state.js +0 -276
- package/dist/channels/github/github.d.ts +0 -31
- package/dist/channels/github/github.js +0 -89
- package/dist/channels/github/scaffold/channel.ts +0 -25
- package/dist/cli/commands/attach.d.ts +0 -108
- package/dist/cli/commands/attach.js +0 -637
- package/dist/cli/commands/fire.d.ts +0 -7
- package/dist/cli/commands/fire.js +0 -38
- package/dist/cli/commands/schedule.d.ts +0 -12
- package/dist/cli/commands/schedule.js +0 -87
- package/dist/github.d.ts +0 -3
- package/dist/github.js +0 -2
- package/dist/scaffold/templates/fastagent.config.mjs +0 -23
- package/dist/schedule/audit.d.ts +0 -18
- package/dist/schedule/audit.js +0 -57
- package/dist/schedule/schedule.d.ts +0 -35
- package/dist/schedule/schedule.js +0 -23
|
@@ -1,54 +1,33 @@
|
|
|
1
1
|
import type { ChannelContext, LongConnection, Routes } from "../channel.ts";
|
|
2
2
|
import { type ModuleLoadFailure } from "../loader.ts";
|
|
3
|
-
|
|
3
|
+
import { type DeclaredSecret } from "../declared-secrets.ts";
|
|
4
|
+
/** A dropped route: two channels claim the same key. */
|
|
4
5
|
export interface ChannelCollision {
|
|
5
6
|
route: string;
|
|
6
7
|
source: string;
|
|
7
8
|
}
|
|
8
|
-
/** A long-connection module bound to the same context route factories receive.
|
|
9
|
+
/** A long-connection module bound to the same context route factories receive. */
|
|
9
10
|
export interface LoadedLongConnectionChannel {
|
|
10
11
|
name: string;
|
|
11
12
|
connect(signal: AbortSignal): LongConnection;
|
|
12
13
|
}
|
|
13
|
-
/**
|
|
14
|
-
* HOW A CHANNEL IS REACHED — the authored structural fact, and the ONE shape it travels in.
|
|
15
|
-
*
|
|
16
|
-
* A webhook channel is reached at a URL someone must set; a long-connection channel dials out, so
|
|
17
|
-
* there is no URL and setting one breaks it (Telegram answers `getUpdates` with 409 once a webhook
|
|
18
|
-
* exists). Everything downstream — which secrets to carry, what the runbook says, what `--run` and
|
|
19
|
-
* `--tunnel` register — is a question about THIS.
|
|
20
|
-
*
|
|
21
|
-
* It is one list because it used to be three (`channels` + `routeChannels` + `longConnectionChannels`,
|
|
22
|
-
* two of them including custom channels and one not), and every consumer re-derived the answer from
|
|
23
|
-
* whichever pair it happened to hold. Two deploys shipped a webhook for a long-connection channel
|
|
24
|
-
* that way. A list of pairs cannot be recombined wrongly, and a consumer that needs a subset asks for
|
|
25
|
-
* it here rather than trusting its caller to have filtered.
|
|
26
|
-
*/
|
|
14
|
+
/** HOW A CHANNEL IS REACHED — the authored structural fact, and the ONE shape it travels in. */
|
|
27
15
|
export type ChannelIngress = "webhook" | "long-connection";
|
|
28
|
-
/** One channel a directory declares, with the ingress its module shape says it has.
|
|
29
|
-
* basename, which is a {@link ChannelKind} for the first-party ones and anything for a custom one. */
|
|
16
|
+
/** One channel a directory declares, with the ingress its module shape says it has. */
|
|
30
17
|
export interface DeclaredChannel {
|
|
31
18
|
name: string;
|
|
32
19
|
ingress: ChannelIngress;
|
|
33
20
|
}
|
|
34
|
-
/** Declared channels from basenames that share one ingress
|
|
35
|
-
* (a long-connection channel mounts no HTTP route, so every route IS a webhook channel), and fixtures. */
|
|
21
|
+
/** Declared channels from basenames that share one ingress. */
|
|
36
22
|
export declare function declaredChannels(names: readonly string[], ingress?: ChannelIngress): DeclaredChannel[];
|
|
37
|
-
/**
|
|
38
|
-
* Import channel files without mounting route factories or opening connections. Deployment needs only
|
|
39
|
-
* the authored structural fact: function exports are webhook channels; `{ connect() }` exports are
|
|
40
|
-
* long-connection channels. There is no second ingress/lifecycle declaration to keep in sync.
|
|
41
|
-
*/
|
|
23
|
+
/** Import channel files without mounting route factories or opening connections. */
|
|
42
24
|
export declare function inspectChannels(dir: string): Promise<{
|
|
43
25
|
channels: DeclaredChannel[];
|
|
26
|
+
/** What each channel declared through {@link defineChannel}, BY CHANNEL NAME — the only way a
|
|
27
|
+
* CUSTOM channel's credentials can reach a deploy, since nothing else here can name them. */
|
|
28
|
+
secrets: Map<string, DeclaredSecret[]>;
|
|
44
29
|
failures: ModuleLoadFailure[];
|
|
45
30
|
}>;
|
|
46
|
-
/**
|
|
47
|
-
* Channel file basenames under `<dir>/channels/` — the authoring view (`fastagent info`), which lists
|
|
48
|
-
* WITHOUT importing. A symlinked channels directory must remain inside the agent dir.
|
|
49
|
-
*/
|
|
50
|
-
export declare function discoverChannelFiles(dir: string): Promise<string[]>;
|
|
51
|
-
/** Discover, validate, and bind all channel modules. No long connection is opened here; the CLI owns it. */
|
|
52
31
|
export declare function loadChannels(dir: string, ctx: ChannelContext): Promise<{
|
|
53
32
|
routes: Routes;
|
|
54
33
|
longConnections: LoadedLongConnectionChannel[];
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Channel discovery (the N axis, filesystem form). A channel file default-exports either the existing
|
|
3
|
-
* route factory `(ctx) => Routes`, or an explicit long-connection module `{ name, connect(ctx, signal) }`.
|
|
4
|
-
*
|
|
5
|
-
* Engine-neutral, and living here rather than under `engines/` because of it: reading `channels/*.ts`
|
|
6
|
-
* is the Channel contract plus a directory, with no engine in sight.
|
|
7
|
-
*/
|
|
1
|
+
/** Channel discovery (the N axis, filesystem form). */
|
|
8
2
|
import { isAbsolute, join } from "node:path";
|
|
9
3
|
import { assertRouteKey, routeKeysConflict } from "./serve.js";
|
|
10
|
-
import { loadModuleDir
|
|
4
|
+
import { loadModuleDir } from "../loader.js";
|
|
5
|
+
import { readSecretDeclaration } from "../declared-secrets.js";
|
|
6
|
+
import { gateSecrets } from "../secrets-gate.js";
|
|
11
7
|
import { assertInsideAgentDir } from "../paths.js";
|
|
12
8
|
function longConnectionModule(value) {
|
|
13
9
|
return value !== null && typeof value === "object" && typeof value.connect === "function";
|
|
@@ -17,47 +13,38 @@ function validateLongConnectionModule(value, label) {
|
|
|
17
13
|
throw new Error(`${label}: long-connection channel name must be a non-empty string`);
|
|
18
14
|
}
|
|
19
15
|
}
|
|
20
|
-
/** Declared channels from basenames that share one ingress
|
|
21
|
-
* (a long-connection channel mounts no HTTP route, so every route IS a webhook channel), and fixtures. */
|
|
16
|
+
/** Declared channels from basenames that share one ingress. */
|
|
22
17
|
export function declaredChannels(names, ingress = "webhook") {
|
|
23
18
|
return names.map((name) => ({ name, ingress }));
|
|
24
19
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Import channel files without mounting route factories or opening connections. Deployment needs only
|
|
27
|
-
* the authored structural fact: function exports are webhook channels; `{ connect() }` exports are
|
|
28
|
-
* long-connection channels. There is no second ingress/lifecycle declaration to keep in sync.
|
|
29
|
-
*/
|
|
20
|
+
/** Import channel files without mounting route factories or opening connections. */
|
|
30
21
|
export async function inspectChannels(dir) {
|
|
31
22
|
await assertInsideAgentDir(dir, "channels");
|
|
32
23
|
const { modules, failures } = await loadModuleDir(join(dir, "channels"));
|
|
33
24
|
const channels = [];
|
|
25
|
+
const secrets = new Map();
|
|
34
26
|
for (const { name, label, file, mod } of modules) {
|
|
35
27
|
try {
|
|
28
|
+
const declaration = readSecretDeclaration(mod.default, label);
|
|
29
|
+
if (declaration.error !== undefined)
|
|
30
|
+
throw new Error(declaration.error);
|
|
36
31
|
if (typeof mod.default === "function") {
|
|
37
32
|
channels.push({ name, ingress: "webhook" });
|
|
38
|
-
continue;
|
|
39
33
|
}
|
|
40
|
-
if (longConnectionModule(mod.default)) {
|
|
34
|
+
else if (longConnectionModule(mod.default)) {
|
|
41
35
|
validateLongConnectionModule(mod.default, label);
|
|
42
36
|
channels.push({ name, ingress: "long-connection" });
|
|
43
|
-
continue;
|
|
44
37
|
}
|
|
45
|
-
|
|
38
|
+
else {
|
|
39
|
+
throw new Error(`${label} must default-export (ctx) => Routes or { name, connect(ctx, signal) }`);
|
|
40
|
+
}
|
|
41
|
+
secrets.set(name, declaration.secrets);
|
|
46
42
|
}
|
|
47
43
|
catch (error) {
|
|
48
44
|
failures.push({ label, file, message: error.message });
|
|
49
45
|
}
|
|
50
46
|
}
|
|
51
|
-
return { channels, failures };
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Channel file basenames under `<dir>/channels/` — the authoring view (`fastagent info`), which lists
|
|
55
|
-
* WITHOUT importing. A symlinked channels directory must remain inside the agent dir.
|
|
56
|
-
*/
|
|
57
|
-
export async function discoverChannelFiles(dir) {
|
|
58
|
-
await assertInsideAgentDir(dir, "channels");
|
|
59
|
-
const entries = await moduleInventory(join(dir, "channels"));
|
|
60
|
-
return entries.map((entry) => entry.name);
|
|
47
|
+
return { channels, secrets, failures };
|
|
61
48
|
}
|
|
62
49
|
function validateRoutes(value, label) {
|
|
63
50
|
if (value === null || typeof value !== "object" || value instanceof Map) {
|
|
@@ -75,7 +62,6 @@ function validateRoutes(value, label) {
|
|
|
75
62
|
}
|
|
76
63
|
return routes;
|
|
77
64
|
}
|
|
78
|
-
/** Discover, validate, and bind all channel modules. No long connection is opened here; the CLI owns it. */
|
|
79
65
|
export async function loadChannels(dir, ctx) {
|
|
80
66
|
if (!isAbsolute(ctx.stateRoot)) {
|
|
81
67
|
throw new Error(`ChannelContext.stateRoot must be absolute, got "${ctx.stateRoot}"`);
|
|
@@ -86,7 +72,21 @@ export async function loadChannels(dir, ctx) {
|
|
|
86
72
|
const longConnections = [];
|
|
87
73
|
const routeChannels = [];
|
|
88
74
|
const collisions = [];
|
|
89
|
-
|
|
75
|
+
// Collected from the channels that actually MOUNTED, GATED at the end of the function: this is a
|
|
76
|
+
// serving path, so an unset declared value must stop it (a channel built from an empty credential
|
|
77
|
+
// is the failure the declaration exists to prevent — a webhook that accepts forged updates, a bot
|
|
78
|
+
// that cannot reply), while `inspectChannels` — the reporting reader — only collects. A channel
|
|
79
|
+
// that failed to mount does not gate: its own failure is the report the author needs.
|
|
80
|
+
const declaredSecrets = new Map();
|
|
81
|
+
const declaring = modules.flatMap((entry) => {
|
|
82
|
+
const declaration = readSecretDeclaration(entry.mod.default, entry.label);
|
|
83
|
+
if (declaration.error !== undefined) {
|
|
84
|
+
failures.push({ label: entry.label, file: entry.file, message: declaration.error });
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
return [{ ...entry, secrets: declaration.secrets }];
|
|
88
|
+
});
|
|
89
|
+
for (const { name, label, file, mod, secrets } of declaring) {
|
|
90
90
|
try {
|
|
91
91
|
if (longConnectionModule(mod.default)) {
|
|
92
92
|
validateLongConnectionModule(mod.default, label);
|
|
@@ -95,6 +95,7 @@ export async function loadChannels(dir, ctx) {
|
|
|
95
95
|
name: channel.name,
|
|
96
96
|
connect: (signal) => channel.connect(ctx, signal),
|
|
97
97
|
});
|
|
98
|
+
declaredSecrets.set(name, secrets);
|
|
98
99
|
continue;
|
|
99
100
|
}
|
|
100
101
|
if (typeof mod.default !== "function") {
|
|
@@ -117,10 +118,15 @@ export async function loadChannels(dir, ctx) {
|
|
|
117
118
|
routes[route] = handler;
|
|
118
119
|
}
|
|
119
120
|
routeChannels.push(name);
|
|
121
|
+
declaredSecrets.set(name, secrets);
|
|
120
122
|
}
|
|
121
123
|
catch (error) {
|
|
122
124
|
failures.push({ label, file, message: error.message });
|
|
123
125
|
}
|
|
124
126
|
}
|
|
127
|
+
// Gated LAST, after the loop has said what it found (the gate reports those failures before it
|
|
128
|
+
// refuses — src/secrets-gate.ts, decision 2). Binding a module ahead of the gate costs nothing: a
|
|
129
|
+
// route factory builds handlers; nothing listens or dials until the caller mounts what this returns.
|
|
130
|
+
gateSecrets({ declared: declaredSecrets, failures });
|
|
125
131
|
return { routes, longConnections, routeChannels, collisions, failures };
|
|
126
132
|
}
|
|
@@ -18,11 +18,9 @@ export interface FeishuBootstrapTokenOptions {
|
|
|
18
18
|
} | undefined>;
|
|
19
19
|
/** Budget for the whole capture (the challenge normally lands within the PATCH round-trip). */
|
|
20
20
|
timeoutMs?: number;
|
|
21
|
-
/** PATCH attempts × delay — the PATCH is the one and only readiness probe
|
|
21
|
+
/** PATCH attempts × delay — the PATCH is the one and only readiness probe. */
|
|
22
22
|
patchAttempts?: number;
|
|
23
23
|
patchRetryMs?: number;
|
|
24
|
-
/** Retry classifier. Default: retry every PATCH failure (Feishu edge warm-up compatibility).
|
|
25
|
-
* Lark onboarding rejects a definitive config-route 404 immediately so it can fall back by hand. */
|
|
26
24
|
shouldRetryPatch?: (error: unknown) => boolean;
|
|
27
25
|
/** Progress hooks: production prints the assigned URL/retries; tests keep transport IO injected. */
|
|
28
26
|
onTunnelReady?: (url: string) => void;
|
|
@@ -33,10 +31,5 @@ export interface FeishuBootstrapTokenOptions {
|
|
|
33
31
|
retryMs: number;
|
|
34
32
|
}) => void;
|
|
35
33
|
}
|
|
36
|
-
/**
|
|
37
|
-
* Run the bootstrap (module header). Resolves with the app's Verification Token; rejects with a
|
|
38
|
-
* plain, actionable Error (tunnel unavailable, PATCH refused, challenge never arrived) — the caller
|
|
39
|
-
* degrades to the manual console instruction.
|
|
40
|
-
*/
|
|
41
34
|
export declare function bootstrapFeishuVerificationToken(options: FeishuBootstrapTokenOptions): Promise<string>;
|
|
42
35
|
export {};
|
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Verification-Token bootstrap — the token has NO read API (field-tested 2026-07: the v6 app detail
|
|
3
|
-
*
|
|
4
|
-
* programmatic delivery is the `url_verification` challenge it POSTs to the request URL while an
|
|
5
|
-
* event-subscription PATCH is verified — the challenge body carries `token`, the app's Verification
|
|
6
|
-
* Token. So `add feishu`'s create flow finishes the job by: standing up a throwaway local responder,
|
|
7
|
-
* exposing it on an ephemeral quick tunnel, PATCHing the app's event subscription at it, capturing
|
|
8
|
-
* the token from the challenge, and tearing everything down. `dev --tunnel` / `deploy … --run`
|
|
9
|
-
* re-PATCH the request URL onto the real server later (the token is app-level and survives that).
|
|
10
|
-
*
|
|
11
|
-
* Security: the serving path keeps its strict non-empty-token requirement — no bootstrap mode there.
|
|
12
|
-
* The throwaway URL is random (unguessable), lives for seconds, and only the FIRST challenge is
|
|
13
|
-
* accepted, immediately after our own credential-authenticated PATCH.
|
|
2
|
+
* Verification-Token bootstrap — the token has NO read API (field-tested 2026-07: the v6 app detail never returns
|
|
3
|
+
* `encryption`, and the v7 config route only supports PATCH).
|
|
14
4
|
*/
|
|
15
5
|
import { createServer } from "node:http";
|
|
16
|
-
/**
|
|
17
|
-
* Run the bootstrap (module header). Resolves with the app's Verification Token; rejects with a
|
|
18
|
-
* plain, actionable Error (tunnel unavailable, PATCH refused, challenge never arrived) — the caller
|
|
19
|
-
* degrades to the manual console instruction.
|
|
20
|
-
*/
|
|
21
6
|
export async function bootstrapFeishuVerificationToken(options) {
|
|
22
7
|
let capturedToken;
|
|
23
8
|
const token = new Promise((resolve) => {
|
|
24
9
|
capturedToken = resolve;
|
|
25
10
|
});
|
|
26
|
-
// The throwaway responder: answers /health (diagnostics) and the url_verification challenge
|
|
27
|
-
//
|
|
11
|
+
// The throwaway responder: answers /health (diagnostics) and the url_verification challenge (echo `challenge`
|
|
12
|
+
// back), capturing `token` from the FIRST challenge only.
|
|
28
13
|
const server = createServer((req, res) => {
|
|
29
14
|
if (req.method === "GET") {
|
|
30
15
|
res.end("ok");
|
|
@@ -46,7 +31,7 @@ export async function bootstrapFeishuVerificationToken(options) {
|
|
|
46
31
|
}
|
|
47
32
|
}
|
|
48
33
|
catch {
|
|
49
|
-
|
|
34
|
+
// not the challenge — fall through to the empty ACK
|
|
50
35
|
}
|
|
51
36
|
res.end("{}");
|
|
52
37
|
});
|
|
@@ -59,9 +44,8 @@ export async function bootstrapFeishuVerificationToken(options) {
|
|
|
59
44
|
if (!tunnel)
|
|
60
45
|
throw new Error("no tunnel came up (is cloudflared installed?)");
|
|
61
46
|
options.onTunnelReady?.(tunnel.url);
|
|
62
|
-
// Do NOT health-poll the edge here: "can WE reach it" says nothing about platform→edge, the path
|
|
63
|
-
//
|
|
64
|
-
// PATCH immediately; it is the real probe because success requires the challenge round-trip.
|
|
47
|
+
// Do NOT health-poll the edge here: "can WE reach it" says nothing about platform→edge, the path the challenge
|
|
48
|
+
// uses (field-tested: local negative-DNS caching failed while that path worked).
|
|
65
49
|
const attempts = options.patchAttempts ?? 8;
|
|
66
50
|
for (let attempt = 1;; attempt++) {
|
|
67
51
|
try {
|
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Card JSON builders — PURE: the streaming-card entity (JSON 2.0) the live preview creates, the settled
|
|
3
|
-
*
|
|
4
|
-
* the card DSL is data-in → string-out and testable without the pump.
|
|
5
|
-
*
|
|
6
|
-
* The streaming card is TWO markdown elements: `process` (the volatile block — thinking tail, tool
|
|
7
|
-
* lines, retry notice) and `answer` (append-only). The pump PUTs full-text snapshots per element
|
|
8
|
-
* (feishu-api.ts `updateCardElement`) and the client renders the typewriter effect. The split is the
|
|
9
|
-
* prefix-stability rule made structural: the client animates an element's update only when the old
|
|
10
|
-
* text is a PREFIX of the new — otherwise it re-types everything after the first divergent character.
|
|
11
|
-
* The process block's head changes every frame (a sliding thinking tail, `…`→`✓` status flips), so
|
|
12
|
-
* sharing one element with the answer re-typed the whole card once a second; two elements confine the
|
|
13
|
-
* churn to the small process block and keep the answer's typewriter smooth. Settling replaces the
|
|
14
|
-
* whole entity (`updateCard`) with the answer element alone, `streaming_mode` off — one write flips
|
|
15
|
-
* content and mode together and drops the process block.
|
|
16
|
-
*
|
|
17
|
-
* Budget: a card entity is capped at 30 KB, so the final answer's card chunk (and the live view) stay
|
|
18
|
-
* well under it; longer answers overflow into follow-up messages (preview.ts owns that policy).
|
|
2
|
+
* Card JSON builders — PURE: the streaming-card entity (JSON 2.0) the live preview creates, the settled final card,
|
|
3
|
+
* and the message content that mounts a card entity into a chat.
|
|
19
4
|
*/
|
|
20
5
|
/** The append-only answer element's id — shared by create (card.ts) and update (preview.ts). */
|
|
21
6
|
export declare const ANSWER_ELEMENT_ID = "answer";
|
|
@@ -24,20 +9,21 @@ export declare const PROCESS_ELEMENT_ID = "process";
|
|
|
24
9
|
/** Byte budget for markdown carried by ONE card (entity cap 30 KB minus JSON envelope + escaping room). */
|
|
25
10
|
export declare const CARD_MARKDOWN_MAX_BYTES: number;
|
|
26
11
|
/**
|
|
27
|
-
* The answer's first line as plain text — what the chat list and the push notification show for the
|
|
28
|
-
*
|
|
29
|
-
* placeholder: the user's notification would never carry the actual answer. Markdown is stripped
|
|
30
|
-
* lightly (this is a one-line teaser, not a renderer): fenced code dropped, links/images → their text,
|
|
31
|
-
* emphasis/heading/list markers removed.
|
|
12
|
+
* The answer's first line as plain text — what the chat list and the push notification show for the settled card
|
|
13
|
+
* (`config.summary.content`).
|
|
32
14
|
*/
|
|
33
15
|
export declare function cardSummary(markdown: string): string;
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
16
|
+
/**
|
|
17
|
+
* The live-preview card entity: streaming on, the process element seeded with the placeholder/queue status and the
|
|
18
|
+
* answer element seeded EMPTY (the platform accepts an empty markdown element; it renders zero-height until the first
|
|
19
|
+
* answer snapshot lands as a clean prefix extension of "").
|
|
20
|
+
*/
|
|
37
21
|
export declare function streamingCardJson(initialProcess: string): string;
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
22
|
+
/**
|
|
23
|
+
* The settled card: final markdown alone (the process block was preview-only), streaming off (stops the client's
|
|
24
|
+
* streaming affordance), plus the answer-derived summary so the chat list / notification shows the reply, not
|
|
25
|
+
* "[Card]".
|
|
26
|
+
*/
|
|
41
27
|
export declare function finalCardJson(markdown: string): string;
|
|
42
28
|
/** The `interactive` message content that mounts a card ENTITY (vs an inline static card). */
|
|
43
29
|
export declare function cardEntityContent(cardId: string): string;
|
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Card JSON builders — PURE: the streaming-card entity (JSON 2.0) the live preview creates, the settled
|
|
3
|
-
*
|
|
4
|
-
* the card DSL is data-in → string-out and testable without the pump.
|
|
5
|
-
*
|
|
6
|
-
* The streaming card is TWO markdown elements: `process` (the volatile block — thinking tail, tool
|
|
7
|
-
* lines, retry notice) and `answer` (append-only). The pump PUTs full-text snapshots per element
|
|
8
|
-
* (feishu-api.ts `updateCardElement`) and the client renders the typewriter effect. The split is the
|
|
9
|
-
* prefix-stability rule made structural: the client animates an element's update only when the old
|
|
10
|
-
* text is a PREFIX of the new — otherwise it re-types everything after the first divergent character.
|
|
11
|
-
* The process block's head changes every frame (a sliding thinking tail, `…`→`✓` status flips), so
|
|
12
|
-
* sharing one element with the answer re-typed the whole card once a second; two elements confine the
|
|
13
|
-
* churn to the small process block and keep the answer's typewriter smooth. Settling replaces the
|
|
14
|
-
* whole entity (`updateCard`) with the answer element alone, `streaming_mode` off — one write flips
|
|
15
|
-
* content and mode together and drops the process block.
|
|
16
|
-
*
|
|
17
|
-
* Budget: a card entity is capped at 30 KB, so the final answer's card chunk (and the live view) stay
|
|
18
|
-
* well under it; longer answers overflow into follow-up messages (preview.ts owns that policy).
|
|
2
|
+
* Card JSON builders — PURE: the streaming-card entity (JSON 2.0) the live preview creates, the settled final card,
|
|
3
|
+
* and the message content that mounts a card entity into a chat.
|
|
19
4
|
*/
|
|
20
5
|
import { truncateCodePointPrefix } from "../kit/text.js";
|
|
21
6
|
/** The append-only answer element's id — shared by create (card.ts) and update (preview.ts). */
|
|
@@ -27,11 +12,8 @@ export const CARD_MARKDOWN_MAX_BYTES = 20 * 1024;
|
|
|
27
12
|
/** Character budget for the settled card's summary (the chat-list / push-notification preview). */
|
|
28
13
|
const SUMMARY_MAX_CHARS = 60;
|
|
29
14
|
/**
|
|
30
|
-
* The answer's first line as plain text — what the chat list and the push notification show for the
|
|
31
|
-
*
|
|
32
|
-
* placeholder: the user's notification would never carry the actual answer. Markdown is stripped
|
|
33
|
-
* lightly (this is a one-line teaser, not a renderer): fenced code dropped, links/images → their text,
|
|
34
|
-
* emphasis/heading/list markers removed.
|
|
15
|
+
* The answer's first line as plain text — what the chat list and the push notification show for the settled card
|
|
16
|
+
* (`config.summary.content`).
|
|
35
17
|
*/
|
|
36
18
|
export function cardSummary(markdown) {
|
|
37
19
|
const line = markdown
|
|
@@ -52,25 +34,29 @@ function cardJson(elements, streaming, summary) {
|
|
|
52
34
|
config: {
|
|
53
35
|
streaming_mode: streaming,
|
|
54
36
|
update_multi: true,
|
|
55
|
-
// Only the settled card sets a summary — while streaming, the platform's default (a localized
|
|
56
|
-
//
|
|
37
|
+
// Only the settled card sets a summary — while streaming, the platform's default (a localized "[Generating…]")
|
|
38
|
+
// is better than any fixed text we could pin.
|
|
57
39
|
...(summary ? { summary: { content: summary } } : {}),
|
|
58
40
|
},
|
|
59
41
|
body: { elements },
|
|
60
42
|
});
|
|
61
43
|
}
|
|
62
|
-
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
44
|
+
/**
|
|
45
|
+
* The live-preview card entity: streaming on, the process element seeded with the placeholder/queue status and the
|
|
46
|
+
* answer element seeded EMPTY (the platform accepts an empty markdown element; it renders zero-height until the first
|
|
47
|
+
* answer snapshot lands as a clean prefix extension of "").
|
|
48
|
+
*/
|
|
65
49
|
export function streamingCardJson(initialProcess) {
|
|
66
50
|
return cardJson([
|
|
67
51
|
{ tag: "markdown", content: initialProcess, element_id: PROCESS_ELEMENT_ID },
|
|
68
52
|
{ tag: "markdown", content: "", element_id: ANSWER_ELEMENT_ID },
|
|
69
53
|
], true);
|
|
70
54
|
}
|
|
71
|
-
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
55
|
+
/**
|
|
56
|
+
* The settled card: final markdown alone (the process block was preview-only), streaming off (stops the client's
|
|
57
|
+
* streaming affordance), plus the answer-derived summary so the chat list / notification shows the reply, not
|
|
58
|
+
* "[Card]".
|
|
59
|
+
*/
|
|
74
60
|
export function finalCardJson(markdown) {
|
|
75
61
|
return cardJson([{ tag: "markdown", content: markdown, element_id: ANSWER_ELEMENT_ID }], false, cardSummary(markdown));
|
|
76
62
|
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feishu is the canonical implementation. Lark international speaks the same protocol but trails the
|
|
3
|
-
* primary cloud in control-plane capabilities, so it binds the Feishu engine through this explicit
|
|
4
|
-
* compatibility profile instead of becoming the engine's naming/design center.
|
|
5
|
-
*/
|
|
1
|
+
/** Feishu is the canonical implementation. */
|
|
6
2
|
export type FeishuCloudKind = "feishu" | "lark";
|
|
7
3
|
export interface FeishuCloudProfile {
|
|
8
4
|
kind: FeishuCloudKind;
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feishu/Lark's half of the shared context buffer (mechanics + consume protocol:
|
|
3
|
-
* ../kit/context-buffer.ts): the entry shape, its fold-line rendering, place-key derivation, and
|
|
4
|
-
* buffered-resource selection. Entries are bucketed by conversation place (main chat, or one
|
|
5
|
-
* concrete thread root) and folded into the next answered turn in that place.
|
|
6
|
-
*/
|
|
1
|
+
/** Feishu/Lark's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts). */
|
|
7
2
|
import { type ContextBuffer } from "../kit/context-buffer.ts";
|
|
8
3
|
import type { NormalizedFeishuMessage } from "./model.ts";
|
|
9
4
|
export interface FeishuBufferedResource {
|
|
@@ -23,25 +18,11 @@ export interface FeishuBufferEntry {
|
|
|
23
18
|
export interface FeishuBufferedRef extends FeishuBufferedResource {
|
|
24
19
|
from: string;
|
|
25
20
|
}
|
|
26
|
-
/**
|
|
27
|
-
* The place a message belongs to: the main chat, or a thread within it. Keyed by `thread_id`, the
|
|
28
|
-
* platform's own identity for a side conversation — NOT `root_id`, which tracks the reply chain and
|
|
29
|
-
* can differ between messages of one thread (which would split a thread's context across buckets).
|
|
30
|
-
* A quoted reply outside a thread carries a root but is main-chat discussion, so it buckets there.
|
|
31
|
-
*
|
|
32
|
-
* Its own namespace, deliberately: this names a BUCKET of undelivered text, while `parse.ts`'s
|
|
33
|
-
* `placeKey` names a SESSION. Nothing here claims anything about that session — unlike thread
|
|
34
|
-
* participation, which asserts "the agent answered into this memory" and is therefore keyed by the
|
|
35
|
-
* session itself. Re-keying either one leaves the other correct, and converging them now would strand
|
|
36
|
-
* live buckets for no gain (Slack keeps its own shape for the same reason).
|
|
37
|
-
*/
|
|
21
|
+
/** The place a message belongs to: the main chat, or a thread within it. */
|
|
38
22
|
export declare function feishuBufferPlaceKey(conversation: Pick<NormalizedFeishuMessage["conversation"], "chatId" | "threadId">): string;
|
|
39
|
-
/** One-line, bounded background text.
|
|
23
|
+
/** One-line, bounded background text. */
|
|
40
24
|
export declare function feishuBufferText(text: string): string;
|
|
41
|
-
/**
|
|
42
|
-
* Select the most recent background resources, excluding resources already primary on this turn. A
|
|
43
|
-
* resource is message-scoped in Feishu/Lark, so identity is `message_id + key`, never the bare key.
|
|
44
|
-
*/
|
|
25
|
+
/** Select the most recent background resources, excluding resources already primary on this turn. */
|
|
45
26
|
export declare function collectFeishuBufferedAttachments(consumed: FeishuBufferEntry[], primary: {
|
|
46
27
|
files: FeishuBufferedResource[];
|
|
47
28
|
images: FeishuBufferedResource[];
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* ../kit/context-buffer.ts): the entry shape, its fold-line rendering, place-key derivation, and
|
|
4
|
-
* buffered-resource selection. Entries are bucketed by conversation place (main chat, or one
|
|
5
|
-
* concrete thread root) and folded into the next answered turn in that place.
|
|
6
|
-
*/
|
|
7
|
-
import { BUFFER_ATTACH_MAX, BUFFER_LINE_MAX_CHARS, createContextBuffer as createGenericContextBuffer, } from "../kit/context-buffer.js";
|
|
1
|
+
/** Feishu/Lark's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts). */
|
|
2
|
+
import { capBufferedRefs, BUFFER_LINE_MAX_CHARS, createContextBuffer as createGenericContextBuffer, } from "../kit/context-buffer.js";
|
|
8
3
|
import { truncateCodePointPrefix } from "../kit/text.js";
|
|
9
4
|
function bufferLine(entry) {
|
|
10
5
|
const meta = [`msg ${entry.messageId}`, entry.replyTo ? `reply to msg ${entry.replyTo}` : undefined]
|
|
@@ -12,32 +7,18 @@ function bufferLine(entry) {
|
|
|
12
7
|
.join(", ");
|
|
13
8
|
return `${entry.sender} (${meta}): ${entry.body}`;
|
|
14
9
|
}
|
|
15
|
-
/**
|
|
16
|
-
* The place a message belongs to: the main chat, or a thread within it. Keyed by `thread_id`, the
|
|
17
|
-
* platform's own identity for a side conversation — NOT `root_id`, which tracks the reply chain and
|
|
18
|
-
* can differ between messages of one thread (which would split a thread's context across buckets).
|
|
19
|
-
* A quoted reply outside a thread carries a root but is main-chat discussion, so it buckets there.
|
|
20
|
-
*
|
|
21
|
-
* Its own namespace, deliberately: this names a BUCKET of undelivered text, while `parse.ts`'s
|
|
22
|
-
* `placeKey` names a SESSION. Nothing here claims anything about that session — unlike thread
|
|
23
|
-
* participation, which asserts "the agent answered into this memory" and is therefore keyed by the
|
|
24
|
-
* session itself. Re-keying either one leaves the other correct, and converging them now would strand
|
|
25
|
-
* live buckets for no gain (Slack keeps its own shape for the same reason).
|
|
26
|
-
*/
|
|
10
|
+
/** The place a message belongs to: the main chat, or a thread within it. */
|
|
27
11
|
export function feishuBufferPlaceKey(conversation) {
|
|
28
12
|
return conversation.threadId ? `${conversation.chatId}:thread:${conversation.threadId}` : conversation.chatId;
|
|
29
13
|
}
|
|
30
|
-
/** One-line, bounded background text.
|
|
14
|
+
/** One-line, bounded background text. */
|
|
31
15
|
export function feishuBufferText(text) {
|
|
32
16
|
return truncateCodePointPrefix(text.replace(/\s+/g, " ").trim(), BUFFER_LINE_MAX_CHARS);
|
|
33
17
|
}
|
|
34
18
|
function resourceIdentity(resource) {
|
|
35
19
|
return `${resource.messageId}\u0000${resource.key}`;
|
|
36
20
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Select the most recent background resources, excluding resources already primary on this turn. A
|
|
39
|
-
* resource is message-scoped in Feishu/Lark, so identity is `message_id + key`, never the bare key.
|
|
40
|
-
*/
|
|
21
|
+
/** Select the most recent background resources, excluding resources already primary on this turn. */
|
|
41
22
|
export function collectFeishuBufferedAttachments(consumed, primary) {
|
|
42
23
|
const refs = (pick, primaryRefs) => {
|
|
43
24
|
const excluded = new Set(primaryRefs.map(resourceIdentity));
|
|
@@ -54,13 +35,9 @@ export function collectFeishuBufferedAttachments(consumed, primary) {
|
|
|
54
35
|
}
|
|
55
36
|
return out;
|
|
56
37
|
};
|
|
57
|
-
const files = refs((entry) => entry.files, primary.files);
|
|
58
|
-
const images = refs((entry) => entry.images, primary.images);
|
|
59
|
-
return {
|
|
60
|
-
files: files.slice(-BUFFER_ATTACH_MAX),
|
|
61
|
-
images: images.slice(-BUFFER_ATTACH_MAX),
|
|
62
|
-
skipped: Math.max(0, files.length - BUFFER_ATTACH_MAX) + Math.max(0, images.length - BUFFER_ATTACH_MAX),
|
|
63
|
-
};
|
|
38
|
+
const files = capBufferedRefs(refs((entry) => entry.files, primary.files));
|
|
39
|
+
const images = capBufferedRefs(refs((entry) => entry.images, primary.images));
|
|
40
|
+
return { files: files.kept, images: images.kept, skipped: files.skipped + images.skipped };
|
|
64
41
|
}
|
|
65
42
|
function isResource(value) {
|
|
66
43
|
const resource = value;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
1
|
+
/**
|
|
2
|
+
* Decrypt an `{"encrypt": …}` event payload to its plaintext JSON string; throws on malformed input or invalid
|
|
3
|
+
* padding. AES-CBC is NOT authenticated, so a wrong key is not mathematically guaranteed to fail padding: the caller
|
|
4
|
+
* must verify a signed event BEFORE decrypting it, and JSON-parse every plaintext envelope afterwards, so wrong-key
|
|
5
|
+
* garbage becomes a 4xx rather than a silent drop.
|
|
6
|
+
*/
|
|
5
7
|
export declare function decryptEvent(encryptKey: string, encryptB64: string): string;
|
|
6
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* The expected `X-Lark-Signature` for a request: sha256(timestamp + nonce + encryptKey + rawBody) hex. `rawBody` is
|
|
10
|
+
* the VERBATIM request body (the encrypted form) — the digest is over bytes, so a re-serialization never matches.
|
|
11
|
+
*/
|
|
7
12
|
export declare function eventSignature(encryptKey: string, timestamp: string, nonce: string, rawBody: string): string;
|
|
8
13
|
/** Whether a request's signature headers verify against the raw body (constant-time). */
|
|
9
14
|
export declare function verifySignature(encryptKey: string, headers: {
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical Feishu webhook security (reused by Lark compatibility) — PURE: AES event decryption and request
|
|
3
|
-
*
|
|
4
|
-
* developer console, every event arrives as `{"encrypt": "<base64>"}` with signature headers; without
|
|
5
|
-
* one, events arrive in plaintext and carry only the verification token in the body. feishu.ts owns the
|
|
6
|
-
* fail-closed policy (which checks run when); this module owns the math.
|
|
7
|
-
*
|
|
8
|
-
* - Decryption: AES-256-CBC. The key is sha256(encryptKey); the base64 payload is IV (16 bytes) ‖
|
|
9
|
-
* ciphertext; the plaintext is the event JSON (PKCS#7 padding handled by the cipher).
|
|
10
|
-
* - Signature: `X-Lark-Signature = sha256(timestamp + nonce + encryptKey + rawBody)` hex, where
|
|
11
|
-
* rawBody is the VERBATIM request body (the encrypted form) — computed over bytes, so the caller
|
|
12
|
-
* must pass the raw text, never a re-serialization.
|
|
13
|
-
*
|
|
14
|
-
* Comparisons are constant-time (timingSafeEqual) so neither the signature check nor the verification-
|
|
15
|
-
* token check leaks a timing signal.
|
|
2
|
+
* Canonical Feishu webhook security (reused by Lark compatibility) — PURE: AES event decryption and request signature,
|
|
3
|
+
* exactly as the open platform defines them.
|
|
16
4
|
*/
|
|
17
5
|
import { createDecipheriv, createHash } from "node:crypto";
|
|
18
6
|
import { secretEquals } from "../secret.js";
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
7
|
+
/**
|
|
8
|
+
* Decrypt an `{"encrypt": …}` event payload to its plaintext JSON string; throws on malformed input or invalid
|
|
9
|
+
* padding. AES-CBC is NOT authenticated, so a wrong key is not mathematically guaranteed to fail padding: the caller
|
|
10
|
+
* must verify a signed event BEFORE decrypting it, and JSON-parse every plaintext envelope afterwards, so wrong-key
|
|
11
|
+
* garbage becomes a 4xx rather than a silent drop.
|
|
12
|
+
*/
|
|
23
13
|
export function decryptEvent(encryptKey, encryptB64) {
|
|
24
14
|
const key = createHash("sha256").update(encryptKey, "utf8").digest();
|
|
25
15
|
const buf = Buffer.from(encryptB64, "base64");
|
|
@@ -28,7 +18,10 @@ export function decryptEvent(encryptKey, encryptB64) {
|
|
|
28
18
|
const decipher = createDecipheriv("aes-256-cbc", key, buf.subarray(0, 16));
|
|
29
19
|
return Buffer.concat([decipher.update(buf.subarray(16)), decipher.final()]).toString("utf8");
|
|
30
20
|
}
|
|
31
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The expected `X-Lark-Signature` for a request: sha256(timestamp + nonce + encryptKey + rawBody) hex. `rawBody` is
|
|
23
|
+
* the VERBATIM request body (the encrypted form) — the digest is over bytes, so a re-serialization never matches.
|
|
24
|
+
*/
|
|
32
25
|
export function eventSignature(encryptKey, timestamp, nonce, rawBody) {
|
|
33
26
|
return createHash("sha256").update(`${timestamp}${nonce}${encryptKey}${rawBody}`, "utf8").digest("hex");
|
|
34
27
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ImageRef } from "../../agent.ts";
|
|
2
2
|
import type { FeishuCloudKind } from "./cloud.ts";
|
|
3
|
+
import type { CallOptions } from "../kit/transport.ts";
|
|
3
4
|
/** Where a reply goes: a chat, optionally quote-replying a message inside a platform thread. */
|
|
4
5
|
export interface FeishuTarget {
|
|
5
6
|
chatId: string;
|
|
@@ -118,7 +119,7 @@ export interface FeishuApi {
|
|
|
118
119
|
/** Create a card entity (card JSON 2.0). Returns its card_id. */
|
|
119
120
|
createCard(cardJson: string): Promise<string>;
|
|
120
121
|
/** Stream-update a card element's text (full-content snapshot + strictly increasing sequence). */
|
|
121
|
-
updateCardElement(cardId: string, elementId: string, content: string, sequence: number): Promise<void>;
|
|
122
|
+
updateCardElement(cardId: string, elementId: string, content: string, sequence: number, opts?: CallOptions): Promise<void>;
|
|
122
123
|
/** Replace a card entity's content (the settle write; also flips streaming_mode off via the JSON). */
|
|
123
124
|
updateCard(cardId: string, cardJson: string, sequence: number): Promise<void>;
|
|
124
125
|
}
|