insika 0.2.0 → 0.7.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 (300) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +325 -17
  3. data/README.md +52 -11
  4. data/bin/insika +895 -8
  5. data/bin/insika-router +87 -0
  6. data/docs/AGENTS.md +229 -163
  7. data/docs/API.md +56 -0
  8. data/docs/ARCHITECTURE.md +3 -2
  9. data/docs/ARTIFACTS.md +95 -0
  10. data/docs/BENCHMARK.md +2 -2
  11. data/docs/CHANNELS.md +111 -21
  12. data/docs/CONTEXT.md +132 -19
  13. data/docs/DEMO.md +80 -0
  14. data/docs/DEPLOY.md +75 -4
  15. data/docs/EMBEDDING.md +1 -1
  16. data/docs/EVALS.md +146 -2
  17. data/docs/FACTS.md +135 -0
  18. data/docs/HARVEST.md +116 -0
  19. data/docs/KNOWLEDGE.md +290 -0
  20. data/docs/LOADTEST.md +16 -10
  21. data/docs/MEDIA.md +128 -0
  22. data/docs/OBSERVABILITY.md +65 -6
  23. data/docs/OUTCOMES.md +137 -0
  24. data/docs/PLUGINS.md +51 -6
  25. data/docs/POLICY.md +216 -0
  26. data/docs/REFINEMENT.md +17 -12
  27. data/docs/RELEASING.md +27 -0
  28. data/docs/ROUTER.md +213 -0
  29. data/docs/RUNNING-LOCAL.md +6 -6
  30. data/docs/SCHEDULING.md +121 -0
  31. data/docs/SECURITY.md +79 -2
  32. data/docs/SKILLS.md +13 -4
  33. data/docs/SOAK.md +127 -0
  34. data/docs/TEMPLATES.md +134 -0
  35. data/docs/TOOLS.md +220 -27
  36. data/docs/WHY.md +1 -1
  37. data/docs/WORKFLOWS.md +2 -2
  38. data/docs/_includes/head_custom.html +5 -0
  39. data/docs/_includes/title.html +13 -0
  40. data/docs/_sass/color_schemes/insika.scss +32 -0
  41. data/docs/_sass/custom/custom.scss +199 -0
  42. data/docs/_sass/custom/setup.scss +26 -0
  43. data/docs/assets/img/favicon.svg +7 -0
  44. data/docs/assets/img/insika-mark.svg +7 -0
  45. data/docs/core-concepts.md +21 -0
  46. data/docs/domain.md +115 -0
  47. data/docs/improve.md +20 -0
  48. data/docs/index.md +9 -6
  49. data/docs/integrate.md +20 -0
  50. data/docs/operate.md +13 -6
  51. data/docs/prompts/ADD-TOOL.md +118 -0
  52. data/docs/prompts/DIAGNOSE-TURN.md +65 -0
  53. data/docs/prompts/GO-LIVE.md +138 -0
  54. data/docs/prompts/RUN-EXAMPLES.md +70 -0
  55. data/docs/reference.md +19 -0
  56. data/docs/ship.md +10 -2
  57. data/docs/start-here.md +18 -0
  58. data/lib/insika/agent_profile.rb +202 -10
  59. data/lib/insika/artifact_signing.rb +82 -0
  60. data/lib/insika/artifact_store.rb +160 -0
  61. data/lib/insika/balloon_splitter.rb +102 -0
  62. data/lib/insika/budget_ledger.rb +34 -3
  63. data/lib/insika/cache_series_store.rb +49 -0
  64. data/lib/insika/channel_delivery.rb +119 -14
  65. data/lib/insika/channels/relay.rb +77 -3
  66. data/lib/insika/channels/web.rb +2 -2
  67. data/lib/insika/chat_builder.rb +105 -2
  68. data/lib/insika/checkpoint_store.rb +16 -0
  69. data/lib/insika/commands/agent_payload.rb +3 -3
  70. data/lib/insika/commands/backfill_knowledge.rb +145 -0
  71. data/lib/insika/commands/cancel_followup.rb +49 -0
  72. data/lib/insika/commands/delete_artifact.rb +35 -0
  73. data/lib/insika/commands/delete_concept.rb +34 -0
  74. data/lib/insika/commands/delete_mcp.rb +6 -2
  75. data/lib/insika/commands/delete_tenant_data.rb +107 -0
  76. data/lib/insika/commands/export_customer_memory.rb +48 -0
  77. data/lib/insika/commands/forget_customer.rb +117 -0
  78. data/lib/insika/commands/freeze_funnel_baseline.rb +113 -0
  79. data/lib/insika/commands/gate_harvest.rb +138 -0
  80. data/lib/insika/commands/gate_refinement.rb +1 -1
  81. data/lib/insika/commands/judge_shadow_pairs.rb +124 -0
  82. data/lib/insika/commands/memory_forget_fact.rb +20 -4
  83. data/lib/insika/commands/memory_put_fact.rb +23 -4
  84. data/lib/insika/commands/promote_harvest.rb +130 -0
  85. data/lib/insika/commands/record_outcome.rb +46 -0
  86. data/lib/insika/commands/record_shadow_reply.rb +68 -0
  87. data/lib/insika/commands/refresh_mcp_tools.rb +47 -0
  88. data/lib/insika/commands/reject_harvest.rb +38 -0
  89. data/lib/insika/commands/resolve_proposal.rb +108 -0
  90. data/lib/insika/commands/restore_concept.rb +34 -0
  91. data/lib/insika/commands/revoke_contact.rb +49 -0
  92. data/lib/insika/commands/rollback_harvest.rb +86 -0
  93. data/lib/insika/commands/run_distillation.rb +186 -0
  94. data/lib/insika/commands/run_harvest.rb +393 -0
  95. data/lib/insika/commands/seed_demo_data.rb +31 -0
  96. data/lib/insika/commands/send_message.rb +103 -6
  97. data/lib/insika/commands/session_purge.rb +67 -0
  98. data/lib/insika/commands/upsert_mcp.rb +6 -3
  99. data/lib/insika/commands/write_concept.rb +57 -0
  100. data/lib/insika/contact_store.rb +183 -0
  101. data/lib/insika/context/builder.rb +21 -3
  102. data/lib/insika/context/fragment.rb +7 -3
  103. data/lib/insika/context/priority.rb +5 -0
  104. data/lib/insika/context/provider.rb +17 -3
  105. data/lib/insika/context/providers/briefing.rb +96 -0
  106. data/lib/insika/context/providers/knowledge.rb +108 -0
  107. data/lib/insika/context/providers/memory.rb +16 -7
  108. data/lib/insika/context/providers/prompt.rb +57 -23
  109. data/lib/insika/context/providers/skill.rb +2 -0
  110. data/lib/insika/context/providers/tool_search.rb +2 -0
  111. data/lib/insika/context_trace_store.rb +38 -2
  112. data/lib/insika/cron.rb +189 -0
  113. data/lib/insika/demo/agent_attrs.rb +43 -0
  114. data/lib/insika/demo/golden_cases.rb +81 -0
  115. data/lib/insika/demo/seeder.rb +336 -0
  116. data/lib/insika/distill.rb +224 -0
  117. data/lib/insika/distill_engine.rb +169 -0
  118. data/lib/insika/doctor.rb +889 -11
  119. data/lib/insika/dsl/definition.rb +3 -2
  120. data/lib/insika/dsl/runtime.rb +69 -82
  121. data/lib/insika/dsl/server_boot.rb +92 -1
  122. data/lib/insika/dsl/system.rb +10 -2
  123. data/lib/insika/dsl.rb +186 -5
  124. data/lib/insika/edge_limiter.rb +28 -7
  125. data/lib/insika/env_schema.rb +25 -3
  126. data/lib/insika/errors.rb +11 -0
  127. data/lib/insika/evals/assertions.rb +3 -2
  128. data/lib/insika/evals/golden.rb +41 -4
  129. data/lib/insika/evals/judge.rb +47 -2
  130. data/lib/insika/evals/pairwise.rb +29 -6
  131. data/lib/insika/evals/persona.rb +98 -0
  132. data/lib/insika/evals/runner.rb +9 -0
  133. data/lib/insika/evals/simulator.rb +225 -0
  134. data/lib/insika/evals/transport.rb +83 -1
  135. data/lib/insika/event_stream.rb +10 -0
  136. data/lib/insika/evidence.rb +183 -0
  137. data/lib/insika/executor.rb +869 -76
  138. data/lib/insika/followup_engine.rb +207 -0
  139. data/lib/insika/followup_policy.rb +198 -0
  140. data/lib/insika/followup_store.rb +306 -0
  141. data/lib/insika/funnel_declaration.rb +106 -0
  142. data/lib/insika/funnel_fold.rb +179 -0
  143. data/lib/insika/funnel_store.rb +163 -0
  144. data/lib/insika/golden_store.rb +17 -2
  145. data/lib/insika/grounding/matcher.rb +69 -0
  146. data/lib/insika/grounding.rb +44 -0
  147. data/lib/insika/harvest/conversion_gate.rb +159 -0
  148. data/lib/insika/harvest/criterion.rb +98 -0
  149. data/lib/insika/harvest/gate.rb +194 -0
  150. data/lib/insika/harvest/negative_list.rb +199 -0
  151. data/lib/insika/harvest.rb +241 -0
  152. data/lib/insika/harvest_engine.rb +193 -0
  153. data/lib/insika/harvest_store.rb +548 -0
  154. data/lib/insika/knowledge.rb +680 -0
  155. data/lib/insika/knowledge_store.rb +140 -0
  156. data/lib/insika/mcp_client.rb +94 -0
  157. data/lib/insika/mcp_json.rb +74 -0
  158. data/lib/insika/mcp_live_tool.rb +43 -0
  159. data/lib/insika/mcp_store.rb +98 -26
  160. data/lib/insika/mcp_tool_ingestor.rb +30 -8
  161. data/lib/insika/mcp_tool_registry.rb +100 -0
  162. data/lib/insika/media.rb +382 -0
  163. data/lib/insika/memory_audit_store.rb +85 -0
  164. data/lib/insika/memory_store.rb +264 -23
  165. data/lib/insika/message_origin.rb +7 -2
  166. data/lib/insika/middleware.rb +9 -0
  167. data/lib/insika/model_visible.rb +87 -0
  168. data/lib/insika/model_visible_trace_store.rb +66 -0
  169. data/lib/insika/onboarding.rb +22 -1
  170. data/lib/insika/outbox_store.rb +42 -4
  171. data/lib/insika/outcome_store.rb +147 -0
  172. data/lib/insika/overlay_tool_registry.rb +37 -17
  173. data/lib/insika/packaging.rb +163 -0
  174. data/lib/insika/parity/criterion.rb +79 -0
  175. data/lib/insika/parity/verdict.rb +318 -0
  176. data/lib/insika/prefix_fingerprint.rb +58 -0
  177. data/lib/insika/profile_source.rb +29 -1
  178. data/lib/insika/prompt_catalog.rb +10 -0
  179. data/lib/insika/proposal_store.rb +271 -0
  180. data/lib/insika/queue_policy.rb +4 -1
  181. data/lib/insika/refinement/proposer.rb +1 -1
  182. data/lib/insika/reliability.rb +32 -6
  183. data/lib/insika/retention.rb +316 -0
  184. data/lib/insika/router/app.rb +157 -0
  185. data/lib/insika/router/backend_pool.rb +98 -0
  186. data/lib/insika/router/hash_ring.rb +55 -0
  187. data/lib/insika/router/proxy_body.rb +34 -0
  188. data/lib/insika/router/session_key.rb +54 -0
  189. data/lib/insika/router.rb +18 -0
  190. data/lib/insika/routing.rb +101 -0
  191. data/lib/insika/safety/config.rb +44 -4
  192. data/lib/insika/safety/corpus.rb +255 -0
  193. data/lib/insika/safety/detectors.rb +32 -113
  194. data/lib/insika/safety/factory.rb +15 -2
  195. data/lib/insika/safety/grounding_enforcer.rb +59 -0
  196. data/lib/insika/safety/grounding_validator.rb +49 -0
  197. data/lib/insika/safety/input_guardrail.rb +1 -1
  198. data/lib/insika/safety/moderator.rb +1 -1
  199. data/lib/insika/safety/output_filter.rb +9 -5
  200. data/lib/insika/safety/output_validator.rb +11 -5
  201. data/lib/insika/schedule.rb +177 -0
  202. data/lib/insika/schedule_engine.rb +314 -0
  203. data/lib/insika/schedule_store.rb +208 -0
  204. data/lib/insika/schema_guard.rb +35 -0
  205. data/lib/insika/server/app.rb +247 -17
  206. data/lib/insika/server/rack_app.rb +21 -1
  207. data/lib/insika/server/responses.rb +40 -1
  208. data/lib/insika/session_actor.rb +8 -4
  209. data/lib/insika/session_store.rb +65 -2
  210. data/lib/insika/settings_store.rb +10 -0
  211. data/lib/insika/shadow_pair_store.rb +258 -0
  212. data/lib/insika/skill_catalog.rb +16 -0
  213. data/lib/insika/soak/envelope.rb +140 -0
  214. data/lib/insika/soak/report.rb +392 -0
  215. data/lib/insika/soak/runner.rb +554 -0
  216. data/lib/insika/steer_injector.rb +21 -10
  217. data/lib/insika/store.rb +10 -1
  218. data/lib/insika/stores/memory.rb +6 -0
  219. data/lib/insika/stores/sqlite.rb +8 -0
  220. data/lib/insika/studio/app.rb +1437 -54
  221. data/lib/insika/studio/assets/dist/application.css +1 -1
  222. data/lib/insika/studio/assets/dist/application.js +23 -23
  223. data/lib/insika/studio/assets/dist/favicon.svg +6 -0
  224. data/lib/insika/studio/forms.rb +308 -10
  225. data/lib/insika/studio/nav_icons.rb +19 -1
  226. data/lib/insika/studio/views/_agent_tab_cache.erb +25 -0
  227. data/lib/insika/studio/views/_agent_tab_config.erb +514 -0
  228. data/lib/insika/studio/views/_agent_tab_history.erb +24 -0
  229. data/lib/insika/studio/views/_agent_tab_loops.erb +54 -0
  230. data/lib/insika/studio/views/_agent_tab_memory.erb +51 -0
  231. data/lib/insika/studio/views/_agent_tab_outcomes.erb +31 -0
  232. data/lib/insika/studio/views/_agent_tab_prompts.erb +108 -0
  233. data/lib/insika/studio/views/_agent_tab_skills.erb +38 -0
  234. data/lib/insika/studio/views/_agents_master.erb +44 -0
  235. data/lib/insika/studio/views/_message.erb +49 -32
  236. data/lib/insika/studio/views/agent_detail.erb +62 -278
  237. data/lib/insika/studio/views/agents.erb +71 -54
  238. data/lib/insika/studio/views/approvals.erb +4 -1
  239. data/lib/insika/studio/views/artifact.erb +23 -0
  240. data/lib/insika/studio/views/artifacts.erb +59 -0
  241. data/lib/insika/studio/views/chats.erb +4 -1
  242. data/lib/insika/studio/views/customer.erb +94 -0
  243. data/lib/insika/studio/views/customers.erb +32 -0
  244. data/lib/insika/studio/views/evals.erb +6 -3
  245. data/lib/insika/studio/views/facts.erb +133 -0
  246. data/lib/insika/studio/views/followups.erb +125 -0
  247. data/lib/insika/studio/views/funnel.erb +106 -0
  248. data/lib/insika/studio/views/harvest.erb +234 -0
  249. data/lib/insika/studio/views/home.erb +106 -66
  250. data/lib/insika/studio/views/knowledge.erb +123 -0
  251. data/lib/insika/studio/views/layout.erb +15 -11
  252. data/lib/insika/studio/views/mcp.erb +174 -80
  253. data/lib/insika/studio/views/parity.erb +147 -0
  254. data/lib/insika/studio/views/playground.erb +7 -1
  255. data/lib/insika/studio/views/session.erb +233 -124
  256. data/lib/insika/studio/views/settings.erb +41 -1
  257. data/lib/insika/studio/views/skills.erb +1 -2
  258. data/lib/insika/studio/views/system_files.erb +1 -1
  259. data/lib/insika/studio/views/task.erb +13 -0
  260. data/lib/insika/studio/views/tasks.erb +4 -1
  261. data/lib/insika/studio/views/tools.erb +24 -10
  262. data/lib/insika/task_store.rb +21 -1
  263. data/lib/insika/templates/browser-agent/README.md +36 -0
  264. data/lib/insika/templates/browser-agent/agent.rb +49 -0
  265. data/lib/insika/templates/daily-digest/README.md +38 -0
  266. data/lib/insika/templates/daily-digest/agent.rb +77 -0
  267. data/lib/insika/templates/repo-explorer/README.md +36 -0
  268. data/lib/insika/templates/repo-explorer/agent.rb +45 -0
  269. data/lib/insika/templates/research-analyst/README.md +26 -0
  270. data/lib/insika/templates/research-analyst/agent.rb +58 -0
  271. data/lib/insika/templates/review-panel/README.md +20 -0
  272. data/lib/insika/templates/review-panel/agent.rb +50 -0
  273. data/lib/insika/templates/travel-planner/README.md +35 -0
  274. data/lib/insika/templates/travel-planner/agent.rb +87 -0
  275. data/lib/insika/templates.rb +112 -0
  276. data/lib/insika/testing/store_contract.rb +27 -6
  277. data/lib/insika/tick.rb +47 -11
  278. data/lib/insika/timezone.rb +45 -0
  279. data/lib/insika/tool_definition.rb +17 -7
  280. data/lib/insika/tool_envelope.rb +69 -0
  281. data/lib/insika/tool_manifest.rb +5 -1
  282. data/lib/insika/tools/data_defined_tool.rb +10 -0
  283. data/lib/insika/tools/generate_image.rb +89 -0
  284. data/lib/insika/tools/load_knowledge.rb +74 -0
  285. data/lib/insika/tools/run_persona_eval.rb +328 -0
  286. data/lib/insika/tools/save_artifact.rb +95 -0
  287. data/lib/insika/tools/schedule_followup.rb +164 -0
  288. data/lib/insika/tools/tts.rb +47 -0
  289. data/lib/insika/tools/update_briefing.rb +126 -0
  290. data/lib/insika/turn_output.rb +1 -1
  291. data/lib/insika/turn_state.rb +49 -1
  292. data/lib/insika/turn_timing.rb +22 -2
  293. data/lib/insika/version.rb +1 -1
  294. data/lib/insika/vitals.rb +84 -0
  295. data/lib/insika/wiring/graph.rb +472 -13
  296. data/lib/insika/wiring/graph_chat.rb +102 -0
  297. data/lib/insika.rb +149 -3
  298. metadata +181 -5
  299. data/docs/build.md +0 -14
  300. data/docs/understand.md +0 -10
