@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,108 @@
1
+ package channels_test
2
+
3
+ import (
4
+ "encoding/json"
5
+ "reflect"
6
+ "testing"
7
+
8
+ "github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
9
+
10
+ "github.com/MichaelKinsy/pigpen/ahp/internal/channels"
11
+ "github.com/MichaelKinsy/pigpen/ahp/internal/twin"
12
+ )
13
+
14
+ // Twins of the "session chat aggregation" describe of upstream test/session-summary.test.ts.
15
+
16
+ const (
17
+ start = "2025-01-01T00:00:00.000Z"
18
+ end = "2025-01-01T00:00:01.000Z"
19
+ )
20
+
21
+ func summary(resource string, status ahptypes.SessionStatus, modifiedAt string) ahptypes.ChatSummary {
22
+ return ahptypes.ChatSummary{Resource: resource, Title: resource, Status: status, ModifiedAt: modifiedAt}
23
+ }
24
+
25
+ func withActivity(s ahptypes.ChatSummary, activity string) ahptypes.ChatSummary {
26
+ s.Activity = &activity
27
+ return s
28
+ }
29
+
30
+ func sessionState(chats ...ahptypes.ChatSummary) *ahptypes.SessionState {
31
+ return &ahptypes.SessionState{
32
+ Provider: "pi", Title: "Session", Status: ahptypes.SessionStatusIdle, Lifecycle: ahptypes.SessionLifecycleReady,
33
+ ActiveClients: []ahptypes.SessionActiveClient{}, Chats: chats,
34
+ }
35
+ }
36
+
37
+ func sp(s string) *string { return &s }
38
+
39
+ func check(t *testing.T, got channels.SessionChatAggregate, status ahptypes.SessionStatus, activity *string, modifiedAt string) {
40
+ t.Helper()
41
+ want := channels.SessionChatAggregate{Status: status, Activity: activity, ModifiedAt: modifiedAt}
42
+ if !reflect.DeepEqual(got, want) {
43
+ gj, _ := json.Marshal(got)
44
+ wj, _ := json.Marshal(want)
45
+ t.Fatalf("aggregate = %s, want %s", gj, wj)
46
+ }
47
+ }
48
+
49
+ func TestSessionChatAggregation(t *testing.T) {
50
+ twin.Run(t, "session-summary", "passes through single-chat activity but preserves session-owned and unknown flags", func(t *testing.T) {
51
+ flags := ahptypes.SessionStatusIsRead | ahptypes.SessionStatusIsArchived | (1 << 10)
52
+ for _, status := range []ahptypes.SessionStatus{
53
+ ahptypes.SessionStatusIdle, ahptypes.SessionStatusInProgress, ahptypes.SessionStatusError, ahptypes.SessionStatusInputNeeded,
54
+ } {
55
+ state := sessionState(withActivity(summary("chat", status, start), "Working"))
56
+ state.Status = ahptypes.SessionStatusIdle | flags
57
+ before, _ := json.Marshal(state)
58
+ check(t, channels.AggregateSessionChats(state), status|flags, sp("Working"), start)
59
+ after, _ := json.Marshal(state)
60
+ if string(before) != string(after) {
61
+ t.Fatal("projection must not mutate source state")
62
+ }
63
+ }
64
+ state := sessionState(summary("chat", ahptypes.SessionStatusIdle|ahptypes.SessionStatusIsRead, start))
65
+ if got := channels.AggregateSessionChats(state).Status; got != ahptypes.SessionStatusIdle {
66
+ t.Fatalf("chat flags must not leak into the session; status = %d", got)
67
+ }
68
+ })
69
+
70
+ twin.Run(t, "session-summary", "uses default chat activity but the latest timestamp, comparing instants rather than strings", func(t *testing.T) {
71
+ recent := "2024-12-31T19:01:00-05:00"
72
+ state := sessionState(
73
+ withActivity(summary("default", ahptypes.SessionStatusIdle, start), "Default"),
74
+ withActivity(summary("recent", ahptypes.SessionStatusInProgress, recent), "Recent"),
75
+ )
76
+ state.DefaultChat = sp("default")
77
+ check(t, channels.AggregateSessionChats(state), ahptypes.SessionStatusIdle, sp("Default"), recent)
78
+ for _, defaultChat := range []*string{nil, sp("missing")} {
79
+ state.DefaultChat = defaultChat
80
+ check(t, channels.AggregateSessionChats(state), ahptypes.SessionStatusInProgress, sp("Recent"), recent)
81
+ }
82
+ })
83
+
84
+ twin.Run(t, "session-summary", "promotes Error and InputNeeded, preferring the most recent blocker of the winning kind", func(t *testing.T) {
85
+ state := sessionState(summary("default", ahptypes.SessionStatusIdle, start), withActivity(summary("error", ahptypes.SessionStatusError, start), "Error"))
86
+ state.DefaultChat = sp("default")
87
+ check(t, channels.AggregateSessionChats(state), ahptypes.SessionStatusError, sp("Error"), start)
88
+ state.Chats = append(state.Chats,
89
+ summary("old-input", ahptypes.SessionStatusInputNeeded, start),
90
+ withActivity(summary("input", ahptypes.SessionStatusInputNeeded, end), "Approval"))
91
+ check(t, channels.AggregateSessionChats(state), ahptypes.SessionStatusInputNeeded, sp("Approval"), end)
92
+ })
93
+
94
+ twin.Run(t, "session-summary", "handles an empty catalogue and chooses stable ties and valid timestamps", func(t *testing.T) {
95
+ empty := sessionState()
96
+ empty.Activity = sp("Starting")
97
+ check(t, channels.AggregateSessionChats(empty), ahptypes.SessionStatusIdle, sp("Starting"), "")
98
+ if got := channels.SessionSummaryOf("session", start, empty, nil).ModifiedAt; got != start {
99
+ t.Fatalf("modifiedAt = %s", got)
100
+ }
101
+ state := sessionState(
102
+ withActivity(summary("invalid", ahptypes.SessionStatusIdle, "invalid"), "Invalid"),
103
+ withActivity(summary("first", ahptypes.SessionStatusInProgress, start), "First"),
104
+ withActivity(summary("tie", ahptypes.SessionStatusIdle, start), "Tie"),
105
+ )
106
+ check(t, channels.AggregateSessionChats(state), ahptypes.SessionStatusInProgress, sp("First"), start)
107
+ })
108
+ }
@@ -0,0 +1,153 @@
1
+ // Package compose is the composition root (port of src/host/pi-host.ts createPiHost): it builds
2
+ // a host and wires the independently tested services to it. Nothing here opens a socket; the
3
+ // caller decides whether and where to listen.
4
+ package compose
5
+
6
+ import (
7
+ "errors"
8
+ "sync"
9
+
10
+ ahptypes "github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
11
+
12
+ "github.com/MichaelKinsy/pigpen/ahp/internal/host"
13
+ "github.com/MichaelKinsy/pigpen/ahp/internal/pi"
14
+ "github.com/MichaelKinsy/pigpen/ahp/internal/svc"
15
+ )
16
+
17
+ // MentionTrigger is the character that starts a file mention in a message.
18
+ const MentionTrigger = "@"
19
+
20
+ // FilesystemOptions opts a client into browsing and editing files (resources and watches). The
21
+ // remote filesystem is never exposed unless this is given: Roots confine every path, and
22
+ // Unrestricted is the explicit way to drop the confinement.
23
+ type FilesystemOptions struct {
24
+ Roots []string
25
+ Unrestricted bool
26
+ }
27
+
28
+ // TerminalOptions opts a client into shells on this machine.
29
+ type TerminalOptions struct {
30
+ Shell string
31
+ Spawn svc.PtySpawner // nil = the real PTY
32
+ }
33
+
34
+ // Options configure Build.
35
+ type Options struct {
36
+ ServerInfo *ahptypes.Implementation
37
+ WorkingDirectory string
38
+ // Models lists the models offered to clients; nil or empty means none.
39
+ Models func() []pi.Model
40
+ // SessionRoot is where Pi's session files live, for the catalogue and hydration.
41
+ SessionRoot string
42
+ // CreateSessionManager is the storage for sessions created through the host.
43
+ CreateSessionManager pi.SessionManagerFactory
44
+ CreateBackend pi.BackendFactory
45
+ DefaultSelection func() *ahptypes.ModelSelection
46
+ DeleteFile func(path string) (pi.SessionFileDeletionResult, error)
47
+ // WrapStore replaces the store of a session hydrated from disk (see pi.HydratorOptions).
48
+ WrapStore func(sessionID string, opened pi.SessionStore) pi.SessionStore
49
+ // AdoptForeignTurns mirrors prompts typed into the session locally into its chat.
50
+ AdoptForeignTurns bool
51
+ AgentDir string
52
+ ProjectTrustPolicy pi.ProjectTrustPolicy
53
+ Log func(format string, args ...any)
54
+
55
+ // Filesystem and Terminals are nil unless the user opted in.
56
+ Filesystem *FilesystemOptions
57
+ Terminals *TerminalOptions
58
+ }
59
+
60
+ // Built is the composed host and the services a caller may need to reach or shut down.
61
+ type Built struct {
62
+ Host *host.Host
63
+ Sessions *pi.Services
64
+ Resources *svc.ResourceService // nil unless Filesystem was given
65
+ Watches *svc.WatchService // nil unless Filesystem was given
66
+ Terminals *svc.TerminalService // nil unless Terminals was given
67
+
68
+ closeOnce sync.Once
69
+ }
70
+
71
+ // Build composes the host.
72
+ func Build(opts Options) (*Built, error) {
73
+ if opts.CreateBackend == nil {
74
+ return nil, errors.New("compose: CreateBackend is required")
75
+ }
76
+ if opts.CreateSessionManager == nil {
77
+ opts.CreateSessionManager = pi.InMemoryStorage
78
+ }
79
+ logf := func(format string, args ...any) {
80
+ if opts.Log != nil {
81
+ opts.Log(format, args...)
82
+ }
83
+ }
84
+ h := host.New(host.Options{
85
+ ServerInfo: opts.ServerInfo, CompletionTriggerCharacters: []string{MentionTrigger},
86
+ Log: func(message string) { logf("%s", message) },
87
+ })
88
+ var models []pi.Model
89
+ if opts.Models != nil {
90
+ models = opts.Models()
91
+ }
92
+ h.InstallRootChannel([]ahptypes.AgentInfo{pi.BuildAgentInfo(models)})
93
+
94
+ deleteFile := opts.DeleteFile
95
+ if deleteFile == nil {
96
+ deleteFile = pi.FileDeleter
97
+ }
98
+ sessions := pi.NewServices(pi.ServicesOptions{
99
+ Host: h, SessionRoot: opts.SessionRoot, DefaultWorkingDirectory: opts.WorkingDirectory,
100
+ CreateBackend: opts.CreateBackend, CreateSessionManager: opts.CreateSessionManager,
101
+ DefaultSelection: opts.DefaultSelection, DeleteFile: deleteFile, WrapStore: opts.WrapStore, AdoptForeignTurns: opts.AdoptForeignTurns, Log: logf,
102
+ })
103
+ built := &Built{Host: h, Sessions: sessions}
104
+
105
+ caps := sessions.Capabilities()
106
+ caps.SessionConfig = pi.NewSessionConfigService(pi.SessionConfigOptions{
107
+ DefaultWorkingDirectory: opts.WorkingDirectory, ProjectTrustPolicy: opts.ProjectTrustPolicy, AgentDir: opts.AgentDir,
108
+ })
109
+ caps.Completions = pi.NewCompletionService(pi.CompletionServiceOptions{WorkingDirectoryFor: func(chat string) string {
110
+ if s, ok := sessions.Registry.GetByChat(chat); ok {
111
+ return s.WorkingDirectory
112
+ }
113
+ return ""
114
+ }})
115
+
116
+ if fs := opts.Filesystem; fs != nil {
117
+ if len(fs.Roots) == 0 && !fs.Unrestricted {
118
+ return nil, errors.New("compose: filesystem access needs roots, or Unrestricted set explicitly")
119
+ }
120
+ var roots []string
121
+ if !fs.Unrestricted {
122
+ roots = fs.Roots
123
+ }
124
+ policy, err := svc.NewPathPolicy(roots...)
125
+ if err != nil {
126
+ return nil, err
127
+ }
128
+ built.Resources = svc.NewResourceService(svc.ResourceOptions{Paths: policy})
129
+ built.Watches = svc.NewWatchService(h, svc.WatchOptions{Paths: policy, Log: func(m string) { logf("%s", m) }})
130
+ caps.Resources = built.Resources
131
+ caps.ResourceWatches = built.Watches
132
+ }
133
+ if t := opts.Terminals; t != nil {
134
+ built.Terminals = svc.NewTerminalService(h, svc.TerminalOptions{
135
+ DefaultWorkingDirectory: opts.WorkingDirectory, Shell: t.Shell, Spawn: t.Spawn, Log: func(m string) { logf("%s", m) },
136
+ })
137
+ caps.Terminals = built.Terminals
138
+ }
139
+ h.Serve(caps)
140
+ return built, nil
141
+ }
142
+
143
+ // Close shuts the optional services down: terminals are killed, watches released.
144
+ func (b *Built) Close() {
145
+ b.closeOnce.Do(func() {
146
+ if b.Terminals != nil {
147
+ b.Terminals.Shutdown()
148
+ }
149
+ if b.Watches != nil {
150
+ <-b.Watches.Dispose()
151
+ }
152
+ })
153
+ }
@@ -0,0 +1,120 @@
1
+ package compose
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "os"
7
+ "path/filepath"
8
+ "reflect"
9
+ "testing"
10
+
11
+ ahptypes "github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
12
+
13
+ "github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
14
+ "github.com/MichaelKinsy/pigpen/ahp/internal/pi"
15
+ "github.com/MichaelKinsy/pigpen/ahp/internal/testkit"
16
+ "github.com/MichaelKinsy/pigpen/ahp/internal/twin"
17
+ "github.com/MichaelKinsy/pigpen/ahp/internal/wire"
18
+ )
19
+
20
+ type idleBackend struct{}
21
+
22
+ func (idleBackend) Subscribe(func(mapper.Event)) func() { return func() {} }
23
+ func (idleBackend) Prompt(context.Context, string, []mapper.Image) error { return nil }
24
+ func (idleBackend) Steer(context.Context, string, []mapper.Image) error { return nil }
25
+ func (idleBackend) Abort(context.Context) error { return nil }
26
+
27
+ func TestComposition(t *testing.T) {
28
+ // Twin of upstream test/pi-host.test.ts. Upstream's resource read works without roots; here the
29
+ // unrestricted filesystem must be asked for explicitly (see PORT.md).
30
+ twin.Run(t, "pi-host", "wires product services through createPiHost", func(t *testing.T) {
31
+ workspace, _ := filepath.EvalSymlinks(t.TempDir())
32
+ sessionRoot := t.TempDir()
33
+ if err := os.WriteFile(filepath.Join(workspace, "notes.md"), []byte("# Notes\n"), 0o644); err != nil {
34
+ t.Fatal(err)
35
+ }
36
+ built, err := Build(Options{
37
+ ServerInfo: &ahptypes.Implementation{Name: "pi-ahp"}, WorkingDirectory: workspace, SessionRoot: sessionRoot,
38
+ CreateSessionManager: pi.PersistentStorage(sessionRoot),
39
+ CreateBackend: func(*pi.LiveSession) (pi.Backend, error) { return idleBackend{}, nil },
40
+ DeleteFile: func(string) (pi.SessionFileDeletionResult, error) { return pi.SessionFileDeletionResult{OK: true}, nil },
41
+ Filesystem: &FilesystemOptions{Unrestricted: true},
42
+ Terminals: &TerminalOptions{},
43
+ })
44
+ if err != nil {
45
+ t.Fatal(err)
46
+ }
47
+ defer built.Close()
48
+ c := testkit.Connect(t, built.Host)
49
+ defer c.Close()
50
+ init := c.Initialize("composition-client", map[string]any{"initialSubscriptions": []string{wire.RootChannel}})
51
+ if !reflect.DeepEqual(init.CompletionTriggerCharacters, []string{"@"}) {
52
+ t.Fatalf("trigger characters %v", init.CompletionTriggerCharacters)
53
+ }
54
+
55
+ var resource ahptypes.ResourceReadResult
56
+ c.Decode(c.Must("resourceRead", map[string]any{"channel": wire.RootChannel, "uri": wire.PathToFileURI(filepath.Join(workspace, "notes.md"))}), &resource)
57
+ if resource.Data != "# Notes\n" {
58
+ t.Fatalf("%+v", resource)
59
+ }
60
+
61
+ var config struct {
62
+ Schema struct{ Properties map[string]any }
63
+ }
64
+ c.Decode(c.Must("resolveSessionConfig", map[string]any{"channel": wire.RootChannel, "workingDirectory": wire.PathToFileURI(workspace)}), &config)
65
+ if config.Schema.Properties[pi.ProjectTrustKey] == nil {
66
+ t.Fatalf("no project trust property in %v", config.Schema.Properties)
67
+ }
68
+
69
+ id := "0a3c1e52-6b7d-4f3a-9d10-000000000001"
70
+ session, chat := wire.SessionURI(id), wire.ChatURI(id)
71
+ c.Must("createSession", map[string]any{"channel": session})
72
+ c.Must("subscribe", map[string]any{"channel": chat})
73
+ testkit.Eventually(t, "the composed session backend to become ready", func() bool {
74
+ s := built.Host.Store().Session(session)
75
+ return s != nil && s.Lifecycle == ahptypes.SessionLifecycleReady
76
+ })
77
+
78
+ var completions ahptypes.CompletionsResult
79
+ c.Decode(c.Must("completions", map[string]any{"channel": chat, "kind": "userMessage", "text": "see @not", "offset": 8}), &completions)
80
+ var inserted []string
81
+ for _, item := range completions.Items {
82
+ inserted = append(inserted, item.InsertText)
83
+ }
84
+ if !reflect.DeepEqual(inserted, []string{"@notes.md"}) {
85
+ raw, _ := json.Marshal(completions)
86
+ t.Fatalf("completions %s", raw)
87
+ }
88
+ })
89
+ }
90
+
91
+ func TestNothingIsExposedByDefault(t *testing.T) {
92
+ // Additions: fs and terminals are opt-in, and an unconfined filesystem must be explicit.
93
+ built, err := Build(Options{WorkingDirectory: t.TempDir(), CreateBackend: func(*pi.LiveSession) (pi.Backend, error) { return idleBackend{}, nil }})
94
+ if err != nil {
95
+ t.Fatal(err)
96
+ }
97
+ defer built.Close()
98
+ if built.Resources != nil || built.Watches != nil || built.Terminals != nil {
99
+ t.Fatal("no filesystem or terminal service may exist without opt-in")
100
+ }
101
+ c := testkit.Connect(t, built.Host)
102
+ defer c.Close()
103
+ c.Initialize("nothing", nil)
104
+ c.ExpectError("resourceRead", map[string]any{"channel": wire.RootChannel, "uri": "file:///etc/hostname"}, wire.CodeMethodNotFound)
105
+ c.ExpectError("createTerminal", map[string]any{"channel": "ahp-terminal:/x", "claim": map[string]any{"kind": "client", "clientId": "nothing"}}, wire.CodeMethodNotFound)
106
+ c.ExpectError("createResourceWatch", map[string]any{"channel": wire.RootChannel, "uri": "file:///tmp"}, wire.CodeMethodNotFound)
107
+
108
+ if _, err := Build(Options{CreateBackend: func(*pi.LiveSession) (pi.Backend, error) { return idleBackend{}, nil }, Filesystem: &FilesystemOptions{}}); err == nil {
109
+ t.Fatal("a filesystem with no roots and no explicit Unrestricted must be refused")
110
+ }
111
+ confined, err := Build(Options{WorkingDirectory: t.TempDir(), CreateBackend: func(*pi.LiveSession) (pi.Backend, error) { return idleBackend{}, nil }, Filesystem: &FilesystemOptions{Roots: []string{t.TempDir()}}})
112
+ if err != nil {
113
+ t.Fatal(err)
114
+ }
115
+ defer confined.Close()
116
+ cc := testkit.Connect(t, confined.Host)
117
+ defer cc.Close()
118
+ cc.Initialize("confined", nil)
119
+ cc.ExpectError("resourceRead", map[string]any{"channel": wire.RootChannel, "uri": "file:///etc/hostname"}, wire.CodePermissionDenied)
120
+ }
@@ -0,0 +1,88 @@
1
+ // Package gaps records the upstream (pi-ahp) test cases that have no runnable Go twin, each as a
2
+ // named skipped twin with its reason. Nothing here is silent: the twin gate counts these, and
3
+ // proof/PORT.md explains every family.
4
+ package gaps
5
+
6
+ import (
7
+ "testing"
8
+
9
+ "github.com/MichaelKinsy/pigpen/ahp/internal/twin"
10
+ )
11
+
12
+ func TestGapTunnel(t *testing.T) {
13
+ twin.Skip(t, "tunnel", "uses the shared discovery labels and port", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
14
+ twin.Skip(t, "tunnel", "keeps the identity label out of the display name", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
15
+ twin.Skip(t, "tunnel", "folds a name the way VS Code does", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
16
+ twin.Skip(t, "tunnel", "reports when the CLI is not installed", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
17
+ twin.Skip(t, "tunnel", "requires a logged-in account", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
18
+ twin.Skip(t, "tunnel", "reports account inspection failures", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
19
+ twin.Skip(t, "tunnel", "finds this host's first labelled tunnel", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
20
+ twin.Skip(t, "tunnel", "returns no tunnel for a successful empty listing", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
21
+ twin.Skip(t, "tunnel", "reports tunnel listing failures and malformed responses", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
22
+ twin.Skip(t, "tunnel", "creates a tunnel with identity, protocol, launcher, and display labels", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
23
+ twin.Skip(t, "tunnel", "rejects failed or malformed tunnel creation", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
24
+ twin.Skip(t, "tunnel", "renames a tunnel without disturbing reserved labels", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
25
+ twin.Skip(t, "tunnel", "reports a failed rename", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
26
+ twin.Skip(t, "tunnel", "creates port 31546 when a successful listing is empty", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
27
+ twin.Skip(t, "tunnel", "keeps an existing HTTP port", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
28
+ twin.Skip(t, "tunnel", "replaces an old HTTPS port with an HTTP port", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
29
+ twin.Skip(t, "tunnel", "does not discard port-specific access control while migrating", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
30
+ twin.Skip(t, "tunnel", "uses current port details when the protocol changes during inspection", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
31
+ twin.Skip(t, "tunnel", "rejects malformed port inspection output", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
32
+ twin.Skip(t, "tunnel", "reports listing, inspection, deletion, and creation failures", "dev tunnel is not bundled: an owner decision (ACP, AHP and A2A are separate extensions, tunnel management stays outside this port)")
33
+ }
34
+
35
+ func TestGapPiReplay(t *testing.T) {
36
+ twin.Skip(t, "pi-replay", "plain-text", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
37
+ twin.Skip(t, "pi-replay", "single-tool", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
38
+ twin.Skip(t, "pi-replay", "parallel-tools", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
39
+ twin.Skip(t, "pi-replay", "tool-loop", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
40
+ twin.Skip(t, "pi-replay", "tool-error", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
41
+ twin.Skip(t, "pi-replay", "abort", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
42
+ twin.Skip(t, "pi-replay", "steering", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
43
+ twin.Skip(t, "pi-replay", "tool-edit", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
44
+ twin.Skip(t, "pi-replay", "tool-write", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
45
+ twin.Skip(t, "pi-replay", "tool-bash", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
46
+ twin.Skip(t, "pi-replay", "tool-ls", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
47
+ twin.Skip(t, "pi-replay", "tool-grep", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
48
+ twin.Skip(t, "pi-replay", "tool-find", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
49
+ twin.Skip(t, "pi-replay", "compaction", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
50
+ twin.Skip(t, "pi-replay", "bash-long-output", "replays provider streams through a live Pi agent loop, which a Go extension cannot embed; the recorded-stream half is the mapper-fixtures twins and the live half is the layer-6 differential against a scripted provider")
51
+ }
52
+
53
+ func TestGapChangeset(t *testing.T) {
54
+ twin.Skip(t, "changeset", "keeps the latest commit separate from uncommitted work and refreshes it", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
55
+ twin.Skip(t, "changeset", "limits changes to the session working-directory subtree", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
56
+ twin.Skip(t, "changeset", "ignores ambient Git repository overrides", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
57
+ twin.Skip(t, "changeset", "omits changesets when Git is unavailable", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
58
+ twin.Skip(t, "changeset", "omits changesets outside Git workspaces", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
59
+ twin.Skip(t, "changeset", "omits Latest Commit before the repository has a commit", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
60
+ twin.Skip(t, "changeset", "resumes changeset updates when durable session deletion fails", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
61
+ twin.Skip(t, "changeset", "hydrates the parent session when its changeset is subscribed directly", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
62
+ }
63
+
64
+ func TestGapChangesetLifecycle(t *testing.T) {
65
+ twin.Skip(t, "changeset-lifecycle", "observes changes during the initial scan and rebuilds changed ignore rules", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
66
+ twin.Skip(t, "changeset-lifecycle", "refreshes file changes while a turn is still active", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
67
+ twin.Skip(t, "changeset-lifecycle", "does not recursively watch when ignored paths cannot be determined", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
68
+ twin.Skip(t, "changeset-lifecycle", "publishes an explicit error when Git cannot compute the changeset", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
69
+ twin.Skip(t, "changeset-lifecycle", "drains an in-flight scan before deleting its channel", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
70
+ }
71
+
72
+ func TestGapChangesetURI(t *testing.T) {
73
+ twin.Skip(t, "changeset-uri", "builds stable static catalogue channels", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
74
+ twin.Skip(t, "changeset-uri", "rejects channels the host did not advertise", "git changeset service not ported in this release (documented gap: no changeset channels are advertised, see PORT.md)")
75
+ }
76
+
77
+ func TestGapImageSession(t *testing.T) {
78
+ twin.Skip(t, "image-session", "passes and persists images while honouring preflight cancellation", "needs Pi's InProcessPiBackend with a faux model; the SDK adapter's image path is covered by its own adapter tests")
79
+ }
80
+
81
+ func TestGapLiveTurn(t *testing.T) {
82
+ twin.Skip(t, "live-turn", "live turn # SKIP needs a real model \u2014 set PI_AHP_LIVE_SETTINGS and PI_AHP_LIVE_AUTH", "upstream itself skips this case without a real model (PI_AHP_LIVE_SETTINGS and PI_AHP_LIVE_AUTH)")
83
+ }
84
+
85
+ func TestGapModelDiscovery(t *testing.T) {
86
+ twin.Skip(t, "model-discovery", "advertises extension models and uses pi's configured default", "needs an embedded Pi model registry with extension-provided models; the SDK adapter reads models through the PiG SDK and is covered by adapter tests")
87
+ twin.Skip(t, "model-discovery", "resolves stale and provider-qualified model selections safely", "needs an embedded Pi model registry with extension-provided models; the SDK adapter reads models through the PiG SDK and is covered by adapter tests")
88
+ }