@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.
Files changed (304) hide show
  1. package/CREDITS.md +27 -0
  2. package/LICENSE +23 -0
  3. package/README.md +148 -0
  4. package/extensions/ahp/access_options_test.go +116 -0
  5. package/extensions/ahp/extension.go +54 -0
  6. package/extensions/ahp/go.mod +12 -0
  7. package/extensions/ahp/go.sum +2 -0
  8. package/extensions/ahp/go.work +8 -0
  9. package/extensions/ahp/internal/channels/chat.go +110 -0
  10. package/extensions/ahp/internal/channels/root.go +32 -0
  11. package/extensions/ahp/internal/channels/session.go +128 -0
  12. package/extensions/ahp/internal/channels/session_test.go +108 -0
  13. package/extensions/ahp/internal/compose/compose.go +153 -0
  14. package/extensions/ahp/internal/compose/compose_test.go +120 -0
  15. package/extensions/ahp/internal/gaps/gaps_test.go +88 -0
  16. package/extensions/ahp/internal/host/handshake_test.go +206 -0
  17. package/extensions/ahp/internal/host/host.go +1305 -0
  18. package/extensions/ahp/internal/host/reconnect_test.go +197 -0
  19. package/extensions/ahp/internal/host/replaywindow_test.go +31 -0
  20. package/extensions/ahp/internal/host/schema_test.go +79 -0
  21. package/extensions/ahp/internal/host/store.go +291 -0
  22. package/extensions/ahp/internal/host/store_update_test.go +85 -0
  23. package/extensions/ahp/internal/host/subscriptions_test.go +160 -0
  24. package/extensions/ahp/internal/host/surface_test.go +95 -0
  25. package/extensions/ahp/internal/host/workarounds.go +458 -0
  26. package/extensions/ahp/internal/host/workarounds_test.go +96 -0
  27. package/extensions/ahp/internal/live/live.go +328 -0
  28. package/extensions/ahp/internal/live/live_test.go +91 -0
  29. package/extensions/ahp/internal/live/store.go +113 -0
  30. package/extensions/ahp/internal/mapper/activity.go +199 -0
  31. package/extensions/ahp/internal/mapper/activity_test.go +188 -0
  32. package/extensions/ahp/internal/mapper/bench_test.go +38 -0
  33. package/extensions/ahp/internal/mapper/event_mapper_test.go +380 -0
  34. package/extensions/ahp/internal/mapper/fixtures_test.go +387 -0
  35. package/extensions/ahp/internal/mapper/helpers_test.go +251 -0
  36. package/extensions/ahp/internal/mapper/js.go +131 -0
  37. package/extensions/ahp/internal/mapper/mapper.go +600 -0
  38. package/extensions/ahp/internal/mapper/message_input_test.go +196 -0
  39. package/extensions/ahp/internal/mapper/messageinput.go +272 -0
  40. package/extensions/ahp/internal/mapper/schema_helpers_test.go +5 -0
  41. package/extensions/ahp/internal/mapper/testdata/LICENSE-pi-ahp +21 -0
  42. package/extensions/ahp/internal/mapper/testdata/fixtures/abort.json +711 -0
  43. package/extensions/ahp/internal/mapper/testdata/fixtures/bash-long-output.json +1312 -0
  44. package/extensions/ahp/internal/mapper/testdata/fixtures/compaction.json +6285 -0
  45. package/extensions/ahp/internal/mapper/testdata/fixtures/parallel-tools.json +2622 -0
  46. package/extensions/ahp/internal/mapper/testdata/fixtures/plain-text.json +1856 -0
  47. package/extensions/ahp/internal/mapper/testdata/fixtures/single-tool.json +1128 -0
  48. package/extensions/ahp/internal/mapper/testdata/fixtures/steering.json +3926 -0
  49. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-bash.json +2372 -0
  50. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-edit.json +3983 -0
  51. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-error.json +2850 -0
  52. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-find.json +1162 -0
  53. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-grep.json +2783 -0
  54. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-loop.json +1693 -0
  55. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-ls.json +2427 -0
  56. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-write.json +1271 -0
  57. package/extensions/ahp/internal/mapper/title.go +34 -0
  58. package/extensions/ahp/internal/mapper/usermsg.go +87 -0
  59. package/extensions/ahp/internal/pi/activeturn_test.go +296 -0
  60. package/extensions/ahp/internal/pi/backend.go +64 -0
  61. package/extensions/ahp/internal/pi/catalogue.go +370 -0
  62. package/extensions/ahp/internal/pi/catalogue_test.go +271 -0
  63. package/extensions/ahp/internal/pi/chatdriver.go +533 -0
  64. package/extensions/ahp/internal/pi/chatdriver_test.go +416 -0
  65. package/extensions/ahp/internal/pi/clientactions.go +212 -0
  66. package/extensions/ahp/internal/pi/clientactions_test.go +273 -0
  67. package/extensions/ahp/internal/pi/completions.go +230 -0
  68. package/extensions/ahp/internal/pi/completions_test.go +272 -0
  69. package/extensions/ahp/internal/pi/config_test.go +269 -0
  70. package/extensions/ahp/internal/pi/deletesession.go +69 -0
  71. package/extensions/ahp/internal/pi/deletesession_test.go +153 -0
  72. package/extensions/ahp/internal/pi/disposal_test.go +362 -0
  73. package/extensions/ahp/internal/pi/fixture_test.go +308 -0
  74. package/extensions/ahp/internal/pi/foreign_test.go +122 -0
  75. package/extensions/ahp/internal/pi/harness_test.go +83 -0
  76. package/extensions/ahp/internal/pi/history.go +269 -0
  77. package/extensions/ahp/internal/pi/hydrated_test.go +171 -0
  78. package/extensions/ahp/internal/pi/hydratedlifecycle_test.go +196 -0
  79. package/extensions/ahp/internal/pi/hydration_test.go +209 -0
  80. package/extensions/ahp/internal/pi/hydrator.go +212 -0
  81. package/extensions/ahp/internal/pi/imageinput.go +224 -0
  82. package/extensions/ahp/internal/pi/imageinput_test.go +57 -0
  83. package/extensions/ahp/internal/pi/lifecycle_test.go +381 -0
  84. package/extensions/ahp/internal/pi/models.go +212 -0
  85. package/extensions/ahp/internal/pi/models_test.go +128 -0
  86. package/extensions/ahp/internal/pi/paging.go +95 -0
  87. package/extensions/ahp/internal/pi/pagingtruncate_test.go +323 -0
  88. package/extensions/ahp/internal/pi/projecttrust.go +222 -0
  89. package/extensions/ahp/internal/pi/registry.go +924 -0
  90. package/extensions/ahp/internal/pi/restart_test.go +185 -0
  91. package/extensions/ahp/internal/pi/services.go +70 -0
  92. package/extensions/ahp/internal/pi/sessionconfig.go +64 -0
  93. package/extensions/ahp/internal/pi/sessionfiles_test.go +74 -0
  94. package/extensions/ahp/internal/pi/sessionstore.go +83 -0
  95. package/extensions/ahp/internal/pi/summary_test.go +426 -0
  96. package/extensions/ahp/internal/pi/workarounds_wire_test.go +208 -0
  97. package/extensions/ahp/internal/pisession/fromentries_test.go +32 -0
  98. package/extensions/ahp/internal/pisession/json.go +31 -0
  99. package/extensions/ahp/internal/pisession/pisession.go +580 -0
  100. package/extensions/ahp/internal/settings/access.go +63 -0
  101. package/extensions/ahp/internal/settings/access_test.go +52 -0
  102. package/extensions/ahp/internal/settings/settings.go +117 -0
  103. package/extensions/ahp/internal/settings/settings_test.go +104 -0
  104. package/extensions/ahp/internal/svc/etag_other.go +15 -0
  105. package/extensions/ahp/internal/svc/etag_unix.go +34 -0
  106. package/extensions/ahp/internal/svc/glob.go +197 -0
  107. package/extensions/ahp/internal/svc/mime.go +37 -0
  108. package/extensions/ahp/internal/svc/paths.go +108 -0
  109. package/extensions/ahp/internal/svc/pty.go +64 -0
  110. package/extensions/ahp/internal/svc/pty_darwin.go +42 -0
  111. package/extensions/ahp/internal/svc/pty_linux.go +36 -0
  112. package/extensions/ahp/internal/svc/pty_other.go +9 -0
  113. package/extensions/ahp/internal/svc/pty_test.go +114 -0
  114. package/extensions/ahp/internal/svc/pty_unix.go +147 -0
  115. package/extensions/ahp/internal/svc/resource.go +495 -0
  116. package/extensions/ahp/internal/svc/resource_test.go +501 -0
  117. package/extensions/ahp/internal/svc/stat_bsd.go +9 -0
  118. package/extensions/ahp/internal/svc/stat_linux.go +9 -0
  119. package/extensions/ahp/internal/svc/terminal.go +454 -0
  120. package/extensions/ahp/internal/svc/terminal_test.go +512 -0
  121. package/extensions/ahp/internal/svc/watch.go +590 -0
  122. package/extensions/ahp/internal/svc/watch_test.go +627 -0
  123. package/extensions/ahp/internal/svc/watchevents_test.go +452 -0
  124. package/extensions/ahp/internal/svc/watchpolicy.go +81 -0
  125. package/extensions/ahp/internal/svc/watchpolicy_test.go +151 -0
  126. package/extensions/ahp/internal/svc/watchracy_test.go +61 -0
  127. package/extensions/ahp/internal/testkit/schema/LICENSE-agent-host-protocol +21 -0
  128. package/extensions/ahp/internal/testkit/schema/actions.schema.json +9208 -0
  129. package/extensions/ahp/internal/testkit/schema/commands.schema.json +10862 -0
  130. package/extensions/ahp/internal/testkit/schema/errors.schema.json +10928 -0
  131. package/extensions/ahp/internal/testkit/schema/notifications.schema.json +6667 -0
  132. package/extensions/ahp/internal/testkit/schema/state.schema.json +6392 -0
  133. package/extensions/ahp/internal/testkit/schema.go +360 -0
  134. package/extensions/ahp/internal/testkit/testkit.go +367 -0
  135. package/extensions/ahp/internal/twin/twin.go +27 -0
  136. package/extensions/ahp/internal/wire/dispatchable.go +106 -0
  137. package/extensions/ahp/internal/wire/dispatchable_list.go +15 -0
  138. package/extensions/ahp/internal/wire/helpers_test.go +5 -0
  139. package/extensions/ahp/internal/wire/wire.go +218 -0
  140. package/extensions/ahp/internal/wire/wire_test.go +123 -0
  141. package/extensions/ahp/internal/ws/helpers_test.go +7 -0
  142. package/extensions/ahp/internal/ws/vscode_test.go +198 -0
  143. package/extensions/ahp/internal/ws/ws.go +674 -0
  144. package/extensions/ahp/internal/ws/ws_test.go +324 -0
  145. package/extensions/ahp/realpig_test.go +589 -0
  146. package/extensions/ahp/runtime.go +311 -0
  147. package/extensions/ahp/runtime_test.go +110 -0
  148. package/extensions/ahp/testdata/upstream-tests.json +3833 -0
  149. package/extensions/ahp/third_party/agent-host-protocol-go/LICENSE +21 -0
  150. package/extensions/ahp/third_party/agent-host-protocol-go/NOTICE-PIGPEN.md +18 -0
  151. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/client.go +1011 -0
  152. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/error.go +111 -0
  153. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/multi_host_state_mirror.go +239 -0
  154. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/reducers.go +1939 -0
  155. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/transport.go +176 -0
  156. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/actions.generated.go +2447 -0
  157. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/commands.generated.go +1546 -0
  158. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/common.go +205 -0
  159. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/errors.generated.go +65 -0
  160. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/messages.generated.go +138 -0
  161. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/notifications.generated.go +264 -0
  162. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/state.generated.go +6171 -0
  163. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/version.generated.go +31 -0
  164. package/extensions/ahp/third_party/agent-host-protocol-go/go.mod +3 -0
  165. package/extensions/ahp/twins_test.go +91 -0
  166. package/package.json +40 -0
  167. package/proof/PORT.md +121 -0
  168. package/proof/mutations.json +152 -0
  169. package/proof/oracle/LICENSE +21 -0
  170. package/proof/oracle/README.md +120 -0
  171. package/proof/oracle/UPSTREAM.md +7 -0
  172. package/proof/oracle/package.json +68 -0
  173. package/proof/oracle/src/bin/cli.ts +66 -0
  174. package/proof/oracle/src/bin/tunnel.ts +128 -0
  175. package/proof/oracle/src/channels/chat.ts +134 -0
  176. package/proof/oracle/src/channels/root.ts +39 -0
  177. package/proof/oracle/src/channels/session.ts +118 -0
  178. package/proof/oracle/src/channels/terminal.ts +11 -0
  179. package/proof/oracle/src/core/channels.ts +108 -0
  180. package/proof/oracle/src/core/client-workarounds.ts +358 -0
  181. package/proof/oracle/src/core/connection.ts +41 -0
  182. package/proof/oracle/src/core/host.ts +882 -0
  183. package/proof/oracle/src/core/sequencer.ts +75 -0
  184. package/proof/oracle/src/core/state-store.ts +162 -0
  185. package/proof/oracle/src/core/uri.ts +25 -0
  186. package/proof/oracle/src/host/direct-settings.ts +110 -0
  187. package/proof/oracle/src/host/pi-host.ts +220 -0
  188. package/proof/oracle/src/host/serve.ts +71 -0
  189. package/proof/oracle/src/host/terminal-service.ts +346 -0
  190. package/proof/oracle/src/pi/activity.ts +171 -0
  191. package/proof/oracle/src/pi/changeset-service.ts +646 -0
  192. package/proof/oracle/src/pi/changeset-uri.ts +64 -0
  193. package/proof/oracle/src/pi/chat-driver.ts +529 -0
  194. package/proof/oracle/src/pi/completions.ts +160 -0
  195. package/proof/oracle/src/pi/delete-session.ts +53 -0
  196. package/proof/oracle/src/pi/event-mapper.ts +648 -0
  197. package/proof/oracle/src/pi/git-changes.ts +605 -0
  198. package/proof/oracle/src/pi/history.ts +305 -0
  199. package/proof/oracle/src/pi/image-input.ts +56 -0
  200. package/proof/oracle/src/pi/image-mime.ts +8 -0
  201. package/proof/oracle/src/pi/in-process-backend.ts +155 -0
  202. package/proof/oracle/src/pi/message-input.ts +212 -0
  203. package/proof/oracle/src/pi/models.ts +124 -0
  204. package/proof/oracle/src/pi/project-trust.ts +66 -0
  205. package/proof/oracle/src/pi/provider.ts +2 -0
  206. package/proof/oracle/src/pi/resource-paths.ts +80 -0
  207. package/proof/oracle/src/pi/resource-service.ts +356 -0
  208. package/proof/oracle/src/pi/resource-watch-policy.ts +35 -0
  209. package/proof/oracle/src/pi/resource-watch.ts +357 -0
  210. package/proof/oracle/src/pi/session-catalogue.ts +318 -0
  211. package/proof/oracle/src/pi/session-config.ts +84 -0
  212. package/proof/oracle/src/pi/session-history.ts +75 -0
  213. package/proof/oracle/src/pi/session-hydrator.ts +219 -0
  214. package/proof/oracle/src/pi/session-registry.ts +815 -0
  215. package/proof/oracle/src/pi/session-storage.ts +29 -0
  216. package/proof/oracle/src/pi/session-title.ts +18 -0
  217. package/proof/oracle/src/pi/turn-paging.ts +94 -0
  218. package/proof/oracle/src/pi/user-message.ts +66 -0
  219. package/proof/oracle/src/protocol/errors.ts +44 -0
  220. package/proof/oracle/src/protocol/jsonrpc.ts +89 -0
  221. package/proof/oracle/src/protocol/version.ts +39 -0
  222. package/proof/oracle/src/transport/websocket.ts +136 -0
  223. package/proof/oracle/src/tunnel/devtunnel.ts +293 -0
  224. package/proof/oracle/src/tunnel/discovery.ts +37 -0
  225. package/proof/oracle/test/active-turn-reconnect.test.ts +274 -0
  226. package/proof/oracle/test/activity.test.ts +171 -0
  227. package/proof/oracle/test/changeset-lifecycle.test.serial.ts +303 -0
  228. package/proof/oracle/test/changeset-uri.test.ts +37 -0
  229. package/proof/oracle/test/changeset.test.serial.ts +487 -0
  230. package/proof/oracle/test/chat-driver.test.ts +691 -0
  231. package/proof/oracle/test/client-actions.test.ts +409 -0
  232. package/proof/oracle/test/client-workarounds.test.ts +320 -0
  233. package/proof/oracle/test/completions.test.ts +323 -0
  234. package/proof/oracle/test/delete-session.test.ts +142 -0
  235. package/proof/oracle/test/direct-settings.test.ts +81 -0
  236. package/proof/oracle/test/event-mapper.test.ts +619 -0
  237. package/proof/oracle/test/fetch-turns.test.ts +207 -0
  238. package/proof/oracle/test/fixtures/abort.json +711 -0
  239. package/proof/oracle/test/fixtures/bash-long-output.json +1312 -0
  240. package/proof/oracle/test/fixtures/compaction.json +6285 -0
  241. package/proof/oracle/test/fixtures/parallel-tools.json +2622 -0
  242. package/proof/oracle/test/fixtures/plain-text.json +1856 -0
  243. package/proof/oracle/test/fixtures/single-tool.json +1128 -0
  244. package/proof/oracle/test/fixtures/steering.json +3926 -0
  245. package/proof/oracle/test/fixtures/tool-bash.json +2372 -0
  246. package/proof/oracle/test/fixtures/tool-edit.json +3983 -0
  247. package/proof/oracle/test/fixtures/tool-error.json +2850 -0
  248. package/proof/oracle/test/fixtures/tool-find.json +1162 -0
  249. package/proof/oracle/test/fixtures/tool-grep.json +2783 -0
  250. package/proof/oracle/test/fixtures/tool-loop.json +1693 -0
  251. package/proof/oracle/test/fixtures/tool-ls.json +2427 -0
  252. package/proof/oracle/test/fixtures/tool-write.json +1271 -0
  253. package/proof/oracle/test/handshake.test.ts +275 -0
  254. package/proof/oracle/test/harness.ts +153 -0
  255. package/proof/oracle/test/hydrated-session-lifecycle.test.ts +211 -0
  256. package/proof/oracle/test/image-input.test.ts +28 -0
  257. package/proof/oracle/test/image-session.test.ts +69 -0
  258. package/proof/oracle/test/live-turn.test.ts +304 -0
  259. package/proof/oracle/test/mapper-fixtures.test.ts +293 -0
  260. package/proof/oracle/test/message-input.test.ts +181 -0
  261. package/proof/oracle/test/model-discovery.test.ts +170 -0
  262. package/proof/oracle/test/models.test.ts +109 -0
  263. package/proof/oracle/test/pi-host.test.ts +91 -0
  264. package/proof/oracle/test/pi-replay.test.ts +141 -0
  265. package/proof/oracle/test/project-trust.test.ts +125 -0
  266. package/proof/oracle/test/protocol-surface.test.ts +121 -0
  267. package/proof/oracle/test/pty.test.ts +100 -0
  268. package/proof/oracle/test/reconnect.test.ts +389 -0
  269. package/proof/oracle/test/resource-watch-policy.test.ts +78 -0
  270. package/proof/oracle/test/resource-watch.test.serial.ts +536 -0
  271. package/proof/oracle/test/resource.test.ts +499 -0
  272. package/proof/oracle/test/schema.test.ts +88 -0
  273. package/proof/oracle/test/session-catalogue.test.ts +314 -0
  274. package/proof/oracle/test/session-config.test.ts +224 -0
  275. package/proof/oracle/test/session-disposal.test.ts +364 -0
  276. package/proof/oracle/test/session-hydration.test.ts +194 -0
  277. package/proof/oracle/test/session-lifecycle.test.ts +416 -0
  278. package/proof/oracle/test/session-storage.test.serial.ts +32 -0
  279. package/proof/oracle/test/session-summary.test.ts +418 -0
  280. package/proof/oracle/test/subscriptions.test.ts +198 -0
  281. package/proof/oracle/test/support/assertions.ts +32 -0
  282. package/proof/oracle/test/support/async.ts +26 -0
  283. package/proof/oracle/test/support/hydrated-session.ts +205 -0
  284. package/proof/oracle/test/support/images.ts +8 -0
  285. package/proof/oracle/test/support/recorded-fixtures.ts +60 -0
  286. package/proof/oracle/test/support/recorded-scenarios.ts +145 -0
  287. package/proof/oracle/test/support/replay.ts +194 -0
  288. package/proof/oracle/test/support/schema.ts +221 -0
  289. package/proof/oracle/test/support/session-files.ts +12 -0
  290. package/proof/oracle/test/support/session-storage.ts +22 -0
  291. package/proof/oracle/test/support/upstream.ts +25 -0
  292. package/proof/oracle/test/support/watch-events.ts +108 -0
  293. package/proof/oracle/test/terminal-service.test.ts +495 -0
  294. package/proof/oracle/test/truncate.test.ts +230 -0
  295. package/proof/oracle/test/tunnel.test.ts +258 -0
  296. package/proof/oracle/test/upstream-workarounds.test.ts +37 -0
  297. package/proof/oracle/test/uri.test.ts +24 -0
  298. package/proof/oracle/test/watch-events.test.ts +118 -0
  299. package/proof/oracle/tsconfig.json +32 -0
  300. package/proof/proof-piglet/piglet.yaml +18 -0
  301. package/proof/tools/fakellm/go.mod +3 -0
  302. package/proof/tools/fakellm/main.go +109 -0
  303. package/proof/tools/gen-dispatchable.py +30 -0
  304. package/provenance.json +28 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Pre-creation session configuration.
