@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,36 +1,33 @@
|
|
|
1
|
+
import * as Deferred from "effect/Deferred";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Fiber from "effect/Fiber";
|
|
4
|
+
import * as Queue from "effect/Queue";
|
|
1
5
|
import { ABORTED_CODE, SESSION_BUSY_CODE, } from "../../agent.js";
|
|
2
|
-
import { toRetryScheduledEvent } from "./retry-event.js";
|
|
3
6
|
import { cancellableStream } from "../../collect.js";
|
|
4
7
|
import { log } from "../../log.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
* `auto_retry_start` reports as a `retry_scheduled` with `operation: "assistant"`: pi retries a
|
|
11
|
-
* failed ANSWER request itself, which the summarization-only cases of that vocabulary predate.
|
|
12
|
-
*/
|
|
8
|
+
import { toRetryScheduledEvent } from "./retry-event.js";
|
|
9
|
+
import { PortFailure, port, portAbort, portCleanup, portError } from "../../effect-port.js";
|
|
10
|
+
import { SessionBusy, acquireSession, acquireSessionLease } from "./session-effects.js";
|
|
11
|
+
import { errorToTerminal, inProcessLease, projectAgentEvent, toPiPromptOptions, toTerminal, } from "./turn-kit.js";
|
|
13
12
|
function toSessionEvent(event, runId) {
|
|
14
13
|
const at = Date.now();
|
|
15
14
|
switch (event.type) {
|
|
16
15
|
case "message_start":
|
|
17
|
-
// Assistant streaming only — a user/toolResult message is not a live message boundary.
|
|
18
16
|
if (event.message.role !== "assistant")
|
|
19
17
|
return null;
|
|
20
18
|
return { type: "message_started", timestamp: at, runId, data: {} };
|
|
21
19
|
case "message_update": {
|
|
22
|
-
// An empty delta is not output: it moves no consumer's state, and treating it as output would
|
|
23
|
-
// spend the silent window that auto-retry is allowed to use (see runOnSession).
|
|
24
20
|
const ev = event.assistantMessageEvent;
|
|
25
|
-
if (ev.type === "text_delta") {
|
|
21
|
+
if (ev.type === "text_delta" || ev.type === "thinking_delta") {
|
|
22
|
+
// Empty deltas must not spend the silent window in which a provider retry is safe.
|
|
26
23
|
return ev.delta === ""
|
|
27
24
|
? null
|
|
28
|
-
: {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
: {
|
|
26
|
+
type: "message_delta",
|
|
27
|
+
timestamp: at,
|
|
28
|
+
runId,
|
|
29
|
+
data: { channel: ev.type === "text_delta" ? "text" : "thinking", delta: ev.delta },
|
|
30
|
+
};
|
|
34
31
|
}
|
|
35
32
|
return null;
|
|
36
33
|
}
|
|
@@ -64,7 +61,7 @@ function toSessionEvent(event, runId) {
|
|
|
64
61
|
type: "queue_changed",
|
|
65
62
|
timestamp: at,
|
|
66
63
|
runId,
|
|
67
|
-
data: { steering: event.steering
|
|
64
|
+
data: { steering: [...event.steering], followUp: [...event.followUp] },
|
|
68
65
|
};
|
|
69
66
|
case "auto_retry_start":
|
|
70
67
|
case "summarization_retry_scheduled":
|
|
@@ -73,46 +70,40 @@ function toSessionEvent(event, runId) {
|
|
|
73
70
|
return null;
|
|
74
71
|
}
|
|
75
72
|
}
|
|
73
|
+
/** pi's error-channel events for work an invoke owes its caller: the command it ran, and the turns it started. */
|
|
74
|
+
const OWED_BY_THE_RUN = new Set(["command", "send_user_message", "send_message"]);
|
|
76
75
|
/**
|
|
77
|
-
* The
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* That message is taken from the EVENT STREAM, not from `session.state.messages`. Session state is
|
|
82
|
-
* mutable mid-turn: compaction replaces the array, and overflow recovery splices the last assistant
|
|
83
|
-
* message out of it outright (`state.messages = messages.slice(0, -1)`). Any index into it is a
|
|
84
|
-
* turn boundary that the engine is free to invalidate, while a `message_end` payload is a fact that
|
|
85
|
-
* already happened. Auto-compaction runs its own model call outside the agent's event stream, so it
|
|
86
|
-
* cannot masquerade as the turn's answer here.
|
|
76
|
+
* The turns extensions start on this session and have not finished. `pi.sendUserMessage` and `pi.sendMessage` reach
|
|
77
|
+
* these two session methods, and pi discards the promise; wrapping the methods keeps it. The session is this run's
|
|
78
|
+
* alone, so the wrap never outlives the run.
|
|
87
79
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
function trackExtensionTurns(session) {
|
|
81
|
+
const pending = new Set();
|
|
82
|
+
const track = (sent) => {
|
|
83
|
+
pending.add(sent);
|
|
84
|
+
const done = () => void pending.delete(sent);
|
|
85
|
+
sent.then(done, done); // pi's own `.catch` on `sent` reports the failure
|
|
86
|
+
return sent;
|
|
87
|
+
};
|
|
88
|
+
const sendUserMessage = session.sendUserMessage.bind(session);
|
|
89
|
+
const sendCustomMessage = session.sendCustomMessage.bind(session);
|
|
90
|
+
session.sendUserMessage = (...args) => track(sendUserMessage(...args));
|
|
91
|
+
session.sendCustomMessage = (...args) => track(sendCustomMessage(...args));
|
|
92
|
+
return pending;
|
|
93
|
+
}
|
|
95
94
|
export function createPiAgentFromSession(options) {
|
|
96
95
|
const { sessionFactory, lease = inProcessLease(), observer } = options;
|
|
97
96
|
async function* turn(scope, prompt, { onCancelReady, wasCancelled }) {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
details: "session busy: a turn is already in flight for this session",
|
|
104
|
-
retryable: true,
|
|
105
|
-
code: SESSION_BUSY_CODE,
|
|
106
|
-
};
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
// The run exists from here: exactly one run_started, exactly one run_settled. The settlement is
|
|
110
|
-
// emitted in the outer finally, immediately before the lease releases, so the observation
|
|
111
|
-
// plane's "running" window equals the lease window — state() must never read idle while a new
|
|
112
|
-
// invoke would still be rejected session_busy. A run with no recorded outcome was cancelled by
|
|
113
|
-
// the caller (SPEC: cancellation has no terminal event), which settles as aborted.
|
|
97
|
+
const queue = Effect.runSync(Queue.unbounded());
|
|
98
|
+
const consumed = Deferred.makeUnsafe();
|
|
99
|
+
const bound = Deferred.makeUnsafe();
|
|
100
|
+
const abort = new AbortController();
|
|
101
|
+
onCancelReady(() => abort.abort());
|
|
114
102
|
const runId = crypto.randomUUID();
|
|
103
|
+
let settled = false;
|
|
115
104
|
let outcome;
|
|
105
|
+
let abortsInFlight = 0;
|
|
106
|
+
let abortSucceeded = false;
|
|
116
107
|
const observe = (event, run) => {
|
|
117
108
|
if (!event || !observer)
|
|
118
109
|
return;
|
|
@@ -120,220 +111,194 @@ export function createPiAgentFromSession(options) {
|
|
|
120
111
|
observer(scope.session, event, run);
|
|
121
112
|
}
|
|
122
113
|
catch (error) {
|
|
123
|
-
// The observation plane must never break the data plane; a broken hub is its own problem.
|
|
124
114
|
log.warn(`[fastagent] session observer threw (event ${event.type}): ${String(error)}`);
|
|
125
115
|
}
|
|
126
116
|
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// that was still in flight when the terminal arrived counts too.
|
|
147
|
-
let abortsInFlight = 0;
|
|
148
|
-
let abortSucceeded = false;
|
|
117
|
+
const ready = Deferred.await(bound);
|
|
118
|
+
/**
|
|
119
|
+
* Every control command obeys one admission rule: wait for this run's binding, then refuse once it has settled.
|
|
120
|
+
* Kept in one place so a fourth command cannot arrive with a fifth spelling of it.
|
|
121
|
+
*/
|
|
122
|
+
const command = (run) => Effect.runPromise(Effect.gen(function* () {
|
|
123
|
+
const session = yield* ready;
|
|
124
|
+
if (settled)
|
|
125
|
+
return yield* Effect.fail(new PortFailure(new Error("run already settled; the command cannot take effect")));
|
|
126
|
+
yield* run(session);
|
|
127
|
+
}));
|
|
128
|
+
// Prompt preparation stays OUTSIDE admission. An await between the settled check and the enqueue would let the run
|
|
129
|
+
// end in between, and pi accepts a message for a finished run without complaint (`_steeringMessages.push`) — the
|
|
130
|
+
// command would be dropped and still reported as success. Image resizing is exactly such an await, hundreds of
|
|
131
|
+
// milliseconds of dynamic import and Photon work, and doing it here also keeps it overlapping session acquisition.
|
|
132
|
+
const enqueue = async (p, kind) => {
|
|
133
|
+
const opts = await toPiPromptOptions(p, "queued");
|
|
134
|
+
return command((session) => port(() => session[kind](p.text, opts?.images)));
|
|
135
|
+
};
|
|
149
136
|
const controls = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (settled)
|
|
154
|
-
throw settledError();
|
|
155
|
-
await session.steer(p.text, opts?.images);
|
|
156
|
-
},
|
|
157
|
-
async followUp(p) {
|
|
158
|
-
const opts = await toPiPromptOptions(p);
|
|
159
|
-
const session = await bound;
|
|
160
|
-
if (settled)
|
|
161
|
-
throw settledError();
|
|
162
|
-
await session.followUp(p.text, opts?.images);
|
|
163
|
-
},
|
|
164
|
-
async abort() {
|
|
165
|
-
const session = await bound;
|
|
166
|
-
if (settled)
|
|
167
|
-
throw settledError();
|
|
137
|
+
steer: (p) => enqueue(p, "steer"),
|
|
138
|
+
followUp: (p) => enqueue(p, "followUp"),
|
|
139
|
+
abort: () => command((session) => Effect.gen(function* () {
|
|
168
140
|
abortsInFlight++;
|
|
169
|
-
|
|
170
|
-
await session.abort();
|
|
141
|
+
yield* port(() => session.abort()).pipe(Effect.tap(() => Effect.sync(() => {
|
|
171
142
|
abortSucceeded = true;
|
|
172
|
-
}
|
|
173
|
-
finally {
|
|
143
|
+
})), Effect.ensuring(Effect.sync(() => {
|
|
174
144
|
abortsInFlight--;
|
|
175
|
-
}
|
|
176
|
-
},
|
|
145
|
+
})));
|
|
146
|
+
})),
|
|
177
147
|
};
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
// The scope's lineage reaches the store's CREATE path only — an existing session opens
|
|
183
|
-
// exactly as before, whatever the scope names.
|
|
184
|
-
session = await sessionFactory(scope.session, scope.parentSession === undefined
|
|
185
|
-
? undefined
|
|
186
|
-
: {
|
|
187
|
-
parentSession: scope.parentSession,
|
|
188
|
-
...(scope.branchHints !== undefined ? { branchHints: scope.branchHints } : {}),
|
|
189
|
-
});
|
|
190
|
-
sessionReady(session);
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
// Setup failures (session open, auth, a broken definition) are EVENTS, never throws
|
|
194
|
-
// (MUST 2) — and they settle the run as failed: an unrecorded outcome means the caller
|
|
195
|
-
// cancelled, which this is not.
|
|
196
|
-
sessionFailed(error); // a pending dispatch learns the run cannot take commands
|
|
197
|
-
const terminal = errorToTerminal(error);
|
|
198
|
-
outcome = { status: "failed", error: { message: terminal.details, retryable: terminal.retryable } };
|
|
148
|
+
const execute = Effect.gen(function* () {
|
|
149
|
+
yield* acquireSessionLease(lease, scope.session);
|
|
150
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
199
151
|
settled = true;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let streamedAnswer = false;
|
|
210
|
-
/** Set when a retry is refused because the answer already streamed — carries the ending error. */
|
|
211
|
-
let retriedAfterAnswer;
|
|
212
|
-
const unsub = session.subscribe((event) => {
|
|
213
|
-
if (retriedAfterAnswer !== undefined)
|
|
214
|
-
return; // decided; the retry's output is not ours
|
|
215
|
-
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
216
|
-
finalAssistant = event.message;
|
|
217
|
-
// Error messages and details can contain provider payloads; log only diagnostic metadata.
|
|
218
|
-
for (const diagnostic of finalAssistant.diagnostics ?? []) {
|
|
219
|
-
log.warn(`[fastagent] provider diagnostic ${diagnostic.type} (${finalAssistant.provider}/${finalAssistant.model}, session ${scope.session}, run ${runId})`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (event.type === "compaction_end" && event.reason !== "manual") {
|
|
223
|
-
const status = event.aborted ? "aborted" : event.errorMessage ? "failed" : "completed";
|
|
224
|
-
const emit = event.errorMessage && !event.aborted ? log.warn : log.debug;
|
|
225
|
-
emit(`[fastagent] automatic compaction ${event.reason} (session ${scope.session}, run ${runId}): ${status}`);
|
|
226
|
-
}
|
|
227
|
-
// pi retries a failed assistant request by DISCARDING that attempt's assistant message and
|
|
228
|
-
// asking again. Everything the turn achieved before it survives — executed tools keep
|
|
229
|
-
// their persisted results, and the retry resumes from them — so the only thing a retry can
|
|
230
|
-
// duplicate is answer text already streamed, which SPEC deltas cannot retract. Refuse it
|
|
231
|
-
// exactly there: refusing on tool events instead would push the retry out to the CALLER,
|
|
232
|
-
// who can only re-run the whole prompt and execute the tool a second time.
|
|
233
|
-
if (event.type === "auto_retry_start" && streamedAnswer) {
|
|
234
|
-
retriedAfterAnswer = event.errorMessage;
|
|
235
|
-
// Not synchronously: pi emits this event BEFORE creating the controller that makes its
|
|
236
|
-
// backoff abortable, so an abort from inside the listener would find nothing to cancel
|
|
237
|
-
// and the turn would still pay the delay and burn a provider call on a discarded answer.
|
|
238
|
-
queueMicrotask(() => void abort());
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const rich = toSessionEvent(event, runId);
|
|
242
|
-
if (!rich)
|
|
243
|
-
return;
|
|
244
|
-
observe(rich);
|
|
245
|
-
const projected = projectAgentEvent(rich);
|
|
246
|
-
if (!projected)
|
|
247
|
-
return;
|
|
248
|
-
if (projected.type === "text" || projected.type === "thinking")
|
|
249
|
-
streamedAnswer = true;
|
|
250
|
-
queue.push(projected);
|
|
152
|
+
observe({ type: "run_settled", timestamp: Date.now(), runId, data: outcome ?? { status: "aborted" } });
|
|
153
|
+
}));
|
|
154
|
+
// Admission precedes binding so a racing control command waits on this run's readiness gate.
|
|
155
|
+
observe({ type: "run_started", timestamp: Date.now(), runId, data: {} }, controls);
|
|
156
|
+
const session = yield* acquireSession(sessionFactory, scope.session, scope.parentSession === undefined
|
|
157
|
+
? undefined
|
|
158
|
+
: {
|
|
159
|
+
parentSession: scope.parentSession,
|
|
160
|
+
...(scope.branchHints !== undefined ? { branchHints: scope.branchHints } : {}),
|
|
251
161
|
});
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const terminal = errorToTerminal(error);
|
|
265
|
-
outcome = { status: "failed", error: { message: terminal.details, retryable: terminal.retryable } };
|
|
266
|
-
settled = true;
|
|
267
|
-
yield terminal;
|
|
162
|
+
let finalAssistant;
|
|
163
|
+
let runStarted = false;
|
|
164
|
+
let extensionFailure;
|
|
165
|
+
let streamedAnswer = false;
|
|
166
|
+
let retriedAfterAnswer;
|
|
167
|
+
let eventFailure;
|
|
168
|
+
const stop = () => {
|
|
169
|
+
void Effect.runPromise(portCleanup("event-fault abort", () => session.abort()));
|
|
170
|
+
};
|
|
171
|
+
yield* Effect.acquireRelease(Effect.try({
|
|
172
|
+
try: () => session.subscribe((event) => {
|
|
173
|
+
if (retriedAfterAnswer !== undefined || eventFailure)
|
|
268
174
|
return;
|
|
269
|
-
}
|
|
270
|
-
if (wasCancelled()) {
|
|
271
|
-
settled = true;
|
|
272
|
-
await abort();
|
|
273
|
-
return; // cancelled: the outer finally settles it as aborted
|
|
274
|
-
}
|
|
275
|
-
const run = session.prompt(prompt.text, promptOptions);
|
|
276
|
-
yield* queue.drainUntil(run);
|
|
277
|
-
let terminal;
|
|
278
175
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
176
|
+
if (event.type === "agent_start")
|
|
177
|
+
runStarted = true;
|
|
178
|
+
// Compaction rewrites session history; the event's assistant message is the turn's fact.
|
|
179
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
180
|
+
finalAssistant = event.message;
|
|
181
|
+
for (const diagnostic of finalAssistant.diagnostics ?? []) {
|
|
182
|
+
log.warn(`[fastagent] provider diagnostic ${diagnostic.type} (${finalAssistant.provider}/${finalAssistant.model}, session ${scope.session}, run ${runId})`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (event.type === "compaction_end" && event.reason !== "manual") {
|
|
186
|
+
const status = event.aborted ? "aborted" : event.errorMessage ? "failed" : "completed";
|
|
187
|
+
const emit = event.errorMessage && !event.aborted ? log.warn : log.debug;
|
|
188
|
+
emit(`[fastagent] automatic compaction ${event.reason} (session ${scope.session}, run ${runId}): ${status}`);
|
|
189
|
+
}
|
|
190
|
+
if (event.type === "auto_retry_start" && streamedAnswer) {
|
|
191
|
+
retriedAfterAnswer = event.errorMessage;
|
|
192
|
+
// Pi installs the retry controller after emitting this event.
|
|
193
|
+
queueMicrotask(stop);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const rich = toSessionEvent(event, runId);
|
|
197
|
+
observe(rich);
|
|
198
|
+
if (!rich)
|
|
199
|
+
return;
|
|
200
|
+
const projected = projectAgentEvent(rich);
|
|
201
|
+
if (!projected)
|
|
202
|
+
return;
|
|
203
|
+
if (projected.type === "text" || projected.type === "thinking")
|
|
204
|
+
streamedAnswer = true;
|
|
205
|
+
Queue.offerUnsafe(queue, projected);
|
|
286
206
|
}
|
|
287
207
|
catch (error) {
|
|
288
|
-
|
|
208
|
+
eventFailure = new PortFailure(error);
|
|
209
|
+
queueMicrotask(stop);
|
|
289
210
|
}
|
|
290
|
-
|
|
291
|
-
|
|
211
|
+
}),
|
|
212
|
+
catch: (error) => new PortFailure(error),
|
|
213
|
+
}), (unsubscribe) => portCleanup("unsubscribe", unsubscribe));
|
|
214
|
+
// pi swallows a failing extension command, and a turn an extension failed to start, into its error channel;
|
|
215
|
+
// both are work this run owes its caller, so the run reports them.
|
|
216
|
+
yield* Effect.acquireRelease(Effect.sync(() => session.extensionRunner.onError((error) => {
|
|
217
|
+
if (OWED_BY_THE_RUN.has(error.event))
|
|
218
|
+
extensionFailure ??= `${error.event} failed (${error.extensionPath}): ${error.error}`;
|
|
219
|
+
})), (unsubscribe) => Effect.sync(unsubscribe));
|
|
220
|
+
const extensionTurns = trackExtensionTurns(session);
|
|
221
|
+
// Completing the gate can run waiting controls synchronously; their queue events must be observed.
|
|
222
|
+
yield* Deferred.succeed(bound, session);
|
|
223
|
+
const promptOptions = yield* port(() => toPiPromptOptions(prompt, "prompt"));
|
|
224
|
+
if (eventFailure)
|
|
225
|
+
return yield* Effect.fail(eventFailure);
|
|
226
|
+
// Whether an extension could take this input instead of the model (a command, or an `input` handler): only
|
|
227
|
+
// then may a prompt settle with no model run and still have done its job.
|
|
228
|
+
const extensionMayTakeInput = session.extensionRunner.getRegisteredCommands().length > 0 || session.extensionRunner.hasHandlers("input");
|
|
229
|
+
yield* portAbort("prompt", async () => {
|
|
230
|
+
await session.prompt(prompt.text, promptOptions);
|
|
231
|
+
// An extension command returns from `prompt()` as soon as its handler does, and a turn it started may not
|
|
232
|
+
// have begun yet, so idleness alone proves nothing. This run owns those turns until they settle, and any
|
|
233
|
+
// turn they start in turn.
|
|
234
|
+
do {
|
|
235
|
+
await Promise.allSettled([...extensionTurns]);
|
|
236
|
+
await session.waitForIdle();
|
|
237
|
+
} while (extensionTurns.size > 0);
|
|
238
|
+
}, () => session.abort());
|
|
239
|
+
if (eventFailure)
|
|
240
|
+
return yield* Effect.fail(eventFailure);
|
|
241
|
+
if (extensionFailure !== undefined)
|
|
242
|
+
return { type: "failed", details: extensionFailure, retryable: false };
|
|
243
|
+
if (retriedAfterAnswer !== undefined)
|
|
244
|
+
return { type: "failed", details: retriedAfterAnswer, retryable: true };
|
|
245
|
+
if (finalAssistant)
|
|
246
|
+
return toTerminal(finalAssistant);
|
|
247
|
+
// No model run at all: an extension took the input (a command, or an `input` handler) and did its work.
|
|
248
|
+
if (!runStarted && extensionMayTakeInput)
|
|
249
|
+
return { type: "completed" };
|
|
250
|
+
return {
|
|
251
|
+
type: "failed",
|
|
252
|
+
details: "the engine settled the run without ending an assistant message",
|
|
253
|
+
retryable: false,
|
|
254
|
+
};
|
|
255
|
+
}).pipe(Effect.onError((cause) => Deferred.failCause(bound, cause)));
|
|
256
|
+
const work = Effect.runFork(Effect.scoped(Effect.gen(function* () {
|
|
257
|
+
const terminal = yield* execute.pipe(Effect.catchCause((cause) => {
|
|
258
|
+
const error = portError(cause);
|
|
259
|
+
return Effect.succeed(error instanceof SessionBusy
|
|
260
|
+
? {
|
|
261
|
+
type: "failed",
|
|
262
|
+
details: "session busy: a turn is already in flight for this session",
|
|
263
|
+
retryable: true,
|
|
264
|
+
code: SESSION_BUSY_CODE,
|
|
292
265
|
}
|
|
293
|
-
|
|
266
|
+
: errorToTerminal(error));
|
|
267
|
+
}));
|
|
268
|
+
settled = true;
|
|
269
|
+
Queue.offerUnsafe(queue, terminal.type === "failed" && (abortSucceeded || abortsInFlight > 0)
|
|
270
|
+
? { ...terminal, retryable: false, code: ABORTED_CODE }
|
|
271
|
+
: terminal);
|
|
272
|
+
Queue.endUnsafe(queue);
|
|
273
|
+
// Producer completion is earlier than consumer completion.
|
|
274
|
+
yield* Deferred.await(consumed);
|
|
275
|
+
})).pipe(Effect.ensuring(Effect.sync(() => {
|
|
276
|
+
Queue.endUnsafe(queue);
|
|
277
|
+
}))), { signal: abort.signal });
|
|
278
|
+
const read = Queue.takeAll(queue).pipe(Effect.catchTag("Done", () => Effect.succeed([])));
|
|
279
|
+
try {
|
|
280
|
+
for (;;) {
|
|
281
|
+
const events = await Effect.runPromise(read);
|
|
282
|
+
if (events.length === 0)
|
|
283
|
+
return;
|
|
284
|
+
for (const event of events) {
|
|
285
|
+
if (wasCancelled())
|
|
286
|
+
return;
|
|
287
|
+
if (event.type === "failed") {
|
|
294
288
|
outcome =
|
|
295
|
-
|
|
296
|
-
?
|
|
297
|
-
|
|
298
|
-
{ status: "aborted", error: { message: terminal.details, retryable: false } }
|
|
299
|
-
: {
|
|
300
|
-
status: "failed",
|
|
301
|
-
error: { code: terminal.code, message: terminal.details, retryable: terminal.retryable },
|
|
302
|
-
};
|
|
289
|
+
event.code === ABORTED_CODE
|
|
290
|
+
? { status: "aborted", error: { message: event.details, retryable: false } }
|
|
291
|
+
: { status: "failed", error: { code: event.code, message: event.details, retryable: event.retryable } };
|
|
303
292
|
}
|
|
304
|
-
else
|
|
293
|
+
else if (event.type === "completed")
|
|
305
294
|
outcome = { status: "completed" };
|
|
306
|
-
|
|
307
|
-
// Commands become ineffective the moment the run resolved — not at the outer finally,
|
|
308
|
-
// which sits behind a consumer-paced `yield`.
|
|
309
|
-
settled = true;
|
|
310
|
-
yield terminal;
|
|
311
|
-
}
|
|
312
|
-
finally {
|
|
313
|
-
settled = true;
|
|
314
|
-
unsub();
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
finally {
|
|
318
|
-
// NO session_shutdown here, deliberately. A per-invoke session makes one look right, but the
|
|
319
|
-
// extension INSTANCE it would tear down is not per-invoke: extensions belong to the agent's
|
|
320
|
-
// assembly and every turn shares one. Emitting a shutdown per turn had a finished
|
|
321
|
-
// turn clearing a timer a concurrent turn had just opened (measured, and pinned in
|
|
322
|
-
// definition-extensions.test.ts). The lifecycle has to match the instance, not the session
|
|
323
|
-
// wrapper: one agent, one instance, no per-turn teardown. Extensions that need per-turn
|
|
324
|
-
// cleanup do it in the tool or handler that opened the resource.
|
|
325
|
-
try {
|
|
326
|
-
session.dispose();
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
log.warn(`[fastagent] session dispose failed during cleanup: ${String(error)}`);
|
|
295
|
+
yield event;
|
|
330
296
|
}
|
|
331
297
|
}
|
|
332
298
|
}
|
|
333
299
|
finally {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
release(); // after the settlement, so the next invoke for this session cannot outrun it
|
|
300
|
+
Deferred.doneUnsafe(consumed, Effect.void);
|
|
301
|
+
await Effect.runPromise(Fiber.await(work));
|
|
337
302
|
}
|
|
338
303
|
}
|
|
339
304
|
return { invoke: (scope, prompt) => cancellableStream((hooks) => turn(scope, prompt, hooks)) };
|