@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
|
@@ -1,32 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The AgentSession L0's engine binding: fastagent's assembled agent — model, prompt, skills, tools —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* The chat path builds the same pi class from the same assembly (session-builder.ts) but keeps ONE
|
|
6
|
-
* resident session. What is specific here is the posture: many sessions, one turn each, nothing in
|
|
7
|
-
* memory between turns.
|
|
8
|
-
*
|
|
9
|
-
* Shared once, rebuilt per turn:
|
|
10
|
-
* - `services` (ResourceLoader, settings, model runtime) is built lazily and reused — it is the
|
|
11
|
-
* expensive half, and it holds nothing session-specific;
|
|
12
|
-
* - the `AgentSession` and its tool bindings are per turn, because a tool's `execute` closes over the
|
|
13
|
-
* session it runs in and this posture has several in flight at once.
|
|
2
|
+
* The AgentSession L0's engine binding: fastagent's assembled agent — model, prompt, skills, tools — bound to one
|
|
3
|
+
* durable record, per invoke.
|
|
14
4
|
*/
|
|
15
|
-
import { dirname
|
|
16
|
-
import {
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
import { readMachine, withMachine } from "./machine.js";
|
|
7
|
+
import { ExtensionRunner, ModelRegistry, SessionManager, DefaultResourceLoader, createAgentSessionFromServices, getAgentDir, initTheme, } from "@earendil-works/pi-coding-agent";
|
|
17
8
|
import { log } from "../../log.js";
|
|
18
9
|
import { isDeferredTool } from "./tool.js";
|
|
19
10
|
import { activePath, resolveSessionSettings } from "./session-settings.js";
|
|
20
11
|
import { DEFAULT_THINKING_LEVEL } from "./models.js";
|
|
21
12
|
import { agentSessionManager, sessionToolActivation, turnContext } from "./tool-context.js";
|
|
22
13
|
/**
|
|
23
|
-
* The session custom-entry type recording ONE activation delta: `{ names }` — exactly the deferred
|
|
24
|
-
*
|
|
14
|
+
* The session custom-entry type recording ONE activation delta: `{ names }` — exactly the deferred tools a loader
|
|
15
|
+
* activated in that call.
|
|
25
16
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
17
|
+
* Not replaceable by pi's own transcript record. Since 0.86 the journal carries `toolsAdded` system messages and
|
|
18
|
+
* `getCurrentTools()` replays them, but that answers a different question: it reports every tool ever DECLARED to
|
|
19
|
+
* the model, which cannot tell a tool this conversation DISCOVERED from one that merely happened to be in the
|
|
20
|
+
* initial set at the time. Restoring from it would keep a tool later flipped to `deferred` active in sessions that
|
|
21
|
+
* never discovered it. This entry holds only what a loader activated, which is what makes the restore below
|
|
22
|
+
* "today's non-deferred tools PLUS what this conversation found".
|
|
30
23
|
*/
|
|
31
24
|
const TOOL_ACTIVATION_ENTRY = "fastagent:tool-activation";
|
|
32
25
|
/** Every deferred tool this session has ever discovered, oldest first. */
|
|
@@ -44,17 +37,12 @@ function recordedActivations(session) {
|
|
|
44
37
|
}
|
|
45
38
|
return names;
|
|
46
39
|
}
|
|
47
|
-
/** Warned once per session+missing set: a fresh session is built per invoke and channel sessions run
|
|
48
|
-
* for weeks, so an un-deduped warn would repeat every turn and dilute its own signal. */
|
|
49
|
-
const warnedDroppedActivations = new Set();
|
|
50
40
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* `bound` is filled after the session exists — pi needs the definitions to build the session, and a
|
|
54
|
-
* tool needs the session to reach the turn context. A tool that somehow runs before that binding
|
|
55
|
-
* throws rather than executing outside the turn: a broken lifecycle must not look like a normal
|
|
56
|
-
* out-of-turn call.
|
|
41
|
+
* Warned once per session+missing set: a fresh session is built per invoke and channel sessions run for weeks, so an
|
|
42
|
+
* un-deduped warn would repeat every turn and dilute its own signal.
|
|
57
43
|
*/
|
|
44
|
+
const warnedDroppedActivations = new Set();
|
|
45
|
+
/** fastagent's tools as pi tool definitions, bound to ONE session. */
|
|
58
46
|
function toolDefinitions(tools, bound, sessionId) {
|
|
59
47
|
return tools.map((tool) => ({
|
|
60
48
|
...tool,
|
|
@@ -85,12 +73,8 @@ function toolDefinitions(tools, bound, sessionId) {
|
|
|
85
73
|
}));
|
|
86
74
|
}
|
|
87
75
|
/**
|
|
88
|
-
* Bind ONE pi session to a record: the definition's tools as pi definitions over one turn context,
|
|
89
|
-
*
|
|
90
|
-
* runtime both bind here — they differ in what they hand in (a shared vs a per-session `services`,
|
|
91
|
-
* record-resolved vs configured settings) and in whether a discovered activation has a record to
|
|
92
|
-
* land in, and in nothing else. Two copies of this drifted once (the tool adapter and the deferral
|
|
93
|
-
* narrowing each existed twice, identical but for those parameters).
|
|
76
|
+
* Bind ONE pi session to a record: the definition's tools as pi definitions over one turn context, pi's own tool
|
|
77
|
+
* copies kept off, and deferral applied.
|
|
94
78
|
*/
|
|
95
79
|
export async function bindPiSession(options) {
|
|
96
80
|
const { services, sessionManager, model, thinkingLevel, tools, cwd, recordActivations } = options;
|
|
@@ -105,16 +89,12 @@ export async function bindPiSession(options) {
|
|
|
105
89
|
model,
|
|
106
90
|
thinkingLevel,
|
|
107
91
|
// pi would otherwise mount its built-ins on top of fastagent's copies, offering duplicate names.
|
|
108
|
-
// Lower-level callers with an explicit list also rely on omitted built-ins staying omitted. And
|
|
109
|
-
// NO `tools` allowlist: it would freeze the set at bind time, while pi lets an extension register
|
|
110
|
-
// from `session_start` — `noTools: "builtin"` gives the guarantee the allowlist was there for.
|
|
111
92
|
noTools: "builtin",
|
|
112
93
|
...(excludedToolNames.length > 0 ? { excludeTools: [...excludedToolNames] } : {}),
|
|
113
94
|
customTools: toolDefinitions(tools, bound, sessionId),
|
|
114
95
|
});
|
|
115
96
|
const { session } = result;
|
|
116
|
-
// One context for the whole session: it describes the SESSION, not the call.
|
|
117
|
-
// bridge above all — a tool call has to see what the previous one activated.
|
|
97
|
+
// One context for the whole session: it describes the SESSION, not the call.
|
|
118
98
|
bound.current = {
|
|
119
99
|
session,
|
|
120
100
|
context: {
|
|
@@ -126,15 +106,14 @@ export async function bindPiSession(options) {
|
|
|
126
106
|
: undefined),
|
|
127
107
|
},
|
|
128
108
|
};
|
|
129
|
-
// Deferral, then restoration: pi starts every mounted tool active, so narrow by SUBTRACTING the
|
|
130
|
-
//
|
|
131
|
-
//
|
|
109
|
+
// Deferral, then restoration: pi starts every mounted tool active, so narrow by SUBTRACTING the deferred names
|
|
110
|
+
// (robust to pi mounting tools of its own, unlike an exact-set replacement), then add back what THIS session has
|
|
111
|
+
// already discovered.
|
|
132
112
|
if (deferred.length > 0) {
|
|
133
113
|
const active = session.getActiveToolNames();
|
|
134
114
|
const mounted = new Set(session.getAllTools().map((tool) => tool.name));
|
|
135
115
|
const recorded = recordActivations ? recordedActivations(session) : [];
|
|
136
|
-
// A recorded name that is no longer mounted is dropped rather than replayed
|
|
137
|
-
// THROWS on an unknown name, so replaying one would brick every future turn of this session.
|
|
116
|
+
// A recorded name that is no longer mounted is dropped rather than replayed.
|
|
138
117
|
const restored = recorded.filter((name) => mounted.has(name));
|
|
139
118
|
const dropped = recorded.filter((name) => !mounted.has(name));
|
|
140
119
|
if (dropped.length > 0) {
|
|
@@ -150,135 +129,251 @@ export async function bindPiSession(options) {
|
|
|
150
129
|
}
|
|
151
130
|
return result;
|
|
152
131
|
}
|
|
132
|
+
/** Each distinct load failure is said once per process: serving loads the extensions again for every session. */
|
|
133
|
+
const reportedExtensionErrors = new Set();
|
|
153
134
|
/**
|
|
154
|
-
* Announce extensions pi failed to load. pi collects them into `LoadExtensionsResult.errors` and
|
|
155
|
-
*
|
|
156
|
-
* A definition running without the extension it ships is exactly the "quietly missing" failure this
|
|
157
|
-
* exists to remove. CHAT calls it, once per built services — serving does not load extensions at
|
|
158
|
-
* all, and announces that instead (see PiAgentSessionFactoryOptions.extensionPaths).
|
|
135
|
+
* Announce extensions pi failed to load. pi collects them into `LoadExtensionsResult.errors` and carries on with the
|
|
136
|
+
* rest.
|
|
159
137
|
*/
|
|
160
138
|
export function reportExtensionErrors(services) {
|
|
161
139
|
for (const { path, error } of services.resourceLoader.getExtensions().errors) {
|
|
140
|
+
const key = `${path}\u0000${error}`;
|
|
141
|
+
if (reportedExtensionErrors.has(key))
|
|
142
|
+
continue;
|
|
143
|
+
reportedExtensionErrors.add(key);
|
|
162
144
|
log.warn(`[fastagent] extension ${path} failed to load: ${error}`);
|
|
163
145
|
}
|
|
164
146
|
}
|
|
147
|
+
const PROVIDER_REFUSAL = "extensions cannot register model providers when serving: a provider is process-wide, shared by every " +
|
|
148
|
+
"conversation, and never unregistered. Declare it in the agent's models.json";
|
|
165
149
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
150
|
+
* Serving refuses an extension that registers a provider while loading. pi would write it into the ONE `ModelRuntime`
|
|
151
|
+
* every conversation resolves against, merge a re-registration into the old entry, and never drop one the code stopped
|
|
152
|
+
* registering. The whole extension is left out, the way a tool file that fails to load is, and the refusal is its
|
|
153
|
+
* load error.
|
|
154
|
+
*/
|
|
155
|
+
function refuseLoadTimeProviders(base) {
|
|
156
|
+
const offenders = new Set([
|
|
157
|
+
...base.runtime.pendingProviderRegistrations.map((r) => r.extensionPath),
|
|
158
|
+
...base.runtime.pendingNativeProviderRegistrations.map((r) => r.extensionPath),
|
|
159
|
+
]);
|
|
160
|
+
if (offenders.size === 0)
|
|
161
|
+
return base;
|
|
162
|
+
base.runtime.pendingProviderRegistrations = [];
|
|
163
|
+
base.runtime.pendingNativeProviderRegistrations = [];
|
|
164
|
+
return {
|
|
165
|
+
...base,
|
|
166
|
+
extensions: base.extensions.filter((extension) => !offenders.has(extension.path)),
|
|
167
|
+
errors: [...base.errors, ...[...offenders].map((path) => ({ path, error: PROVIDER_REFUSAL }))],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* ...and a registration made AFTER loading (from an event handler or a command), which pi applies to the shared
|
|
172
|
+
* runtime directly. Must run after the session is created (pi installs its own actions then) and before
|
|
173
|
+
* `bindExtensions` (which fires `session_start`).
|
|
174
|
+
*/
|
|
175
|
+
function refuseLateProviders(services) {
|
|
176
|
+
const runtime = services.resourceLoader.getExtensions().runtime;
|
|
177
|
+
const refuse = () => {
|
|
178
|
+
throw new Error(PROVIDER_REFUSAL);
|
|
179
|
+
};
|
|
180
|
+
runtime.registerProvider = refuse;
|
|
181
|
+
runtime.registerNativeProvider = refuse;
|
|
182
|
+
runtime.unregisterProvider = refuse;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* What a command's `ctx` can do to the session when serving. Each turn binds its own session and disposes it, so
|
|
186
|
+
* there is no current session to replace, fork or reload; pi's unbound default would report `{ cancelled: false }`
|
|
187
|
+
* for all of them without doing anything.
|
|
178
188
|
*/
|
|
189
|
+
function servingCommandActions(session) {
|
|
190
|
+
const unavailable = (action) => async () => {
|
|
191
|
+
throw new Error(`ctx.${action}() is not available when serving: every turn runs on its own session`);
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
waitForIdle: () => session.waitForIdle(),
|
|
195
|
+
newSession: unavailable("newSession"),
|
|
196
|
+
fork: unavailable("fork"),
|
|
197
|
+
navigateTree: unavailable("navigateTree"),
|
|
198
|
+
switchSession: unavailable("switchSession"),
|
|
199
|
+
reload: unavailable("reload"),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/** The resource posture a fastagent definition asks pi for — ONE definition of it, for both assemblies. */
|
|
179
203
|
export function definitionResourceLoaderOptions(source) {
|
|
180
204
|
return {
|
|
181
|
-
//
|
|
182
|
-
// extensions, slash commands, global AGENTS.md, APPEND_SYSTEM.md) stays out, so the agent that
|
|
183
|
-
// runs is the artifact, not the artifact plus whoever's laptop it is.
|
|
205
|
+
// The machine's extensions are its owner's setup, not this agent's.
|
|
184
206
|
noExtensions: true,
|
|
185
|
-
// ...except the definition's OWN extensions/: pi honours additionalExtensionPaths even under
|
|
186
|
-
//
|
|
187
|
-
// extensions, the machine's stay out.
|
|
207
|
+
// ...except the definition's OWN extensions/: pi honours additionalExtensionPaths even under noExtensions, which
|
|
208
|
+
// is exactly the split wanted here.
|
|
188
209
|
...(source.extensionPaths?.length ? { additionalExtensionPaths: [...source.extensionPaths] } : {}),
|
|
189
|
-
|
|
210
|
+
// Not pi's: fastagent already loads the SAME files into segment ② (`loadProjectContextFiles` in
|
|
211
|
+
// definition.ts). Leaving both on would put every AGENTS.md in the prompt twice.
|
|
190
212
|
noContextFiles: true,
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
// (`createPiAgent({ model, tools })`) never asked for. pi appends its own working-directory line
|
|
194
|
-
// either way — that is engine behaviour this binding does not fight.
|
|
213
|
+
// The IDENTITY is the definition's, whatever the machine thinks. Inheriting skills is inheriting capability;
|
|
214
|
+
// inheriting a system prompt would be the agent becoming someone else's agent.
|
|
195
215
|
systemPromptOverride: () => source.systemPrompt() || " ",
|
|
196
216
|
appendSystemPromptOverride: () => [],
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
217
|
+
/**
|
|
218
|
+
* THE DEFINITION'S SKILLS, PLUS THE MACHINE'S — an agent inherits the box it runs on (machine.ts), and the
|
|
219
|
+
* definition wins a name collision.
|
|
220
|
+
*
|
|
221
|
+
* NO DISCOVERY OF ITS OWN: the machine's half is the process's single read, so a bound session and `commands()`
|
|
222
|
+
* describe the same box. A loader discovering for itself is what made the menu live while a session was not.
|
|
223
|
+
*/
|
|
224
|
+
noSkills: true,
|
|
225
|
+
skillsOverride: () => ({
|
|
226
|
+
skills: withMachine(toPiSkills(source.skills()), source.machine.skills),
|
|
227
|
+
diagnostics: [],
|
|
200
228
|
}),
|
|
229
|
+
noPromptTemplates: true,
|
|
230
|
+
promptsOverride: () => ({ prompts: [...source.machine.prompts], diagnostics: [] }),
|
|
201
231
|
};
|
|
202
232
|
}
|
|
203
|
-
/**
|
|
233
|
+
/** Manual compactions waiting for pi's admission, keyed by the session's record (what an extension's `ctx` names). */
|
|
234
|
+
const compactAdmissions = new WeakMap();
|
|
235
|
+
const COMPACT_ADMISSION = "fastagent-compact-admission";
|
|
236
|
+
/**
|
|
237
|
+
* pi's own admission signal for a manual compaction: `session_before_compact` fires only once `prepareCompaction`
|
|
238
|
+
* found work, and before the model call. It must be the FIRST listener ({@link admissionFirst}): pi awaits each
|
|
239
|
+
* handler in turn, and a definition's own handler (a custom summarizer, a cancel) runs after admission, like the
|
|
240
|
+
* model call it may replace.
|
|
241
|
+
*/
|
|
242
|
+
const compactAdmission = {
|
|
243
|
+
name: COMPACT_ADMISSION,
|
|
244
|
+
hidden: true,
|
|
245
|
+
factory: (pi) => {
|
|
246
|
+
pi.on("session_before_compact", (event, ctx) => {
|
|
247
|
+
if (event.reason === "manual")
|
|
248
|
+
admitCompaction(ctx.sessionManager);
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* pi loads path extensions first and appends inline factories after them, so {@link compactAdmission} arrives last.
|
|
254
|
+
* Moved to the front here, since pi dispatches handlers in extension order.
|
|
255
|
+
*/
|
|
256
|
+
function admissionFirst(base) {
|
|
257
|
+
const path = `<inline:${COMPACT_ADMISSION}>`;
|
|
258
|
+
const admission = base.extensions.filter((extension) => extension.path === path);
|
|
259
|
+
return { ...base, extensions: [...admission, ...base.extensions.filter((extension) => extension.path !== path)] };
|
|
260
|
+
}
|
|
261
|
+
/** Tell a waiting {@link startCompaction} that pi admitted the compaction on this record. */
|
|
262
|
+
export function admitCompaction(record) {
|
|
263
|
+
compactAdmissions.get(record)?.();
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* pi's refusals when `prepareCompaction` finds nothing. They are plain Errors, so their text is the only way to tell
|
|
267
|
+
* them from any other error pi raises before admission. If pi rewords them, the refusal still fails visibly, as
|
|
268
|
+
* `boundary_command_failed` instead of `nothing_to_compact`, and the admission tests in session-control.test.ts,
|
|
269
|
+
* which run pi's real refusal, fail with it.
|
|
270
|
+
*/
|
|
271
|
+
const NOTHING_TO_COMPACT = /^(Nothing to compact|Already compacted)\b/;
|
|
272
|
+
/**
|
|
273
|
+
* Start pi's manual compaction on a bound session. `onAdmitted` runs INSIDE pi's admission event, so everything it
|
|
274
|
+
* publishes precedes anything pi does next (the model call, its retries). `admission` settles before the model call.
|
|
275
|
+
* pi's own pre-admission failures come back as `refused` rather than a rejection: the caller answers them to its
|
|
276
|
+
* Caller as a result. `done` is the compaction itself.
|
|
277
|
+
*/
|
|
278
|
+
export function startCompaction(session, instructions, onAdmitted) {
|
|
279
|
+
const record = session.sessionManager;
|
|
280
|
+
const admitted = new Promise((resolve) => {
|
|
281
|
+
compactAdmissions.set(record, () => {
|
|
282
|
+
try {
|
|
283
|
+
onAdmitted();
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
resolve("admitted");
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
const done = session.compact(instructions);
|
|
291
|
+
const forget = () => void compactAdmissions.delete(record);
|
|
292
|
+
done.then(forget, forget);
|
|
293
|
+
const settledFirst = done.then(() => {
|
|
294
|
+
throw new Error("pi finished a compaction without announcing its admission (session_before_compact)");
|
|
295
|
+
}, (error) => error instanceof Error && NOTHING_TO_COMPACT.test(error.message) ? "nothing_to_compact" : { refused: error });
|
|
296
|
+
return { admission: Promise.race([admitted, settledFirst]), done };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The resources ONE served session runs on: a fresh loader, so fresh extension instances.
|
|
300
|
+
*
|
|
301
|
+
* Assembled here rather than by pi's `createAgentSessionServices`, which ends by refreshing the model runtime it is
|
|
302
|
+
* given — re-reading `models.json` and rebuilding every provider on the ONE runtime all conversations share. That is
|
|
303
|
+
* there to fold in providers extensions registered, which serving refuses; per session it would be a full provider
|
|
304
|
+
* rebuild per turn.
|
|
305
|
+
*/
|
|
306
|
+
async function servingServices(options) {
|
|
307
|
+
const { cwd, modelRuntime, definition, extensionPaths } = options;
|
|
308
|
+
const machine = await readMachine(cwd);
|
|
309
|
+
const agentDir = getAgentDir();
|
|
310
|
+
// The machine's engine settings, as read at boot and without `packages` — a turn never resolves one.
|
|
311
|
+
const settingsManager = machine.settingsManager();
|
|
312
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
313
|
+
...definitionResourceLoaderOptions({
|
|
314
|
+
systemPrompt: () => definition.systemPrompt,
|
|
315
|
+
skills: () => definition.skills,
|
|
316
|
+
machine,
|
|
317
|
+
extensionPaths,
|
|
318
|
+
}),
|
|
319
|
+
extensionsOverride: (base) => admissionFirst(refuseLoadTimeProviders(base)),
|
|
320
|
+
extensionFactories: [compactAdmission],
|
|
321
|
+
cwd,
|
|
322
|
+
agentDir,
|
|
323
|
+
settingsManager,
|
|
324
|
+
});
|
|
325
|
+
await resourceLoader.reload();
|
|
326
|
+
const services = { cwd, agentDir, modelRuntime, settingsManager, resourceLoader, diagnostics: [] };
|
|
327
|
+
reportExtensionErrors(services);
|
|
328
|
+
return services;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* The `/name` commands a served session dispatches, named the way pi resolves them (a name two extensions share gets
|
|
332
|
+
* a `:N` suffix). Loaded through the same {@link servingServices} a turn binds, so the menu and the dispatch cannot
|
|
333
|
+
* disagree. Loading runs the extensions' factories; no session opens, so `session_start` does not fire, and the
|
|
334
|
+
* instances are never bound (any action they call throws).
|
|
335
|
+
*/
|
|
336
|
+
export async function servedExtensionCommands(options) {
|
|
337
|
+
if (options.extensionPaths.length === 0)
|
|
338
|
+
return [];
|
|
339
|
+
const services = await servingServices({ ...options, definition: { skills: [] } });
|
|
340
|
+
const { extensions, runtime } = services.resourceLoader.getExtensions();
|
|
341
|
+
const runner = new ExtensionRunner(extensions, runtime, options.cwd, SessionManager.inMemory(options.cwd), new ModelRegistry(options.modelRuntime));
|
|
342
|
+
return runner.getRegisteredCommands();
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Open-or-create the record, then bind a fresh session to it — on a fresh resource loader.
|
|
346
|
+
*
|
|
347
|
+
* ONE LOADER PER SESSION, because pi's extension runtime belongs to its loader: a loader shared across concurrent
|
|
348
|
+
* turns would let one conversation's extension act on another's session. It also makes the definition live with no
|
|
349
|
+
* bookkeeping: every bind reads the prompt and skills this invoke read. The machine's half is the process's one read
|
|
350
|
+
* (machine.ts). Extension CODE is imported once per process, so an edit to `extensions/` needs a restart, which is
|
|
351
|
+
* what `dev` does on one.
|
|
352
|
+
*/
|
|
204
353
|
export function piAgentSessionFactory(options) {
|
|
205
354
|
const { sessions, thinkingLevel, cwd } = options;
|
|
206
355
|
const extensionPaths = options.extensionPaths ?? [];
|
|
207
356
|
const excludedToolNames = options.excludedToolNames ?? [];
|
|
208
|
-
if (extensionPaths.length > 0) {
|
|
209
|
-
log.warn(`[fastagent] ${extensionPaths.length} extension(s) in the definition are NOT loaded when serving ` +
|
|
210
|
-
"(they run in `fastagent chat`): pi's extension runtime is shared across sessions, and serving " +
|
|
211
|
-
"runs concurrent turns for different conversations. See docs/configuration.md#extensions.");
|
|
212
|
-
}
|
|
213
357
|
const tools = options.tools ?? [];
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
358
|
+
// `ctx.ui.theme` reads pi's global theme, which only pi's own entry points initialize.
|
|
359
|
+
if (extensionPaths.length > 0)
|
|
360
|
+
initTheme();
|
|
217
361
|
let engine;
|
|
218
|
-
const buildServices = async (modelRuntime) => createAgentSessionServices({
|
|
219
|
-
cwd,
|
|
220
|
-
agentDir: options.agentDir ?? join(cwd, ".fastagent", "pi"),
|
|
221
|
-
modelRuntime,
|
|
222
|
-
// No extensionPaths: serving does not run them (see PiAgentSessionFactoryOptions), which is
|
|
223
|
-
// the one resource question the two assemblies answer differently. The accessors read the
|
|
224
|
-
// CURRENT prompt/skills — serving refreshes both per turn, so a snapshot taken here would
|
|
225
|
-
// serve a stale definition after the first edit.
|
|
226
|
-
resourceLoaderOptions: definitionResourceLoaderOptions({
|
|
227
|
-
systemPrompt: () => definition.systemPrompt,
|
|
228
|
-
skills: () => definition.skills,
|
|
229
|
-
}),
|
|
230
|
-
});
|
|
231
362
|
return async (sessionId, inherit) => {
|
|
232
|
-
const
|
|
363
|
+
const definition = await options.readDefinition();
|
|
233
364
|
engine ??= options.engine();
|
|
234
365
|
const { modelRuntime, model } = await engine;
|
|
235
|
-
|
|
236
|
-
definition = next;
|
|
237
|
-
services = buildServices(modelRuntime); // assigned before any await: concurrent turns share it
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
// The ResourceLoader reads the overrides once and caches, so a re-read of the definition only
|
|
241
|
-
// reaches the model after a reload. Reload only when the definition ACTUALLY changed — an
|
|
242
|
-
// author edits persona.md far less often than the agent takes a turn, and a reload costs ~5ms
|
|
243
|
-
// against ~0.6ms to bind a session.
|
|
244
|
-
//
|
|
245
|
-
// "Changed" is measured against the LOADER, not against what this factory last wrote. Serving
|
|
246
|
-
// is concurrent across sessions, and a shared variable makes the check lie: one turn writes
|
|
247
|
-
// its new prompt, awaits before reloading, and the next turn sees that value already present,
|
|
248
|
-
// concludes nothing changed, and skips the reload — so the edit reaches neither the loader nor
|
|
249
|
-
// any error. Asking the loader what it is actually serving cannot go stale that way. The cost
|
|
250
|
-
// of losing the race is one redundant reload, not a swallowed edit.
|
|
251
|
-
//
|
|
252
|
-
// Skill CONTENT is not part of this — pi reads a skill from its file at invocation time, so
|
|
253
|
-
// only the declared set matters.
|
|
254
|
-
//
|
|
255
|
-
// What this deliberately does NOT provide is a per-turn snapshot. The definition is an AGENT
|
|
256
|
-
// property, not a session one: two turns running either side of an edit each get a definition
|
|
257
|
-
// that genuinely existed, and the product promise — an edit is live on the next turn — holds
|
|
258
|
-
// for both. Pinning a snapshot per turn would cost either a loader per turn or a queue in
|
|
259
|
-
// front of every bind, to buy a guarantee nothing asks for.
|
|
260
|
-
const loader = (await services).resourceLoader;
|
|
261
|
-
const definitionChanged = loader.getSystemPrompt() !== (next.systemPrompt || " ") ||
|
|
262
|
-
skillSet(loader.getSkills().skills) !== skillSet(next.skills);
|
|
263
|
-
if (definitionChanged) {
|
|
264
|
-
definition = next;
|
|
265
|
-
await loader.reload();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
366
|
+
// The record first: a control write that races this turn must find the session and be refused busy.
|
|
268
367
|
const sessionManager = await sessions.openOrCreate(sessionId, inherit);
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
// The SAME read the control plane performs, including its integrity check: a record whose chain
|
|
273
|
-
// is broken must not run on assembly defaults while `state()` rejects it — one of the two planes
|
|
274
|
-
// would be lying. A throw here becomes this turn's `failed` event, which is where the fault has
|
|
275
|
-
// a channel to be reported through.
|
|
368
|
+
const services = await servingServices({ cwd, modelRuntime, definition, extensionPaths });
|
|
369
|
+
// What the session RUNS on: the boundary plane records model/thinking overrides as entries, and pi does not read
|
|
370
|
+
// them back.
|
|
276
371
|
const settings = resolveSessionSettings(activePath(sessionManager), modelRuntime, {
|
|
277
372
|
model,
|
|
278
373
|
thinkingLevel: thinkingLevel ?? DEFAULT_THINKING_LEVEL,
|
|
279
374
|
});
|
|
280
375
|
const { session } = await bindPiSession({
|
|
281
|
-
services
|
|
376
|
+
services,
|
|
282
377
|
sessionManager,
|
|
283
378
|
model: settings.model,
|
|
284
379
|
thinkingLevel: settings.thinkingLevel,
|
|
@@ -288,15 +383,23 @@ export function piAgentSessionFactory(options) {
|
|
|
288
383
|
sessionId,
|
|
289
384
|
recordActivations: true,
|
|
290
385
|
});
|
|
386
|
+
refuseLateProviders(services);
|
|
387
|
+
// `onError` is THE ONLY LISTENER for a fault pi reports nowhere else: `/skill:<name>` is expanded by reading
|
|
388
|
+
// `filePath` at prompt time, and when that read fails pi raises `skill_expansion` on the extension error channel
|
|
389
|
+
// and sends the line to the model unexpanded. The list can outlive the file: it is refreshed per invoke, so a
|
|
390
|
+
// steer or follow-up inside a run, or a definition replaced under a running container (`src/deploy/workspace.ts`),
|
|
391
|
+
// reaches exactly that state. The same channel carries every extension handler's failure.
|
|
392
|
+
//
|
|
393
|
+
// No `uiContext`: a served turn has no human at a terminal, and pi's default is what extensions are written to
|
|
394
|
+
// detect (`ctx.hasUI === false`; dialogs resolve as cancelled). This call fires `session_start`.
|
|
395
|
+
await session.bindExtensions({
|
|
396
|
+
onError: ({ extensionPath, event, error }) => log.warn(`[fastagent] session ${sessionId}: ${event} failed for ${extensionPath}: ${error}`),
|
|
397
|
+
commandContextActions: servingCommandActions(session),
|
|
398
|
+
shutdownHandler: () => log.warn(`[fastagent] session ${sessionId}: an extension called ctx.shutdown(); a served process is stopped by its host, not by a turn`),
|
|
399
|
+
});
|
|
291
400
|
return session;
|
|
292
401
|
};
|
|
293
402
|
}
|
|
294
|
-
/** What a reload has to notice: the declared set, not the files behind it. */
|
|
295
|
-
function skillSet(skills) {
|
|
296
|
-
return skills
|
|
297
|
-
.map((s) => `${s.name}\u0000${s.filePath ?? ""}\u0000${s.description}\u0000${s.disableModelInvocation ?? false}`)
|
|
298
|
-
.join("\u0001");
|
|
299
|
-
}
|
|
300
403
|
/** fastagent's Skill (content inline) as pi's (read from filePath at invocation time). */
|
|
301
404
|
function toPiSkills(skills) {
|
|
302
405
|
return skills.map((skill) => {
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import type { CredentialStore } from "@earendil-works/pi-ai";
|
|
2
2
|
/**
|
|
3
|
-
* The GLOBAL fastagent credentials file (distinct from pi's `~/.pi`), under the user-global machinery
|
|
4
|
-
*
|
|
5
|
-
* The project-level default is `<agentDir>/.secrets/auth.json` (computed by the opener and by
|
|
6
|
-
* `fastagent login`); this is only the `loginFlow()` PROGRAMMATIC fallback (when a caller omits
|
|
7
|
-
* `authPath`) and the path to point `--auth-path`/`FASTAGENT_AUTH_PATH` at to deliberately share ONE
|
|
8
|
-
* credential file across projects (safe — one file, one lock-serialized refresh lifecycle). The
|
|
9
|
-
* `fastagent login` CLI is project-level by default, never this.
|
|
3
|
+
* The GLOBAL fastagent credentials file (distinct from pi's `~/.pi`), under the user-global machinery home
|
|
4
|
+
* `~/.fastagent/`.
|
|
10
5
|
*/
|
|
11
6
|
export declare const GLOBAL_AUTH_PATH: string;
|
|
12
7
|
export interface FastagentAuthOptions {
|
|
13
|
-
/** Sink for non-fatal auth anomalies (unreadable/corrupt file).
|
|
8
|
+
/** Sink for non-fatal auth anomalies (unreadable/corrupt file). */
|
|
14
9
|
warn?: (message: string) => void;
|
|
15
10
|
}
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
18
|
-
|
|
11
|
+
/**
|
|
12
|
+
* A read-write `CredentialStore` over the given credentials file (default {@link GLOBAL_AUTH_PATH}; the directory
|
|
13
|
+
* opener passes the project-level `<root>/.secrets/auth.json`), optionally falling back to a second file.
|
|
14
|
+
*
|
|
15
|
+
* The fallback is PER PROVIDER, not per file: logging one provider into a project must not hide the others a person
|
|
16
|
+
* already has globally. And the layer a credential was READ from is the layer its refresh is written back to —
|
|
17
|
+
* anything else would conjure a second holder of the same OAuth grant, which is the failure this whole area exists
|
|
18
|
+
* to avoid. A provider present in neither layer is new, and new credentials belong to the primary.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fastagentCredentialStore(authPath?: string, options?: FastagentAuthOptions & {
|
|
21
|
+
fallbackPath?: string;
|
|
22
|
+
}): CredentialStore;
|