@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/paths.js
CHANGED
|
@@ -1,74 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PLACEMENT: which directory holds the agent, and which directory the agent works ON — plus the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* ONE marker and ONE rule. `fastagent.config.*` declares an agent; the WORKSPACE is the directory you
|
|
6
|
-
* pointed fastagent at. Nothing here reads a directory NAME, so an agent directory can be called
|
|
7
|
-
* anything — and the same tree answers two ways depending on where you aim it: point at the project and
|
|
8
|
-
* the agent directory inside it answers with the project as its workspace; point at the agent directory
|
|
9
|
-
* itself (all a container may have been shipped) and it works on itself. That is not an ambiguity to
|
|
10
|
-
* resolve but the honest answer — an agent alone on a box has no project to work on, and a rule that
|
|
11
|
-
* insisted otherwise would hand it the container root.
|
|
12
|
-
*
|
|
13
|
-
* Engine-neutral by nature (pure fs/path: existence checks and one shallow scan), so it lives here
|
|
14
|
-
* rather than under engines/pi. Not a filing preference: the scaffold, the deploy planners, the dev
|
|
15
|
-
* watcher and env.ts all need these facts, and routing them through the engine would make neutral
|
|
16
|
-
* modules depend on it for something the engine has no say in.
|
|
2
|
+
* PLACEMENT: which directory holds the agent, and which directory the agent works ON — plus the machinery paths that
|
|
3
|
+
* follow from it.
|
|
17
4
|
*/
|
|
18
|
-
import {
|
|
19
|
-
import { access,
|
|
5
|
+
import { readdirSync, statSync } from "node:fs";
|
|
6
|
+
import { access, readFile, realpath } from "node:fs/promises";
|
|
20
7
|
import { homedir } from "node:os";
|
|
21
8
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
22
|
-
/**
|
|
23
|
-
* The directory name `init` gives a nested agent (`<workspace>/fastagent/`) unless `--agent-dir` names
|
|
24
|
-
* another. A DEFAULT, not a rule: resolution reads the config marker and never a name, so renaming an
|
|
25
|
-
* agent directory changes nothing about how it resolves. Visible on purpose — the agent directory holds
|
|
26
|
-
* the AUTHOR's content (persona, skills, tool code: code, not tool configuration), so it follows the
|
|
27
|
-
* repo convention for code (a plain directory), while fastagent's own machinery inside it (`.secrets/`,
|
|
28
|
-
* `.state/`) keeps the dot prefix.
|
|
29
|
-
*/
|
|
9
|
+
/** The directory name `init` gives an agent (`<workspace>/fastagent/`) unless `--agent-dir` names another. */
|
|
30
10
|
export const DEFAULT_AGENT_DIRNAME = "fastagent";
|
|
31
|
-
/** The user-global machinery home under `$HOME
|
|
32
|
-
* tool homes (`~/.cargo`, `~/.docker`); unrelated to {@link DEFAULT_AGENT_DIRNAME}, which only names
|
|
33
|
-
* what `init` creates.
|
|
34
|
-
* It carries the same shape inside it as an agent dir does (`~/.fastagent/.secrets/auth.json`), so the
|
|
35
|
-
* resolvers below need no special case: `login` outside any agent simply hands them this directory. */
|
|
11
|
+
/** The user-global machinery home under `$HOME`. */
|
|
36
12
|
export const GLOBAL_HOME_DIR = ".fastagent";
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
13
|
+
/**
|
|
14
|
+
* The secrets segment inside an agent dir (or the global home). Every path fastagent resolves — `.env`,
|
|
15
|
+
* `.env.example`, auth.json, the scaffold's write — derives from it, so they cannot drift apart.
|
|
16
|
+
* `FASTAGENT_SECRETS_DIR` relocates the RESOLVED dir ({@link resolveSecretsDir}), never this name; the scaffolded
|
|
17
|
+
* ignore templates spell it out as literal text, so renaming this constant means editing them too.
|
|
18
|
+
*/
|
|
42
19
|
export const SECRETS_DIRNAME = ".secrets";
|
|
43
20
|
/** The state segment inside an agent dir — same rule and same template caveat as {@link SECRETS_DIRNAME}. */
|
|
44
21
|
export const STATE_DIRNAME = ".state";
|
|
45
|
-
/**
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* scope must not silently drift from what the worker actually loads. */
|
|
22
|
+
/**
|
|
23
|
+
* THE config filename. One spelling, not a family: fastagent generates this file, so a choice of extension buys
|
|
24
|
+
* an author nothing and costs a precedence order plus a "you have two of them" failure path. Everything else an
|
|
25
|
+
* author writes (`tools/`, `channels/`, `routines/`) still accepts `.ts`/`.js`/`.mjs` — that is THEIR code.
|
|
26
|
+
*/
|
|
27
|
+
export const AGENT_CONFIG_FILE = "fastagent.config.ts";
|
|
28
|
+
/** The optional custom-model-endpoint file inside an agent dir (pi's models.json schema). */
|
|
53
29
|
export const AGENT_MODELS_FILE = "models.json";
|
|
54
|
-
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
const LOADED_SURFACE = ["persona.md", "skills", "tools", "channels", "
|
|
30
|
+
/**
|
|
31
|
+
* The definition paths an agent LOADS content from — the surface a second agent must not be scaffolded inside
|
|
32
|
+
* ({@link agentDefinitionOwner}), because the outer agent would read it as its own skills/tools.
|
|
33
|
+
*/
|
|
34
|
+
const LOADED_SURFACE = ["persona.md", "skills", "tools", "channels", "routines"];
|
|
35
|
+
/**
|
|
36
|
+
* "Not there" and "could not look" are different answers, and only the first may read as an absence: an agent
|
|
37
|
+
* directory the caller cannot enter (EACCES) reported as no-agent-here ends in `run \`fastagent init\` to
|
|
38
|
+
* scaffold one` — over a definition that exists. So ENOENT/ENOTDIR are absence, and every other errno travels
|
|
39
|
+
* with its path, as {@link agentChildren} already did for the scan itself.
|
|
40
|
+
*/
|
|
41
|
+
function statOrAbsent(p) {
|
|
42
|
+
try {
|
|
43
|
+
return statSync(p);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
const code = e.code;
|
|
47
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
48
|
+
return undefined;
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
59
52
|
function isDir(p) {
|
|
60
|
-
return
|
|
53
|
+
return statOrAbsent(p)?.isDirectory() === true;
|
|
61
54
|
}
|
|
62
|
-
/** THE marker: a directory that declares itself an agent with a
|
|
63
|
-
* it, at every position — which is why an agent directory needs no reserved name and why there is no
|
|
64
|
-
* zero-config agent. */
|
|
55
|
+
/** THE marker: a directory that declares itself an agent with a {@link AGENT_CONFIG_FILE}. */
|
|
65
56
|
function hasConfig(p) {
|
|
66
|
-
return isDir(p) &&
|
|
57
|
+
return isDir(p) && statOrAbsent(join(p, AGENT_CONFIG_FILE)) !== undefined;
|
|
67
58
|
}
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
59
|
+
/**
|
|
60
|
+
* DIRECTLY inside `dir`: the agent directories, and the ones the answer is missing for.
|
|
61
|
+
*
|
|
62
|
+
* A NAMED directory and a SCANNED one are different questions. `hasConfig` throws for the first, because the
|
|
63
|
+
* caller pointed at it. Here it cannot: every machine has directories this process may not enter, and one of
|
|
64
|
+
* them sitting next to the agent must not fail a command that found the agent — `workspaceHint` scans the
|
|
65
|
+
* PARENT for a hint, which on a CI runner means scanning `/tmp` and its `snap-private-tmp`. So an unreadable
|
|
66
|
+
* candidate is carried, not thrown, and {@link resolvePlacement} spends it where it is the answer: a caller
|
|
67
|
+
* that must produce an agent and found none says "I could not look at these", never `fastagent init`.
|
|
68
|
+
*/
|
|
72
69
|
function agentChildren(dir) {
|
|
73
70
|
let entries;
|
|
74
71
|
try {
|
|
@@ -77,44 +74,41 @@ function agentChildren(dir) {
|
|
|
77
74
|
catch (e) {
|
|
78
75
|
const code = e.code;
|
|
79
76
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
80
|
-
return [];
|
|
77
|
+
return { agents: [], unreadable: [] };
|
|
81
78
|
throw e;
|
|
82
79
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
.
|
|
80
|
+
const agents = [];
|
|
81
|
+
const unreadable = [];
|
|
82
|
+
for (const entry of entries.filter((e) => !e.isFile())) {
|
|
83
|
+
const child = join(dir, entry.name);
|
|
84
|
+
try {
|
|
85
|
+
if (hasConfig(child))
|
|
86
|
+
agents.push(child);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
// PERMISSION ONLY. "Someone else's 0700 directory sits next to mine" is the normal case this carrying
|
|
90
|
+
// exists for, and it is the one an operator can act on. EIO, ELOOP, a non-errno throw: those are real
|
|
91
|
+
// failures with different fixes, and a scan has no business turning them into a line of prose.
|
|
92
|
+
const code = e.code;
|
|
93
|
+
if (code !== "EACCES" && code !== "EPERM")
|
|
94
|
+
throw e;
|
|
95
|
+
unreadable.push(entry.name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { agents: agents.sort(), unreadable: unreadable.sort() };
|
|
87
99
|
}
|
|
88
100
|
/**
|
|
89
|
-
* The agents `dir` resolves over: ITSELF when it holds a config, else the ones directly inside it —
|
|
90
|
-
*
|
|
91
|
-
* same question this lookup asks: which agents would `dir` resolve over, before and after scaffolding.
|
|
101
|
+
* The agents `dir` resolves over: ITSELF when it holds a config, else the ones directly inside it — never both,
|
|
102
|
+
* because aiming at an agent can only mean that agent.
|
|
92
103
|
*/
|
|
93
104
|
export function agentsAt(dir) {
|
|
94
|
-
|
|
95
|
-
return hasConfig(base) ? [base] : agentChildren(base);
|
|
105
|
+
return scanAgents(resolve(dir)).agents;
|
|
96
106
|
}
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* reads the agent's `.env` — it is what tells the loader which agent's `.env` to read — so a value set
|
|
103
|
-
* there is a silent no-op (the same ordering caveat `FASTAGENT_SECRETS_DIR` carries). A shell, an
|
|
104
|
-
* `.envrc`, or the command itself.
|
|
105
|
-
*
|
|
106
|
-
* It is an ASSERTION, not a preference: it names the agent, and a directory without one
|
|
107
|
-
* by that name resolves to nothing — even when exactly one agent is sitting there. Serving a DIFFERENT
|
|
108
|
-
* agent than the one asked for is the silent-wrong-target this codebase refuses everywhere else, and the
|
|
109
|
-
* uniform rule ("it names the agent") beats one that changes meaning with the sibling count. The stated
|
|
110
|
-
* cost: a `FASTAGENT_AGENT` exported in a shell PROFILE refuses in every unrelated directory it travels
|
|
111
|
-
* into. Scope it per-repo (an `.envrc`) or pass it per-command; the refusal names the way out.
|
|
112
|
-
*
|
|
113
|
-
* With nothing asserted, the {@link DEFAULT_AGENT_DIRNAME} breaks the tie. That is the one place a
|
|
114
|
-
* directory NAME carries weight, and deliberately not an identity rule — the config alone says what IS
|
|
115
|
-
* an agent; the name only decides which already-identified one answers. What it buys: adding a second
|
|
116
|
-
* agent to a working `<workspace>/fastagent/` setup does not break the command everyone already types.
|
|
117
|
-
*/
|
|
107
|
+
/** {@link agentsAt} plus what the scan could not look at — one readdir answering both questions. */
|
|
108
|
+
function scanAgents(base) {
|
|
109
|
+
return hasConfig(base) ? { agents: [base], unreadable: [] } : agentChildren(base);
|
|
110
|
+
}
|
|
111
|
+
/** WHICH agent is meant, when a directory holds several. */
|
|
118
112
|
function selectAgent(agents, env) {
|
|
119
113
|
const wanted = env.FASTAGENT_AGENT;
|
|
120
114
|
if (wanted)
|
|
@@ -122,59 +116,16 @@ function selectAgent(agents, env) {
|
|
|
122
116
|
const [only, ...rest] = agents;
|
|
123
117
|
return rest.length === 0 ? only : agents.find((a) => basename(a) === DEFAULT_AGENT_DIRNAME);
|
|
124
118
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Resolve `dir` into its placement, or undefined when nothing selects one agent. The whole rule: `dir`
|
|
127
|
-
* is the WORKSPACE, and the agent is a `fastagent.config.*` holder at it or one level inside
|
|
128
|
-
* ({@link agentsAt}), narrowed by {@link selectAgent} when there are several.
|
|
129
|
-
*/
|
|
119
|
+
/** Resolve `dir` into its placement, or undefined when nothing selects one agent. */
|
|
130
120
|
function findPlacement(dir, env = process.env) {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
return agentDir === undefined ? undefined : { agentDir, workspace: base };
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* The one-line hint for "you pointed at the agent, but the project around it is what you meant" — or
|
|
137
|
-
* undefined. The workspace being whatever you aimed at is deliberate (a deployed box may hold nothing
|
|
138
|
-
* but the agent), and the cost is that `cd my-agent && fastagent dev` legitimately narrows the agent's
|
|
139
|
-
* WORKSPACE to its own directory: its cwd, its coding tools' root, and deploy's build context. (②
|
|
140
|
-
* context is not affected — that walk climbs ancestors either way.) Resolution must not guess which you
|
|
141
|
-
* wanted, so this is a HINT — and a hint may use the heuristic ("the parent carries an AGENTS.md or a
|
|
142
|
-
* .git") that a rule may not.
|
|
143
|
-
*
|
|
144
|
-
* It suggests a command only after RUNNING the lookup that command would run, because a hint that dead-ends
|
|
145
|
-
* is worse than none: with several agents beside this one, `..` refuses and names them — sending the
|
|
146
|
-
* reader to a refusal whose own advice points back here.
|
|
147
|
-
*/
|
|
148
|
-
export function workspaceHint({ agentDir, workspace }, env = process.env) {
|
|
149
|
-
if (agentDir !== workspace)
|
|
150
|
-
return undefined;
|
|
151
|
-
const parent = dirname(agentDir);
|
|
152
|
-
if (parent === agentDir)
|
|
153
|
-
return undefined;
|
|
154
|
-
// Would `..` actually serve THIS agent? A parent holding a config is an agent itself, and one holding
|
|
155
|
-
// siblings resolves to none without a selector — neither is the project view asked about here.
|
|
156
|
-
if (findPlacement(parent, env)?.agentDir !== agentDir)
|
|
157
|
-
return undefined;
|
|
158
|
-
if (!["AGENTS.md", ".git"].some((name) => existsSync(join(parent, name))))
|
|
159
|
-
return undefined;
|
|
160
|
-
return `${parent} looks like a project — point fastagent at it (\`..\`) to have the agent work ON it`;
|
|
121
|
+
const agentDir = selectAgent(agentsAt(resolve(dir)), env);
|
|
122
|
+
return agentDir === undefined ? undefined : { agentDir, workspace: dirname(agentDir) };
|
|
161
123
|
}
|
|
162
|
-
/** The agent dir for `dir`, or undefined when there is none — {@link findPlacement} without the pair
|
|
163
|
-
* or the throw. Login and standalone channel senders also operate outside configured agents, so
|
|
164
|
-
* they need absence as a value. Filesystem errors still propagate. */
|
|
124
|
+
/** The agent dir for `dir`, or undefined when there is none — {@link findPlacement} without the pair or the throw. */
|
|
165
125
|
export function findAgentDir(dir) {
|
|
166
126
|
return findPlacement(dir)?.agentDir;
|
|
167
127
|
}
|
|
168
|
-
/** The agent `dir` sits INSIDE (the nearest proper ancestor that IS an agent dir), or undefined.
|
|
169
|
-
* Module-private: the two questions callers actually ask are "where do I `cd` to?"
|
|
170
|
-
* ({@link placementDeadEnd}) and "would a new agent here become part of an existing one's definition?"
|
|
171
|
-
* ({@link agentDefinitionOwner}) — and those answers differ (an agent's `src/` is inside it, but is not
|
|
172
|
-
* part of what it loads), so exporting the raw containment fact would invite conflating them.
|
|
173
|
-
* Placement resolution deliberately never walks up — the answer must not depend on how deep you stand
|
|
174
|
-
* — but "you are inside an agent, just not at its root" is the likeliest reason resolution fails, and
|
|
175
|
-
* both the refusal below and `login`'s global-fallback decision need to tell that case apart. Uses the
|
|
176
|
-
* same marker as resolution (an ancestor holding a config), so it never claims a position it cannot
|
|
177
|
-
* justify. */
|
|
128
|
+
/** The agent `dir` sits INSIDE (the nearest proper ancestor that IS an agent dir), or undefined. */
|
|
178
129
|
function enclosingAgentDir(dir) {
|
|
179
130
|
let candidate = dirname(resolve(dir));
|
|
180
131
|
for (let prev = ""; candidate !== prev; prev = candidate, candidate = dirname(candidate)) {
|
|
@@ -184,12 +135,8 @@ function enclosingAgentDir(dir) {
|
|
|
184
135
|
return undefined;
|
|
185
136
|
}
|
|
186
137
|
/**
|
|
187
|
-
* The agent whose DEFINITION contains `dir` — scaffolding there would make the new agent part of the
|
|
188
|
-
*
|
|
189
|
-
* purpose: an agent owns only what it LOADS ({@link LOADED_SURFACE}); the rest of its directory is the
|
|
190
|
-
* author's tree, where a second agent (a monorepo package, say) is a legitimate thing to create.
|
|
191
|
-
* `enclosingAgentDir` answers a different question ("where do I `cd` to?"), and for that an agent's
|
|
192
|
-
* `src/` genuinely IS inside it.
|
|
138
|
+
* The agent whose DEFINITION contains `dir` — scaffolding there would make the new agent part of the outer one's
|
|
139
|
+
* loaded surface rather than an agent of its own.
|
|
193
140
|
*/
|
|
194
141
|
export function agentDefinitionOwner(dir) {
|
|
195
142
|
const base = resolve(dir);
|
|
@@ -199,15 +146,7 @@ export function agentDefinitionOwner(dir) {
|
|
|
199
146
|
const [head] = relative(agent, base).split(sep);
|
|
200
147
|
return head && LOADED_SURFACE.includes(head) ? agent : undefined;
|
|
201
148
|
}
|
|
202
|
-
/**
|
|
203
|
-
* Why `dir` is not an agent, when it has its OWN way out — or undefined when it is simply not near one.
|
|
204
|
-
* Two positions qualify: standing INSIDE an agent (its `tools/`, its `src/`), and standing on a
|
|
205
|
-
* directory whose several agents nothing selects between. Both matter because the generic advice ("run
|
|
206
|
-
* `fastagent init`") would not help — the agent already exists, one step away.
|
|
207
|
-
*
|
|
208
|
-
* Exported because `login` is the one command allowed to run outside an agent, and it must tell "truly
|
|
209
|
-
* outside" (→ the global credential) from "a dead end" (→ refuse, like every other command).
|
|
210
|
-
*/
|
|
149
|
+
/** Why `dir` is not an agent, when it has its OWN way out — or undefined when it is simply not near one. */
|
|
211
150
|
export function placementDeadEnd(dir, env = process.env) {
|
|
212
151
|
const base = resolve(dir);
|
|
213
152
|
const enclosing = enclosingAgentDir(base);
|
|
@@ -216,60 +155,72 @@ export function placementDeadEnd(dir, env = process.env) {
|
|
|
216
155
|
}
|
|
217
156
|
const agents = agentsAt(base).map((a) => basename(a));
|
|
218
157
|
const listed = `${base} holds ${agents.length} agent${agents.length === 1 ? "" : "s"} (${agents.join(", ")})`;
|
|
219
|
-
// Asserting a name that is not here is a different mistake from asserting none, and it is worth its own
|
|
220
|
-
//
|
|
221
|
-
// somewhere else), so echo it back with what is actually here instead of restating the general rule.
|
|
158
|
+
// Asserting a name that is not here is a different mistake from asserting none, and it is worth its own message at
|
|
159
|
+
// ANY count.
|
|
222
160
|
if (env.FASTAGENT_AGENT && agents.length > 0) {
|
|
223
161
|
return (`${listed}, and FASTAGENT_AGENT asserts "${env.FASTAGENT_AGENT}", which is not one of them — set it ` +
|
|
224
162
|
`to one of those, unset it, or scope it to the repository that needs it (an .envrc)`);
|
|
225
163
|
}
|
|
226
164
|
if (agents.length > 1) {
|
|
227
165
|
return (`${listed} and none of them is named "${DEFAULT_AGENT_DIRNAME}" (the default) — pick one with ` +
|
|
228
|
-
`FASTAGENT_AGENT=<name>, or point fastagent at the one you want
|
|
166
|
+
`FASTAGENT_AGENT=<name>, or point fastagent at the one you want`);
|
|
229
167
|
}
|
|
230
168
|
return undefined;
|
|
231
169
|
}
|
|
232
170
|
/**
|
|
233
|
-
* Resolve a directory into its placement — the ONE owner of the rule ({@link findPlacement})
|
|
234
|
-
* the workspace, and the agent is the single `fastagent.config.*` holder at it or one level inside.
|
|
235
|
-
* Placement is never configured and never detected from surroundings; it is that lookup and the
|
|
236
|
-
* directory you pointed at.
|
|
171
|
+
* Resolve a directory into its placement — the ONE owner of the rule ({@link findPlacement}).
|
|
237
172
|
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
173
|
+
* WHERE AN UNREADABLE CANDIDATE IS SPENT, and only here: this caller MUST produce an agent, so a directory it
|
|
174
|
+
* could not look into is the difference between "there is none" and "there might be". `placementDeadEnd` is
|
|
175
|
+
* asked a weaker question — is this place a dead end with its own way out — by callers like `login`, for which
|
|
176
|
+
* a neighbour it may not enter is environment noise, not a reason to exit instead of logging in globally. It
|
|
177
|
+
* also comes AFTER that call: with two agents to choose between, a permission problem in a third directory is
|
|
178
|
+
* not the message that gets the caller moving.
|
|
241
179
|
*/
|
|
242
180
|
export function resolvePlacement(dir, env = process.env) {
|
|
243
181
|
const placement = findPlacement(dir, env);
|
|
244
182
|
if (!placement) {
|
|
245
183
|
const base = resolve(dir);
|
|
246
|
-
throw new Error(placementDeadEnd(base, env) ??
|
|
247
|
-
`${base} is not a fastagent agent — no fastagent.config.* here, and no directory holding one ` +
|
|
248
|
-
`directly inside; run \`fastagent init\` to scaffold one`);
|
|
184
|
+
throw new Error(placementDeadEnd(base, env) ?? `${base} is not a fastagent agent${noAgentHere(base)}`);
|
|
249
185
|
}
|
|
250
186
|
return placement;
|
|
251
187
|
}
|
|
252
|
-
/**
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
*
|
|
188
|
+
/** At most this many names before the rest become a count — `/tmp` on a shared machine has plenty. */
|
|
189
|
+
const LISTED_UNREADABLE = 3;
|
|
190
|
+
/**
|
|
191
|
+
* The rest of the "not an agent" refusal: the way out, plus what could not be looked at.
|
|
192
|
+
*
|
|
193
|
+
* BOTH, never one or the other. `fastagent init` is the only line that tells the caller what to do, and a
|
|
194
|
+
* directory it cannot enter is exactly why that advice can be wrong (#571: scaffolding over a definition that
|
|
195
|
+
* is there but invisible). Substituting the second for the first sends anyone running in `/tmp`, or on a
|
|
196
|
+
* shared machine, to fix permissions on directories that were never theirs and hold no agent.
|
|
197
|
+
*/
|
|
198
|
+
function noAgentHere(base) {
|
|
199
|
+
const { unreadable } = scanAgents(base);
|
|
200
|
+
const noConfig = ` — no fastagent.config.ts here, and no readable directory holding one directly inside`;
|
|
201
|
+
const scaffold = "run `fastagent init` to scaffold one";
|
|
202
|
+
if (unreadable.length === 0)
|
|
203
|
+
return `${noConfig}; ${scaffold}`;
|
|
204
|
+
const shown = unreadable.slice(0, LISTED_UNREADABLE).join(", ");
|
|
205
|
+
const rest = unreadable.length - LISTED_UNREADABLE;
|
|
206
|
+
// The unknown FIRST, because it is the one thing that can make the advice wrong.
|
|
207
|
+
return (`${noConfig}. ${unreadable.length} director${unreadable.length === 1 ? "y" : "ies"} here could not be ` +
|
|
208
|
+
`read (permission), so an agent may be inside one: ${shown}${rest > 0 ? `, +${rest} more` : ""}. Check ` +
|
|
209
|
+
`those first; ${scaffold} only if none of them holds an agent`);
|
|
210
|
+
}
|
|
211
|
+
/** How to WRITE a path for someone standing in `cwd`. */
|
|
256
212
|
export function displayPath(cwd, dir) {
|
|
257
213
|
const rel = relative(cwd, dir);
|
|
258
214
|
if (rel === "")
|
|
259
215
|
return undefined;
|
|
260
|
-
// "Climbs out" is a path-SEGMENT check — rel is ".." or starts with "../" (or "..\" on Windows).
|
|
261
|
-
// bare startsWith("..") would wrongly flag an in-cwd directory literally named "..agent".
|
|
216
|
+
// "Climbs out" is a path-SEGMENT check — rel is ".." or starts with "../" (or "..\" on Windows).
|
|
262
217
|
const escapes = rel === ".." || /^\.\.[/\\]/.test(rel);
|
|
263
218
|
return escapes ? dir : rel;
|
|
264
219
|
}
|
|
265
|
-
/** Does a path exist? Plain fs, no placement in it — it lives here because `paths.ts` is where the
|
|
266
|
-
* neutral path helpers are, and the scaffolder is not a utility home for the CLI and deploy. */
|
|
267
220
|
export async function exists(p) {
|
|
268
221
|
return access(p).then(() => true, () => false);
|
|
269
222
|
}
|
|
270
|
-
/** The file's text, or undefined when there is no file.
|
|
271
|
-
* exists but cannot be read (EACCES, a directory in its place) throws, because a decision made on
|
|
272
|
-
* "not there" — regenerate it, skip its gate — is the wrong one for a file that is there. */
|
|
223
|
+
/** The file's text, or undefined when there is no file. */
|
|
273
224
|
export async function readTextIfExists(p) {
|
|
274
225
|
try {
|
|
275
226
|
return await readFile(p, "utf8");
|
|
@@ -281,11 +232,8 @@ export async function readTextIfExists(p) {
|
|
|
281
232
|
}
|
|
282
233
|
}
|
|
283
234
|
/**
|
|
284
|
-
* Resolve a user-supplied path override (a CLI flag or an env var) to an absolute path, expanding a
|
|
285
|
-
*
|
|
286
|
-
* never gets the shell's `~` expansion, so a bare `resolve("~/x")` would silently create a literal `~`
|
|
287
|
-
* directory — a fail-silently footgun for a secret/state path. Expanding here makes `~` mean home
|
|
288
|
-
* everywhere these knobs are read.
|
|
235
|
+
* Resolve a user-supplied path override (a CLI flag or an env var) to an absolute path, expanding a leading `~`/`~/`
|
|
236
|
+
* to the home dir FIRST.
|
|
289
237
|
*/
|
|
290
238
|
export function resolveOverridePath(raw) {
|
|
291
239
|
if (!raw)
|
|
@@ -294,64 +242,54 @@ export function resolveOverridePath(raw) {
|
|
|
294
242
|
return resolve(expanded);
|
|
295
243
|
}
|
|
296
244
|
/**
|
|
297
|
-
* The resolved state root — the durable machine-state home (sessions/, channels/<kind>/, schedule
|
|
298
|
-
*
|
|
299
|
-
* startup report agree regardless of cwd. Definition: mutable runtime state — single lifecycle
|
|
300
|
-
* (precious, survives redeploy), single process; a container points this at its mounted volume.
|
|
301
|
-
* Secrets are NOT here — they live under {@link resolveSecretsDir} (a different deploy lifecycle:
|
|
302
|
-
* secret store vs volume). The finer knob (`FASTAGENT_SESSIONS_DIR`) still overrides its path on top.
|
|
303
|
-
*
|
|
304
|
-
* `FASTAGENT_STATE_DIR` is an OPERATOR override, so a relative value resolves against `process.cwd()`
|
|
305
|
-
* — the CLI convention its sibling knobs share (`resolveOverridePath`), NOT against `dir`. Only the
|
|
306
|
-
* DEFAULT (`<root>/.state`) is dir-anchored.
|
|
245
|
+
* The resolved state root — the durable machine-state home (sessions/, channels/<kind>/, schedule/):
|
|
246
|
+
* `FASTAGENT_STATE_DIR` env > `<agentDir>/.state`.
|
|
307
247
|
*/
|
|
308
248
|
export function resolveStateRoot(dir, env = process.env) {
|
|
309
249
|
return resolveOverridePath(env.FASTAGENT_STATE_DIR) ?? join(resolve(dir), STATE_DIRNAME);
|
|
310
250
|
}
|
|
311
251
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
252
|
+
* WHERE an agent's session records live: `<state root>/sessions`, always. Records are machine state like channel
|
|
253
|
+
* state and schedule claims, so they move with the ONE knob that moves all of it rather than with a second one that
|
|
254
|
+
* moves only them — a split every reader would then have to agree about, and which left channel state behind anyway.
|
|
255
|
+
* An embedder that really wants the records elsewhere passes `sessionsDir` to `createAgentService`; there is no env
|
|
256
|
+
* or flag spelling of it, for the reason `resolveAuthPathOverride` states.
|
|
257
|
+
*/
|
|
258
|
+
export function resolveSessionsDir(dir, env = process.env) {
|
|
259
|
+
return join(resolveStateRoot(dir, env), "sessions");
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The resolved secrets dir — everything fastagent manages that must NEVER leave the machine (the agent's `.env` +
|
|
263
|
+
* auth.json).
|
|
320
264
|
*/
|
|
321
265
|
export function resolveSecretsDir(dir, env = process.env) {
|
|
322
266
|
return resolveOverridePath(env.FASTAGENT_SECRETS_DIR) ?? join(resolve(dir), SECRETS_DIRNAME);
|
|
323
267
|
}
|
|
324
|
-
/**
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
268
|
+
/** Is this process the deployed container the generated artifacts describe? ONE reading of the
|
|
269
|
+
* environment fact the image states, so a second spelling cannot drift from it. */
|
|
270
|
+
export function isDeployedWorkspace() {
|
|
271
|
+
return Boolean(process.env.FASTAGENT_RELEASE_FILE);
|
|
272
|
+
}
|
|
273
|
+
/** Is this process running inside the AgentCore Runtime? Set by the generated deploy artifacts. */
|
|
274
|
+
export function isAgentcoreRuntime() {
|
|
275
|
+
return process.env.FASTAGENT_AGENTCORE === "1";
|
|
276
|
+
}
|
|
328
277
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
278
|
+
* The mode of a file fastagent CREATES to hold a secret: `auth.json`, a `.env` it makes itself,
|
|
279
|
+
* a host's parameter file. That is the whole extent of its opinion about permissions — a file it did not create
|
|
280
|
+
* keeps the mode its owner gave it, and no directory's mode is ever decided or repaired. Every comparable tool
|
|
281
|
+
* draws the line here: Rails chmods the `master.key` it generates and leaves `config/` alone
|
|
282
|
+
* (rails/rails@4c6c357), the aws CLI ships a 0600 `~/.aws/config` inside a 0755 `~/.aws`.
|
|
331
283
|
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* writer gets there first — `mkdir`'s `mode` is ignored for a directory that already exists, so a
|
|
335
|
-
* later, more careful caller silently inherits the first one's answer. Four callers create this
|
|
336
|
-
* directory (`init`, `add <channel>`, the credential store, the deploy seed) and the ordinary order
|
|
337
|
-
* is init → add → login, so the careful one is LAST: the rule has to live where all of them can
|
|
338
|
-
* reach it, and it has to chmod rather than trust the create.
|
|
284
|
+
* "Who created the file" is the load-bearing half: asking instead whether a given WRITE carries a secret has a
|
|
285
|
+
* different answer at every call site and one more with each new writer.
|
|
339
286
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*/
|
|
344
|
-
export async function ensureSecretsDir(dir) {
|
|
345
|
-
await mkdir(dir, { recursive: true, mode: SECRETS_DIR_MODE });
|
|
346
|
-
await chmod(dir, SECRETS_DIR_MODE).catch((e) => {
|
|
347
|
-
throw new Error(`cannot secure secrets dir ${dir} (fastagent keeps it 0700): ${e.message} — point --auth-path/FASTAGENT_SECRETS_DIR at a directory this process owns`, { cause: e });
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Guard that `<agentDir>/<name>` resolves INSIDE the agent dir — a symlink that escapes (or an
|
|
352
|
-
* absolute target) is rejected, so discovery/scaffolding never reaches out of the definition directory.
|
|
353
|
-
* A missing target is fine (nothing to guard yet).
|
|
287
|
+
* One consequence worth knowing: `writeFileAtomic` sets the mode on the temp file it renames into place, so a
|
|
288
|
+
* file it owns end-to-end (`auth.json`) is 0600 after every write, including one an operator had
|
|
289
|
+
* placed by hand. Appending to a file fastagent did not create (`.env`) cannot and does not do that.
|
|
354
290
|
*/
|
|
291
|
+
export const SECRET_FILE_MODE = 0o600;
|
|
292
|
+
/** Guard that `<agentDir>/<name>` resolves INSIDE the agent dir. */
|
|
355
293
|
export async function assertInsideAgentDir(agentDir, name) {
|
|
356
294
|
const target = join(agentDir, name);
|
|
357
295
|
const real = await realpath(target).catch((e) => {
|
|
@@ -368,12 +306,7 @@ export async function assertInsideAgentDir(agentDir, name) {
|
|
|
368
306
|
`use a real directory or a symlink that stays within it`);
|
|
369
307
|
}
|
|
370
308
|
}
|
|
371
|
-
/**
|
|
372
|
-
* Whether `targetPath` lives inside `baseDir` (same path counts). Used to ask "did an override move
|
|
373
|
-
* this OUT of the agent?" — the startup report's redeploy notes, `add`'s printed `.env` label, and the
|
|
374
|
-
* dev watcher's "your .env is not watched" warning all turn on that fact. Reporting only: fastagent
|
|
375
|
-
* does not act on where a user's paths point.
|
|
376
|
-
*/
|
|
309
|
+
/** Whether `targetPath` lives inside `baseDir` (same path counts). */
|
|
377
310
|
export function isUnderDir(targetPath, baseDir) {
|
|
378
311
|
const rel = relative(baseDir, targetPath);
|
|
379
312
|
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
package/dist/pi.d.ts
CHANGED
|
@@ -3,13 +3,9 @@ export { defineTool, type DefineToolOptions, type FastagentTool, type MountedToo
|
|
|
3
3
|
export type { ReadonlySessionManager, ToolActivation } from "./engines/pi/tool-context.ts";
|
|
4
4
|
export { z } from "zod";
|
|
5
5
|
export type { AgentTool, ExecutionEnv, Skill, SkillDiagnostic } from "@earendil-works/pi-agent-core";
|
|
6
|
-
/**
|
|
7
|
-
* A conversation record, as the tool runtime and the control plane hold it. Its entries are pi's own
|
|
8
|
-
* — exported under a qualified name because `SessionEntry` in this package is the NEUTRAL one the
|
|
9
|
-
* control plane publishes (session.ts), and the two are different shapes.
|
|
10
|
-
*/
|
|
6
|
+
/** A conversation record, as the tool runtime and the control plane hold it. */
|
|
11
7
|
export type { SessionManager, SessionEntry as PiSessionEntry } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
export { createPiAgentFromDir, type CreatePiAgentFromDirOptions, } from "./engines/pi/open.ts";
|
|
8
|
+
export { availableModelsFromDir, createPiAgentFromDir, type CreatePiAgentFromDirOptions, } from "./engines/pi/open.ts";
|
|
13
9
|
export type { LoadedDefinition, SkillCollision } from "./engines/pi/definition.ts";
|
|
14
10
|
export { defineConfig, listModels, resolveModel, type FastagentConfig, } from "./engines/pi/config.ts";
|
|
15
11
|
export type { SessionObserver } from "./engines/pi/turn-kit.ts";
|
|
@@ -19,6 +15,8 @@ export { piInMemorySessionRecordStore, piSessionRecordStore, type PiSessionRecor
|
|
|
19
15
|
export type { SessionInheritance } from "./engines/pi/session-inheritance.ts";
|
|
20
16
|
export { GLOBAL_AUTH_PATH, fastagentCredentialStore, type FastagentAuthOptions } from "./engines/pi/auth.ts";
|
|
21
17
|
export { createPiModels, probeAuthSource, type CreatePiModelsOptions } from "./engines/pi/models.ts";
|
|
18
|
+
export { login, loginOptions, LoginCancelled, type LoginMethod, type LoginOption, type LoginRequest, type LoginResult, } from "./engines/pi/login.ts";
|
|
19
|
+
export type { AuthEvent, AuthInteraction, AuthPrompt } from "@earendil-works/pi-ai";
|
|
22
20
|
export type { Models } from "@earendil-works/pi-ai";
|
|
23
21
|
export type { Provider, ProviderAuth } from "@earendil-works/pi-ai";
|
|
24
22
|
export type { Model } from "@earendil-works/pi-ai";
|
package/dist/pi.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
export { createPiAgent, createPiAgentFromDefinition, } from "./engines/pi/create.js";
|
|
3
3
|
export { defineTool, } from "./engines/pi/tool.js";
|
|
4
4
|
export { z } from "zod";
|
|
5
|
-
export { createPiAgentFromDir, } from "./engines/pi/open.js";
|
|
5
|
+
export { availableModelsFromDir, createPiAgentFromDir, } from "./engines/pi/open.js";
|
|
6
6
|
export { defineConfig, listModels, resolveModel, } from "./engines/pi/config.js";
|
|
7
7
|
export { inProcessLease } from "./engines/pi/turn-kit.js";
|
|
8
8
|
export { createPiSessionControl, } from "./engines/pi/session-control.js";
|
|
9
9
|
export { piInMemorySessionRecordStore, piSessionRecordStore, } from "./engines/pi/session-store.js";
|
|
10
10
|
export { GLOBAL_AUTH_PATH, fastagentCredentialStore } from "./engines/pi/auth.js";
|
|
11
11
|
export { createPiModels, probeAuthSource } from "./engines/pi/models.js";
|
|
12
|
-
|
|
13
|
-
//
|
|
12
|
+
export { login, loginOptions, LoginCancelled, } from "./engines/pi/login.js";
|
|
13
|
+
// The product's one-call assembly: a directory becomes a live service.
|
|
14
14
|
export { createAgentService } from "./engines/pi/service.js";
|
package/dist/proxy.d.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
* The exemption list the dispatcher is built with: whatever the environment declares, plus {@link LOOPBACK}.
|
|
3
|
+
*
|
|
4
|
+
* Its own function because installing happens ONCE per process, so these branches are unreachable through
|
|
5
|
+
* {@link installProxyFetch} — and the wildcard one is the kind that is silently wrong until someone reads it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function noProxyList(env?: NodeJS.ProcessEnv): string;
|
|
8
|
+
/**
|
|
9
|
+
* Point this process's fetch at the proxy the environment declares, with loopback exempt by default.
|
|
10
|
+
*
|
|
11
|
+
* 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
|
|
12
|
+
* bundled fetch skips gzip decompression when it dispatches through npm undici (empty `stopReason:"stop"`), so fetch
|
|
13
|
+
* and the dispatcher must come from the SAME undici. No-op under Bun, whose native fetch already does both (and
|
|
14
|
+
* lacks these entry points).
|
|
15
|
+
*
|
|
16
|
+
* Call it through `enterAgentEnv` (env.ts) rather than directly: the proxy may be declared in the agent's own `.env`,
|
|
17
|
+
* so it has to be read first. The only direct callers are the ones with no agent directory to read.
|
|
8
18
|
*/
|
|
9
19
|
export declare function installProxyFetch(): void;
|