@@ -1,3 +1,13 @@
1
+ <%# MCP — miller columns: instances master | editor detail.
2
+ ?i= selects the instance whose editor fills the detail pane; nil shows the
3
+ empty state with the new/import forms. Master rows and the editor form
4
+ frame-navigate (mcp-detail); create/import/delete submit to the top window
5
+ so the master list re-renders with the change. Credentials never come back
6
+ in plaintext — each value shows as the sentinel; keeping it preserves the
7
+ secret, changing it replaces, clearing the line removes it. %>
8
+ <%# Roda renders with plain locals — no local_assigns hash (Rails-ism). %>
9
+ <% frame_only = defined?(frame_only) && frame_only %>
10
+ <% unless frame_only %>
1
11
  <header class="page-head">
2
12
  <div>
3
13
  <nav class="crumbs"><a href="/studio/agents">agents</a> / <span>mcp</span></nav>
@@ -5,87 +15,171 @@
5
15
  </div>
6
16
  </header>
7
17
 
8
- <p class="muted matrix-hint">MCP server instances (durable config). Credentials in <code>env</code> never come back in plaintext — each value shows as <code><%= secret_sentinel %></code>; keeping it preserves the secret, changing it replaces, clearing the line removes it.</p>
9
-
10
- <% if @instances.empty? %>
11
- <div class="empty">No MCP instances. Add one below (e.g. <code>tavily</code>) to expose an MCP server to your agents' tools.</div>
18
+ <div class="drill" data-controller="list-filter">
19
+ <aside class="drill-pane drill-master">
20
+ <div class="drill-pane-head">
21
+ <div class="title"><strong>MCP <span class="count"><%= @instances.size %></span></strong></div>
22
+ <a class="btn primary btn-sm" href="/studio/mcp?i=" data-turbo-frame="mcp-detail" data-turbo-action="advance">+ new</a>
23
+ </div>
24
+ <div class="drill-search">
25
+ <input type="search" class="filter-input" placeholder="Filter by name, transport or status…" aria-label="Filter MCP instances"
26
+ data-list-filter-target="query" data-action="input->list-filter#filter keydown->list-filter#clear">
27
+ </div>
28
+ <nav class="drill-pane-body drill-list" aria-label="MCP instances" data-controller="active-link">
29
+ <% if @instances.empty? %>
30
+ <div class="empty">No MCP instances. Create one on the right (e.g. <code>tavily</code>) to expose an MCP server to your agents' tools.</div>
31
+ <% else %>
32
+ <% @instances.each do |m| %>
33
+ <% status_label, status_cls = m["enabled"] ? mcp_status(m) : ["off", "warn"] %>
34
+ <a class="drill-item<%== ' active' if @selected_mcp && @selected_mcp["name"] == m["name"] %>"
35
+ href="/studio/mcp?i=<%= Rack::Utils.escape(m["name"]) %>"
36
+ data-turbo-frame="mcp-detail" data-turbo-action="advance"
37
+ data-list-filter-target="item" data-filter-text="<%= mcp_filter_text(m) %>"
38
+ data-active-link-target="item" data-action="click->active-link#select">
39
+ <div class="drill-item-name">
40
+ <span class="txt" title="<%= m["name"] %>"><%= m["name"] %></span>
41
+ <span class="pill <%= status_cls %>"><%= status_label %></span>
42
+ </div>
43
+ <div class="drill-item-sub"><%= m["transport"] %><%== " · #{m['url']}" if m["url"].to_s != "" %><%== " · #{Array(m['tools_cache']).size} tool(s)" if Array(m["tools_cache"]).any? %></div>
44
+ </a>
45
+ <% end %>
46
+ <div class="empty" data-list-filter-target="empty" hidden>No MCP instances match your filter.</div>
47
+ <% end %>
48
+ </nav>
49
+ </aside>
12
50
  <% end %>
