@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,199 @@
1
+ package mapper
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "strings"
7
+ )
8
+
9
+ // Human-readable descriptions of what the agent is doing right now (port of src/pi/activity.ts).
10
+ // They are display strings a client renders verbatim next to its spinner.
11
+
12
+ const (
13
+ // ThinkingActivity and RespondingActivity are the two model-side activities.
14
+ ThinkingActivity = "Thinking"
15
+ RespondingActivity = "Responding"
16
+
17
+ maxLength = 60
18
+ )
19
+
20
+ // shorten trims the middle rather than the end: the informative parts of a path or a shell
21
+ // command sit at the two ends.
22
+ func shorten(value string, limit ...int) string {
23
+ lim := maxLength
24
+ if len(limit) > 0 {
25
+ lim = limit[0]
26
+ }
27
+ collapsed := collapseSpace(value)
28
+ n := utf16Len(collapsed)
29
+ if n <= lim {
30
+ return collapsed
31
+ }
32
+ head := (lim - 1 + 1) / 2 // Math.ceil((limit-1)/2)
33
+ tail := (lim - 1) / 2 // Math.floor((limit-1)/2)
34
+ return sliceUTF16(collapsed, 0, head) + "…" + sliceUTF16(collapsed, n-tail, n)
35
+ }
36
+
37
+ // displayPath renders a path relative to the working directory when it sits underneath it.
38
+ func displayPath(value any, workingDirectory string) (string, bool) {
39
+ s, ok := value.(string)
40
+ if !ok || s == "" {
41
+ return "", false
42
+ }
43
+ if workingDirectory != "" && strings.HasPrefix(s, workingDirectory+"/") {
44
+ s = s[len(workingDirectory)+1:]
45
+ }
46
+ return shorten(s), true
47
+ }
48
+
49
+ func argsMap(args any) map[string]any {
50
+ m, _ := args.(map[string]any)
51
+ return m
52
+ }
53
+
54
+ // DescribeToolCall describes a tool invocation. pi's built-in tools are named individually;
55
+ // anything else falls back to its own name.
56
+ func DescribeToolCall(toolName string, args any, workingDirectory string) string {
57
+ input := argsMap(args)
58
+ path, hasPath := displayPath(input["path"], workingDirectory)
59
+ switch toolName {
60
+ case "read":
61
+ if hasPath {
62
+ return "Reading " + path
63
+ }
64
+ return "Reading a file"
65
+ case "write":
66
+ if hasPath {
67
+ return "Writing " + path
68
+ }
69
+ return "Writing a file"
70
+ case "edit":
71
+ if hasPath {
72
+ return "Editing " + path
73
+ }
74
+ return "Editing a file"
75
+ case "bash":
76
+ if c, ok := input["command"].(string); ok {
77
+ if command := shorten(c); command != "" {
78
+ return "Running " + command
79
+ }
80
+ }
81
+ return "Running a command"
82
+ case "ls":
83
+ if hasPath {
84
+ return "Listing " + path
85
+ }
86
+ return "Listing files"
87
+ case "find":
88
+ if p, ok := input["pattern"].(string); ok {
89
+ if pattern := shorten(p, 30); pattern != "" {
90
+ return "Finding " + pattern
91
+ }
92
+ }
93
+ return "Finding files"
94
+ case "grep":
95
+ if p, ok := input["pattern"].(string); ok {
96
+ if pattern := shorten(p, 30); pattern != "" {
97
+ return "Searching for " + pattern
98
+ }
99
+ }
100
+ return "Searching"
101
+ default:
102
+ return "Running " + shorten(toolName, 40)
103
+ }
104
+ }
105
+
106
+ func stringArg(v any) (string, bool) {
107
+ s, ok := v.(string)
108
+ return s, ok && s != ""
109
+ }
110
+
111
+ // fullArguments is JSON.stringify(args, null, 2). Go marshals object keys in sorted order where
112
+ // JavaScript keeps insertion order; Pi's events reach an extension already decoded, so the
113
+ // original order is not available (documented in PORT.md).
114
+ func fullArguments(args any) (string, bool) {
115
+ if args == nil {
116
+ return "", false
117
+ }
118
+ out, err := StringifyJSON(args, true)
119
+ if err != nil {
120
+ return "", false
121
+ }
122
+ return out, true
123
+ }
124
+
125
+ // StringifyJSON is JSON.stringify(v) (or JSON.stringify(v, null, 2) when indent is set): unlike
126
+ // encoding/json.Marshal it leaves <, > and & unescaped.
127
+ func StringifyJSON(v any, indent bool) (string, error) {
128
+ var buf bytes.Buffer
129
+ enc := json.NewEncoder(&buf)
130
+ enc.SetEscapeHTML(false)
131
+ if indent {
132
+ enc.SetIndent("", " ")
133
+ }
134
+ if err := enc.Encode(v); err != nil {
135
+ return "", err
136
+ }
137
+ return strings.TrimSuffix(buf.String(), "\n"), nil
138
+ }
139
+
140
+ // ToolInputFor is the one argument that says what a tool call is about. Anything without an
141
+ // obvious subject keeps the full arguments. ok is false when there is nothing to show.
142
+ func ToolInputFor(toolName string, args any) (string, bool) {
143
+ input := argsMap(args)
144
+ switch toolName {
145
+ case "bash":
146
+ if s, ok := stringArg(input["command"]); ok {
147
+ return s, true
148
+ }
149
+ case "grep", "find":
150
+ if s, ok := searchInput(input); ok {
151
+ return s, true
152
+ }
153
+ case "read", "write", "edit", "ls":
154
+ if s, ok := stringArg(input["path"]); ok {
155
+ return s, true
156
+ }
157
+ }
158
+ return fullArguments(args)
159
+ }
160
+
161
+ // searchInput renders a search as its pattern plus whatever narrows it. Models restate the
162
+ // defaults (`path: "."`, a glob matching everything, `ignoreCase: false`), so only arguments that
163
+ // actually narrow the search are shown, in the order `rg` writes them.
164
+ func searchInput(input map[string]any) (string, bool) {
165
+ pattern, ok := stringArg(input["pattern"])
166
+ if !ok {
167
+ return "", false
168
+ }
169
+ parts := []string{pattern}
170
+ if glob, ok := stringArg(input["glob"]); ok && glob != "**/*" {
171
+ parts = append(parts, "--glob "+glob)
172
+ }
173
+ if ic, _ := input["ignoreCase"].(bool); ic {
174
+ parts = append(parts, "--ignore-case")
175
+ }
176
+ if path, ok := stringArg(input["path"]); ok && path != "." {
177
+ parts = append(parts, "in "+path)
178
+ }
179
+ return strings.Join(parts, " "), true
180
+ }
181
+
182
+ var pastTenseVerbs = map[string]string{
183
+ "Reading": "Read", "Writing": "Wrote", "Editing": "Edited", "Running": "Ran",
184
+ "Listing": "Listed", "Finding": "Found", "Searching": "Searched",
185
+ }
186
+
187
+ // DescribeFinishedToolCall is the same description phrased for a call that has finished: only
188
+ // the leading verb changes, which keeps the subject identical.
189
+ func DescribeFinishedToolCall(toolName string, args any, workingDirectory string) string {
190
+ description := DescribeToolCall(toolName, args, workingDirectory)
191
+ verb, rest, _ := strings.Cut(description, " ")
192
+ if past, ok := pastTenseVerbs[verb]; ok {
193
+ if rest == "" && !strings.Contains(description, " ") {
194
+ return past
195
+ }
196
+ return past + " " + rest
197
+ }
198
+ return description
199
+ }
@@ -0,0 +1,188 @@
1
+ package mapper_test
2
+
3
+ import (
4
+ "regexp"
5
+ "strings"
6
+ "testing"
7
+ "unicode/utf16"
8
+
9
+ "github.com/microsoft/agent-host-protocol/clients/go/ahp"
10
+ "github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
11
+
12
+ "github.com/MichaelKinsy/pigpen/ahp/internal/channels"
13
+ "github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
14
+ "github.com/MichaelKinsy/pigpen/ahp/internal/twin"
15
+ )
16
+
17
+ func utf16Len(s string) int { return len(utf16.Encode([]rune(s))) }
18
+
19
+ // Twins of upstream test/activity.test.ts, describe "activity descriptions".
20
+ func TestActivityDescriptions(t *testing.T) {
21
+ describe := func(name string, args any, cwd string) string { return mapper.DescribeToolCall(name, args, cwd) }
22
+
23
+ twin.Run(t, "activity", "names the file a read touches", func(t *testing.T) {
24
+ if got := describe("read", obj{"path": "/ws/src/main.ts"}, "/ws"); got != "Reading src/main.ts" {
25
+ t.Fatal(got)
26
+ }
27
+ })
28
+
29
+ twin.Run(t, "activity", "renders paths relative to the workspace", func(t *testing.T) {
30
+ // An absolute path is mostly noise the user already knows.
31
+ if got := describe("edit", obj{"path": "/ws/a/b.ts"}, "/ws"); got != "Editing a/b.ts" {
32
+ t.Fatal(got)
33
+ }
34
+ if got := describe("edit", obj{"path": "/elsewhere/b.ts"}, "/ws"); got != "Editing /elsewhere/b.ts" {
35
+ t.Fatal(got)
36
+ }
37
+ })
38
+
39
+ twin.Run(t, "activity", "shows the command a bash call runs", func(t *testing.T) {
40
+ if got := describe("bash", obj{"command": "npm test"}, ""); got != "Running npm test" {
41
+ t.Fatal(got)
42
+ }
43
+ })
44
+
45
+ twin.Run(t, "activity", "shortens the middle, keeping both informative ends", func(t *testing.T) {
46
+ long := describe("bash", obj{"command": "git log " + strings.Repeat("x", 200) + " --oneline"}, "")
47
+ if utf16Len(long) >= 80 {
48
+ t.Fatalf("not shortened: %d units", utf16Len(long))
49
+ }
50
+ if !regexp.MustCompile(`^Running git log`).MatchString(long) || !strings.HasSuffix(long, "--oneline") {
51
+ t.Fatal(long)
52
+ }
53
+ })
54
+
55
+ twin.Run(t, "activity", "falls back to the tool's own name", func(t *testing.T) {
56
+ // Extension and custom tools still say something useful.
57
+ if got := describe("my_custom_tool", obj{}, ""); got != "Running my_custom_tool" {
58
+ t.Fatal(got)
59
+ }
60
+ })
61
+
62
+ twin.Run(t, "activity", "copes with missing arguments", func(t *testing.T) {
63
+ if got := describe("read", nil, ""); got != "Reading a file" {
64
+ t.Fatal(got)
65
+ }
66
+ if got := describe("bash", obj{}, ""); got != "Running a command" {
67
+ t.Fatal(got)
68
+ }
69
+ })
70
+ }
71
+
72
+ func runActivity(t testing.TB, events []obj) replayed {
73
+ t.Helper()
74
+ m := mapper.NewTurnMapper("t", 0, mapper.Options{WorkingDirectory: "/ws"})
75
+ actions := []ahptypes.StateAction{mapper.UserTurnStarted("t", "go", "1970-01-01T00:00:00.000Z")}
76
+ for _, e := range events {
77
+ actions = append(actions, m.Handle(e)...)
78
+ }
79
+ state := channels.InitialChatState("ahp-chat:/a", "chat", nil)
80
+ for _, a := range actions {
81
+ ahp.ApplyActionToChat(state, a)
82
+ }
83
+ return replayed{actions: actions, state: toObj(t, state)}
84
+ }
85
+
86
+ // activities lists each chat/activityChanged action's activity, "<cleared>" when it has none.
87
+ func activities(actions []ahptypes.StateAction) []string {
88
+ out := []string{}
89
+ for _, a := range ofType(actions, "chat/activityChanged") {
90
+ if s, ok := a["activity"].(string); ok {
91
+ out = append(out, s)
92
+ } else {
93
+ out = append(out, "<cleared>")
94
+ }
95
+ }
96
+ return out
97
+ }
98
+
99
+ func toolStartEvent(id, name string, args any) obj {
100
+ return obj{"type": "tool_execution_start", "toolCallId": id, "toolName": name, "args": args}
101
+ }
102
+
103
+ // Twins of upstream test/activity.test.ts, describe "activity over a turn".
104
+ func TestActivityOverATurn(t *testing.T) {
105
+ msgStart := obj{"type": "message_start", "message": obj{"role": "assistant"}}
106
+
107
+ twin.Run(t, "activity", "tracks thinking, tools, and responding in order", func(t *testing.T) {
108
+ r := runActivity(t, []obj{
109
+ pi.agentStart(), msgStart,
110
+ toolStartEvent("tc1", "read", obj{"path": "/ws/a.ts"}),
111
+ {"type": "tool_execution_end", "toolCallId": "tc1", "toolName": "read", "result": obj{}, "isError": false},
112
+ pi.text(0, "done"), pi.settled(),
113
+ })
114
+ equal(t, activities(r.actions), []string{mapper.ThinkingActivity, "Reading a.ts", mapper.ThinkingActivity, mapper.RespondingActivity, "<cleared>"}, "activities")
115
+ })
116
+
117
+ twin.Run(t, "activity", "clears the description when the turn ends", func(t *testing.T) {
118
+ // A stale one would leave the client showing "Reading ..." against an idle chat.
119
+ r := runActivity(t, []obj{pi.agentStart(), toolStartEvent("tc1", "bash", obj{"command": "ls"}), pi.settled()})
120
+ if r.state["activity"] != nil {
121
+ t.Fatalf("activity = %v", r.state["activity"])
122
+ }
123
+ })
124
+
125
+ twin.Run(t, "activity", "clears it on a cancelled turn too", func(t *testing.T) {
126
+ m := mapper.NewTurnMapper("t", 0)
127
+ m.Handle(pi.agentStart())
128
+ equal(t, activities(m.Finish(mapper.OutcomeCancelled, "")), []string{"<cleared>"}, "activities")
129
+ })
130
+
131
+ twin.Run(t, "activity", "does not re-send an unchanged description", func(t *testing.T) {
132
+ // Every delta of one response is still "Responding"; re-sending would burn a serverSeq per token.
133
+ events := []obj{pi.agentStart(), msgStart}
134
+ for _, d := range []string{"a", "b", "c", "d"} {
135
+ events = append(events, pi.text(0, d))
136
+ }
137
+ events = append(events, pi.settled())
138
+ r := runActivity(t, events)
139
+ equal(t, activities(r.actions), []string{mapper.ThinkingActivity, mapper.RespondingActivity, "<cleared>"}, "activities")
140
+ })
141
+
142
+ twin.Run(t, "activity", "emits schema-conforming actions", func(t *testing.T) {
143
+ r := runActivity(t, []obj{pi.agentStart(), toolStartEvent("tc1", "grep", obj{"pattern": "TODO"}), pi.settled()})
144
+ for _, a := range r.actions {
145
+ if actionType(a) == "chat/activityChanged" {
146
+ assertActionsValid(t, []ahptypes.StateAction{a}, "activity")
147
+ }
148
+ }
149
+ })
150
+ }
151
+
152
+ // Twins of upstream test/activity.test.ts, describe "toolInputFor".
153
+ func TestToolInputFor(t *testing.T) {
154
+ input := func(name string, args any) string {
155
+ s, ok := mapper.ToolInputFor(name, args)
156
+ if !ok {
157
+ t.Fatalf("no input for %s", name)
158
+ }
159
+ return s
160
+ }
161
+
162
+ twin.Run(t, "activity", "shows the one argument a call is about", func(t *testing.T) {
163
+ if got := input("bash", obj{"command": "ls -la", "timeout": 5}); got != "ls -la" {
164
+ t.Fatal(got)
165
+ }
166
+ if got := input("read", obj{"path": "a.ts", "offset": 1, "limit": 50}); got != "a.ts" {
167
+ t.Fatal(got)
168
+ }
169
+ })
170
+
171
+ twin.Run(t, "activity", "keeps a search's scope, and drops the defaults models restate", func(t *testing.T) {
172
+ // Every one of these is what a capture actually contained: the model fills in the defaults.
173
+ if got := input("grep", obj{"pattern": "BEACON", "path": ".", "glob": "**/*", "ignoreCase": false, "limit": 100}); got != "BEACON" {
174
+ t.Fatal(got)
175
+ }
176
+ if got := input("grep", obj{"pattern": "TODO", "path": "src", "glob": "*.ts", "ignoreCase": true}); got != "TODO --glob *.ts --ignore-case in src" {
177
+ t.Fatal(got)
178
+ }
179
+ })
180
+
181
+ twin.Run(t, "activity", "falls back to the arguments for a tool it does not know", func(t *testing.T) {
182
+ // An extension's tool has no argument this code can single out; its arguments are still the
183
+ // most informative thing available for it.
184
+ if got := input("some-extension-tool", obj{"a": 1}); got != "{\n \"a\": 1\n}" {
185
+ t.Fatalf("%q", got)
186
+ }
187
+ })
188
+ }
@@ -0,0 +1,38 @@
1
+ package mapper_test
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
7
+ )
8
+
9
+ // One streamed text delta through the turn mapper: this runs for every token batch of a turn while a remote
10
+ // client is attached (the listener is off, and nothing here runs, unless it is configured).
11
+ //
12
+ // go test -run xxx -bench . -benchmem
13
+ func BenchmarkMapTextDelta(b *testing.B) {
14
+ m := mapper.NewTurnMapper("turn-1", 0)
15
+ m.Handle(pi.agentStart())
16
+ m.Handle(pi.assistantStart())
17
+ delta := pi.text(0, "some streamed text from the model ")
18
+ b.ReportAllocs()
19
+ b.ResetTimer()
20
+ for i := 0; i < b.N; i++ {
21
+ m.Handle(delta)
22
+ }
23
+ }
24
+
25
+ func BenchmarkMapToolExecution(b *testing.B) {
26
+ m := mapper.NewTurnMapper("turn-1", 0)
27
+ m.Handle(pi.agentStart())
28
+ m.Handle(pi.assistantStart())
29
+ b.ReportAllocs()
30
+ b.ResetTimer()
31
+ for i := 0; i < b.N; i++ {
32
+ m.Handle(pi.toolStart(1, "call-1", "bash"))
33
+ m.Handle(pi.toolDelta(1, `{"command":"go test ./..."}`))
34
+ m.Handle(pi.toolEnd(1, "call-1", "bash", map[string]any{"command": "go test ./..."}))
35
+ m.Handle(pi.execUpdate("call-1", "bash", "ok \tpkg\t0.1s\n"))
36
+ m.Handle(pi.execEnd("call-1", "bash", "ok \tpkg\t0.1s\n", false))
37
+ }
38
+ }