@fastagent-sh/fastagent 0.24.3 → 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 -517
- 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 -83
- package/dist/engines/pi/turn-kit.js +0 -275
- 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,70 @@
|
|
|
1
|
+
/** One content entry as `context.json` holds it, under its name: a repository. */
|
|
2
|
+
export interface ContentEntry {
|
|
3
|
+
/** The repository, `owner/repo`. */
|
|
4
|
+
github: string;
|
|
5
|
+
/** The branch, tag or commit to clone. */
|
|
6
|
+
ref?: string;
|
|
7
|
+
/** The agent knows it and does not write it. */
|
|
8
|
+
readonly?: boolean;
|
|
9
|
+
/** One sentence for the agent: what this is, and how to treat it. */
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
/** A declared entry, read. */
|
|
13
|
+
export interface DeclaredContent {
|
|
14
|
+
name: string;
|
|
15
|
+
/** `owner/repo`. */
|
|
16
|
+
repo: string;
|
|
17
|
+
ref?: string;
|
|
18
|
+
readonly: boolean;
|
|
19
|
+
description?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One binding as `context.local.json` holds it, under its name: a directory of this machine. Under the name of an
|
|
23
|
+
* entry `context.json` declares it is this machine's checkout of that repository, and carries only `path`.
|
|
24
|
+
*/
|
|
25
|
+
export interface ContentBinding {
|
|
26
|
+
/** Absolute, `~/`, or relative to the agent directory. */
|
|
27
|
+
path: string;
|
|
28
|
+
readonly?: boolean;
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A binding, read: `path` absolute. */
|
|
32
|
+
export type BoundContent = {
|
|
33
|
+
name: string;
|
|
34
|
+
path: string;
|
|
35
|
+
} & ({
|
|
36
|
+
/** This machine's checkout of the declared entry of the same name. */
|
|
37
|
+
kind: "checkout";
|
|
38
|
+
} | {
|
|
39
|
+
/** A directory only this machine has. */
|
|
40
|
+
kind: "local";
|
|
41
|
+
readonly: boolean;
|
|
42
|
+
description?: string;
|
|
43
|
+
});
|
|
44
|
+
/** What an agent's content is declared as: the shared declaration, and this machine's bindings. */
|
|
45
|
+
export interface ContentDeclaration {
|
|
46
|
+
declared: DeclaredContent[];
|
|
47
|
+
bound: BoundContent[];
|
|
48
|
+
}
|
|
49
|
+
/** Whether `name` can name a content entry. */
|
|
50
|
+
export declare function isContentName(name: string): boolean;
|
|
51
|
+
/** Whether `repo` names a GitHub repository, `owner/repo`. */
|
|
52
|
+
export declare function isGithubRepo(repo: string): boolean;
|
|
53
|
+
/** `entry` with its keys in {@link ENTRY_KEYS} order: the form written. */
|
|
54
|
+
export declare function canonicalEntry(entry: ContentEntry): ContentEntry;
|
|
55
|
+
/** `binding` with its keys in {@link BINDING_KEYS} order: the form written. */
|
|
56
|
+
export declare function canonicalBinding(binding: ContentBinding): ContentBinding;
|
|
57
|
+
/** Read `context.json`'s `content` map (undefined is none). Every refusal names the entry; nothing is defaulted. */
|
|
58
|
+
export declare function declareContent(raw: unknown): DeclaredContent[];
|
|
59
|
+
/**
|
|
60
|
+
* Read `context.local.json`'s `content` map (undefined is none) beside `declared`, with relative paths resolved against
|
|
61
|
+
* `agentDir`. A binding under a declared entry's name is that entry's checkout here; any other is a directory of this
|
|
62
|
+
* machine alone.
|
|
63
|
+
*/
|
|
64
|
+
export declare function declareBindings(raw: unknown, declared: readonly DeclaredContent[], agentDir: string): BoundContent[];
|
|
65
|
+
/**
|
|
66
|
+
* A directory the agent is bound to and the agent directory are kept apart (agent-model.md §2): a definition is
|
|
67
|
+
* released, writable content is kept, and one directory cannot be both. The ONE statement of the rule; resolve.ts asks
|
|
68
|
+
* it of the real paths.
|
|
69
|
+
*/
|
|
70
|
+
export declare function nestingError(agentDir: string, location: string, name: string): string | undefined;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHAT AN AUTHOR DECLARES an agent works on or knows, read and refused in ONE place (file.ts reads the files through
|
|
3
|
+
* it): the `content` map of `context.json`, which is shared and so holds only repositories every instance reaches, and
|
|
4
|
+
* the `content` map of `context.local.json`, the directories of this machine it is bound to. Pure: what is on the disk
|
|
5
|
+
* is resolve.ts's question. docs/design/agent-model.md §3 is the rule.
|
|
6
|
+
*/
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
9
|
+
import { CONTEXT_FILE, LOCAL_CONTEXT_FILE, isUnderDir } from "../paths.js";
|
|
10
|
+
/**
|
|
11
|
+
* The keys an entry and a binding carry, in the order they are written: where it is, then how it is treated. One list
|
|
12
|
+
* each, so a key the reader accepts is one the writer keeps.
|
|
13
|
+
*/
|
|
14
|
+
const ENTRY_KEYS = ["github", "ref", "readonly", "description"];
|
|
15
|
+
const BINDING_KEYS = ["path", "readonly", "description"];
|
|
16
|
+
/** A name becomes a directory name, so it is one path segment (the spelling a release's agent name has). */
|
|
17
|
+
const NAME = /^[A-Za-z0-9_-]+$/;
|
|
18
|
+
/** Whether `name` can name a content entry. */
|
|
19
|
+
export function isContentName(name) {
|
|
20
|
+
return NAME.test(name);
|
|
21
|
+
}
|
|
22
|
+
const GITHUB_REPO = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
23
|
+
/** Whether `repo` names a GitHub repository, `owner/repo`. */
|
|
24
|
+
export function isGithubRepo(repo) {
|
|
25
|
+
return GITHUB_REPO.test(repo);
|
|
26
|
+
}
|
|
27
|
+
/** `entry` with its keys in {@link ENTRY_KEYS} order: the form written. */
|
|
28
|
+
export function canonicalEntry(entry) {
|
|
29
|
+
return pick(entry, ENTRY_KEYS);
|
|
30
|
+
}
|
|
31
|
+
/** `binding` with its keys in {@link BINDING_KEYS} order: the form written. */
|
|
32
|
+
export function canonicalBinding(binding) {
|
|
33
|
+
return pick(binding, BINDING_KEYS);
|
|
34
|
+
}
|
|
35
|
+
function pick(value, keys) {
|
|
36
|
+
const out = {};
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
const field = value[key];
|
|
39
|
+
if (field !== undefined)
|
|
40
|
+
out[key] = field;
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/** Read `context.json`'s `content` map (undefined is none). Every refusal names the entry; nothing is defaulted. */
|
|
45
|
+
export function declareContent(raw) {
|
|
46
|
+
return entriesByName(raw).map(([name, entry]) => declareOne(name, entry));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read `context.local.json`'s `content` map (undefined is none) beside `declared`, with relative paths resolved against
|
|
50
|
+
* `agentDir`. A binding under a declared entry's name is that entry's checkout here; any other is a directory of this
|
|
51
|
+
* machine alone.
|
|
52
|
+
*/
|
|
53
|
+
export function declareBindings(raw, declared, agentDir) {
|
|
54
|
+
return entriesByName(raw).map(([name, binding]) => {
|
|
55
|
+
const at = `content "${name}"`;
|
|
56
|
+
const e = objectWithKeys(at, binding, BINDING_KEYS);
|
|
57
|
+
if (typeof e.path !== "string" || e.path === "")
|
|
58
|
+
throw new Error(`${at}: "path" must be a non-empty string`);
|
|
59
|
+
const path = absolutePath(e.path, agentDir);
|
|
60
|
+
const shared = declared.find((entry) => entry.name.toLowerCase() === name.toLowerCase());
|
|
61
|
+
if (shared !== undefined) {
|
|
62
|
+
if (shared.name !== name) {
|
|
63
|
+
throw new Error(`${at} binds the entry ${CONTEXT_FILE} names "${shared.name}": spell it the same`);
|
|
64
|
+
}
|
|
65
|
+
for (const key of ["readonly", "description"]) {
|
|
66
|
+
if (e[key] !== undefined) {
|
|
67
|
+
throw new Error(`${at} binds this machine's checkout of the entry ${CONTEXT_FILE} declares, which says "${key}" for ` +
|
|
68
|
+
`every instance: give it only "path"`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { name, path, kind: "checkout" };
|
|
72
|
+
}
|
|
73
|
+
const treatment = treatmentOf(at, e);
|
|
74
|
+
return { name, path, kind: "local", ...treatment };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** `path` as written in `context.local.json`, absolute: `~/` is the home directory, a relative one the agent's. */
|
|
78
|
+
function absolutePath(path, agentDir) {
|
|
79
|
+
if (path === "~" || path.startsWith("~/"))
|
|
80
|
+
return join(homedir(), path.slice(1));
|
|
81
|
+
return isAbsolute(path) ? resolve(path) : resolve(agentDir, path);
|
|
82
|
+
}
|
|
83
|
+
/** The entries of a `content` map, each name checked, unique ignoring case. */
|
|
84
|
+
function entriesByName(raw) {
|
|
85
|
+
if (raw === undefined)
|
|
86
|
+
return [];
|
|
87
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
88
|
+
throw new Error(`"content" must be an object of entries by name`);
|
|
89
|
+
}
|
|
90
|
+
const seen = new Map();
|
|
91
|
+
return Object.entries(raw).map(([name, entry]) => {
|
|
92
|
+
if (!NAME.test(name)) {
|
|
93
|
+
throw new Error(`content "${name}": a name is one path segment of letters, digits, "-" and "_"`);
|
|
94
|
+
}
|
|
95
|
+
const key = name.toLowerCase();
|
|
96
|
+
const taken = seen.get(key);
|
|
97
|
+
// Equal ignoring case is the same name: it becomes a directory, and some filesystems do not tell them apart.
|
|
98
|
+
if (taken !== undefined) {
|
|
99
|
+
throw new Error(`two content entries are named "${taken}" and "${name}" — names must differ ignoring case`);
|
|
100
|
+
}
|
|
101
|
+
seen.set(key, name);
|
|
102
|
+
return [name, entry];
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function objectWithKeys(at, value, keys) {
|
|
106
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
107
|
+
throw new Error(`${at} must be an object`);
|
|
108
|
+
const e = value;
|
|
109
|
+
for (const key of Object.keys(e)) {
|
|
110
|
+
if (!keys.includes(key))
|
|
111
|
+
throw new Error(`${at}: unknown key "${key}" (valid keys: ${keys.join(", ")})`);
|
|
112
|
+
}
|
|
113
|
+
return e;
|
|
114
|
+
}
|
|
115
|
+
function treatmentOf(at, e) {
|
|
116
|
+
if (e.description !== undefined && (typeof e.description !== "string" || e.description === "")) {
|
|
117
|
+
throw new Error(`${at}: "description" must be a non-empty string`);
|
|
118
|
+
}
|
|
119
|
+
if (e.readonly !== undefined && typeof e.readonly !== "boolean")
|
|
120
|
+
throw new Error(`${at}: "readonly" must be a boolean`);
|
|
121
|
+
const description = e.description;
|
|
122
|
+
return { readonly: e.readonly === true, ...(description !== undefined ? { description } : {}) };
|
|
123
|
+
}
|
|
124
|
+
function declareOne(name, entry) {
|
|
125
|
+
const at = `content "${name}"`;
|
|
126
|
+
const e = objectWithKeys(at, entry, ENTRY_KEYS);
|
|
127
|
+
for (const key of ["github", "ref"]) {
|
|
128
|
+
if (e[key] !== undefined && (typeof e[key] !== "string" || e[key] === "")) {
|
|
129
|
+
throw new Error(`${at}: "${key}" must be a non-empty string`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const treatment = treatmentOf(at, e);
|
|
133
|
+
if (e.github === undefined) {
|
|
134
|
+
throw new Error(`${at} needs "github": ${CONTEXT_FILE} is shared, so it declares only what every instance reaches — bind a ` +
|
|
135
|
+
`directory of this machine in ${LOCAL_CONTEXT_FILE} instead (fastagent content add <dir>)`);
|
|
136
|
+
}
|
|
137
|
+
const repo = e.github;
|
|
138
|
+
if (!isGithubRepo(repo))
|
|
139
|
+
throw new Error(`${at}: "github" must be "owner/repo", got "${repo}"`);
|
|
140
|
+
const ref = e.ref;
|
|
141
|
+
// git reads a leading "-" as an option, and no branch, tag or commit name has one.
|
|
142
|
+
if (ref?.startsWith("-"))
|
|
143
|
+
throw new Error(`${at}: "ref" must name a branch, tag or commit, got "${ref}"`);
|
|
144
|
+
return { name, repo, ...(ref !== undefined ? { ref } : {}), ...treatment };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* A directory the agent is bound to and the agent directory are kept apart (agent-model.md §2): a definition is
|
|
148
|
+
* released, writable content is kept, and one directory cannot be both. The ONE statement of the rule; resolve.ts asks
|
|
149
|
+
* it of the real paths.
|
|
150
|
+
*/
|
|
151
|
+
export function nestingError(agentDir, location, name) {
|
|
152
|
+
if (!isAbsolute(location))
|
|
153
|
+
throw new Error(`nestingError: "${location}" is not absolute`);
|
|
154
|
+
if (isUnderDir(agentDir, location)) {
|
|
155
|
+
return (`content "${name}" (${location}) contains the agent directory ${agentDir} — an agent lives in a directory of ` +
|
|
156
|
+
`its own: move it out of ${location}, or bind other content`);
|
|
157
|
+
}
|
|
158
|
+
if (isUnderDir(location, agentDir)) {
|
|
159
|
+
return (`content "${name}" (${location}) is inside the agent directory ${agentDir} — content is a directory of its ` +
|
|
160
|
+
`own: move it out, or bind other content`);
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type DeclaredEnvironment } from "../environment/declare.ts";
|
|
2
|
+
import { type BoundContent, type ContentBinding, type ContentDeclaration, type ContentEntry, type DeclaredContent } from "./declare.ts";
|
|
3
|
+
/** `context.json` as read: the content entries as written and as declared, and the environment. */
|
|
4
|
+
export interface ContextFile {
|
|
5
|
+
$schema?: string;
|
|
6
|
+
content: Record<string, ContentEntry>;
|
|
7
|
+
declared: DeclaredContent[];
|
|
8
|
+
environment?: DeclaredEnvironment;
|
|
9
|
+
}
|
|
10
|
+
/** `context.local.json` as read: the bindings as written and as declared. */
|
|
11
|
+
export interface LocalContextFile {
|
|
12
|
+
content: Record<string, ContentBinding>;
|
|
13
|
+
bound: BoundContent[];
|
|
14
|
+
}
|
|
15
|
+
/** Read `text`, the contents of `path`, whole: what an edit, which writes the whole, reads. */
|
|
16
|
+
export declare function parseContextFile(text: string, path: string): ContextFile;
|
|
17
|
+
/** Read `text`, the contents of `path`, whole, beside the entries `context.json` declares. */
|
|
18
|
+
export declare function parseLocalContextFile(text: string, path: string, declared: readonly DeclaredContent[], agentDir: string): LocalContextFile;
|
|
19
|
+
/** The agent's `context.json`, read whole. */
|
|
20
|
+
export declare function readContextFile(agentDir: string): ContextFile;
|
|
21
|
+
/** The content of the agent in `agentDir`: what `context.json` declares, and what this machine binds. */
|
|
22
|
+
export declare function loadContent(agentDir: string): ContentDeclaration;
|
|
23
|
+
/** The environment the agent in `agentDir` declares, or undefined when it declares none. */
|
|
24
|
+
export declare function loadEnvironment(agentDir: string): DeclaredEnvironment | undefined;
|
|
25
|
+
/** The text `context.json` is written as. An environment that declares nothing is left out. */
|
|
26
|
+
export declare function contextFileText(file: {
|
|
27
|
+
$schema?: string;
|
|
28
|
+
content: Record<string, ContentEntry>;
|
|
29
|
+
environment?: DeclaredEnvironment;
|
|
30
|
+
}): string;
|
|
31
|
+
/** The text `context.local.json` is written as. */
|
|
32
|
+
export declare function localContextFileText(content: Record<string, ContentBinding>): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent's `context.json` and this machine's `context.local.json`, read in ONE place, so the opener, `info`,
|
|
3
|
+
* `content list`, `env`, `tool` and deploy read one declaration; and the one shape each is written in. A reader
|
|
4
|
+
* declares the part it uses (its content, or its environment), so a part that is refused stops what uses it and is
|
|
5
|
+
* reported where it is, not as the other's; an edit declares the whole, since it writes the whole. JSON, like
|
|
6
|
+
* `mcp.json` and `package.json`, so a command, a client or the agent itself can edit it without touching TypeScript.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join, resolve } from "node:path";
|
|
10
|
+
import { CONTEXT_FILE, LOCAL_CONTEXT_FILE } from "../paths.js";
|
|
11
|
+
import { declareEnvironment } from "../environment/declare.js";
|
|
12
|
+
import { declareBindings, declareContent, } from "./declare.js";
|
|
13
|
+
const FILE_KEYS = ["$schema", "content", "environment"];
|
|
14
|
+
const LOCAL_FILE_KEYS = ["content"];
|
|
15
|
+
/** `text`, the contents of `path`, as the object a context file is: refused when the file itself is wrong. */
|
|
16
|
+
function parseFile(text, path, keys = FILE_KEYS) {
|
|
17
|
+
let raw;
|
|
18
|
+
try {
|
|
19
|
+
raw = JSON.parse(text);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error(`${path} is not valid JSON: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
25
|
+
throw new Error(`${path} must hold a JSON object`);
|
|
26
|
+
const file = raw;
|
|
27
|
+
for (const key of Object.keys(file)) {
|
|
28
|
+
if (!keys.includes(key))
|
|
29
|
+
throw new Error(`${path}: unknown key "${key}" (valid keys: ${keys.join(", ")})`);
|
|
30
|
+
}
|
|
31
|
+
if (file.$schema !== undefined && typeof file.$schema !== "string") {
|
|
32
|
+
throw new Error(`${path}: "$schema" must be a string`);
|
|
33
|
+
}
|
|
34
|
+
return file;
|
|
35
|
+
}
|
|
36
|
+
/** One part of `context.json` at `path`, declared by `declare`; its refusal names the file. */
|
|
37
|
+
function declarePart(path, declare) {
|
|
38
|
+
try {
|
|
39
|
+
return declare();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(`${path}: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Read `text`, the contents of `path`, whole: what an edit, which writes the whole, reads. */
|
|
46
|
+
export function parseContextFile(text, path) {
|
|
47
|
+
const file = parseFile(text, path);
|
|
48
|
+
const declared = declarePart(path, () => declareContent(file.content));
|
|
49
|
+
const environment = declarePart(path, () => declareEnvironment(file.environment));
|
|
50
|
+
return {
|
|
51
|
+
...(file.$schema !== undefined ? { $schema: file.$schema } : {}),
|
|
52
|
+
content: (file.content ?? {}),
|
|
53
|
+
declared,
|
|
54
|
+
...(environment !== undefined ? { environment } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Read `text`, the contents of `path`, whole, beside the entries `context.json` declares. */
|
|
58
|
+
export function parseLocalContextFile(text, path, declared, agentDir) {
|
|
59
|
+
const file = parseFile(text, path, LOCAL_FILE_KEYS);
|
|
60
|
+
const bound = declarePart(path, () => declareBindings(file.content, declared, resolve(agentDir)));
|
|
61
|
+
return { content: (file.content ?? {}), bound };
|
|
62
|
+
}
|
|
63
|
+
/** The agent's context file `name` and its path, or undefined text when it has none (it declares nothing). */
|
|
64
|
+
function contextFileAt(agentDir, name = CONTEXT_FILE) {
|
|
65
|
+
const path = join(resolve(agentDir), name);
|
|
66
|
+
try {
|
|
67
|
+
return { path, text: readFileSync(path, "utf8") };
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error.code === "ENOENT")
|
|
71
|
+
return { path };
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** The agent's `context.json`, read whole. */
|
|
76
|
+
export function readContextFile(agentDir) {
|
|
77
|
+
const { path, text } = contextFileAt(agentDir);
|
|
78
|
+
return text === undefined ? { content: {}, declared: [] } : parseContextFile(text, path);
|
|
79
|
+
}
|
|
80
|
+
/** This machine's `context.local.json` for the agent in `agentDir`, read whole, beside what it `declared`. */
|
|
81
|
+
function readLocalContextFile(agentDir, declared) {
|
|
82
|
+
const { path, text } = contextFileAt(agentDir, LOCAL_CONTEXT_FILE);
|
|
83
|
+
return text === undefined ? { content: {}, bound: [] } : parseLocalContextFile(text, path, declared, agentDir);
|
|
84
|
+
}
|
|
85
|
+
/** The content of the agent in `agentDir`: what `context.json` declares, and what this machine binds. */
|
|
86
|
+
export function loadContent(agentDir) {
|
|
87
|
+
const { path, text } = contextFileAt(agentDir);
|
|
88
|
+
const declared = text === undefined ? [] : declarePart(path, () => declareContent(parseFile(text, path).content));
|
|
89
|
+
return { declared, bound: readLocalContextFile(agentDir, declared).bound };
|
|
90
|
+
}
|
|
91
|
+
/** The environment the agent in `agentDir` declares, or undefined when it declares none. */
|
|
92
|
+
export function loadEnvironment(agentDir) {
|
|
93
|
+
const { path, text } = contextFileAt(agentDir);
|
|
94
|
+
if (text === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const file = parseFile(text, path);
|
|
97
|
+
return declarePart(path, () => declareEnvironment(file.environment));
|
|
98
|
+
}
|
|
99
|
+
/** The text `context.json` is written as. An environment that declares nothing is left out. */
|
|
100
|
+
export function contextFileText(file) {
|
|
101
|
+
const packages = file.environment?.packages ?? {};
|
|
102
|
+
const aptPackages = file.environment?.aptPackages ?? [];
|
|
103
|
+
const variables = file.environment?.variables ?? {};
|
|
104
|
+
const environment = {
|
|
105
|
+
...(Object.keys(packages).length > 0 ? { packages } : {}),
|
|
106
|
+
...(aptPackages.length > 0 ? { aptPackages } : {}),
|
|
107
|
+
...(Object.keys(variables).length > 0 ? { variables } : {}),
|
|
108
|
+
};
|
|
109
|
+
const out = {
|
|
110
|
+
...(file.$schema !== undefined ? { $schema: file.$schema } : {}),
|
|
111
|
+
content: file.content,
|
|
112
|
+
...(Object.keys(environment).length > 0 ? { environment } : {}),
|
|
113
|
+
};
|
|
114
|
+
return `${JSON.stringify(out, null, 2)}\n`;
|
|
115
|
+
}
|
|
116
|
+
/** The text `context.local.json` is written as. */
|
|
117
|
+
export function localContextFileText(content) {
|
|
118
|
+
return `${JSON.stringify({ content }, null, 2)}\n`;
|
|
119
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** The `owner/repo` a GitHub remote URL names, in any of its spellings (https, ssh, scp-like), or undefined. */
|
|
2
|
+
export declare function githubRepoOf(url: string): string | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* The checkout `dir` is in: its root, and its `origin` as configured (not as `insteadOf` rewrites it). Undefined when
|
|
5
|
+
* `dir` is not a directory inside a git checkout.
|
|
6
|
+
*/
|
|
7
|
+
export declare function checkoutOf(dir: string): {
|
|
8
|
+
root: string;
|
|
9
|
+
origin?: string;
|
|
10
|
+
} | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Why the directory `local` cannot stand for github `repo` on this machine, or undefined when it can: it is the root
|
|
13
|
+
* of a checkout whose `origin` is that repository.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkoutProblem(local: string, repo: string): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* What to say when the checkout `dir` is not at `ref`, or undefined when it is: on that branch, or at the commit `ref`
|
|
18
|
+
* names. FastAgent never moves a checkout the user owns, so this is said, not acted on.
|
|
19
|
+
*/
|
|
20
|
+
export declare function refNotice(dir: string, ref: string): string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* What a start did with a clone: made it, moved it to what the remote has now, found it there already, or kept it as
|
|
23
|
+
* it is, for the `reason` given (git refused to touch the agent's work, or the remote could not be reached).
|
|
24
|
+
*/
|
|
25
|
+
export type CloneOutcome = {
|
|
26
|
+
outcome: "cloned" | "updated" | "current";
|
|
27
|
+
} | {
|
|
28
|
+
outcome: "kept";
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Clone github `repo` at `ref` (its default branch when unset) into `dir`, or bring the clone there up to date IN
|
|
33
|
+
* PLACE, by git's own rules: a fetch, then a fast-forward of the branch it is on, or a checkout of the tag or commit
|
|
34
|
+
* it is pinned to. git refuses whatever would overwrite the agent's work (a changed file the update touches, an
|
|
35
|
+
* untracked file it would replace, commits the remote does not have), and the clone is then kept as it is, with
|
|
36
|
+
* git's reason; so it is when the fetch fails, or when the clone is on another branch than declared. Nothing is
|
|
37
|
+
* deleted: the agent's branches, stashes and the changes an update does not touch stay where they are.
|
|
38
|
+
* - None yet: cloned beside it and renamed into place. A first clone that fails stops the start.
|
|
39
|
+
* - A clone of another repository there (the entry was renamed or redeclared): refused, naming it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function refreshClone(repo: string, ref: string | undefined, dir: string): Promise<CloneOutcome>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The git GitHub content needs on this machine: which repository a checkout is of, whether it is at the declared
|
|
3
|
+
* `ref`, and a clone, made or brought up to date in place. git's own configuration applies throughout (credential helpers, `url.<base>.insteadOf`), so
|
|
4
|
+
* a private repository is reached the way the user's own `git clone` reaches it.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, realpathSync, renameSync, rmSync, statSync } from "node:fs";
|
|
7
|
+
import { basename, dirname, join } from "node:path";
|
|
8
|
+
import { gitFor } from "../git.js";
|
|
9
|
+
const { answer: gitAnswer, run: runGit } = gitFor("github content needs it on this machine");
|
|
10
|
+
/** The `owner/repo` a GitHub remote URL names, in any of its spellings (https, ssh, scp-like), or undefined. */
|
|
11
|
+
export function githubRepoOf(url) {
|
|
12
|
+
const match = /^(?:(?:https?|git|ssh):\/\/(?:[^@/]+@)?github\.com\/|(?:[^@/:]+@)?github\.com:)([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/i.exec(url.trim());
|
|
13
|
+
return match ? `${match[1]}/${match[2]}` : undefined;
|
|
14
|
+
}
|
|
15
|
+
/** The URL fastagent clones github `repo` from. */
|
|
16
|
+
function githubUrl(repo) {
|
|
17
|
+
return `https://github.com/${repo}.git`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The checkout `dir` is in: its root, and its `origin` as configured (not as `insteadOf` rewrites it). Undefined when
|
|
21
|
+
* `dir` is not a directory inside a git checkout.
|
|
22
|
+
*/
|
|
23
|
+
export function checkoutOf(dir) {
|
|
24
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory())
|
|
25
|
+
return undefined;
|
|
26
|
+
const root = gitAnswer(["rev-parse", "--show-toplevel"], dir);
|
|
27
|
+
if (root === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
const origin = gitAnswer(["config", "--get", "remote.origin.url"], root);
|
|
30
|
+
return { root, ...(origin ? { origin } : {}) };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Why the directory `local` cannot stand for github `repo` on this machine, or undefined when it can: it is the root
|
|
34
|
+
* of a checkout whose `origin` is that repository.
|
|
35
|
+
*/
|
|
36
|
+
export function checkoutProblem(local, repo) {
|
|
37
|
+
if (!existsSync(local))
|
|
38
|
+
return "does not exist";
|
|
39
|
+
const checkout = checkoutOf(local);
|
|
40
|
+
if (checkout === undefined)
|
|
41
|
+
return "is not a git checkout";
|
|
42
|
+
if (realpathSync(checkout.root) !== realpathSync(local))
|
|
43
|
+
return `is inside the checkout ${checkout.root}, not its root`;
|
|
44
|
+
const of = checkout.origin === undefined ? undefined : githubRepoOf(checkout.origin);
|
|
45
|
+
if (of?.toLowerCase() !== repo.toLowerCase()) {
|
|
46
|
+
return `is a checkout of ${checkout.origin ?? "no origin"}, not of github ${repo}`;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* What to say when the checkout `dir` is not at `ref`, or undefined when it is: on that branch, or at the commit `ref`
|
|
52
|
+
* names. FastAgent never moves a checkout the user owns, so this is said, not acted on.
|
|
53
|
+
*/
|
|
54
|
+
export function refNotice(dir, ref) {
|
|
55
|
+
const branch = gitAnswer(["symbolic-ref", "--quiet", "--short", "HEAD"], dir);
|
|
56
|
+
if (branch === ref)
|
|
57
|
+
return undefined;
|
|
58
|
+
const head = gitAnswer(["rev-parse", "HEAD"], dir);
|
|
59
|
+
const target = gitAnswer(["rev-parse", "--verify", "--quiet", "--end-of-options", `${ref}^{commit}`], dir);
|
|
60
|
+
if (head !== undefined && head === target) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const at = branch ?? `a detached ${head?.slice(0, 7) ?? "HEAD"}`;
|
|
64
|
+
return `the checkout is on ${at}, declared ${ref}; it is left as it is`;
|
|
65
|
+
}
|
|
66
|
+
/** A commit named in full: fetched by its hash, since `clone --branch` takes only a branch or a tag. */
|
|
67
|
+
const FULL_COMMIT = /^[0-9a-f]{40}$/i;
|
|
68
|
+
/**
|
|
69
|
+
* git aborts a transfer that moves under 1 KB/s for 15 seconds, so a network that drops packets silently does not hold
|
|
70
|
+
* a start until TCP gives up, while a large transfer that is moving is left alone. http(s) only. Not covered by a
|
|
71
|
+
* test: it takes a remote that stops answering.
|
|
72
|
+
*/
|
|
73
|
+
const SLOW_NETWORK = ["-c", "http.lowSpeedLimit=1000", "-c", "http.lowSpeedTime=15"];
|
|
74
|
+
/**
|
|
75
|
+
* The ref that holds the commit a clone pinned to a commit was last put on. Fetching a commit by its hash creates no
|
|
76
|
+
* ref, and without one every commit of a detached clone would look like the agent's own.
|
|
77
|
+
*/
|
|
78
|
+
const PINNED = "refs/remotes/origin/pinned";
|
|
79
|
+
/**
|
|
80
|
+
* The credential a clone of fastagent's reaches GitHub with when git has none of its own: `GITHUB_TOKEN`, read from
|
|
81
|
+
* the environment each time git asks, so the token is never written to disk. Set in the clone's own config, so the
|
|
82
|
+
* agent's `git push` there uses it too. git asks its own helpers first (a credential manager on a laptop), then this
|
|
83
|
+
* one, which answers nothing when the variable is unset.
|
|
84
|
+
*/
|
|
85
|
+
const TOKEN_HELPER = '!f() { test "$1" = get && test -n "$GITHUB_TOKEN" && printf "username=x-access-token\\npassword=%s\\n" "$GITHUB_TOKEN"; }; f';
|
|
86
|
+
const HELPER_KEY = "credential.https://github.com.helper";
|
|
87
|
+
/**
|
|
88
|
+
* Clone github `repo` at `ref` (its default branch when unset) into `dir`, or bring the clone there up to date IN
|
|
89
|
+
* PLACE, by git's own rules: a fetch, then a fast-forward of the branch it is on, or a checkout of the tag or commit
|
|
90
|
+
* it is pinned to. git refuses whatever would overwrite the agent's work (a changed file the update touches, an
|
|
91
|
+
* untracked file it would replace, commits the remote does not have), and the clone is then kept as it is, with
|
|
92
|
+
* git's reason; so it is when the fetch fails, or when the clone is on another branch than declared. Nothing is
|
|
93
|
+
* deleted: the agent's branches, stashes and the changes an update does not touch stay where they are.
|
|
94
|
+
* - None yet: cloned beside it and renamed into place. A first clone that fails stops the start.
|
|
95
|
+
* - A clone of another repository there (the entry was renamed or redeclared): refused, naming it.
|
|
96
|
+
*/
|
|
97
|
+
export async function refreshClone(repo, ref, dir) {
|
|
98
|
+
// A library caller can hand this anything; git reads a leading "-" as an option, and no repository or ref has one
|
|
99
|
+
// (a declaration with one is refused at load, declare.ts).
|
|
100
|
+
if (repo.startsWith("-") || ref?.startsWith("-")) {
|
|
101
|
+
throw new Error(`github ${repo}${ref !== undefined ? ` at ${ref}` : ""} would reach git as an option`);
|
|
102
|
+
}
|
|
103
|
+
if (!existsSync(dir))
|
|
104
|
+
return cloneInto(repo, ref, dir);
|
|
105
|
+
const origin = gitAnswer(["config", "--get", "remote.origin.url"], dir);
|
|
106
|
+
if (origin === undefined || githubRepoOf(origin)?.toLowerCase() !== repo.toLowerCase()) {
|
|
107
|
+
throw new Error(`${dir} is a clone of ${origin ?? "no repository"}, not of github ${repo}: move it away (push what should last ` +
|
|
108
|
+
`first), then start again`);
|
|
109
|
+
}
|
|
110
|
+
const kept = (reason) => ({ outcome: "kept", reason });
|
|
111
|
+
const branch = gitAnswer(["symbolic-ref", "--quiet", "--short", "HEAD"], dir);
|
|
112
|
+
const declared = ref ?? gitAnswer(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], dir)?.replace(/^origin\//, "");
|
|
113
|
+
if (branch !== undefined && branch !== declared) {
|
|
114
|
+
return kept(`it is on branch ${branch}, declared ${ref ?? `the default branch${declared ? `, ${declared}` : ""}`}`);
|
|
115
|
+
}
|
|
116
|
+
if (branch === undefined) {
|
|
117
|
+
if (ref === undefined)
|
|
118
|
+
return kept("it is on a detached commit, declared the default branch");
|
|
119
|
+
// Moving a detached HEAD away from commits of its own would leave them held by nothing.
|
|
120
|
+
if (gitAnswer(["rev-list", "--count", "HEAD", "--not", "--branches", "--tags", "--remotes"], dir) !== "0") {
|
|
121
|
+
return kept("it has commits no branch or tag holds");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// A clone made before the helper existed, or whose config was edited, gets it back.
|
|
125
|
+
await runGit(["config", HELPER_KEY, TOKEN_HELPER], dir);
|
|
126
|
+
const before = gitAnswer(["rev-parse", "HEAD"], dir);
|
|
127
|
+
// Pinned to a commit and on it: nothing a remote says can change that.
|
|
128
|
+
if (ref !== undefined && FULL_COMMIT.test(ref) && before === ref.toLowerCase())
|
|
129
|
+
return { outcome: "current" };
|
|
130
|
+
try {
|
|
131
|
+
await runGit([...SLOW_NETWORK, "fetch", "-q", "--end-of-options", "origin", branch ?? ref], dir);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return kept(`could not reach github ${repo}: ${gitReason(error)}`);
|
|
135
|
+
}
|
|
136
|
+
if (gitAnswer(["rev-parse", "FETCH_HEAD^{commit}"], dir) === before)
|
|
137
|
+
return { outcome: "current" };
|
|
138
|
+
try {
|
|
139
|
+
if (branch !== undefined) {
|
|
140
|
+
await runGit(["merge", "-q", "--ff-only", "FETCH_HEAD"], dir);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
await runGit(["checkout", "-q", "--detach", "FETCH_HEAD"], dir);
|
|
144
|
+
await runGit(["update-ref", PINNED, "HEAD"], dir);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return kept(`git would not update it: ${gitReason(error)}`);
|
|
149
|
+
}
|
|
150
|
+
return { outcome: "updated" };
|
|
151
|
+
}
|
|
152
|
+
/** git's own words for a refusal, without its hints. */
|
|
153
|
+
function gitReason(error) {
|
|
154
|
+
return error.message
|
|
155
|
+
.split("\n")
|
|
156
|
+
.map((line) => line.trim())
|
|
157
|
+
.filter((line) => line !== "" && !line.startsWith("hint:"))
|
|
158
|
+
.join(" ");
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Clone github `repo` at `ref` into `dir`, which does not exist: built beside it and renamed into place, so another
|
|
162
|
+
* process reads no clone or a whole one. When another process put its own there first, that one stands and this one
|
|
163
|
+
* is discarded. Shallow: a starting point to work from; a later fetch brings the history an update needs.
|
|
164
|
+
*/
|
|
165
|
+
async function cloneInto(repo, ref, dir) {
|
|
166
|
+
const parent = dirname(dir);
|
|
167
|
+
mkdirSync(parent, { recursive: true });
|
|
168
|
+
const next = join(parent, `.${basename(dir)}.next-${process.pid}-${Date.now()}`);
|
|
169
|
+
const url = githubUrl(repo);
|
|
170
|
+
try {
|
|
171
|
+
try {
|
|
172
|
+
if (ref !== undefined && FULL_COMMIT.test(ref)) {
|
|
173
|
+
await runGit(["init", "-q", next], parent);
|
|
174
|
+
await runGit(["config", HELPER_KEY, TOKEN_HELPER], next);
|
|
175
|
+
await runGit(["remote", "add", "origin", url], next);
|
|
176
|
+
await runGit([...SLOW_NETWORK, "fetch", "-q", "--depth", "1", "--end-of-options", "origin", ref], next);
|
|
177
|
+
await runGit(["checkout", "-q", "--detach", "FETCH_HEAD"], next);
|
|
178
|
+
await runGit(["update-ref", PINNED, "HEAD"], next);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// One token, and `--` before the operands: a declared value never reaches git as an option of its own.
|
|
182
|
+
const branch = ref !== undefined ? [`--branch=${ref}`] : [];
|
|
183
|
+
await runGit([
|
|
184
|
+
...SLOW_NETWORK,
|
|
185
|
+
"clone",
|
|
186
|
+
"-q",
|
|
187
|
+
"--depth",
|
|
188
|
+
"1",
|
|
189
|
+
"--config",
|
|
190
|
+
`${HELPER_KEY}=${TOKEN_HELPER}`,
|
|
191
|
+
...branch,
|
|
192
|
+
"--",
|
|
193
|
+
url,
|
|
194
|
+
next,
|
|
195
|
+
], parent);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
throw new Error(`could not clone github ${repo}${ref !== undefined ? ` at ${ref}` : ""}: ${error.message} — ` +
|
|
200
|
+
`git's own credentials on this machine are used (a credential helper, or url.<base>.insteadOf for SSH)`);
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
renameSync(next, dir);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
// Another process's clone got there first (renaming onto a directory that is not empty fails): it stands.
|
|
207
|
+
const code = error.code;
|
|
208
|
+
if ((code === "ENOTEMPTY" || code === "EEXIST") && existsSync(dir))
|
|
209
|
+
return { outcome: "current" };
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
return { outcome: "cloned" };
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
rmSync(next, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent's `content/`, where fastagent clones the repositories `context.json` declares. The directory carries its
|
|
3
|
+
* own `.gitignore` (`*`), as `.secrets/` does: each clone is its repository's, never the definition's, so it stays out
|
|
4
|
+
* of git even in an agent whose own `.gitignore` predates it.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
/** Make `dir`, the agent's `content/`, ready to hold a clone, and return it. */
|
|
9
|
+
export function ensureContentDir(dir) {
|
|
10
|
+
mkdirSync(dir, { recursive: true });
|
|
11
|
+
const ignore = join(dir, ".gitignore");
|
|
12
|
+
if (!existsSync(ignore))
|
|
13
|
+
writeFileSync(ignore, "*\n");
|
|
14
|
+
return dir;
|
|
15
|
+
}
|