riffer-rig 0.11.0 → 0.13.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 (313) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +140 -0
  3. data/README.md +102 -39
  4. data/lib/riffer/rig/acp/host.rb +126 -0
  5. data/lib/riffer/rig/acp/mapper.rb +242 -0
  6. data/lib/riffer/rig/acp.rb +356 -0
  7. data/lib/riffer/rig/bundled/bash.rb +5 -1
  8. data/lib/riffer/rig/bundled/mcp.rb +6 -1
  9. data/lib/riffer/rig/cli/flags.rb +75 -22
  10. data/lib/riffer/rig/cli/sessions.rb +147 -0
  11. data/lib/riffer/rig/cli.rb +34 -82
  12. data/lib/riffer/rig/command/context.rb +1 -1
  13. data/lib/riffer/rig/command.rb +4 -2
  14. data/lib/riffer/rig/commands/auth.rb +23 -23
  15. data/lib/riffer/rig/commands/cost.rb +59 -0
  16. data/lib/riffer/rig/commands/model.rb +24 -18
  17. data/lib/riffer/rig/commands/reload.rb +1 -1
  18. data/lib/riffer/rig/commands/skill.rb +1 -1
  19. data/lib/riffer/rig/commands/trace/replay.rb +88 -0
  20. data/lib/riffer/rig/commands/trace.rb +25 -0
  21. data/lib/riffer/rig/credentials/provider_fields.rb +47 -0
  22. data/lib/riffer/rig/credentials.rb +94 -48
  23. data/lib/riffer/rig/env.rb +1 -1
  24. data/lib/riffer/rig/events/after_response.rb +4 -11
  25. data/lib/riffer/rig/events/after_tool_call.rb +15 -13
  26. data/lib/riffer/rig/events/before_prompt.rb +4 -11
  27. data/lib/riffer/rig/events/before_request.rb +4 -11
  28. data/lib/riffer/rig/events/before_tool_call.rb +12 -13
  29. data/lib/riffer/rig/events/command_output.rb +4 -11
  30. data/lib/riffer/rig/events/job_exit.rb +28 -0
  31. data/lib/riffer/rig/events/notify.rb +4 -11
  32. data/lib/riffer/rig/events/session_end.rb +4 -11
  33. data/lib/riffer/rig/events/session_start.rb +4 -11
  34. data/lib/riffer/rig/events/skill_activated.rb +4 -11
  35. data/lib/riffer/rig/events/turn_end.rb +4 -11
  36. data/lib/riffer/rig/extension.rb +5 -4
  37. data/lib/riffer/rig/headless/host.rb +11 -15
  38. data/lib/riffer/rig/headless/ndjson.rb +49 -0
  39. data/lib/riffer/rig/headless.rb +140 -31
  40. data/lib/riffer/rig/hosts/mirror.rb +8 -5
  41. data/lib/riffer/rig/hosts/non_interactive.rb +24 -0
  42. data/lib/riffer/rig/hosts/null.rb +2 -21
  43. data/lib/riffer/rig/loader/credentials.rb +23 -0
  44. data/lib/riffer/rig/loader/extension_source.rb +177 -0
  45. data/lib/riffer/rig/loader/options.rb +35 -0
  46. data/lib/riffer/rig/loader.rb +118 -322
  47. data/lib/riffer/rig/mcp/auth.rb +61 -0
  48. data/lib/riffer/rig/mcp/declaration.rb +29 -5
  49. data/lib/riffer/rig/provider_setup.rb +2 -2
  50. data/lib/riffer/rig/providers.rb +5 -0
  51. data/lib/riffer/rig/registrar.rb +12 -4
  52. data/lib/riffer/rig/runtime/abort.rb +66 -0
  53. data/lib/riffer/rig/runtime/concurrency.rb +71 -0
  54. data/lib/riffer/rig/runtime/error_message.rb +27 -0
  55. data/lib/riffer/rig/runtime/hooks.rb +6 -2
  56. data/lib/riffer/rig/runtime/job_completion.rb +31 -0
  57. data/lib/riffer/rig/runtime/job_envelope.rb +36 -0
  58. data/lib/riffer/rig/runtime/job_inbox.rb +74 -0
  59. data/lib/riffer/rig/runtime/job_notification.rb +31 -0
  60. data/lib/riffer/rig/runtime/mcp_registrar.rb +91 -0
  61. data/lib/riffer/rig/runtime/request_guardrail.rb +1 -1
  62. data/lib/riffer/rig/runtime/snapshot.rb +80 -0
  63. data/lib/riffer/rig/runtime/system_prompt.rb +56 -0
  64. data/lib/riffer/rig/runtime/tool_runtime.rb +47 -7
  65. data/lib/riffer/rig/runtime/usage.rb +19 -0
  66. data/lib/riffer/rig/runtime.rb +333 -213
  67. data/lib/riffer/rig/sdk.rb +29 -7
  68. data/lib/riffer/rig/settings/file.rb +27 -0
  69. data/lib/riffer/rig/settings/model_options.rb +64 -0
  70. data/lib/riffer/rig/settings.rb +0 -129
  71. data/lib/riffer/rig/stores/entries.rb +23 -0
  72. data/lib/riffer/rig/stores/header.rb +3 -17
  73. data/lib/riffer/rig/stores/jsonl.rb +7 -6
  74. data/lib/riffer/rig/stores/message_entry.rb +10 -10
  75. data/lib/riffer/rig/stores/model_entry.rb +10 -10
  76. data/lib/riffer/rig/stores/recorder.rb +34 -13
  77. data/lib/riffer/rig/stores/skill_entry.rb +10 -10
  78. data/lib/riffer/rig/stores/stamped.rb +9 -0
  79. data/lib/riffer/rig/stores/timestamp.rb +23 -0
  80. data/lib/riffer/rig/support/json_file.rb +53 -0
  81. data/lib/riffer/rig/support/typed_value/schema.rb +16 -0
  82. data/lib/riffer/rig/support/typed_value.rb +17 -0
  83. data/lib/riffer/rig/terminal/autocomplete.rb +133 -0
  84. data/lib/riffer/rig/terminal/banner.rb +48 -11
  85. data/lib/riffer/rig/terminal/blocking_prompter.rb +37 -0
  86. data/lib/riffer/rig/terminal/builtin.rb +16 -0
  87. data/lib/riffer/rig/terminal/columns.rb +63 -0
  88. data/lib/riffer/rig/terminal/console.rb +49 -0
  89. data/lib/riffer/rig/terminal/echo.rb +26 -0
  90. data/lib/riffer/rig/terminal/equalizer.rb +20 -0
  91. data/lib/riffer/rig/terminal/frame.rb +339 -0
  92. data/lib/riffer/rig/terminal/frame_prompter.rb +63 -0
  93. data/lib/riffer/rig/terminal/help.rb +41 -0
  94. data/lib/riffer/rig/terminal/host.rb +7 -28
  95. data/lib/riffer/rig/terminal/job_counter.rb +33 -0
  96. data/lib/riffer/rig/terminal/job_exit_notice.rb +11 -0
  97. data/lib/riffer/rig/terminal/key.rb +38 -0
  98. data/lib/riffer/rig/terminal/key_decoder.rb +219 -0
  99. data/lib/riffer/rig/terminal/key_pipeline.rb +111 -0
  100. data/lib/riffer/rig/terminal/line_editor.rb +183 -0
  101. data/lib/riffer/rig/terminal/markdown/blocks.rb +96 -0
  102. data/lib/riffer/rig/terminal/markdown/flow.rb +77 -0
  103. data/lib/riffer/rig/terminal/markdown/inline.rb +85 -0
  104. data/lib/riffer/rig/terminal/markdown/span.rb +25 -0
  105. data/lib/riffer/rig/terminal/markdown/table.rb +166 -0
  106. data/lib/riffer/rig/terminal/markdown.rb +187 -0
  107. data/lib/riffer/rig/terminal/message_queue/message.rb +13 -0
  108. data/lib/riffer/rig/terminal/message_queue.rb +27 -0
  109. data/lib/riffer/rig/terminal/opening.rb +50 -0
  110. data/lib/riffer/rig/terminal/peek.rb +74 -0
  111. data/lib/riffer/rig/terminal/picker.rb +169 -0
  112. data/lib/riffer/rig/terminal/question.rb +37 -0
  113. data/lib/riffer/rig/terminal/quiet_display.rb +48 -0
  114. data/lib/riffer/rig/terminal/renderer.rb +19 -250
  115. data/lib/riffer/rig/terminal/repl.rb +242 -0
  116. data/lib/riffer/rig/terminal/replay/turn.rb +23 -0
  117. data/lib/riffer/rig/terminal/replay.rb +76 -0
  118. data/lib/riffer/rig/terminal/session.rb +29 -0
  119. data/lib/riffer/rig/terminal/tally.rb +12 -0
  120. data/lib/riffer/rig/terminal/theme.rb +34 -1
  121. data/lib/riffer/rig/terminal/ticker.rb +189 -0
  122. data/lib/riffer/rig/terminal/ticker_display.rb +91 -0
  123. data/lib/riffer/rig/terminal/turn_buffer/stats.rb +19 -0
  124. data/lib/riffer/rig/terminal/turn_buffer.rb +133 -0
  125. data/lib/riffer/rig/terminal/turn_report.rb +100 -0
  126. data/lib/riffer/rig/terminal/wrapper.rb +46 -62
  127. data/lib/riffer/rig/terminal.rb +277 -93
  128. data/lib/riffer/rig/tools/bash/capture.rb +58 -0
  129. data/lib/riffer/rig/tools/bash/job.rb +84 -0
  130. data/lib/riffer/rig/tools/bash/job_log.rb +117 -0
  131. data/lib/riffer/rig/tools/bash/jobs.rb +197 -0
  132. data/lib/riffer/rig/tools/bash/kill.rb +24 -0
  133. data/lib/riffer/rig/tools/bash/kill_group.rb +15 -0
  134. data/lib/riffer/rig/tools/bash/monotonic_clock.rb +8 -0
  135. data/lib/riffer/rig/tools/bash/output.rb +30 -0
  136. data/lib/riffer/rig/tools/bash/truncation.rb +12 -0
  137. data/lib/riffer/rig/tools/bash.rb +42 -29
  138. data/lib/riffer/rig/tools/read.rb +2 -2
  139. data/lib/riffer/rig/trust.rb +9 -18
  140. data/lib/riffer/rig/version.rb +1 -1
  141. data/lib/riffer/rig.rb +1 -1
  142. data/sig/generated/riffer/rig/acp/host.rbs +74 -0
  143. data/sig/generated/riffer/rig/acp/mapper.rbs +108 -0
  144. data/sig/generated/riffer/rig/acp.rbs +162 -0
  145. data/sig/generated/riffer/rig/cli/flags.rbs +24 -4
  146. data/sig/generated/riffer/rig/cli/sessions.rbs +86 -0
  147. data/sig/generated/riffer/rig/cli.rbs +7 -32
  148. data/sig/generated/riffer/rig/command.rbs +5 -3
  149. data/sig/generated/riffer/rig/commands/auth.rbs +3 -7
  150. data/sig/generated/riffer/rig/commands/cost.rbs +31 -0
  151. data/sig/generated/riffer/rig/commands/model.rbs +2 -0
  152. data/sig/generated/riffer/rig/commands/trace/replay.rbs +53 -0
  153. data/sig/generated/riffer/rig/commands/trace.rbs +14 -0
  154. data/sig/generated/riffer/rig/credentials/provider_fields.rbs +26 -0
  155. data/sig/generated/riffer/rig/credentials.rbs +36 -15
  156. data/sig/generated/riffer/rig/events/after_response.rbs +3 -7
  157. data/sig/generated/riffer/rig/events/after_tool_call.rbs +14 -9
  158. data/sig/generated/riffer/rig/events/before_prompt.rbs +3 -7
  159. data/sig/generated/riffer/rig/events/before_request.rbs +3 -7
  160. data/sig/generated/riffer/rig/events/before_tool_call.rbs +11 -9
  161. data/sig/generated/riffer/rig/events/command_output.rbs +3 -7
  162. data/sig/generated/riffer/rig/events/job_exit.rbs +24 -0
  163. data/sig/generated/riffer/rig/events/notify.rbs +3 -7
  164. data/sig/generated/riffer/rig/events/session_end.rbs +3 -7
  165. data/sig/generated/riffer/rig/events/session_start.rbs +3 -7
  166. data/sig/generated/riffer/rig/events/skill_activated.rbs +3 -7
  167. data/sig/generated/riffer/rig/events/turn_end.rbs +3 -7
  168. data/sig/generated/riffer/rig/extension.rbs +3 -3
  169. data/sig/generated/riffer/rig/headless/host.rbs +6 -11
  170. data/sig/generated/riffer/rig/headless/ndjson.rbs +27 -0
  171. data/sig/generated/riffer/rig/headless.rbs +49 -9
  172. data/sig/generated/riffer/rig/hosts/mirror.rbs +7 -6
  173. data/sig/generated/riffer/rig/hosts/non_interactive.rbs +18 -0
  174. data/sig/generated/riffer/rig/hosts/null.rbs +2 -15
  175. data/sig/generated/riffer/rig/loader/credentials.rbs +12 -0
  176. data/sig/generated/riffer/rig/loader/extension_source.rbs +89 -0
  177. data/sig/generated/riffer/rig/loader/options.rbs +25 -0
  178. data/sig/generated/riffer/rig/loader.rbs +39 -132
  179. data/sig/generated/riffer/rig/mcp/auth.rbs +32 -0
  180. data/sig/generated/riffer/rig/mcp/declaration.rbs +23 -4
  181. data/sig/generated/riffer/rig/provider_setup.rbs +1 -1
  182. data/sig/generated/riffer/rig/providers.rbs +3 -0
  183. data/sig/generated/riffer/rig/registrar.rbs +6 -2
  184. data/sig/generated/riffer/rig/runtime/abort.rbs +36 -0
  185. data/sig/generated/riffer/rig/runtime/concurrency.rbs +41 -0
  186. data/sig/generated/riffer/rig/runtime/error_message.rbs +13 -0
  187. data/sig/generated/riffer/rig/runtime/hooks.rbs +3 -1
  188. data/sig/generated/riffer/rig/runtime/job_completion.rbs +28 -0
  189. data/sig/generated/riffer/rig/runtime/job_envelope.rbs +17 -0
  190. data/sig/generated/riffer/rig/runtime/job_inbox.rbs +40 -0
  191. data/sig/generated/riffer/rig/runtime/job_notification.rbs +22 -0
  192. data/sig/generated/riffer/rig/runtime/mcp_registrar.rbs +46 -0
  193. data/sig/generated/riffer/rig/runtime/snapshot.rbs +42 -0
  194. data/sig/generated/riffer/rig/runtime/system_prompt.rbs +37 -0
  195. data/sig/generated/riffer/rig/runtime/tool_runtime.rbs +25 -1
  196. data/sig/generated/riffer/rig/runtime/usage.rbs +11 -0
  197. data/sig/generated/riffer/rig/runtime.rbs +131 -80
  198. data/sig/generated/riffer/rig/sdk.rbs +11 -1
  199. data/sig/generated/riffer/rig/settings/file.rbs +19 -0
  200. data/sig/generated/riffer/rig/settings/model_options.rbs +31 -0
  201. data/sig/generated/riffer/rig/settings.rbs +0 -66
  202. data/sig/generated/riffer/rig/stores/entries.rbs +12 -0
  203. data/sig/generated/riffer/rig/stores/header.rbs +2 -5
  204. data/sig/generated/riffer/rig/stores/message_entry.rbs +9 -7
  205. data/sig/generated/riffer/rig/stores/model_entry.rbs +9 -7
  206. data/sig/generated/riffer/rig/stores/recorder.rbs +16 -4
  207. data/sig/generated/riffer/rig/stores/skill_entry.rbs +9 -7
  208. data/sig/generated/riffer/rig/stores/stamped.rbs +7 -0
  209. data/sig/generated/riffer/rig/stores/timestamp.rbs +13 -0
  210. data/sig/generated/riffer/rig/support/json_file.rbs +23 -0
  211. data/sig/generated/riffer/rig/support/typed_value/schema.rbs +14 -0
  212. data/sig/generated/riffer/rig/support/typed_value.rbs +13 -0
  213. data/sig/generated/riffer/rig/terminal/autocomplete.rbs +72 -0
  214. data/sig/generated/riffer/rig/terminal/banner.rbs +25 -4
  215. data/sig/generated/riffer/rig/terminal/blocking_prompter.rbs +23 -0
  216. data/sig/generated/riffer/rig/terminal/builtin.rbs +13 -0
  217. data/sig/generated/riffer/rig/terminal/columns.rbs +43 -0
  218. data/sig/generated/riffer/rig/terminal/console.rbs +32 -0
  219. data/sig/generated/riffer/rig/terminal/echo.rbs +15 -0
  220. data/sig/generated/riffer/rig/terminal/equalizer.rbs +12 -0
  221. data/sig/generated/riffer/rig/terminal/frame.rbs +169 -0
  222. data/sig/generated/riffer/rig/terminal/frame_prompter.rbs +34 -0
  223. data/sig/generated/riffer/rig/terminal/help.rbs +22 -0
  224. data/sig/generated/riffer/rig/terminal/host.rbs +5 -11
  225. data/sig/generated/riffer/rig/terminal/job_counter.rbs +21 -0
  226. data/sig/generated/riffer/rig/terminal/job_exit_notice.rbs +7 -0
  227. data/sig/generated/riffer/rig/terminal/key.rbs +27 -0
  228. data/sig/generated/riffer/rig/terminal/key_decoder.rbs +81 -0
  229. data/sig/generated/riffer/rig/terminal/key_pipeline.rbs +54 -0
  230. data/sig/generated/riffer/rig/terminal/line_editor.rbs +108 -0
  231. data/sig/generated/riffer/rig/terminal/markdown/blocks.rbs +57 -0
  232. data/sig/generated/riffer/rig/terminal/markdown/flow.rbs +42 -0
  233. data/sig/generated/riffer/rig/terminal/markdown/inline.rbs +41 -0
  234. data/sig/generated/riffer/rig/terminal/markdown/span.rbs +20 -0
  235. data/sig/generated/riffer/rig/terminal/markdown/table.rbs +101 -0
  236. data/sig/generated/riffer/rig/terminal/markdown.rbs +112 -0
  237. data/sig/generated/riffer/rig/terminal/message_queue/message.rbs +10 -0
  238. data/sig/generated/riffer/rig/terminal/message_queue.rbs +17 -0
  239. data/sig/generated/riffer/rig/terminal/opening.rbs +33 -0
  240. data/sig/generated/riffer/rig/terminal/peek.rbs +37 -0
  241. data/sig/generated/riffer/rig/terminal/picker.rbs +82 -0
  242. data/sig/generated/riffer/rig/terminal/question.rbs +21 -0
  243. data/sig/generated/riffer/rig/terminal/quiet_display.rbs +33 -0
  244. data/sig/generated/riffer/rig/terminal/renderer.rbs +10 -122
  245. data/sig/generated/riffer/rig/terminal/repl.rbs +116 -0
  246. data/sig/generated/riffer/rig/terminal/replay/turn.rbs +20 -0
  247. data/sig/generated/riffer/rig/terminal/replay.rbs +29 -0
  248. data/sig/generated/riffer/rig/terminal/session.rbs +27 -0
  249. data/sig/generated/riffer/rig/terminal/tally.rbs +7 -0
  250. data/sig/generated/riffer/rig/terminal/theme.rbs +23 -2
  251. data/sig/generated/riffer/rig/terminal/ticker.rbs +107 -0
  252. data/sig/generated/riffer/rig/terminal/ticker_display.rbs +48 -0
  253. data/sig/generated/riffer/rig/terminal/turn_buffer/stats.rbs +16 -0
  254. data/sig/generated/riffer/rig/terminal/turn_buffer.rbs +67 -0
  255. data/sig/generated/riffer/rig/terminal/turn_report.rbs +52 -0
  256. data/sig/generated/riffer/rig/terminal/wrapper.rbs +16 -14
  257. data/sig/generated/riffer/rig/terminal.rbs +101 -28
  258. data/sig/generated/riffer/rig/tools/bash/capture.rbs +39 -0
  259. data/sig/generated/riffer/rig/tools/bash/job.rbs +65 -0
  260. data/sig/generated/riffer/rig/tools/bash/job_log.rbs +60 -0
  261. data/sig/generated/riffer/rig/tools/bash/jobs.rbs +86 -0
  262. data/sig/generated/riffer/rig/tools/bash/kill.rbs +8 -0
  263. data/sig/generated/riffer/rig/tools/bash/kill_group.rbs +7 -0
  264. data/sig/generated/riffer/rig/tools/bash/monotonic_clock.rbs +6 -0
  265. data/sig/generated/riffer/rig/tools/bash/output.rbs +10 -0
  266. data/sig/generated/riffer/rig/tools/bash/truncation.rbs +7 -0
  267. data/sig/generated/riffer/rig/tools/bash.rbs +15 -13
  268. data/sig/generated/riffer/rig/trust.rbs +7 -8
  269. data/sig/manual/riffer/rig/acp/mapper.rbs +6 -0
  270. data/sig/manual/riffer/rig/commands/cost.rbs +6 -0
  271. data/sig/manual/riffer/rig/commands/trace/replay.rbs +6 -0
  272. data/sig/manual/riffer/rig/commands/trace.rbs +6 -0
  273. data/sig/manual/riffer/rig/credentials/provider_fields.rbs +6 -0
  274. data/sig/manual/riffer/rig/events.rbs +1 -0
  275. data/sig/manual/riffer/rig/headless/ndjson.rbs +6 -0
  276. data/sig/manual/riffer/rig/headless.rbs +7 -0
  277. data/sig/manual/riffer/rig/hosts.rbs +1 -1
  278. data/sig/manual/riffer/rig/loader/credentials.rbs +6 -0
  279. data/sig/manual/riffer/rig/mcp/auth.rbs +6 -0
  280. data/sig/manual/riffer/rig/runtime/error_message.rbs +6 -0
  281. data/sig/manual/riffer/rig/runtime/job_envelope.rbs +6 -0
  282. data/sig/manual/riffer/rig/runtime/usage.rbs +6 -0
  283. data/sig/manual/riffer/rig/sdk.rbs +1 -1
  284. data/sig/manual/riffer/rig/settings/file.rbs +6 -0
  285. data/sig/manual/riffer/rig/settings/model_options.rbs +6 -0
  286. data/sig/manual/riffer/rig/stores/entries.rbs +6 -0
  287. data/sig/manual/riffer/rig/stores/timestamp.rbs +6 -0
  288. data/sig/manual/riffer/rig/stores.rbs +8 -0
  289. data/sig/manual/riffer/rig/support/json_file.rbs +6 -0
  290. data/sig/manual/riffer/rig/support.rbs +10 -0
  291. data/sig/manual/riffer/rig/terminal/autocomplete.rbs +8 -0
  292. data/sig/manual/riffer/rig/terminal/columns.rbs +6 -0
  293. data/sig/manual/riffer/rig/terminal/echo.rbs +6 -0
  294. data/sig/manual/riffer/rig/terminal/equalizer.rbs +6 -0
  295. data/sig/manual/riffer/rig/terminal/help.rbs +6 -0
  296. data/sig/manual/riffer/rig/terminal/job_exit_notice.rbs +6 -0
  297. data/sig/manual/riffer/rig/terminal/markdown/blocks.rbs +6 -0
  298. data/sig/manual/riffer/rig/terminal/markdown/flow.rbs +6 -0
  299. data/sig/manual/riffer/rig/terminal/markdown/inline.rbs +6 -0
  300. data/sig/manual/riffer/rig/terminal/markdown/table.rbs +6 -0
  301. data/sig/manual/riffer/rig/terminal/markdown.rbs +6 -0
  302. data/sig/manual/riffer/rig/terminal/question.rbs +6 -0
  303. data/sig/manual/riffer/rig/terminal/replay.rbs +14 -0
  304. data/sig/manual/riffer/rig/terminal/tally.rbs +6 -0
  305. data/sig/manual/riffer/rig/terminal/turn_report.rbs +14 -0
  306. data/sig/manual/riffer/rig/terminal.rbs +37 -0
  307. metadata +214 -9
  308. data/lib/riffer/rig/terminal/animator.rb +0 -139
  309. data/lib/riffer/rig/terminal/cursor.rb +0 -44
  310. data/lib/riffer/rig/terminal/smoother.rb +0 -126
  311. data/sig/generated/riffer/rig/terminal/animator.rbs +0 -68
  312. data/sig/generated/riffer/rig/terminal/cursor.rbs +0 -31
  313. data/sig/generated/riffer/rig/terminal/smoother.rbs +0 -59
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::ACP::Mapper
4
+ extend self
5
+
6
+ TOOL_KINDS = {
7
+ 'read' => ::ACP::Types::ToolKind::READ,
8
+ 'write' => ::ACP::Types::ToolKind::EDIT,
9
+ 'edit' => ::ACP::Types::ToolKind::EDIT,
10
+ 'bash' => ::ACP::Types::ToolKind::EXECUTE
11
+ }.freeze #: Hash[String, String]
12
+
13
+ CONFIRMED = 'yes' #: String
14
+ CONFIRM_OPTIONS = [
15
+ ::ACP::Types::PermissionOption.new(
16
+ option_id: CONFIRMED, name: 'Yes', kind: ::ACP::Types::PermissionOptionKind::ALLOW_ONCE
17
+ ),
18
+ ::ACP::Types::PermissionOption.new(option_id: 'no', name: 'No', kind: ::ACP::Types::PermissionOptionKind::REJECT_ONCE)
19
+ ].freeze #: Array[::ACP::Types::PermissionOption]
20
+ ANSWER_FIELD = 'answer' #: String
21
+
22
+ # Text and resource links are ACP's must-support prompt blocks; the client
23
+ # cannot send the others unless the agent advertised them.
24
+ # @rbs blocks: Array[::ACP::Types::ContentBlock::t]
25
+ # @rbs return: String
26
+ def prompt_text(blocks)
27
+ blocks.filter_map { |block| prompt_text_of(block) }.join("\n\n")
28
+ end
29
+
30
+ # @rbs content: String
31
+ # @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
32
+ def message_chunk(content)
33
+ ::ACP::Types::SessionUpdate::AgentMessageChunk.new(content: text_block(content))
34
+ end
35
+
36
+ # @rbs text: String
37
+ # @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
38
+ def notice(text)
39
+ # ACP has no update for a notice, and without blank lines around it a
40
+ # notice runs into the reply and into the next notice.
41
+ message_chunk("\n\n#{text}\n\n")
42
+ end
43
+
44
+ # @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
45
+ def job_exit
46
+ # ACP has no update for a job exit notice, and without a break the replies
47
+ # either side of one read as a single message.
48
+ message_chunk("\n\n")
49
+ end
50
+
51
+ # @rbs completion: Riffer::Rig::Runtime::JobCompletion
52
+ # @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
53
+ def job_completion(completion)
54
+ notice("Background job #{completion.job_id} #{ending(completion)}: #{completion.command}")
55
+ end
56
+
57
+ # @rbs message: Hash[Symbol, untyped]
58
+ # @rbs return: ::ACP::Types::SessionUpdate::t?
59
+ def replay_of(message)
60
+ return job_exit if message[Riffer::Rig::Runtime::JobNotification::MARKER]
61
+
62
+ case message[:role]
63
+ when 'user' then ::ACP::Types::SessionUpdate::UserMessageChunk.new(content: text_block(message[:content].to_s))
64
+ when 'assistant' then message_chunk(message[:content].to_s) unless message[:content].to_s.strip.empty?
65
+ when 'tool' then replayed_tool_call(message)
66
+ end
67
+ end
68
+
69
+ # @rbs event: Riffer::StreamEvents::ToolCallDone
70
+ # @rbs return: ::ACP::Types::SessionUpdate::ToolCall
71
+ def tool_call(event)
72
+ ::ACP::Types::SessionUpdate::ToolCall.new(
73
+ tool_call_id: event.call_id,
74
+ title: "#{event.name}(#{event.arguments})",
75
+ name: event.name,
76
+ kind: TOOL_KINDS.fetch(event.name, ::ACP::Types::ToolKind::OTHER),
77
+ status: ::ACP::Types::ToolCallStatus::IN_PROGRESS
78
+ )
79
+ end
80
+
81
+ # @rbs event: Riffer::Rig::Events::AfterToolCall
82
+ # @rbs return: ::ACP::Types::SessionUpdate::ToolCallUpdate
83
+ def tool_call_done(event)
84
+ result = event.result
85
+ ::ACP::Types::SessionUpdate::ToolCallUpdate.new(
86
+ tool_call_id: event.call_id,
87
+ status: result.error? ? ::ACP::Types::ToolCallStatus::FAILED : ::ACP::Types::ToolCallStatus::COMPLETED,
88
+ content: tool_content(result.content)
89
+ )
90
+ end
91
+
92
+ # riffer's failure reasons (:error, :other, :guardrail_blocked) have no ACP
93
+ # counterpart; refusal is the closest signal that the turn did not complete.
94
+ # @rbs stop_reason: Symbol
95
+ # @rbs return: String
96
+ def stop_reason_of(stop_reason)
97
+ case stop_reason
98
+ when :completed then ::ACP::Types::StopReason::END_TURN
99
+ when Riffer::Rig::Runtime::INTERRUPT_CANCELLED then ::ACP::Types::StopReason::CANCELLED
100
+ when :max_steps then ::ACP::Types::StopReason::MAX_TURN_REQUESTS
101
+ else
102
+ stopped = Riffer::Rig::Runtime::STOPPED_WITHOUT_COMPLETING.include?(stop_reason)
103
+ stopped ? ::ACP::Types::StopReason::MAX_TOKENS : ::ACP::Types::StopReason::REFUSAL
104
+ end
105
+ end
106
+
107
+ # @rbs options: Array[String]
108
+ # @rbs return: Array[::ACP::Types::PermissionOption]
109
+ def choices(options)
110
+ options.map do |option|
111
+ ::ACP::Types::PermissionOption.new(
112
+ option_id: option, name: option, kind: ::ACP::Types::PermissionOptionKind::ALLOW_ONCE
113
+ )
114
+ end
115
+ end
116
+
117
+ # @rbs tool_call_id: String
118
+ # @rbs question: String
119
+ # @rbs return: ::ACP::Types::SessionUpdate::ToolCall
120
+ def permission_call(tool_call_id, question)
121
+ ::ACP::Types::SessionUpdate::ToolCall.new(
122
+ tool_call_id: tool_call_id,
123
+ title: question,
124
+ kind: ::ACP::Types::ToolKind::OTHER,
125
+ status: ::ACP::Types::ToolCallStatus::PENDING
126
+ )
127
+ end
128
+
129
+ # @rbs session_id: String
130
+ # @rbs tool_call_id: String
131
+ # @rbs question: String
132
+ # @rbs options: Array[::ACP::Types::PermissionOption]
133
+ # @rbs return: ::ACP::Types::RequestPermissionRequest
134
+ def permission_request(session_id, tool_call_id, question, options)
135
+ ::ACP::Types::RequestPermissionRequest.new(
136
+ session_id: session_id,
137
+ tool_call: ::ACP::Types::ToolCallUpdate.new(
138
+ tool_call_id: tool_call_id,
139
+ title: question,
140
+ kind: ::ACP::Types::ToolKind::OTHER,
141
+ status: ::ACP::Types::ToolCallStatus::PENDING
142
+ ),
143
+ options: options
144
+ )
145
+ end
146
+
147
+ # @rbs response: ::ACP::Types::RequestPermissionResponse | ::ACP::RequestError
148
+ # @rbs return: String?
149
+ def selected_option(response)
150
+ return nil unless response.is_a?(::ACP::Types::RequestPermissionResponse)
151
+
152
+ outcome = response.outcome
153
+ outcome.option_id if outcome.is_a?(::ACP::Types::RequestPermissionOutcome::Selected)
154
+ end
155
+
156
+ # @rbs tool_call_id: String
157
+ # @rbs selected: String?
158
+ # @rbs return: ::ACP::Types::SessionUpdate::ToolCallUpdate
159
+ def permission_done(tool_call_id, selected)
160
+ ::ACP::Types::SessionUpdate::ToolCallUpdate.new(
161
+ tool_call_id: tool_call_id,
162
+ status: selected ? ::ACP::Types::ToolCallStatus::COMPLETED : ::ACP::Types::ToolCallStatus::FAILED
163
+ )
164
+ end
165
+
166
+ # @rbs session_id: String
167
+ # @rbs question: String
168
+ # @rbs return: ::ACP::Types::CreateElicitationRequest
169
+ def elicitation_request(session_id, question)
170
+ ::ACP::Types::CreateElicitationRequest.new(
171
+ message: question,
172
+ mode: ::ACP::Types::CreateElicitationRequest::Mode::Form.new(
173
+ requested_schema: ::ACP::Types::ElicitationSchema.new(
174
+ type: ::ACP::Types::ElicitationSchemaType::OBJECT,
175
+ properties: {
176
+ ANSWER_FIELD => ::ACP::Types::ElicitationPropertySchema::StringPropertySchema.new(title: question)
177
+ },
178
+ required: [ANSWER_FIELD]
179
+ ),
180
+ scope: ::ACP::Types::ElicitationSessionScope.new(session_id: session_id)
181
+ )
182
+ )
183
+ end
184
+
185
+ # @rbs response: ::ACP::Types::CreateElicitationResponse | ::ACP::RequestError
186
+ # @rbs return: String?
187
+ def elicited_answer(response)
188
+ return nil unless response.is_a?(::ACP::Types::CreateElicitationResponse)
189
+
190
+ action = response.action
191
+ return nil unless action.is_a?(::ACP::Types::CreateElicitationResponse::Action::Accept)
192
+
193
+ answer = action.content&.fetch(ANSWER_FIELD, nil)
194
+ answer if answer.is_a?(String)
195
+ end
196
+
197
+ private
198
+
199
+ # @rbs block: ::ACP::Types::ContentBlock::t
200
+ # @rbs return: String?
201
+ def prompt_text_of(block)
202
+ case block
203
+ when ::ACP::Types::ContentBlock::Text then block.text
204
+ when ::ACP::Types::ContentBlock::ResourceLink then "#{block.title || block.name} #{block.uri}"
205
+ end
206
+ end
207
+
208
+ # @rbs text: String
209
+ # @rbs return: ::ACP::Types::ContentBlock::Text
210
+ def text_block(text)
211
+ ::ACP::Types::ContentBlock::Text.new(text: text)
212
+ end
213
+
214
+ # @rbs completion: Riffer::Rig::Runtime::JobCompletion
215
+ # @rbs return: String
216
+ def ending(completion)
217
+ exit_status = completion.exit_status
218
+ exit_status ? "exited with status #{exit_status}" : "was killed by SIG#{completion.signal}"
219
+ end
220
+
221
+ # The stored result is all a replayed call has: the arguments live in the
222
+ # assistant message's tool_calls, so the title is the name alone.
223
+ # @rbs message: Hash[Symbol, untyped]
224
+ # @rbs return: ::ACP::Types::SessionUpdate::ToolCall
225
+ def replayed_tool_call(message)
226
+ name = message[:name].to_s
227
+ ::ACP::Types::SessionUpdate::ToolCall.new(
228
+ tool_call_id: message[:tool_call_id].to_s,
229
+ title: name,
230
+ name: name,
231
+ kind: TOOL_KINDS.fetch(name, ::ACP::Types::ToolKind::OTHER),
232
+ status: ::ACP::Types::ToolCallStatus::COMPLETED,
233
+ content: tool_content(message[:content].to_s)
234
+ )
235
+ end
236
+
237
+ # @rbs text: String
238
+ # @rbs return: Array[::ACP::Types::ToolCallContent::t]
239
+ def tool_content(text)
240
+ [::ACP::Types::ToolCallContent::Content.new(content: text_block(text))]
241
+ end
242
+ end
@@ -0,0 +1,356 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'acp/sdk'
4
+ require 'time'
5
+
6
+ # The ACP tier: Runtime events reach the client only through this adapter's
7
+ # mapping, so nothing else in rig may translate an event into an ACP type.
8
+ class Riffer::Rig::ACP
9
+ AGENT_NAME = 'riffer' #: String
10
+ AGENT_TITLE = 'Riffer' #: String
11
+
12
+ SESSION_EXTENSION = 'acp' #: String
13
+
14
+ # @rbs @input: IO
15
+ # @rbs @output: IO
16
+ # @rbs @env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
17
+ # @rbs @home: String
18
+ # @rbs @cwd: String
19
+ # @rbs @build: ^(String, Riffer::Rig::Hosts::_Host, Hash[String, Riffer::Rig::Mcp::Declaration]) -> Riffer::Rig::Runtime
20
+ # @rbs @adopt: (^(String) -> Riffer::Rig::Runtime?)?
21
+ # @rbs @client: ::ACP::AgentConnection::Client?
22
+ # @rbs @sessions: Hash[String, Riffer::Rig::Runtime]
23
+ # @rbs @hosts: Hash[String, Riffer::Rig::ACP::Host]
24
+ # @rbs @owned: Array[Riffer::Rig::Runtime]
25
+ # @rbs @lock: Thread::Mutex
26
+
27
+ # @rbs input: IO
28
+ # @rbs output: IO
29
+ # @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
30
+ # @rbs home: String
31
+ # @rbs cwd: String
32
+ # @rbs build: (^(String, Riffer::Rig::Hosts::_Host, Hash[String, Riffer::Rig::Mcp::Declaration]) -> Riffer::Rig::Runtime)?
33
+ # @rbs adopt: (^(String) -> Riffer::Rig::Runtime?)?
34
+ # @rbs return: Riffer::Rig::ACP
35
+ def self.for(input:, output:, env:, home:, cwd:, build: nil, adopt: nil)
36
+ new(input: input, output: output, env: env, home: home, cwd: cwd, build: build, adopt: adopt)
37
+ end
38
+
39
+ # Builds the Runtime a session runs on: the client's MCP servers flow
40
+ # through the rig.mcp seam as a synthetic extension, so a reload rebuild
41
+ # re-declares them and a same-name declaration from an extension replaces
42
+ # them with the usual notify.
43
+ # @rbs cwd: String
44
+ # @rbs host: Riffer::Rig::Hosts::_Host
45
+ # @rbs servers: Hash[String, Riffer::Rig::Mcp::Declaration]
46
+ # @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
47
+ # @rbs home: String
48
+ # @rbs return: Riffer::Rig::Runtime
49
+ def self.build_runtime(cwd, host, servers, env:, home:)
50
+ Riffer::Rig::Loader.runtime(
51
+ cwd: cwd, host: host, env: env, home: home, extra_extensions: session_extensions(servers)
52
+ )
53
+ end
54
+
55
+ # @rbs servers: Hash[String, Riffer::Rig::Mcp::Declaration]
56
+ # @rbs return: Array[Riffer::Rig::Extension]
57
+ def self.session_extensions(servers)
58
+ return [] if servers.empty?
59
+
60
+ extension = Riffer::Rig::Extension.new(SESSION_EXTENSION) do |rig|
61
+ servers.each { |name, server| rig.mcp(name, command: server.command, args: server.args, env: server.env) }
62
+ end
63
+ [extension]
64
+ end
65
+
66
+ # @rbs input: IO
67
+ # @rbs output: IO
68
+ # @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
69
+ # @rbs home: String
70
+ # @rbs cwd: String
71
+ # @rbs build: (^(String, Riffer::Rig::Hosts::_Host, Hash[String, Riffer::Rig::Mcp::Declaration]) -> Riffer::Rig::Runtime)?
72
+ # @rbs adopt: (^(String) -> Riffer::Rig::Runtime?)?
73
+ # @rbs return: void
74
+ def initialize(input:, output:, env:, home:, cwd:, build: nil, adopt: nil)
75
+ @input = input
76
+ @output = output
77
+ @env = env
78
+ @home = home
79
+ @cwd = cwd
80
+ @build = build || lambda do |cwd, host, servers|
81
+ own(self.class.build_runtime(cwd, host, servers, env: @env, home: @home))
82
+ end
83
+ @adopt = adopt
84
+ @client = nil
85
+ @sessions = {}
86
+ @hosts = {}
87
+ @owned = []
88
+ @lock = Mutex.new
89
+ end
90
+
91
+ # Serves the connection until the client closes stdin; then the reader
92
+ # thread ends and run returns.
93
+ # @rbs &block: ? (::ACP::AgentConnection) -> void
94
+ # @rbs return: Integer
95
+ def run
96
+ connection = ::ACP::AgentConnection.new(
97
+ transport: ::ACP::Transport::Stdio.new(input: @input, output: @output),
98
+ capabilities: ::ACP::Types::AgentCapabilities.new(
99
+ load_session: true,
100
+ session_capabilities: ::ACP::Types::SessionCapabilities.new(list: ::ACP::Types::SessionListCapabilities.new)
101
+ ),
102
+ agent_info: ::ACP::Types::Implementation.new(name: AGENT_NAME, version: Riffer::Rig::VERSION, title: AGENT_TITLE)
103
+ ) do |client|
104
+ @client = client
105
+ self
106
+ end
107
+ connection.start.join
108
+ yield connection if block_given?
109
+ 0
110
+ ensure
111
+ # Background jobs run in process groups of their own, so only closing
112
+ # their Runtime stops them outliving this process. A turn still running is
113
+ # not waited for, so it is cancelled first to stop it starting another.
114
+ @lock.synchronize { @owned.dup }.each do |runtime|
115
+ runtime.cancel
116
+ runtime.close
117
+ end
118
+ end
119
+
120
+ # @rbs request: ::ACP::Types::NewSessionRequest
121
+ # @rbs return: (::ACP::Types::NewSessionResponse | ::ACP::RequestError)
122
+ def new_session(request)
123
+ host = Riffer::Rig::ACP::Host.new(client: client)
124
+ servers = declarations(request.mcp_servers, host)
125
+ runtime = announcing_job_exits(@build.call(request.cwd, host, servers), host)
126
+ @lock.synchronize do
127
+ @sessions[runtime.id] = runtime
128
+ @hosts[runtime.id] = host
129
+ end
130
+ ::ACP::Types::NewSessionResponse.new(session_id: runtime.id)
131
+ rescue StandardError => e
132
+ ::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
133
+ end
134
+
135
+ # @rbs request: ::ACP::Types::PromptRequest
136
+ # @rbs return: (::ACP::Types::PromptResponse | ::ACP::RequestError)
137
+ def prompt(request)
138
+ runtime = session(request.session_id)
139
+ return ::ACP::RequestError.resource_not_found unless runtime
140
+
141
+ commands = available_commands(runtime).to_set(&:to_h)
142
+ # A command that runs no turn sends no turn_end.
143
+ stop_reason = :completed
144
+ dispatch(runtime, Riffer::Rig::ACP::Mapper.prompt_text(request.prompt)) do |event|
145
+ case event
146
+ when Riffer::StreamEvents::TextDelta
147
+ client.update(request.session_id, Riffer::Rig::ACP::Mapper.message_chunk(event.content))
148
+ when Riffer::Rig::Events::CommandOutput
149
+ client.update(request.session_id, Riffer::Rig::ACP::Mapper.message_chunk(event.text))
150
+ when Riffer::StreamEvents::ToolCallDone
151
+ client.update(request.session_id, Riffer::Rig::ACP::Mapper.tool_call(event))
152
+ when Riffer::Rig::Events::AfterToolCall
153
+ client.update(request.session_id, Riffer::Rig::ACP::Mapper.tool_call_done(event))
154
+ when Riffer::Rig::Events::JobExit
155
+ client.update(request.session_id, Riffer::Rig::ACP::Mapper.job_exit)
156
+ when Riffer::Rig::Events::TurnEnd
157
+ stop_reason = event.stop_reason
158
+ end
159
+ end
160
+ # A reload in the dispatch, by /reload or the automatic check, can change
161
+ # the commands; its rebuild also reorders them, which is no change.
162
+ reloaded = available_commands(runtime)
163
+ client.available_commands(request.session_id, reloaded) unless reloaded.to_set(&:to_h) == commands
164
+ ::ACP::Types::PromptResponse.new(stop_reason: Riffer::Rig::ACP::Mapper.stop_reason_of(stop_reason))
165
+ rescue Riffer::Rig::Runtime::BusyError
166
+ ::ACP::RequestError.new(
167
+ code: ::ACP::RequestError::INVALID_REQUEST,
168
+ message: 'a prompt is already running on this session; wait for it to finish or cancel it'
169
+ )
170
+ rescue StandardError => e
171
+ ::ACP::RequestError.new(
172
+ code: ::ACP::RequestError::INTERNAL_ERROR, message: Riffer::Rig::Runtime::ErrorMessage.of(e)
173
+ )
174
+ end
175
+
176
+ # @rbs request: ::ACP::Types::LoadSessionRequest
177
+ # @rbs return: (::ACP::Types::LoadSessionResponse | ::ACP::RequestError)
178
+ def load_session(request)
179
+ host = Riffer::Rig::ACP::Host.new(client: client)
180
+ loader = Riffer::Rig::Loader.new(cwd: request.cwd, host: host, env: @env, home: @home)
181
+ runtime = adopted(request.session_id) || resumed(loader, request, host)
182
+ return ::ACP::RequestError.resource_not_found unless runtime
183
+
184
+ # A client need not answer a question while its own session/load is
185
+ # pending, and acp-sdk never times a request out, so the host takes the
186
+ # session id only once the build can no longer ask one.
187
+ host.session_id = request.session_id
188
+ @lock.synchronize { @sessions[request.session_id] = runtime }
189
+ loader.messages(request.session_id)
190
+ .filter_map { |message| Riffer::Rig::ACP::Mapper.replay_of(message) }
191
+ .each { |update| client.update(request.session_id, update) }
192
+ client.available_commands(request.session_id, available_commands(runtime))
193
+ ::ACP::Types::LoadSessionResponse.new
194
+ rescue StandardError => e
195
+ ::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
196
+ end
197
+
198
+ # @rbs request: ::ACP::Types::ListSessionsRequest
199
+ # @rbs return: (::ACP::Types::ListSessionsResponse | ::ACP::RequestError)
200
+ def list_sessions(request)
201
+ loader = Riffer::Rig::Loader.new(
202
+ cwd: request.cwd || @cwd,
203
+ host: Riffer::Rig::Hosts::Null.new,
204
+ env: @env,
205
+ home: @home
206
+ )
207
+ headers = request.cwd ? loader.list : loader.list(all: true)
208
+ sessions = headers.map do |header|
209
+ ::ACP::Types::SessionInfo.new(
210
+ session_id: header.id,
211
+ cwd: header.cwd,
212
+ title: header.title,
213
+ updated_at: header.updated&.utc&.iso8601
214
+ )
215
+ end
216
+ ::ACP::Types::ListSessionsResponse.new(sessions: sessions)
217
+ rescue StandardError => e
218
+ ::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
219
+ end
220
+
221
+ # Runs on the transport's reader thread, so it asks the turn to stop and
222
+ # never waits for it.
223
+ # @rbs notification: ::ACP::Types::CancelNotification
224
+ # @rbs return: void
225
+ def cancel(notification)
226
+ session(notification.session_id)&.cancel
227
+ end
228
+
229
+ # Runs after the session/new reply, so the client knows the id before any
230
+ # update for it arrives: the build-time notifies flush here, then the
231
+ # commands go out.
232
+ # @rbs response: ::ACP::Types::NewSessionResponse
233
+ # @rbs return: void
234
+ def session_created(response)
235
+ id = response.session_id
236
+ # @type var found: [Riffer::Rig::ACP::Host?, Riffer::Rig::Runtime?]
237
+ found = @lock.synchronize { [@hosts[id], @sessions[id]] }
238
+ host, runtime = found
239
+ return unless host && runtime
240
+
241
+ host.session_id = id
242
+ client.available_commands(id, available_commands(runtime))
243
+ end
244
+
245
+ private
246
+
247
+ # @rbs return: ::ACP::AgentConnection::Client
248
+ def client
249
+ @client or raise 'ACP agent used before ::ACP::AgentConnection.start built it'
250
+ end
251
+
252
+ # @rbs id: String
253
+ # @rbs return: Riffer::Rig::Runtime?
254
+ def session(id)
255
+ @lock.synchronize { @sessions[id] }
256
+ end
257
+
258
+ # @rbs runtime: Riffer::Rig::Runtime
259
+ # @rbs text: String
260
+ # @rbs &: (::Riffer::StreamEvents::Base | Riffer::Rig::Events::_Event) -> void
261
+ # @rbs return: void
262
+ def dispatch(runtime, text, &)
263
+ command = Riffer::Rig::Command::INVOCATION.match(text)
264
+ command ? runtime.run_command(command[1].to_s, command[2].to_s, &) : runtime.prompt(text, &)
265
+ end
266
+
267
+ # @rbs id: String
268
+ # @rbs return: Riffer::Rig::Runtime?
269
+ def adopted(id)
270
+ runtime = @adopt&.call(id)
271
+ # The Loader that built the Runtime holds this same host, so it is
272
+ # re-pointed in place rather than replaced.
273
+ case (host = runtime&.host&.host)
274
+ when Riffer::Rig::ACP::Host then host.client = client
275
+ end
276
+ runtime
277
+ end
278
+
279
+ # @rbs loader: Riffer::Rig::Loader
280
+ # @rbs request: ::ACP::Types::LoadSessionRequest
281
+ # @rbs host: Riffer::Rig::ACP::Host
282
+ # @rbs return: Riffer::Rig::Runtime?
283
+ def resumed(loader, request, host)
284
+ runtime = loader.resume(
285
+ request.session_id,
286
+ extra_extensions: self.class.session_extensions(declarations(request.mcp_servers, host))
287
+ )
288
+ runtime && own(announcing_job_exits(runtime, host))
289
+ end
290
+
291
+ # @rbs runtime: Riffer::Rig::Runtime
292
+ # @rbs return: Riffer::Rig::Runtime
293
+ def own(runtime)
294
+ @lock.synchronize { @owned << runtime }
295
+ runtime
296
+ end
297
+
298
+ # @rbs runtime: Riffer::Rig::Runtime
299
+ # @rbs host: Riffer::Rig::ACP::Host
300
+ # @rbs return: Riffer::Rig::Runtime
301
+ def announcing_job_exits(runtime, host)
302
+ # A prompt in flight marks the exit on its own stream when it tells the
303
+ # model. One starting meanwhile collects the exit from the inbox whose
304
+ # lock this subscriber holds, so it cannot stream ahead of this update.
305
+ runtime.on_job_exit do |completion|
306
+ host.update(Riffer::Rig::ACP::Mapper.job_completion(completion)) unless runtime.busy?
307
+ end
308
+ runtime
309
+ end
310
+
311
+ # @rbs servers: Array[::ACP::Types::McpServer::t]
312
+ # @rbs host: Riffer::Rig::ACP::Host
313
+ # @rbs return: Hash[String, Riffer::Rig::Mcp::Declaration]
314
+ def declarations(servers, host)
315
+ servers.filter_map { |server| declaration_of(server, host) }.to_h
316
+ end
317
+
318
+ # @rbs server: ::ACP::Types::McpServer::t
319
+ # @rbs host: Riffer::Rig::ACP::Host
320
+ # @rbs return: [String, Riffer::Rig::Mcp::Declaration]?
321
+ def declaration_of(server, host)
322
+ unless server.is_a?(::ACP::Types::McpServerStdio)
323
+ host.notify("Skipped MCP server #{server_name(server)}: only stdio servers are supported", level: :warning)
324
+ return nil
325
+ end
326
+
327
+ [
328
+ server.name,
329
+ Riffer::Rig::Mcp::Declaration.new(command: server.command, args: server.args, env: stdio_env(server))
330
+ ]
331
+ end
332
+
333
+ # @rbs server: ::ACP::Types::McpServer::t
334
+ # @rbs return: String
335
+ def server_name(server)
336
+ case server
337
+ when ::ACP::Types::McpServer::Http, ::ACP::Types::McpServer::Sse then server.name
338
+ when Hash then server['name'].to_s
339
+ else 'unnamed'
340
+ end
341
+ end
342
+
343
+ # @rbs server: ::ACP::Types::McpServerStdio
344
+ # @rbs return: Hash[String, String]
345
+ def stdio_env(server)
346
+ server.env.to_h { |variable| [variable.name, variable.value] }
347
+ end
348
+
349
+ # @rbs runtime: Riffer::Rig::Runtime
350
+ # @rbs return: Array[::ACP::Types::AvailableCommand]
351
+ def available_commands(runtime)
352
+ runtime.commands.map do |command|
353
+ ::ACP::Types::AvailableCommand.new(name: command.name, description: command.description)
354
+ end
355
+ end
356
+ end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Riffer::Rig::Bundled
4
- Bash = Riffer::Rig::Extension.new('bash') { |rig| rig.tool Riffer::Rig::Tools::Bash } #: Riffer::Rig::Extension
4
+ Bash = Riffer::Rig::Extension.new('bash') do |rig|
5
+ rig.tool Riffer::Rig::Tools::Bash
6
+ rig.tool Riffer::Rig::Tools::Bash::Output
7
+ rig.tool Riffer::Rig::Tools::Bash::Kill
8
+ end #: Riffer::Rig::Extension
5
9
  end
@@ -3,7 +3,12 @@
3
3
  module Riffer::Rig::Bundled
4
4
  Mcp = Riffer::Rig::Extension.new('mcp') do |rig|
5
5
  rig.settings[:servers].to_h.each do |name, server|
6
- rig.mcp(name.to_s, url: server.fetch(:url), headers: server[:headers].to_h.transform_keys(&:to_s))
6
+ rig.mcp(
7
+ name.to_s,
8
+ url: server.fetch(:url),
9
+ headers: server[:headers].to_h.transform_keys(&:to_s),
10
+ auth: Hash(server[:auth])
11
+ )
7
12
  end
8
13
  end #: Riffer::Rig::Extension
9
14
  end