@pi-in-go/pigpen-ahp 0.1.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/CREDITS.md +27 -0
- package/LICENSE +23 -0
- package/README.md +148 -0
- package/extensions/ahp/access_options_test.go +116 -0
- package/extensions/ahp/extension.go +54 -0
- package/extensions/ahp/go.mod +12 -0
- package/extensions/ahp/go.sum +2 -0
- package/extensions/ahp/go.work +8 -0
- package/extensions/ahp/internal/channels/chat.go +110 -0
- package/extensions/ahp/internal/channels/root.go +32 -0
- package/extensions/ahp/internal/channels/session.go +128 -0
- package/extensions/ahp/internal/channels/session_test.go +108 -0
- package/extensions/ahp/internal/compose/compose.go +153 -0
- package/extensions/ahp/internal/compose/compose_test.go +120 -0
- package/extensions/ahp/internal/gaps/gaps_test.go +88 -0
- package/extensions/ahp/internal/host/handshake_test.go +206 -0
- package/extensions/ahp/internal/host/host.go +1305 -0
- package/extensions/ahp/internal/host/reconnect_test.go +197 -0
- package/extensions/ahp/internal/host/replaywindow_test.go +31 -0
- package/extensions/ahp/internal/host/schema_test.go +79 -0
- package/extensions/ahp/internal/host/store.go +291 -0
- package/extensions/ahp/internal/host/store_update_test.go +85 -0
- package/extensions/ahp/internal/host/subscriptions_test.go +160 -0
- package/extensions/ahp/internal/host/surface_test.go +95 -0
- package/extensions/ahp/internal/host/workarounds.go +458 -0
- package/extensions/ahp/internal/host/workarounds_test.go +96 -0
- package/extensions/ahp/internal/live/live.go +328 -0
- package/extensions/ahp/internal/live/live_test.go +91 -0
- package/extensions/ahp/internal/live/store.go +113 -0
- package/extensions/ahp/internal/mapper/activity.go +199 -0
- package/extensions/ahp/internal/mapper/activity_test.go +188 -0
- package/extensions/ahp/internal/mapper/bench_test.go +38 -0
- package/extensions/ahp/internal/mapper/event_mapper_test.go +380 -0
- package/extensions/ahp/internal/mapper/fixtures_test.go +387 -0
- package/extensions/ahp/internal/mapper/helpers_test.go +251 -0
- package/extensions/ahp/internal/mapper/js.go +131 -0
- package/extensions/ahp/internal/mapper/mapper.go +600 -0
- package/extensions/ahp/internal/mapper/message_input_test.go +196 -0
- package/extensions/ahp/internal/mapper/messageinput.go +272 -0
- package/extensions/ahp/internal/mapper/schema_helpers_test.go +5 -0
- package/extensions/ahp/internal/mapper/testdata/LICENSE-pi-ahp +21 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/abort.json +711 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/bash-long-output.json +1312 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/compaction.json +6285 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/parallel-tools.json +2622 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/plain-text.json +1856 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/single-tool.json +1128 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/steering.json +3926 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-bash.json +2372 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-edit.json +3983 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-error.json +2850 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-find.json +1162 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-grep.json +2783 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-loop.json +1693 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-ls.json +2427 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-write.json +1271 -0
- package/extensions/ahp/internal/mapper/title.go +34 -0
- package/extensions/ahp/internal/mapper/usermsg.go +87 -0
- package/extensions/ahp/internal/pi/activeturn_test.go +296 -0
- package/extensions/ahp/internal/pi/backend.go +64 -0
- package/extensions/ahp/internal/pi/catalogue.go +370 -0
- package/extensions/ahp/internal/pi/catalogue_test.go +271 -0
- package/extensions/ahp/internal/pi/chatdriver.go +533 -0
- package/extensions/ahp/internal/pi/chatdriver_test.go +416 -0
- package/extensions/ahp/internal/pi/clientactions.go +212 -0
- package/extensions/ahp/internal/pi/clientactions_test.go +273 -0
- package/extensions/ahp/internal/pi/completions.go +230 -0
- package/extensions/ahp/internal/pi/completions_test.go +272 -0
- package/extensions/ahp/internal/pi/config_test.go +269 -0
- package/extensions/ahp/internal/pi/deletesession.go +69 -0
- package/extensions/ahp/internal/pi/deletesession_test.go +153 -0
- package/extensions/ahp/internal/pi/disposal_test.go +362 -0
- package/extensions/ahp/internal/pi/fixture_test.go +308 -0
- package/extensions/ahp/internal/pi/foreign_test.go +122 -0
- package/extensions/ahp/internal/pi/harness_test.go +83 -0
- package/extensions/ahp/internal/pi/history.go +269 -0
- package/extensions/ahp/internal/pi/hydrated_test.go +171 -0
- package/extensions/ahp/internal/pi/hydratedlifecycle_test.go +196 -0
- package/extensions/ahp/internal/pi/hydration_test.go +209 -0
- package/extensions/ahp/internal/pi/hydrator.go +212 -0
- package/extensions/ahp/internal/pi/imageinput.go +224 -0
- package/extensions/ahp/internal/pi/imageinput_test.go +57 -0
- package/extensions/ahp/internal/pi/lifecycle_test.go +381 -0
- package/extensions/ahp/internal/pi/models.go +212 -0
- package/extensions/ahp/internal/pi/models_test.go +128 -0
- package/extensions/ahp/internal/pi/paging.go +95 -0
- package/extensions/ahp/internal/pi/pagingtruncate_test.go +323 -0
- package/extensions/ahp/internal/pi/projecttrust.go +222 -0
- package/extensions/ahp/internal/pi/registry.go +924 -0
- package/extensions/ahp/internal/pi/restart_test.go +185 -0
- package/extensions/ahp/internal/pi/services.go +70 -0
- package/extensions/ahp/internal/pi/sessionconfig.go +64 -0
- package/extensions/ahp/internal/pi/sessionfiles_test.go +74 -0
- package/extensions/ahp/internal/pi/sessionstore.go +83 -0
- package/extensions/ahp/internal/pi/summary_test.go +426 -0
- package/extensions/ahp/internal/pi/workarounds_wire_test.go +208 -0
- package/extensions/ahp/internal/pisession/fromentries_test.go +32 -0
- package/extensions/ahp/internal/pisession/json.go +31 -0
- package/extensions/ahp/internal/pisession/pisession.go +580 -0
- package/extensions/ahp/internal/settings/access.go +63 -0
- package/extensions/ahp/internal/settings/access_test.go +52 -0
- package/extensions/ahp/internal/settings/settings.go +117 -0
- package/extensions/ahp/internal/settings/settings_test.go +104 -0
- package/extensions/ahp/internal/svc/etag_other.go +15 -0
- package/extensions/ahp/internal/svc/etag_unix.go +34 -0
- package/extensions/ahp/internal/svc/glob.go +197 -0
- package/extensions/ahp/internal/svc/mime.go +37 -0
- package/extensions/ahp/internal/svc/paths.go +108 -0
- package/extensions/ahp/internal/svc/pty.go +64 -0
- package/extensions/ahp/internal/svc/pty_darwin.go +42 -0
- package/extensions/ahp/internal/svc/pty_linux.go +36 -0
- package/extensions/ahp/internal/svc/pty_other.go +9 -0
- package/extensions/ahp/internal/svc/pty_test.go +114 -0
- package/extensions/ahp/internal/svc/pty_unix.go +147 -0
- package/extensions/ahp/internal/svc/resource.go +495 -0
- package/extensions/ahp/internal/svc/resource_test.go +501 -0
- package/extensions/ahp/internal/svc/stat_bsd.go +9 -0
- package/extensions/ahp/internal/svc/stat_linux.go +9 -0
- package/extensions/ahp/internal/svc/terminal.go +454 -0
- package/extensions/ahp/internal/svc/terminal_test.go +512 -0
- package/extensions/ahp/internal/svc/watch.go +590 -0
- package/extensions/ahp/internal/svc/watch_test.go +627 -0
- package/extensions/ahp/internal/svc/watchevents_test.go +452 -0
- package/extensions/ahp/internal/svc/watchpolicy.go +81 -0
- package/extensions/ahp/internal/svc/watchpolicy_test.go +151 -0
- package/extensions/ahp/internal/svc/watchracy_test.go +61 -0
- package/extensions/ahp/internal/testkit/schema/LICENSE-agent-host-protocol +21 -0
- package/extensions/ahp/internal/testkit/schema/actions.schema.json +9208 -0
- package/extensions/ahp/internal/testkit/schema/commands.schema.json +10862 -0
- package/extensions/ahp/internal/testkit/schema/errors.schema.json +10928 -0
- package/extensions/ahp/internal/testkit/schema/notifications.schema.json +6667 -0
- package/extensions/ahp/internal/testkit/schema/state.schema.json +6392 -0
- package/extensions/ahp/internal/testkit/schema.go +360 -0
- package/extensions/ahp/internal/testkit/testkit.go +367 -0
- package/extensions/ahp/internal/twin/twin.go +27 -0
- package/extensions/ahp/internal/wire/dispatchable.go +106 -0
- package/extensions/ahp/internal/wire/dispatchable_list.go +15 -0
- package/extensions/ahp/internal/wire/helpers_test.go +5 -0
- package/extensions/ahp/internal/wire/wire.go +218 -0
- package/extensions/ahp/internal/wire/wire_test.go +123 -0
- package/extensions/ahp/internal/ws/helpers_test.go +7 -0
- package/extensions/ahp/internal/ws/vscode_test.go +198 -0
- package/extensions/ahp/internal/ws/ws.go +674 -0
- package/extensions/ahp/internal/ws/ws_test.go +324 -0
- package/extensions/ahp/realpig_test.go +589 -0
- package/extensions/ahp/runtime.go +311 -0
- package/extensions/ahp/runtime_test.go +110 -0
- package/extensions/ahp/testdata/upstream-tests.json +3833 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/LICENSE +21 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/NOTICE-PIGPEN.md +18 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/client.go +1011 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/error.go +111 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/multi_host_state_mirror.go +239 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/reducers.go +1939 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/transport.go +176 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/actions.generated.go +2447 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/commands.generated.go +1546 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/common.go +205 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/errors.generated.go +65 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/messages.generated.go +138 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/notifications.generated.go +264 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/state.generated.go +6171 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/version.generated.go +31 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/go.mod +3 -0
- package/extensions/ahp/twins_test.go +91 -0
- package/package.json +40 -0
- package/proof/PORT.md +121 -0
- package/proof/mutations.json +152 -0
- package/proof/oracle/LICENSE +21 -0
- package/proof/oracle/README.md +120 -0
- package/proof/oracle/UPSTREAM.md +7 -0
- package/proof/oracle/package.json +68 -0
- package/proof/oracle/src/bin/cli.ts +66 -0
- package/proof/oracle/src/bin/tunnel.ts +128 -0
- package/proof/oracle/src/channels/chat.ts +134 -0
- package/proof/oracle/src/channels/root.ts +39 -0
- package/proof/oracle/src/channels/session.ts +118 -0
- package/proof/oracle/src/channels/terminal.ts +11 -0
- package/proof/oracle/src/core/channels.ts +108 -0
- package/proof/oracle/src/core/client-workarounds.ts +358 -0
- package/proof/oracle/src/core/connection.ts +41 -0
- package/proof/oracle/src/core/host.ts +882 -0
- package/proof/oracle/src/core/sequencer.ts +75 -0
- package/proof/oracle/src/core/state-store.ts +162 -0
- package/proof/oracle/src/core/uri.ts +25 -0
- package/proof/oracle/src/host/direct-settings.ts +110 -0
- package/proof/oracle/src/host/pi-host.ts +220 -0
- package/proof/oracle/src/host/serve.ts +71 -0
- package/proof/oracle/src/host/terminal-service.ts +346 -0
- package/proof/oracle/src/pi/activity.ts +171 -0
- package/proof/oracle/src/pi/changeset-service.ts +646 -0
- package/proof/oracle/src/pi/changeset-uri.ts +64 -0
- package/proof/oracle/src/pi/chat-driver.ts +529 -0
- package/proof/oracle/src/pi/completions.ts +160 -0
- package/proof/oracle/src/pi/delete-session.ts +53 -0
- package/proof/oracle/src/pi/event-mapper.ts +648 -0
- package/proof/oracle/src/pi/git-changes.ts +605 -0
- package/proof/oracle/src/pi/history.ts +305 -0
- package/proof/oracle/src/pi/image-input.ts +56 -0
- package/proof/oracle/src/pi/image-mime.ts +8 -0
- package/proof/oracle/src/pi/in-process-backend.ts +155 -0
- package/proof/oracle/src/pi/message-input.ts +212 -0
- package/proof/oracle/src/pi/models.ts +124 -0
- package/proof/oracle/src/pi/project-trust.ts +66 -0
- package/proof/oracle/src/pi/provider.ts +2 -0
- package/proof/oracle/src/pi/resource-paths.ts +80 -0
- package/proof/oracle/src/pi/resource-service.ts +356 -0
- package/proof/oracle/src/pi/resource-watch-policy.ts +35 -0
- package/proof/oracle/src/pi/resource-watch.ts +357 -0
- package/proof/oracle/src/pi/session-catalogue.ts +318 -0
- package/proof/oracle/src/pi/session-config.ts +84 -0
- package/proof/oracle/src/pi/session-history.ts +75 -0
- package/proof/oracle/src/pi/session-hydrator.ts +219 -0
- package/proof/oracle/src/pi/session-registry.ts +815 -0
- package/proof/oracle/src/pi/session-storage.ts +29 -0
- package/proof/oracle/src/pi/session-title.ts +18 -0
- package/proof/oracle/src/pi/turn-paging.ts +94 -0
- package/proof/oracle/src/pi/user-message.ts +66 -0
- package/proof/oracle/src/protocol/errors.ts +44 -0
- package/proof/oracle/src/protocol/jsonrpc.ts +89 -0
- package/proof/oracle/src/protocol/version.ts +39 -0
- package/proof/oracle/src/transport/websocket.ts +136 -0
- package/proof/oracle/src/tunnel/devtunnel.ts +293 -0
- package/proof/oracle/src/tunnel/discovery.ts +37 -0
- package/proof/oracle/test/active-turn-reconnect.test.ts +274 -0
- package/proof/oracle/test/activity.test.ts +171 -0
- package/proof/oracle/test/changeset-lifecycle.test.serial.ts +303 -0
- package/proof/oracle/test/changeset-uri.test.ts +37 -0
- package/proof/oracle/test/changeset.test.serial.ts +487 -0
- package/proof/oracle/test/chat-driver.test.ts +691 -0
- package/proof/oracle/test/client-actions.test.ts +409 -0
- package/proof/oracle/test/client-workarounds.test.ts +320 -0
- package/proof/oracle/test/completions.test.ts +323 -0
- package/proof/oracle/test/delete-session.test.ts +142 -0
- package/proof/oracle/test/direct-settings.test.ts +81 -0
- package/proof/oracle/test/event-mapper.test.ts +619 -0
- package/proof/oracle/test/fetch-turns.test.ts +207 -0
- package/proof/oracle/test/fixtures/abort.json +711 -0
- package/proof/oracle/test/fixtures/bash-long-output.json +1312 -0
- package/proof/oracle/test/fixtures/compaction.json +6285 -0
- package/proof/oracle/test/fixtures/parallel-tools.json +2622 -0
- package/proof/oracle/test/fixtures/plain-text.json +1856 -0
- package/proof/oracle/test/fixtures/single-tool.json +1128 -0
- package/proof/oracle/test/fixtures/steering.json +3926 -0
- package/proof/oracle/test/fixtures/tool-bash.json +2372 -0
- package/proof/oracle/test/fixtures/tool-edit.json +3983 -0
- package/proof/oracle/test/fixtures/tool-error.json +2850 -0
- package/proof/oracle/test/fixtures/tool-find.json +1162 -0
- package/proof/oracle/test/fixtures/tool-grep.json +2783 -0
- package/proof/oracle/test/fixtures/tool-loop.json +1693 -0
- package/proof/oracle/test/fixtures/tool-ls.json +2427 -0
- package/proof/oracle/test/fixtures/tool-write.json +1271 -0
- package/proof/oracle/test/handshake.test.ts +275 -0
- package/proof/oracle/test/harness.ts +153 -0
- package/proof/oracle/test/hydrated-session-lifecycle.test.ts +211 -0
- package/proof/oracle/test/image-input.test.ts +28 -0
- package/proof/oracle/test/image-session.test.ts +69 -0
- package/proof/oracle/test/live-turn.test.ts +304 -0
- package/proof/oracle/test/mapper-fixtures.test.ts +293 -0
- package/proof/oracle/test/message-input.test.ts +181 -0
- package/proof/oracle/test/model-discovery.test.ts +170 -0
- package/proof/oracle/test/models.test.ts +109 -0
- package/proof/oracle/test/pi-host.test.ts +91 -0
- package/proof/oracle/test/pi-replay.test.ts +141 -0
- package/proof/oracle/test/project-trust.test.ts +125 -0
- package/proof/oracle/test/protocol-surface.test.ts +121 -0
- package/proof/oracle/test/pty.test.ts +100 -0
- package/proof/oracle/test/reconnect.test.ts +389 -0
- package/proof/oracle/test/resource-watch-policy.test.ts +78 -0
- package/proof/oracle/test/resource-watch.test.serial.ts +536 -0
- package/proof/oracle/test/resource.test.ts +499 -0
- package/proof/oracle/test/schema.test.ts +88 -0
- package/proof/oracle/test/session-catalogue.test.ts +314 -0
- package/proof/oracle/test/session-config.test.ts +224 -0
- package/proof/oracle/test/session-disposal.test.ts +364 -0
- package/proof/oracle/test/session-hydration.test.ts +194 -0
- package/proof/oracle/test/session-lifecycle.test.ts +416 -0
- package/proof/oracle/test/session-storage.test.serial.ts +32 -0
- package/proof/oracle/test/session-summary.test.ts +418 -0
- package/proof/oracle/test/subscriptions.test.ts +198 -0
- package/proof/oracle/test/support/assertions.ts +32 -0
- package/proof/oracle/test/support/async.ts +26 -0
- package/proof/oracle/test/support/hydrated-session.ts +205 -0
- package/proof/oracle/test/support/images.ts +8 -0
- package/proof/oracle/test/support/recorded-fixtures.ts +60 -0
- package/proof/oracle/test/support/recorded-scenarios.ts +145 -0
- package/proof/oracle/test/support/replay.ts +194 -0
- package/proof/oracle/test/support/schema.ts +221 -0
- package/proof/oracle/test/support/session-files.ts +12 -0
- package/proof/oracle/test/support/session-storage.ts +22 -0
- package/proof/oracle/test/support/upstream.ts +25 -0
- package/proof/oracle/test/support/watch-events.ts +108 -0
- package/proof/oracle/test/terminal-service.test.ts +495 -0
- package/proof/oracle/test/truncate.test.ts +230 -0
- package/proof/oracle/test/tunnel.test.ts +258 -0
- package/proof/oracle/test/upstream-workarounds.test.ts +37 -0
- package/proof/oracle/test/uri.test.ts +24 -0
- package/proof/oracle/test/watch-events.test.ts +118 -0
- package/proof/oracle/tsconfig.json +32 -0
- package/proof/proof-piglet/piglet.yaml +18 -0
- package/proof/tools/fakellm/go.mod +3 -0
- package/proof/tools/fakellm/main.go +109 -0
- package/proof/tools/gen-dispatchable.py +30 -0
- package/provenance.json +28 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
package pi_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/base64"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"reflect"
|
|
8
|
+
"strings"
|
|
9
|
+
"sync"
|
|
10
|
+
"testing"
|
|
11
|
+
|
|
12
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
|
|
13
|
+
|
|
14
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
|
|
15
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/pi"
|
|
16
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/pisession"
|
|
17
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/testkit"
|
|
18
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/twin"
|
|
19
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/wire"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
// restartBackend answers every prompt with an empty settled run.
|
|
23
|
+
type restartBackend struct {
|
|
24
|
+
mu sync.Mutex
|
|
25
|
+
prompts []string
|
|
26
|
+
listeners []func(mapper.Event)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func (b *restartBackend) Subscribe(l func(mapper.Event)) func() {
|
|
30
|
+
b.mu.Lock()
|
|
31
|
+
b.listeners = append(b.listeners, l)
|
|
32
|
+
b.mu.Unlock()
|
|
33
|
+
return func() {}
|
|
34
|
+
}
|
|
35
|
+
func (b *restartBackend) Prompt(_ context.Context, text string, _ []mapper.Image) error {
|
|
36
|
+
b.mu.Lock()
|
|
37
|
+
b.prompts = append(b.prompts, text)
|
|
38
|
+
ls := append([]func(mapper.Event){}, b.listeners...)
|
|
39
|
+
b.mu.Unlock()
|
|
40
|
+
for _, l := range ls {
|
|
41
|
+
l(mapper.Event{"type": "agent_start"})
|
|
42
|
+
l(mapper.Event{"type": "agent_settled"})
|
|
43
|
+
}
|
|
44
|
+
return nil
|
|
45
|
+
}
|
|
46
|
+
func (b *restartBackend) Steer(context.Context, string, []mapper.Image) error { return nil }
|
|
47
|
+
func (b *restartBackend) Abort(context.Context) error { return nil }
|
|
48
|
+
|
|
49
|
+
// Twin of upstream test/reconnect.test.ts "reconnect after host restart": the durable session
|
|
50
|
+
// written by a previous host is restored when a client that knew the old host reconnects.
|
|
51
|
+
func TestReconnectAfterHostRestart(t *testing.T) {
|
|
52
|
+
twin.Run(t, "reconnect", "repairs VS Code's channel-less reconnect and restores a durable session", func(t *testing.T) {
|
|
53
|
+
root, workspace := t.TempDir(), t.TempDir()
|
|
54
|
+
id := newID()
|
|
55
|
+
writer, err := pisession.Create(workspace, filepath.Join(root, "fixture"), pisession.Options{ID: id})
|
|
56
|
+
if err != nil {
|
|
57
|
+
t.Fatal(err)
|
|
58
|
+
}
|
|
59
|
+
if _, err := writer.AppendMessage(obj{"role": "user", "content": "before restart", "timestamp": 0}); err != nil {
|
|
60
|
+
t.Fatal(err)
|
|
61
|
+
}
|
|
62
|
+
if _, err := writer.AppendMessage(obj{"role": "assistant", "content": []any{obj{"type": "text", "text": "persisted reply"}}, "timestamp": 0}); err != nil {
|
|
63
|
+
t.Fatal(err)
|
|
64
|
+
}
|
|
65
|
+
backend := &restartBackend{}
|
|
66
|
+
h := startHarness(t, harnessOptions{sessionRoot: root, workingDir: workspace, createBackend: func(*pi.LiveSession) (pi.Backend, error) { return backend, nil }})
|
|
67
|
+
|
|
68
|
+
clientSession := "pi:/" + id
|
|
69
|
+
clientChat := "ahp-chat://default/" + base64.RawURLEncoding.EncodeToString([]byte(clientSession))
|
|
70
|
+
client := testkit.Connect(t, h.host)
|
|
71
|
+
t.Cleanup(client.Close)
|
|
72
|
+
// The reconnect names no channel, as VS Code's does.
|
|
73
|
+
var result struct {
|
|
74
|
+
Type string
|
|
75
|
+
Snapshots []struct {
|
|
76
|
+
Resource string
|
|
77
|
+
State map[string]any
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
client.Decode(client.Must("reconnect", obj{
|
|
81
|
+
"clientId": "vscode-from-previous-host", "lastSeenServerSeq": 42,
|
|
82
|
+
"subscriptions": []string{wire.RootChannel, clientSession}, "_meta": obj{"vscode.telemetryLevel": "off"},
|
|
83
|
+
}), &result)
|
|
84
|
+
if result.Type != "snapshot" {
|
|
85
|
+
t.Fatalf("type = %q", result.Type)
|
|
86
|
+
}
|
|
87
|
+
var resources []string
|
|
88
|
+
var sessionState map[string]any
|
|
89
|
+
for _, s := range result.Snapshots {
|
|
90
|
+
resources = append(resources, s.Resource)
|
|
91
|
+
if s.Resource == clientSession {
|
|
92
|
+
sessionState = s.State
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if !reflect.DeepEqual(sortedCopy(resources), sortedCopy([]string{wire.RootChannel, clientSession})) {
|
|
96
|
+
t.Fatalf("snapshots = %v", resources)
|
|
97
|
+
}
|
|
98
|
+
if sessionState["defaultChat"] != clientChat {
|
|
99
|
+
t.Fatalf("defaultChat = %v, want %s", sessionState["defaultChat"], clientChat)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
rpcErr := client.ExpectError("disposeSession", obj{"channel": clientSession}, wire.CodeInvalidRequest)
|
|
103
|
+
if !strings.Contains(rpcErr.Message, "VS Code provisional-session lifecycle bug") {
|
|
104
|
+
t.Fatalf("message = %q", rpcErr.Message)
|
|
105
|
+
}
|
|
106
|
+
if !h.host.Store().Has(wire.SessionURI(id)) {
|
|
107
|
+
t.Fatal("the durable session must survive the refused disposal")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
chat := snapshotState[ahptypes.ChatState](t, client, clientChat)
|
|
111
|
+
turn := chat.Turns[0]
|
|
112
|
+
if turn.Message.Text != "before restart" || turn.State != ahptypes.TurnStateComplete {
|
|
113
|
+
t.Fatalf("restored turn = %+v", turn)
|
|
114
|
+
}
|
|
115
|
+
var reply string
|
|
116
|
+
for _, p := range turn.ResponseParts {
|
|
117
|
+
if md, ok := p.Value.(*ahptypes.MarkdownResponsePart); ok {
|
|
118
|
+
reply = md.Content
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if reply != "persisted reply" {
|
|
122
|
+
t.Fatalf("reply = %q", reply)
|
|
123
|
+
}
|
|
124
|
+
if !h.host.Store().Has(wire.SessionURI(id)) || !h.host.Store().Has(wire.ChatURI(id)) {
|
|
125
|
+
t.Fatal("session and chat must both be restored")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
client.Dispatch(clientChat, obj{"type": "chat/turnStarted", "turnId": "after-restart", "startedAt": "2025-01-01T00:00:00.000Z", "message": userMessage("continue after restart")})
|
|
129
|
+
testkit.Eventually(t, "the post-restart prompt to reach the backend", func() bool { backend.mu.Lock(); defer backend.mu.Unlock(); return len(backend.prompts) == 1 })
|
|
130
|
+
if !reflect.DeepEqual(backend.prompts, []string{"continue after restart"}) {
|
|
131
|
+
t.Fatalf("prompts = %v", backend.prompts)
|
|
132
|
+
}
|
|
133
|
+
testkit.Eventually(t, "the resumed turn to settle", func() bool {
|
|
134
|
+
s := h.host.Store().Chat(wire.ChatURI(id))
|
|
135
|
+
return s.ActiveTurn == nil && len(s.Turns) == 2
|
|
136
|
+
})
|
|
137
|
+
state := h.host.Store().Chat(wire.ChatURI(id))
|
|
138
|
+
if state.Turns[1].Message.Text != "continue after restart" || state.Turns[1].State != ahptypes.TurnStateComplete {
|
|
139
|
+
t.Fatalf("turns = %+v", turnShapes(state.Turns))
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func sortedCopy(in []string) []string {
|
|
145
|
+
out := append([]string(nil), in...)
|
|
146
|
+
for i := range out {
|
|
147
|
+
for j := i + 1; j < len(out); j++ {
|
|
148
|
+
if out[j] < out[i] {
|
|
149
|
+
out[i], out[j] = out[j], out[i]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return out
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Twin of upstream test/session-storage.test.serial.ts.
|
|
157
|
+
func TestDefaultSessionStorage(t *testing.T) {
|
|
158
|
+
twin.Run(t, "session-storage", "keeps the default catalogue and Pi session writer on the same isolated profile", func(t *testing.T) {
|
|
159
|
+
agentDir, workspace := t.TempDir(), t.TempDir()
|
|
160
|
+
catalogueRoot, create := pi.DefaultStorage(agentDir)
|
|
161
|
+
sessionID := newID()
|
|
162
|
+
store, err := create(workspace, sessionID)
|
|
163
|
+
if err != nil {
|
|
164
|
+
t.Fatal(err)
|
|
165
|
+
}
|
|
166
|
+
manager := store.(*pisession.Manager)
|
|
167
|
+
if _, err := manager.AppendMessage(obj{"role": "user", "content": "hello", "timestamp": 0}); err != nil {
|
|
168
|
+
t.Fatal(err)
|
|
169
|
+
}
|
|
170
|
+
if _, err := manager.AppendMessage(obj{"role": "assistant", "content": []any{obj{"type": "text", "text": "hello"}}, "timestamp": 0}); err != nil {
|
|
171
|
+
t.Fatal(err)
|
|
172
|
+
}
|
|
173
|
+
file := store.File()
|
|
174
|
+
if file == "" {
|
|
175
|
+
t.Fatal("no session file")
|
|
176
|
+
}
|
|
177
|
+
found, err := pi.NewCatalogue(catalogueRoot).FindSessionFile(sessionID)
|
|
178
|
+
if err != nil || found != file {
|
|
179
|
+
t.Fatalf("catalogue found %q (%v), writer wrote %q", found, err, file)
|
|
180
|
+
}
|
|
181
|
+
if rel, err := filepath.Rel(filepath.Join(agentDir, "sessions"), file); err != nil || strings.HasPrefix(rel, "..") {
|
|
182
|
+
t.Fatalf("file %s is outside the profile (%q, %v)", file, rel, err)
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
package pi
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
|
|
7
|
+
|
|
8
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/host"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// ServicesOptions wire the session services together.
|
|
12
|
+
type ServicesOptions struct {
|
|
13
|
+
Host *host.Host
|
|
14
|
+
// SessionRoot is where Pi's session files live (one sub-directory per working directory).
|
|
15
|
+
SessionRoot string
|
|
16
|
+
DefaultWorkingDirectory string
|
|
17
|
+
CreateBackend BackendFactory
|
|
18
|
+
CreateSessionManager SessionManagerFactory
|
|
19
|
+
DefaultSelection func() *ahptypes.ModelSelection
|
|
20
|
+
DeleteFile func(path string) (SessionFileDeletionResult, error)
|
|
21
|
+
WrapStore func(sessionID string, opened SessionStore) SessionStore
|
|
22
|
+
AdoptForeignTurns bool
|
|
23
|
+
Log func(format string, args ...any)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Services is the registry, catalogue and hydrator wired to one host.
|
|
27
|
+
type Services struct {
|
|
28
|
+
Registry *Registry
|
|
29
|
+
Catalogue *Catalogue
|
|
30
|
+
Hydrator *Hydrator
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// NewServices builds the session services.
|
|
34
|
+
func NewServices(opts ServicesOptions) *Services {
|
|
35
|
+
catalogue := NewCatalogue(opts.SessionRoot)
|
|
36
|
+
registry := NewRegistry(RegistryOptions{
|
|
37
|
+
Host: opts.Host, DefaultWorkingDirectory: opts.DefaultWorkingDirectory, CreateBackend: opts.CreateBackend,
|
|
38
|
+
CreateSessionManager: opts.CreateSessionManager, DefaultSelection: opts.DefaultSelection, DeleteFile: opts.DeleteFile,
|
|
39
|
+
FindSessionFile: catalogue.FindSessionFile, AdoptForeignTurns: opts.AdoptForeignTurns, Log: opts.Log,
|
|
40
|
+
})
|
|
41
|
+
hydrator := NewHydrator(HydratorOptions{
|
|
42
|
+
Host: opts.Host, Catalogue: catalogue,
|
|
43
|
+
IsLive: registry.Has,
|
|
44
|
+
IsDisposing: registry.IsDisposing,
|
|
45
|
+
FallbackSelection: opts.DefaultSelection,
|
|
46
|
+
Adopt: func(a AdoptedSession) { registry.Adopt(a) },
|
|
47
|
+
WrapStore: opts.WrapStore,
|
|
48
|
+
Log: opts.Log,
|
|
49
|
+
})
|
|
50
|
+
return &Services{Registry: registry, Catalogue: catalogue, Hydrator: hydrator}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type liveCatalogue struct{ s *Services }
|
|
54
|
+
|
|
55
|
+
func (c liveCatalogue) List(ctx context.Context, limit *int64, cursor *string) (ahptypes.ListSessionsResult, error) {
|
|
56
|
+
return c.s.Catalogue.List(ctx, limit, cursor, c.s.Registry.CatalogueOverrides)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type turnPaging struct{ r *Registry }
|
|
60
|
+
|
|
61
|
+
func (p turnPaging) FetchTurns(ctx context.Context, params ahptypes.FetchTurnsParams) error {
|
|
62
|
+
return p.r.FetchTurns(ctx, params)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Capabilities are what the host serves through these services.
|
|
66
|
+
func (s *Services) Capabilities() host.Capabilities {
|
|
67
|
+
return host.Capabilities{
|
|
68
|
+
Catalogue: liveCatalogue{s}, Sessions: s.Registry, Hydrator: s.Hydrator, TurnPaging: turnPaging{s.Registry},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package pi
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
|
|
7
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
|
|
8
|
+
|
|
9
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/wire"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// ProjectTrustKey is the pre-creation config property that reports project-resource trust.
|
|
13
|
+
const ProjectTrustKey = "projectResources"
|
|
14
|
+
|
|
15
|
+
// SessionConfigOptions configure a [SessionConfigService].
|
|
16
|
+
type SessionConfigOptions struct {
|
|
17
|
+
DefaultWorkingDirectory string
|
|
18
|
+
ProjectTrustPolicy ProjectTrustPolicy
|
|
19
|
+
AgentDir string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// SessionConfigService answers resolveSessionConfig: what a session would be like before it is
|
|
23
|
+
// created. The exchange is iterative: the answer changes as the user picks a directory, which is
|
|
24
|
+
// the whole reason the command exists (port of src/pi/session-config.ts).
|
|
25
|
+
type SessionConfigService struct{ opts SessionConfigOptions }
|
|
26
|
+
|
|
27
|
+
// NewSessionConfigService creates the service.
|
|
28
|
+
func NewSessionConfigService(opts SessionConfigOptions) *SessionConfigService {
|
|
29
|
+
return &SessionConfigService{opts: opts}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Resolve implements host.SessionConfigHandler.
|
|
33
|
+
func (s *SessionConfigService) Resolve(_ context.Context, params ahptypes.ResolveSessionConfigParams) (ahptypes.ResolveSessionConfigResult, error) {
|
|
34
|
+
workingDirectory := s.opts.DefaultWorkingDirectory
|
|
35
|
+
if params.WorkingDirectory != nil && *params.WorkingDirectory != "" {
|
|
36
|
+
path, err := wire.FileURIToPath(*params.WorkingDirectory)
|
|
37
|
+
if err != nil {
|
|
38
|
+
return ahptypes.ResolveSessionConfigResult{}, wire.InvalidParams(err.Error())
|
|
39
|
+
}
|
|
40
|
+
workingDirectory = path
|
|
41
|
+
}
|
|
42
|
+
trust, err := ResolveProjectTrust(workingDirectory, s.opts.ProjectTrustPolicy, s.opts.AgentDir)
|
|
43
|
+
if err != nil {
|
|
44
|
+
return ahptypes.ResolveSessionConfigResult{}, err
|
|
45
|
+
}
|
|
46
|
+
description := "Trust-gated project resources (" + trust.Reason + ")."
|
|
47
|
+
if trust.Reason == "no-project-resources" {
|
|
48
|
+
description = "This folder has no trust-gated project resources."
|
|
49
|
+
}
|
|
50
|
+
readOnly := true
|
|
51
|
+
value, _ := json.Marshal(trust.Trusted)
|
|
52
|
+
return ahptypes.ResolveSessionConfigResult{
|
|
53
|
+
Schema: ahptypes.SessionConfigSchema{Type: "object", Properties: map[string]ahptypes.SessionConfigPropertySchema{
|
|
54
|
+
// Host policy, not a per-session choice.
|
|
55
|
+
ProjectTrustKey: {Type: "boolean", Title: "Load project resources", Description: &description, ReadOnly: &readOnly},
|
|
56
|
+
}},
|
|
57
|
+
Values: map[string]json.RawMessage{ProjectTrustKey: value},
|
|
58
|
+
}, nil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Completions implements host.SessionConfigHandler: this host offers no dynamic completions.
|
|
62
|
+
func (s *SessionConfigService) Completions(context.Context, ahptypes.SessionConfigCompletionsParams) (ahptypes.SessionConfigCompletionsResult, error) {
|
|
63
|
+
return ahptypes.SessionConfigCompletionsResult{Items: []ahptypes.SessionConfigValueItem{}}, nil
|
|
64
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package pi_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strconv"
|
|
9
|
+
"strings"
|
|
10
|
+
"testing"
|
|
11
|
+
"time"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// fixtureSessionDirectory mirrors Pi's default cwd partition solely for synthetic JSONL fixtures
|
|
15
|
+
// (upstream test/support/session-files.ts).
|
|
16
|
+
func fixtureSessionDirectory(t testing.TB, root, cwd string) string {
|
|
17
|
+
t.Helper()
|
|
18
|
+
abs, err := filepath.Abs(cwd)
|
|
19
|
+
if err != nil {
|
|
20
|
+
t.Fatal(err)
|
|
21
|
+
}
|
|
22
|
+
abs = strings.TrimLeft(abs, `/\`)
|
|
23
|
+
partition := "--" + strings.NewReplacer("/", "-", `\`, "-", ":", "-").Replace(abs) + "--"
|
|
24
|
+
dir := filepath.Join(root, partition)
|
|
25
|
+
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
26
|
+
t.Fatal(err)
|
|
27
|
+
}
|
|
28
|
+
return dir
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type fakeSession struct {
|
|
32
|
+
cwd string
|
|
33
|
+
firstUserMessage string
|
|
34
|
+
name string
|
|
35
|
+
mtimeSeconds int64 // controls catalogue ordering
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var entryCounter int
|
|
39
|
+
|
|
40
|
+
// writeFakeSession writes a minimal but genuine Pi session file: a session header followed by
|
|
41
|
+
// entries linked through id/parentId (upstream writeFakeSession).
|
|
42
|
+
func writeFakeSession(t testing.TB, root, id string, o fakeSession) string {
|
|
43
|
+
t.Helper()
|
|
44
|
+
dir := fixtureSessionDirectory(t, root, o.cwd)
|
|
45
|
+
timestamp := time.Unix(o.mtimeSeconds, 0).UTC().Format("2006-01-02T15:04:05.000Z")
|
|
46
|
+
line := func(v map[string]any) string {
|
|
47
|
+
raw, _ := json.Marshal(v)
|
|
48
|
+
return string(raw)
|
|
49
|
+
}
|
|
50
|
+
lines := []string{line(map[string]any{"type": "session", "id": id, "parentId": nil, "timestamp": timestamp, "version": 3, "cwd": o.cwd})}
|
|
51
|
+
var parent any
|
|
52
|
+
push := func(entry map[string]any) {
|
|
53
|
+
entryCounter++
|
|
54
|
+
entryID := fmt.Sprintf("entry-%d", entryCounter)
|
|
55
|
+
entry["id"], entry["parentId"], entry["timestamp"] = entryID, parent, timestamp
|
|
56
|
+
lines = append(lines, line(entry))
|
|
57
|
+
parent = entryID
|
|
58
|
+
}
|
|
59
|
+
if o.firstUserMessage != "" {
|
|
60
|
+
push(map[string]any{"type": "message", "message": map[string]any{"role": "user", "content": o.firstUserMessage, "timestamp": 0}})
|
|
61
|
+
}
|
|
62
|
+
if o.name != "" {
|
|
63
|
+
push(map[string]any{"type": "session_info", "name": o.name})
|
|
64
|
+
}
|
|
65
|
+
path := filepath.Join(dir, strconv.FormatInt(o.mtimeSeconds, 10)+"_"+id+".jsonl")
|
|
66
|
+
if err := os.WriteFile(path, []byte(strings.Join(lines, "\n")+"\n"), 0o644); err != nil {
|
|
67
|
+
t.Fatal(err)
|
|
68
|
+
}
|
|
69
|
+
at := time.Unix(o.mtimeSeconds, 0)
|
|
70
|
+
if err := os.Chtimes(path, at, at); err != nil {
|
|
71
|
+
t.Fatal(err)
|
|
72
|
+
}
|
|
73
|
+
return path
|
|
74
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
package pi
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/pisession"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// SessionStore is the slice of Pi's session manager the host uses. *pisession.Manager satisfies
|
|
11
|
+
// it; the extension adapter provides one over the live PiG session.
|
|
12
|
+
type SessionStore interface {
|
|
13
|
+
// File is the session file, "" when the session is not persisted; it may not exist yet.
|
|
14
|
+
File() string
|
|
15
|
+
LeafEntry() pisession.Entry
|
|
16
|
+
SessionName() string
|
|
17
|
+
AppendSessionInfo(name string) (string, error)
|
|
18
|
+
// Branch is the path from the root to the leaf.
|
|
19
|
+
Branch(fromID string) []pisession.Entry
|
|
20
|
+
// ContextEntries is the compaction-aware entry list along the current branch.
|
|
21
|
+
ContextEntries() []pisession.Entry
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// SessionManagerFactory creates the storage for a new session.
|
|
25
|
+
type SessionManagerFactory func(workingDirectory, sessionID string) (SessionStore, error)
|
|
26
|
+
|
|
27
|
+
// InMemoryStorage is a factory for sessions that never touch disk.
|
|
28
|
+
func InMemoryStorage(workingDirectory, sessionID string) (SessionStore, error) {
|
|
29
|
+
return pisession.InMemory(workingDirectory, pisession.Options{ID: sessionID})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// PersistentStorage is a factory for sessions written under dir.
|
|
33
|
+
func PersistentStorage(dir string) SessionManagerFactory {
|
|
34
|
+
return func(workingDirectory, sessionID string) (SessionStore, error) {
|
|
35
|
+
return pisession.Create(workingDirectory, dir, pisession.Options{ID: sessionID})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// HistorySource is what history serving needs from a live session.
|
|
40
|
+
type HistorySource interface {
|
|
41
|
+
Store() SessionStore
|
|
42
|
+
ID() string
|
|
43
|
+
// Anchor is the recorded truncation anchor of a turn.
|
|
44
|
+
Anchor(turnID string) (string, bool)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// TruncationAnchor is the session entry a truncation to turnID would move the agent to ("" turnID
|
|
48
|
+
// clears everything). ok is false when no entry matches: accepting an impossible truncation would
|
|
49
|
+
// shorten the client's view while Pi kept using context the user believes is gone.
|
|
50
|
+
func TruncationAnchor(source HistorySource, turnID *string) (string, bool) {
|
|
51
|
+
key := ClearAllAnchor
|
|
52
|
+
if turnID != nil {
|
|
53
|
+
key = *turnID
|
|
54
|
+
}
|
|
55
|
+
if known, ok := source.Anchor(key); ok && known != "" {
|
|
56
|
+
return known, true
|
|
57
|
+
}
|
|
58
|
+
// A turn this host never ran: recompute from the file, which is where a hydrated session's ids
|
|
59
|
+
// came from in the first place.
|
|
60
|
+
a, ok := RebuildHistoryFromSession(source.Store(), RebuildOptions{TurnIDPrefix: source.ID()}).Anchors[key]
|
|
61
|
+
return a, ok && a != ""
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// DefaultSessionDir is where Pi keeps the sessions of a working directory: one sub-directory of
|
|
65
|
+
// <agentDir>/sessions per cwd, named after the encoded path (Pi's getDefaultSessionDirPath).
|
|
66
|
+
func DefaultSessionDir(agentDir, cwd string) string {
|
|
67
|
+
abs, err := filepath.Abs(cwd)
|
|
68
|
+
if err != nil {
|
|
69
|
+
abs = cwd
|
|
70
|
+
}
|
|
71
|
+
abs = strings.TrimLeft(abs, `/\`)
|
|
72
|
+
safe := "--" + strings.NewReplacer("/", "-", `\`, "-", ":", "-").Replace(abs) + "--"
|
|
73
|
+
return filepath.Join(agentDir, "sessions", safe)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// DefaultStorage keeps new sessions where Pi itself would, under <agentDir>/sessions, and pairs the
|
|
77
|
+
// catalogue root with the same profile so the catalogue and the writer never disagree (port of
|
|
78
|
+
// createDefaultPiSessionStorage).
|
|
79
|
+
func DefaultStorage(agentDir string) (catalogueRoot string, create SessionManagerFactory) {
|
|
80
|
+
return filepath.Join(agentDir, "sessions"), func(workingDirectory, sessionID string) (SessionStore, error) {
|
|
81
|
+
return pisession.Create(workingDirectory, DefaultSessionDir(agentDir, workingDirectory), pisession.Options{ID: sessionID})
|
|
82
|
+
}
|
|
83
|
+
}
|