@fastagent-sh/fastagent 0.24.4 → 0.25.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 +53 -82
- package/dist/agent-api.d.ts +452 -0
- package/dist/agent-api.js +201 -0
- package/dist/answer-text.d.ts +8 -0
- package/dist/answer-text.js +18 -0
- package/dist/atomic-write.d.ts +2 -2
- package/dist/atomic-write.js +2 -2
- package/dist/authoring.d.ts +77 -0
- package/dist/authoring.js +238 -0
- package/dist/channel.d.ts +29 -15
- package/dist/channel.js +4 -1
- package/dist/channels/agentcore-protocol.d.ts +36 -50
- package/dist/channels/agentcore-protocol.js +3 -3
- package/dist/channels/agentcore-service.d.ts +8 -18
- package/dist/channels/agentcore-service.js +108 -127
- package/dist/channels/agentcore.d.ts +8 -24
- package/dist/channels/agentcore.js +32 -65
- package/dist/channels/body.d.ts +0 -16
- package/dist/channels/body.js +0 -25
- package/dist/channels/busy.js +6 -1
- package/dist/channels/context.d.ts +4 -0
- package/dist/channels/context.js +35 -0
- package/dist/channels/discover.d.ts +18 -20
- package/dist/channels/discover.js +41 -99
- package/dist/channels/feishu/feishu-api.d.ts +54 -4
- package/dist/channels/feishu/feishu-api.js +192 -149
- package/dist/channels/feishu/feishu.d.ts +3 -0
- package/dist/channels/feishu/feishu.js +117 -106
- package/dist/channels/feishu/history.d.ts +55 -0
- package/dist/channels/feishu/history.js +199 -0
- package/dist/channels/feishu/invoke-turn.d.ts +13 -11
- package/dist/channels/feishu/invoke-turn.js +9 -10
- package/dist/channels/feishu/model.d.ts +2 -1
- package/dist/channels/feishu/normalize.js +100 -0
- package/dist/channels/feishu/preview.d.ts +2 -2
- package/dist/channels/feishu/register-webhook.d.ts +1 -0
- package/dist/channels/feishu/register-webhook.js +3 -2
- package/dist/channels/feishu/scaffold/channel.ts +65 -46
- package/dist/channels/feishu/scaffold/feishu-send.ts +38 -33
- package/dist/channels/feishu/scaffold/feishu-threads.ts +25 -0
- package/dist/channels/feishu/setup-mode.d.ts +45 -25
- package/dist/channels/feishu/setup-mode.js +74 -23
- package/dist/channels/feishu/shared-api.d.ts +24 -6
- package/dist/channels/feishu/shared-api.js +30 -24
- package/dist/channels/kit/context-buffer.d.ts +11 -2
- package/dist/channels/kit/context-buffer.js +7 -4
- package/dist/channels/kit/delivery.d.ts +19 -3
- package/dist/channels/kit/delivery.js +74 -15
- package/dist/channels/kit/event-stream.js +2 -1
- package/dist/channels/kit/invoke-turn-kit.d.ts +41 -41
- package/dist/channels/kit/invoke-turn-kit.js +62 -71
- package/dist/channels/kit/place-history.d.ts +107 -0
- package/dist/channels/kit/place-history.js +182 -0
- package/dist/channels/kit/preview-kit.d.ts +9 -5
- package/dist/channels/kit/preview-kit.js +36 -15
- package/dist/channels/kit/room-threads.d.ts +46 -0
- package/dist/channels/kit/room-threads.js +81 -0
- package/dist/channels/kit/seen.js +1 -1
- package/dist/channels/kit/stop-command.d.ts +6 -3
- package/dist/channels/kit/stop-command.js +20 -18
- package/dist/channels/kit/thread-fork.d.ts +17 -0
- package/dist/channels/kit/thread-fork.js +43 -0
- package/dist/channels/kit/thread-participants.js +1 -1
- package/dist/channels/kit/transport.d.ts +5 -0
- package/dist/channels/kit/transport.js +5 -0
- package/dist/channels/kit/turn-runner.d.ts +10 -9
- package/dist/channels/kit/turn-runner.js +17 -19
- package/dist/channels/kit/turn-store.d.ts +5 -4
- package/dist/channels/kit/turn-store.js +5 -5
- package/dist/channels/lark/lark.d.ts +3 -0
- package/dist/channels/lark/lark.js +5 -0
- package/dist/channels/lark/onboard.d.ts +5 -3
- package/dist/channels/lark/onboard.js +14 -14
- package/dist/channels/lark/scaffold/channel.ts +67 -44
- package/dist/channels/lark/scaffold/lark-send.ts +38 -33
- package/dist/channels/lark/scaffold/lark-threads.ts +25 -0
- package/dist/channels/respond.d.ts +0 -4
- package/dist/channels/respond.js +1 -1
- package/dist/channels/router.d.ts +18 -0
- package/dist/channels/router.js +68 -0
- package/dist/channels/serve.d.ts +5 -70
- package/dist/channels/serve.js +105 -287
- package/dist/channels/slack/history.d.ts +42 -0
- package/dist/channels/slack/history.js +282 -0
- package/dist/channels/slack/invoke-turn.d.ts +12 -5
- package/dist/channels/slack/invoke-turn.js +7 -5
- package/dist/channels/slack/manifest.d.ts +1 -4
- package/dist/channels/slack/manifest.js +22 -10
- package/dist/channels/slack/model.d.ts +2 -1
- package/dist/channels/slack/onboard.d.ts +0 -2
- package/dist/channels/slack/onboard.js +2 -4
- package/dist/channels/slack/onboarding-state.d.ts +0 -2
- package/dist/channels/slack/onboarding-state.js +0 -1
- package/dist/channels/slack/parse.d.ts +0 -5
- package/dist/channels/slack/parse.js +1 -16
- package/dist/channels/slack/preview.d.ts +2 -3
- package/dist/channels/slack/preview.js +78 -52
- package/dist/channels/slack/register-webhook.d.ts +1 -0
- package/dist/channels/slack/register-webhook.js +6 -5
- package/dist/channels/slack/scaffold/channel.ts +23 -24
- package/dist/channels/slack/scaffold/slack-send.ts +50 -43
- package/dist/channels/slack/scaffold/slack-threads.ts +29 -0
- package/dist/channels/slack/setup-server.d.ts +19 -0
- package/dist/channels/slack/setup-server.js +113 -11
- package/dist/channels/slack/shared-api.d.ts +15 -4
- package/dist/channels/slack/shared-api.js +19 -14
- package/dist/channels/slack/slack-api.d.ts +61 -2
- package/dist/channels/slack/slack-api.js +28 -5
- package/dist/channels/slack/slack.js +97 -52
- package/dist/channels/slack/welcomed.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +6 -1
- package/dist/channels/telegram/context-buffer.js +19 -0
- package/dist/channels/telegram/invoke-turn.d.ts +8 -4
- package/dist/channels/telegram/invoke-turn.js +7 -7
- package/dist/channels/telegram/parse.d.ts +7 -2
- package/dist/channels/telegram/parse.js +7 -0
- package/dist/channels/telegram/preview.d.ts +2 -2
- package/dist/channels/telegram/register-webhook.d.ts +1 -0
- package/dist/channels/telegram/register-webhook.js +3 -2
- package/dist/channels/telegram/scaffold/channel.ts +27 -28
- package/dist/channels/telegram/scaffold/telegram-send.ts +61 -108
- package/dist/channels/telegram/shared-api.d.ts +34 -0
- package/dist/channels/telegram/shared-api.js +79 -0
- package/dist/channels/telegram/telegram-api.d.ts +15 -5
- package/dist/channels/telegram/telegram-api.js +10 -6
- package/dist/channels/telegram/telegram.js +27 -21
- package/dist/channels/telegram/turn-store.js +1 -1
- package/dist/channels/{kit/turn-queue.d.ts → turn-queue.d.ts} +2 -2
- package/dist/channels/{kit/turn-queue.js → turn-queue.js} +10 -10
- package/dist/cli/add-feishu.d.ts +42 -12
- package/dist/cli/add-feishu.js +177 -124
- package/dist/cli/add-slack.d.ts +8 -3
- package/dist/cli/add-slack.js +115 -63
- package/dist/cli/auth-view.d.ts +10 -3
- package/dist/cli/auth-view.js +8 -6
- package/dist/cli/box-login.d.ts +17 -7
- package/dist/cli/box-login.js +46 -12
- package/dist/cli/commands/add.d.ts +15 -4
- package/dist/cli/commands/add.js +252 -143
- package/dist/cli/commands/auth.d.ts +6 -0
- package/dist/cli/commands/auth.js +50 -0
- package/dist/cli/commands/content.d.ts +6 -0
- package/dist/cli/commands/content.js +45 -0
- package/dist/cli/commands/deploy/agentcore.js +50 -77
- package/dist/cli/commands/deploy/docker.js +38 -39
- package/dist/cli/commands/deploy/fly.js +21 -18
- package/dist/cli/commands/deploy/host-login.d.ts +26 -0
- package/dist/cli/commands/deploy/host-login.js +90 -0
- package/dist/cli/commands/deploy/railway.js +21 -26
- package/dist/cli/commands/deploy/shared.d.ts +45 -17
- package/dist/cli/commands/deploy/shared.js +34 -15
- package/dist/cli/commands/deploy.d.ts +0 -4
- package/dist/cli/commands/deploy.js +237 -50
- package/dist/cli/commands/destroy.js +8 -6
- package/dist/cli/commands/dev.d.ts +0 -2
- package/dist/cli/commands/dev.js +20 -19
- package/dist/cli/commands/env.d.ts +12 -0
- package/dist/cli/commands/env.js +190 -0
- package/dist/cli/commands/info.js +95 -156
- package/dist/cli/commands/init.d.ts +2 -2
- package/dist/cli/commands/init.js +35 -33
- package/dist/cli/commands/invoke.d.ts +6 -0
- package/dist/cli/commands/invoke.js +131 -16
- package/dist/cli/commands/login.d.ts +31 -2
- package/dist/cli/commands/login.js +413 -64
- package/dist/cli/commands/logout.d.ts +4 -0
- package/dist/cli/commands/logout.js +53 -0
- package/dist/cli/commands/logs.js +8 -6
- package/dist/cli/commands/models.d.ts +2 -1
- package/dist/cli/commands/models.js +39 -37
- package/dist/cli/commands/schedules.d.ts +21 -0
- package/dist/cli/commands/schedules.js +143 -0
- package/dist/cli/commands/start.d.ts +11 -10
- package/dist/cli/commands/start.js +35 -62
- package/dist/cli/content-view.d.ts +9 -0
- package/dist/cli/content-view.js +26 -0
- package/dist/cli/fail.d.ts +9 -12
- package/dist/cli/fail.js +17 -14
- package/dist/cli/invoke-stream.d.ts +12 -3
- package/dist/cli/invoke-stream.js +85 -23
- package/dist/cli/kernel.d.ts +9 -2
- package/dist/cli/kernel.js +15 -6
- package/dist/cli/login-relay.d.ts +3 -2
- package/dist/cli/login-relay.js +18 -6
- package/dist/cli/models-view.d.ts +5 -1
- package/dist/cli/models-view.js +1 -1
- package/dist/cli/person.d.ts +10 -0
- package/dist/cli/person.js +23 -0
- package/dist/cli/program.js +421 -203
- package/dist/cli/serve.d.ts +13 -63
- package/dist/cli/serve.js +31 -121
- package/dist/cli/shared.d.ts +21 -48
- package/dist/cli/shared.js +68 -89
- package/dist/cli/ticket.d.ts +136 -0
- package/dist/cli/ticket.js +476 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +2 -0
- package/dist/content/declare.d.ts +70 -0
- package/dist/content/declare.js +163 -0
- package/dist/content/file.d.ts +32 -0
- package/dist/content/file.js +119 -0
- package/dist/content/git.d.ts +41 -0
- package/dist/content/git.js +217 -0
- package/dist/content/mount.d.ts +2 -0
- package/dist/content/mount.js +15 -0
- package/dist/content/resolve.d.ts +52 -0
- package/dist/content/resolve.js +151 -0
- package/dist/content/source.d.ts +27 -0
- package/dist/content/source.js +53 -0
- package/dist/conversation-sessions.d.ts +26 -0
- package/dist/conversation-sessions.js +99 -0
- package/dist/declared-secrets.d.ts +19 -37
- package/dist/declared-secrets.js +16 -58
- package/dist/deploy/agentcore/destroy.d.ts +3 -3
- package/dist/deploy/agentcore/destroy.js +46 -28
- package/dist/deploy/agentcore/forwarder.js +105 -22
- package/dist/deploy/agentcore/logs.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +10 -24
- package/dist/deploy/agentcore/plan.js +31 -135
- package/dist/deploy/agentcore/run.d.ts +1 -1
- package/dist/deploy/agentcore/run.js +8 -5
- package/dist/deploy/agentcore/shell.d.ts +1 -1
- package/dist/deploy/agentcore/shell.js +5 -5
- package/dist/deploy/api.d.ts +34 -0
- package/dist/deploy/api.js +50 -0
- package/dist/deploy/box-shell.d.ts +6 -0
- package/dist/deploy/box-shell.js +7 -0
- package/dist/deploy/build-context.d.ts +7 -5
- package/dist/deploy/build-context.js +41 -35
- package/dist/deploy/channel-ingress.d.ts +6 -1
- package/dist/deploy/channel-ingress.js +18 -5
- package/dist/deploy/container.d.ts +22 -9
- package/dist/deploy/container.js +96 -55
- package/dist/deploy/docker/plan.d.ts +3 -2
- package/dist/deploy/docker/plan.js +13 -14
- package/dist/deploy/docker/run.d.ts +2 -2
- package/dist/deploy/docker/run.js +8 -4
- package/dist/deploy/fly/plan.d.ts +1 -1
- package/dist/deploy/fly/plan.js +12 -21
- package/dist/deploy/fly/run.d.ts +10 -6
- package/dist/deploy/fly/run.js +11 -6
- package/dist/deploy/hosts.d.ts +2 -1
- package/dist/deploy/hosts.js +2 -1
- package/dist/deploy/preflight.d.ts +21 -23
- package/dist/deploy/preflight.js +222 -201
- package/dist/deploy/railway/plan.d.ts +8 -6
- package/dist/deploy/railway/plan.js +15 -58
- package/dist/deploy/railway/run.d.ts +8 -3
- package/dist/deploy/railway/run.js +12 -8
- package/dist/deploy/residency.d.ts +5 -11
- package/dist/deploy/residency.js +0 -6
- package/dist/deploy/runner.d.ts +1 -1
- package/dist/deploy/runner.js +1 -1
- package/dist/deploy/secrets.d.ts +16 -2
- package/dist/deploy/secrets.js +33 -6
- package/dist/deploy/workspace.d.ts +11 -6
- package/dist/deploy/workspace.js +55 -38
- package/dist/dev-supervisor.d.ts +11 -2
- package/dist/dev-supervisor.js +51 -26
- package/dist/effect-port.d.ts +4 -4
- package/dist/effect-port.js +4 -4
- package/dist/environment/apt.d.ts +4 -0
- package/dist/environment/apt.js +32 -0
- package/dist/environment/declare.d.ts +59 -0
- package/dist/environment/declare.js +210 -0
- package/dist/environment/mise.d.ts +50 -0
- package/dist/environment/mise.js +268 -0
- package/dist/feishu.d.ts +2 -2
- package/dist/feishu.js +2 -2
- package/dist/git.d.ts +13 -0
- package/dist/git.js +55 -0
- package/dist/harnesses/models/catalog.d.ts +20 -0
- package/dist/harnesses/models/catalog.js +254 -0
- package/dist/{engines/pi/auth.d.ts → harnesses/models/credentials.d.ts} +3 -3
- package/dist/{engines/pi/auth.js → harnesses/models/credentials.js} +3 -3
- package/dist/harnesses/models/environment.d.ts +43 -0
- package/dist/harnesses/models/environment.js +82 -0
- package/dist/{engines/pi → harnesses/models}/login.d.ts +57 -26
- package/dist/{engines/pi → harnesses/models}/login.js +187 -79
- package/dist/harnesses/models/models.d.ts +83 -0
- package/dist/harnesses/models/models.js +158 -0
- package/dist/harnesses/pi-durable/agent.d.ts +42 -0
- package/dist/harnesses/pi-durable/agent.js +579 -0
- package/dist/harnesses/pi-durable/defaults.d.ts +28 -0
- package/dist/harnesses/pi-durable/defaults.js +26 -0
- package/dist/harnesses/pi-durable/define.d.ts +77 -0
- package/dist/harnesses/pi-durable/define.js +28 -0
- package/dist/harnesses/pi-durable/entries.d.ts +32 -0
- package/dist/harnesses/pi-durable/entries.js +126 -0
- package/dist/harnesses/pi-durable/events.d.ts +25 -0
- package/dist/harnesses/pi-durable/events.js +238 -0
- package/dist/harnesses/pi-durable/ids.d.ts +16 -0
- package/dist/harnesses/pi-durable/ids.js +55 -0
- package/dist/harnesses/pi-durable/messages.d.ts +41 -0
- package/dist/harnesses/pi-durable/messages.js +116 -0
- package/dist/harnesses/pi-durable/open.d.ts +88 -0
- package/dist/harnesses/pi-durable/open.js +329 -0
- package/dist/harnesses/pi-durable/reads.d.ts +30 -0
- package/dist/harnesses/pi-durable/reads.js +94 -0
- package/dist/harnesses/pi-durable/registry.d.ts +18 -0
- package/dist/harnesses/pi-durable/registry.js +56 -0
- package/dist/harnesses/pi-durable/release.d.ts +13 -0
- package/dist/harnesses/pi-durable/release.js +40 -0
- package/dist/harnesses/pi-durable/serve.d.ts +80 -0
- package/dist/harnesses/pi-durable/serve.js +156 -0
- package/dist/harnesses/pi-durable/sessions.d.ts +27 -0
- package/dist/harnesses/pi-durable/sessions.js +47 -0
- package/dist/harnesses/pi-durable/storage.d.ts +4 -0
- package/dist/harnesses/pi-durable/storage.js +49 -0
- package/dist/harnesses/pi-durable/stream.d.ts +20 -0
- package/dist/harnesses/pi-durable/stream.js +221 -0
- package/dist/harnesses/pi-durable/text.d.ts +30 -0
- package/dist/harnesses/pi-durable/text.js +292 -0
- package/dist/harnesses/pi-durable/wake.d.ts +4 -0
- package/dist/harnesses/pi-durable/wake.js +81 -0
- package/dist/http/client.d.ts +6 -0
- package/dist/http/client.js +256 -0
- package/dist/http/contract.d.ts +541 -0
- package/dist/http/contract.js +179 -0
- package/dist/http/server.d.ts +34 -0
- package/dist/http/server.js +194 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +10 -5
- package/dist/lark.d.ts +2 -2
- package/dist/lark.js +2 -2
- package/dist/locked-file.d.ts +17 -0
- package/dist/{engines/pi/locked-file.js → locked-file.js} +16 -10
- package/dist/once.d.ts +6 -0
- package/dist/once.js +23 -0
- package/dist/paths.d.ts +36 -52
- package/dist/paths.js +66 -182
- package/dist/runtime.d.ts +25 -2
- package/dist/runtime.js +27 -0
- package/dist/scaffold/add-channel.d.ts +14 -27
- package/dist/scaffold/add-channel.js +82 -180
- package/dist/scaffold/init.d.ts +17 -18
- package/dist/scaffold/init.js +87 -99
- package/dist/scaffold/templates/SYSTEM.md +12 -0
- package/dist/scaffold/templates/env.example +4 -10
- package/dist/scaffold/templates/fastagent.config.ts +21 -33
- package/dist/scaffold/templates/gitignore +8 -1
- package/dist/scaffold/templates/skills/writing-great-skills/SKILL.md +1 -2
- package/dist/scaffold/templates.d.ts +9 -4
- package/dist/scaffold/templates.js +7 -5
- package/dist/scaffold/vendor-skill.d.ts +1 -4
- package/dist/scaffold/vendor-skill.js +5 -6
- package/dist/schedule/cron.d.ts +13 -0
- package/dist/schedule/cron.js +52 -0
- package/dist/schedule/eventbridge-cron.d.ts +17 -0
- package/dist/schedule/eventbridge-cron.js +88 -0
- package/dist/schedule/schedule.d.ts +23 -0
- package/dist/schedule/schedule.js +34 -0
- package/dist/schedule/scheduler.d.ts +34 -33
- package/dist/schedule/scheduler.js +251 -137
- package/dist/schedule/state.d.ts +4 -14
- package/dist/schedule/state.js +7 -7
- package/dist/schedule/wake-alarm.d.ts +22 -2
- package/dist/schedule/wake-alarm.js +66 -20
- package/dist/schedule/wakeups.d.ts +16 -15
- package/dist/schedule/wakeups.js +47 -45
- package/dist/service.d.ts +56 -169
- package/dist/service.js +91 -187
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +16 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/{channels/kit/state.js → state-file.js} +6 -6
- package/dist/telegram.d.ts +1 -0
- package/dist/telegram.js +1 -0
- package/dist/tunnel.d.ts +3 -0
- package/dist/tunnel.js +12 -4
- package/dist/value-file.d.ts +21 -0
- package/dist/value-file.js +108 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +14 -1
- package/package.json +18 -28
- package/dist/agent.d.ts +0 -106
- package/dist/agent.js +0 -21
- package/dist/channels/control.d.ts +0 -31
- package/dist/channels/control.js +0 -329
- package/dist/channels/define-channel.d.ts +0 -39
- package/dist/channels/define-channel.js +0 -9
- package/dist/channels/feishu/context-buffer.d.ts +0 -35
- package/dist/channels/feishu/context-buffer.js +0 -65
- package/dist/channels/feishu/scaffold/channel.websocket.ts +0 -31
- package/dist/channels/http.d.ts +0 -23
- package/dist/channels/http.js +0 -57
- package/dist/channels/lark/scaffold/channel.websocket.ts +0 -31
- package/dist/channels/slack/context-buffer.d.ts +0 -23
- package/dist/channels/slack/context-buffer.js +0 -37
- package/dist/channels/sse.d.ts +0 -4
- package/dist/channels/sse.js +0 -66
- package/dist/cli/commands/chat.d.ts +0 -3
- package/dist/cli/commands/chat.js +0 -14
- package/dist/cli/commands/routine-run.d.ts +0 -6
- package/dist/cli/commands/routine-run.js +0 -46
- package/dist/cli/commands/routine.d.ts +0 -25
- package/dist/cli/commands/routine.js +0 -122
- package/dist/cli/commands/tool.d.ts +0 -1
- package/dist/cli/commands/tool.js +0 -57
- package/dist/collect.d.ts +0 -63
- package/dist/collect.js +0 -90
- package/dist/core.d.ts +0 -13
- package/dist/core.js +0 -15
- package/dist/engines/pi/agent-models.d.ts +0 -83
- package/dist/engines/pi/agent-models.js +0 -120
- package/dist/engines/pi/agent-session-factory.d.ts +0 -121
- package/dist/engines/pi/agent-session-factory.js +0 -535
- package/dist/engines/pi/chat.d.ts +0 -3
- package/dist/engines/pi/chat.js +0 -8
- package/dist/engines/pi/config.d.ts +0 -71
- package/dist/engines/pi/config.js +0 -199
- package/dist/engines/pi/create.d.ts +0 -170
- package/dist/engines/pi/create.js +0 -343
- package/dist/engines/pi/definition.d.ts +0 -74
- package/dist/engines/pi/definition.js +0 -236
- package/dist/engines/pi/entry-images.d.ts +0 -14
- package/dist/engines/pi/entry-images.js +0 -23
- package/dist/engines/pi/invoke-session.d.ts +0 -18
- package/dist/engines/pi/invoke-session.js +0 -403
- package/dist/engines/pi/locked-file.d.ts +0 -9
- package/dist/engines/pi/machine.d.ts +0 -53
- package/dist/engines/pi/machine.js +0 -137
- package/dist/engines/pi/models.d.ts +0 -200
- package/dist/engines/pi/models.js +0 -533
- package/dist/engines/pi/open.d.ts +0 -180
- package/dist/engines/pi/open.js +0 -270
- package/dist/engines/pi/openai-account-models.d.ts +0 -35
- package/dist/engines/pi/openai-account-models.js +0 -116
- package/dist/engines/pi/report.d.ts +0 -18
- package/dist/engines/pi/report.js +0 -59
- package/dist/engines/pi/retry-event.d.ts +0 -6
- package/dist/engines/pi/retry-event.js +0 -15
- package/dist/engines/pi/service.d.ts +0 -9
- package/dist/engines/pi/service.js +0 -14
- package/dist/engines/pi/session-builder.d.ts +0 -13
- package/dist/engines/pi/session-builder.js +0 -117
- package/dist/engines/pi/session-control.d.ts +0 -77
- package/dist/engines/pi/session-control.js +0 -908
- package/dist/engines/pi/session-effects.d.ts +0 -14
- package/dist/engines/pi/session-effects.js +0 -30
- package/dist/engines/pi/session-inheritance.d.ts +0 -21
- package/dist/engines/pi/session-inheritance.js +0 -204
- package/dist/engines/pi/session-markers.d.ts +0 -49
- package/dist/engines/pi/session-markers.js +0 -60
- package/dist/engines/pi/session-settings.d.ts +0 -67
- package/dist/engines/pi/session-settings.js +0 -119
- package/dist/engines/pi/session-store.d.ts +0 -69
- package/dist/engines/pi/session-store.js +0 -430
- package/dist/engines/pi/tool-context.d.ts +0 -45
- package/dist/engines/pi/tool-context.js +0 -31
- package/dist/engines/pi/tool.d.ts +0 -99
- package/dist/engines/pi/tool.js +0 -118
- package/dist/engines/pi/turn-kit.d.ts +0 -92
- package/dist/engines/pi/turn-kit.js +0 -288
- package/dist/engines/pi/wake-tool.d.ts +0 -9
- package/dist/engines/pi/wake-tool.js +0 -92
- package/dist/loader.d.ts +0 -41
- package/dist/loader.js +0 -116
- package/dist/node.d.ts +0 -3
- package/dist/node.js +0 -4
- package/dist/observe.d.ts +0 -6
- package/dist/observe.js +0 -47
- package/dist/pi.d.ts +0 -25
- package/dist/pi.js +0 -15
- package/dist/scaffold/templates/APPEND_SYSTEM.md +0 -12
- package/dist/scaffold/templates/tools/fetch-url.ts +0 -25
- package/dist/schedule/discover.d.ts +0 -17
- package/dist/schedule/discover.js +0 -61
- package/dist/schedule/routine.d.ts +0 -43
- package/dist/schedule/routine.js +0 -32
- package/dist/schedule/run.d.ts +0 -55
- package/dist/schedule/run.js +0 -146
- package/dist/secrets-gate.d.ts +0 -32
- package/dist/secrets-gate.js +0 -33
- package/dist/session-remote.d.ts +0 -47
- package/dist/session-remote.js +0 -460
- package/dist/session.d.ts +0 -466
- package/dist/session.js +0 -57
- /package/dist/{channels/kit/state.d.ts → state-file.d.ts} +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const REGISTRY_KINDS: readonly ["tools", "extensions", "channels", "schedules"];
|
|
2
|
+
type RegistryKind = (typeof REGISTRY_KINDS)[number];
|
|
3
|
+
/** One module of the definition: where it is, and what it default-exports. */
|
|
4
|
+
interface RegisteredModule {
|
|
5
|
+
/** Relative to the agent directory, as the author opens it: `src/tools/weather.ts`. */
|
|
6
|
+
file: string;
|
|
7
|
+
/** The file's name without its extension: a schedule's name. */
|
|
8
|
+
name: string;
|
|
9
|
+
value: unknown;
|
|
10
|
+
}
|
|
11
|
+
export type Registry = Record<RegistryKind, readonly RegisteredModule[]>;
|
|
12
|
+
export declare const GENERATED_DIR = ".fastagent";
|
|
13
|
+
export declare const REGISTRY_FILE = "registry.ts";
|
|
14
|
+
/** The registry's source: one default import per module, grouped by directory. */
|
|
15
|
+
export declare function registrySource(dir: string): Promise<string>;
|
|
16
|
+
/** Write `.fastagent/registry.ts` for the agent in `dir`, and return its path. */
|
|
17
|
+
export declare function generateRegistry(dir: string): Promise<string>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated registry (architecture.md §3): `.fastagent/registry.ts`, plain imports of every module in `src/tools/`,
|
|
3
|
+
* `src/extensions/`, `src/channels/` and `src/schedules/`, so nothing is imported by hand, types flow and a bundler can
|
|
4
|
+
* follow it. Each module default-exports one thing; a file directly in the directory is a module, and anything else a
|
|
5
|
+
* module imports (a helper) lives outside these directories. Generated from the files, never edited.
|
|
6
|
+
*/
|
|
7
|
+
import { readdir } from "node:fs/promises";
|
|
8
|
+
import { writeFileAtomic } from "../../atomic-write.js";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
export const REGISTRY_KINDS = ["tools", "extensions", "channels", "schedules"];
|
|
11
|
+
export const GENERATED_DIR = ".fastagent";
|
|
12
|
+
export const REGISTRY_FILE = "registry.ts";
|
|
13
|
+
const MODULE = /^([^.][^/]*)\.(ts|mts|js|mjs)$/;
|
|
14
|
+
const NOT_A_MODULE = /\.(d|test|spec)\.[a-z]+$/;
|
|
15
|
+
/** The module files of one directory of `src/`, sorted so the registry is the same wherever it is generated. */
|
|
16
|
+
async function modulesIn(dir, kind) {
|
|
17
|
+
let names;
|
|
18
|
+
try {
|
|
19
|
+
names = await readdir(join(dir, "src", kind));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code === "ENOENT")
|
|
23
|
+
return [];
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
return names.sort().flatMap((entry) => {
|
|
27
|
+
const match = MODULE.exec(entry);
|
|
28
|
+
return match === null || NOT_A_MODULE.test(entry)
|
|
29
|
+
? []
|
|
30
|
+
: [{ file: `src/${kind}/${entry}`, name: match[1] }];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** The registry's source: one default import per module, grouped by directory. */
|
|
34
|
+
export async function registrySource(dir) {
|
|
35
|
+
const lines = ["// Generated by fastagent from src/: do not edit.", ""];
|
|
36
|
+
const groups = [];
|
|
37
|
+
let index = 0;
|
|
38
|
+
for (const kind of REGISTRY_KINDS) {
|
|
39
|
+
const entries = [];
|
|
40
|
+
for (const { file, name } of await modulesIn(dir, kind)) {
|
|
41
|
+
const local = `m${index++}`;
|
|
42
|
+
lines.push(`import ${local} from ${JSON.stringify(`../${file}`)};`);
|
|
43
|
+
entries.push(` { file: ${JSON.stringify(file)}, name: ${JSON.stringify(name)}, value: ${local} },`);
|
|
44
|
+
}
|
|
45
|
+
groups.push(`export const ${kind} = [\n${entries.join("\n")}${entries.length > 0 ? "\n" : ""}];`);
|
|
46
|
+
}
|
|
47
|
+
return `${lines.join("\n")}${index > 0 ? "\n" : ""}\n${groups.join("\n\n")}\n`;
|
|
48
|
+
}
|
|
49
|
+
/** Write `.fastagent/registry.ts` for the agent in `dir`, and return its path. */
|
|
50
|
+
export async function generateRegistry(dir) {
|
|
51
|
+
const path = join(dir, GENERATED_DIR, REGISTRY_FILE);
|
|
52
|
+
// Every command that reads the definition writes it, `dev`'s worker importing it meanwhile: a reader must see a whole
|
|
53
|
+
// file, and two writers write the same bytes.
|
|
54
|
+
writeFileAtomic(path, await registrySource(dir), undefined, true);
|
|
55
|
+
return path;
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporary: pi-durable 1.3.0 leaves a conversation's queued messages waiting after a run that did not answer (a model
|
|
3
|
+
* failure, an abort, a message sent while an aborted run was still ending) until another submission places them, while
|
|
4
|
+
* the Agent API takes them whatever the message before them ended with (SPEC.md §5.3). A write of FastAgent's own
|
|
5
|
+
* kind places them. Remove once pi-durable ships `afterFailedRun` and it covers aborts too.
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from "@earendil-works/chord/context";
|
|
8
|
+
import { type Harness } from "@earendil-works/pi-durable";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import type * as Scope from "effect/Scope";
|
|
11
|
+
import { type PortFailure } from "../../effect-port.ts";
|
|
12
|
+
/** Release every conversation that waits now, and each one a run that did not answer leaves waiting, until the scope closes. */
|
|
13
|
+
export declare const releaseWaitingQueues: (harness: Harness, context: Context) => Effect.Effect<void, PortFailure, Scope.Scope>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { InboxDoc, LiveDoc } from "@earendil-works/pi-durable";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Queue from "effect/Queue";
|
|
4
|
+
import { port, portError } from "../../effect-port.js";
|
|
5
|
+
import { log } from "../../log.js";
|
|
6
|
+
import { RELEASE_ENTRY } from "./entries.js";
|
|
7
|
+
import { queuedInputs } from "./messages.js";
|
|
8
|
+
/** Release every conversation that waits now, and each one a run that did not answer leaves waiting, until the scope closes. */
|
|
9
|
+
export const releaseWaitingQueues = (harness, context) => Effect.gen(function* () {
|
|
10
|
+
const waiting = yield* Queue.unbounded();
|
|
11
|
+
const unsubscribe = harness.subscribeCommits(({ changes }) => {
|
|
12
|
+
for (const change of changes) {
|
|
13
|
+
const record = change.type === "submission" ? change.value : undefined;
|
|
14
|
+
if (record?.type === "input" && record.status === "unanswered") {
|
|
15
|
+
Queue.offerUnsafe(waiting, record.conversationId);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
yield* Effect.addFinalizer(() => Effect.sync(unsubscribe));
|
|
20
|
+
yield* Queue.take(waiting).pipe(Effect.flatMap((conversationId) => release(harness, conversationId, context).pipe(Effect.catchCause((cause) => Effect.sync(() => log.error(`[fastagent] could not take the messages waiting in conversation ${conversationId}: ${String(portError(cause))}`))))), Effect.forever, Effect.forkScoped);
|
|
21
|
+
const inspection = yield* port(() => harness.inspect(context));
|
|
22
|
+
for (const record of inspection.submissions) {
|
|
23
|
+
if (record.type === "input" && record.status === "queued")
|
|
24
|
+
Queue.offerUnsafe(waiting, record.conversationId);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
function release(harness, conversationId, context) {
|
|
28
|
+
return Effect.gen(function* () {
|
|
29
|
+
const live = yield* port(() => harness.snapshot(LiveDoc, conversationId, context));
|
|
30
|
+
if (live?.run !== undefined)
|
|
31
|
+
return;
|
|
32
|
+
const inbox = yield* port(() => harness.snapshot(InboxDoc, conversationId, context));
|
|
33
|
+
if (queuedInputs(inbox).length === 0)
|
|
34
|
+
return;
|
|
35
|
+
const conversation = yield* port(() => harness.conversation(conversationId, context));
|
|
36
|
+
if (conversation === undefined)
|
|
37
|
+
return;
|
|
38
|
+
yield* port(() => conversation.submit({ type: "write", entry: { kind: RELEASE_ENTRY, edits: [] } }, context));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { CredentialStore, ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type Harness } from "@earendil-works/pi-durable";
|
|
3
|
+
import type { CredentialSourceOptions } from "../models/credentials.ts";
|
|
4
|
+
import { type ModelEnvironment } from "../models/environment.ts";
|
|
5
|
+
import { type ResolvedContent } from "../../content/resolve.ts";
|
|
6
|
+
import type { Agent } from "../../agent-api.ts";
|
|
7
|
+
import { type Service } from "../../service.ts";
|
|
8
|
+
import type { DurableAgent, Unfinished } from "./agent.ts";
|
|
9
|
+
import { type AgentDefinition, type BuiltDefinition } from "./open.ts";
|
|
10
|
+
import { type StorageLocking } from "./storage.ts";
|
|
11
|
+
export interface OpenServingOptions extends CredentialSourceOptions {
|
|
12
|
+
/** What a deployment sets over the definition's defaults (a flag, `FASTAGENT_MODEL`). */
|
|
13
|
+
model?: string;
|
|
14
|
+
thinkingLevel?: ModelThinkingLevel;
|
|
15
|
+
/**
|
|
16
|
+
* Where the Agent keeps its sessions. Default `{ instance: "posix" }`: the instance's `agent.sqlite`, which one process
|
|
17
|
+
* holds at a time (storage.ts), with `wake` and `unwake`, since a serve's clock fires the wake-ups they keep. The other
|
|
18
|
+
* two never meet the storage a serve of the same directory holds, and have no `wake`, since no clock fires for them:
|
|
19
|
+
* `{ file }` is a database of the caller's own, held the same way (`holders`, when given, names in the refusal of a
|
|
20
|
+
* second opener what else opens it), and `"memory"` keeps nothing.
|
|
21
|
+
*/
|
|
22
|
+
storage?: ServingStorage;
|
|
23
|
+
/** What becomes of the messages an earlier process left unfinished in the storage. Default `"resume"`. */
|
|
24
|
+
unfinished?: Unfinished;
|
|
25
|
+
}
|
|
26
|
+
type ServingStorage = {
|
|
27
|
+
instance: StorageLocking;
|
|
28
|
+
} | {
|
|
29
|
+
file: string;
|
|
30
|
+
holders?: string;
|
|
31
|
+
} | "memory";
|
|
32
|
+
export interface OpenedServing {
|
|
33
|
+
agent: DurableAgent;
|
|
34
|
+
definition: AgentDefinition;
|
|
35
|
+
code: BuiltDefinition;
|
|
36
|
+
models: ModelEnvironment;
|
|
37
|
+
/** What the agent works on and knows, resolved for this instance. */
|
|
38
|
+
content: readonly ResolvedContent[];
|
|
39
|
+
agentDir: string;
|
|
40
|
+
/** Where the instance's state is: its storage, its channels' and schedules' state, its wake-ups. */
|
|
41
|
+
stateRoot: string;
|
|
42
|
+
/** The definition's channels and schedules, as `serveAgent` serves them: `serveAgent(opened)`. */
|
|
43
|
+
channels: BuiltDefinition["channels"];
|
|
44
|
+
schedules: BuiltDefinition["schedules"];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Run `work` holding the instance's storage, so no serve of the instance runs meanwhile: how a command changes the
|
|
48
|
+
* instance's state outside a serve without losing a write to a serve's own.
|
|
49
|
+
*/
|
|
50
|
+
export declare function holdingInstance<T>(stateRoot: string, work: () => T): Promise<T>;
|
|
51
|
+
export declare function openServing(directory: string, options?: OpenServingOptions): Promise<OpenedServing>;
|
|
52
|
+
/** An agent opened in this process (SPEC.md §8): the Agent API, and how it is served and operated. */
|
|
53
|
+
export interface LocalAgent extends Agent {
|
|
54
|
+
/**
|
|
55
|
+
* Channels, schedules and wake-ups, `/health`, and the Agent API under `/v1` when `apiKeys` holds a key: a Fetch
|
|
56
|
+
* handler, or a server of its own (`listen`).
|
|
57
|
+
*/
|
|
58
|
+
serve(options?: ServeAgentOptions): Promise<Service>;
|
|
59
|
+
/** pi-durable's own API, for operating the agent. */
|
|
60
|
+
readonly harness: Harness;
|
|
61
|
+
/** Close what `serve` started, then release the instance's storage. */
|
|
62
|
+
close(): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
export interface ServeAgentOptions {
|
|
65
|
+
/** The keys a Caller of the Agent API may present (SPEC.md §9.1). */
|
|
66
|
+
apiKeys?: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
export interface OpenAgentOptions {
|
|
69
|
+
/** `"provider/modelId"`, over the definition's: what a session that names none answers with. */
|
|
70
|
+
model?: string;
|
|
71
|
+
/** Where provider credentials are kept, instead of the agent's own `.secrets/auth.json` over the machine's. */
|
|
72
|
+
credentials?: CredentialStore;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Open the agent directory `dir` the way `dev` and `start` do before they serve: its definition, its code built with
|
|
76
|
+
* the secrets it declares, its environment, its content and the instance's storage, which one process holds at a time.
|
|
77
|
+
* Nothing reaches the agent until `serve`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function openAgent(dir: string, options?: OpenAgentOptions): Promise<LocalAgent>;
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An agent package opened: its definition prepared (the registry generated, the secrets manifest written), its code
|
|
3
|
+
* built with its secrets, its model environment, its storage, and the Agent over them. `openAgent` is how an app opens
|
|
4
|
+
* one (SPEC.md §8); `dev`, `start`, AgentCore, `invoke` and `schedules run` open it through `openServing`, which
|
|
5
|
+
* also takes where the Agent keeps its sessions and what becomes of an earlier process's messages.
|
|
6
|
+
*/
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
9
|
+
import { MemoryStorage } from "@earendil-works/pi-durable";
|
|
10
|
+
import { modelEnvironment } from "../models/environment.js";
|
|
11
|
+
import { loadContent } from "../../content/file.js";
|
|
12
|
+
import { absentNotice, cloneContent, contentAbsentHere, resolveContent, } from "../../content/resolve.js";
|
|
13
|
+
import { enterEnvironment } from "../../environment/mise.js";
|
|
14
|
+
import { log } from "../../log.js";
|
|
15
|
+
import { resolveStateRoot } from "../../paths.js";
|
|
16
|
+
import { serveAgent } from "../../service.js";
|
|
17
|
+
import { buildDefinition, assembleAgent, prepareDefinition, } from "./open.js";
|
|
18
|
+
import { openLockedStorage } from "./storage.js";
|
|
19
|
+
/** Who opens the instance's storage, for the refusal of a second opener. */
|
|
20
|
+
const INSTANCE_HOLDERS = "another `fastagent dev`, `start` or `schedules` command of this agent (or of another agent with the same state root)";
|
|
21
|
+
/**
|
|
22
|
+
* The content, once per process, before the agent opens: the locations are fixed until a restart, what is in them is
|
|
23
|
+
* read per message. This process runs the agent, so a repository with no checkout here is cloned, or its clone brought
|
|
24
|
+
* up to date in place where git can do so without touching the agent's work, first; then resolved again as what is now
|
|
25
|
+
* on disk.
|
|
26
|
+
*/
|
|
27
|
+
async function contentForThisRun(dir) {
|
|
28
|
+
const declaration = loadContent(dir);
|
|
29
|
+
for (const entry of resolveContent(dir, declaration)) {
|
|
30
|
+
if (entry.kind !== "github" || !entry.clone)
|
|
31
|
+
continue;
|
|
32
|
+
const done = await cloneContent(entry);
|
|
33
|
+
if (done.outcome === "kept") {
|
|
34
|
+
log.warn(`[fastagent] content "${entry.name}": the clone in ${entry.location} is kept as it is, ${done.reason}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const said = { cloned: "cloned", updated: "brought up to date", current: "already up to date" }[done.outcome];
|
|
38
|
+
log.info(`[fastagent] github ${entry.repo}${entry.ref ? ` at ${entry.ref}` : ""}: ${said} in ${entry.location}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const absent of contentAbsentHere(declaration))
|
|
42
|
+
log.warn(`[fastagent] content ${absentNotice(absent)}`);
|
|
43
|
+
return resolveContent(dir, declaration);
|
|
44
|
+
}
|
|
45
|
+
/** The instance's database, under its state root. */
|
|
46
|
+
const instanceStorageFile = (stateRoot) => join(stateRoot, "agent.sqlite");
|
|
47
|
+
/**
|
|
48
|
+
* Run `work` holding the instance's storage, so no serve of the instance runs meanwhile: how a command changes the
|
|
49
|
+
* instance's state outside a serve without losing a write to a serve's own.
|
|
50
|
+
*/
|
|
51
|
+
export async function holdingInstance(stateRoot, work) {
|
|
52
|
+
const storage = await openLockedStorage(instanceStorageFile(stateRoot), "posix", INSTANCE_HOLDERS);
|
|
53
|
+
try {
|
|
54
|
+
return work();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await storage.close(BACKGROUND_CONTEXT);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function openStorage(stateRoot, storage) {
|
|
61
|
+
if (storage === "memory")
|
|
62
|
+
return new MemoryStorage();
|
|
63
|
+
if ("file" in storage)
|
|
64
|
+
return openLockedStorage(storage.file, "posix", storage.holders);
|
|
65
|
+
return openLockedStorage(instanceStorageFile(stateRoot), storage.instance, INSTANCE_HOLDERS);
|
|
66
|
+
}
|
|
67
|
+
export async function openServing(directory, options = {}) {
|
|
68
|
+
const definition = await prepareDefinition(directory);
|
|
69
|
+
const { dir } = definition;
|
|
70
|
+
const code = buildDefinition(definition);
|
|
71
|
+
// The commands the agent runs, as `context.json`'s environment declares them, before anything can run one.
|
|
72
|
+
await enterEnvironment(dir);
|
|
73
|
+
const content = await contentForThisRun(dir);
|
|
74
|
+
const models = await modelEnvironment(dir, {
|
|
75
|
+
...(options.authPath === undefined ? {} : { authPath: options.authPath }),
|
|
76
|
+
...(options.credentialStore === undefined ? {} : { credentialStore: options.credentialStore }),
|
|
77
|
+
}, code.providers);
|
|
78
|
+
const stateRoot = resolveStateRoot(dir);
|
|
79
|
+
const chosen = options.storage ?? { instance: "posix" };
|
|
80
|
+
const storage = await openStorage(stateRoot, chosen);
|
|
81
|
+
const agent = await assembleAgent(definition, {
|
|
82
|
+
storage,
|
|
83
|
+
code,
|
|
84
|
+
content,
|
|
85
|
+
models: models.models,
|
|
86
|
+
...(models.catalogProblem === undefined ? {} : { catalogProblem: models.catalogProblem }),
|
|
87
|
+
...(typeof chosen === "object" && "instance" in chosen ? { stateRoot } : {}),
|
|
88
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
89
|
+
...(options.thinkingLevel === undefined ? {} : { thinkingLevel: options.thinkingLevel }),
|
|
90
|
+
...(options.unfinished === undefined ? {} : { unfinished: options.unfinished }),
|
|
91
|
+
}).catch(async (error) => {
|
|
92
|
+
// An Agent that did not open does not own the storage, and its lock would refuse this process's next attempt.
|
|
93
|
+
await storage.close(BACKGROUND_CONTEXT);
|
|
94
|
+
throw error;
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
agent,
|
|
98
|
+
definition,
|
|
99
|
+
code,
|
|
100
|
+
models,
|
|
101
|
+
content,
|
|
102
|
+
agentDir: dir,
|
|
103
|
+
stateRoot,
|
|
104
|
+
channels: code.channels,
|
|
105
|
+
schedules: code.schedules,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Open the agent directory `dir` the way `dev` and `start` do before they serve: its definition, its code built with
|
|
110
|
+
* the secrets it declares, its environment, its content and the instance's storage, which one process holds at a time.
|
|
111
|
+
* Nothing reaches the agent until `serve`.
|
|
112
|
+
*/
|
|
113
|
+
export async function openAgent(dir, options = {}) {
|
|
114
|
+
const opened = await openServing(dir, {
|
|
115
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
116
|
+
...(options.credentials === undefined ? {} : { credentialStore: options.credentials }),
|
|
117
|
+
});
|
|
118
|
+
const { agent } = opened;
|
|
119
|
+
let served;
|
|
120
|
+
let closing;
|
|
121
|
+
return {
|
|
122
|
+
info: agent.info,
|
|
123
|
+
usage: agent.usage,
|
|
124
|
+
sessions: agent.sessions,
|
|
125
|
+
messages: agent.messages,
|
|
126
|
+
harness: agent.harness,
|
|
127
|
+
serve(serveOptions = {}) {
|
|
128
|
+
// Its storage is let go, or about to be: a service now would run channels and schedules nobody closes.
|
|
129
|
+
if (closing !== undefined)
|
|
130
|
+
return Promise.reject(new Error("this agent is closed"));
|
|
131
|
+
if (served !== undefined)
|
|
132
|
+
return Promise.reject(new Error("this agent is already served"));
|
|
133
|
+
const serving = serveAgent(opened, serveOptions);
|
|
134
|
+
served = serving;
|
|
135
|
+
// A serve that failed to start rolled itself back, so the agent may be served again.
|
|
136
|
+
serving.catch(() => {
|
|
137
|
+
if (served === serving)
|
|
138
|
+
served = undefined;
|
|
139
|
+
});
|
|
140
|
+
return serving;
|
|
141
|
+
},
|
|
142
|
+
close() {
|
|
143
|
+
closing ??= (async () => {
|
|
144
|
+
// A serve that never started has nothing to close; its failure reached the caller of `serve`.
|
|
145
|
+
const service = await served?.catch(() => undefined);
|
|
146
|
+
const failures = [];
|
|
147
|
+
await service?.close().catch((error) => failures.push(error));
|
|
148
|
+
await agent.close().catch((error) => failures.push(error));
|
|
149
|
+
if (failures.length > 0) {
|
|
150
|
+
throw failures.length === 1 ? failures[0] : new AggregateError(failures, "the agent did not close cleanly");
|
|
151
|
+
}
|
|
152
|
+
})();
|
|
153
|
+
return closing;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which conversations are sessions, and each one's latest activity (SPEC.md §4), kept in memory as a cache of the
|
|
3
|
+
* storage: read once when the Agent opens, then moved forward by every commit, so `sessions.list` orders without
|
|
4
|
+
* reading the storage and every operation asks the same question `sessions.list` answers.
|
|
5
|
+
*
|
|
6
|
+
* pi-durable mints every record's id from one counter that grows across reopen (`Storage.mintId`), so a session's
|
|
7
|
+
* latest activity is the newest of its own ids: its creation, its newest message, its newest entry. No clock is
|
|
8
|
+
* needed. A conversation a task owns (a subagent's) is the harness's own work, not a session.
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from "@earendil-works/chord/context";
|
|
11
|
+
import type { ConversationId, ConversationRecord, Harness, Storage } from "@earendil-works/pi-durable";
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import type * as Scope from "effect/Scope";
|
|
14
|
+
import { type PortFailure } from "../../effect-port.ts";
|
|
15
|
+
export declare const isSession: (record: ConversationRecord) => boolean;
|
|
16
|
+
interface SessionActivity {
|
|
17
|
+
conversationId: ConversationId;
|
|
18
|
+
activity: number;
|
|
19
|
+
}
|
|
20
|
+
export interface Sessions {
|
|
21
|
+
has(id: ConversationId): boolean;
|
|
22
|
+
/** Most recently active first. */
|
|
23
|
+
byActivity(): SessionActivity[];
|
|
24
|
+
}
|
|
25
|
+
/** The cache, following commits until the scope closes. */
|
|
26
|
+
export declare const trackSessions: (harness: Harness, storage: Storage, context: Context) => Effect.Effect<Sessions, PortFailure, Scope.Scope>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import * as Stream from "effect/Stream";
|
|
4
|
+
import { port } from "../../effect-port.js";
|
|
5
|
+
const SCAN_PAGE = 500;
|
|
6
|
+
export const isSession = (record) => record.owner === undefined;
|
|
7
|
+
const conversations = (storage, context) => Stream.paginate(undefined, (cursor) => port(() => storage.scanConversations({}, SCAN_PAGE, cursor, context)).pipe(Effect.map((page) => [[page.items], Option.fromUndefinedOr(page.next)]))).pipe(Stream.flattenIterable);
|
|
8
|
+
/** What the storage holds before the cache follows commits: the newest entry an existing session wrote, and message. */
|
|
9
|
+
const storedActivity = (storage, context, conversation) => Effect.all([
|
|
10
|
+
port(() => storage.scanEntries({ conversationId: conversation.id, order: "descending" }, 1, undefined, context)),
|
|
11
|
+
port(() => storage.scanSubmissions({ conversationId: conversation.id, order: "descending" }, 1, undefined, context)),
|
|
12
|
+
], { concurrency: "unbounded" }).pipe(
|
|
13
|
+
// An entry a fork shares is older than the fork, so the fork-aware entry scan cannot overstate it.
|
|
14
|
+
Effect.map(([entries, submissions]) => Math.max(conversation.id, entries.items[0]?.id ?? 0, submissions.items[0]?.id ?? 0)));
|
|
15
|
+
/** The cache, following commits until the scope closes. */
|
|
16
|
+
export const trackSessions = (harness, storage, context) => Effect.gen(function* () {
|
|
17
|
+
const sessions = new Set();
|
|
18
|
+
const newest = new Map();
|
|
19
|
+
const touch = (id, value) => {
|
|
20
|
+
if (value > (newest.get(id) ?? 0))
|
|
21
|
+
newest.set(id, value);
|
|
22
|
+
};
|
|
23
|
+
// Subscribed before the scan, so a commit while it reads is not lost: either reports the newest id, and the
|
|
24
|
+
// larger stands. A change's conversation may be created in the same commit, so membership is not checked here.
|
|
25
|
+
yield* Effect.acquireRelease(Effect.sync(() => harness.subscribeCommits(({ changes }) => {
|
|
26
|
+
for (const change of changes) {
|
|
27
|
+
if (change.type === "conversation") {
|
|
28
|
+
if (isSession(change.value))
|
|
29
|
+
sessions.add(change.value.id);
|
|
30
|
+
touch(change.value.id, change.value.id);
|
|
31
|
+
}
|
|
32
|
+
else if (change.type === "entry" || change.type === "submission") {
|
|
33
|
+
touch(change.value.conversationId, change.value.id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})), (unsubscribe) => Effect.sync(unsubscribe));
|
|
37
|
+
yield* conversations(storage, context).pipe(Stream.filter(isSession), Stream.runForEach((conversation) => storedActivity(storage, context, conversation).pipe(Effect.tap((activity) => Effect.sync(() => {
|
|
38
|
+
sessions.add(conversation.id);
|
|
39
|
+
touch(conversation.id, activity);
|
|
40
|
+
})))));
|
|
41
|
+
return {
|
|
42
|
+
has: (id) => sessions.has(id),
|
|
43
|
+
byActivity: () => [...sessions]
|
|
44
|
+
.map((conversationId) => ({ conversationId, activity: newest.get(conversationId) ?? conversationId }))
|
|
45
|
+
.sort((a, b) => b.activity - a.activity),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Storage } from "@earendil-works/pi-durable";
|
|
2
|
+
export type StorageLocking = "posix" | "leased-dotfile";
|
|
3
|
+
/** Open the database at `path`, or refuse, naming `holders` (what else opens it) when given. */
|
|
4
|
+
export declare function openLockedStorage(path: string, locking: StorageLocking, holders?: string): Promise<Storage>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A storage only one process opens: the instance's SQLite database, held by the one process that owns the instance
|
|
3
|
+
* (architecture.md §4.3), and `fastagent invoke`'s own. pi-durable does no cross-process locking of its own, and in
|
|
4
|
+
* WAL mode SQLite lets a second process open and write the same file, which would resume and run the same messages
|
|
5
|
+
* twice. So the database is opened in EXCLUSIVE locking mode: the first access takes the file's lock and keeps it
|
|
6
|
+
* until the process closes it or dies, and a second opener is refused. Locally that is SQLite's own POSIX lock. On a
|
|
7
|
+
* file system where those fail (AgentCore's session storage), the database locks by a `<path>.lock` directory
|
|
8
|
+
* instead, which outlives a stopped microVM: the process that holds the deployment's lease knows such a lock is a dead
|
|
9
|
+
* process's, removes it, and says so.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, mkdirSync, rmdirSync } from "node:fs";
|
|
12
|
+
import { dirname } from "node:path";
|
|
13
|
+
import { DatabaseSync } from "node:sqlite";
|
|
14
|
+
import { NodeSqliteDatabase } from "@earendil-works/pi-durable/storage/sqlite/node";
|
|
15
|
+
import { SqliteStorage } from "@earendil-works/pi-durable/storage/sqlite";
|
|
16
|
+
import { log } from "../../log.js";
|
|
17
|
+
/** How long an opener waits for a lock: long enough for a restart's old process to finish closing. */
|
|
18
|
+
const BUSY_TIMEOUT_MS = 5_000;
|
|
19
|
+
const SQLITE_BUSY = 5;
|
|
20
|
+
/** Open the database at `path`, or refuse, naming `holders` (what else opens it) when given. */
|
|
21
|
+
export async function openLockedStorage(path, locking, holders) {
|
|
22
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
23
|
+
if (locking === "leased-dotfile")
|
|
24
|
+
breakStaleLock(`${path}.lock`);
|
|
25
|
+
const location = locking === "posix" ? path : `file:${path}?vfs=unix-dotfile`;
|
|
26
|
+
const database = new DatabaseSync(location, { timeout: BUSY_TIMEOUT_MS });
|
|
27
|
+
const adapter = new NodeSqliteDatabase(database);
|
|
28
|
+
try {
|
|
29
|
+
// In this order: an exclusive lock keeps WAL's index in this process's memory, so no shared memory is needed.
|
|
30
|
+
await adapter.exec("PRAGMA locking_mode = EXCLUSIVE");
|
|
31
|
+
await adapter.exec("PRAGMA journal_mode = WAL");
|
|
32
|
+
await adapter.exec("PRAGMA synchronous = NORMAL");
|
|
33
|
+
return await SqliteStorage.open(adapter);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// The connection only: the adapter's close checkpoints first, which a refused opener cannot do.
|
|
37
|
+
database.close();
|
|
38
|
+
if (error.errcode !== SQLITE_BUSY)
|
|
39
|
+
throw error;
|
|
40
|
+
const who = holders === undefined ? "" : `: ${holders} is running`;
|
|
41
|
+
throw new Error(`the storage ${path} is held by another process${who}. Stop that one first`, { cause: error });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function breakStaleLock(lock) {
|
|
45
|
+
if (!existsSync(lock))
|
|
46
|
+
return;
|
|
47
|
+
rmdirSync(lock);
|
|
48
|
+
log.warn(`[fastagent] removed ${lock}: a process that held the storage stopped without releasing it`);
|
|
49
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `messages.stream` (SPEC.md §6.2): send a message and deliver it as it is written, over the Agent's own reads.
|
|
3
|
+
* The session is watched before the message is sent, so nothing it writes afterwards is missed; a send whose key the
|
|
4
|
+
* session has seen delivers that message from wherever it is.
|
|
5
|
+
*/
|
|
6
|
+
import { type Entry, type Message, type MessageStreamEvent, type Page, type SendRequest, type SessionEvent } from "../../agent-api.ts";
|
|
7
|
+
export interface StreamSource {
|
|
8
|
+
events(sessionId: string, after?: string): AsyncIterable<SessionEvent>;
|
|
9
|
+
/** A send, and whether its key had already been sent: the message is then delivered from where it is. */
|
|
10
|
+
admit(request: SendRequest): Promise<{
|
|
11
|
+
message: Message;
|
|
12
|
+
replayed: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
get(sessionId: string, messageId: string): Promise<Message>;
|
|
15
|
+
entry(sessionId: string, entryId: string): Promise<Entry>;
|
|
16
|
+
entries(sessionId: string, options: {
|
|
17
|
+
after: string;
|
|
18
|
+
}): Promise<Page<Entry>>;
|
|
19
|
+
}
|
|
20
|
+
export declare function messageStream(source: StreamSource, request: SendRequest, signal: AbortSignal | undefined): AsyncIterable<MessageStreamEvent>;
|