@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,136 @@
|
|
|
1
|
+
import type { Person } from "./person.ts";
|
|
2
|
+
type TicketStatus = "starting" | "need_user_action" | "working" | "done" | "failed" | "expired";
|
|
3
|
+
export interface Ticket {
|
|
4
|
+
ticket: string;
|
|
5
|
+
/** What the step is for, one per subject at a time: `login:openai`, `add:feishu:production`. */
|
|
6
|
+
subject: string;
|
|
7
|
+
/** What the person does, to follow "so they can": `sign in to OpenAI`, `confirm the new Feishu app`. */
|
|
8
|
+
action: string;
|
|
9
|
+
/** The command that reads the ticket, its id appended: `fastagent login --check`. */
|
|
10
|
+
check: string;
|
|
11
|
+
/** The command that starts the step again. */
|
|
12
|
+
restart: string;
|
|
13
|
+
status: TicketStatus;
|
|
14
|
+
/** When the step gives up (ms since the epoch). */
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
/** The process running the step. */
|
|
17
|
+
pid?: number;
|
|
18
|
+
/** Where the person acts. */
|
|
19
|
+
url?: string;
|
|
20
|
+
/** What they enter there, for a device sign-in. */
|
|
21
|
+
code?: string;
|
|
22
|
+
/** What the provider or platform says to do there. */
|
|
23
|
+
instructions?: string;
|
|
24
|
+
/** When the person's step itself stops being usable (a confirmation link's own expiry), if before `expiresAt`. */
|
|
25
|
+
until?: number;
|
|
26
|
+
/** The person's part is done and the step is finishing it: never stopped halfway. */
|
|
27
|
+
finishing?: boolean;
|
|
28
|
+
/** What every report of the ticket carries beside its status (the provider a sign-in is to, the deployment). */
|
|
29
|
+
fields?: Record<string, string>;
|
|
30
|
+
/** The step's browser may end on a page this machine never sees, whose address `--check --code` hands over. */
|
|
31
|
+
takesAddress?: boolean;
|
|
32
|
+
/** Steps left to the person after the run, which nothing waits for. */
|
|
33
|
+
afterwards?: {
|
|
34
|
+
instructions: string;
|
|
35
|
+
url?: string;
|
|
36
|
+
}[];
|
|
37
|
+
/** Why the step failed. */
|
|
38
|
+
message?: string;
|
|
39
|
+
/** What the step reports when done, in fields of its own (a credentials path, the values it wrote). */
|
|
40
|
+
result?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/** What the step's process says while it runs: the place to look when it fails. */
|
|
43
|
+
export declare const ticketLogPath: (id: string) => string;
|
|
44
|
+
export interface TicketRequest {
|
|
45
|
+
subject: string;
|
|
46
|
+
action: string;
|
|
47
|
+
check: string;
|
|
48
|
+
restart: string;
|
|
49
|
+
timeoutSeconds: number;
|
|
50
|
+
/** {@link Ticket.fields}. */
|
|
51
|
+
fields?: Record<string, string>;
|
|
52
|
+
}
|
|
53
|
+
/** A new ticket, its step not started yet. Not public: what `startTicket` starts, and a test seam. */
|
|
54
|
+
export declare function createTicket(request: TicketRequest): Ticket;
|
|
55
|
+
/**
|
|
56
|
+
* Start a step in a process of its own and return its ticket once the person has something to do, or it has ended.
|
|
57
|
+
*/
|
|
58
|
+
export declare function startTicket(input: TicketRequest & {
|
|
59
|
+
/** The command that runs the step, its arguments, and the directory it runs in; `--ticket <id>` is added. */
|
|
60
|
+
command: {
|
|
61
|
+
file: string;
|
|
62
|
+
args: readonly string[];
|
|
63
|
+
cwd: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* true: the person's part comes first (a sign-in's address), and a step that gives none within the start timeout
|
|
67
|
+
* is stopped. false: the step may work a while first (a tunnel, a verification), or need no one at all, and one
|
|
68
|
+
* still at it then is returned working, its process carrying on.
|
|
69
|
+
*/
|
|
70
|
+
waitsFirst: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* A step for the same subject still running: `replace` stops one waiting on its person or still getting ready (a
|
|
73
|
+
* sign-in, holding a port the new one needs) and answers with one finishing what its person did; `join` answers
|
|
74
|
+
* with it whatever it is doing (a deploy, which must not be stopped halfway, even between its person's steps).
|
|
75
|
+
*/
|
|
76
|
+
whenRunning: "replace" | "join";
|
|
77
|
+
/** How long the step may take to give the person something to do; a test's is short. */
|
|
78
|
+
startTimeoutMs?: number;
|
|
79
|
+
}): Promise<Ticket>;
|
|
80
|
+
/**
|
|
81
|
+
* Where the step `id` stands. `address`: what the person's browser ended on (or the code its page showed), for a step
|
|
82
|
+
* waiting on one; it is handed over, and the answer is the ticket once the step has taken it.
|
|
83
|
+
*/
|
|
84
|
+
export declare function checkTicket(id: string, address?: string): Promise<Ticket | undefined>;
|
|
85
|
+
/**
|
|
86
|
+
* Stop the step of ticket `id` where it is, and everything it started, on a person's or an agent's say: the way out of
|
|
87
|
+
* a step that hangs while finishing, which no deadline cuts. What it was doing may be left half-done.
|
|
88
|
+
*/
|
|
89
|
+
export declare function stopTicket(id: string): Promise<Ticket>;
|
|
90
|
+
/**
|
|
91
|
+
* Why the command whose `--check` is `check` cannot read ticket `id`: there is none on this machine, or it is another
|
|
92
|
+
* command's, named so the agent runs that one. Undefined when it is this command's.
|
|
93
|
+
*/
|
|
94
|
+
export declare function refusedCheck(id: string, check: string): string | undefined;
|
|
95
|
+
/** What a step's run is handed: the person's part as it comes, and the address `--check --code` brings. */
|
|
96
|
+
export interface TicketStep {
|
|
97
|
+
/** When the step gives up. */
|
|
98
|
+
signal: AbortSignal;
|
|
99
|
+
/** The person, as the run speaks to them (person.ts): what it waits on, what it leaves them. */
|
|
100
|
+
person: Person;
|
|
101
|
+
/** The person's step the run now waits on, with what a sign-in's flow says of it. */
|
|
102
|
+
waits(on: {
|
|
103
|
+
url: string;
|
|
104
|
+
code?: string;
|
|
105
|
+
instructions?: string;
|
|
106
|
+
takesAddress?: boolean;
|
|
107
|
+
until?: number;
|
|
108
|
+
}): void;
|
|
109
|
+
/** The address `--check --code` hands over, once it does. */
|
|
110
|
+
address(signal: AbortSignal): Promise<string>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The step of ticket `id`, in the process `startTicket` started: `run` does it, through what it is handed, and
|
|
114
|
+
* resolves with what to report once done. Whatever it throws fails the ticket, the whole error going to the log. At
|
|
115
|
+
* the deadline the ticket expires, whether or not `run` heeds the signal (the caller ends the process after this, and
|
|
116
|
+
* a run still going ends with it), unless the step is finishing what its person did: that runs to its end, and
|
|
117
|
+
* expires only if it waits on its person again.
|
|
118
|
+
*/
|
|
119
|
+
export declare function runTicket(id: string, run: (step: TicketStep) => Promise<Record<string, unknown> | undefined>): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* A ticket as its command reports it, with what to do next (`agent_next_steps`): one JSON object, or its lines for a
|
|
122
|
+
* person. `done` is what the step's `result` adds to the report, beside a sentence for the agent.
|
|
123
|
+
*/
|
|
124
|
+
export declare function ticketReport(ticket: Ticket, when: "started" | "checked", done?: (result: Record<string, unknown>) => {
|
|
125
|
+
fields: Record<string, unknown>;
|
|
126
|
+
text: string[];
|
|
127
|
+
next?: string;
|
|
128
|
+
}): {
|
|
129
|
+
json: Record<string, unknown>;
|
|
130
|
+
text: string[];
|
|
131
|
+
};
|
|
132
|
+
/** Print one outcome on stdout, the result it is: the JSON object with `--json`, else its lines for a person. */
|
|
133
|
+
export declare function printReport(asJson: boolean | undefined, json: Record<string, unknown>, text: readonly string[]): void;
|
|
134
|
+
/** The exit code a ticket's command ends with: 1 once it failed or expired, 0 while it runs or once done. */
|
|
135
|
+
export declare const ticketExitCode: (ticket: Ticket) => 0 | 1;
|
|
136
|
+
export {};
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A step a coding agent starts and a person finishes: an account sign-in, a new app's confirmation, an app's install.
|
|
3
|
+
* A coding agent works in turns: it runs a command, reads what it printed once it ends, then talks to its user. So the
|
|
4
|
+
* step outlives the command that starts it, as Netlify's `login --request`/`--check` and Dosu's login tickets have it:
|
|
5
|
+
* the command starts a process of its own that runs the step and keeps a ticket up to date, and returns as soon as
|
|
6
|
+
* there is something for the person to do. The command's `--check <ticket>` reads where it stands from any process,
|
|
7
|
+
* and hands the step the address a browser ended on when that browser could not reach this machine.
|
|
8
|
+
*
|
|
9
|
+
* A ticket is a file in the machine's own state, ~/.fastagent/.state/tickets, written whole by one process at a time:
|
|
10
|
+
* the starting command until the step starts, then the step alone (or whoever finds its process gone). One that has
|
|
11
|
+
* ended stays as it ended. A newer step for the same subject stops an older one still waiting on its person, whose
|
|
12
|
+
* process would otherwise hold what it holds (a sign-in's callback port, OpenAI's fixed) until it expires; so does one
|
|
13
|
+
* still getting ready (reaching a box, opening a tunnel), which may hang there. One finishing what its person did (a
|
|
14
|
+
* code exchanged, an app created from a pasted token) is never stopped halfway, and the newer attempt is answered
|
|
15
|
+
* with it.
|
|
16
|
+
*
|
|
17
|
+
* The step's process leads a process group of its own, so what it starts (a tunnel, a host CLI, a shell to a box) is
|
|
18
|
+
* stopped with it, by `stop` and when it exits.
|
|
19
|
+
*/
|
|
20
|
+
import { randomBytes } from "node:crypto";
|
|
21
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
22
|
+
import { spawn } from "node:child_process";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { writeFileAtomic } from "../atomic-write.js";
|
|
25
|
+
import { STATE_DIRNAME, globalHome } from "../paths.js";
|
|
26
|
+
import { recordFailures } from "./fail.js";
|
|
27
|
+
/** How long a starting step may take to give the person something to do before its command gives up on it. */
|
|
28
|
+
const START_TIMEOUT_MS = 30_000;
|
|
29
|
+
/** How long `--check` waits for a step to take the address it was handed. */
|
|
30
|
+
const ADDRESS_TIMEOUT_MS = 15_000;
|
|
31
|
+
/** How long a stopped step's process has to let go of what it holds before it is killed. */
|
|
32
|
+
const STOP_TIMEOUT_MS = 5_000;
|
|
33
|
+
const POLL_MS = 200;
|
|
34
|
+
/** Ticket files are random hex: anything else names no ticket, and no file outside the directory. */
|
|
35
|
+
const TICKET_ID = /^[0-9a-f]{16}$/;
|
|
36
|
+
const ticketsDir = () => join(globalHome(), STATE_DIRNAME, "tickets");
|
|
37
|
+
const ticketPath = (id) => join(ticketsDir(), `${id}.json`);
|
|
38
|
+
const addressPath = (id) => join(ticketsDir(), `${id}.address`);
|
|
39
|
+
/** What the step's process says while it runs: the place to look when it fails. */
|
|
40
|
+
export const ticketLogPath = (id) => join(ticketsDir(), `${id}.log`);
|
|
41
|
+
function readTicket(id) {
|
|
42
|
+
if (!TICKET_ID.test(id))
|
|
43
|
+
return undefined;
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(readFileSync(ticketPath(id), "utf8"));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error.code === "ENOENT")
|
|
49
|
+
return undefined;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const writeTicket = (ticket) => writeFileAtomic(ticketPath(ticket.ticket), JSON.stringify(ticket));
|
|
54
|
+
const waiting = (ticket) => ticket.status === "starting" || ticket.status === "need_user_action" || ticket.status === "working";
|
|
55
|
+
/**
|
|
56
|
+
* Change the ticket `id` while its step still runs; one that has ended is returned as it ended. A writer that read it
|
|
57
|
+
* before it ended (a check that found its process gone the moment it finished) cannot reopen it.
|
|
58
|
+
*/
|
|
59
|
+
function update(id, change) {
|
|
60
|
+
const current = readTicket(id);
|
|
61
|
+
if (current === undefined)
|
|
62
|
+
throw new Error(`ticket ${id} is gone from ${ticketsDir()}`);
|
|
63
|
+
if (!waiting(current))
|
|
64
|
+
return current;
|
|
65
|
+
const next = { ...current, ...change };
|
|
66
|
+
writeTicket(next);
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
/** Whether the process `pid` still runs: signal 0 delivers nothing and only says whether it could. */
|
|
70
|
+
function running(pid) {
|
|
71
|
+
if (pid === undefined)
|
|
72
|
+
return false;
|
|
73
|
+
try {
|
|
74
|
+
process.kill(pid, 0);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return error.code === "EPERM";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
82
|
+
/**
|
|
83
|
+
* Signal the process group `pid` leads. A group gone has nothing left to stop (ESRCH), and one this user may not
|
|
84
|
+
* signal is not ours (EPERM, which macOS also answers for a group of one exited, unreaped process).
|
|
85
|
+
*/
|
|
86
|
+
function signalGroup(pid, signal) {
|
|
87
|
+
try {
|
|
88
|
+
process.kill(-pid, signal);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const code = error.code;
|
|
92
|
+
if (code !== "ESRCH" && code !== "EPERM")
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Stop a step's process and everything it started, and wait until it is gone, so what it held is free. */
|
|
97
|
+
async function stop(pid) {
|
|
98
|
+
if (!running(pid))
|
|
99
|
+
return;
|
|
100
|
+
signalGroup(pid, "SIGTERM");
|
|
101
|
+
for (const deadline = Date.now() + STOP_TIMEOUT_MS; Date.now() < deadline; await sleep(POLL_MS)) {
|
|
102
|
+
if (!running(pid))
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
// What the step started may outlive it, or ignore SIGTERM.
|
|
106
|
+
signalGroup(pid, "SIGKILL");
|
|
107
|
+
}
|
|
108
|
+
/** Whether this process leads a process group: a step's process, which `startTicket` starts detached. */
|
|
109
|
+
function leadsGroup() {
|
|
110
|
+
try {
|
|
111
|
+
process.kill(-process.pid, 0);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The steps for `subject` still running. Only an unexpired one is taken for a process of ours: its pid is minutes old,
|
|
120
|
+
* where one an ended process left could by now name another's.
|
|
121
|
+
*/
|
|
122
|
+
function runningFor(subject, now) {
|
|
123
|
+
if (!existsSync(ticketsDir()))
|
|
124
|
+
return [];
|
|
125
|
+
return readdirSync(ticketsDir()).flatMap((file) => {
|
|
126
|
+
const ticket = file.endsWith(".json") ? readTicket(file.slice(0, -".json".length)) : undefined;
|
|
127
|
+
// One finishing past its deadline still runs: the deadline is for the person, never for work begun.
|
|
128
|
+
const current = ticket !== undefined && (ticket.expiresAt >= now || ticket.finishing === true);
|
|
129
|
+
const runs = ticket?.subject === subject && waiting(ticket) && current && running(ticket.pid);
|
|
130
|
+
return runs ? [ticket] : [];
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Before a newer step for `subject` starts: one finishing what its person did is the answer instead, never stopped
|
|
135
|
+
* halfway (an app created whose credentials are not written yet); every other one, waiting on its person or still
|
|
136
|
+
* getting ready, is stopped. Answers the one finishing.
|
|
137
|
+
*/
|
|
138
|
+
async function supersede(subject, now) {
|
|
139
|
+
const runs = runningFor(subject, now);
|
|
140
|
+
const finishing = runs.find((ticket) => ticket.finishing === true);
|
|
141
|
+
if (finishing !== undefined)
|
|
142
|
+
return finishing;
|
|
143
|
+
for (const ticket of runs) {
|
|
144
|
+
if (ticket.pid !== undefined)
|
|
145
|
+
await stop(ticket.pid);
|
|
146
|
+
update(ticket.ticket, { status: "failed", message: `replaced by a newer attempt to ${ticket.action}` });
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
/** Tickets a day past their end: each one's files go. */
|
|
151
|
+
function prune(now) {
|
|
152
|
+
if (!existsSync(ticketsDir()))
|
|
153
|
+
return;
|
|
154
|
+
for (const file of readdirSync(ticketsDir())) {
|
|
155
|
+
const id = file.slice(0, 16);
|
|
156
|
+
const ticket = TICKET_ID.test(id) ? readTicket(id) : undefined;
|
|
157
|
+
if (ticket !== undefined && ticket.expiresAt < now - 24 * 60 * 60 * 1000) {
|
|
158
|
+
for (const path of [ticketPath(id), addressPath(id), ticketLogPath(id)])
|
|
159
|
+
rmSync(path, { force: true });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** A new ticket, its step not started yet. Not public: what `startTicket` starts, and a test seam. */
|
|
164
|
+
export function createTicket(request) {
|
|
165
|
+
const now = Date.now();
|
|
166
|
+
prune(now);
|
|
167
|
+
mkdirSync(ticketsDir(), { recursive: true });
|
|
168
|
+
const ticket = {
|
|
169
|
+
ticket: randomBytes(8).toString("hex"),
|
|
170
|
+
subject: request.subject,
|
|
171
|
+
action: request.action,
|
|
172
|
+
check: request.check,
|
|
173
|
+
restart: request.restart,
|
|
174
|
+
status: "starting",
|
|
175
|
+
expiresAt: now + request.timeoutSeconds * 1000,
|
|
176
|
+
...(request.fields === undefined ? {} : { fields: request.fields }),
|
|
177
|
+
};
|
|
178
|
+
writeTicket(ticket);
|
|
179
|
+
return ticket;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Start a step in a process of its own and return its ticket once the person has something to do, or it has ended.
|
|
183
|
+
*/
|
|
184
|
+
export async function startTicket(input) {
|
|
185
|
+
if (input.whenRunning === "join") {
|
|
186
|
+
const [joined] = runningFor(input.subject, Date.now());
|
|
187
|
+
if (joined !== undefined)
|
|
188
|
+
return joined;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const finishing = await supersede(input.subject, Date.now());
|
|
192
|
+
if (finishing !== undefined)
|
|
193
|
+
return finishing;
|
|
194
|
+
}
|
|
195
|
+
const id = createTicket(input).ticket;
|
|
196
|
+
const startTimeoutMs = input.startTimeoutMs ?? START_TIMEOUT_MS;
|
|
197
|
+
const log = openSync(ticketLogPath(id), "a");
|
|
198
|
+
const child = spawn(input.command.file, [...input.command.args, "--ticket", id], {
|
|
199
|
+
cwd: input.command.cwd,
|
|
200
|
+
detached: true,
|
|
201
|
+
stdio: ["ignore", log, log],
|
|
202
|
+
env: process.env,
|
|
203
|
+
});
|
|
204
|
+
closeSync(log);
|
|
205
|
+
let exited = false;
|
|
206
|
+
child.once("exit", () => {
|
|
207
|
+
exited = true;
|
|
208
|
+
});
|
|
209
|
+
child.unref();
|
|
210
|
+
const settled = (ticket) => ticket.status !== "starting" && ticket.status !== "working";
|
|
211
|
+
for (const deadline = Date.now() + startTimeoutMs; Date.now() < deadline;) {
|
|
212
|
+
const ticket = readTicket(id);
|
|
213
|
+
if (settled(ticket))
|
|
214
|
+
return ticket;
|
|
215
|
+
if (exited)
|
|
216
|
+
break;
|
|
217
|
+
await sleep(POLL_MS);
|
|
218
|
+
}
|
|
219
|
+
const ticket = readTicket(id);
|
|
220
|
+
if (settled(ticket))
|
|
221
|
+
return ticket;
|
|
222
|
+
// Claimed and at work: the step writes the ticket now, so this command reports it as it is.
|
|
223
|
+
if (!exited && !input.waitsFirst && ticket.status === "working")
|
|
224
|
+
return ticket;
|
|
225
|
+
// Given up on, unclaimed or giving the person nothing: its process goes first, or it would hold its port and write
|
|
226
|
+
// the ticket after this.
|
|
227
|
+
const stoppedItself = exited;
|
|
228
|
+
if (!stoppedItself && child.pid !== undefined)
|
|
229
|
+
await stop(child.pid);
|
|
230
|
+
const within = `${startTimeoutMs / 1000}s: ${ticketLogPath(id)} says why`;
|
|
231
|
+
return update(id, {
|
|
232
|
+
status: "failed",
|
|
233
|
+
message: stoppedItself
|
|
234
|
+
? `it stopped before it started: ${ticketLogPath(id)} says why`
|
|
235
|
+
: ticket.status === "starting"
|
|
236
|
+
? `it did not start within ${within}`
|
|
237
|
+
: `it gave the person nothing to do within ${within}`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Where the step `id` stands. `address`: what the person's browser ended on (or the code its page showed), for a step
|
|
242
|
+
* waiting on one; it is handed over, and the answer is the ticket once the step has taken it.
|
|
243
|
+
*/
|
|
244
|
+
export async function checkTicket(id, address) {
|
|
245
|
+
let ticket = readTicket(id);
|
|
246
|
+
if (ticket === undefined)
|
|
247
|
+
return undefined;
|
|
248
|
+
if (address !== undefined && ticket.status === "need_user_action") {
|
|
249
|
+
writeFileAtomic(addressPath(id), address);
|
|
250
|
+
for (const deadline = Date.now() + ADDRESS_TIMEOUT_MS; Date.now() < deadline;) {
|
|
251
|
+
ticket = readTicket(id);
|
|
252
|
+
if (ticket.status !== "need_user_action" || !running(ticket.pid))
|
|
253
|
+
break;
|
|
254
|
+
await sleep(POLL_MS);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (waiting(ticket) && !running(ticket.pid)) {
|
|
258
|
+
return Date.now() > ticket.expiresAt
|
|
259
|
+
? update(id, { status: "expired" })
|
|
260
|
+
: update(id, { status: "failed", message: `it stopped: ${ticketLogPath(id)} says why` });
|
|
261
|
+
}
|
|
262
|
+
return ticket;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Stop the step of ticket `id` where it is, and everything it started, on a person's or an agent's say: the way out of
|
|
266
|
+
* a step that hangs while finishing, which no deadline cuts. What it was doing may be left half-done.
|
|
267
|
+
*/
|
|
268
|
+
export async function stopTicket(id) {
|
|
269
|
+
const ticket = readTicket(id);
|
|
270
|
+
if (ticket === undefined)
|
|
271
|
+
throw new Error(`ticket ${id} is gone from ${ticketsDir()}`);
|
|
272
|
+
if (!waiting(ticket))
|
|
273
|
+
return ticket;
|
|
274
|
+
if (ticket.pid !== undefined)
|
|
275
|
+
await stop(ticket.pid);
|
|
276
|
+
return update(id, {
|
|
277
|
+
status: "failed",
|
|
278
|
+
message: `stopped on request; what it was doing may be left half-done (${ticketLogPath(id)} says where it was)`,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Why the command whose `--check` is `check` cannot read ticket `id`: there is none on this machine, or it is another
|
|
283
|
+
* command's, named so the agent runs that one. Undefined when it is this command's.
|
|
284
|
+
*/
|
|
285
|
+
export function refusedCheck(id, check) {
|
|
286
|
+
const ticket = readTicket(id);
|
|
287
|
+
if (ticket === undefined)
|
|
288
|
+
return `no ticket "${id}" on this machine: a ticket is what the command that started it printed`;
|
|
289
|
+
if (ticket.check === check)
|
|
290
|
+
return undefined;
|
|
291
|
+
return `ticket ${id} is not this command's: it is to ${ticket.action}; check it with \`${ticket.check} ${id}\``;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* The step of ticket `id`, in the process `startTicket` started: `run` does it, through what it is handed, and
|
|
295
|
+
* resolves with what to report once done. Whatever it throws fails the ticket, the whole error going to the log. At
|
|
296
|
+
* the deadline the ticket expires, whether or not `run` heeds the signal (the caller ends the process after this, and
|
|
297
|
+
* a run still going ends with it), unless the step is finishing what its person did: that runs to its end, and
|
|
298
|
+
* expires only if it waits on its person again.
|
|
299
|
+
*/
|
|
300
|
+
export async function runTicket(id, run) {
|
|
301
|
+
const ticket = readTicket(id);
|
|
302
|
+
if (ticket === undefined)
|
|
303
|
+
throw new Error(`no ticket ${id} in ${ticketsDir()}`);
|
|
304
|
+
const claimed = update(id, { pid: process.pid, status: "working" });
|
|
305
|
+
if (claimed.status !== "working" || claimed.pid !== process.pid) {
|
|
306
|
+
// Given up on before this process got to it (a start slower than the start window): not ours to run.
|
|
307
|
+
console.error(`[fastagent] ticket ${id} ended (${claimed.status}) before this process took it; not running it`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
recordFailures((message) => update(id, { status: "failed", message }));
|
|
311
|
+
// However this process ends (done, failed, expired with the run still waiting), what it started goes with it.
|
|
312
|
+
if (leadsGroup())
|
|
313
|
+
process.once("exit", () => signalGroup(process.pid, "SIGTERM"));
|
|
314
|
+
// The deadline bounds what the step waits on (its person, getting ready), never what it is finishing (a host's
|
|
315
|
+
// deploy, a code exchanged): past it, a step finishing is let finish, and expires the moment it would wait again.
|
|
316
|
+
const expiry = new AbortController();
|
|
317
|
+
let finishing = false;
|
|
318
|
+
const expireIfDue = () => {
|
|
319
|
+
if (!finishing && !expiry.signal.aborted && Date.now() >= ticket.expiresAt)
|
|
320
|
+
expiry.abort(new Error("expired"));
|
|
321
|
+
};
|
|
322
|
+
const clock = setInterval(expireIfDue, POLL_MS);
|
|
323
|
+
const signal = expiry.signal;
|
|
324
|
+
// Each step replaces the last one's fields: a code or an expiry of the one before is not this one's.
|
|
325
|
+
const waits = (on) => {
|
|
326
|
+
finishing = false;
|
|
327
|
+
update(id, {
|
|
328
|
+
status: "need_user_action",
|
|
329
|
+
code: undefined,
|
|
330
|
+
instructions: undefined,
|
|
331
|
+
takesAddress: undefined,
|
|
332
|
+
until: undefined,
|
|
333
|
+
finishing: undefined,
|
|
334
|
+
...on,
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
const afterwards = (instructions, url) => {
|
|
338
|
+
const current = readTicket(id);
|
|
339
|
+
update(id, {
|
|
340
|
+
afterwards: [...(current?.afterwards ?? []), { instructions, ...(url === undefined ? {} : { url }) }],
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
const step = {
|
|
344
|
+
signal,
|
|
345
|
+
person: {
|
|
346
|
+
waitsOn: (url, instructions, until) => waits({ url, instructions, ...(until === undefined ? {} : { until }) }),
|
|
347
|
+
carriesOn: () => {
|
|
348
|
+
finishing = true;
|
|
349
|
+
update(id, { status: "working", finishing: true });
|
|
350
|
+
},
|
|
351
|
+
afterwards,
|
|
352
|
+
},
|
|
353
|
+
waits,
|
|
354
|
+
address: (abort) => awaitAddress(id, abort),
|
|
355
|
+
};
|
|
356
|
+
const deadline = new Promise((_, reject) => {
|
|
357
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
358
|
+
});
|
|
359
|
+
try {
|
|
360
|
+
const result = await Promise.race([run(step), deadline]);
|
|
361
|
+
update(id, { status: "done", ...(result === undefined ? {} : { result }) });
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
// The log is where a failed ticket points: the whole error goes there, its message to the ticket.
|
|
365
|
+
console.error(error);
|
|
366
|
+
if (signal.aborted)
|
|
367
|
+
update(id, { status: "expired" });
|
|
368
|
+
else
|
|
369
|
+
update(id, { status: "failed", message: error.message });
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
clearInterval(clock);
|
|
373
|
+
recordFailures(undefined);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** The address `--check --code` hands over, once it does. */
|
|
377
|
+
async function awaitAddress(id, signal) {
|
|
378
|
+
while (!signal.aborted) {
|
|
379
|
+
if (existsSync(addressPath(id))) {
|
|
380
|
+
const address = readFileSync(addressPath(id), "utf8");
|
|
381
|
+
rmSync(addressPath(id), { force: true });
|
|
382
|
+
return address;
|
|
383
|
+
}
|
|
384
|
+
await sleep(POLL_MS);
|
|
385
|
+
}
|
|
386
|
+
throw new Error("the step no longer waits for an address");
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* A ticket as its command reports it, with what to do next (`agent_next_steps`): one JSON object, or its lines for a
|
|
390
|
+
* person. `done` is what the step's `result` adds to the report, beside a sentence for the agent.
|
|
391
|
+
*/
|
|
392
|
+
export function ticketReport(ticket, when, done = (result) => ({ fields: result, text: [] })) {
|
|
393
|
+
const base = { ...ticket.fields, status: ticket.status, ticket: ticket.ticket };
|
|
394
|
+
const check = `${ticket.check} ${ticket.ticket}`;
|
|
395
|
+
const left = (ticket.afterwards ?? []).map((step) => step.url === undefined ? step.instructions : `${step.instructions} ${step.url}`);
|
|
396
|
+
if (ticket.status === "done") {
|
|
397
|
+
const report = done(ticket.result ?? {});
|
|
398
|
+
const tell = left.length === 0 ? "" : ` Tell the user what is left for them: ${left.join(" ")}`;
|
|
399
|
+
const next = `${report.next ?? "Done."}${tell}`;
|
|
400
|
+
return {
|
|
401
|
+
json: {
|
|
402
|
+
...base,
|
|
403
|
+
...report.fields,
|
|
404
|
+
...(ticket.afterwards ? { afterwards: ticket.afterwards } : {}),
|
|
405
|
+
agent_next_steps: next,
|
|
406
|
+
},
|
|
407
|
+
text: [`done: ${ticket.action}`, ...report.text, ...left.map((line) => `afterwards: ${line}`)],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const log = ticketLogPath(ticket.ticket);
|
|
411
|
+
if (ticket.status === "failed" || ticket.status === "expired") {
|
|
412
|
+
const message = ticket.status === "expired" ? "it was not finished in time" : ticket.message;
|
|
413
|
+
// What the step reached before it ended is still the person's: where to get the value it stopped for, say.
|
|
414
|
+
const reached = left.length === 0 ? "" : `Tell the user what the steps it reached leave them: ${left.join(" ")} `;
|
|
415
|
+
return {
|
|
416
|
+
json: {
|
|
417
|
+
...base,
|
|
418
|
+
message,
|
|
419
|
+
log,
|
|
420
|
+
...(ticket.afterwards ? { afterwards: ticket.afterwards } : {}),
|
|
421
|
+
agent_next_steps: `${reached}To try again, run: ${ticket.restart} --json`,
|
|
422
|
+
},
|
|
423
|
+
text: [
|
|
424
|
+
`${ticket.status}: ${message}`,
|
|
425
|
+
`log: ${log}`,
|
|
426
|
+
...left.map((line) => `left for the person: ${line}`),
|
|
427
|
+
`to try again: ${ticket.restart}`,
|
|
428
|
+
],
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
if (ticket.status === "starting" || ticket.status === "working") {
|
|
432
|
+
return {
|
|
433
|
+
json: {
|
|
434
|
+
...base,
|
|
435
|
+
check_command: `${check} --json`,
|
|
436
|
+
log,
|
|
437
|
+
agent_next_steps: "fastagent is working on it, with nothing for the user to do now. Run check_command again in a minute.",
|
|
438
|
+
},
|
|
439
|
+
text: [`${ticket.status}: nothing for the person to do now`, `log: ${log}`, `then run: ${check}`],
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
const resume = `${check} --code '<address>'`;
|
|
443
|
+
const give = ticket.code === undefined
|
|
444
|
+
? `Give the URL to the user so they can ${ticket.action}.`
|
|
445
|
+
: `Give the URL and the code ${ticket.code} to the user so they can ${ticket.action} there.`;
|
|
446
|
+
const then = ticket.takesAddress
|
|
447
|
+
? "Once they say they have, run check_command. If their browser ends on a page that does not load (an address " +
|
|
448
|
+
"on localhost), or on one showing a one-time code to hand back, ask them for that address or code and run " +
|
|
449
|
+
"resume_command with it in place of <address>."
|
|
450
|
+
: "Once they say they have, run check_command.";
|
|
451
|
+
return {
|
|
452
|
+
json: {
|
|
453
|
+
...base,
|
|
454
|
+
...(ticket.url === undefined ? {} : { url: ticket.url }),
|
|
455
|
+
...(ticket.code === undefined ? {} : { code: ticket.code }),
|
|
456
|
+
...(ticket.instructions === undefined ? {} : { instructions: ticket.instructions }),
|
|
457
|
+
// The person's step can lapse before the ticket does (a confirmation link of the platform's own).
|
|
458
|
+
expires_in: Math.max(0, Math.round((Math.min(ticket.expiresAt, ticket.until ?? Infinity) - Date.now()) / 1000)),
|
|
459
|
+
check_command: `${check} --json`,
|
|
460
|
+
...(ticket.takesAddress ? { resume_command: `${resume} --json` } : {}),
|
|
461
|
+
agent_next_steps: when === "started" ? `${give} ${then}` : `The user has not finished yet. ${give} ${then}`,
|
|
462
|
+
},
|
|
463
|
+
text: [
|
|
464
|
+
`need_user_action: ${ticket.action} at`,
|
|
465
|
+
` ${ticket.url ?? "(no address yet)"}${ticket.code === undefined ? "" : ` (code: ${ticket.code})`}`,
|
|
466
|
+
`then run: ${check}`,
|
|
467
|
+
...(ticket.takesAddress ? [`if the browser ends on a page that does not load, run: ${resume}`] : []),
|
|
468
|
+
],
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/** Print one outcome on stdout, the result it is: the JSON object with `--json`, else its lines for a person. */
|
|
472
|
+
export function printReport(asJson, json, text) {
|
|
473
|
+
console.log(asJson ? JSON.stringify(json, null, 2) : text.join("\n"));
|
|
474
|
+
}
|
|
475
|
+
/** The exit code a ticket's command ends with: 1 once it failed or expired, 0 while it runs or once done. */
|
|
476
|
+
export const ticketExitCode = (ticket) => (ticket.status === "failed" || ticket.status === "expired" ? 1 : 0);
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Agent API over HTTP, for a program anywhere `fetch` runs (SPEC.md §8): `connectAgent`, its errors and the
|
|
3
|
+
* types. It opens no agent and needs no Node API, so it also runs on Deno and Workers.
|
|
4
|
+
*/
|
|
5
|
+
export type { Agent, AgentErrorData, AgentInfo, CompactOptions, ContentPart, CreateSessionOptions, EntriesOptions, Entry, ErrorCode, Json, ListOptions, Message, MessageKey, MessageStatus, MessageStreamEvent, ModelRef, Page, SendRequest, Session, SessionChanges, SessionEvent, SessionSettings, SessionStatus, ThinkingLevel, Tokens, Usage, WaitOptions, } from "./agent-api.ts";
|
|
6
|
+
export { AgentError, LIMITS, hasEnded } from "./agent-api.ts";
|
|
7
|
+
export { connectAgent, type ConnectOptions } from "./http/client.ts";
|
package/dist/client.js
ADDED