3
+ *
4
+ * Before `createSession`, a client asks the host what a new session needs to be
5
+ * configured with. The exchange is iterative: the client sends the working
6
+ * directory and whatever the user has filled in so far, and the host returns the
7
+ * *full* current property set plus resolved values — never a delta — so options
8
+ * can appear and disappear as earlier choices are made.
9
+ *
10
+ * Model selection stays out of this schema: models are advertised on
11
+ * `AgentInfo.models` and chosen per message via `Message.model`. The schema here
12
+ * covers only what pi must decide before creating the session.
13
+ *
14
+ * @see https://microsoft.github.io/agent-host-protocol/specification/root-channel
15
+ */
16
+
17
+ import type {
18
+ ResolveSessionConfigParams,
19
+ ResolveSessionConfigResult,
20
+ SessionConfigCompletionsParams,
21
+ SessionConfigCompletionsResult,
22
+ SessionConfigSchema,
23
+ } from "@microsoft/agent-host-protocol";
24
+ import { fileUriToPath } from "../core/uri.ts";
25
+ import { type ProjectTrustPolicy, resolveProjectTrust } from "./project-trust.ts";
26
+
27
+ /** Config key describing whether trust-gated project resources load. */
28
+ export const PROJECT_TRUST_KEY = "projectResources";
29
+
30
+ export interface SessionConfigOptions {
31
+ readonly defaultWorkingDirectory: string;
32
+ readonly projectTrustPolicy?: ProjectTrustPolicy;
33
+ }
34
+
35
+ export class SessionConfigService {
36
+ readonly #options: SessionConfigOptions;
37
+
38
+ constructor(options: SessionConfigOptions) {
39
+ this.#options = options;
40
+ }
41
+
42
+ /**
43
+ * Describes what a new session in this directory would do.
44
+ *
45
+ * Only one property, and it is read-only: whether trust-gated resources from
46
+ * the target directory and its ancestors will load. It is surfaced because
47
+ * the answer changes with the directory and materially changes the agent's
48
+ * tools, settings, and instructions.
49
+ */
50
+ resolve(params: ResolveSessionConfigParams): ResolveSessionConfigResult {
51
+ const workingDirectory = params.workingDirectory
52
+ ? fileUriToPath(params.workingDirectory)
53
+ : this.#options.defaultWorkingDirectory;
54
+ const trust = resolveProjectTrust(workingDirectory, this.#options.projectTrustPolicy);
55
+
56
+ const schema: SessionConfigSchema = {
57
+ type: "object",
58
+ properties: {
59
+ [PROJECT_TRUST_KEY]: {
60
+ type: "boolean",
61
+ title: "Load project resources",
62
+ description:
63
+ trust.reason === "no-project-resources"
64
+ ? "This folder has no trust-gated project resources."
65
+ : `Trust-gated project resources (${trust.reason}).`,
66
+ // Host policy, not a per-session choice — see project-trust.ts.
67
+ readOnly: true,
68
+ },
69
+ },
70
+ };
71
+
72
+ return { schema, values: { [PROJECT_TRUST_KEY]: trust.trusted } };
73
+ }
74
+
75
+ /**
76
+ * No dynamic values to enumerate.
77
+ *
78
+ * Every property this host exposes has a static domain, so the completions
79
+ * endpoint exists only to keep a client that probes it from erroring.
80
+ */
81
+ completions(_params: SessionConfigCompletionsParams): SessionConfigCompletionsResult {
82
+ return { items: [] };
83
+ }
84
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The parts of a session that are about its *history file* rather than its
3
+ * lifecycle: paging older turns in, and resolving where a truncation lands.
4
+ *
5
+ * Both are questions about pi's append-only entry tree, not about registries or
6
+ * channels, and both need the same two things — a session manager and the id
7
+ * prefix its turn ids were built from. Keeping them beside the rebuild they
8
+ * depend on means the registry stays about *live sessions* instead of also
9
+ * knowing how a turn id maps onto a session entry.
10
+ */
11
+
12
+ import type { SessionManager } from "@earendil-works/pi-coding-agent";
13
+ import { ActionType, type ChatState, type URI } from "@microsoft/agent-host-protocol";
14
+ import type { AhpHost } from "../core/host.ts";
15
+ import { ProtocolError } from "../protocol/errors.ts";
16
+ import { CLEAR_ALL_ANCHOR, rebuildHistoryFromSession } from "./history.ts";
17
+ import { loadOlderTurns } from "./turn-paging.ts";
18
+
19
+ /** The session file, and the prefix its turn ids were derived from. */
20
+ export interface HistorySource {
21
+ readonly sessionManager: SessionManager;
22
+ readonly sessionId: string;
23
+ /**
24
+ * Anchors already known for turns this host ran itself.
25
+ *
26
+ * Consulted first because a live turn's id came from the client and cannot
27
+ * be recomputed from the file.
28
+ */
29
+ readonly turnAnchors: ReadonlyMap<string, string>;
30
+ }
31
+
32
+ /**
33
+ * Resolves the pi entry a `chat/truncated` names.
34
+ *
35
+ * `undefined` turnId means "clear everything", which maps to the very first
36
+ * user message: navigating to a user entry lands the leaf on its parent, and
37
+ * the first one's parent is `null`.
38
+ */
39
+ export function truncationAnchor(source: HistorySource, turnId: string | undefined): string | undefined {
40
+ const key = turnId ?? CLEAR_ALL_ANCHOR;
41
+ const known = source.turnAnchors.get(key);
42
+ if (known) {
43
+ return known;
44
+ }
45
+ // A turn this host never ran: recompute from the file, which is where a
46
+ // hydrated session's ids came from in the first place.
47
+ return rebuildHistoryFromSession(source.sessionManager, { turnIdPrefix: source.sessionId }).anchors.get(key);
48
+ }
49
+
50
+ /**
51
+ * Serves `fetchTurns` for a chat.
52
+ *
53
+ * Dispatches the page before returning, as the protocol requires — the client's
54
+ * state must already hold it when the call resolves.
55
+ */
56
+ export function dispatchOlderTurns(
57
+ host: AhpHost,
58
+ channel: URI,
59
+ source: HistorySource,
60
+ cursor: string | undefined,
61
+ ): void {
62
+ const state = host.store.get(channel) as ChatState | undefined;
63
+ if (cursor !== undefined && cursor !== state?.turnsNextCursor) {
64
+ throw ProtocolError.invalidParams(`Unrecognised fetchTurns cursor for ${channel}`);
65
+ }
66
+
67
+ const page = loadOlderTurns(source.sessionManager, source.sessionId, cursor);
68
+ // Dispatched even when empty: it is what clears a cursor that has no more
69
+ // history behind it, so the client stops asking.
70
+ host.dispatchServerAction(channel, {
71
+ type: ActionType.ChatTurnsLoaded,
72
+ turns: page.turns,
73
+ ...(page.nextCursor ? { turnsNextCursor: page.nextCursor } : {}),
74
+ });
75
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Loads a durable pi session into live channel state.
3
+ *
4
+ * Sessions outlive the host process: pi writes them to
5
+ * `~/.pi/agent/sessions/**`, `listSessions` reports them, and a client then
6
+ * subscribes to one. Only sessions this host created are in memory, so opening
7
+ * any other one has to reconstruct its channels from disk — otherwise
8
+ * `subscribe` answers `NotFound` for a session the catalogue just advertised.
9
+ */
10
+
11
+ import { stat } from "node:fs/promises";
12
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
13
+ import {
14
+ type ChatState,
15
+ MessageKind,
16
+ type ModelSelection,
17
+ SessionLifecycle,
18
+ type SessionState,
19
+ SessionStatus,
20
+ type URI,
21
+ } from "@microsoft/agent-host-protocol";
22
+ import { chatSummaryOf } from "../channels/chat.ts";
23
+ import { chatIdFromUri, chatUri, isChatChannel, sessionIdFromUri, sessionUri } from "../core/channels.ts";
24
+ import type { AhpHost, ChannelHydrator } from "../core/host.ts";
25
+ import { pathToFileUri } from "../core/uri.ts";
26
+ import { rebuildTurnsFromSession } from "./history.ts";
27
+ import { modelSelectionId, THINKING_CONFIG_KEY } from "./models.ts";
28
+ import { PI_PROVIDER } from "./provider.ts";
29
+ import type { PiSessionCatalogue } from "./session-catalogue.ts";
30
+ import { sessionDisplayTitle } from "./session-title.ts";
31
+ import { initialTurnsCursor } from "./turn-paging.ts";
32
+
33
+ export interface SessionHydratorOptions {
34
+ readonly host: AhpHost;
35
+ readonly catalogue: PiSessionCatalogue;
36
+ /** Sessions this host already has live; those must never be reloaded over. */
37
+ readonly isLive: (sessionChannel: URI) => boolean;
38
+ /** Prevents a disk session from becoming live while its removal is pending. */
39
+ readonly isDisposing: (sessionChannel: URI) => boolean;
40
+ /**
41
+ * The model to show when the session file records none.
42
+ *
43
+ * A hydrated session has no agent yet, so nothing else can answer "which
44
+ * model is selected" — and a client with an empty picker cannot send.
45
+ */
46
+ readonly fallbackSelection?: () => ModelSelection | undefined;
47
+ /**
48
+ * Registers the loaded session so it can later run turns.
49
+ *
50
+ * Without this a resumed session is read-only in a way nothing reports:
51
+ * the client's message reduces into state and echoes back, but no agent is
52
+ * listening and no reply ever comes.
53
+ */
54
+ readonly adopt?: (session: {
55
+ uri: URI;
56
+ sessionId: string;
57
+ workingDirectory: string;
58
+ sessionManager: SessionManager;
59
+ chatChannel: URI;
60
+ createdAt: string;
61
+ }) => void;
62
+ readonly log?: (message: string) => void;
63
+ }
64
+
65
+ export class SessionHydrator implements ChannelHydrator {
66
+ readonly #options: SessionHydratorOptions;
67
+ /** Session and chat subscriptions for one id join the same disk materialization. */
68
+ readonly #hydrations = new Map<string, Promise<void>>();
69
+
70
+ constructor(options: SessionHydratorOptions) {
71
+ this.#options = options;
72
+ }
73
+
74
+ async hydrate(channel: URI): Promise<boolean> {
75
+ // A chat and its session share one id, so either URI loads the pair.
76
+ // The provider alias is explicit: an unknown channel scheme must not
77
+ // become a session merely because its path resembles one.
78
+ const sessionId = isChatChannel(channel) ? chatIdFromUri(channel) : sessionIdFromUri(channel, [PI_PROVIDER]);
79
+ if (!sessionId) {
80
+ return false;
81
+ }
82
+
83
+ // Register the session under the URI the client actually used, so a
84
+ // non-standard scheme resolves to the same channel it subscribed to.
85
+ const session = isChatChannel(channel) ? sessionUri(sessionId) : channel;
86
+ if (this.#options.isLive(session)) {
87
+ return this.#options.host.store.has(channel);
88
+ }
89
+ if (this.#options.isDisposing(session)) {
90
+ return false;
91
+ }
92
+
93
+ const pending = this.#hydrations.get(sessionId);
94
+ if (pending) {
95
+ await pending;
96
+ return this.#options.host.store.has(channel);
97
+ }
98
+
99
+ const hydration = this.#hydrateSession(sessionId, session);
100
+ this.#hydrations.set(sessionId, hydration);
101
+ try {
102
+ await hydration;
103
+ } finally {
104
+ if (this.#hydrations.get(sessionId) === hydration) {
105
+ this.#hydrations.delete(sessionId);
106
+ }
107
+ }
108
+ return this.#options.host.store.has(channel);
109
+ }
110
+
111
+ async #hydrateSession(sessionId: string, session: URI): Promise<void> {
112
+ const chat = chatUri(sessionId);
113
+ const file = await this.#options.catalogue.findSessionFile(sessionId);
114
+ if (!file) {
115
+ return;
116
+ }
117
+
118
+ let manager: SessionManager;
119
+ try {
120
+ manager = SessionManager.open(file);
121
+ } catch (error) {
122
+ this.#options.log?.(`cannot open ${file}: ${String(error)}`);
123
+ return;
124
+ }
125
+
126
+ const turns = rebuildTurnsFromSession(manager, { turnIdPrefix: sessionId });
127
+ // Present only when the window really is a tail: its presence is the
128
+ // protocol's signal that more history can be paged in.
129
+ const turnsNextCursor = initialTurnsCursor(manager, sessionId, turns);
130
+ const lastTurn = turns[turns.length - 1];
131
+ let modifiedAt = lastTurn?.startedAt ?? new Date().toISOString();
132
+ try {
133
+ modifiedAt = (await stat(file)).mtime.toISOString();
134
+ } catch {
135
+ // The file may disappear after it was located; the reconstructed
136
+ // transcript timestamp remains a valid fallback for this snapshot.
137
+ }
138
+ const workingDirectory = manager.getCwd();
139
+ const title = sessionDisplayTitle(manager.getSessionName(), firstUserText(turns));
140
+
141
+ // Another create/adopt or disposal may have won during file I/O. From this
142
+ // check through adoption there is no await, so live state cannot be
143
+ // replaced by this disk snapshot after the check.
144
+ if (this.#options.isLive(session) || this.#options.isDisposing(session)) {
145
+ return;
146
+ }
147
+
148
+ // Read, for the same reason the catalogue reports read — see
149
+ // `readSessionSummary`. The reducer still clears the bit if a turn starts.
150
+ const status = SessionStatus.Idle | SessionStatus.IsRead;
151
+
152
+ // Which model this conversation was last using. The agent starts only on
153
+ // the first new turn, so without seeding it here the client's model
154
+ // picker is empty until the user has already sent something.
155
+ //
156
+ // The recorded model is checked field by field: a session file can carry a
157
+ // partially-populated entry, and the wire id needs both halves of pi's
158
+ // `(provider, modelId)` identity.
159
+ const recorded = manager.buildSessionContext();
160
+ const recordedProvider = recorded.model?.provider;
161
+ const recordedId = recorded.model?.modelId;
162
+ const selection: ModelSelection | undefined =
163
+ typeof recordedProvider === "string" &&
164
+ recordedProvider.length > 0 &&
165
+ typeof recordedId === "string" &&
166
+ recordedId.length > 0
167
+ ? {
168
+ id: modelSelectionId({ provider: recordedProvider, id: recordedId }),
169
+ config: { [THINKING_CONFIG_KEY]: recorded.thinkingLevel },
170
+ }
171
+ : this.#options.fallbackSelection?.();
172
+
173
+ const chatState: ChatState = {
174
+ resource: chat,
175
+ title,
176
+ status,
177
+ modifiedAt,
178
+ turns,
179
+ ...(turnsNextCursor ? { turnsNextCursor } : {}),
180
+ ...(selection ? { draft: { text: "", origin: { kind: MessageKind.User }, model: selection } } : {}),
181
+ };
182
+ this.#options.host.store.create(chat, chatState);
183
+
184
+ const sessionState: SessionState = {
185
+ provider: PI_PROVIDER,
186
+ title,
187
+ status,
188
+ // The transcript is genuinely available, so the session is ready. It
189
+ // simply has no agent attached until someone starts a turn.
190
+ lifecycle: SessionLifecycle.Ready,
191
+ ...(workingDirectory ? { workingDirectories: [pathToFileUri(workingDirectory)] } : {}),
192
+ activeClients: [],
193
+ chats: [chatSummaryOf(chatState)],
194
+ defaultChat: chat,
195
+ _meta: { piSessionFile: file, hydrated: true },
196
+ };
197
+ this.#options.host.store.create(session, sessionState, "session");
198
+
199
+ this.#options.adopt?.({
200
+ uri: session,
201
+ sessionId,
202
+ workingDirectory,
203
+ sessionManager: manager,
204
+ chatChannel: chat,
205
+ createdAt: turns[0]?.startedAt ?? modifiedAt,
206
+ });
207
+
208
+ this.#options.log?.(`hydrated ${session} with ${turns.length} turns`);
209
+ }
210
+ }
211
+
212
+ function firstUserText(turns: readonly { message: { text: string } }[]): string | undefined {
213
+ for (const turn of turns) {
214
+ if (turn.message.text.trim()) {
215
+ return turn.message.text;
216
+ }
217
+ }
218
+ return undefined;
219
+ }