@fastagent-sh/fastagent 0.21.1 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -12
- package/dist/agent.d.ts +24 -37
- package/dist/agent.js +7 -15
- package/dist/atomic-write.d.ts +13 -17
- package/dist/atomic-write.js +18 -29
- package/dist/bind.d.ts +12 -22
- package/dist/bind.js +18 -27
- package/dist/channel.d.ts +11 -33
- package/dist/channels/agentcore-limits.d.ts +2 -13
- package/dist/channels/agentcore-limits.js +2 -13
- package/dist/channels/agentcore-protocol.d.ts +67 -58
- package/dist/channels/agentcore-protocol.js +23 -15
- package/dist/channels/agentcore-service.d.ts +19 -33
- package/dist/channels/agentcore-service.js +176 -59
- package/dist/channels/agentcore.d.ts +39 -33
- package/dist/channels/agentcore.js +90 -224
- package/dist/channels/body.d.ts +15 -2
- package/dist/channels/body.js +24 -2
- package/dist/channels/busy.d.ts +1 -21
- package/dist/channels/busy.js +2 -39
- package/dist/channels/control.d.ts +17 -62
- package/dist/channels/control.js +74 -182
- package/dist/channels/define-channel.d.ts +39 -0
- package/dist/channels/define-channel.js +9 -0
- package/dist/channels/discover.d.ts +10 -31
- package/dist/channels/discover.js +37 -31
- package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
- package/dist/channels/feishu/bootstrap-token.js +7 -23
- package/dist/channels/feishu/card.d.ts +14 -28
- package/dist/channels/feishu/card.js +16 -30
- package/dist/channels/feishu/cloud.d.ts +1 -5
- package/dist/channels/feishu/context-buffer.d.ts +4 -23
- package/dist/channels/feishu/context-buffer.js +8 -31
- package/dist/channels/feishu/crypto.d.ts +10 -5
- package/dist/channels/feishu/crypto.js +12 -19
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu-api.js +5 -4
- package/dist/channels/feishu/feishu.d.ts +4 -10
- package/dist/channels/feishu/feishu.js +113 -227
- package/dist/channels/feishu/invoke-turn.d.ts +5 -4
- package/dist/channels/feishu/invoke-turn.js +36 -54
- package/dist/channels/feishu/model.d.ts +4 -21
- package/dist/channels/feishu/model.js +1 -8
- package/dist/channels/feishu/normalize.d.ts +2 -10
- package/dist/channels/feishu/normalize.js +7 -42
- package/dist/channels/feishu/parse.d.ts +9 -29
- package/dist/channels/feishu/parse.js +8 -26
- package/dist/channels/feishu/preview.d.ts +12 -14
- package/dist/channels/feishu/preview.js +108 -207
- package/dist/channels/feishu/register-app.d.ts +8 -23
- package/dist/channels/feishu/register-app.js +7 -20
- package/dist/channels/feishu/register-webhook.d.ts +2 -9
- package/dist/channels/feishu/register-webhook.js +9 -33
- package/dist/channels/feishu/scaffold/channel.ts +27 -19
- package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/feishu/setup-mode.d.ts +11 -36
- package/dist/channels/feishu/setup-mode.js +7 -26
- package/dist/channels/feishu/ws-ingress.d.ts +1 -2
- package/dist/channels/feishu/ws-ingress.js +6 -8
- package/dist/channels/http.d.ts +15 -15
- package/dist/channels/http.js +19 -9
- package/dist/channels/kit/attachment-path.d.ts +4 -5
- package/dist/channels/kit/attachment-path.js +10 -34
- package/dist/channels/kit/context-buffer.d.ts +14 -21
- package/dist/channels/kit/context-buffer.js +25 -38
- package/dist/channels/kit/delivery.d.ts +35 -0
- package/dist/channels/kit/delivery.js +129 -0
- package/dist/channels/kit/event-stream.d.ts +5 -0
- package/dist/channels/kit/event-stream.js +29 -0
- package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
- package/dist/channels/kit/invoke-turn-kit.js +89 -32
- package/dist/channels/kit/preview-kit.d.ts +18 -72
- package/dist/channels/kit/preview-kit.js +22 -88
- package/dist/channels/kit/signature.d.ts +3 -9
- package/dist/channels/kit/signature.js +3 -9
- package/dist/channels/kit/state.d.ts +1 -2
- package/dist/channels/kit/state.js +5 -14
- package/dist/channels/kit/stop-command.d.ts +1 -2
- package/dist/channels/kit/stop-command.js +6 -9
- package/dist/channels/kit/tasks.d.ts +1 -1
- package/dist/channels/kit/tasks.js +8 -21
- package/dist/channels/kit/text.d.ts +2 -16
- package/dist/channels/kit/text.js +23 -31
- package/dist/channels/kit/thread-participants.d.ts +4 -17
- package/dist/channels/kit/thread-participants.js +8 -55
- package/dist/channels/kit/transport.d.ts +26 -0
- package/dist/channels/kit/transport.js +20 -0
- package/dist/channels/kit/turn-queue.d.ts +7 -11
- package/dist/channels/kit/turn-queue.js +21 -39
- package/dist/channels/kit/turn-runner.d.ts +32 -18
- package/dist/channels/kit/turn-runner.js +98 -53
- package/dist/channels/kit/turn-store.d.ts +32 -32
- package/dist/channels/kit/turn-store.js +45 -88
- package/dist/channels/lark/lark.d.ts +1 -5
- package/dist/channels/lark/onboard.d.ts +3 -12
- package/dist/channels/lark/onboard.js +3 -13
- package/dist/channels/lark/scaffold/channel.ts +27 -19
- package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/registration.d.ts +9 -35
- package/dist/channels/registration.js +6 -37
- package/dist/channels/secret.js +2 -5
- package/dist/channels/serve.d.ts +47 -44
- package/dist/channels/serve.js +165 -101
- package/dist/channels/slack/config-api.d.ts +4 -27
- package/dist/channels/slack/config-api.js +5 -34
- package/dist/channels/slack/context-buffer.d.ts +2 -3
- package/dist/channels/slack/context-buffer.js +5 -5
- package/dist/channels/slack/invoke-turn.d.ts +3 -1
- package/dist/channels/slack/invoke-turn.js +25 -34
- package/dist/channels/slack/manifest.js +1 -3
- package/dist/channels/slack/model.d.ts +1 -2
- package/dist/channels/slack/model.js +1 -1
- package/dist/channels/slack/onboard.d.ts +4 -3
- package/dist/channels/slack/onboard.js +8 -11
- package/dist/channels/slack/onboarding-state.d.ts +3 -5
- package/dist/channels/slack/onboarding-state.js +9 -6
- package/dist/channels/slack/parse.d.ts +4 -13
- package/dist/channels/slack/parse.js +7 -28
- package/dist/channels/slack/preview.d.ts +12 -2
- package/dist/channels/slack/preview.js +292 -269
- package/dist/channels/slack/reaction.d.ts +11 -3
- package/dist/channels/slack/reaction.js +19 -8
- package/dist/channels/slack/register-webhook.js +4 -7
- package/dist/channels/slack/scaffold/channel.ts +20 -13
- package/dist/channels/slack/scaffold/slack-send.ts +2 -2
- package/dist/channels/slack/setup-server.d.ts +1 -5
- package/dist/channels/slack/setup-server.js +1 -5
- package/dist/channels/slack/shared-api.d.ts +1 -4
- package/dist/channels/slack/shared-api.js +2 -13
- package/dist/channels/slack/slack-api.d.ts +2 -1
- package/dist/channels/slack/slack-api.js +6 -4
- package/dist/channels/slack/slack.d.ts +11 -14
- package/dist/channels/slack/slack.js +121 -122
- package/dist/channels/sse.d.ts +1 -1
- package/dist/channels/sse.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +7 -22
- package/dist/channels/telegram/context-buffer.js +6 -23
- package/dist/channels/telegram/invoke-turn.d.ts +7 -7
- package/dist/channels/telegram/invoke-turn.js +24 -43
- package/dist/channels/telegram/parse.d.ts +24 -51
- package/dist/channels/telegram/parse.js +32 -84
- package/dist/channels/telegram/preview.d.ts +10 -1
- package/dist/channels/telegram/preview.js +79 -100
- package/dist/channels/telegram/register-webhook.js +5 -23
- package/dist/channels/telegram/scaffold/channel.ts +25 -18
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
- package/dist/channels/telegram/telegram-api.d.ts +4 -3
- package/dist/channels/telegram/telegram-api.js +9 -7
- package/dist/channels/telegram/telegram.d.ts +4 -18
- package/dist/channels/telegram/telegram.js +38 -94
- package/dist/channels/telegram/turn-store.d.ts +11 -14
- package/dist/channels/telegram/turn-store.js +8 -6
- package/dist/channels/wait-health.d.ts +4 -6
- package/dist/channels/wait-health.js +10 -14
- package/dist/cli/add-feishu.d.ts +2 -12
- package/dist/cli/add-feishu.js +24 -71
- package/dist/cli/add-slack.d.ts +5 -3
- package/dist/cli/add-slack.js +53 -76
- package/dist/cli/auth-view.d.ts +2 -16
- package/dist/cli/auth-view.js +2 -16
- package/dist/cli/commands/add.js +25 -40
- package/dist/cli/commands/chat.d.ts +0 -1
- package/dist/cli/commands/chat.js +5 -6
- package/dist/cli/commands/deploy/agentcore.js +60 -65
- package/dist/cli/commands/deploy/docker.js +50 -23
- package/dist/cli/commands/deploy/fly.js +23 -61
- package/dist/cli/commands/deploy/railway.js +23 -18
- package/dist/cli/commands/deploy/shared.d.ts +38 -57
- package/dist/cli/commands/deploy/shared.js +40 -59
- package/dist/cli/commands/deploy.d.ts +10 -27
- package/dist/cli/commands/deploy.js +38 -60
- package/dist/cli/commands/destroy.d.ts +4 -0
- package/dist/cli/commands/destroy.js +56 -0
- package/dist/cli/commands/dev.d.ts +2 -1
- package/dist/cli/commands/dev.js +20 -23
- package/dist/cli/commands/info.d.ts +0 -2
- package/dist/cli/commands/info.js +83 -49
- package/dist/cli/commands/init.d.ts +1 -2
- package/dist/cli/commands/init.js +16 -55
- package/dist/cli/commands/invoke.d.ts +0 -1
- package/dist/cli/commands/invoke.js +5 -7
- package/dist/cli/commands/login.d.ts +2 -1
- package/dist/cli/commands/login.js +33 -38
- package/dist/cli/commands/logs.js +3 -4
- package/dist/cli/commands/models.js +1 -5
- package/dist/cli/commands/routine-run.d.ts +6 -0
- package/dist/cli/commands/routine-run.js +47 -0
- package/dist/cli/commands/routine.d.ts +25 -0
- package/dist/cli/commands/routine.js +122 -0
- package/dist/cli/commands/start.d.ts +31 -3
- package/dist/cli/commands/start.js +131 -81
- package/dist/cli/commands/tool.js +26 -12
- package/dist/cli/fail.d.ts +13 -23
- package/dist/cli/fail.js +20 -23
- package/dist/cli/invoke-stream.d.ts +1 -8
- package/dist/cli/kernel.d.ts +6 -38
- package/dist/cli/kernel.js +14 -77
- package/dist/cli/models-view.d.ts +1 -9
- package/dist/cli/models-view.js +11 -22
- package/dist/cli/program.d.ts +5 -6
- package/dist/cli/program.js +120 -157
- package/dist/cli/serve.d.ts +57 -41
- package/dist/cli/serve.js +94 -121
- package/dist/cli/shared.d.ts +15 -61
- package/dist/cli/shared.js +55 -181
- package/dist/cli.js +1 -8
- package/dist/collect.d.ts +6 -0
- package/dist/collect.js +6 -0
- package/dist/core.d.ts +4 -1
- package/dist/core.js +9 -7
- package/dist/declared-secrets.d.ts +55 -0
- package/dist/declared-secrets.js +88 -0
- package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
- package/dist/deploy/agentcore/aws-cli.js +64 -0
- package/dist/deploy/agentcore/destroy.d.ts +51 -0
- package/dist/deploy/agentcore/destroy.js +273 -0
- package/dist/deploy/agentcore/forwarder.js +28 -79
- package/dist/deploy/agentcore/logs.d.ts +2 -18
- package/dist/deploy/agentcore/logs.js +27 -51
- package/dist/deploy/agentcore/plan.d.ts +85 -105
- package/dist/deploy/agentcore/plan.js +152 -314
- package/dist/deploy/agentcore/run.d.ts +28 -28
- package/dist/deploy/agentcore/run.js +227 -306
- package/dist/deploy/agentcore/zip.d.ts +2 -15
- package/dist/deploy/agentcore/zip.js +3 -17
- package/dist/deploy/build-context.d.ts +21 -0
- package/dist/deploy/build-context.js +137 -0
- package/dist/deploy/channel-ingress.d.ts +26 -39
- package/dist/deploy/channel-ingress.js +57 -28
- package/dist/deploy/container.d.ts +20 -34
- package/dist/deploy/container.js +49 -100
- package/dist/deploy/docker/plan.d.ts +12 -11
- package/dist/deploy/docker/plan.js +43 -24
- package/dist/deploy/docker/run.d.ts +22 -25
- package/dist/deploy/docker/run.js +39 -37
- package/dist/deploy/fly/plan.d.ts +15 -44
- package/dist/deploy/fly/plan.js +41 -73
- package/dist/deploy/fly/run.d.ts +16 -51
- package/dist/deploy/fly/run.js +42 -95
- package/dist/deploy/hosts.d.ts +24 -3
- package/dist/deploy/hosts.js +24 -3
- package/dist/deploy/preflight.d.ts +47 -41
- package/dist/deploy/preflight.js +282 -383
- package/dist/deploy/railway/plan.d.ts +16 -42
- package/dist/deploy/railway/plan.js +42 -60
- package/dist/deploy/railway/run.d.ts +30 -68
- package/dist/deploy/railway/run.js +86 -100
- package/dist/deploy/registration-gate.d.ts +6 -13
- package/dist/deploy/residency.d.ts +37 -0
- package/dist/deploy/residency.js +24 -0
- package/dist/deploy/runner.d.ts +10 -9
- package/dist/deploy/runner.js +7 -18
- package/dist/deploy/secrets.d.ts +40 -41
- package/dist/deploy/secrets.js +117 -103
- package/dist/deploy/workspace.d.ts +50 -0
- package/dist/deploy/workspace.js +197 -0
- package/dist/dev-supervisor.d.ts +1 -9
- package/dist/dev-supervisor.js +21 -64
- package/dist/effect-port.d.ts +50 -0
- package/dist/effect-port.js +77 -0
- package/dist/engines/pi/agent-session-factory.d.ts +50 -69
- package/dist/engines/pi/agent-session-factory.js +249 -146
- package/dist/engines/pi/auth.d.ts +15 -11
- package/dist/engines/pi/auth.js +96 -99
- package/dist/engines/pi/chat.d.ts +1 -5
- package/dist/engines/pi/chat.js +2 -14
- package/dist/engines/pi/config.d.ts +53 -51
- package/dist/engines/pi/config.js +83 -92
- package/dist/engines/pi/create.d.ts +60 -75
- package/dist/engines/pi/create.js +108 -141
- package/dist/engines/pi/definition.d.ts +14 -58
- package/dist/engines/pi/definition.js +21 -85
- package/dist/engines/pi/invoke-session.d.ts +5 -25
- package/dist/engines/pi/invoke-session.js +233 -244
- package/dist/engines/pi/login.d.ts +95 -35
- package/dist/engines/pi/login.js +259 -107
- package/dist/engines/pi/machine.d.ts +31 -0
- package/dist/engines/pi/machine.js +106 -0
- package/dist/engines/pi/models.d.ts +70 -89
- package/dist/engines/pi/models.js +188 -81
- package/dist/engines/pi/open.d.ts +65 -53
- package/dist/engines/pi/open.js +129 -69
- package/dist/engines/pi/report.d.ts +3 -12
- package/dist/engines/pi/report.js +2 -16
- package/dist/engines/pi/search-tools.d.ts +1 -9
- package/dist/engines/pi/search-tools.js +26 -57
- package/dist/engines/pi/service.d.ts +2 -16
- package/dist/engines/pi/service.js +2 -16
- package/dist/engines/pi/session-builder.d.ts +5 -9
- package/dist/engines/pi/session-builder.js +44 -82
- package/dist/engines/pi/session-control.js +286 -269
- package/dist/engines/pi/session-effects.d.ts +14 -0
- package/dist/engines/pi/session-effects.js +30 -0
- package/dist/engines/pi/session-inheritance.d.ts +9 -18
- package/dist/engines/pi/session-inheritance.js +103 -160
- package/dist/engines/pi/session-markers.d.ts +31 -30
- package/dist/engines/pi/session-markers.js +26 -25
- package/dist/engines/pi/session-settings.d.ts +6 -22
- package/dist/engines/pi/session-settings.js +10 -21
- package/dist/engines/pi/session-store.d.ts +26 -92
- package/dist/engines/pi/session-store.js +96 -310
- package/dist/engines/pi/tool-context.d.ts +17 -45
- package/dist/engines/pi/tool-context.js +7 -26
- package/dist/engines/pi/tool.d.ts +45 -36
- package/dist/engines/pi/tool.js +43 -57
- package/dist/engines/pi/turn-kit.d.ts +14 -52
- package/dist/engines/pi/turn-kit.js +18 -67
- package/dist/engines/pi/wake-tool.d.ts +2 -10
- package/dist/engines/pi/wake-tool.js +7 -27
- package/dist/env.d.ts +27 -28
- package/dist/env.js +45 -50
- package/dist/index.js +0 -2
- package/dist/lark.d.ts +1 -2
- package/dist/lark.js +1 -2
- package/dist/loader.d.ts +14 -55
- package/dist/loader.js +24 -43
- package/dist/log.d.ts +2 -12
- package/dist/log.js +3 -24
- package/dist/node.d.ts +2 -15
- package/dist/node.js +2 -18
- package/dist/observe.d.ts +2 -8
- package/dist/open-url.d.ts +1 -1
- package/dist/open-url.js +1 -1
- package/dist/paths.d.ts +70 -134
- package/dist/paths.js +165 -232
- package/dist/pi.d.ts +4 -6
- package/dist/pi.js +3 -3
- package/dist/proxy.d.ts +16 -6
- package/dist/proxy.js +52 -16
- package/dist/runtime.d.ts +18 -12
- package/dist/runtime.js +18 -11
- package/dist/scaffold/add-channel.d.ts +14 -31
- package/dist/scaffold/add-channel.js +48 -112
- package/dist/scaffold/init.d.ts +9 -31
- package/dist/scaffold/init.js +45 -138
- package/dist/scaffold/templates/env.example +6 -4
- package/dist/scaffold/templates/fastagent.config.ts +36 -0
- package/dist/scaffold/templates.d.ts +3 -3
- package/dist/scaffold/templates.js +5 -8
- package/dist/scaffold/vendor-skill.d.ts +2 -5
- package/dist/scaffold/vendor-skill.js +19 -29
- package/dist/schedule/cron.d.ts +9 -5
- package/dist/schedule/cron.js +9 -11
- package/dist/schedule/discover.d.ts +12 -12
- package/dist/schedule/discover.js +43 -34
- package/dist/schedule/routine.d.ts +43 -0
- package/dist/schedule/routine.js +32 -0
- package/dist/schedule/run.d.ts +55 -0
- package/dist/schedule/run.js +146 -0
- package/dist/schedule/scheduler.d.ts +44 -55
- package/dist/schedule/scheduler.js +291 -265
- package/dist/schedule/state.d.ts +105 -10
- package/dist/schedule/state.js +293 -19
- package/dist/schedule/wake-alarm.d.ts +10 -23
- package/dist/schedule/wake-alarm.js +91 -121
- package/dist/schedule/wakeups.d.ts +23 -39
- package/dist/schedule/wakeups.js +33 -75
- package/dist/secrets-gate.d.ts +32 -0
- package/dist/secrets-gate.js +33 -0
- package/dist/service.d.ts +137 -98
- package/dist/service.js +250 -252
- package/dist/session-remote.d.ts +33 -44
- package/dist/session-remote.js +203 -192
- package/dist/session.d.ts +166 -229
- package/dist/session.js +33 -55
- package/dist/tunnel.d.ts +8 -36
- package/dist/tunnel.js +24 -65
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +10 -16
- package/dist/channels/agentcore-state.d.ts +0 -77
- package/dist/channels/agentcore-state.js +0 -276
- package/dist/channels/github/github.d.ts +0 -31
- package/dist/channels/github/github.js +0 -89
- package/dist/channels/github/scaffold/channel.ts +0 -25
- package/dist/cli/commands/attach.d.ts +0 -108
- package/dist/cli/commands/attach.js +0 -637
- package/dist/cli/commands/fire.d.ts +0 -7
- package/dist/cli/commands/fire.js +0 -38
- package/dist/cli/commands/schedule.d.ts +0 -12
- package/dist/cli/commands/schedule.js +0 -87
- package/dist/github.d.ts +0 -3
- package/dist/github.js +0 -2
- package/dist/scaffold/templates/fastagent.config.mjs +0 -23
- package/dist/schedule/audit.d.ts +0 -18
- package/dist/schedule/audit.js +0 -57
- package/dist/schedule/schedule.d.ts +0 -35
- package/dist/schedule/schedule.js +0 -23
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-deploy durability for the state root on AgentCore.
|
|
3
|
-
*
|
|
4
|
-
* WHY THIS EXISTS: AgentCore's managed SessionStorage — the `/mnt/state` mount — is reset on every
|
|
5
|
-
* runtime VERSION UPDATE, i.e. on EVERY deploy ("On runtime version update: Data wiped — fresh file
|
|
6
|
-
* system on next invoke", AWS file-system configuration docs), and again after 14 idle days. The
|
|
7
|
-
* mount is therefore a fast LOCAL disk, not the source of truth. Without this module a deploy
|
|
8
|
-
* silently resurrects the agent with no sessions, no channel dedup, no pending wake-ups — and the
|
|
9
|
-
* wake ALARM, which lives in the operator's EventBridge and survives independently, still fires into
|
|
10
|
-
* that empty store: a miss with no error anywhere (exactly the silent-failure class the repo's
|
|
11
|
-
* fail-visibly rule exists to prevent).
|
|
12
|
-
*
|
|
13
|
-
* The durable copy is ONE S3 object reached through PRESIGNED URLS minted per-envelope by the
|
|
14
|
-
* forwarder Lambda. The container holds no AWS credentials (the platform injects none — verified on
|
|
15
|
-
* a live deployment) and stays AWS-SDK-free: a snapshot is one `fetch` GET and one `fetch` PUT.
|
|
16
|
-
*
|
|
17
|
-
* Format: gzip(JSON `{ v, files: { relPath: base64 } }`). Deliberately NOT tar — the state root is a
|
|
18
|
-
* handful of small JSON/JSONL files, and a single self-describing object makes restore ATOMIC: a
|
|
19
|
-
* half-applied state root is far worse than a slightly stale one.
|
|
20
|
-
*/
|
|
21
|
-
import { Buffer } from "node:buffer";
|
|
22
|
-
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
23
|
-
import { dirname, join, relative, sep } from "node:path";
|
|
24
|
-
import { gunzipSync, gzipSync } from "node:zlib";
|
|
25
|
-
import { log } from "../log.js";
|
|
26
|
-
import { beginWork } from "./busy.js";
|
|
27
|
-
/** Snapshot envelope version — an unknown version fails the restore loudly (never a silent skip). */
|
|
28
|
-
export const SNAPSHOT_VERSION = 1;
|
|
29
|
-
/** Refuse to pack beyond this (before gzip): a runaway state root would OOM the microVM silently. */
|
|
30
|
-
export const MAX_SNAPSHOT_BYTES = 64 << 20;
|
|
31
|
-
/** Warn past this — the operator should know the snapshot is getting expensive to round-trip. */
|
|
32
|
-
const WARN_SNAPSHOT_BYTES = 16 << 20;
|
|
33
|
-
/** Upload deadline. Generous (a large snapshot on a cold network) but finite. */
|
|
34
|
-
const PUT_TIMEOUT_MS = 60_000;
|
|
35
|
-
/**
|
|
36
|
-
* Files the snapshot must NOT carry. `control.json` is a PER-BOOT artifact (this process's control
|
|
37
|
-
* URL + token, written once the port is known): snapshotting it would hand the next boot a file
|
|
38
|
-
* advertising a dead endpoint and a token that no longer matches the one in memory.
|
|
39
|
-
*
|
|
40
|
-
* Everything else is durable and restores VERBATIM — including `auth.json`. The deploy seeds that
|
|
41
|
-
* file from the builder machine, but the box's own copy is the one that has been REFRESHED, and this
|
|
42
|
-
* snapshot is its volume: the same rule every other host states ("a credential already refreshed on
|
|
43
|
-
* the volume is never overwritten"). The seed is bootstrap for a snapshot that has none.
|
|
44
|
-
*
|
|
45
|
-
* That sentence is only true because the generated template points FASTAGENT_SECRETS_DIR INSIDE the
|
|
46
|
-
* state root (deploy/agentcore/plan.ts `SECRETS_DIR`) — nothing here special-cases credentials, the
|
|
47
|
-
* walk below simply reaches them. Moving the secrets dir back out (e.g. to the sibling layout the
|
|
48
|
-
* volume-backed hosts use) silently un-does it: the file stops being copied, the platform wipes it
|
|
49
|
-
* with the microVM, and a single-use OAuth refresh token dies with it. `restores VERBATIM` is a
|
|
50
|
-
* statement about where that directory is, not a property this module can enforce on its own.
|
|
51
|
-
*/
|
|
52
|
-
const EXCLUDED = new Set(["control.json"]);
|
|
53
|
-
/** Every regular file under `root`, as root-relative POSIX paths (stable across platforms). */
|
|
54
|
-
async function walk(root, dir = root, out = []) {
|
|
55
|
-
let entries;
|
|
56
|
-
try {
|
|
57
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
58
|
-
}
|
|
59
|
-
catch (e) {
|
|
60
|
-
if (e.code === "ENOENT")
|
|
61
|
-
return out;
|
|
62
|
-
throw e;
|
|
63
|
-
}
|
|
64
|
-
for (const entry of entries) {
|
|
65
|
-
const full = join(dir, entry.name);
|
|
66
|
-
// Symlinks/sockets/FIFOs are skipped on purpose: the state root holds plain files, and
|
|
67
|
-
// AgentCore's session storage does not support special files anyway.
|
|
68
|
-
if (entry.isDirectory())
|
|
69
|
-
await walk(root, full, out);
|
|
70
|
-
else if (entry.isFile())
|
|
71
|
-
out.push(relative(root, full).split(sep).join("/"));
|
|
72
|
-
}
|
|
73
|
-
return out;
|
|
74
|
-
}
|
|
75
|
-
/** Pack the whole state root into one gzipped snapshot object.
|
|
76
|
-
* ponytail: the WHOLE root, every idle edge — O(state size) per settled turn, and session jsonl
|
|
77
|
-
* grows without bound. Upgrade path when the 16 MiB warning is real for someone: a per-file
|
|
78
|
-
* manifest with content hashes, uploading only what changed. */
|
|
79
|
-
export async function packStateRoot(stateRoot, maxBytes = MAX_SNAPSHOT_BYTES) {
|
|
80
|
-
const files = {};
|
|
81
|
-
let raw = 0;
|
|
82
|
-
for (const rel of await walk(stateRoot)) {
|
|
83
|
-
if (EXCLUDED.has(rel))
|
|
84
|
-
continue;
|
|
85
|
-
const content = await readFile(join(stateRoot, rel));
|
|
86
|
-
raw += content.byteLength;
|
|
87
|
-
if (raw > maxBytes) {
|
|
88
|
-
throw new Error(`state root exceeds ${maxBytes} bytes — it cannot be snapshotted for cross-deploy durability`);
|
|
89
|
-
}
|
|
90
|
-
files[rel] = content.toString("base64");
|
|
91
|
-
}
|
|
92
|
-
if (raw > WARN_SNAPSHOT_BYTES) {
|
|
93
|
-
log.warn(`[agentcore] state snapshot is large (${Math.round(raw / (1 << 20))} MiB) — every turn round-trips it`);
|
|
94
|
-
}
|
|
95
|
-
return gzipSync(Buffer.from(JSON.stringify({ v: SNAPSHOT_VERSION, files })));
|
|
96
|
-
}
|
|
97
|
-
/** Apply a snapshot over the state root. Returns how many files were written. */
|
|
98
|
-
export async function unpackIntoStateRoot(stateRoot, packed) {
|
|
99
|
-
let snapshot;
|
|
100
|
-
try {
|
|
101
|
-
snapshot = JSON.parse(gunzipSync(packed).toString());
|
|
102
|
-
}
|
|
103
|
-
catch (e) {
|
|
104
|
-
throw new Error(`state snapshot is unreadable (${String(e)})`);
|
|
105
|
-
}
|
|
106
|
-
if (snapshot?.v !== SNAPSHOT_VERSION || typeof snapshot.files !== "object" || snapshot.files === null) {
|
|
107
|
-
throw new Error(`state snapshot has an unsupported shape (v=${String(snapshot?.v)})`);
|
|
108
|
-
}
|
|
109
|
-
let written = 0;
|
|
110
|
-
for (const [rel, b64] of Object.entries(snapshot.files)) {
|
|
111
|
-
// A snapshot is written by this same code, but it arrives over the network: refuse anything
|
|
112
|
-
// that could escape the state root.
|
|
113
|
-
if (rel.startsWith("/") || rel.split("/").includes("..")) {
|
|
114
|
-
throw new Error(`state snapshot contains an unsafe path (${rel})`);
|
|
115
|
-
}
|
|
116
|
-
if (EXCLUDED.has(rel))
|
|
117
|
-
continue; // never write a per-boot artifact from an older boot
|
|
118
|
-
const target = join(stateRoot, rel);
|
|
119
|
-
await mkdir(dirname(target), { recursive: true });
|
|
120
|
-
await writeFile(target, Buffer.from(b64, "base64"));
|
|
121
|
-
written += 1;
|
|
122
|
-
}
|
|
123
|
-
return written;
|
|
124
|
-
}
|
|
125
|
-
export function createStateSync(options) {
|
|
126
|
-
const { stateRoot } = options;
|
|
127
|
-
const doFetch = options.fetchImpl ?? fetch;
|
|
128
|
-
let urls;
|
|
129
|
-
let restore;
|
|
130
|
-
let restored = false;
|
|
131
|
-
let saving;
|
|
132
|
-
let queued = false;
|
|
133
|
-
// Whether the upload loop is still able to pick up another round. Counting the upload as in-flight
|
|
134
|
-
// work (below) means ITS completion is itself a 0-in-flight edge, which re-enters save() — without
|
|
135
|
-
// this the snapshot would queue a redundant follow-up after every single upload.
|
|
136
|
-
let looping = false;
|
|
137
|
-
const runRestore = async (urls) => {
|
|
138
|
-
const res = await doFetch(urls.getUrl, { method: "GET" });
|
|
139
|
-
// ONLY a proven 404 is "first deploy". A missing key answers 404 only because the generated
|
|
140
|
-
// template grants the signer s3:ListBucket on the snapshot prefix — without it S3 folds "absent"
|
|
141
|
-
// into 403 (anti-enumeration). A 403 therefore means an expired or malformed signature, a
|
|
142
|
-
// revoked permission, or a template from before that grant — i.e. the snapshot may well exist.
|
|
143
|
-
// Reading 403 as "absent" would serve an empty agent and then overwrite the real snapshot with
|
|
144
|
-
// that emptiness.
|
|
145
|
-
if (res.status === 404) {
|
|
146
|
-
log.info("[agentcore] no state snapshot yet — starting from an empty state root (first deploy)");
|
|
147
|
-
restored = true;
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (!res.ok) {
|
|
151
|
-
const hint = res.status === 403
|
|
152
|
-
? " (an expired presigned URL, a revoked permission, or a template generated before the " +
|
|
153
|
-
"ForwarderRole granted s3:ListBucket — S3 answers 403 even for a MISSING first-deploy " +
|
|
154
|
-
"snapshot without it; regenerate with `fastagent deploy agentcore --force` and redeploy)"
|
|
155
|
-
: "";
|
|
156
|
-
throw new Error(`state snapshot GET failed: ${res.status}${hint}`);
|
|
157
|
-
}
|
|
158
|
-
const written = await unpackIntoStateRoot(stateRoot, Buffer.from(await res.arrayBuffer()));
|
|
159
|
-
log.info(`[agentcore] restored ${written} state file(s) from the snapshot`);
|
|
160
|
-
restored = true;
|
|
161
|
-
};
|
|
162
|
-
const refreshUrls = async () => {
|
|
163
|
-
if (!urls?.refresh)
|
|
164
|
-
return;
|
|
165
|
-
const res = await doFetch(urls.refresh.url, {
|
|
166
|
-
method: "POST",
|
|
167
|
-
headers: { "content-type": "application/json" },
|
|
168
|
-
body: JSON.stringify({ auth: urls.refresh.auth }),
|
|
169
|
-
signal: AbortSignal.timeout(PUT_TIMEOUT_MS),
|
|
170
|
-
});
|
|
171
|
-
if (!res.ok)
|
|
172
|
-
throw new Error(`state snapshot URL refresh failed: ${res.status}`);
|
|
173
|
-
const fresh = (await res.json());
|
|
174
|
-
if (typeof fresh.getUrl !== "string" || typeof fresh.putUrl !== "string") {
|
|
175
|
-
throw new Error("state snapshot URL refresh returned an invalid response");
|
|
176
|
-
}
|
|
177
|
-
urls = { ...urls, getUrl: fresh.getUrl, putUrl: fresh.putUrl };
|
|
178
|
-
};
|
|
179
|
-
const runSave = async () => {
|
|
180
|
-
// Counted as in-flight work for its whole duration: `save()` fires on the 0-in-flight edge, the
|
|
181
|
-
// exact moment /ping starts answering Healthy — without this the platform may reclaim the microVM
|
|
182
|
-
// mid-upload and the turn that just finished is lost with only a log line. Bounded too: a hung
|
|
183
|
-
// PUT would otherwise pin `saving` forever and block every later snapshot.
|
|
184
|
-
const workDone = beginWork();
|
|
185
|
-
looping = true;
|
|
186
|
-
try {
|
|
187
|
-
do {
|
|
188
|
-
queued = false;
|
|
189
|
-
if (!restored || !urls)
|
|
190
|
-
return;
|
|
191
|
-
// A webhook turn may settle hours after its envelope. Re-mint immediately before every PUT
|
|
192
|
-
// instead of assuming the Lambda credentials that signed the envelope outlive the turn.
|
|
193
|
-
await refreshUrls();
|
|
194
|
-
const body = await packStateRoot(stateRoot);
|
|
195
|
-
const res = await doFetch(urls.putUrl, {
|
|
196
|
-
method: "PUT",
|
|
197
|
-
body: new Uint8Array(body),
|
|
198
|
-
signal: AbortSignal.timeout(PUT_TIMEOUT_MS),
|
|
199
|
-
});
|
|
200
|
-
if (!res.ok) {
|
|
201
|
-
const hint = res.status === 403 ? " (presigned URL or its temporary signing credentials may have expired)" : "";
|
|
202
|
-
throw new Error(`state snapshot PUT failed: ${res.status}${hint}`);
|
|
203
|
-
}
|
|
204
|
-
} while (queued);
|
|
205
|
-
}
|
|
206
|
-
finally {
|
|
207
|
-
looping = false;
|
|
208
|
-
workDone();
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
return {
|
|
212
|
-
use(next) {
|
|
213
|
-
urls = next;
|
|
214
|
-
},
|
|
215
|
-
configured() {
|
|
216
|
-
return urls !== undefined;
|
|
217
|
-
},
|
|
218
|
-
ready() {
|
|
219
|
-
// No URLs = not an ingress envelope (a direct invoke has its own isolated storage): nothing to
|
|
220
|
-
// restore, and deliberately NOT cached, so the first envelope that does carry them still runs
|
|
221
|
-
// the restore.
|
|
222
|
-
if (!urls)
|
|
223
|
-
return Promise.resolve();
|
|
224
|
-
// One attempt per process otherwise: a rejected restore stays rejected so every subsequent
|
|
225
|
-
// envelope fails the same visible way instead of quietly serving an empty agent.
|
|
226
|
-
restore ??= runRestore(urls);
|
|
227
|
-
return restore;
|
|
228
|
-
},
|
|
229
|
-
save() {
|
|
230
|
-
if (!restored)
|
|
231
|
-
return; // never overwrite a good snapshot with a state root we failed to fill
|
|
232
|
-
if (saving) {
|
|
233
|
-
if (looping)
|
|
234
|
-
queued = true; // otherwise this is the upload's own completion edge, not new work
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
saving = runSave()
|
|
238
|
-
.catch((e) => {
|
|
239
|
-
log.error(`[agentcore] could not save the state snapshot: ${String(e)} — retrying when work next settles`);
|
|
240
|
-
})
|
|
241
|
-
.finally(() => {
|
|
242
|
-
saving = undefined;
|
|
243
|
-
});
|
|
244
|
-
},
|
|
245
|
-
async flush() {
|
|
246
|
-
while (saving)
|
|
247
|
-
await saving;
|
|
248
|
-
},
|
|
249
|
-
async checkpoint() {
|
|
250
|
-
if (!urls) {
|
|
251
|
-
return { written: false, reason: "this session has never served a forwarder envelope" };
|
|
252
|
-
}
|
|
253
|
-
if (!restored) {
|
|
254
|
-
return { written: false, reason: "the state root is not authoritative here (nothing restored)" };
|
|
255
|
-
}
|
|
256
|
-
// Never overlap an in-flight upload, then run a FRESH one: joining the running round could
|
|
257
|
-
// return before the bytes written moments ago (the interrupted turn's intent) are included.
|
|
258
|
-
while (saving)
|
|
259
|
-
await saving;
|
|
260
|
-
const run = runSave();
|
|
261
|
-
// Stored form never rejects (an unawaited rejection would be an unhandled crash); the caller
|
|
262
|
-
// awaits `run` itself and gets the error.
|
|
263
|
-
saving = run.then(() => { }, () => { });
|
|
264
|
-
const settle = saving.finally(() => {
|
|
265
|
-
saving = undefined;
|
|
266
|
-
});
|
|
267
|
-
try {
|
|
268
|
-
await run;
|
|
269
|
-
}
|
|
270
|
-
finally {
|
|
271
|
-
await settle;
|
|
272
|
-
}
|
|
273
|
-
return { written: true };
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { Schema } from "@octokit/webhooks-types";
|
|
2
|
-
import type { ChannelModule } from "../../channel.ts";
|
|
3
|
-
/** A verified GitHub webhook event. Header fields plus the official typed payload. */
|
|
4
|
-
export interface GithubEvent {
|
|
5
|
-
/** `X-GitHub-Event` (e.g. "pull_request", "issue_comment"). */
|
|
6
|
-
event: string;
|
|
7
|
-
/** `payload.action` (e.g. "opened"), when present — the usual routing discriminant. */
|
|
8
|
-
action?: string;
|
|
9
|
-
/** `X-GitHub-Delivery` — unique per delivery. */
|
|
10
|
-
deliveryId: string;
|
|
11
|
-
/** The native payload (union of all events); narrow it, e.g. `if ("pull_request" in event.payload)`. */
|
|
12
|
-
payload: Schema;
|
|
13
|
-
}
|
|
14
|
-
/** What `on` returns per acted-on delivery: a session + the prompt text for the agent turn. */
|
|
15
|
-
export interface Intent {
|
|
16
|
-
session: string;
|
|
17
|
-
text: string;
|
|
18
|
-
}
|
|
19
|
-
export interface GithubChannelOptions {
|
|
20
|
-
/** Webhook secret — verifies inbound deliveries (HMAC-SHA256 over the raw body). */
|
|
21
|
-
secret: string;
|
|
22
|
-
/** Map a verified event to the intents this agent acts on (empty array = ignore). */
|
|
23
|
-
on: (event: GithubEvent) => Intent[];
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Build a GitHub webhook channel: policy options in, a {@link ChannelModule} out (mounts
|
|
27
|
-
* `POST /webhook`). `agent` arrives via the mount context. The adapter owns that route key; to serve
|
|
28
|
-
* it elsewhere, re-key the returned module:
|
|
29
|
-
* `(ctx) => ({ "POST /gh": githubChannel(opts)(ctx)["POST /webhook"]! })`.
|
|
30
|
-
*/
|
|
31
|
-
export declare function githubChannel({ secret, on }: GithubChannelOptions): ChannelModule;
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub webhook channel: verify → route via `on(event)` → fire-and-forget agent turns, ACK 202.
|
|
3
|
-
* The developer writes only `on`. Concurrency safety is the engine's per-session lease. A turn that
|
|
4
|
-
* fails after the 202, or an in-flight turn on shutdown, is lost (server log only).
|
|
5
|
-
*/
|
|
6
|
-
import { verify } from "@octokit/webhooks-methods";
|
|
7
|
-
import { collect } from "../../collect.js";
|
|
8
|
-
import { log } from "../../log.js";
|
|
9
|
-
import { readBodyCapped } from "../body.js";
|
|
10
|
-
import { beginWork } from "../busy.js";
|
|
11
|
-
import { text } from "../respond.js";
|
|
12
|
-
/** Raw body cap before verification — GitHub caps webhook payloads at 25 MB; reject larger early. */
|
|
13
|
-
const MAX_WEBHOOK_BYTES = 25 << 20;
|
|
14
|
-
/**
|
|
15
|
-
* Build a GitHub webhook channel: policy options in, a {@link ChannelModule} out (mounts
|
|
16
|
-
* `POST /webhook`). `agent` arrives via the mount context. The adapter owns that route key; to serve
|
|
17
|
-
* it elsewhere, re-key the returned module:
|
|
18
|
-
* `(ctx) => ({ "POST /gh": githubChannel(opts)(ctx)["POST /webhook"]! })`.
|
|
19
|
-
*/
|
|
20
|
-
export function githubChannel({ secret, on }) {
|
|
21
|
-
const channel = ({ agent }) => ({
|
|
22
|
-
"POST /webhook": async (req) => {
|
|
23
|
-
if (req.method !== "POST")
|
|
24
|
-
return text("POST only\n", 405);
|
|
25
|
-
// Cap before verify: a public endpoint must not buffer an unbounded body (chunked bypasses Content-Length).
|
|
26
|
-
const body = await readBodyCapped(req, MAX_WEBHOOK_BYTES);
|
|
27
|
-
if ("tooLarge" in body)
|
|
28
|
-
return text("payload too large\n", 413);
|
|
29
|
-
const raw = body.text;
|
|
30
|
-
// Fail closed: verify() throws on an empty/missing arg, so treat any verify error as a clean 401.
|
|
31
|
-
const signature = req.headers.get("x-hub-signature-256");
|
|
32
|
-
if (!signature || !(await verify(secret, raw, signature).catch(() => false))) {
|
|
33
|
-
return text("invalid signature\n", 401);
|
|
34
|
-
}
|
|
35
|
-
const eventName = req.headers.get("x-github-event") ?? "";
|
|
36
|
-
if (eventName === "ping")
|
|
37
|
-
return new Response(null, { status: 204 });
|
|
38
|
-
// x-www-form-urlencoded (GitHub's UI default) wraps the JSON in a `payload` field; json is the body.
|
|
39
|
-
let json = raw;
|
|
40
|
-
if ((req.headers.get("content-type") ?? "").includes("application/x-www-form-urlencoded")) {
|
|
41
|
-
const field = new URLSearchParams(raw).get("payload");
|
|
42
|
-
if (field === null)
|
|
43
|
-
return text("missing form payload\n", 400);
|
|
44
|
-
json = field;
|
|
45
|
-
}
|
|
46
|
-
let payload;
|
|
47
|
-
try {
|
|
48
|
-
payload = JSON.parse(json);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return text("invalid json\n", 400);
|
|
52
|
-
}
|
|
53
|
-
const event = {
|
|
54
|
-
event: eventName,
|
|
55
|
-
action: typeof payload.action === "string" ? payload.action : undefined,
|
|
56
|
-
deliveryId: req.headers.get("x-github-delivery") ?? "",
|
|
57
|
-
payload: payload, // trust boundary: the verified body is a GitHub event
|
|
58
|
-
};
|
|
59
|
-
// Fire each turn, return 202; the process runs them to completion. The lifecycle is logged to
|
|
60
|
-
// stderr — after the 202 there is no response body, so these lines are the operator's only signal
|
|
61
|
-
// (and the sink that keeps a post-ACK error from going unhandled).
|
|
62
|
-
const intents = on(event);
|
|
63
|
-
const label = event.action ? `${event.event}.${event.action}` : event.event;
|
|
64
|
-
for (let i = 0; i < intents.length; i++) {
|
|
65
|
-
const { session, text } = intents[i];
|
|
66
|
-
// Per-turn correlation id (deliveryId is unique per webhook; the index disambiguates fan-out),
|
|
67
|
-
// threaded through start/done/failed so a terminal line joins back to its start.
|
|
68
|
-
const turn = `${event.deliveryId}#${i}`;
|
|
69
|
-
log.info(`[github] turn start: turn=${turn} session=${session} event=${label}`);
|
|
70
|
-
const startedAt = Date.now();
|
|
71
|
-
// Post-ACK turns are process-wide in-flight work (busy.ts): a serving surface that must not
|
|
72
|
-
// idle mid-turn (the AgentCore /ping's HealthyBusy) has no other way to see them — and github
|
|
73
|
-
// turns have NO replay, so an idle reclaim here loses the review outright.
|
|
74
|
-
const workDone = beginWork();
|
|
75
|
-
void collect(agent.invoke({ session }, { text }))
|
|
76
|
-
.then(() => log.info(`[github] turn done: turn=${turn} session=${session} (${Date.now() - startedAt}ms)`), (error) => log.error(`[github] turn failed: turn=${turn} session=${session} (${Date.now() - startedAt}ms): ${String(error)}`))
|
|
77
|
-
.finally(workDone);
|
|
78
|
-
}
|
|
79
|
-
return new Response(null, { status: 202 });
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
return (ctx) => {
|
|
83
|
-
// Validate at activation so deploy may inspect the module shape before secrets are provisioned.
|
|
84
|
-
if (!secret) {
|
|
85
|
-
throw new Error("githubChannel requires a non-empty secret (the GitHub webhook secret, e.g. GITHUB_WEBHOOK_SECRET)");
|
|
86
|
-
}
|
|
87
|
-
return channel(ctx);
|
|
88
|
-
};
|
|
89
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { githubChannel } from "@fastagent-sh/fastagent/github";
|
|
2
|
-
|
|
3
|
-
// A channel = a third-party ADAPTER (githubChannel: verify + parse + ACK) configured with YOUR on() policy.
|
|
4
|
-
// fastagent discovers this file under channels/, mounts POST /webhook, and pipes the agent to the
|
|
5
|
-
// adapter — this file holds only policy. Set GITHUB_WEBHOOK_SECRET in .env (a missing secret fails at
|
|
6
|
-
// startup — an empty key would accept forged deliveries) and point a GitHub webhook (JSON) at POST /webhook.
|
|
7
|
-
export default githubChannel({
|
|
8
|
-
secret: process.env.GITHUB_WEBHOOK_SECRET ?? "",
|
|
9
|
-
// Map a verified event to the intents the agent acts on (empty array = ignore). Each review is
|
|
10
|
-
// INDEPENDENT and idempotent (it reconciles against the PR's existing comments), so use a
|
|
11
|
-
// distinct per-delivery session (event.deliveryId): overlapping deliveries then run on their
|
|
12
|
-
// own session without a shared-lease drop.
|
|
13
|
-
on: (event) => {
|
|
14
|
-
if (event.event === "pull_request" && event.action === "opened" && "pull_request" in event.payload) {
|
|
15
|
-
const { repository, pull_request } = event.payload;
|
|
16
|
-
return [
|
|
17
|
-
{
|
|
18
|
-
session: event.deliveryId,
|
|
19
|
-
text: `Review pull request #${pull_request.number} in ${repository.full_name}.`,
|
|
20
|
-
},
|
|
21
|
-
];
|
|
22
|
-
}
|
|
23
|
-
return [];
|
|
24
|
-
},
|
|
25
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { type SessionControl, type SessionEntry } from "../../session.ts";
|
|
2
|
-
export interface AttachOptions {
|
|
3
|
-
/** Override the control endpoint (skip control.json discovery) — for a remote serve. */
|
|
4
|
-
url?: string;
|
|
5
|
-
token?: string;
|
|
6
|
-
}
|
|
7
|
-
export declare function runAttach(sessionArg: string, dirArg: string | undefined, opts: AttachOptions): Promise<void>;
|
|
8
|
-
/** One round's observed facts, gathered by the loop (IO) and judged by {@link decideRound} (pure). */
|
|
9
|
-
export type RoundOutcome = {
|
|
10
|
-
type: "progress";
|
|
11
|
-
}
|
|
12
|
-
/** Clean end that delivered nothing — indistinguishable from a half-dead proxy closing every stream. */
|
|
13
|
-
| {
|
|
14
|
-
type: "empty";
|
|
15
|
-
} | {
|
|
16
|
-
type: "error";
|
|
17
|
-
error: unknown;
|
|
18
|
-
isAuth: boolean;
|
|
19
|
-
/** discover(dir) compared against the current endpoint; remote endpoints report "unavailable". */
|
|
20
|
-
discovery: "unchanged" | "changed" | "unavailable";
|
|
21
|
-
/** The freshly discovered credentials when {@link discovery} is "changed". */
|
|
22
|
-
fresh?: {
|
|
23
|
-
url: string;
|
|
24
|
-
token: string;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
export type RoundDecision = {
|
|
28
|
-
kind: "reset";
|
|
29
|
-
} | {
|
|
30
|
-
kind: "exit";
|
|
31
|
-
message: string;
|
|
32
|
-
} | {
|
|
33
|
-
kind: "try-reattach";
|
|
34
|
-
fresh: {
|
|
35
|
-
url: string;
|
|
36
|
-
token: string;
|
|
37
|
-
};
|
|
38
|
-
} | {
|
|
39
|
-
kind: "retry";
|
|
40
|
-
warn: string;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* The reconnect policy for BOTH phases (`startup` = connectWithGrace, `steady` = the round loop),
|
|
44
|
-
* pure and testable: every exit diagnosis and budget claim lives here. `downMs` is wall-clock time
|
|
45
|
-
* since the phase's anchor (first connect attempt / first non-progress round). The phases differ
|
|
46
|
-
* on priors, not principle: startup has never succeeded (short budget; a remote non-auth error
|
|
47
|
-
* exits at once), steady-state had a working attach (longer budgets); the local-401-unchanged
|
|
48
|
-
* diagnosis and reattach-on-changed-credentials apply identically to both.
|
|
49
|
-
*/
|
|
50
|
-
export declare function decideRound(outcome: RoundOutcome, ctx: {
|
|
51
|
-
discovered: boolean;
|
|
52
|
-
downMs: number;
|
|
53
|
-
phase?: "startup" | "steady";
|
|
54
|
-
}): RoundDecision;
|
|
55
|
-
/** What one ROUND prints through (streamed deltas included), so tests can observe every path a
|
|
56
|
-
* round writes. Round output only: stdin-side acceptance/rejection lines print directly — they
|
|
57
|
-
* are immediate feedback to a user action and may interleave with a replay block. Timing knobs
|
|
58
|
-
* travel as their own parameter. */
|
|
59
|
-
export interface AttachIo {
|
|
60
|
-
println: (line: string) => void;
|
|
61
|
-
/** Raw chunk, no newline — the streamed message_delta path. */
|
|
62
|
-
write: (chunk: string) => void;
|
|
63
|
-
warn: (line: string) => void;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* The backfill slice REDUCED TO THE ACTIVE PATH — a session is a tree (a leaf move, or a
|
|
67
|
-
* compaction, leaves sibling branches behind), and printing the slice raw renders the abandoned
|
|
68
|
-
* branch interleaved with the live one as if it were one conversation. Reachability is computed
|
|
69
|
-
* backwards from the leaf; an entry whose parent lies BEFORE the slice is path-connected by
|
|
70
|
-
* construction (its ancestors were rendered in an earlier round), so only what the walk cannot
|
|
71
|
-
* reach is dropped. An engine that reports NO leaf says nothing about branches, so its slice stands
|
|
72
|
-
* whole — the one case where "unknown" must not read as "off-path".
|
|
73
|
-
*/
|
|
74
|
-
export declare function activePathSlice(entries: SessionEntry[], leafEntryId: string | undefined): SessionEntry[];
|
|
75
|
-
/**
|
|
76
|
-
* Answer a RESERVED-SLASH line (anything starting with `/` that is not `/abort`). Two intents share
|
|
77
|
-
* the prefix — a mistyped control command and an attempt to invoke a name — and they are answered
|
|
78
|
-
* differently:
|
|
79
|
-
*
|
|
80
|
-
* - a mistyped slash gets the certain half NOW (a leading `/` is reserved here, whatever the token
|
|
81
|
-
* turns out to be), because waiting on a remote read that can be slow or fail would leave the
|
|
82
|
-
* input unanswered; it deliberately does not pre-judge the token as unknown — the read may be
|
|
83
|
-
* about to prove it names a real skill;
|
|
84
|
-
* - `/commands` prints nothing first: its whole answer IS the read, and a placeholder is noise;
|
|
85
|
-
* - the enumeration answers `/commands` ONLY — dumping every skill at a mistyped `/aboort` answers
|
|
86
|
-
* an intent the typo did not express.
|
|
87
|
-
*
|
|
88
|
-
* Names print BARE: this composer cannot expand `/name` (the data plane takes prompts as text), so
|
|
89
|
-
* printing them with a slash would invite the user straight back into this branch. Returns the
|
|
90
|
-
* promise for the remote half, so a caller (or a test) can await the second line.
|
|
91
|
-
*/
|
|
92
|
-
export declare function answerSlashInput(trimmed: string, control: Pick<SessionControl, "commands">, println: (line: string) => void): Promise<void>;
|
|
93
|
-
/**
|
|
94
|
-
* ONE attach round: subscribe → backfill (render the durable record since `cursor`) → drain live
|
|
95
|
-
* until the stream drops. Returns the advanced cursor. Subscribing first + the server's eager
|
|
96
|
-
* registration (subscribed before response headers) covers the cursor→subscription window in the
|
|
97
|
-
* normal case; `settleMs` is a HEURISTIC — on a very slow link an event can still land between the
|
|
98
|
-
* backfill and the subscription taking effect, surfacing only at the next round's replay. Live
|
|
99
|
-
* events carry no entry ids, so the cursor advances only on backfill; a replay may overlap what
|
|
100
|
-
* was already seen live — labeled, not silently dropped. A 401 (stale token after a serve restart)
|
|
101
|
-
* is thrown to the caller: unrecoverable here, never retried silently.
|
|
102
|
-
*/
|
|
103
|
-
export declare function attachRound(control: SessionControl, session: string, cursor: string | undefined, io: AttachIo,
|
|
104
|
-
/** The subscribe→sync settle heuristic (see the round comment). Tests shrink it. */
|
|
105
|
-
settleMs?: number): Promise<{
|
|
106
|
-
cursor: string | undefined;
|
|
107
|
-
sawProgress: boolean;
|
|
108
|
-
}>;
|