51
+ <turbo-frame id="mcp-detail" class="drill-pane drill-detail">
52
+ <% if frame_only %>
53
+ <% if flash["error"] %><div class="flash err" role="alert"><%= flash["error"] %></div><% end %>
54
+ <% if flash["notice"] %><div class="flash ok" role="status"><%= flash["notice"] %></div><% end %>
55
+ <% end %>
56
+ <% if @selected_mcp %>
57
+ <% m = @selected_mcp %>
58
+ <div class="drill-pane-head">
59
+ <div class="title">
60
+ <strong class="mono"><%= m["name"] %></strong>
61
+ <span class="count"><%= m["transport"] %><%== " · #{m['description']}" if m["description"].to_s != "" %></span>
62
+ </div>
63
+ <div class="head-actions">
64
+ <form method="post" action="/studio/mcp/test">
65
+ <%== csrf_tag %>
66
+ <input type="hidden" name="name" value="<%= m["name"] %>">
67
+ <button type="submit" class="btn btn-sm" data-turbo-submits-with="Testing…">Test connection</button>
68
+ </form>
69
+ <form method="post" action="/studio/mcp/delete" data-turbo-frame="_top" data-turbo-confirm="Remove MCP instance &quot;<%= m["name"] %>&quot;? Agents stop seeing its tools next turn.">
70
+ <%== csrf_tag %>
71
+ <input type="hidden" name="name" value="<%= m["name"] %>">
72
+ <button type="submit" class="btn btn-sm danger" data-turbo-submits-with="Removing…">Remove</button>
73
+ </form>
74
+ </div>
75
+ </div>
76
+ <div class="drill-pane-body">
77
+ <% tools = Array(m["tools_cache"]) %>
78
+ <% if tools.any? %>
79
+ <details class="mcp-tools-details" open>
80
+ <summary><%= tools.size %> tool(s) · live cache</summary>
81
+ <ul class="mcp-tools muted">
82
+ <% tools.each do |t| %>
83
+ <li><code><%= t["name"] %></code><% if presence(t["description"]) %> — <%= t["description"] %><% end %></li>
84
+ <% end %>
85
+ </ul>
86
+ </details>
87
+ <% end %>
13
88
 
