@fastagent-sh/fastagent 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -12
- package/dist/agent.d.ts +24 -37
- package/dist/agent.js +7 -15
- package/dist/atomic-write.d.ts +13 -17
- package/dist/atomic-write.js +18 -29
- package/dist/bind.d.ts +12 -22
- package/dist/bind.js +18 -27
- package/dist/channel.d.ts +11 -33
- package/dist/channels/agentcore-limits.d.ts +2 -13
- package/dist/channels/agentcore-limits.js +2 -13
- package/dist/channels/agentcore-protocol.d.ts +67 -58
- package/dist/channels/agentcore-protocol.js +23 -15
- package/dist/channels/agentcore-service.d.ts +19 -33
- package/dist/channels/agentcore-service.js +176 -59
- package/dist/channels/agentcore.d.ts +39 -33
- package/dist/channels/agentcore.js +90 -224
- package/dist/channels/body.d.ts +15 -2
- package/dist/channels/body.js +24 -2
- package/dist/channels/busy.d.ts +1 -21
- package/dist/channels/busy.js +2 -39
- package/dist/channels/control.d.ts +17 -62
- package/dist/channels/control.js +74 -182
- package/dist/channels/define-channel.d.ts +39 -0
- package/dist/channels/define-channel.js +9 -0
- package/dist/channels/discover.d.ts +10 -31
- package/dist/channels/discover.js +37 -31
- package/dist/channels/feishu/bootstrap-token.d.ts +1 -8
- package/dist/channels/feishu/bootstrap-token.js +7 -23
- package/dist/channels/feishu/card.d.ts +14 -28
- package/dist/channels/feishu/card.js +16 -30
- package/dist/channels/feishu/cloud.d.ts +1 -5
- package/dist/channels/feishu/context-buffer.d.ts +4 -23
- package/dist/channels/feishu/context-buffer.js +8 -31
- package/dist/channels/feishu/crypto.d.ts +10 -5
- package/dist/channels/feishu/crypto.js +12 -19
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu-api.js +5 -4
- package/dist/channels/feishu/feishu.d.ts +4 -10
- package/dist/channels/feishu/feishu.js +113 -227
- package/dist/channels/feishu/invoke-turn.d.ts +5 -4
- package/dist/channels/feishu/invoke-turn.js +36 -54
- package/dist/channels/feishu/model.d.ts +4 -21
- package/dist/channels/feishu/model.js +1 -8
- package/dist/channels/feishu/normalize.d.ts +2 -10
- package/dist/channels/feishu/normalize.js +7 -42
- package/dist/channels/feishu/parse.d.ts +9 -29
- package/dist/channels/feishu/parse.js +8 -26
- package/dist/channels/feishu/preview.d.ts +12 -14
- package/dist/channels/feishu/preview.js +108 -207
- package/dist/channels/feishu/register-app.d.ts +8 -23
- package/dist/channels/feishu/register-app.js +7 -20
- package/dist/channels/feishu/register-webhook.d.ts +2 -9
- package/dist/channels/feishu/register-webhook.js +9 -33
- package/dist/channels/feishu/scaffold/channel.ts +27 -19
- package/dist/channels/feishu/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/feishu/setup-mode.d.ts +11 -36
- package/dist/channels/feishu/setup-mode.js +7 -26
- package/dist/channels/feishu/ws-ingress.d.ts +1 -2
- package/dist/channels/feishu/ws-ingress.js +6 -8
- package/dist/channels/http.d.ts +15 -15
- package/dist/channels/http.js +19 -9
- package/dist/channels/kit/attachment-path.d.ts +4 -5
- package/dist/channels/kit/attachment-path.js +10 -34
- package/dist/channels/kit/context-buffer.d.ts +14 -21
- package/dist/channels/kit/context-buffer.js +25 -38
- package/dist/channels/kit/delivery.d.ts +35 -0
- package/dist/channels/kit/delivery.js +129 -0
- package/dist/channels/kit/event-stream.d.ts +5 -0
- package/dist/channels/kit/event-stream.js +29 -0
- package/dist/channels/kit/invoke-turn-kit.d.ts +53 -26
- package/dist/channels/kit/invoke-turn-kit.js +89 -32
- package/dist/channels/kit/preview-kit.d.ts +18 -72
- package/dist/channels/kit/preview-kit.js +22 -88
- package/dist/channels/kit/signature.d.ts +3 -9
- package/dist/channels/kit/signature.js +3 -9
- package/dist/channels/kit/state.d.ts +1 -2
- package/dist/channels/kit/state.js +5 -14
- package/dist/channels/kit/stop-command.d.ts +1 -2
- package/dist/channels/kit/stop-command.js +6 -9
- package/dist/channels/kit/tasks.d.ts +1 -1
- package/dist/channels/kit/tasks.js +8 -21
- package/dist/channels/kit/text.d.ts +2 -16
- package/dist/channels/kit/text.js +23 -31
- package/dist/channels/kit/thread-participants.d.ts +4 -17
- package/dist/channels/kit/thread-participants.js +8 -55
- package/dist/channels/kit/transport.d.ts +26 -0
- package/dist/channels/kit/transport.js +20 -0
- package/dist/channels/kit/turn-queue.d.ts +7 -11
- package/dist/channels/kit/turn-queue.js +21 -39
- package/dist/channels/kit/turn-runner.d.ts +32 -18
- package/dist/channels/kit/turn-runner.js +98 -53
- package/dist/channels/kit/turn-store.d.ts +32 -32
- package/dist/channels/kit/turn-store.js +45 -88
- package/dist/channels/lark/lark.d.ts +1 -5
- package/dist/channels/lark/onboard.d.ts +3 -12
- package/dist/channels/lark/onboard.js +3 -13
- package/dist/channels/lark/scaffold/channel.ts +27 -19
- package/dist/channels/lark/scaffold/channel.websocket.ts +31 -0
- package/dist/channels/registration.d.ts +9 -35
- package/dist/channels/registration.js +6 -37
- package/dist/channels/secret.js +2 -5
- package/dist/channels/serve.d.ts +47 -44
- package/dist/channels/serve.js +165 -101
- package/dist/channels/slack/config-api.d.ts +4 -27
- package/dist/channels/slack/config-api.js +5 -34
- package/dist/channels/slack/context-buffer.d.ts +2 -3
- package/dist/channels/slack/context-buffer.js +5 -5
- package/dist/channels/slack/invoke-turn.d.ts +3 -1
- package/dist/channels/slack/invoke-turn.js +25 -34
- package/dist/channels/slack/manifest.js +1 -3
- package/dist/channels/slack/model.d.ts +1 -2
- package/dist/channels/slack/model.js +1 -1
- package/dist/channels/slack/onboard.d.ts +4 -3
- package/dist/channels/slack/onboard.js +8 -11
- package/dist/channels/slack/onboarding-state.d.ts +3 -5
- package/dist/channels/slack/onboarding-state.js +9 -6
- package/dist/channels/slack/parse.d.ts +4 -13
- package/dist/channels/slack/parse.js +7 -28
- package/dist/channels/slack/preview.d.ts +12 -2
- package/dist/channels/slack/preview.js +292 -269
- package/dist/channels/slack/reaction.d.ts +11 -3
- package/dist/channels/slack/reaction.js +19 -8
- package/dist/channels/slack/register-webhook.js +4 -7
- package/dist/channels/slack/scaffold/channel.ts +20 -13
- package/dist/channels/slack/scaffold/slack-send.ts +2 -2
- package/dist/channels/slack/setup-server.d.ts +1 -5
- package/dist/channels/slack/setup-server.js +1 -5
- package/dist/channels/slack/shared-api.d.ts +1 -4
- package/dist/channels/slack/shared-api.js +2 -13
- package/dist/channels/slack/slack-api.d.ts +2 -1
- package/dist/channels/slack/slack-api.js +6 -4
- package/dist/channels/slack/slack.d.ts +11 -14
- package/dist/channels/slack/slack.js +121 -122
- package/dist/channels/sse.d.ts +1 -1
- package/dist/channels/sse.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +7 -22
- package/dist/channels/telegram/context-buffer.js +6 -23
- package/dist/channels/telegram/invoke-turn.d.ts +7 -7
- package/dist/channels/telegram/invoke-turn.js +24 -43
- package/dist/channels/telegram/parse.d.ts +24 -51
- package/dist/channels/telegram/parse.js +32 -84
- package/dist/channels/telegram/preview.d.ts +10 -1
- package/dist/channels/telegram/preview.js +79 -100
- package/dist/channels/telegram/register-webhook.js +5 -23
- package/dist/channels/telegram/scaffold/channel.ts +25 -18
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -4
- package/dist/channels/telegram/telegram-api.d.ts +4 -3
- package/dist/channels/telegram/telegram-api.js +9 -7
- package/dist/channels/telegram/telegram.d.ts +4 -18
- package/dist/channels/telegram/telegram.js +38 -94
- package/dist/channels/telegram/turn-store.d.ts +11 -14
- package/dist/channels/telegram/turn-store.js +8 -6
- package/dist/channels/wait-health.d.ts +4 -6
- package/dist/channels/wait-health.js +10 -14
- package/dist/cli/add-feishu.d.ts +2 -12
- package/dist/cli/add-feishu.js +24 -71
- package/dist/cli/add-slack.d.ts +5 -3
- package/dist/cli/add-slack.js +53 -76
- package/dist/cli/auth-view.d.ts +2 -16
- package/dist/cli/auth-view.js +2 -16
- package/dist/cli/commands/add.js +25 -43
- package/dist/cli/commands/chat.d.ts +0 -1
- package/dist/cli/commands/chat.js +5 -6
- package/dist/cli/commands/deploy/agentcore.js +60 -65
- package/dist/cli/commands/deploy/docker.js +50 -23
- package/dist/cli/commands/deploy/fly.js +23 -61
- package/dist/cli/commands/deploy/railway.js +23 -18
- package/dist/cli/commands/deploy/shared.d.ts +38 -57
- package/dist/cli/commands/deploy/shared.js +40 -59
- package/dist/cli/commands/deploy.d.ts +10 -27
- package/dist/cli/commands/deploy.js +38 -60
- package/dist/cli/commands/destroy.d.ts +4 -0
- package/dist/cli/commands/destroy.js +56 -0
- package/dist/cli/commands/dev.d.ts +2 -1
- package/dist/cli/commands/dev.js +20 -23
- package/dist/cli/commands/info.d.ts +0 -2
- package/dist/cli/commands/info.js +83 -49
- package/dist/cli/commands/init.d.ts +1 -2
- package/dist/cli/commands/init.js +16 -55
- package/dist/cli/commands/invoke.d.ts +0 -1
- package/dist/cli/commands/invoke.js +5 -7
- package/dist/cli/commands/login.d.ts +2 -1
- package/dist/cli/commands/login.js +33 -38
- package/dist/cli/commands/logs.js +3 -4
- package/dist/cli/commands/models.js +1 -5
- package/dist/cli/commands/routine-run.d.ts +6 -0
- package/dist/cli/commands/routine-run.js +47 -0
- package/dist/cli/commands/routine.d.ts +25 -0
- package/dist/cli/commands/routine.js +122 -0
- package/dist/cli/commands/start.d.ts +31 -3
- package/dist/cli/commands/start.js +131 -81
- package/dist/cli/commands/tool.js +26 -12
- package/dist/cli/fail.d.ts +13 -23
- package/dist/cli/fail.js +20 -23
- package/dist/cli/invoke-stream.d.ts +1 -8
- package/dist/cli/kernel.d.ts +6 -38
- package/dist/cli/kernel.js +14 -77
- package/dist/cli/models-view.d.ts +1 -9
- package/dist/cli/models-view.js +11 -22
- package/dist/cli/program.d.ts +5 -6
- package/dist/cli/program.js +120 -157
- package/dist/cli/serve.d.ts +57 -41
- package/dist/cli/serve.js +94 -121
- package/dist/cli/shared.d.ts +15 -61
- package/dist/cli/shared.js +55 -181
- package/dist/cli.js +1 -8
- package/dist/collect.d.ts +6 -0
- package/dist/collect.js +6 -0
- package/dist/core.d.ts +4 -1
- package/dist/core.js +9 -7
- package/dist/declared-secrets.d.ts +55 -0
- package/dist/declared-secrets.js +88 -0
- package/dist/deploy/agentcore/aws-cli.d.ts +91 -0
- package/dist/deploy/agentcore/aws-cli.js +64 -0
- package/dist/deploy/agentcore/destroy.d.ts +51 -0
- package/dist/deploy/agentcore/destroy.js +273 -0
- package/dist/deploy/agentcore/forwarder.js +28 -79
- package/dist/deploy/agentcore/logs.d.ts +2 -18
- package/dist/deploy/agentcore/logs.js +27 -51
- package/dist/deploy/agentcore/plan.d.ts +85 -105
- package/dist/deploy/agentcore/plan.js +152 -314
- package/dist/deploy/agentcore/run.d.ts +28 -28
- package/dist/deploy/agentcore/run.js +227 -306
- package/dist/deploy/agentcore/zip.d.ts +2 -15
- package/dist/deploy/agentcore/zip.js +3 -17
- package/dist/deploy/build-context.d.ts +21 -0
- package/dist/deploy/build-context.js +137 -0
- package/dist/deploy/channel-ingress.d.ts +26 -39
- package/dist/deploy/channel-ingress.js +57 -28
- package/dist/deploy/container.d.ts +20 -34
- package/dist/deploy/container.js +49 -100
- package/dist/deploy/docker/plan.d.ts +12 -11
- package/dist/deploy/docker/plan.js +43 -24
- package/dist/deploy/docker/run.d.ts +22 -25
- package/dist/deploy/docker/run.js +39 -37
- package/dist/deploy/fly/plan.d.ts +15 -44
- package/dist/deploy/fly/plan.js +41 -73
- package/dist/deploy/fly/run.d.ts +16 -51
- package/dist/deploy/fly/run.js +42 -95
- package/dist/deploy/hosts.d.ts +24 -3
- package/dist/deploy/hosts.js +24 -3
- package/dist/deploy/preflight.d.ts +47 -41
- package/dist/deploy/preflight.js +282 -383
- package/dist/deploy/railway/plan.d.ts +16 -42
- package/dist/deploy/railway/plan.js +42 -60
- package/dist/deploy/railway/run.d.ts +30 -68
- package/dist/deploy/railway/run.js +86 -100
- package/dist/deploy/registration-gate.d.ts +6 -13
- package/dist/deploy/residency.d.ts +37 -0
- package/dist/deploy/residency.js +24 -0
- package/dist/deploy/runner.d.ts +10 -9
- package/dist/deploy/runner.js +7 -18
- package/dist/deploy/secrets.d.ts +40 -41
- package/dist/deploy/secrets.js +117 -103
- package/dist/deploy/workspace.d.ts +50 -0
- package/dist/deploy/workspace.js +197 -0
- package/dist/dev-supervisor.d.ts +1 -9
- package/dist/dev-supervisor.js +21 -64
- package/dist/effect-port.d.ts +50 -0
- package/dist/effect-port.js +77 -0
- package/dist/engines/pi/agent-session-factory.d.ts +50 -69
- package/dist/engines/pi/agent-session-factory.js +249 -146
- package/dist/engines/pi/auth.d.ts +15 -11
- package/dist/engines/pi/auth.js +96 -99
- package/dist/engines/pi/chat.d.ts +1 -5
- package/dist/engines/pi/chat.js +2 -14
- package/dist/engines/pi/config.d.ts +53 -51
- package/dist/engines/pi/config.js +83 -92
- package/dist/engines/pi/create.d.ts +60 -75
- package/dist/engines/pi/create.js +108 -141
- package/dist/engines/pi/definition.d.ts +14 -58
- package/dist/engines/pi/definition.js +21 -85
- package/dist/engines/pi/invoke-session.d.ts +5 -25
- package/dist/engines/pi/invoke-session.js +209 -244
- package/dist/engines/pi/login.d.ts +95 -35
- package/dist/engines/pi/login.js +259 -107
- package/dist/engines/pi/machine.d.ts +31 -0
- package/dist/engines/pi/machine.js +106 -0
- package/dist/engines/pi/models.d.ts +70 -89
- package/dist/engines/pi/models.js +188 -81
- package/dist/engines/pi/open.d.ts +65 -53
- package/dist/engines/pi/open.js +129 -69
- package/dist/engines/pi/report.d.ts +3 -12
- package/dist/engines/pi/report.js +2 -16
- package/dist/engines/pi/search-tools.d.ts +1 -9
- package/dist/engines/pi/search-tools.js +26 -57
- package/dist/engines/pi/service.d.ts +2 -16
- package/dist/engines/pi/service.js +2 -16
- package/dist/engines/pi/session-builder.d.ts +5 -9
- package/dist/engines/pi/session-builder.js +44 -82
- package/dist/engines/pi/session-control.js +286 -269
- package/dist/engines/pi/session-effects.d.ts +14 -0
- package/dist/engines/pi/session-effects.js +30 -0
- package/dist/engines/pi/session-inheritance.d.ts +9 -18
- package/dist/engines/pi/session-inheritance.js +103 -160
- package/dist/engines/pi/session-markers.d.ts +31 -30
- package/dist/engines/pi/session-markers.js +26 -25
- package/dist/engines/pi/session-settings.d.ts +6 -22
- package/dist/engines/pi/session-settings.js +10 -21
- package/dist/engines/pi/session-store.d.ts +26 -92
- package/dist/engines/pi/session-store.js +96 -310
- package/dist/engines/pi/tool-context.d.ts +17 -45
- package/dist/engines/pi/tool-context.js +7 -26
- package/dist/engines/pi/tool.d.ts +45 -36
- package/dist/engines/pi/tool.js +43 -57
- package/dist/engines/pi/turn-kit.d.ts +14 -52
- package/dist/engines/pi/turn-kit.js +18 -67
- package/dist/engines/pi/wake-tool.d.ts +2 -10
- package/dist/engines/pi/wake-tool.js +7 -27
- package/dist/env.d.ts +27 -28
- package/dist/env.js +45 -50
- package/dist/index.js +0 -2
- package/dist/lark.d.ts +1 -2
- package/dist/lark.js +1 -2
- package/dist/loader.d.ts +14 -55
- package/dist/loader.js +24 -43
- package/dist/log.d.ts +2 -12
- package/dist/log.js +3 -24
- package/dist/node.d.ts +2 -15
- package/dist/node.js +2 -18
- package/dist/observe.d.ts +2 -8
- package/dist/open-url.d.ts +1 -1
- package/dist/open-url.js +1 -1
- package/dist/paths.d.ts +70 -134
- package/dist/paths.js +165 -232
- package/dist/pi.d.ts +4 -6
- package/dist/pi.js +3 -3
- package/dist/proxy.d.ts +16 -6
- package/dist/proxy.js +52 -16
- package/dist/runtime.d.ts +18 -12
- package/dist/runtime.js +18 -11
- package/dist/scaffold/add-channel.d.ts +14 -31
- package/dist/scaffold/add-channel.js +48 -112
- package/dist/scaffold/init.d.ts +9 -31
- package/dist/scaffold/init.js +45 -138
- package/dist/scaffold/templates/env.example +6 -4
- package/dist/scaffold/templates/fastagent.config.ts +36 -0
- package/dist/scaffold/templates.d.ts +3 -3
- package/dist/scaffold/templates.js +5 -8
- package/dist/scaffold/vendor-skill.d.ts +2 -5
- package/dist/scaffold/vendor-skill.js +19 -29
- package/dist/schedule/cron.d.ts +9 -5
- package/dist/schedule/cron.js +9 -11
- package/dist/schedule/discover.d.ts +12 -12
- package/dist/schedule/discover.js +43 -34
- package/dist/schedule/routine.d.ts +43 -0
- package/dist/schedule/routine.js +32 -0
- package/dist/schedule/run.d.ts +55 -0
- package/dist/schedule/run.js +146 -0
- package/dist/schedule/scheduler.d.ts +44 -55
- package/dist/schedule/scheduler.js +291 -265
- package/dist/schedule/state.d.ts +105 -10
- package/dist/schedule/state.js +293 -19
- package/dist/schedule/wake-alarm.d.ts +10 -23
- package/dist/schedule/wake-alarm.js +91 -121
- package/dist/schedule/wakeups.d.ts +23 -39
- package/dist/schedule/wakeups.js +33 -75
- package/dist/secrets-gate.d.ts +32 -0
- package/dist/secrets-gate.js +33 -0
- package/dist/service.d.ts +137 -98
- package/dist/service.js +250 -252
- package/dist/session-remote.d.ts +33 -44
- package/dist/session-remote.js +203 -192
- package/dist/session.d.ts +153 -228
- package/dist/session.js +33 -55
- package/dist/tunnel.d.ts +8 -36
- package/dist/tunnel.js +24 -65
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +10 -16
- package/dist/channels/agentcore-state.d.ts +0 -77
- package/dist/channels/agentcore-state.js +0 -276
- package/dist/channels/github/github.d.ts +0 -31
- package/dist/channels/github/github.js +0 -89
- package/dist/channels/github/scaffold/channel.ts +0 -25
- package/dist/cli/commands/attach.d.ts +0 -108
- package/dist/cli/commands/attach.js +0 -637
- package/dist/cli/commands/fire.d.ts +0 -7
- package/dist/cli/commands/fire.js +0 -38
- package/dist/cli/commands/schedule.d.ts +0 -12
- package/dist/cli/commands/schedule.js +0 -87
- package/dist/github.d.ts +0 -3
- package/dist/github.js +0 -2
- package/dist/scaffold/templates/fastagent.config.mjs +0 -23
- package/dist/schedule/audit.d.ts +0 -18
- package/dist/schedule/audit.js +0 -57
- package/dist/schedule/schedule.d.ts +0 -35
- package/dist/schedule/schedule.js +0 -23
package/dist/proxy.js
CHANGED
|
@@ -1,23 +1,59 @@
|
|
|
1
|
-
// Namespace import (not `import { install }`): Bun's `undici` is a native shim that omits `install`, and
|
|
2
|
-
//
|
|
3
|
-
// before any code runs. A namespace import binds whatever exports exist; the Bun guard below then never
|
|
4
|
-
// touches the Node-only members.
|
|
1
|
+
// Namespace import (not `import { install }`): Bun's `undici` is a native shim that omits `install`, and a STATIC
|
|
2
|
+
// NAMED import of a missing export fails at link time.
|
|
5
3
|
import * as undici from "undici";
|
|
6
|
-
/** The Bun runtime (`process.versions.bun` is set only there).
|
|
7
|
-
* proxy env vars and decompresses gzip, and its `undici` shim has no `install()` — so the Node-only
|
|
8
|
-
* undici wiring below is both unnecessary and unavailable there. */
|
|
4
|
+
/** The Bun runtime (`process.versions.bun` is set only there). */
|
|
9
5
|
const isBun = typeof process.versions.bun === "string";
|
|
10
6
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
7
|
+
* What is ALWAYS exempt from the proxy. `EnvHttpProxyAgent` proxies EVERYTHING while NO_PROXY is empty — loopback
|
|
8
|
+
* included ("Always proxy if NO_PROXY is not set or empty") — so installing a proxy would otherwise send this process's
|
|
9
|
+
* own local traffic through it: a Docker health probe on 127.0.0.1, a control-plane call to a local serve, an `ssh -L`
|
|
10
|
+
* forward given to a control-plane client's `url`. None of those are what a proxy variable is asking for.
|
|
11
|
+
*
|
|
12
|
+
* APPENDED to a declared NO_PROXY rather than used as its default, because `NO_PROXY=.corp.com` is the normal shape of
|
|
13
|
+
* a corporate environment and it is not a statement about 127.0.0.1: replacing would hand exactly the users who have a
|
|
14
|
+
* proxy back the failure this exists to remove, as a probe timeout rather than an error.
|
|
15
|
+
*/
|
|
16
|
+
const LOOPBACK = "localhost,127.0.0.1,::1";
|
|
17
|
+
/**
|
|
18
|
+
* The exemption list the dispatcher is built with: whatever the environment declares, plus {@link LOOPBACK}.
|
|
19
|
+
*
|
|
20
|
+
* Its own function because installing happens ONCE per process, so these branches are unreachable through
|
|
21
|
+
* {@link installProxyFetch} — and the wildcard one is the kind that is silently wrong until someone reads it.
|
|
22
|
+
*/
|
|
23
|
+
export function noProxyList(env = process.env) {
|
|
24
|
+
// Mirroring undici's own precedence (lowercase first) so our value cannot disagree with the one it would read.
|
|
25
|
+
// `||`, not `??`: a `NO_PROXY=` line in a .env is an empty string, and undici treats it as "proxy everything".
|
|
26
|
+
const declared = (env.no_proxy || env.NO_PROXY)?.trim();
|
|
27
|
+
if (!declared)
|
|
28
|
+
return LOOPBACK;
|
|
29
|
+
// `*` is undici's "never proxy anything", matched EXACTLY (`#noProxyValue === '*'`). Appending would demote it to a
|
|
30
|
+
// hostname that matches nothing — turning "disable the proxy" into "proxy everything but loopback".
|
|
31
|
+
return declared === "*" ? declared : `${declared},${LOOPBACK}`;
|
|
32
|
+
}
|
|
33
|
+
/** The install is process-global and once is enough; a second call would only leak the first dispatcher (nothing
|
|
34
|
+
* closes it) and re-read the same environment. */
|
|
35
|
+
let installed = false;
|
|
36
|
+
/** Whoever owned `fetch` before this module loaded. A caller that replaced it since (a test's mock, an embedder's
|
|
37
|
+
* instrumentation) meant it, so the swap below leaves it alone — the dispatcher still routes everything that runs
|
|
38
|
+
* on undici's global. */
|
|
39
|
+
const originalFetch = globalThis.fetch;
|
|
40
|
+
/**
|
|
41
|
+
* Point this process's fetch at the proxy the environment declares, with loopback exempt by default.
|
|
42
|
+
*
|
|
43
|
+
* Two jobs in one, and both are needed UNDER NODE: Node's fetch does not honor HTTP(S)_PROXY at all, and Node 26's
|
|
44
|
+
* bundled fetch skips gzip decompression when it dispatches through npm undici (empty `stopReason:"stop"`), so fetch
|
|
45
|
+
* and the dispatcher must come from the SAME undici. No-op under Bun, whose native fetch already does both (and
|
|
46
|
+
* lacks these entry points).
|
|
47
|
+
*
|
|
48
|
+
* Call it through `enterAgentEnv` (env.ts) rather than directly: the proxy may be declared in the agent's own `.env`,
|
|
49
|
+
* so it has to be read first. The only direct callers are the ones with no agent directory to read.
|
|
17
50
|
*/
|
|
18
51
|
export function installProxyFetch() {
|
|
19
|
-
if (isBun)
|
|
52
|
+
if (isBun || installed)
|
|
20
53
|
return;
|
|
21
|
-
undici.setGlobalDispatcher(new undici.EnvHttpProxyAgent());
|
|
22
|
-
|
|
54
|
+
undici.setGlobalDispatcher(new undici.EnvHttpProxyAgent({ noProxy: noProxyList() }));
|
|
55
|
+
// Skipping `install` leaves the caller's fetch in place, and with it Node 26's missing gzip decompression above.
|
|
56
|
+
if (globalThis.fetch === originalFetch)
|
|
57
|
+
undici.install();
|
|
58
|
+
installed = true; // last: a throw above must leave the latch open, or the process runs un-proxied forever
|
|
23
59
|
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
1
|
+
/**
|
|
2
|
+
* How to install and run the AGENT: its package.json + lockfile decide, never the surrounding workspace's (whose
|
|
3
|
+
* toolchain is the agent's runtime concern, not fastagent's).
|
|
4
|
+
*/
|
|
3
5
|
export interface AgentRuntime {
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
+
/**
|
|
7
|
+
* The JS runtime the agent targets — drives the generated Dockerfile base + install/run commands and the
|
|
8
|
+
* package-manager hints in `init`/`add`.
|
|
9
|
+
*/
|
|
6
10
|
runtime: "node" | "bun";
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
11
|
+
/**
|
|
12
|
+
* For `runtime: "bun"`, the version from package.json's `packageManager: "bun@x"` (undefined if a bun lockfile made
|
|
13
|
+
* it bun but no version is pinned).
|
|
14
|
+
*/
|
|
9
15
|
bunVersion?: string;
|
|
10
16
|
/** Whether the runtime's lockfile is present (package-lock.json for node, bun.lock/bun.lockb for bun). */
|
|
11
17
|
hasLockfile: boolean;
|
|
12
18
|
}
|
|
13
19
|
/**
|
|
14
|
-
* Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a
|
|
15
|
-
*
|
|
16
|
-
* `{}` when there is none / it is malformed). One source for the deploy Dockerfile and the CLI hints, so
|
|
17
|
-
* they agree on what the agent is.
|
|
20
|
+
* Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a bun lockfile
|
|
21
|
+
* (bun.lock/bun.lockb) is present, else `node`.
|
|
18
22
|
*/
|
|
19
23
|
export declare function detectRuntime(dir: string, pkg: {
|
|
20
24
|
packageManager?: unknown;
|
|
21
25
|
}): AgentRuntime;
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
26
|
+
/**
|
|
27
|
+
* Parse `<dir>/package.json`, or `{}` when there is none (a markdown-only agent). A file that does not parse throws,
|
|
28
|
+
* naming itself: read as `{}` it became "does not list @fastagent-sh/fastagent" and an npm-based image, both wrong.
|
|
29
|
+
*/
|
|
24
30
|
export declare function readPackageJson(dir: string): Promise<{
|
|
25
31
|
packageManager?: unknown;
|
|
26
32
|
dependencies?: Record<string, unknown>;
|
package/dist/runtime.js
CHANGED
|
@@ -2,28 +2,35 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
/**
|
|
5
|
-
* Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a
|
|
6
|
-
*
|
|
7
|
-
* `{}` when there is none / it is malformed). One source for the deploy Dockerfile and the CLI hints, so
|
|
8
|
-
* they agree on what the agent is.
|
|
5
|
+
* Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a bun lockfile
|
|
6
|
+
* (bun.lock/bun.lockb) is present, else `node`.
|
|
9
7
|
*/
|
|
10
8
|
export function detectRuntime(dir, pkg) {
|
|
11
9
|
const pm = typeof pkg.packageManager === "string" ? pkg.packageManager : "";
|
|
12
10
|
const bunLock = existsSync(join(dir, "bun.lock")) || existsSync(join(dir, "bun.lockb"));
|
|
13
11
|
if (pm.startsWith("bun@") || bunLock) {
|
|
14
|
-
// corepack pins as `bun@1.3.13+sha256.<hash>`; the version (a Docker tag for oven/bun) is the part
|
|
15
|
-
// BEFORE `+` — keeping the hash would build an invalid `FROM oven/bun:1.3.13+sha256…` tag.
|
|
12
|
+
// corepack pins as `bun@1.3.13+sha256.<hash>`; the version (a Docker tag for oven/bun) is the part BEFORE `+`.
|
|
16
13
|
return { runtime: "bun", bunVersion: pm.match(/^bun@([^+]+)/)?.[1], hasLockfile: bunLock };
|
|
17
14
|
}
|
|
18
15
|
return { runtime: "node", hasLockfile: existsSync(join(dir, "package-lock.json")) };
|
|
19
16
|
}
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
17
|
+
/**
|
|
18
|
+
* Parse `<dir>/package.json`, or `{}` when there is none (a markdown-only agent). A file that does not parse throws,
|
|
19
|
+
* naming itself: read as `{}` it became "does not list @fastagent-sh/fastagent" and an npm-based image, both wrong.
|
|
20
|
+
*/
|
|
22
21
|
export async function readPackageJson(dir) {
|
|
22
|
+
const path = join(dir, "package.json");
|
|
23
|
+
const raw = await readFile(path, "utf8").catch((error) => {
|
|
24
|
+
if (error.code === "ENOENT")
|
|
25
|
+
return undefined;
|
|
26
|
+
throw error;
|
|
27
|
+
});
|
|
28
|
+
if (raw === undefined)
|
|
29
|
+
return {};
|
|
23
30
|
try {
|
|
24
|
-
return JSON.parse(
|
|
31
|
+
return JSON.parse(raw);
|
|
25
32
|
}
|
|
26
|
-
catch {
|
|
27
|
-
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`${path} is not valid JSON: ${error.message}`);
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import type { FeishuSubscriptionMode } from "../channels/feishu/setup-mode.ts";
|
|
2
|
-
export type ChannelKind = "
|
|
3
|
-
/** Group-visibility choice shared by the slack/feishu/lark onboarding flows.
|
|
4
|
-
* own channel-level type (`SlackGroupBehavior`, `FeishuGroupBehavior`) — this is the CLI-side value. */
|
|
2
|
+
export type ChannelKind = "telegram" | "slack" | "feishu" | "lark";
|
|
3
|
+
/** Group-visibility choice shared by the slack/feishu/lark onboarding flows. */
|
|
5
4
|
export type GroupBehavior = "context" | "mentions";
|
|
6
|
-
/** A resolved group-behavior decision plus whether the author actually chose it (flag or prompt).
|
|
7
|
-
* A defaulted "context" (non-interactive, no flag) must never drive a sensitive-scope write. */
|
|
5
|
+
/** A resolved group-behavior decision plus whether the author actually chose it (flag or prompt). */
|
|
8
6
|
export interface GroupBehaviorChoice {
|
|
9
7
|
behavior: GroupBehavior;
|
|
10
8
|
explicit: boolean;
|
|
11
9
|
}
|
|
12
|
-
/** An env var a scaffolded channel reads.
|
|
10
|
+
/** An env var a scaffolded channel reads. */
|
|
13
11
|
export interface ChannelEnv {
|
|
14
12
|
name: string;
|
|
15
13
|
hint: string;
|
|
16
|
-
/** Required for the channel to run.
|
|
14
|
+
/** Required for the channel to run. */
|
|
17
15
|
required: boolean;
|
|
18
16
|
generate?: boolean;
|
|
19
17
|
}
|
|
@@ -25,9 +23,8 @@ export declare function channelSetup(kind: ChannelKind, ingress?: FeishuSubscrip
|
|
|
25
23
|
steps: string[];
|
|
26
24
|
};
|
|
27
25
|
/**
|
|
28
|
-
* Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who
|
|
29
|
-
*
|
|
30
|
-
* is already present. Placeholders only — no real secret lands in the committable template.
|
|
26
|
+
* Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who copies it to
|
|
27
|
+
* `.env` finds the vars already there.
|
|
31
28
|
*/
|
|
32
29
|
export declare function appendChannelEnv(dir: string, kind: ChannelKind, ingress?: FeishuSubscriptionMode): Promise<boolean>;
|
|
33
30
|
export interface DotEnvWriteResult {
|
|
@@ -35,38 +32,24 @@ export interface DotEnvWriteResult {
|
|
|
35
32
|
written: string[];
|
|
36
33
|
/** Vars already present with a non-empty active value; left untouched and omitted from next steps. */
|
|
37
34
|
alreadySet: string[];
|
|
38
|
-
/** Set when the secrets dir is an operator-chosen one (`FASTAGENT_SECRETS_DIR`) carrying no
|
|
39
|
-
* `.gitignore`: a secret was just written into a directory fastagent does not own, so the caller
|
|
40
|
-
* states the fact rather than dropping a `*`-ignoring file into someone else's path. */
|
|
35
|
+
/** Set when the secrets dir is an operator-chosen one (`FASTAGENT_SECRETS_DIR`) carrying no `.gitignore`. */
|
|
41
36
|
unprotectedSecretsDir?: string;
|
|
42
37
|
}
|
|
43
38
|
/**
|
|
44
|
-
* Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`)
|
|
45
|
-
*
|
|
46
|
-
* `overwrite`: those are authoritative (e.g. the credentials of an app `add feishu` JUST minted —
|
|
47
|
-
* skipping them for a stale value would silently discard a fresh, unrecoverable secret). Manual values
|
|
48
|
-
* (e.g. TELEGRAM_BOT_TOKEN from BotFather) are added only as commented placeholders, so the file is
|
|
49
|
-
* ready to edit while no fake secret is committed to the user's mental model.
|
|
39
|
+
* Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`) Existing non-empty
|
|
40
|
+
* values are kept.
|
|
50
41
|
*/
|
|
51
42
|
export declare function appendChannelDotEnv(dir: string, kind: ChannelKind, generated: Record<string, string>, overwrite?: readonly string[], ingress?: FeishuSubscriptionMode): Promise<DotEnvWriteResult>;
|
|
52
43
|
/** Whether a channel file already exists — checked before any mutation, so a no-clobber re-add is side-effect-free. */
|
|
53
44
|
export declare function channelExists(dir: string, kind: ChannelKind): Promise<boolean>;
|
|
54
|
-
/**
|
|
55
|
-
* Scaffold `channels/<kind>.ts` into {@link dir}. Never clobbers an existing file (the glue is
|
|
56
|
-
* authored content). The wx write is the TOCTOU safety net behind {@link channelExists}.
|
|
57
|
-
*/
|
|
45
|
+
/** Scaffold `channels/<kind>.ts` into {@link dir}. */
|
|
58
46
|
export declare function scaffoldChannel(dir: string, kind: ChannelKind, options?: {
|
|
59
47
|
ingress?: FeishuSubscriptionMode;
|
|
60
48
|
}): Promise<string>;
|
|
61
|
-
/**
|
|
62
|
-
* The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). Unlike the channel
|
|
63
|
-
* file they are the package's, not authored glue, so they are written on EVERY add: re-running
|
|
64
|
-
* `add <kind>` is how an upgraded tool reaches an existing agent.
|
|
65
|
-
*/
|
|
49
|
+
/** The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). */
|
|
66
50
|
export declare function scaffoldCompanionTools(dir: string, kind: ChannelKind): Promise<string[]>;
|
|
67
51
|
/**
|
|
68
|
-
* Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares
|
|
69
|
-
*
|
|
70
|
-
* is `init`'s job.
|
|
52
|
+
* Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares `@fastagent-sh/fastagent` (the
|
|
53
|
+
* channel file imports it).
|
|
71
54
|
*/
|
|
72
55
|
export declare function assertChannelReady(dir: string): Promise<void>;
|
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fastagent add <channel>`: drop a `channels/<kind>.ts` adapter-glue file (+ any companion tool, +
|
|
3
|
-
* `.secrets/.env.example` vars) into an existing agent.
|
|
4
|
-
* bootstraps an agent (that is `init`'s job). Each channel's template files live in its own bundle
|
|
5
|
-
* at src/channels/<kind>/scaffold/, read here at scaffold time.
|
|
3
|
+
* `.secrets/.env.example` vars) into an existing agent.
|
|
6
4
|
*/
|
|
7
5
|
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
8
6
|
import { dirname, join } from "node:path";
|
|
9
7
|
import { detectRuntime } from "../runtime.js";
|
|
10
|
-
import { SECRETS_DIRNAME,
|
|
8
|
+
import { SECRETS_DIRNAME, SECRET_FILE_MODE, assertInsideAgentDir, exists } from "../paths.js";
|
|
11
9
|
import { baseTemplate, channelBundleFiles, channelTemplate } from "./templates.js";
|
|
12
10
|
import { dotEnvPath, envExamplePath, parseEnvContent } from "../env.js";
|
|
13
11
|
const CHANNEL_SCAFFOLDS = {
|
|
14
|
-
github: {
|
|
15
|
-
env: [
|
|
16
|
-
{
|
|
17
|
-
name: "GITHUB_WEBHOOK_SECRET",
|
|
18
|
-
hint: "any random string; set the same value in the GitHub webhook",
|
|
19
|
-
required: true,
|
|
20
|
-
generate: true,
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
// `{channel}` / `{tools}` are path placeholders the CLI resolves to the real agent-dir-relative
|
|
24
|
-
// location — the CLI holds no channel-private filenames.
|
|
25
|
-
steps: [
|
|
26
|
-
"edit {channel} — map events to intents in on()",
|
|
27
|
-
"add the webhook in your repo (Settings → Webhooks): Payload URL = <public-url>/webhook, content type application/json",
|
|
28
|
-
],
|
|
29
|
-
},
|
|
30
12
|
telegram: {
|
|
31
13
|
env: [
|
|
32
14
|
{ name: "TELEGRAM_BOT_TOKEN", hint: "from @BotFather → /newbot", required: true },
|
|
@@ -54,11 +36,8 @@ const CHANNEL_SCAFFOLDS = {
|
|
|
54
36
|
"the agent can send messages or files by calling the scaffolded {tools}/slack-send.ts tool",
|
|
55
37
|
],
|
|
56
38
|
},
|
|
57
|
-
// Feishu is the canonical engine/cloud; Lark international reuses its protocol through a degraded
|
|
58
|
-
//
|
|
59
|
-
// This table is the webhook setup; continuous mode below selects only App ID/Secret. No `generate`
|
|
60
|
-
// in either: values come FROM the platform. `add feishu` scan-creates the app; Lark lacks that
|
|
61
|
-
// control-plane capability, so `add lark` guides console credential collection.
|
|
39
|
+
// Feishu is the canonical engine/cloud; Lark international reuses its protocol through a degraded compatibility
|
|
40
|
+
// profile.
|
|
62
41
|
feishu: {
|
|
63
42
|
env: [
|
|
64
43
|
{
|
|
@@ -165,9 +144,8 @@ export function channelSetup(kind, ingress = "webhook", groupBehavior) {
|
|
|
165
144
|
return { env: setup.env, steps: setup.steps };
|
|
166
145
|
}
|
|
167
146
|
/**
|
|
168
|
-
* Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who
|
|
169
|
-
*
|
|
170
|
-
* is already present. Placeholders only — no real secret lands in the committable template.
|
|
147
|
+
* Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who copies it to
|
|
148
|
+
* `.env` finds the vars already there.
|
|
171
149
|
*/
|
|
172
150
|
export async function appendChannelEnv(dir, kind, ingress = "webhook") {
|
|
173
151
|
const file = envExamplePath(dir);
|
|
@@ -183,19 +161,14 @@ export async function appendChannelEnv(dir, kind, ingress = "webhook") {
|
|
|
183
161
|
const marker = `# --- ${kind} channel ---`;
|
|
184
162
|
if (current.includes(marker))
|
|
185
163
|
return false;
|
|
186
|
-
// Hint on its OWN line above the placeholder (like the base env.example template)
|
|
187
|
-
// after `=`: loadEnvFile does not strip trailing comments, so an uncommented `KEY= # hint` (or a
|
|
188
|
-
// value pasted before the `#`) would carry the hint text into the parsed value.
|
|
164
|
+
// Hint on its OWN line above the placeholder (like the base env.example template).
|
|
189
165
|
const block = `\n${marker}\n${channelSetup(kind, ingress)
|
|
190
166
|
.env.map((e) => `# ${e.hint}\n# ${e.name}=`)
|
|
191
167
|
.join("\n")}\n`;
|
|
192
168
|
await appendFile(file, block);
|
|
193
169
|
return true;
|
|
194
170
|
}
|
|
195
|
-
/** Whether `.env` content carries a non-empty ACTIVE value for `name
|
|
196
|
-
* ({@link parseEnvContent}), not a re-implementation, so this check can never disagree with what
|
|
197
|
-
* `loadEnvFile` will actually read (a missed match here would append a second assignment that
|
|
198
|
-
* last-wins over the user's working secret). */
|
|
171
|
+
/** Whether `.env` content carries a non-empty ACTIVE value for `name`. */
|
|
199
172
|
function hasActiveEnvValue(content, name) {
|
|
200
173
|
return (parseEnvContent(content).get(name)?.trim() ?? "") !== "";
|
|
201
174
|
}
|
|
@@ -203,34 +176,19 @@ function mentionsEnvName(content, name) {
|
|
|
203
176
|
return content.split("\n").some((line) => new RegExp(`^\\s*#?\\s*${name}\\s*=`).test(line));
|
|
204
177
|
}
|
|
205
178
|
/**
|
|
206
|
-
* Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`)
|
|
207
|
-
*
|
|
208
|
-
* `overwrite`: those are authoritative (e.g. the credentials of an app `add feishu` JUST minted —
|
|
209
|
-
* skipping them for a stale value would silently discard a fresh, unrecoverable secret). Manual values
|
|
210
|
-
* (e.g. TELEGRAM_BOT_TOKEN from BotFather) are added only as commented placeholders, so the file is
|
|
211
|
-
* ready to edit while no fake secret is committed to the user's mental model.
|
|
179
|
+
* Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`) Existing non-empty
|
|
180
|
+
* values are kept.
|
|
212
181
|
*/
|
|
213
182
|
export async function appendChannelDotEnv(dir, kind, generated, overwrite = [], ingress = "webhook") {
|
|
214
183
|
const file = dotEnvPath(dir);
|
|
215
184
|
const secretsDir = dirname(file);
|
|
216
|
-
await
|
|
217
|
-
// THE one exception to "fastagent has no opinion about git": the directory it writes secrets into
|
|
218
|
-
//
|
|
219
|
-
// missing (a hand-made agent) is exactly the one where the next line mints an unrecoverable app
|
|
220
|
-
// secret. `wx`, so a file the author wrote is never touched; the accepted cost is that someone who
|
|
221
|
-
// DELETED it to track secrets deliberately gets it back once. The risk is not symmetric — that is an
|
|
222
|
-
// annoyance; the other way is a published credential.
|
|
223
|
-
//
|
|
224
|
-
// Scoped to the DEFAULT `<agentDir>/.secrets`, which is fastagent's own directory. A dir named by
|
|
225
|
-
// `FASTAGENT_SECRETS_DIR` belongs to the operator, and this template is `*` plus two negations —
|
|
226
|
-
// dropping it there would hide that directory's OTHER contents from their `git add`, which is a
|
|
227
|
-
// bigger harm than the one it prevents, and inflicted on a path they chose deliberately. They get the
|
|
228
|
-
// fact instead, and own the decision.
|
|
185
|
+
await mkdir(secretsDir, { recursive: true });
|
|
186
|
+
// THE one exception to "fastagent has no opinion about git": the directory it writes secrets into carries its own
|
|
187
|
+
// `.gitignore`.
|
|
229
188
|
const owned = secretsDir === join(dir, SECRETS_DIRNAME);
|
|
230
189
|
let unprotectedSecretsDir;
|
|
231
190
|
if (owned) {
|
|
232
|
-
// Only EEXIST is tolerable (already protected, or a concurrent writer).
|
|
233
|
-
// the file that keeps credentials out of git must surface, not be swallowed.
|
|
191
|
+
// Only EEXIST is tolerable (already protected, or a concurrent writer).
|
|
234
192
|
await writeFile(join(secretsDir, ".gitignore"), baseTemplate("secrets.gitignore"), { flag: "wx" }).catch((e) => {
|
|
235
193
|
if (e.code !== "EEXIST")
|
|
236
194
|
throw e;
|
|
@@ -260,10 +218,8 @@ export async function appendChannelDotEnv(dir, kind, generated, overwrite = [],
|
|
|
260
218
|
continue;
|
|
261
219
|
const value = generated[e.name];
|
|
262
220
|
if (value !== undefined) {
|
|
263
|
-
// An ACTIVE but EMPTY assignment already in the file (an uncommented, unfilled placeholder from
|
|
264
|
-
//
|
|
265
|
-
// to it or wins by position under last-wins — both silently. Replace the LAST occurrence (the one
|
|
266
|
-
// the parser would honor). Line-level match uses THE parser, never a hand regex.
|
|
221
|
+
// An ACTIVE but EMPTY assignment already in the file (an uncommented, unfilled placeholder from `cp
|
|
222
|
+
// .env.example .env`) must be replaced IN PLACE.
|
|
267
223
|
let idx = -1;
|
|
268
224
|
for (let i = contentLines.length - 1; i >= 0; i--) {
|
|
269
225
|
if (parseEnvContent(contentLines[i]).has(e.name)) {
|
|
@@ -281,24 +237,28 @@ export async function appendChannelDotEnv(dir, kind, generated, overwrite = [],
|
|
|
281
237
|
written.push(e.name);
|
|
282
238
|
}
|
|
283
239
|
else if (!mentionsEnvName(current, e.name)) {
|
|
284
|
-
// Hint above, never inline after `=` — see appendChannelEnv (this IS the file
|
|
240
|
+
// Hint above, never inline after `=` — see appendChannelEnv (this IS the file loadDotEnv reads).
|
|
285
241
|
lines.push(`# ${e.hint}`, `# ${e.name}=`);
|
|
286
242
|
}
|
|
287
243
|
}
|
|
244
|
+
// 0600 on the file this call CREATES, and nothing at all about one it did not: a `.env` the operator made with
|
|
245
|
+
// `cp .secrets/.env.example .secrets/.env` carries the mode that command gave it, and re-deciding it would be
|
|
246
|
+
// fastagent changing the permissions of a file it does not own.
|
|
247
|
+
const secret = { mode: SECRET_FILE_MODE };
|
|
288
248
|
if (replacedInPlace) {
|
|
289
249
|
current = contentLines.join("\n");
|
|
290
|
-
await writeFile(file, current);
|
|
250
|
+
await writeFile(file, current, secret);
|
|
291
251
|
}
|
|
292
252
|
if (lines.length > 0) {
|
|
293
253
|
const marker = `# --- ${kind} channel ---`;
|
|
294
254
|
if (current.includes(marker)) {
|
|
295
|
-
// A marker already present (e.g. a .env copied from .env.example) — slot the new lines under it
|
|
296
|
-
//
|
|
297
|
-
await writeFile(file, current.replace(marker, `${marker}\n${lines.join("\n")}`));
|
|
255
|
+
// A marker already present (e.g. a .env copied from .env.example) — slot the new lines under it instead of
|
|
256
|
+
// orphaning them at the end of the file.
|
|
257
|
+
await writeFile(file, current.replace(marker, `${marker}\n${lines.join("\n")}`), secret);
|
|
298
258
|
}
|
|
299
259
|
else {
|
|
300
260
|
const prefix = current === "" ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
301
|
-
await appendFile(file, `${prefix}${marker}\n${lines.join("\n")}\n
|
|
261
|
+
await appendFile(file, `${prefix}${marker}\n${lines.join("\n")}\n`, secret);
|
|
302
262
|
}
|
|
303
263
|
}
|
|
304
264
|
return { written, alreadySet, unprotectedSecretsDir };
|
|
@@ -311,10 +271,7 @@ function channelPath(dir, kind) {
|
|
|
311
271
|
export async function channelExists(dir, kind) {
|
|
312
272
|
return exists(channelPath(dir, kind));
|
|
313
273
|
}
|
|
314
|
-
/**
|
|
315
|
-
* Scaffold `channels/<kind>.ts` into {@link dir}. Never clobbers an existing file (the glue is
|
|
316
|
-
* authored content). The wx write is the TOCTOU safety net behind {@link channelExists}.
|
|
317
|
-
*/
|
|
274
|
+
/** Scaffold `channels/<kind>.ts` into {@link dir}. */
|
|
318
275
|
export async function scaffoldChannel(dir, kind, options = {}) {
|
|
319
276
|
const channelsDir = join(dir, "channels");
|
|
320
277
|
// Don't write through a channels/ symlink that escapes the agent dir; one inside it is fine.
|
|
@@ -324,48 +281,29 @@ export async function scaffoldChannel(dir, kind, options = {}) {
|
|
|
324
281
|
throw new Error(`${file} already exists — edit it, or remove it to re-scaffold`);
|
|
325
282
|
}
|
|
326
283
|
await mkdir(channelsDir, { recursive: true });
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
throw new Error(`${kind} channel template header anchors are missing`);
|
|
341
|
-
const brand = kind === "feishu" ? "Feishu" : "Lark";
|
|
342
|
-
configured =
|
|
343
|
-
`${configured.slice(0, importEnd)}\n\n` +
|
|
344
|
-
`// ${brand} WebSocket long connection: the process connects OUT to the platform, so no public URL,\n` +
|
|
345
|
-
`// Verification Token, Encrypt Key, or --tunnel is needed. In Events & Callbacks choose long\n` +
|
|
346
|
-
`// connection, subscribe im.message.receive_v1, then publish the app version. Keep one process\n` +
|
|
347
|
-
`// running in production: scale-to-zero/App Sleeping would disconnect ingress.\n` +
|
|
348
|
-
configured.slice(exportAt);
|
|
349
|
-
configured = configured
|
|
350
|
-
.split("\n")
|
|
351
|
-
.filter((line) => !line.includes(`verificationToken: process.env.${prefix}_VERIFICATION_TOKEN`) &&
|
|
352
|
-
!line.includes(`encryptKey: process.env.${prefix}_ENCRYPT_KEY`))
|
|
353
|
-
.join("\n");
|
|
354
|
-
content = configured;
|
|
284
|
+
// The long-connection variant is its own template FILE, not a transform of the webhook one: a transform
|
|
285
|
+
// has to be re-taught every time the template changes shape, and a missed anchor scaffolds a channel
|
|
286
|
+
// configured for the wrong ingress with nothing failing.
|
|
287
|
+
// Which templates exist is the BUNDLE's fact, asked here rather than assumed from the kind: only
|
|
288
|
+
// feishu/lark ship a long-connection variant today, and a caller asking for one that is missing
|
|
289
|
+
// must get that sentence, not the ENOENT of a path it never named.
|
|
290
|
+
const template = options.ingress === "websocket" ? "channel.websocket.ts" : "channel.ts";
|
|
291
|
+
// CHANNEL templates only: the bundle also holds companion tools (`telegram-send.ts`), and listing
|
|
292
|
+
// one as an available scaffold sends whoever reads this line looking for an ingress that is a tool.
|
|
293
|
+
const available = channelBundleFiles(kind).filter((f) => f.startsWith("channel."));
|
|
294
|
+
if (!available.includes(template)) {
|
|
295
|
+
throw new Error(`${kind} has no ${options.ingress ?? "webhook"} scaffold (${template} is not in its bundle) — ` +
|
|
296
|
+
`available: ${available.join(", ")}`);
|
|
355
297
|
}
|
|
356
|
-
await writeFile(file,
|
|
298
|
+
await writeFile(file, channelTemplate(kind, template), { flag: "wx" });
|
|
357
299
|
return file;
|
|
358
300
|
}
|
|
359
|
-
/**
|
|
360
|
-
* The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). Unlike the channel
|
|
361
|
-
* file they are the package's, not authored glue, so they are written on EVERY add: re-running
|
|
362
|
-
* `add <kind>` is how an upgraded tool reaches an existing agent.
|
|
363
|
-
*/
|
|
301
|
+
/** The bundle's companion tools (every `.ts` beside `channel.ts` → `tools/<name>`). */
|
|
364
302
|
export async function scaffoldCompanionTools(dir, kind) {
|
|
365
303
|
const written = [];
|
|
366
304
|
for (const name of channelBundleFiles(kind)) {
|
|
367
|
-
if (name
|
|
368
|
-
continue;
|
|
305
|
+
if (name.startsWith("channel."))
|
|
306
|
+
continue; // channel.ts / channel.websocket.ts are the channel, not tools
|
|
369
307
|
const file = join(dir, "tools", name);
|
|
370
308
|
await mkdir(dirname(file), { recursive: true });
|
|
371
309
|
await writeFile(file, channelTemplate(kind, name));
|
|
@@ -374,9 +312,8 @@ export async function scaffoldCompanionTools(dir, kind) {
|
|
|
374
312
|
return written;
|
|
375
313
|
}
|
|
376
314
|
/**
|
|
377
|
-
* Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares
|
|
378
|
-
*
|
|
379
|
-
* is `init`'s job.
|
|
315
|
+
* Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares `@fastagent-sh/fastagent` (the
|
|
316
|
+
* channel file imports it).
|
|
380
317
|
*/
|
|
381
318
|
export async function assertChannelReady(dir) {
|
|
382
319
|
const pkgPath = join(dir, "package.json");
|
|
@@ -386,10 +323,9 @@ export async function assertChannelReady(dir) {
|
|
|
386
323
|
}
|
|
387
324
|
catch (e) {
|
|
388
325
|
if (e.code === "ENOENT") {
|
|
389
|
-
// `dir` is the AGENT dir, so `fastagent init` here would nest a second agent inside it
|
|
390
|
-
// right remedy is the missing manifest (a --minimal init writes none), or init in the workspace.
|
|
326
|
+
// `dir` is the AGENT dir, so `fastagent init` here would nest a second agent inside it.
|
|
391
327
|
throw new Error(`${dir}: no package.json — a channel adapter is code and needs the agent's own manifest. ` +
|
|
392
|
-
`Add a package.json declaring @fastagent-sh/fastagent there (
|
|
328
|
+
`Add a package.json declaring @fastagent-sh/fastagent there (\`init\` scaffolds one), ` +
|
|
393
329
|
`or run \`fastagent init\` in the workspace for a fresh agent`);
|
|
394
330
|
}
|
|
395
331
|
throw e;
|
package/dist/scaffold/init.d.ts
CHANGED
|
@@ -1,45 +1,23 @@
|
|
|
1
|
-
/** The agent directory name for a raw `--agent-dir` value: the default when unset, and `./bot` read as
|
|
2
|
-
* `bot` — `basename` already says that is what it means, so rejecting the spelling would be pedantry. */
|
|
1
|
+
/** The agent directory name for a raw `--agent-dir` value: the default when unset, and `./bot` read as `bot`. */
|
|
3
2
|
export declare function agentDirName(raw: string | undefined): string;
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
* one-level lookup cannot see it — an agent nothing would ever serve.
|
|
3
|
+
/**
|
|
4
|
+
* Why `name` cannot be an agent directory name, or undefined when it can.
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* `"."` is refused along with every path: the definition always lands in a SUBDIRECTORY, and the directory around it
|
|
7
|
+
* is the workspace. The subdirectory also carries its own `package.json`, so a `fastagent.config.ts` never loads under
|
|
8
|
+
* the host repo's module settings.
|
|
9
|
+
*/
|
|
11
10
|
export declare function agentDirNameError(name: string): string | undefined;
|
|
12
11
|
export interface ScaffoldOptions {
|
|
13
|
-
/**
|
|
14
|
-
minimal?: boolean;
|
|
15
|
-
/** The agent directory's name inside `dir` — default {@link DEFAULT_AGENT_DIRNAME}, `"."` for `dir`
|
|
16
|
-
* itself. ONE path segment: a separator or `..` would put the agent outside the directory the author
|
|
17
|
-
* named, where the one-level lookup could never find it. */
|
|
12
|
+
/** The agent directory's name inside `dir` — default {@link DEFAULT_AGENT_DIRNAME}. */
|
|
18
13
|
agentDir?: string;
|
|
19
14
|
}
|
|
20
15
|
export interface ScaffoldResult {
|
|
21
16
|
dir: string;
|
|
22
|
-
/** Whether a complete (code-tool) agent was scaffolded (false for --minimal). */
|
|
23
|
-
complete: boolean;
|
|
24
17
|
/** The agent dir relative to `dir`: the {@link ScaffoldOptions.agentDir} that was used. */
|
|
25
18
|
agentDir: string;
|
|
26
19
|
/** Files written by this run (relative to `dir`). */
|
|
27
20
|
created: string[];
|
|
28
|
-
/** Files that already existed and were KEPT untouched. Only reachable with `agentDir: "."` (a
|
|
29
|
-
* subdirectory target is proven empty first): adopting a directory means its `.gitignore`/
|
|
30
|
-
* `package.json` are the author's. The caller surfaces them — silently skipping a file the user
|
|
31
|
-
* expected would be worse. */
|
|
32
|
-
kept: string[];
|
|
33
21
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Scaffold a runnable agent into `<dir>/<agentDir>/` — or into `dir` itself when `agentDir` is `"."`
|
|
36
|
-
* (both created if missing). Default is a complete agent (persona.md + the writing-great-skills skill +
|
|
37
|
-
* a code tool + package.json); `--minimal` drops the code tool and package.json.
|
|
38
|
-
*
|
|
39
|
-
* A SUBDIRECTORY target must be empty (any content there is an unfinished agent or something unrelated,
|
|
40
|
-
* and landing persona.md beside it would be a silent mix), while `"."` is a directory being adopted —
|
|
41
|
-
* content is expected. So `"."` KEEPS every file that already exists (reported, never overwritten, never
|
|
42
|
-
* verified) and refuses only on a config, which means the directory is already an agent. An existing
|
|
43
|
-
* AGENTS.md is untouched either way: that is the project's context, adopted as-is.
|
|
44
|
-
*/
|
|
22
|
+
/** Scaffold a runnable agent into `<dir>/<agentDir>/` (both created if missing). */
|
|
45
23
|
export declare function scaffoldAgent(dir: string, options?: ScaffoldOptions): Promise<ScaffoldResult>;
|