@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,48 +1,54 @@
|
|
|
1
1
|
import type { Agent } from "../agent.ts";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
2
|
+
import type { ChannelHandler, Routes } from "../channel.ts";
|
|
3
|
+
/**
|
|
4
|
+
* What the lazy factory hands back: the CHANNELS' routes, and nothing else.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately not "the whole inner surface". Every request that reaches this dispatcher arrived through the
|
|
7
|
+
* forwarder's public Function URL, which relays an arbitrary path verbatim and supplies the ingress secret itself —
|
|
8
|
+
* so behind THIS door an anonymous caller and an IAM one are the same thing. A channel route survives that because
|
|
9
|
+
* it verifies the platform's signature inside itself; nothing fastagent serves does.
|
|
10
|
+
*
|
|
11
|
+
* The other door is the Runtime's own, IAM-gated: the forwarder emits only `webhook`, `routine-fire`, `wake-poke`
|
|
12
|
+
* and `probe`, so any other KIND can only come from a direct `InvokeAgentRuntime` call. That is what lets
|
|
13
|
+
* `kind: "invoke"` run a turn here with no ingress secret, and it is where anything else that needs a real caller
|
|
14
|
+
* identity belongs.
|
|
15
|
+
*/
|
|
8
16
|
export interface RouteSurface {
|
|
9
17
|
routes: Routes;
|
|
10
|
-
mounts?: readonly PrefixMount[];
|
|
11
18
|
}
|
|
12
19
|
export interface AgentcoreAdapterOptions {
|
|
13
|
-
/** The
|
|
14
|
-
* built LAZILY: channel construction loads channel state and replays durable turn intent, so on
|
|
15
|
-
* AgentCore it must not run until the state root is authoritative — which happens at the first
|
|
16
|
-
* envelope's `stateSync.ready()` (the restore URLs only an envelope carries), never at boot, where
|
|
17
|
-
* the mount is pre-restore (empty after every version update). A factory, not a value: there is no
|
|
18
|
-
* moment during construction at which the right answer is knowable. May answer synchronously — the
|
|
19
|
-
* resolution chain normalizes it either way. */
|
|
20
|
+
/** The channel surface. */
|
|
20
21
|
channels: () => Promise<RouteSurface> | RouteSurface;
|
|
21
22
|
agent: Agent;
|
|
22
23
|
/** Where the forwarder URL from envelopes is persisted for the wake-alarm sink (the state root). */
|
|
23
24
|
stateRoot: string;
|
|
24
25
|
/** Process-wide background-work signal (busy.ts `activeWork() > 0`) — injected for tests. */
|
|
25
26
|
isBusy: () => boolean;
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
27
|
+
/**
|
|
28
|
+
* Fire ONE declared schedule for the occurrence EventBridge named — the claim/run/settle the resident clock uses,
|
|
29
|
+
* not the `POST /run` API.
|
|
30
|
+
*
|
|
31
|
+
* THIS IS WHERE OCCURRENCE SEMANTICS LIVE ON THIS HOST, and it is separate from that route on purpose. `deploy`
|
|
32
|
+
* wrote the rule, injected `<aws.scheduler.scheduled-time>` and the forwarder relays it behind the ingress
|
|
33
|
+
* secret, so this caller is one we produced and authenticated: the instant it names really is a grid point of
|
|
34
|
+
* that schedule, which is what makes a claim (and therefore dedup across EventBridge's redeliveries, a fire
|
|
35
|
+
* history and the overlap policy) mean anything. `POST /run` has none of that — it is an unauthenticated API
|
|
36
|
+
* whose caller cannot be told which occurrence it means, so it does not pretend to (schedule/run.ts).
|
|
37
|
+
*
|
|
38
|
+
* Undefined when the definition declares no schedules.
|
|
39
|
+
*/
|
|
40
|
+
fireRoutine?: (name: string, occurrence: Date) => Promise<Response>;
|
|
41
|
+
/**
|
|
42
|
+
* Run a declared routine BY NAME — the `POST /run` contract, for the host that publishes no routes.
|
|
43
|
+
*
|
|
44
|
+
* Undefined when the definition declares none. Note what this does NOT do: no occurrence, no claim, no fire
|
|
45
|
+
* history. Those belong to {@link fireRoutine}, whose caller is a clock we wrote.
|
|
46
|
+
*/
|
|
47
|
+
runRoutine?: (name: string) => Promise<Response>;
|
|
48
|
+
/** FASTAGENT_INGRESS_SECRET: what makes an envelope the FORWARDER's rather than any IAM principal's. */
|
|
35
49
|
ingressSecret?: string;
|
|
36
|
-
/**
|
|
37
|
-
* state on every later idle edge — including work belonging to whatever mounted after it. */
|
|
38
|
-
signal?: AbortSignal;
|
|
39
|
-
/** Runs ONCE, after the state root is authoritative (post-restore) — the wake-alarm reconcile, which
|
|
40
|
-
* at boot would see the mount the platform just wiped and conclude there is nothing pending. */
|
|
50
|
+
/** Runs once on activation, after accepting the current forwarder callback URL. */
|
|
41
51
|
onStateReady?: () => void;
|
|
42
52
|
}
|
|
43
53
|
export declare function agentcoreRoutes(options: AgentcoreAdapterOptions): Routes;
|
|
44
|
-
|
|
45
|
-
* not have — the adapter maps it to 404 (deploy drift stays visible in the external clock's logs). */
|
|
46
|
-
export declare class UnknownScheduleError extends Error {
|
|
47
|
-
constructor(name: string);
|
|
48
|
-
}
|
|
54
|
+
export declare function agentcorePing(isBusy: () => boolean): ChannelHandler;
|
|
@@ -1,124 +1,54 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AWS AgentCore Runtime adapter: serve fastagent's whole HTTP surface through the Runtime's service
|
|
3
|
-
* contract. AgentCore gives a container exactly TWO paths — `POST /invocations` (the only ingress,
|
|
4
|
-
* reached via the SigV4 `InvokeAgentRuntime` API) and `GET /ping` (health) — and no public URL, so
|
|
5
|
-
* the deployment fronts webhooks with a thin forwarder Lambda and delivers cron slots from
|
|
6
|
-
* EventBridge Scheduler; both arrive here as an ENVELOPE in the /invocations payload:
|
|
7
|
-
*
|
|
8
|
-
* - `{ kind: "webhook", method, path, headers?, bodyB64? }` — a verbatim webhook request captured
|
|
9
|
-
* by the forwarder. Reconstructed into a real `Request` and dispatched to the SAME channel routes
|
|
10
|
-
* a direct deployment serves — signature verification (Telegram secret token, Feishu signatures)
|
|
11
|
-
* runs unchanged inside the channel. The channel's HTTP response travels back INSIDE the
|
|
12
|
-
* transport reply (`{ status, headers, bodyB64 }`, transport always 200): AgentCore folds a
|
|
13
|
-
* container non-2xx into its own 424 RuntimeClientError, so riding the real status inside the
|
|
14
|
-
* envelope is the only way the forwarder can re-emit it verbatim (a Feishu URL-verification
|
|
15
|
-
* challenge needs the exact body + content-type back).
|
|
16
|
-
* - `{ kind: "schedule-fire", name, slot }` — one cron instant from the external clock. Dispatched
|
|
17
|
-
* to {@link fireScheduleOnce}-shaped `fire` with the slot as the idempotency key (EventBridge
|
|
18
|
-
* delivery is at-least-once; a duplicate slot must not double-fire).
|
|
19
|
-
* - `{ kind: "invoke", session, text }` — the programmatic data plane; streams the invoke back as
|
|
20
|
-
* SSE (AgentCore's streaming response form), reusing the HTTP channel's handler wholesale.
|
|
21
|
-
*
|
|
22
|
-
* `/ping` reports `HealthyBusy` (+ `time_of_last_update`, required — see the handler) while
|
|
23
|
-
* process-wide background work is in flight (busy.ts) — webhook
|
|
24
|
-
* channels ACK fast and run turns fire-and-forget, and AgentCore ends an idle session, so without
|
|
25
|
-
* this signal a long turn would be killed mid-flight right after its ACK. `Healthy` when idle lets
|
|
26
|
-
* the platform reclaim the microVM (that idle-to-zero IS the point of this deployment).
|
|
27
|
-
*/
|
|
1
|
+
/** AWS AgentCore Runtime adapter: serve fastagent's whole HTTP surface through the Runtime's service contract. */
|
|
28
2
|
import { Buffer } from "node:buffer";
|
|
29
|
-
import
|
|
30
|
-
import {
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { PortFailure, portJoin } from "../effect-port.js";
|
|
5
|
+
import { ENVELOPE_KINDS, fromForwarder } from "./agentcore-protocol.js";
|
|
6
|
+
import { beginWork } from "./busy.js";
|
|
31
7
|
import { router } from "../channels/serve.js";
|
|
32
8
|
import { log } from "../log.js";
|
|
33
9
|
import { rememberWakeAlarmUrl } from "../schedule/wake-alarm.js";
|
|
34
10
|
import { readBodyCapped } from "./body.js";
|
|
35
11
|
import { createInvokeHandler } from "./http.js";
|
|
36
12
|
import { text } from "./respond.js";
|
|
37
|
-
import { secretEquals } from "./secret.js";
|
|
38
13
|
import { MAX_ENVELOPE_BYTES, MAX_WEBHOOK_BODY_BYTES } from "./agentcore-limits.js";
|
|
39
|
-
const unsnapshottedWarning = "[agentcore] this envelope carried no state-snapshot URLs — the state root is LOCAL ONLY and the " +
|
|
40
|
-
"platform erases it on the next deploy (redeploy with a current fastagent to restore durability)";
|
|
41
14
|
const jsonHeaders = { "content-type": "application/json" };
|
|
42
15
|
const json = (body, status) => new Response(`${JSON.stringify(body)}\n`, { status, headers: jsonHeaders });
|
|
43
16
|
/**
|
|
44
|
-
*
|
|
45
|
-
* platform routes into the container. The agent's own channels are not beside these: they are a table
|
|
46
|
-
* inside the envelope dispatch below, reached only by unwrapping a forwarder envelope.
|
|
47
|
-
*/
|
|
48
|
-
/**
|
|
49
|
-
* The process's BOOT, deferred to ingress. A resident host restores its state, wires its sinks and
|
|
50
|
-
* constructs its channels at start-up; here nothing before the first trusted envelope is
|
|
51
|
-
* authoritative (the mount is pre-restore, and the snapshot URLs only an envelope carries), so those
|
|
52
|
-
* steps run against the first one and are asserted again on every later one. Two things happen ONCE
|
|
53
|
-
* per process, in this order: the post-restore hook (the wake-alarm reconcile, which at boot would
|
|
54
|
-
* read the mount the platform just wiped), and channel construction — whose outcome is cached EITHER
|
|
55
|
-
* WAY. Success: the same resident channels a direct host keeps. Failure too: construction is an
|
|
56
|
-
* ACTIVATION with side effects — loadChannels builds every healthy channel (starting its queues and
|
|
57
|
-
* replaying durable turn intent) before reporting another module's failure — and there is no cleanup
|
|
58
|
-
* contract to unwind it, so re-running it per envelope could replay the same recovered turn
|
|
59
|
-
* concurrently. The first rejection is the process's answer: every later envelope fails with the same
|
|
60
|
-
* message (visible each time), the retry boundary is a fresh session (which scale-to-zero provides
|
|
61
|
-
* naturally), and the deploy driver's probe catches deterministic failures at deploy time.
|
|
17
|
+
* Channel construction may already have replayed work before failing; cache either outcome to avoid replaying twice.
|
|
62
18
|
*/
|
|
63
19
|
function createActivation(deps) {
|
|
64
|
-
const {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
20
|
+
const { stateRoot, onStateReady } = deps;
|
|
21
|
+
// UNINTERRUPTIBLE, because the outcome is CACHED: `Effect.cached` memoizes whatever exit it sees,
|
|
22
|
+
// interruption included. A once-per-process activation that remembered "interrupted" would answer
|
|
23
|
+
// every later envelope with an empty cause instead of the success or the diagnosable failure this
|
|
24
|
+
// block promises. Both are already unreachable (no caller passes a signal), which is exactly why
|
|
25
|
+
// the invariant has to be written down rather than relied on.
|
|
26
|
+
const stateReady = Effect.runSync(Effect.cached(portJoin(async () => {
|
|
27
|
+
onStateReady?.();
|
|
28
|
+
}).pipe(Effect.uninterruptible)));
|
|
29
|
+
const channels = Effect.runSync(Effect.cached(portJoin(async () => {
|
|
30
|
+
const surface = await deps.channels();
|
|
31
|
+
return router({ selfVerifying: surface.routes });
|
|
32
|
+
}).pipe(Effect.uninterruptible)));
|
|
68
33
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (envelope.state && typeof envelope.state.getUrl === "string" && typeof envelope.state.putUrl === "string") {
|
|
72
|
-
stateSync.use(envelope.state);
|
|
73
|
-
}
|
|
74
|
-
else if (envelope.kind !== "invoke" && !stateSync.configured() && !warnedUnsnapshotted) {
|
|
75
|
-
// webhook/schedule-fire/wake-poke/probe reach us ONLY through the forwarder, which mints the
|
|
76
|
-
// pair. Missing = a broken/stale topology whose state dies at the next deploy: say so, loudly,
|
|
77
|
-
// once per process (a direct `invoke` legitimately has none — its session storage is its own).
|
|
78
|
-
warnedUnsnapshotted = true;
|
|
79
|
-
log.warn(unsnapshottedWarning);
|
|
80
|
-
}
|
|
81
|
-
await stateSync.ready();
|
|
82
|
-
}
|
|
83
|
-
// The forwarder rides its public URL along on every envelope — persist it (write-if-changed) so
|
|
84
|
-
// the wake-alarm sink can call back. AFTER the restore: a stale snapshot copy must not win over
|
|
85
|
-
// the URL this deployment is actually reachable at. A bad persist must not fail the turn.
|
|
34
|
+
prepare: (envelope) => Effect.gen(function* () {
|
|
35
|
+
// Use the current callback URL rather than the one persisted by an earlier deployment.
|
|
86
36
|
if (typeof envelope.wake?.url === "string") {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (onStateReady && !stateReadyFired) {
|
|
95
|
-
stateReadyFired = true;
|
|
96
|
-
onStateReady();
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
channels() {
|
|
100
|
-
if (!dispatchP) {
|
|
101
|
-
// The factory runs INSIDE the chain: a synchronous throw must land in the cached rejection,
|
|
102
|
-
// not escape before `dispatchP` is assigned (which would silently re-run the activation).
|
|
103
|
-
dispatchP = Promise.resolve()
|
|
104
|
-
.then(deps.channels)
|
|
105
|
-
.then((surface) => router(surface.routes, surface.mounts));
|
|
106
|
-
dispatchP.catch(() => { }); // observed here so the CACHED rejection is never "unhandled"
|
|
37
|
+
const url = envelope.wake.url;
|
|
38
|
+
yield* Effect.try({
|
|
39
|
+
try: () => rememberWakeAlarmUrl(stateRoot, url),
|
|
40
|
+
catch: (cause) => new PortFailure(cause),
|
|
41
|
+
});
|
|
107
42
|
}
|
|
108
|
-
|
|
109
|
-
},
|
|
43
|
+
yield* stateReady;
|
|
44
|
+
}),
|
|
45
|
+
channels,
|
|
110
46
|
};
|
|
111
47
|
}
|
|
112
48
|
export function agentcoreRoutes(options) {
|
|
113
|
-
const { channels, agent, stateRoot, isBusy,
|
|
114
|
-
const activation = createActivation({
|
|
49
|
+
const { channels, agent, stateRoot, isBusy, fireRoutine, runRoutine, ingressSecret, onStateReady } = options;
|
|
50
|
+
const activation = createActivation({ stateRoot, onStateReady, channels });
|
|
115
51
|
const invokeHandler = createInvokeHandler(agent);
|
|
116
|
-
// Snapshot on the 0-in-flight edge: webhook channels ACK fast and finish the turn in the
|
|
117
|
-
// background, so "the request returned" is NOT when the state root settles.
|
|
118
|
-
if (stateSync) {
|
|
119
|
-
const off = onIdle(() => stateSync.save());
|
|
120
|
-
options.signal?.addEventListener("abort", off, { once: true });
|
|
121
|
-
}
|
|
122
52
|
const handleInvocation = async (req) => {
|
|
123
53
|
const body = await readBodyCapped(req, MAX_ENVELOPE_BYTES);
|
|
124
54
|
if ("tooLarge" in body)
|
|
@@ -133,49 +63,34 @@ export function agentcoreRoutes(options) {
|
|
|
133
63
|
if (envelope === null || typeof envelope !== "object" || typeof envelope.kind !== "string") {
|
|
134
64
|
return text(`need { "kind": ${ENVELOPE_KINDS.map((k) => `"${k}"`).join(" | ")}, ... }\n`, 400);
|
|
135
65
|
}
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
// forwarder's; anything else is the PUBLIC data plane, which may run exactly one kind (`invoke`)
|
|
139
|
-
// and may NOT carry internal fields — riding a `state` or `wake` URL on a public invoke would
|
|
140
|
-
// redirect the state snapshot (auth.json) or the alarm callback (the wake secret) to the caller.
|
|
141
|
-
// Internal fields are DROPPED rather than rejected: a public caller has no business knowing them.
|
|
142
|
-
const trusted = secretEquals(envelope.auth, ingressSecret);
|
|
66
|
+
// The forwarder, or another IAM principal? See `fromForwarder` for what that actually decides.
|
|
67
|
+
const trusted = fromForwarder(envelope, ingressSecret);
|
|
143
68
|
if (!trusted) {
|
|
144
|
-
|
|
69
|
+
// The IAM door's kinds: AWS authenticated this caller, so no ingress secret is expected or wanted.
|
|
70
|
+
if (envelope.kind !== "invoke" && envelope.kind !== "routine-run") {
|
|
145
71
|
log.warn(`[agentcore] rejected an unauthenticated "${envelope.kind}" envelope`);
|
|
146
72
|
return text("forbidden\n", 403);
|
|
147
73
|
}
|
|
148
74
|
envelope.wake = undefined;
|
|
149
|
-
envelope.state = undefined;
|
|
150
75
|
}
|
|
151
|
-
// Cross-deploy state: the platform wipes /mnt/state on every version update, so the durable copy
|
|
152
|
-
// must be pulled back BEFORE anything reads it.
|
|
153
76
|
try {
|
|
154
|
-
|
|
77
|
+
if (trusted)
|
|
78
|
+
await Effect.runPromise(activation.prepare(envelope).pipe(Effect.mapError((e) => e.cause)));
|
|
155
79
|
}
|
|
156
80
|
catch (e) {
|
|
157
|
-
log.error(`[agentcore]
|
|
158
|
-
// The probe is the deploy driver's verification channel: its diagnostics must survive the
|
|
159
|
-
//
|
|
160
|
-
// rides a transport-200 structured verdict; every other kind keeps the plain 503.
|
|
81
|
+
log.error(`[agentcore] activation failed: ${String(e)}`);
|
|
82
|
+
// The probe is the deploy driver's verification channel: its diagnostics must survive the forwarder, which
|
|
83
|
+
// folds a non-200 transport into an opaque 502.
|
|
161
84
|
if (envelope.kind === "probe")
|
|
162
|
-
return json({ ok: false, error: `
|
|
163
|
-
return text(`
|
|
85
|
+
return json({ ok: false, error: `activation failed: ${String(e)}` }, 200);
|
|
86
|
+
return text(`activation failed: ${String(e)}\n`, 503);
|
|
164
87
|
}
|
|
165
|
-
//
|
|
166
|
-
// whichever kind carries it, so a cold start woken by a schedule fire or an alarm poke still
|
|
167
|
-
// replays checkpointed turn intent. Two deliberate exceptions: `checkpoint` must push state even
|
|
168
|
-
// when a channel is broken, and a public `invoke` runs in its own isolated storage — constructing
|
|
169
|
-
// against THAT root would cache pre-restore emptiness for the ingress session. Failure policy is
|
|
170
|
-
// per kind below: webhook and wake-poke fail their request (503), the probe reports it
|
|
171
|
-
// structurally, and a schedule fire proceeds — cron does not consume channels, and letting an
|
|
172
|
-
// unrelated channel misconfiguration silence the clock would turn one fault into two (the error
|
|
173
|
-
// is logged here either way).
|
|
88
|
+
// Public invokes do not activate channels.
|
|
174
89
|
let constructionError;
|
|
175
90
|
let dispatch;
|
|
176
|
-
if (trusted && envelope.kind !== "
|
|
91
|
+
if (trusted && envelope.kind !== "invoke") {
|
|
177
92
|
try {
|
|
178
|
-
dispatch = await activation.channels();
|
|
93
|
+
dispatch = await Effect.runPromise(activation.channels.pipe(Effect.mapError((error) => error.cause)));
|
|
179
94
|
}
|
|
180
95
|
catch (e) {
|
|
181
96
|
constructionError = String(e);
|
|
@@ -202,13 +117,13 @@ export function agentcoreRoutes(options) {
|
|
|
202
117
|
? Buffer.from(bodyB64, "base64")
|
|
203
118
|
: undefined,
|
|
204
119
|
});
|
|
205
|
-
// A construction failure is the request's failure (503 through the forwarder, so the
|
|
206
|
-
//
|
|
120
|
+
// A construction failure is the request's failure (503 through the forwarder, so the platform retries and the
|
|
121
|
+
// operator sees the message), never a silently-empty channel.
|
|
207
122
|
if (!dispatch)
|
|
208
123
|
return text(`channel construction failed: ${constructionError ?? "unavailable"}\n`, 503);
|
|
209
124
|
const response = await dispatch(inner);
|
|
210
|
-
// Buffer the channel's ACK (webhook ACKs are small by design — the turn itself runs
|
|
211
|
-
//
|
|
125
|
+
// Buffer the channel's ACK (webhook ACKs are small by design — the turn itself runs fire-and-forget) and ride
|
|
126
|
+
// it inside the transport reply, byte-exact.
|
|
212
127
|
const replyBody = Buffer.from(await response.arrayBuffer());
|
|
213
128
|
const replyHeaders = {};
|
|
214
129
|
response.headers.forEach((value, key) => {
|
|
@@ -221,73 +136,57 @@ export function agentcoreRoutes(options) {
|
|
|
221
136
|
};
|
|
222
137
|
return json(reply, 200);
|
|
223
138
|
}
|
|
224
|
-
case "
|
|
225
|
-
const { name,
|
|
226
|
-
if (typeof name !== "string" || typeof
|
|
227
|
-
return text('
|
|
139
|
+
case "routine-fire": {
|
|
140
|
+
const { name, occurrence } = envelope;
|
|
141
|
+
if (typeof name !== "string" || typeof occurrence !== "string" || Number.isNaN(Date.parse(occurrence))) {
|
|
142
|
+
return text('routine-fire envelope needs { "name": string, "occurrence": ISO-date }\n', 400);
|
|
228
143
|
}
|
|
229
|
-
// No
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
//
|
|
235
|
-
// time out and drop the connection while the turn keeps running server-side. Count it as
|
|
236
|
-
// in-flight work so /ping holds the session (HealthyBusy) for the remainder.
|
|
144
|
+
// No schedules in this definition: nothing to fire, and no rule should have survived a deploy that removed
|
|
145
|
+
// them either.
|
|
146
|
+
if (!fireRoutine)
|
|
147
|
+
return text(`no routines in this deployment (routine-fire "${name}")\n`, 404);
|
|
148
|
+
// The whole agent turn runs inside this request — but the CALLER (the forwarder Lambda) may time out and drop
|
|
149
|
+
// the connection while the turn keeps running server-side.
|
|
237
150
|
const workDone = beginWork();
|
|
238
151
|
try {
|
|
239
|
-
|
|
240
|
-
return json(outcome, 200);
|
|
241
|
-
}
|
|
242
|
-
catch (e) {
|
|
243
|
-
if (e instanceof UnknownScheduleError)
|
|
244
|
-
return text(`${e.message}\n`, 404);
|
|
245
|
-
// A claim-state fault (unreadable/unwritable fires.json) — surface it as the request's
|
|
246
|
-
// failure so the external clock's logs carry it (fail visibly, never a silent absorb).
|
|
247
|
-
log.error(`[agentcore] schedule-fire ${name} failed: ${String(e)}`);
|
|
248
|
-
return text(`schedule-fire failed: ${String(e)}\n`, 500);
|
|
152
|
+
return await fireRoutine(name, new Date(occurrence));
|
|
249
153
|
}
|
|
250
154
|
finally {
|
|
251
155
|
workDone();
|
|
252
156
|
}
|
|
253
157
|
}
|
|
254
|
-
case "
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
158
|
+
case "routine-run": {
|
|
159
|
+
const { name } = envelope;
|
|
160
|
+
if (typeof name !== "string" || name === "") {
|
|
161
|
+
return text('routine-run envelope needs { "name": string }\n', 400);
|
|
162
|
+
}
|
|
163
|
+
if (!runRoutine)
|
|
164
|
+
return text(`no routines in this deployment (routine-run "${name}")\n`, 404);
|
|
165
|
+
// The whole agent turn runs inside this request; the caller may time out while it keeps running.
|
|
166
|
+
const workDone = beginWork();
|
|
260
167
|
try {
|
|
261
|
-
|
|
262
|
-
// protected, so it reports what actually happened — `written: false` (nothing to write)
|
|
263
|
-
// reads differently from `written: true`, and a failure is a failure.
|
|
264
|
-
return json(await stateSync.checkpoint(), 200);
|
|
168
|
+
return await runRoutine(name);
|
|
265
169
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return text(`checkpoint failed: ${String(e)}\n`, 500);
|
|
170
|
+
finally {
|
|
171
|
+
workDone();
|
|
269
172
|
}
|
|
270
173
|
}
|
|
271
174
|
case "wake-poke": {
|
|
272
|
-
// The poke's job is DONE by arriving: the invocation woke (or kept awake) the container, and
|
|
273
|
-
//
|
|
274
|
-
// initialization above already resolved construction (replaying checkpointed turn intent),
|
|
275
|
-
// and its failure is this request's failure so the alarm's log line names it.
|
|
175
|
+
// The poke's job is DONE by arriving: the invocation woke (or kept awake) the container, and the wake pump
|
|
176
|
+
// (boot drain + 30s poll) fires whatever is due.
|
|
276
177
|
if (constructionError !== undefined)
|
|
277
178
|
return text(`channel construction failed: ${constructionError}\n`, 503);
|
|
278
179
|
return json({ ok: true }, 200);
|
|
279
180
|
}
|
|
280
181
|
case "probe": {
|
|
281
|
-
// The structured verdict (transport-200 — see the envelope doc)
|
|
282
|
-
// through the forwarder's reserved path, so the error text survives the hop that turns any
|
|
283
|
-
// non-200 transport into an opaque 502.
|
|
182
|
+
// The structured verdict (transport-200 — see the envelope doc).
|
|
284
183
|
return json(constructionError === undefined
|
|
285
184
|
? { ok: true }
|
|
286
185
|
: { ok: false, error: `channel construction failed: ${constructionError}` }, 200);
|
|
287
186
|
}
|
|
288
187
|
case "invoke": {
|
|
289
|
-
// Reuse the HTTP channel's handler wholesale (SSE, cancellation, backpressure) by handing it
|
|
290
|
-
//
|
|
188
|
+
// Reuse the HTTP channel's handler wholesale (SSE, cancellation, backpressure) by handing it the shape it
|
|
189
|
+
// already validates — one protocol, one implementation.
|
|
291
190
|
const inner = new Request("http://agentcore.local/invoke", {
|
|
292
191
|
method: "POST",
|
|
293
192
|
headers: jsonHeaders,
|
|
@@ -299,53 +198,20 @@ export function agentcoreRoutes(options) {
|
|
|
299
198
|
return text(`unknown envelope kind "${envelope.kind}"\n`, 400);
|
|
300
199
|
}
|
|
301
200
|
};
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
// Counting it as in-flight work would close that and cost more than it buys — the stream drains at
|
|
308
|
-
// the CLIENT's pace, so one parked reader pins /ping at HealthyBusy and defeats the idle reclaim,
|
|
309
|
-
// which is the quota-exhaustion failure the ping contract below warns about. The kinds that carry
|
|
310
|
-
// conversations (webhook, schedule-fire, wake-poke) all run their turn inside the request or
|
|
311
|
-
// through `beginWork`, so they land on one of the two edges; `invoke` is the direct/debug door.
|
|
312
|
-
const invocations = async (req) => {
|
|
313
|
-
const response = await handleInvocation(req);
|
|
314
|
-
if (stateSync && !isBusy())
|
|
315
|
-
stateSync.save();
|
|
316
|
-
return response;
|
|
317
|
-
};
|
|
318
|
-
// The Runtime ping contract: Healthy = reclaimable, HealthyBusy = keep the session alive
|
|
319
|
-
// (background turns in flight). `time_of_last_update` is REQUIRED for the keep-alive to work,
|
|
320
|
-
// despite the contract documenting it as optional ("If you omit the field, the platform tracks
|
|
321
|
-
// status changes on its own"): measured on a live Runtime (us-east-1, 2026-08-04), the platform's
|
|
322
|
-
// idle measurement reads ONLY this field — with it omitted, a session polling every ~2s and
|
|
323
|
-
// receiving HealthyBusy 200s was still reclaimed at exactly IdleRuntimeSessionTimeout after the
|
|
324
|
-
// last InvokeAgentRuntime, mid-turn, 2s after the last HealthyBusy answer; with the field present
|
|
325
|
-
// the same turn survived 3.5× the idle timeout with zero invocations and completed. The value
|
|
326
|
-
// updates ONLY on a real status change: a timestamp advancing on every ping declares a perpetual
|
|
327
|
-
// status change, so the idle timeout never fires and dead-idle sessions live to MaxLifetime
|
|
328
|
-
// (quota exhaustion — the failure mode the contract's warning describes).
|
|
201
|
+
return { "POST /invocations": handleInvocation, "GET /ping": agentcorePing(isBusy) };
|
|
202
|
+
}
|
|
203
|
+
export function agentcorePing(isBusy) {
|
|
204
|
+
// The Runtime ping contract: Healthy = reclaimable, HealthyBusy = keep the session alive (background turns in
|
|
205
|
+
// flight).
|
|
329
206
|
let lastStatus = "Healthy";
|
|
330
207
|
let lastTransition = Math.floor(Date.now() / 1000);
|
|
331
|
-
return {
|
|
332
|
-
"
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
return json({ status, time_of_last_update: lastTransition }, 200);
|
|
341
|
-
},
|
|
208
|
+
return () => {
|
|
209
|
+
const status = isBusy() ? "HealthyBusy" : "Healthy";
|
|
210
|
+
if (status !== lastStatus) {
|
|
211
|
+
lastTransition = Math.floor(Date.now() / 1000);
|
|
212
|
+
log.debug(`[agentcore] ping status: ${lastStatus} → ${status}`);
|
|
213
|
+
lastStatus = status;
|
|
214
|
+
}
|
|
215
|
+
return json({ status, time_of_last_update: lastTransition }, 200);
|
|
342
216
|
};
|
|
343
217
|
}
|
|
344
|
-
/** Thrown by the mount-site `fire` binding when the envelope names a schedule this workspace does
|
|
345
|
-
* not have — the adapter maps it to 404 (deploy drift stays visible in the external clock's logs). */
|
|
346
|
-
export class UnknownScheduleError extends Error {
|
|
347
|
-
constructor(name) {
|
|
348
|
-
super(`unknown schedule "${name}"`);
|
|
349
|
-
this.name = "UnknownScheduleError";
|
|
350
|
-
}
|
|
351
|
-
}
|
package/dist/channels/body.d.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Refuse a request to one of OUR routes that does not declare a JSON body — `undefined` when it does.
|
|
3
|
+
*
|
|
4
|
+
* This is what makes "a foreign origin simply gets no CORS headers" safe. Withholding the headers stops a page from
|
|
5
|
+
* READING the reply; it does not stop the request. A cross-origin POST carrying `text/plain` (or a form's
|
|
6
|
+
* `multipart/form-data`) is a CORS *simple request*: no preflight, sent regardless, and the turn it asks for runs.
|
|
7
|
+
* Demanding `application/json` takes the request out of that class, so the browser must preflight it — and a
|
|
8
|
+
* preflight we do not answer is a request that is never sent.
|
|
9
|
+
*
|
|
10
|
+
* Asked of the ROUTE, never of "does this have a body": the control plane reads an empty body as `{}`, so a
|
|
11
|
+
* body-less POST would otherwise walk straight through the gate.
|
|
12
|
+
*
|
|
13
|
+
* NOT applied to channel routes. Their caller is a platform's server with its own content types (Slack posts
|
|
14
|
+
* `application/x-www-form-urlencoded` for some payloads) and its own signature check, and a page cannot forge that.
|
|
4
15
|
*/
|
|
16
|
+
export declare function refuseNonJsonBody(req: Request): Response | undefined;
|
|
17
|
+
/** Read a request body with a hard byte cap (real bytes). */
|
|
5
18
|
export declare function readBodyCapped(req: Request, max: number): Promise<{
|
|
6
19
|
text: string;
|
|
7
20
|
} | {
|
package/dist/channels/body.js
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
import { text } from "./respond.js";
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
3
|
+
* Refuse a request to one of OUR routes that does not declare a JSON body — `undefined` when it does.
|
|
4
|
+
*
|
|
5
|
+
* This is what makes "a foreign origin simply gets no CORS headers" safe. Withholding the headers stops a page from
|
|
6
|
+
* READING the reply; it does not stop the request. A cross-origin POST carrying `text/plain` (or a form's
|
|
7
|
+
* `multipart/form-data`) is a CORS *simple request*: no preflight, sent regardless, and the turn it asks for runs.
|
|
8
|
+
* Demanding `application/json` takes the request out of that class, so the browser must preflight it — and a
|
|
9
|
+
* preflight we do not answer is a request that is never sent.
|
|
10
|
+
*
|
|
11
|
+
* Asked of the ROUTE, never of "does this have a body": the control plane reads an empty body as `{}`, so a
|
|
12
|
+
* body-less POST would otherwise walk straight through the gate.
|
|
13
|
+
*
|
|
14
|
+
* NOT applied to channel routes. Their caller is a platform's server with its own content types (Slack posts
|
|
15
|
+
* `application/x-www-form-urlencoded` for some payloads) and its own signature check, and a page cannot forge that.
|
|
4
16
|
*/
|
|
17
|
+
export function refuseNonJsonBody(req) {
|
|
18
|
+
// Parameters are part of the header (`application/json; charset=utf-8`), so compare the media type alone.
|
|
19
|
+
const mediaType = (req.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase();
|
|
20
|
+
if (mediaType === "application/json")
|
|
21
|
+
return undefined;
|
|
22
|
+
// Says what is required, and does not echo what arrived: reflecting a request header into a response body is a
|
|
23
|
+
// habit worth not having, and the sender already knows what they sent.
|
|
24
|
+
return text("content-type must be application/json\n", 415);
|
|
25
|
+
}
|
|
26
|
+
/** Read a request body with a hard byte cap (real bytes). */
|
|
5
27
|
export async function readBodyCapped(req, max) {
|
|
6
28
|
if (!req.body)
|
|
7
29
|
return { text: "" };
|
package/dist/channels/busy.d.ts
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SHARED process-wide in-flight work signal. Channels ACK a webhook fast and run the turn
|
|
3
|
-
* fire-and-forget on this process's event loop (serve.ts) — so "is this process busy?" is not
|
|
4
|
-
* derivable from open HTTP requests. The two shared execution primitives (turn-queue chains,
|
|
5
|
-
* task-tracker side tasks) report here; a serving surface that must stay alive while background
|
|
6
|
-
* work runs (the AgentCore adapter's /ping → HealthyBusy) reads it.
|
|
7
|
-
*
|
|
8
|
-
* Deliberately a counter, not a registry: consumers need only "any work in flight?"; keeping the
|
|
9
|
-
* module dependency-free lets both channel primitives import it without cycles.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Mark one unit of background work as started. Returns its completion callback — idempotent, so a
|
|
13
|
-
* caller may safely settle it from multiple cleanup paths (finally + catch) without double-counting.
|
|
14
|
-
*/
|
|
1
|
+
/** Mark background work as started. */
|
|
15
2
|
export declare function beginWork(): () => void;
|
|
16
|
-
/**
|
|
17
|
-
* Subscribe to the 0-in-flight edge: the moment the process finishes its last background turn.
|
|
18
|
-
* Returns an unsubscribe. Used by the AgentCore adapter to push its state snapshot exactly when the
|
|
19
|
-
* state root has settled and before the platform may reclaim the idle microVM.
|
|
20
|
-
*/
|
|
21
|
-
export declare function onIdle(listener: () => void): () => void;
|
|
22
|
-
/** How many units of background work are currently in flight (0 = idle). */
|
|
23
3
|
export declare function activeWork(): number;
|