14
- <% @instances.each do |m| %>
15
- <div class="card promptfile">
16
- <div class="promptfile-head">
17
- <strong class="mono"><%= m["name"] %>
18
- <% if m["enabled"] %><span class="pill ok">on</span><% else %><span class="pill warn">off</span><% end %>
19
- </strong>
20
- <form method="post" action="/studio/mcp/delete" data-turbo-confirm="Remove MCP instance &quot;<%= m["name"] %>&quot;? Agents stop seeing its tools next turn.">
21
- <%== csrf_tag %>
22
- <input type="hidden" name="name" value="<%= m["name"] %>">
23
- <button type="submit" class="ghost danger" data-turbo-submits-with="Removing…">Remove</button>
24
- </form>
25
- </div>
26
- <form method="post" action="/studio/mcp">
27
- <%== csrf_tag %>
28
- <input type="hidden" name="name" value="<%= m["name"] %>">
29
- <div class="grid-2">
30
- <label>transport
31
- <select name="transport">
32
- <% %w[stdio http sse].each do |t| %>
33
- <option value="<%= t %>"<%== " selected" if m["transport"] == t %>><%= t %></option>
34
- <% end %>
35
- </select>
36
- </label>
37
- <label class="check">
38
- <input type="checkbox" name="enabled" value="1"<%== " checked" if m["enabled"] %>>
39
- enabled
40
- </label>
89
+ <form class="mcp-edit" method="post" action="/studio/mcp" data-controller="transport-fields">
90
+ <%== csrf_tag %>
91
+ <input type="hidden" name="name" value="<%= m["name"] %>">
92
+ <div class="grid-2">
93
+ <label>transport
94
+ <select name="transport" data-transport-fields-target="select" data-action="change->transport-fields#sync">
95
+ <% %w[stdio http sse].each do |t| %>
96
+ <option value="<%= t %>"<%== " selected" if m["transport"] == t %>><%= t %></option>
97
+ <% end %>
98
+ </select>
99
+ </label>
100
+ <label class="check">
101
+ <input type="checkbox" name="enabled" value="1"<%== " checked" if m["enabled"] %>>
102
+ enabled
103
+ </label>
104
+ </div>
105
+ <label data-transport-fields-target="stdio">command <span class="muted">(stdio)</span>
106
+ <input type="text" name="command" value="<%= m["command"] %>" placeholder="npx -y @modelcontextprotocol/server-...">
107
+ </label>
108
+ <label data-transport-fields-target="stdio">args <span class="muted">(stdio, one argv token per line)</span>
109
+ <textarea name="args" rows="3" placeholder="-y&#10;@modelcontextprotocol/server-filesystem&#10;/tmp"><%= list_lines(m["args"]) %></textarea>
110
+ </label>
111
+ <label data-transport-fields-target="http">url <span class="muted">(http/sse)</span>
112
+ <input type="text" name="url" value="<%= m["url"] %>" placeholder="https://mcp.example.com">
113
+ </label>
114
+ <label>description
115
+ <input type="text" name="description" value="<%= m["description"] %>">
116
+ </label>
117
+ <label data-transport-fields-target="stdio">env <span class="muted">(stdio process env, one <code>KEY=value</code> per line)</span>
118
+ <textarea name="env" rows="4"><%= env_lines(m["env"]) %></textarea>
119
+ </label>
120
+ <label data-transport-fields-target="http">headers <span class="muted">(http/sse request headers, one <code>Name=value</code> per line)</span>
121
+ <textarea name="headers" rows="4"><%= env_lines(m["headers"]) %></textarea>
122
+ </label>
123
+ <div class="actions">
124
+ <button type="submit" class="primary" data-turbo-submits-with="Saving…">Save instance</button>
125
+ </div>
126
+ </form>
41
127
  </div>
