@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
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Scoped acquisition of the two things a pi turn or control write owns: the session lease and the
|
|
2
|
+
* bound session. The Promise crossing itself is `src/effect-port.ts`. */
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import type { PiAgentSessionFactory } from "./invoke-session.ts";
|
|
5
|
+
import type { SessionInheritance } from "./session-inheritance.ts";
|
|
6
|
+
import type { Lease } from "./turn-kit.ts";
|
|
7
|
+
/** Admission was refused: another turn (or another write) holds this session. Its own tag because it
|
|
8
|
+
* is CONTROL FLOW — a caller answers it with `session_busy` and a retry — not a port failure. */
|
|
9
|
+
export declare class SessionBusy extends Error {
|
|
10
|
+
readonly _tag = "SessionBusy";
|
|
11
|
+
constructor();
|
|
12
|
+
}
|
|
13
|
+
export declare function acquireSessionLease(lease: Lease, session: string): Effect.Effect<import("./turn-kit.ts").Release, SessionBusy, import("effect/Scope").Scope>;
|
|
14
|
+
export declare function acquireSession(factory: PiAgentSessionFactory, session: string, inherit?: SessionInheritance): Effect.Effect<import("@earendil-works/pi-coding-agent").AgentSession, import("../../effect-port.ts").PortFailure, import("effect/Scope").Scope>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Scoped acquisition of the two things a pi turn or control write owns: the session lease and the
|
|
2
|
+
* bound session. The Promise crossing itself is `src/effect-port.ts`. */
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { port, portCleanup } from "../../effect-port.js";
|
|
5
|
+
/** Admission was refused: another turn (or another write) holds this session. Its own tag because it
|
|
6
|
+
* is CONTROL FLOW — a caller answers it with `session_busy` and a retry — not a port failure. */
|
|
7
|
+
export class SessionBusy extends Error {
|
|
8
|
+
_tag = "SessionBusy";
|
|
9
|
+
constructor() {
|
|
10
|
+
super("session busy: a turn or another write is already in flight");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function acquireSessionLease(lease, session) {
|
|
14
|
+
return Effect.acquireRelease(Effect.suspend(() => {
|
|
15
|
+
const release = lease.tryAcquire(session);
|
|
16
|
+
return release ? Effect.succeed(release) : Effect.fail(new SessionBusy());
|
|
17
|
+
}), (release) => portCleanup("lease release", release));
|
|
18
|
+
}
|
|
19
|
+
export function acquireSession(factory, session, inherit) {
|
|
20
|
+
// Acquisition stays uninterruptible: a late factory may still publish durable state under the lease.
|
|
21
|
+
// The extension instances are this session's alone, so their end is its end: pi's `dispose()` does not tell them.
|
|
22
|
+
return Effect.acquireRelease(port(() => factory(session, inherit)), (bound) => portCleanup("dispose", async () => {
|
|
23
|
+
try {
|
|
24
|
+
await bound.extensionRunner.emit({ type: "session_shutdown", reason: "quit" });
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
bound.dispose();
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
@@ -1,30 +1,21 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SessionManager } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
/** What a Caller names when a new session should start from an existing one. */
|
|
3
3
|
export interface SessionInheritance {
|
|
4
|
-
/** The session to inherit from. Missing or unreadable → start empty, with a warn: context is not
|
|
5
|
-
* the ask, and losing it must not cost the turn. */
|
|
6
4
|
parentSession: string;
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
5
|
+
/**
|
|
6
|
+
* Opaque markers that MAY locate the branch point on the parent's active path (searched in message content, first
|
|
7
|
+
* hit wins, most recent occurrence).
|
|
8
|
+
*/
|
|
9
9
|
branchHints?: string[];
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
* The branch point this inheritance should copy up to, and the parent's path — the decision half,
|
|
13
|
-
* shared by both backends because WHERE a thread branches is policy, not storage.
|
|
14
|
-
*/
|
|
11
|
+
/** The branch point this inheritance should copy up to, and the parent's path. */
|
|
15
12
|
export declare function inheritanceCut(parent: SessionManager, branchHints?: string[]): {
|
|
16
13
|
at: string;
|
|
17
14
|
} | undefined;
|
|
18
15
|
/**
|
|
19
|
-
* Copy the parent's path up to `at` into `child`, entry by entry — what a backend with no FILE to
|
|
20
|
-
*
|
|
21
|
-
* the session name) are not copied: they describe the parent's record, not the thread's history.
|
|
22
|
-
*
|
|
23
|
-
* The COPY only. Bounding what the child's model sees is {@link markInheritanceWindow}, which only
|
|
24
|
-
* {@link copyBranchForInheritance} applies: a lifecycle fork that marked a window would hide the
|
|
25
|
-
* exact entries its user forked to keep.
|
|
16
|
+
* Copy the parent's path up to `at` into `child`, entry by entry — what a backend with no FILE to fork has to do
|
|
17
|
+
* instead.
|
|
26
18
|
*/
|
|
27
19
|
export declare function copyBranchInto(parent: SessionManager, child: SessionManager, at: string): void;
|
|
28
|
-
/** {@link copyBranchInto} plus the inheritance window — what a new THREAD gets and a fork does not.
|
|
29
|
-
* Both backends call THIS one, so neither can drift on where a child's context begins. */
|
|
20
|
+
/** {@link copyBranchInto} plus the inheritance window — what a new THREAD gets and a fork does not. */
|
|
30
21
|
export declare function copyBranchForInheritance(parent: SessionManager, child: SessionManager, at: string): void;
|
|
@@ -1,83 +1,20 @@
|
|
|
1
|
+
import { estimateTokens } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import { log } from "../../log.js";
|
|
2
|
-
import { isPlaneMarker } from "./session-markers.js";
|
|
3
|
+
import { isConversationMessage, isPlaneMarker } from "./session-markers.js";
|
|
3
4
|
/** Inheritance window: at most this many exchanges of the parent reach the child's model context. */
|
|
4
5
|
const INHERIT_MAX_EXCHANGES = 50;
|
|
5
|
-
/** …and at most roughly this many tokens (~1/4 of a 200K context: generous, not everything).
|
|
6
|
-
* limits govern how far the window EXTENDS into older history — the newest exchange is a FLOOR,
|
|
7
|
-
* kept whole even when it alone exceeds the budget: the mark's boundary is entry-granular, and an
|
|
8
|
-
* inheritance that drops the exchange the thread branched off would be no inheritance at all. */
|
|
6
|
+
/** …and at most roughly this many tokens (~1/4 of a 200K context: generous, not everything). */
|
|
9
7
|
const INHERIT_MAX_TOKENS = 50_000;
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
8
|
+
/**
|
|
9
|
+
* Branch hints are IDS, not payloads: each one costs a scan over the parent's serialized path, and the wire accepts
|
|
10
|
+
* arbitrary arrays.
|
|
11
|
+
*/
|
|
12
12
|
const MAX_BRANCH_HINTS = 16;
|
|
13
13
|
const MAX_BRANCH_HINT_CHARS = 128;
|
|
14
|
-
/** A vision image is priced FLAT — what a provider bills for a resized image, roughly — because its
|
|
15
|
-
* base64 length (~1M chars for a photo) measures storage, not context: pricing it by chars would
|
|
16
|
-
* let one photo evict the whole text window. */
|
|
17
|
-
const INHERIT_IMAGE_TOKENS = 1_600;
|
|
18
14
|
function isUserMessage(entry) {
|
|
19
15
|
return entry?.type === "message" && entry.message?.role === "user";
|
|
20
16
|
}
|
|
21
|
-
/**
|
|
22
|
-
* the window is a budget, and being 20% off moves a boundary by an exchange, not correctness. */
|
|
23
|
-
function estimateContentTokens(content) {
|
|
24
|
-
if (typeof content === "string")
|
|
25
|
-
return Math.ceil(content.length / 4);
|
|
26
|
-
if (!Array.isArray(content))
|
|
27
|
-
return 0;
|
|
28
|
-
let tokens = 0;
|
|
29
|
-
for (const block of content) {
|
|
30
|
-
if (block.type === "image")
|
|
31
|
-
tokens += INHERIT_IMAGE_TOKENS;
|
|
32
|
-
else if (typeof block.text === "string")
|
|
33
|
-
tokens += Math.ceil(block.text.length / 4);
|
|
34
|
-
else
|
|
35
|
-
tokens += Math.ceil(JSON.stringify(block).length / 4);
|
|
36
|
-
}
|
|
37
|
-
return tokens;
|
|
38
|
-
}
|
|
39
|
-
/** Both entry kinds pi projects into model context from a copied path: a `custom_message` is an
|
|
40
|
-
* extension's injection INTO the conversation, so it charges the budget like any message. */
|
|
41
|
-
function estimateEntryTokens(entry) {
|
|
42
|
-
if (entry.type === "message")
|
|
43
|
-
return estimateContentTokens(entry.message?.content);
|
|
44
|
-
if (entry.type === "custom_message")
|
|
45
|
-
return estimateContentTokens(entry.content);
|
|
46
|
-
return 0;
|
|
47
|
-
}
|
|
48
|
-
/** A compaction entry's summary AND its retained tail — the entries from `firstKeptEntryId` up to
|
|
49
|
-
* the compaction — DO reach the model. They are the floor under every window that starts above the
|
|
50
|
-
* compaction, so the budget must count them; the tail is read off the path, since pi-coding-agent's
|
|
51
|
-
* SessionManager stores it as a POINTER and not as messages on the entry (0.84: `appendCompaction`
|
|
52
|
-
* writes `firstKeptEntryId`, and both of AgentSession's compaction paths go through it).
|
|
53
|
-
*
|
|
54
|
-
* pi-agent-core's harness writes the newer self-contained form instead — `retainedTail` on the entry,
|
|
55
|
-
* no pointer. If pi-coding-agent adopts it the pointer stops resolving, and this function prices the
|
|
56
|
-
* tail at zero while {@link copyBranchInto} drops it. Adopting it the way pi-agent-core did, by
|
|
57
|
-
* REPLACING the pointer, fails to typecheck at `Entry.firstKeptEntryId` rather than running quietly;
|
|
58
|
-
* a `retainedTail` added BESIDE the pointer would not, and is the case to watch on the next bump.
|
|
59
|
-
* Either way this is the place to change — not a reason to read both shapes today. */
|
|
60
|
-
function estimateCompactionTokens(path, compactionIdx) {
|
|
61
|
-
const compaction = path[compactionIdx];
|
|
62
|
-
if (compaction?.type !== "compaction")
|
|
63
|
-
return 0;
|
|
64
|
-
let tokens = Math.ceil((compaction.summary ?? "").length / 4);
|
|
65
|
-
const firstKept = path.findIndex((entry) => entry.id === compaction.firstKeptEntryId);
|
|
66
|
-
if (firstKept < 0)
|
|
67
|
-
return tokens; // an unresolvable pointer keeps nothing — pi's own reading
|
|
68
|
-
for (let i = firstKept; i < compactionIdx; i++) {
|
|
69
|
-
const entry = path[i];
|
|
70
|
-
if (entry)
|
|
71
|
-
tokens += estimateEntryTokens(entry);
|
|
72
|
-
}
|
|
73
|
-
return tokens;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Find the fork target on the parent's active path: the LAST message whose content carries a hint
|
|
77
|
-
* (the most recent turn that talked about that message), extended forward to the end of its exchange
|
|
78
|
-
* — forking mid-exchange would inherit a question without its answer. Hints are tried in caller
|
|
79
|
-
* order; the first that matches anywhere wins. No match → undefined (the caller forks the present).
|
|
80
|
-
*/
|
|
17
|
+
/** Find the fork target on the parent's active path. */
|
|
81
18
|
function locateBranchPoint(path, hints) {
|
|
82
19
|
const usable = hints
|
|
83
20
|
.filter((hint) => hint.length > 0 && hint.length <= MAX_BRANCH_HINT_CHARS)
|
|
@@ -87,10 +24,10 @@ function locateBranchPoint(path, hints) {
|
|
|
87
24
|
}
|
|
88
25
|
if (usable.length === 0)
|
|
89
26
|
return undefined;
|
|
90
|
-
// Serialize each message ONCE — the scan is hints × entries, and stringify must not sit in the
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
const serialized = path.map((entry) => (entry
|
|
27
|
+
// Serialize each conversation message ONCE — the scan is hints × entries, and stringify must not sit in the inner
|
|
28
|
+
// loop. A hint is something a participant said, so the engine's own system entries are not searched: see
|
|
29
|
+
// `isConversationMessage` for what a hint landing in the assembled prompt would otherwise do here.
|
|
30
|
+
const serialized = path.map((entry) => (isConversationMessage(entry) ? JSON.stringify(entry.message) : ""));
|
|
94
31
|
for (const hint of usable) {
|
|
95
32
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
96
33
|
if (!serialized[i]?.includes(hint))
|
|
@@ -104,60 +41,70 @@ function locateBranchPoint(path, hints) {
|
|
|
104
41
|
return undefined;
|
|
105
42
|
}
|
|
106
43
|
/**
|
|
107
|
-
* Bound what the child's MODEL CONTEXT starts with
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
44
|
+
* Bound what the child's MODEL CONTEXT starts with. Measured on pi's projection, what the model will actually see:
|
|
45
|
+
* a `context_edit` omission (an abandoned retry or overflow attempt) costs nothing, and the engine's own `system`
|
|
46
|
+
* entries are not history. The newest copied compaction (its summary, then the tail it retained) reaches the model
|
|
47
|
+
* only while no window is cut: pi reads the NEWEST compaction alone, so a mark placed after it drops that tail. Its
|
|
48
|
+
* summary, a few hundred tokens covering everything older, is carried into the mark instead.
|
|
111
49
|
*/
|
|
112
50
|
function markInheritanceWindow(child) {
|
|
113
51
|
const path = child.getBranch();
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
52
|
+
const position = new Map(path.map((entry, i) => [entry.id, i]));
|
|
53
|
+
const latest = path.map((entry) => entry.type).lastIndexOf("compaction");
|
|
54
|
+
const previous = latest >= 0 ? path[latest] : undefined;
|
|
55
|
+
let summaryTokens = 0;
|
|
56
|
+
let tailTokens = 0;
|
|
57
|
+
let tailExchanges = 0;
|
|
58
|
+
const scanned = [];
|
|
59
|
+
for (const { sourceEntry, messages } of child.buildSessionProjection().entries) {
|
|
60
|
+
const tokens = messages.reduce((sum, message) => (message.role === "system" ? sum : sum + estimateTokens(message)), 0);
|
|
61
|
+
const startsExchange = sourceEntry.type === "message" && sourceEntry.message.role === "user" && messages.length > 0;
|
|
62
|
+
if (sourceEntry.id === previous?.id)
|
|
63
|
+
summaryTokens += tokens;
|
|
64
|
+
else if ((position.get(sourceEntry.id) ?? -1) < latest) {
|
|
65
|
+
// The retained tail: journal entries BEFORE the compaction that pi still projects.
|
|
66
|
+
tailTokens += tokens;
|
|
67
|
+
if (startsExchange)
|
|
68
|
+
tailExchanges++;
|
|
119
69
|
}
|
|
70
|
+
else
|
|
71
|
+
scanned.push({ id: sourceEntry.id, tokens, startsExchange });
|
|
120
72
|
}
|
|
121
|
-
const scanned = path.slice(scanFrom);
|
|
122
|
-
// The compaction's own summary + retained tail reach the model regardless of where the window
|
|
123
|
-
// lands, so they charge the budget as a base cost — not estimating them would over-admit.
|
|
124
|
-
const baseTokens = estimateCompactionTokens(path, scanFrom - 1);
|
|
125
73
|
const starts = [];
|
|
126
74
|
scanned.forEach((entry, i) => {
|
|
127
|
-
if (
|
|
75
|
+
if (entry.startsExchange)
|
|
128
76
|
starts.push(i);
|
|
129
77
|
});
|
|
130
|
-
if (starts.length
|
|
131
|
-
return; //
|
|
78
|
+
if (starts.length === 0)
|
|
79
|
+
return; // no exchange after the compaction to place a mark at
|
|
132
80
|
const suffixTokens = new Array(scanned.length + 1).fill(0);
|
|
133
81
|
for (let i = scanned.length - 1; i >= 0; i--) {
|
|
134
|
-
|
|
135
|
-
suffixTokens[i] = (suffixTokens[i + 1] ?? 0) + (entry ? estimateEntryTokens(entry) : 0);
|
|
82
|
+
suffixTokens[i] = (suffixTokens[i + 1] ?? 0) + (scanned[i]?.tokens ?? 0);
|
|
136
83
|
}
|
|
84
|
+
const fits = (exchanges, tokens) => exchanges <= INHERIT_MAX_EXCHANGES && tokens <= INHERIT_MAX_TOKENS;
|
|
85
|
+
const everything = summaryTokens + tailTokens + (suffixTokens[0] ?? 0);
|
|
86
|
+
if (fits(tailExchanges + starts.length, everything))
|
|
87
|
+
return; // the whole visible history fits the window
|
|
88
|
+
// Cut: the tail goes, the summary stays (carried below). The newest exchange is the floor.
|
|
137
89
|
let chosen = starts.length - 1;
|
|
138
90
|
for (let k = starts.length - 2; k >= 0; k--) {
|
|
139
|
-
|
|
140
|
-
const startIdx = starts[k];
|
|
141
|
-
if (startIdx === undefined)
|
|
142
|
-
break;
|
|
143
|
-
if (exchanges > INHERIT_MAX_EXCHANGES || baseTokens + (suffixTokens[startIdx] ?? 0) > INHERIT_MAX_TOKENS)
|
|
91
|
+
if (!fits(starts.length - k, summaryTokens + (suffixTokens[starts[k] ?? 0] ?? 0)))
|
|
144
92
|
break;
|
|
145
93
|
chosen = k;
|
|
146
94
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const boundaryIdx = starts[chosen];
|
|
150
|
-
if (boundaryIdx === undefined)
|
|
95
|
+
// Without a compaction, a mark at the first exchange would hide nothing that costs tokens.
|
|
96
|
+
if (!previous && chosen === 0)
|
|
151
97
|
return;
|
|
152
|
-
const boundary = scanned[
|
|
98
|
+
const boundary = scanned[starts[chosen] ?? 0];
|
|
153
99
|
if (boundary === undefined)
|
|
154
100
|
return;
|
|
155
|
-
|
|
101
|
+
const hidden = tailExchanges + chosen;
|
|
102
|
+
const note = hidden > 0
|
|
103
|
+
? `Inherited from the parent conversation; ${hidden} earlier exchange(s) are not shown.`
|
|
104
|
+
: "Inherited from the parent conversation; earlier messages are not shown.";
|
|
105
|
+
child.appendCompaction(previous ? `${previous.summary}\n\n${note}` : note, boundary.id, everything);
|
|
156
106
|
}
|
|
157
|
-
/**
|
|
158
|
-
* The branch point this inheritance should copy up to, and the parent's path — the decision half,
|
|
159
|
-
* shared by both backends because WHERE a thread branches is policy, not storage.
|
|
160
|
-
*/
|
|
107
|
+
/** The branch point this inheritance should copy up to, and the parent's path. */
|
|
161
108
|
export function inheritanceCut(parent, branchHints) {
|
|
162
109
|
const path = parent.getBranch();
|
|
163
110
|
const leaf = path[path.length - 1];
|
|
@@ -171,22 +118,16 @@ export function inheritanceCut(parent, branchHints) {
|
|
|
171
118
|
return { at: at ?? leaf.id };
|
|
172
119
|
}
|
|
173
120
|
/**
|
|
174
|
-
* Copy the parent's path up to `at` into `child`, entry by entry — what a backend with no FILE to
|
|
175
|
-
*
|
|
176
|
-
* the session name) are not copied: they describe the parent's record, not the thread's history.
|
|
177
|
-
*
|
|
178
|
-
* The COPY only. Bounding what the child's model sees is {@link markInheritanceWindow}, which only
|
|
179
|
-
* {@link copyBranchForInheritance} applies: a lifecycle fork that marked a window would hide the
|
|
180
|
-
* exact entries its user forked to keep.
|
|
121
|
+
* Copy the parent's path up to `at` into `child`, entry by entry — what a backend with no FILE to fork has to do
|
|
122
|
+
* instead.
|
|
181
123
|
*/
|
|
182
124
|
export function copyBranchInto(parent, child, at) {
|
|
183
|
-
/**
|
|
184
|
-
*
|
|
125
|
+
/**
|
|
126
|
+
* Parent entry id → the child's id for that entry: the copy mints its own, and a compaction points BACK into the
|
|
127
|
+
* path it was appended to.
|
|
128
|
+
*/
|
|
185
129
|
const copied = new Map();
|
|
186
|
-
/** Ids of entries this copy did NOT append.
|
|
187
|
-
* pi walks the cut point backwards onto the metadata entries adjacent to it, which carry no
|
|
188
|
-
* context. They resolve to the next entry that survived — the retained tail starts there — so a
|
|
189
|
-
* dropped anchor moves the boundary by an invisible entry instead of erasing the whole tail. */
|
|
130
|
+
/** Ids of entries this copy did NOT append. */
|
|
190
131
|
let unanchored = [];
|
|
191
132
|
const record = (parentId, childId) => {
|
|
192
133
|
for (const id of unanchored)
|
|
@@ -194,50 +135,57 @@ export function copyBranchInto(parent, child, at) {
|
|
|
194
135
|
unanchored = [];
|
|
195
136
|
copied.set(parentId, childId);
|
|
196
137
|
};
|
|
197
|
-
|
|
198
|
-
|
|
138
|
+
// Typed by pi's own union, and exhaustive: a pi release that adds an entry type fails typecheck here instead of
|
|
139
|
+
// being dropped from every copied branch without a word.
|
|
140
|
+
for (const entry of parent.getBranch(at)) {
|
|
199
141
|
let childId;
|
|
200
142
|
switch (entry.type) {
|
|
201
143
|
case "message":
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
144
|
+
// Every message, INCLUDING the engine's system entries: the child starts from the prompt the parent was
|
|
145
|
+
// running under, and pi diffs its own sections against it, so the thread's first request carries that
|
|
146
|
+
// prompt once rather than twice.
|
|
147
|
+
childId = child.appendMessage(entry.message);
|
|
205
148
|
break;
|
|
206
149
|
case "custom_message":
|
|
207
|
-
// Model-visible history, unlike the `custom` entries below it: an extension injected it INTO
|
|
208
|
-
//
|
|
209
|
-
childId = child.appendCustomMessageEntry(entry.customType
|
|
150
|
+
// Model-visible history, unlike the `custom` entries below it: an extension injected it INTO the
|
|
151
|
+
// conversation, and the assistant messages answering it are being copied.
|
|
152
|
+
childId = child.appendCustomMessageEntry(entry.customType, entry.content, entry.display, entry.details);
|
|
210
153
|
break;
|
|
211
154
|
case "compaction":
|
|
212
|
-
// `firstKeptEntryId` is where the RETAINED TAIL starts — the entries pi did not summarize,
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
// lands wherever the turn ended) the child's first request opened with a tool result whose
|
|
216
|
-
// call had been summarized away, which every provider rejects. An id the copy never saw
|
|
217
|
-
// keeps nothing, which is what pi itself does with a pointer it cannot resolve.
|
|
218
|
-
childId = child.appendCompaction(entry.summary ?? "", copied.get(entry.firstKeptEntryId ?? "") ?? "", entry.tokensBefore ?? 0, entry.details);
|
|
155
|
+
// `firstKeptEntryId` is where the RETAINED TAIL starts — the entries pi did not summarize, which still reach
|
|
156
|
+
// the model.
|
|
157
|
+
childId = child.appendCompaction(entry.summary, copied.get(entry.firstKeptEntryId) ?? "", entry.tokensBefore, entry.details);
|
|
219
158
|
break;
|
|
159
|
+
case "context_edit": {
|
|
160
|
+
// pi omits an abandoned retry/overflow attempt from the model's context this way; dropping the edit would
|
|
161
|
+
// hand that attempt back to the copy. Its target is a message, which the copy always anchors.
|
|
162
|
+
const target = copied.get(entry.targetId);
|
|
163
|
+
if (target)
|
|
164
|
+
childId = child.appendContextEdit(target, entry.replacement);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
220
167
|
case "model_change":
|
|
221
|
-
|
|
222
|
-
childId = child.appendModelChange(entry.provider, entry.modelId);
|
|
168
|
+
childId = child.appendModelChange(entry.provider, entry.modelId);
|
|
223
169
|
break;
|
|
224
170
|
case "thinking_level_change":
|
|
225
|
-
|
|
226
|
-
childId = child.appendThinkingLevelChange(entry.thinkingLevel);
|
|
171
|
+
childId = child.appendThinkingLevelChange(entry.thinkingLevel);
|
|
227
172
|
break;
|
|
228
173
|
case "custom":
|
|
229
|
-
// The plane's markers describe the parent's RECORD, not the thread's history
|
|
230
|
-
|
|
231
|
-
// idempotency check reads that value), and a copied leaf anchor would pin the child's head
|
|
232
|
-
// to a position its own history never chose. Every other custom entry is history and travels
|
|
233
|
-
// — the engine's tool-activation delta above all, since the copied assistant messages call
|
|
234
|
-
// the tools it records.
|
|
235
|
-
if (entry.customType && !isPlaneMarker(entry)) {
|
|
174
|
+
// The plane's markers describe the parent's RECORD, not the thread's history.
|
|
175
|
+
if (!isPlaneMarker(entry))
|
|
236
176
|
childId = child.appendCustomEntry(entry.customType, entry.data);
|
|
237
|
-
}
|
|
238
177
|
break;
|
|
239
|
-
|
|
240
|
-
|
|
178
|
+
case "label":
|
|
179
|
+
case "session_info":
|
|
180
|
+
case "branch_summary":
|
|
181
|
+
case "usage":
|
|
182
|
+
break; // the parent's facts, not the thread's history
|
|
183
|
+
default: {
|
|
184
|
+
// Unreachable by type; a record written by a newer pi can still carry one, and refusing to start the thread
|
|
185
|
+
// over it would be worse than naming it.
|
|
186
|
+
const unhandled = entry;
|
|
187
|
+
log.warn(`[fastagent] inheritance skipped a session entry of unknown type "${unhandled.type}"`);
|
|
188
|
+
}
|
|
241
189
|
}
|
|
242
190
|
if (childId === undefined)
|
|
243
191
|
unanchored.push(entry.id);
|
|
@@ -245,17 +193,12 @@ export function copyBranchInto(parent, child, at) {
|
|
|
245
193
|
record(entry.id, childId);
|
|
246
194
|
}
|
|
247
195
|
}
|
|
248
|
-
/** {@link copyBranchInto} plus the inheritance window — what a new THREAD gets and a fork does not.
|
|
249
|
-
* Both backends call THIS one, so neither can drift on where a child's context begins. */
|
|
196
|
+
/** {@link copyBranchInto} plus the inheritance window — what a new THREAD gets and a fork does not. */
|
|
250
197
|
export function copyBranchForInheritance(parent, child, at) {
|
|
251
198
|
copyBranchInto(parent, child, at);
|
|
252
199
|
markInheritanceWindow(child);
|
|
253
200
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
* not exist, and the failure reads as retryable for a condition no retry can change. Copying entries
|
|
259
|
-
* is what a backend with no file to fork has to do anyway, so both share these functions and one
|
|
260
|
-
* semantics; a fork is not hot enough to buy a second path back.
|
|
261
|
-
*/
|
|
201
|
+
// There is deliberately NO file-level fork here. pi can copy a path into a new file (`createBranchedSession` +
|
|
202
|
+
// `forkFrom`), but that pair writes the intermediate only when the copied path contains an ASSISTANT message — so
|
|
203
|
+
// forking at a user entry hands `forkFrom` a path that does not exist, and the failure reads as retryable for a
|
|
204
|
+
// condition no retry can change.
|
|
@@ -1,48 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which journal entries are POSITIONS
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* back missing something. Hence one module, imported by both, depending on neither.
|
|
2
|
+
* Which journal entries are POSITIONS, which are the control plane's own bookkeeping, and which are turns in the
|
|
3
|
+
* CONVERSATION. pi's journal has one shape for everything, so the plane writes what it needs to remember into the
|
|
4
|
+
* same log the conversation lives in (what fork a record is, and the anchor that makes a leaf move survive a
|
|
5
|
+
* reopen) — and since 0.86 the ENGINE writes its own bookkeeping there too, as the `system` entries carrying the
|
|
6
|
+
* assembled prompt. Three questions, one file: a reader of the journal answers them here or it answers them alone,
|
|
7
|
+
* which is how one reader came to count the prompt as history.
|
|
9
8
|
*/
|
|
10
9
|
import type { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
/** What pins a leaf move to disk. pi's `branch()` writes nothing
|
|
12
|
-
* `open()` puts it back on the file's last entry — so a move that no other write follows is
|
|
13
|
-
* forgotten. Carries no data: its PARENT is the position, which is the whole point of appending it. */
|
|
10
|
+
/** What pins a leaf move to disk. pi's `branch()` writes nothing. */
|
|
14
11
|
export declare const LEAF_ANCHOR = "fastagent.leaf";
|
|
15
12
|
/**
|
|
16
|
-
* The CONTROL PLANE's bookkeeping — not a place in a conversation, so never published, never
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* An EXACT list, not a `fastagent` prefix. The prefix was tried and was wrong for one entry, at a
|
|
20
|
-
* cost worth remembering: `fastagent:tool-activation` is written by the ENGINE and is thread history
|
|
21
|
-
* (which deferred tools this conversation discovered), so matching it here dropped it from every
|
|
22
|
-
* fork and every inherited thread — while the assistant messages that call those tools came along.
|
|
23
|
-
* A new marker joins this list deliberately, which is the point of it being a list.
|
|
13
|
+
* The CONTROL PLANE's bookkeeping — not a place in a conversation, so never published, never navigable, and never
|
|
14
|
+
* copied by a fork.
|
|
24
15
|
*/
|
|
25
16
|
export declare function isPlaneMarker(entry: {
|
|
26
17
|
type?: string;
|
|
27
18
|
customType?: string;
|
|
28
19
|
}): boolean;
|
|
29
|
-
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
|
|
20
|
+
/**
|
|
21
|
+
* An entry that is a TURN IN THE CONVERSATION — what a reader counting, previewing or searching the history means
|
|
22
|
+
* by "a message".
|
|
23
|
+
*
|
|
24
|
+
* The line it draws is the ENGINE's prompt state. Since pi 0.86 pi writes `system` messages into the same log the
|
|
25
|
+
* conversation lives in, carrying the assembled prompt (persona, project context, skill and tool descriptions)
|
|
26
|
+
* plus one more per prompt or tool-set change. They are bookkeeping, not something anyone said, and every reader
|
|
27
|
+
* of the journal has to decide about them. Deciding once, here, is the point: the first reader that forgot cut
|
|
28
|
+
* inheritance above every exchange and handed a new thread an empty history, with no diagnostic.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isConversationMessage(entry: {
|
|
33
31
|
type?: string;
|
|
34
|
-
|
|
32
|
+
message?: {
|
|
33
|
+
role?: string;
|
|
34
|
+
};
|
|
35
35
|
}): boolean;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* Not pi's leaf, which may be a marker — a solo leaf move anchors itself with one — so reporting
|
|
40
|
-
* pi's answer would hand back an id the client did not ask for and cannot find in `entries()`.
|
|
37
|
+
* Every position a client may move the branch head to, and everything `entries()` publishes — ONE predicate, so
|
|
38
|
+
* "anything published is navigable" holds by construction.
|
|
41
39
|
*/
|
|
40
|
+
export declare function isNavigable(entry: {
|
|
41
|
+
type?: string;
|
|
42
|
+
customType?: string;
|
|
43
|
+
}): boolean;
|
|
44
|
+
/** The head a client SEES: the last publishable entry on the active path. */
|
|
42
45
|
export declare function publishedLeaf(record: SessionManager): string | undefined;
|
|
43
46
|
/** Stamp a fresh fork with where it came from. */
|
|
44
47
|
export declare function stampProvenance(record: SessionManager, provenance: string): void;
|
|
45
|
-
/** What fork this record IS, or undefined for a record that was not forked.
|
|
46
|
-
* fork of a fork carries its own — and the whole journal is read rather than the active path, so a
|
|
47
|
-
* later leaf move cannot make a fork stop being one. */
|
|
48
|
+
/** What fork this record IS, or undefined for a record that was not forked. */
|
|
48
49
|
export declare function forkProvenance(record: SessionManager): string | undefined;
|
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
1
|
+
/**
|
|
2
|
+
* "This record is a fork of X at Y." Its own entry rather than pi's header `parentSession`, which pi fills from its
|
|
3
|
+
* own fork path and names a FILE, not the branch point idempotency needs.
|
|
4
|
+
*/
|
|
3
5
|
const FORK_PROVENANCE = "fastagent.fork";
|
|
4
|
-
/** What pins a leaf move to disk. pi's `branch()` writes nothing
|
|
5
|
-
* `open()` puts it back on the file's last entry — so a move that no other write follows is
|
|
6
|
-
* forgotten. Carries no data: its PARENT is the position, which is the whole point of appending it. */
|
|
6
|
+
/** What pins a leaf move to disk. pi's `branch()` writes nothing. */
|
|
7
7
|
export const LEAF_ANCHOR = "fastagent.leaf";
|
|
8
8
|
/**
|
|
9
|
-
* The CONTROL PLANE's bookkeeping — not a place in a conversation, so never published, never
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* An EXACT list, not a `fastagent` prefix. The prefix was tried and was wrong for one entry, at a
|
|
13
|
-
* cost worth remembering: `fastagent:tool-activation` is written by the ENGINE and is thread history
|
|
14
|
-
* (which deferred tools this conversation discovered), so matching it here dropped it from every
|
|
15
|
-
* fork and every inherited thread — while the assistant messages that call those tools came along.
|
|
16
|
-
* A new marker joins this list deliberately, which is the point of it being a list.
|
|
9
|
+
* The CONTROL PLANE's bookkeeping — not a place in a conversation, so never published, never navigable, and never
|
|
10
|
+
* copied by a fork.
|
|
17
11
|
*/
|
|
18
12
|
export function isPlaneMarker(entry) {
|
|
19
13
|
return entry.type === "custom" && (entry.customType === FORK_PROVENANCE || entry.customType === LEAF_ANCHOR);
|
|
20
14
|
}
|
|
21
|
-
/** Every position a client may move the branch head to, and everything `entries()` publishes — ONE
|
|
22
|
-
* predicate, so "anything published is navigable" holds by construction. `label` is metadata ABOUT
|
|
23
|
-
* an entry rather than a place; {@link isPlaneMarker} is ours rather than the conversation's. */
|
|
24
|
-
export function isNavigable(entry) {
|
|
25
|
-
return entry.type !== "label" && !isPlaneMarker(entry);
|
|
26
|
-
}
|
|
27
15
|
/**
|
|
28
|
-
*
|
|
16
|
+
* An entry that is a TURN IN THE CONVERSATION — what a reader counting, previewing or searching the history means
|
|
17
|
+
* by "a message".
|
|
29
18
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
19
|
+
* The line it draws is the ENGINE's prompt state. Since pi 0.86 pi writes `system` messages into the same log the
|
|
20
|
+
* conversation lives in, carrying the assembled prompt (persona, project context, skill and tool descriptions)
|
|
21
|
+
* plus one more per prompt or tool-set change. They are bookkeeping, not something anyone said, and every reader
|
|
22
|
+
* of the journal has to decide about them. Deciding once, here, is the point: the first reader that forgot cut
|
|
23
|
+
* inheritance above every exchange and handed a new thread an empty history, with no diagnostic.
|
|
32
24
|
*/
|
|
25
|
+
export function isConversationMessage(entry) {
|
|
26
|
+
return entry.type === "message" && entry.message?.role !== "system";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Every position a client may move the branch head to, and everything `entries()` publishes — ONE predicate, so
|
|
30
|
+
* "anything published is navigable" holds by construction.
|
|
31
|
+
*/
|
|
32
|
+
export function isNavigable(entry) {
|
|
33
|
+
return entry.type !== "label" && !isPlaneMarker(entry);
|
|
34
|
+
}
|
|
35
|
+
/** The head a client SEES: the last publishable entry on the active path. */
|
|
33
36
|
export function publishedLeaf(record) {
|
|
34
37
|
const path = record.getBranch();
|
|
35
38
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
@@ -43,9 +46,7 @@ export function publishedLeaf(record) {
|
|
|
43
46
|
export function stampProvenance(record, provenance) {
|
|
44
47
|
record.appendCustomEntry(FORK_PROVENANCE, { provenance });
|
|
45
48
|
}
|
|
46
|
-
/** What fork this record IS, or undefined for a record that was not forked.
|
|
47
|
-
* fork of a fork carries its own — and the whole journal is read rather than the active path, so a
|
|
48
|
-
* later leaf move cannot make a fork stop being one. */
|
|
49
|
+
/** What fork this record IS, or undefined for a record that was not forked. */
|
|
49
50
|
export function forkProvenance(record) {
|
|
50
51
|
let found;
|
|
51
52
|
for (const raw of record.getEntries()) {
|