42
- <label>command <span class="muted">(stdio)</span>
43
- <input type="text" name="command" value="<%= m["command"] %>" placeholder="npx -y @modelcontextprotocol/server-...">
44
- </label>
45
- <label>url <span class="muted">(http/sse)</span>
46
- <input type="text" name="url" value="<%= m["url"] %>" placeholder="https://mcp.example.com">
47
- </label>
48
- <label>description
49
- <input type="text" name="description" value="<%= m["description"] %>">
50
- </label>
51
- <label>env <span class="muted">(one <code>KEY=value</code> per line)</span>
52
- <textarea name="env" rows="4"><%= env_lines(m["env"]) %></textarea>
53
- </label>
54
- <div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Saving…">Save instance</button></div>
55
- </form>
56
- </div>
57
- <% end %>
128
+ <% else %>
129
+ <div class="drill-pane-head">
130
+ <div class="title">
131
+ <strong>Pick an instance</strong>
132
+ <span class="count">select on the left, or add one below</span>
133
+ </div>
134
+ </div>
135
+ <div class="drill-pane-body">
136
+ <form method="post" action="/studio/mcp" data-controller="transport-fields" data-turbo-frame="_top">
137
+ <%== csrf_tag %>
138
+ <div class="grid-2">
139
+ <label>name <span class="muted">(slug)</span>
140
+ <input type="text" name="name" placeholder="tavily" required>
141
+ </label>
142
+ <label>transport
143
+ <select name="transport" data-transport-fields-target="select" data-action="change->transport-fields#sync">
144
+ <% %w[stdio http sse].each do |t| %><option value="<%= t %>"><%= t %></option><% end %>
145
+ </select>
146
+ </label>
147
+ </div>
148
+ <label class="check">
149
+ <input type="checkbox" name="enabled" value="1" checked>
150
+ enabled
151
+ </label>
152
+ <label data-transport-fields-target="stdio">command <span class="muted">(stdio)</span>
153
+ <input type="text" name="command" placeholder="npx -y @modelcontextprotocol/server-...">
154
+ </label>
155
+ <label data-transport-fields-target="stdio">args <span class="muted">(stdio, one argv token per line)</span>
156
+ <textarea name="args" rows="3" placeholder="-y&#10;@modelcontextprotocol/server-filesystem&#10;/tmp"></textarea>
157
+ </label>
158
+ <label data-transport-fields-target="http">url <span class="muted">(http/sse)</span>
159
+ <input type="text" name="url" placeholder="https://mcp.example.com">
160
+ </label>
161
+ <label>description
162
+ <input type="text" name="description">
163
+ </label>
164
+ <label data-transport-fields-target="stdio">env <span class="muted">(stdio process env, one <code>KEY=value</code> per line)</span>
165
+ <textarea name="env" rows="4" placeholder="TAVILY_API_KEY=tvly-…"></textarea>
166
+ </label>
167
+ <label data-transport-fields-target="http">headers <span class="muted">(http/sse request headers, one <code>Name=value</code> per line)</span>
168
+ <textarea name="headers" rows="4" placeholder="Authorization=Bearer tvly-…"></textarea>
169
+ </label>
170
+ <div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Creating…">Create instance</button></div>
171
+ </form>
58
172
 
59
- <details class="card newfile">
60
- <summary>+ new MCP instance</summary>
61
- <form method="post" action="/studio/mcp">
62
- <%== csrf_tag %>
63
- <div class="grid-2">
64
- <label>name <span class="muted">(slug)</span>
65
- <input type="text" name="name" placeholder="tavily" required>
66
- </label>
67
- <label>transport
68
- <select name="transport">
69
- <% %w[stdio http sse].each do |t| %><option value="<%= t %>"><%= t %></option><% end %>
70
- </select>
71
- </label>
72
- </div>
73
- <label class="check">
74
- <input type="checkbox" name="enabled" value="1" checked>
75
- enabled
76
- </label>
77
- <label>command <span class="muted">(stdio)</span>
78
- <input type="text" name="command" placeholder="npx -y @modelcontextprotocol/server-...">
79
- </label>
80
- <label>url <span class="muted">(http/sse)</span>
81
- <input type="text" name="url" placeholder="https://mcp.example.com">
82
- </label>
83
- <label>description
84
- <input type="text" name="description">
85
- </label>
86
- <label>env <span class="muted">(one <code>KEY=value</code> per line)</span>
87
- <textarea name="env" rows="4" placeholder="TAVILY_API_KEY=tvly-…"></textarea>
88
- </label>
89
- <div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Creating…">Create instance</button></div>
90
- </form>
91
- </details>
173
+ <form method="post" action="/studio/mcp/import" data-turbo-frame="_top">
174
+ <%== csrf_tag %>
175
+ <label>mcpServers JSON <span class="muted">(paste a <code>{"mcpServers": {...}}</code> document — the same format Claude Desktop/Cursor use)</span>
176
+ <textarea name="json" rows="8" placeholder='{"mcpServers": {"tavily": {"url": "https://mcp.tavily.com/mcp", "headers": {"Authorization": "Bearer …"}}}}'></textarea>
177
+ </label>
178
+ <div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Importing…">Import</button></div>
179
+ </form>
180
+ </div>
181
+ <% end %>
182
+ </turbo-frame>
183
+ <% unless frame_only %>
184
+ </div>
185
+ <% end %>
@@ -0,0 +1,147 @@
1
+ <header class="page-head">
2
+ <div>
3
+ <nav class="crumbs"><span>operate</span></nav>
4
+ <h1>Parity<span class="sub">the shadow experiment</span></h1>
5
+ </div>
6
+ <div class="head-actions">
7
+ <%== agent_filter_form("/studio/parity", @agent) %>
8
+ </div>
9
+ </header>
10
+
11
+ <% if @criterion.nil? %>
12
+ <div class="empty">
13
+ No shadow channel is registered. Set <span class="mono">INSIKA_RELAY_SHADOW=1</span> and
14
+ restart — this page appears only when a channel is actually running in shadow.
15
+ </div>
16
+ <% elsif @pairs.empty? %>
17
+ <div class="empty">
18
+ No pairs yet. A mirrored exchange records a pair the moment our turn ends; the
19
+ incumbent's half lands when the mirror reports its reply
20
+ (<span class="mono">POST /channels/relay/shadow-reply</span>).
21
+ </div>
22
+ <% else %>
23
+ <% r = @report %>
24
+ <div class="card">
25
+ <div class="skill-desc">
26
+ <span class="pill <%= { pass: 'ok', fail: 'err', insufficient: 'warn', invalid: 'err' }.fetch(r.verdict, 'info') %>"><%= r.verdict %></span>
27
+ <span class="muted"><%= r.reason %></span>
28
+ </div>
29
+ <% sha_check = r.checks.find { |c| c.id == :criterion_sha } %>
30
+ <div class="skill-desc">
31
+ <span class="mono"><%= r.criterion_sha %></span>
32
+ <% if sha_check&.met %>
33
+ <span class="pill ok">frozen · matches every pair in the window</span>
34
+ <% else %>
35
+ <span class="pill err">NOT VERIFIED — the window's pairs do not all carry this frozen hash</span>
36
+ <% end %>
37
+ </div>
38
+ </div>
39
+
40
+ <h2>Checks<span class="sub">the arithmetic, not the conclusion</span></h2>
41
+ <div class="card">
42
+ <table class="grid">
43
+ <thead><tr><th>check</th><th>required</th><th>actual</th><th></th></tr></thead>
44
+ <tbody>
45
+ <% r.checks.each do |c| %>
46
+ <tr>
47
+ <td class="mono"><%= c.id %></td>
48
+ <td><%= c.required %></td>
49
+ <td><% if c.actual.is_a?(Hash) %><%= c.actual.map { |k, v| "#{k}: #{v}" }.join(" · ") %><% else %><%= c.actual %><% end %></td>
50
+ <td><span class="pill <%= c.met ? 'ok' : 'err' %>"><%= c.met ? "met" : "no" %></span></td>
51
+ </tr>
52
+ <% end %>
53
+ </tbody>
54
+ </table>
55
+ </div>
56
+
57
+ <h2>Pairs / day<span class="sub">floor <%= @criterion.rule.pairs_per_day %> · a gap day invalidates the window</span></h2>
58
+ <div class="card">
59
+ <table class="grid">
60
+ <thead><tr><th>date</th><th>pairs</th><th>decided</th><th></th></tr></thead>
61
+ <tbody>
62
+ <% r.daily.each do |d| %>
63
+ <tr>
64
+ <td class="mono"><%= d[:date] %></td>
65
+ <td><%= d[:pairs] %></td>
66
+ <td><%= d[:decided] %></td>
67
+ <td><% if d[:floor].zero? %><span class="muted">partial day — reported, not required</span><% elsif d[:pairs] >= d[:floor] %><span class="pill ok">met</span><% else %><span class="pill err">below floor</span><% end %></td>
68
+ </tr>
69
+ <% end %>
70
+ </tbody>
71
+ </table>
72
+ </div>
73
+
74
+ <% if r.counts %>
75
+ <h2>Per store<span class="sub">a store blocks its own cut even when the aggregate passes</span></h2>
76
+ <div class="card">
77
+ <table class="grid">
78
+ <thead><tr><th>store</th><th>decided</th><th>win-or-tie</th><th>worse</th><th>meets floor</th></tr></thead>
79
+ <tbody>
80
+ <% r.per_agent.each do |agent, stats| %>
81
+ <tr>
82
+ <td class="mono"><%= agent %></td>
83
+ <td><%= stats[:decided] %></td>
84
+ <td><%= stats[:win_or_tie] %></td>
85
+ <td><%= stats[:worse] %></td>
86
+ <td><% if stats[:meets].nil? %><span class="muted">below per-store minimum</span><% elsif stats[:meets] %><span class="pill ok">yes</span><% else %><span class="pill err">no</span><% end %></td>
87
+ </tr>
88
+ <% end %>
89
+ </tbody>
90
+ </table>
91
+ </div>
92
+
93
+ <h2>Judge agreement<span class="sub">split / unknown / order-dependent</span></h2>
94
+ <div class="card">
95
+ <div class="skill-desc">
96
+ <span class="pill <%= r.counts[:split].positive? ? 'warn' : 'ok' %>"><%= r.counts[:split] %> split</span>
97
+ <span class="pill <%= r.counts[:unknown].positive? ? 'warn' : 'ok' %>"><%= r.counts[:unknown] %> unknown</span>
98
+ <span class="pill <%= @agreement[:order_dependent].positive? ? 'warn' : 'ok' %>"><%= @agreement[:order_dependent] %> flipped with order</span>
99
+ <span class="muted">of <%= r.counts[:judged] %> judged pairs</span>
100
+ </div>
101
+ </div>
102
+ <% end %>
103
+
104
+ <h2>Judge a batch</h2>
105
+ <div class="card">
106
+ <form method="post" action="/studio/parity" class="actions">
107
+ <%== csrf_tag %>
108
+ <label>
109
+ Agent (all stores when blank)
110
+ <input type="text" name="agent" placeholder="agent-id">
111
+ </label>
112
+ <label>
113
+ Limit
114
+ <input type="number" name="limit" value="50" min="1" max="200">
115
+ </label>
116
+ <button type="submit" class="primary">Judge <%= @pending %> complete pair(s)</button>
117
+ </form>
118
+ <div class="skill-desc muted">
119
+ Cost: <span class="mono"><%= @judge_cost %></span> provider calls
120
+ (<%= @pending %> pairs × <%= @criterion.rule.min_judge_models %> judges × 2 orders).
121
+ <% if Array((@judge_models || {})["judges"]).empty? %>
122
+ <span class="pill err">no judges configured in settings['evals'] — the press will refuse</span>
123
+ <% end %>
124
+ </div>
125
+ </div>
126
+
127
+ <h2>Recent pairs<span class="sub">inbound · incumbent · insika</span></h2>
128
+ <div class="card">
129
+ <ul class="history">
130
+ <% @pairs.each do |p| %>
131
+ <li>
132
+ <span class="pill <%= { open: 'warn', complete: 'info', silent: 'info', judged: 'ok', incomplete: 'err' }.fetch(p.status, 'info') %>"><%= p.status %></span>
133
+ <span class="mono"><%= p.id[0, 12] %></span>
134
+ <% if p.agent %><span class="mono"><%= p.agent %></span><% end %>
135
+ <% if p.outcome %><span class="pill"><%= p.outcome %></span><% end %>
136
+ <% if p.session_id %><a class="mono" href="/studio/sessions/<%= Rack::Utils.escape(p.session_id) %>">session →</a><% end %>
137
+ <div class="skill-desc muted"><strong>customer:</strong> <%= p.inbound.to_s %></div>
138
+ <div class="skill-desc"><strong>incumbent:</strong> <%= p.incumbent_reply.to_s %></div>
139
+ <div class="skill-desc"><strong>insika:</strong> <%= p.insika_reply.to_s %></div>
140
+ <% if p.verdict.is_a?(Hash) && presence(p.verdict["reason"]) %>
141
+ <div class="skill-desc muted">why: <%= p.verdict["reason"] %></div>
142
+ <% end %>
143
+ </li>
144
+ <% end %>
145
+ </ul>
146
+ </div>
147
+ <% end %>
@@ -15,7 +15,13 @@
15
15
  </select>
16
16
  </label>
17
17
  <label class="chat-field">session
18
- <input type="text" name="session_id" value="<%= @session_id %>" placeholder="empty = new conversation">
18
+ <input type="text" name="session_id" value="<%= @session_id %>" placeholder="empty = new conversation"
19
+ list="recent-sessions" autocomplete="off">
20
+ <datalist id="recent-sessions">
21
+ <% Array(@recent).each do |s| %>
22
+ <option value="<%= s.id %>" label="<%= [time_ago(s.updated_at), presence(session_preview(s).to_s[0, 60])].compact.join(" · ") %>">
23
+ <% end %>
24
+ </datalist>
19
25
  </label>
20
26
  <span class="pill info chat-status" data-live-transcript-target="status">disconnected</span>
21
27
  <details class="pin chat-pin">