insika 0.8.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +312 -2
- data/README.md +10 -6
- data/bin/insika +4 -1
- data/bin/insika-router +0 -1
- data/docs/AGENTS.md +142 -18
- data/docs/API.md +73 -0
- data/docs/ARCHITECTURE.md +56 -50
- data/docs/ARTIFACTS.md +33 -3
- data/docs/BENCHMARK.md +50 -7
- data/docs/CHANNELS.md +19 -2
- data/docs/CONTEXT.md +96 -27
- data/docs/CROSS-HARNESS-BENCH.md +157 -0
- data/docs/DEPLOY.md +70 -13
- data/docs/EMBEDDING.md +10 -0
- data/docs/EVALS.md +129 -10
- data/docs/FACTS.md +12 -6
- data/docs/HARVEST.md +8 -3
- data/docs/KNOWLEDGE.md +14 -0
- data/docs/MEDIA.md +6 -2
- data/docs/OBSERVABILITY.md +164 -28
- data/docs/PERFORMANCE.md +110 -0
- data/docs/PLUGINS.md +4 -4
- data/docs/POLICY.md +26 -14
- data/docs/RELEASING.md +9 -0
- data/docs/ROUTER.md +7 -0
- data/docs/RUBYLLM_2_MIGRATION.md +307 -0
- data/docs/SECURITY.md +34 -1
- data/docs/SHARED-CONVERSATIONS.md +40 -0
- data/docs/SKILLS.md +5 -3
- data/docs/STUDIO.md +122 -0
- data/docs/TOOLS.md +238 -42
- data/docs/operate.md +3 -0
- data/docs/prompts/ADD-TOOL.md +12 -2
- data/docs/prompts/DIAGNOSE-TURN.md +3 -0
- data/docs/prompts/GO-LIVE.md +3 -1
- data/lib/insika/agent_defaults.rb +140 -0
- data/lib/insika/agent_file_store.rb +12 -0
- data/lib/insika/agent_profile.rb +99 -17
- data/lib/insika/artifact_signing.rb +11 -0
- data/lib/insika/channels/web/widget.js +33 -0
- data/lib/insika/channels/web.rb +5 -2
- data/lib/insika/chat_builder.rb +124 -28
- data/lib/insika/checkpoint.rb +6 -1
- data/lib/insika/checkpoint_store.rb +59 -7
- data/lib/insika/commands/agent_payload.rb +3 -3
- data/lib/insika/commands/delete_kit.rb +26 -0
- data/lib/insika/commands/gate_harvest.rb +3 -0
- data/lib/insika/commands/review_guardrail_flag.rb +38 -0
- data/lib/insika/commands/run_distillation.rb +5 -8
- data/lib/insika/commands/run_harvest.rb +17 -1
- data/lib/insika/commands/seed_session.rb +128 -0
- data/lib/insika/commands/send_message.rb +9 -3
- data/lib/insika/commands/update_settings.rb +23 -1
- data/lib/insika/commands/write_kit.rb +27 -0
- data/lib/insika/compaction.rb +33 -7
- data/lib/insika/config_store.rb +87 -7
- data/lib/insika/context/builder.rb +29 -9
- data/lib/insika/context/priority.rb +5 -0
- data/lib/insika/context/provider.rb +7 -2
- data/lib/insika/context/providers/briefing.rb +11 -8
- data/lib/insika/context/providers/fence_notice.rb +27 -0
- data/lib/insika/context/providers/knowledge.rb +32 -6
- data/lib/insika/context/providers/memory.rb +61 -8
- data/lib/insika/context/providers/pending_confirmation.rb +52 -0
- data/lib/insika/context/providers/prompt.rb +17 -4
- data/lib/insika/context/providers/session.rb +46 -6
- data/lib/insika/context/reranker.rb +75 -0
- data/lib/insika/distill.rb +22 -13
- data/lib/insika/doctor.rb +142 -4
- data/lib/insika/dsl/runtime.rb +37 -4
- data/lib/insika/dsl/server_boot.rb +4 -12
- data/lib/insika/dsl.rb +19 -5
- data/lib/insika/edge_limiter.rb +27 -16
- data/lib/insika/egress_guard.rb +11 -3
- data/lib/insika/env_schema.rb +33 -73
- data/lib/insika/errors.rb +1 -0
- data/lib/insika/evals/assertions.rb +259 -18
- data/lib/insika/evals/golden.rb +230 -4
- data/lib/insika/evals/judge.rb +19 -10
- data/lib/insika/evals/moderator_compare.rb +47 -0
- data/lib/insika/evals/report.rb +61 -9
- data/lib/insika/evals/runner.rb +64 -4
- data/lib/insika/evals/simulator.rb +21 -3
- data/lib/insika/evals/transport.rb +317 -15
- data/lib/insika/event_stream.rb +63 -7
- data/lib/insika/evidence.rb +105 -17
- data/lib/insika/executor.rb +698 -164
- data/lib/insika/fence.rb +96 -0
- data/lib/insika/golden_store.rb +3 -0
- data/lib/insika/harvest.rb +26 -14
- data/lib/insika/harvest_engine.rb +1 -0
- data/lib/insika/harvest_store.rb +12 -0
- data/lib/insika/http_client.rb +87 -4
- data/lib/insika/kits.rb +54 -0
- data/lib/insika/knowledge.rb +34 -20
- data/lib/insika/llm_configurator.rb +46 -4
- data/lib/insika/llm_provider_store.rb +1 -3
- data/lib/insika/llm_trace_store.rb +54 -0
- data/lib/insika/mcp_client.rb +27 -63
- data/lib/insika/mcp_live_tool.rb +55 -9
- data/lib/insika/mcp_store.rb +37 -3
- data/lib/insika/mcp_tool_registry.rb +76 -26
- data/lib/insika/media.rb +38 -25
- data/lib/insika/memory_store.rb +16 -3
- data/lib/insika/memory_trim.rb +60 -0
- data/lib/insika/middleware.rb +12 -3
- data/lib/insika/model_metrics_store.rb +285 -0
- data/lib/insika/model_registry.rb +110 -0
- data/lib/insika/model_resolver.rb +1 -1
- data/lib/insika/model_selection.rb +14 -21
- data/lib/insika/model_visible.rb +17 -6
- data/lib/insika/onboarding.rb +1 -0
- data/lib/insika/outcome_store.rb +1 -1
- data/lib/insika/overlay_tool_registry.rb +30 -2
- data/lib/insika/pack_importer.rb +54 -9
- data/lib/insika/packaging.rb +14 -5
- data/lib/insika/pending_action_store.rb +45 -18
- data/lib/insika/plugin/loader.rb +4 -2
- data/lib/insika/prefix_fingerprint.rb +32 -27
- data/lib/insika/profile_source.rb +5 -0
- data/lib/insika/provider_error_classifier.rb +18 -3
- data/lib/insika/queue_policy.rb +7 -2
- data/lib/insika/refinement/proposer.rb +42 -17
- data/lib/insika/reliability.rb +12 -3
- data/lib/insika/router/app.rb +14 -5
- data/lib/insika/router/session_key.rb +35 -5
- data/lib/insika/safety/config.rb +20 -3
- data/lib/insika/safety/corpus.rb +10 -3
- data/lib/insika/safety/factory.rb +39 -1
- data/lib/insika/safety/input_guardrail.rb +21 -1
- data/lib/insika/safety/judge_moderator.rb +70 -0
- data/lib/insika/safety/output_validator.rb +29 -5
- data/lib/insika/safety/prompt_echo.rb +89 -0
- data/lib/insika/schema_guard.rb +53 -76
- data/lib/insika/server/app.rb +58 -7
- data/lib/insika/server/rack_app.rb +2 -0
- data/lib/insika/server/responses.rb +50 -5
- data/lib/insika/session_actor.rb +58 -28
- data/lib/insika/session_store.rb +163 -11
- data/lib/insika/settings_store.rb +57 -2
- data/lib/insika/shared_conversations.rb +305 -0
- data/lib/insika/skill_catalog.rb +28 -2
- data/lib/insika/skill_store.rb +6 -0
- data/lib/insika/soak/runner.rb +1 -1
- data/lib/insika/spoken_transcript.rb +31 -0
- data/lib/insika/steer_injector.rb +7 -48
- data/lib/insika/store.rb +29 -1
- data/lib/insika/stores/json_value.rb +37 -0
- data/lib/insika/stores/memory.rb +22 -35
- data/lib/insika/stores/postgres.rb +295 -0
- data/lib/insika/stores/sqlite.rb +257 -43
- data/lib/insika/stores/turn_counter.rb +25 -0
- data/lib/insika/studio/app.rb +1459 -163
- data/lib/insika/studio/assets/dist/application.css +1 -1
- data/lib/insika/studio/assets/dist/application.js +25 -25
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-sans-latin-wght-normal.woff2 +0 -0
- data/lib/insika/studio/forms.rb +189 -12
- data/lib/insika/studio/nav_icons.rb +3 -0
- data/lib/insika/studio/views/_agent_config_retrieval.erb +73 -0
- data/lib/insika/studio/views/_agent_picker.erb +15 -0
- data/lib/insika/studio/views/_agent_tab_config.erb +83 -243
- data/lib/insika/studio/views/_agent_tab_history.erb +56 -1
- data/lib/insika/studio/views/_agent_tab_performance.erb +34 -0
- data/lib/insika/studio/views/_agent_tab_prompts.erb +7 -9
- data/lib/insika/studio/views/_agent_tab_skills.erb +16 -7
- data/lib/insika/studio/views/_agent_tab_tools.erb +90 -0
- data/lib/insika/studio/views/_agents_master.erb +8 -12
- data/lib/insika/studio/views/_cfg_alerts.erb +4 -0
- data/lib/insika/studio/views/_cfg_budget.erb +20 -0
- data/lib/insika/studio/views/_cfg_distill.erb +26 -0
- data/lib/insika/studio/views/_cfg_followup.erb +30 -0
- data/lib/insika/studio/views/_cfg_grounding.erb +21 -0
- data/lib/insika/studio/views/_cfg_guardrails.erb +47 -0
- data/lib/insika/studio/views/_cfg_harvest.erb +35 -0
- data/lib/insika/studio/views/_cfg_reliability.erb +32 -0
- data/lib/insika/studio/views/_chats_master.erb +66 -0
- data/lib/insika/studio/views/_customers_master.erb +37 -0
- data/lib/insika/studio/views/_empty.erb +15 -0
- data/lib/insika/studio/views/_facts_master.erb +42 -0
- data/lib/insika/studio/views/_harvest_candidate_detail.erb +14 -0
- data/lib/insika/studio/views/_identity.erb +16 -0
- data/lib/insika/studio/views/_kpi.erb +17 -0
- data/lib/insika/studio/views/_message.erb +40 -2
- data/lib/insika/studio/views/_model_chart.erb +82 -0
- data/lib/insika/studio/views/_refinement_master.erb +38 -0
- data/lib/insika/studio/views/{tool_edit.erb → _tool_edit.erb} +38 -11
- data/lib/insika/studio/views/_tool_users.erb +17 -0
- data/lib/insika/studio/views/_why.erb +53 -0
- data/lib/insika/studio/views/agent_detail.erb +17 -10
- data/lib/insika/studio/views/agents.erb +28 -16
- data/lib/insika/studio/views/approvals.erb +16 -11
- data/lib/insika/studio/views/artifact.erb +20 -7
- data/lib/insika/studio/views/artifacts.erb +19 -14
- data/lib/insika/studio/views/chats.erb +22 -29
- data/lib/insika/studio/views/customer.erb +88 -43
- data/lib/insika/studio/views/customers.erb +16 -26
- data/lib/insika/studio/views/evals.erb +49 -31
- data/lib/insika/studio/views/fact_detail.erb +114 -0
- data/lib/insika/studio/views/facts.erb +15 -116
- data/lib/insika/studio/views/followups.erb +63 -52
- data/lib/insika/studio/views/funnel.erb +104 -43
- data/lib/insika/studio/views/harvest.erb +85 -59
- data/lib/insika/studio/views/home.erb +138 -23
- data/lib/insika/studio/views/inbox.erb +36 -0
- data/lib/insika/studio/views/kits.erb +176 -0
- data/lib/insika/studio/views/knowledge.erb +92 -80
- data/lib/insika/studio/views/layout.erb +42 -11
- data/lib/insika/studio/views/live.erb +45 -0
- data/lib/insika/studio/views/mcp.erb +43 -13
- data/lib/insika/studio/views/models.erb +187 -0
- data/lib/insika/studio/views/parity.erb +38 -32
- data/lib/insika/studio/views/playground.erb +13 -9
- data/lib/insika/studio/views/refinement.erb +15 -224
- data/lib/insika/studio/views/refinement_detail.erb +243 -0
- data/lib/insika/studio/views/session.erb +153 -118
- data/lib/insika/studio/views/settings.erb +86 -22
- data/lib/insika/studio/views/skills.erb +53 -19
- data/lib/insika/studio/views/system_files.erb +7 -7
- data/lib/insika/studio/views/task.erb +45 -15
- data/lib/insika/studio/views/tasks.erb +35 -18
- data/lib/insika/studio/views/tools.erb +62 -73
- data/lib/insika/task_actor.rb +4 -1
- data/lib/insika/task_store.rb +67 -1
- data/lib/insika/telemetry/pricing.rb +16 -14
- data/lib/insika/telemetry/recorder.rb +115 -9
- data/lib/insika/telemetry/ruby_llm_instrumenter.rb +110 -0
- data/lib/insika/telemetry.rb +12 -2
- data/lib/insika/templates/daily-digest/README.md +13 -0
- data/lib/insika/templates/daily-digest/agent.rb +22 -11
- data/lib/insika/testing/store_contract.rb +65 -0
- data/lib/insika/text_tool_call.rb +66 -0
- data/lib/insika/tick.rb +7 -1
- data/lib/insika/token_store.rb +5 -0
- data/lib/insika/tool_assembly.rb +21 -13
- data/lib/insika/tool_batch.rb +1 -1
- data/lib/insika/tool_catalog.rb +12 -2
- data/lib/insika/tool_definition.rb +82 -13
- data/lib/insika/tool_envelope.rb +266 -29
- data/lib/insika/tool_manifest.rb +1 -0
- data/lib/insika/tool_store.rb +12 -4
- data/lib/insika/tool_trace_store.rb +15 -8
- data/lib/insika/tool_usage_report.rb +14 -4
- data/lib/insika/tools/a2a_remote.rb +1 -1
- data/lib/insika/tools/agent_enum.rb +1 -1
- data/lib/insika/tools/confirm_pending.rb +112 -0
- data/lib/insika/tools/data_defined_tool.rb +24 -15
- data/lib/insika/tools/generate_image.rb +2 -2
- data/lib/insika/tools/load_knowledge.rb +7 -3
- data/lib/insika/tools/load_skill.rb +3 -3
- data/lib/insika/tools/present.rb +115 -0
- data/lib/insika/tools/remember.rb +7 -2
- data/lib/insika/tools/run_persona_eval.rb +9 -6
- data/lib/insika/tools/save_artifact.rb +16 -6
- data/lib/insika/tools/schedule_followup.rb +3 -3
- data/lib/insika/tools/stuck_signal.rb +3 -3
- data/lib/insika/tools/subagent.rb +5 -5
- data/lib/insika/tools/subagents.rb +3 -3
- data/lib/insika/tools/tool_search.rb +11 -7
- data/lib/insika/tools/tts.rb +2 -2
- data/lib/insika/tools/update_briefing.rb +4 -4
- data/lib/insika/turn_output.rb +2 -2
- data/lib/insika/turn_state.rb +31 -1
- data/lib/insika/turn_timing.rb +51 -14
- data/lib/insika/version.rb +1 -1
- data/lib/insika/wiring/graph.rb +88 -16
- data/lib/insika/wiring/graph_chat.rb +4 -0
- data/lib/insika/workflow.rb +25 -64
- data/lib/insika.rb +23 -3
- metadata +74 -13
- data/lib/insika/commands/import_mcp_tools.rb +0 -48
- data/lib/insika/mcp_http_client.rb +0 -67
- data/lib/insika/mcp_tool_ingestor.rb +0 -165
|
@@ -1,23 +1,36 @@
|
|
|
1
|
+
<div class="reports-page reports-artifact">
|
|
1
2
|
<%# the artifact preview page. The content renders ONLY inside a SANDBOXED
|
|
2
3
|
iframe (no scripts, no same-origin, no forms) on top of the content
|
|
3
4
|
route's own restrictive CSP — artifact content is LLM output, untrusted. %>
|
|
4
5
|
<header class="page-head">
|
|
5
6
|
<div>
|
|
6
|
-
<nav class="crumbs"><a href="/studio/artifacts">Artifacts</a></nav>
|
|
7
|
-
<h1><%= @artifact.title %><span class="sub"
|
|
7
|
+
<nav class="crumbs"><a href="/studio/artifacts?agent=<%= Rack::Utils.escape(@artifact.agent) %>">Artifacts</a></nav>
|
|
8
|
+
<h1><%= @artifact.title %><span class="sub"><%= @artifact.agent %> · <%= time_ago(@artifact.created_at) %></span></h1>
|
|
8
9
|
</div>
|
|
9
10
|
<div class="head-actions">
|
|
10
11
|
<% if @signed_url %>
|
|
11
|
-
<button type="button" class="btn sm" data-copy="<%= @signed_url %>">copy signed link</button>
|
|
12
|
+
<button type="button" class="btn btn-sm" data-copy="<%= @signed_url %>">copy signed link</button>
|
|
12
13
|
<% end %>
|
|
13
|
-
<a class="btn sm ghost" href="<%= @content_url %>" target="_blank" rel="noopener">open raw</a>
|
|
14
|
-
<form method="post" action="/studio/artifacts/<%= Rack::Utils.escape_path(@artifact.id) %>/delete" class="inline">
|
|
14
|
+
<a class="btn btn-sm ghost" href="<%= @content_url %>" target="_blank" rel="noopener">open raw</a>
|
|
15
|
+
<form method="post" action="/studio/artifacts/<%= Rack::Utils.escape_path(@artifact.id) %>/delete" class="inline" data-turbo-confirm="Delete this artifact?">
|
|
15
16
|
<%== csrf_tag %>
|
|
16
|
-
<
|
|
17
|
+
<input type="hidden" name="agent" value="<%= @artifact.agent %>">
|
|
18
|
+
<button class="btn btn-sm ghost danger">delete</button>
|
|
17
19
|
</form>
|
|
18
20
|
</div>
|
|
19
21
|
</header>
|
|
20
22
|
|
|
23
|
+
<details class="report-details">
|
|
24
|
+
<summary>Artifact details</summary>
|
|
25
|
+
<dl class="kv">
|
|
26
|
+
<dt>ID</dt><dd class="mono"><%= @artifact.id %></dd>
|
|
27
|
+
<dt>Format</dt><dd><%= @artifact.mime %></dd>
|
|
28
|
+
<dt>Created</dt><dd><%= @artifact.created_at %></dd>
|
|
29
|
+
<dt>Task</dt><dd><a href="<%= task_path(@artifact.task_id) %>"><%= @artifact.task_id %></a></dd>
|
|
30
|
+
</dl>
|
|
31
|
+
</details>
|
|
32
|
+
|
|
21
33
|
<div class="card artifact-frame">
|
|
22
34
|
<iframe sandbox src="<%= @content_url %>" title="<%= @artifact.title %>"></iframe>
|
|
23
|
-
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
<div class="reports-page reports-artifacts">
|
|
1
2
|
<%# the Artifacts page: the report destination. Reads the ArtifactStore
|
|
2
3
|
directly (the listing IS the history); the only mutation — delete —
|
|
3
4
|
dispatches :delete_artifact on the bus. The preview is a sandboxed iframe
|
|
4
5
|
on top of the content route's own restrictive CSP. %>
|
|
5
6
|
<header class="page-head">
|
|
6
7
|
<div>
|
|
7
|
-
|
|
8
|
-
<h1>Artifacts<span class="sub">
|
|
8
|
+
<%== crumbs_for("Artifacts") %>
|
|
9
|
+
<h1>Artifacts<span class="sub">Saved reports & pages</span></h1>
|
|
9
10
|
</div>
|
|
10
11
|
<div class="head-actions">
|
|
11
12
|
<%== agent_filter_form("/studio/artifacts", @agent) %>
|
|
@@ -19,36 +20,38 @@
|
|
|
19
20
|
has <span class="mono">save_artifact</span> in its <span class="mono">tools_allow</span>
|
|
20
21
|
writes its reports here.
|
|
21
22
|
<% else %>
|
|
22
|
-
No artifacts yet
|
|
23
|
+
No artifacts yet. Reports appear here when an agent uses <code>save_artifact</code>.
|
|
23
24
|
<% end %>
|
|
24
25
|
</div>
|
|
25
26
|
<% else %>
|
|
26
|
-
<
|
|
27
|
-
<div class="card">
|
|
27
|
+
<div class="section-head"><h2>Saved artifacts</h2><span class="muted"><%= @artifacts.size %> reports · newest first</span></div>
|
|
28
|
+
<div class="card report-table">
|
|
28
29
|
<table class="grid">
|
|
29
30
|
<thead>
|
|
30
31
|
<tr>
|
|
31
32
|
<% unless @agent %><th>agent</th><% end %>
|
|
32
|
-
<th>
|
|
33
|
+
<th>Title</th><th>Format</th><th>Task</th><th class="time">Created</th><th><span class="sr-only">Actions</span></th>
|
|
33
34
|
</tr>
|
|
34
35
|
</thead>
|
|
35
36
|
<tbody>
|
|
36
37
|
<% @artifacts.each do |a| %>
|
|
37
38
|
<tr>
|
|
38
|
-
<% unless @agent
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<td class="
|
|
42
|
-
<td class="
|
|
39
|
+
<% unless @agent %>
|
|
40
|
+
<td><%== render("_identity", locals: { id: a.agent, name: a.agent, sub: nil }) %></td>
|
|
41
|
+
<% end %>
|
|
42
|
+
<td class="ellipsis"><a href="/studio/artifacts/<%= Rack::Utils.escape_path(a.id) %>"><%= a.title %></a></td>
|
|
43
|
+
<td><span class="status neutral"><%= a.mime %></span></td>
|
|
44
|
+
<td class="mono"><span title="<%= a.task_id %>" data-copy="<%= a.task_id %>"><%= short_id(a.task_id) %></span></td>
|
|
45
|
+
<td class="time"><span title="<%= a.created_at %>"><%= time_ago(a.created_at) %></span></td>
|
|
43
46
|
<td class="row-actions">
|
|
44
47
|
<% if @signed_available %>
|
|
45
|
-
<button type="button" class="btn ghost sm" data-copy="<%= signed_artifact_url(a.id) %>">copy link</button>
|
|
48
|
+
<button type="button" class="btn ghost btn-sm" data-copy="<%= signed_artifact_url(a.id) %>">copy link</button>
|
|
46
49
|
<% end %>
|
|
47
50
|
<form method="post" action="/studio/artifacts/<%= Rack::Utils.escape_path(a.id) %>/delete"
|
|
48
|
-
class="inline" data-confirm="Delete this artifact?">
|
|
51
|
+
class="inline" data-turbo-confirm="Delete this artifact?">
|
|
49
52
|
<%== csrf_tag %>
|
|
50
53
|
<input type="hidden" name="agent" value="<%= a.agent %>">
|
|
51
|
-
<button class="btn ghost sm danger">delete</button>
|
|
54
|
+
<button class="btn ghost btn-sm danger">delete</button>
|
|
52
55
|
</form>
|
|
53
56
|
</td>
|
|
54
57
|
</tr>
|
|
@@ -57,3 +60,5 @@
|
|
|
57
60
|
</table>
|
|
58
61
|
</div>
|
|
59
62
|
<% end %>
|
|
63
|
+
|
|
64
|
+
</div>
|
|
@@ -1,37 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
<%# Chats — miller columns: master list | detail frame. A row click loads the
|
|
2
|
+
session viewer beside the list (no full page load) and advances the URL,
|
|
3
|
+
same shell as Agents/Customers/Refinement. The detail pane's landing state
|
|
4
|
+
just points at the playground — there is no "create a session" form here,
|
|
5
|
+
conversations are born there, not authored in Studio. %>
|
|
6
|
+
<header class="page-head operation-head">
|
|
2
7
|
<div>
|
|
3
|
-
<nav class="crumbs"><a href="/studio/agents">agents</a
|
|
4
|
-
<h1>Chats<span class="sub"><%= @sessions.size %>
|
|
8
|
+
<nav class="crumbs"><a href="/studio/agents">agents</a></nav>
|
|
9
|
+
<h1>Chats<span class="sub"><%= @chats_total || @sessions.size %> conversations</span></h1>
|
|
5
10
|
</div>
|
|
6
11
|
<div class="head-actions">
|
|
7
|
-
|
|
8
|
-
<a class="btn primary" href="/studio/playground">New conversation →</a>
|
|
12
|
+
<a class="btn primary" href="/studio/playground">New conversation</a>
|
|
9
13
|
</div>
|
|
10
14
|
</header>
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
<div class="
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
<div class="drill operation-workspace" data-controller="list-filter">
|
|
17
|
+
<%== render("_chats_master", locals: { active_id: nil }) %>
|
|
18
|
+
|
|
19
|
+
<turbo-frame id="session-detail" class="drill-pane drill-detail">
|
|
20
|
+
<div class="drill-pane-head">
|
|
21
|
+
<div class="title">
|
|
22
|
+
<strong>Pick a conversation</strong>
|
|
23
|
+
<span class="count">select on the left, or start a new one in the playground</span>
|
|
24
|
+
</div>
|
|
20
25
|
</div>
|
|
21
|
-
<div class="
|
|
22
|
-
<
|
|
23
|
-
<% @sessions.each do |s| %>
|
|
24
|
-
<% preview = session_preview(s) %>
|
|
25
|
-
<li data-list-filter-target="item" data-filter-text="<%= s.id %> <%= preview %>">
|
|
26
|
-
<a href="/studio/sessions/<%= Rack::Utils.escape(s.id) %>">
|
|
27
|
-
<span class="mono"><%= s.id[0, 12] %></span>
|
|
28
|
-
</a>
|
|
29
|
-
<span class="muted"><%= Array(s.messages).size %> msg · <%= s.updated_at %></span>
|
|
30
|
-
<% if preview %><span class="preview muted"><%= preview[0, 90] %></span><% end %>
|
|
31
|
-
</li>
|
|
32
|
-
<% end %>
|
|
33
|
-
</ul>
|
|
26
|
+
<div class="drill-empty">
|
|
27
|
+
<div><h2>Read the conversation</h2><p>Review messages, the latest briefing and stage history. New messages appear as they arrive.</p></div>
|
|
34
28
|
</div>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<% end %>
|
|
29
|
+
</turbo-frame>
|
|
30
|
+
</div>
|
|
@@ -1,53 +1,94 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
<%# Customer detail — the DETAIL pane of the Customers miller shell.
|
|
2
|
+
Full page (direct visit/refresh): the two-column shell — master list plus
|
|
3
|
+
this pane. Turbo-Frame request (a row click, a form submission): the pane alone,
|
|
4
|
+
so the master's scroll state and selection survive every interaction. %>
|
|
5
|
+
<%# Roda renders with plain locals — no local_assigns hash (Rails-ism). %>
|
|
6
|
+
<% frame_only = defined?(frame_only) && frame_only %>
|
|
7
|
+
<% unless frame_only %>
|
|
8
|
+
<header class="page-head operation-head">
|
|
9
|
+
<div>
|
|
10
|
+
<nav class="crumbs"><a href="/studio/customers<%= @agent ? "?agent=#{Rack::Utils.escape(@agent)}" : "" %>">customers</a></nav>
|
|
11
|
+
<h1><span class="mono"><%= @cell[:customer] || @cell[:scope] %></span><span class="sub">customer memory</span></h1>
|
|
12
|
+
</div>
|
|
13
|
+
</header>
|
|
14
|
+
|
|
15
|
+
<div class="drill operation-workspace" data-controller="list-filter">
|
|
16
|
+
<%== render("_customers_master", locals: { active_scope: @cell[:scope] }) %>
|
|
17
|
+
<% end %>
|
|
18
|
+
<turbo-frame id="customer-detail" class="drill-pane drill-detail operation-detail">
|
|
19
|
+
<%# A frame response never sees the layout, so the flash strip lives here
|
|
20
|
+
too — without it, save confirmations would vanish on frame submits
|
|
21
|
+
(the layout's copy only renders on full pages). %>
|
|
22
|
+
<% if request.env["HTTP_TURBO_FRAME"] == "customer-detail" %>
|
|
23
|
+
<% if flash["error"] %><div class="flash err" role="alert"><%= flash["error"] %></div><% end %>
|
|
24
|
+
<% if flash["notice"] %><div class="flash ok" role="status"><%= flash["notice"] %></div><% end %>
|
|
25
|
+
<% end %>
|
|
26
|
+
<div class="drill-pane-head">
|
|
27
|
+
<div class="title">
|
|
28
|
+
<strong class="mono"><%= @cell[:customer] || @cell[:scope] %></strong>
|
|
29
|
+
<span class="count"><%= @cell[:tenant] %> · <%= @facts.size %> saved facts</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="actions">
|
|
32
|
+
<form method="post" action="<%= customer_path(@cell[:scope]) %>/export" data-turbo="false">
|
|
33
|
+
<%== csrf_tag %>
|
|
34
|
+
<% if @agent %><input type="hidden" name="agent" value="<%= @agent %>"><% end %>
|
|
35
|
+
<button type="submit" class="btn btn-sm" data-turbo-submits-with="Exporting…">Export</button>
|
|
36
|
+
</form>
|
|
37
|
+
<form method="post" action="<%= customer_path(@cell[:scope]) %>/forget" data-turbo-frame="_top"
|
|
38
|
+
data-turbo-confirm="Forget <%= @cell[:customer] %>? Their memory cell AND their sessions are purged. This cannot be undone.">
|
|
39
|
+
<%== csrf_tag %>
|
|
40
|
+
<% if @agent %><input type="hidden" name="agent" value="<%= @agent %>"><% end %>
|
|
41
|
+
<button type="submit" class="btn btn-sm danger" data-turbo-submits-with="Forgetting…">Forget</button>
|
|
42
|
+
</form>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="drill-pane-body">
|
|
18
46
|
|
|
19
47
|
<section class="section">
|
|
20
48
|
<div class="section-head">
|
|
21
49
|
<h2>Facts</h2>
|
|
22
|
-
<p class="muted">
|
|
50
|
+
<p class="muted">Saved values used in future conversations. Expired facts are excluded.</p>
|
|
23
51
|
</div>
|
|
24
52
|
<div class="card">
|
|
25
53
|
<% if @facts.empty? %>
|
|
26
54
|
<p class="muted">No facts.</p>
|
|
27
55
|
<% else %>
|
|
28
|
-
<
|
|
56
|
+
<div class="operation-facts">
|
|
29
57
|
<% @facts.each do |f| %>
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
58
|
+
<article class="operation-fact">
|
|
59
|
+
<div class="operation-summary">
|
|
60
|
+
<div><h3><%= f.key %></h3><p class="operation-fact-value"><%= f.value %></p></div>
|
|
61
|
+
<form method="post" action="<%= customer_path(@cell[:scope]) %>/forget-fact"
|
|
62
|
+
data-turbo-confirm="Forget "<%= f.key %>"? The next turn stops seeing it.">
|
|
63
|
+
<%== csrf_tag %>
|
|
64
|
+
<% if @agent %><input type="hidden" name="agent" value="<%= @agent %>"><% end %>
|
|
65
|
+
<input type="hidden" name="key" value="<%= f.key %>">
|
|
66
|
+
<button type="submit" class="ghost danger" data-turbo-submits-with="Forgetting…">Forget</button>
|
|
67
|
+
</form>
|
|
68
|
+
</div>
|
|
69
|
+
<details class="operation-fact-source">
|
|
70
|
+
<summary>Source and dates</summary>
|
|
71
|
+
<p class="muted"><%= f.origin %> · saved <%= f.created_at %><%= f.expires_at ? " · expires #{f.expires_at}" : " · no expiry" %></p>
|
|
72
|
+
</details>
|
|
73
|
+
</article>
|
|
41
74
|
<% end %>
|
|
42
|
-
</
|
|
75
|
+
</div>
|
|
43
76
|
<% end %>
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
<details class="operation-fact-editor">
|
|
78
|
+
<summary>Add or update a fact</summary>
|
|
79
|
+
<p class="muted">Saving an existing key replaces its value for future conversations.</p>
|
|
80
|
+
<form method="post" action="<%= customer_path(@cell[:scope]) %>/fact">
|
|
81
|
+
<%== csrf_tag %>
|
|
82
|
+
<% if @agent %><input type="hidden" name="agent" value="<%= @agent %>"><% end %>
|
|
83
|
+
<div class="operation-fields">
|
|
84
|
+
<label for="customer-fact-key">Key<input id="customer-fact-key" type="text" name="key" placeholder="e.g. size" required></label>
|
|
85
|
+
<label for="customer-fact-value">Value<input id="customer-fact-value" type="text" name="value" required></label>
|
|
86
|
+
</div>
|
|
87
|
+
<label for="customer-fact-expiry">Expires at <span class="muted">(optional, ISO8601)</span></label>
|
|
88
|
+
<input id="customer-fact-expiry" type="text" name="expires_at" placeholder="2026-12-31T23:59:59Z">
|
|
89
|
+
<button type="submit" class="primary" data-turbo-submits-with="Saving…">Save fact</button>
|
|
90
|
+
</form>
|
|
91
|
+
</details>
|
|
51
92
|
</div>
|
|
52
93
|
</section>
|
|
53
94
|
|
|
@@ -68,10 +109,9 @@
|
|
|
68
109
|
</div>
|
|
69
110
|
</section>
|
|
70
111
|
|
|
71
|
-
<
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
</div>
|
|
112
|
+
<details class="card operation-diagnostics">
|
|
113
|
+
<summary>Audit history</summary>
|
|
114
|
+
<p class="muted">Operator changes are recorded as digests, without storing the content.</p>
|
|
75
115
|
<div class="card">
|
|
76
116
|
<% if @audit.empty? %>
|
|
77
117
|
<p class="muted">No operator mutations recorded.</p>
|
|
@@ -91,4 +131,9 @@
|
|
|
91
131
|
</ul>
|
|
92
132
|
<% end %>
|
|
93
133
|
</div>
|
|
94
|
-
</
|
|
134
|
+
</details>
|
|
135
|
+
</div>
|
|
136
|
+
</turbo-frame>
|
|
137
|
+
<% unless frame_only %>
|
|
138
|
+
</div>
|
|
139
|
+
<% end %>
|
|
@@ -1,32 +1,22 @@
|
|
|
1
|
-
<header class="page-head">
|
|
2
|
-
<
|
|
3
|
-
<nav class="crumbs"><span>customers</span></nav>
|
|
4
|
-
<h1>Customers<span class="sub"><%= @rows.size %> with memory</span></h1>
|
|
5
|
-
</div>
|
|
1
|
+
<header class="page-head operation-head">
|
|
2
|
+
<h1>Customers<span class="sub"><%= @rows.size %> with memory</span></h1>
|
|
6
3
|
<div class="head-actions">
|
|
7
4
|
<%== agent_filter_form("/studio/customers", @agent) %>
|
|
8
5
|
</div>
|
|
9
6
|
</header>
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
<div class="
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
</div>
|
|
20
|
-
<div class="card">
|
|
21
|
-
<ul class="history">
|
|
22
|
-
<% rows.each do |row| %>
|
|
23
|
-
<li>
|
|
24
|
-
<a href="<%= customer_path(row[:cell][:scope]) %>"><span class="mono"><%= row[:cell][:customer] %></span></a>
|
|
25
|
-
<span class="preview muted"><%= row[:count] %> fact(s)</span>
|
|
26
|
-
</li>
|
|
27
|
-
<% end %>
|
|
28
|
-
</ul>
|
|
8
|
+
<div class="drill operation-workspace" data-controller="list-filter">
|
|
9
|
+
<%== render("_customers_master", locals: { active_scope: nil }) %>
|
|
10
|
+
|
|
11
|
+
<turbo-frame id="customer-detail" class="drill-pane drill-detail">
|
|
12
|
+
<div class="drill-pane-head">
|
|
13
|
+
<div class="title">
|
|
14
|
+
<strong>Pick a customer</strong>
|
|
15
|
+
<span class="count">review memory used in future conversations</span>
|
|
29
16
|
</div>
|
|
30
|
-
</
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
</div>
|
|
18
|
+
<div class="drill-empty">
|
|
19
|
+
<div><h2>Customer memory</h2><p>Select a customer to read saved facts and notes, add a fact or review changes.</p></div>
|
|
20
|
+
</div>
|
|
21
|
+
</turbo-frame>
|
|
22
|
+
</div>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
<div class="reports-page reports-evals">
|
|
1
2
|
<header class="page-head">
|
|
2
3
|
<div>
|
|
3
|
-
|
|
4
|
-
<h1>Evals<span class="sub"
|
|
4
|
+
<%== crumbs_for("Evals") %>
|
|
5
|
+
<h1>Evals<span class="sub">Cases that check your agents</span></h1>
|
|
5
6
|
</div>
|
|
6
7
|
<div class="head-actions">
|
|
7
8
|
<%== agent_filter_form("/studio/evals", @filter) %>
|
|
8
|
-
<a class="btn" href="/studio/
|
|
9
|
+
<a class="btn" href="/studio/evals<%= "?agent=#{Rack::Utils.escape(@filter)}" if @filter %>">New case</a>
|
|
10
|
+
<a class="btn" href="/studio/settings?s=evals">Configure judges</a>
|
|
9
11
|
</div>
|
|
10
12
|
</header>
|
|
11
13
|
|
|
@@ -17,9 +19,12 @@
|
|
|
17
19
|
</div>
|
|
18
20
|
<% end %>
|
|
19
21
|
|
|
20
|
-
<div class="drill">
|
|
22
|
+
<div class="drill report-workspace" data-controller="list-filter">
|
|
21
23
|
<aside class="drill-pane drill-master">
|
|
22
|
-
<div class="drill-pane-head"><div class="title"><strong>Cases</strong></div></div>
|
|
24
|
+
<div class="drill-pane-head"><div class="title"><strong>Cases <span class="count"><%= @cases.size %> case(s)</span></strong></div></div>
|
|
25
|
+
<div class="drill-search">
|
|
26
|
+
<input type="search" class="filter-input" placeholder="Search cases…" aria-label="Filter cases by ID, agent or opening message" data-list-filter-target="query" data-action="input->list-filter#filter keydown->list-filter#clear">
|
|
27
|
+
</div>
|
|
23
28
|
<nav class="drill-pane-body drill-list" aria-label="eval cases">
|
|
24
29
|
<% if @cases.empty? %>
|
|
25
30
|
<div class="empty">No case yet. Seed the corpus with <code>insika evals:import</code>, or write one on the right.</div>
|
|
@@ -27,12 +32,13 @@
|
|
|
27
32
|
<% @by_agent.each do |agent, cases| %>
|
|
28
33
|
<div class="nav-label"><%= agent %></div>
|
|
29
34
|
<% cases.each do |g| %>
|
|
30
|
-
<a class="drill-item<%== ' active' if @case && @case.id == g.id %>" href="/studio/evals
|
|
35
|
+
<a class="drill-item<%== ' active' if @case && @case.id == g.id %>" href="/studio/evals?<%= Rack::Utils.build_query({ "id" => g.id, "agent" => @filter }.compact) %>" data-list-filter-target="item" data-filter-text="<%= g.id %> <%= agent %> <%= g.opens_with %>"<%== ' aria-current="page"' if @case && @case.id == g.id %>>
|
|
31
36
|
<div class="drill-item-name"><span class="txt mono"><%= g.id %></span><% if g.rubric.to_s.strip.empty? %><span class="pill info plain">no rubric</span><% end %><% if g.simulated? %><span class="pill accent plain">simulated</span><% end %></div>
|
|
32
37
|
<div class="drill-item-sub"><%= g.opens_with.to_s %></div>
|
|
33
38
|
</a>
|
|
34
39
|
<% end %>
|
|
35
40
|
<% end %>
|
|
41
|
+
<% if @cases.any? %><div class="empty" data-list-filter-target="empty" hidden>No cases match your search.</div><% end %>
|
|
36
42
|
</nav>
|
|
37
43
|
</aside>
|
|
38
44
|
|
|
@@ -51,36 +57,48 @@
|
|
|
51
57
|
</div>
|
|
52
58
|
</div>
|
|
53
59
|
<div class="drill-pane-body">
|
|
54
|
-
<p class="muted">
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
judge scores; <code>tools_called</code> and <code>must_not</code> are checked
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
<p class="muted">Define the conversation and the behavior you expect. Saving validates the case before adding it to your corpus.</p>
|
|
61
|
+
<details class="report-details">
|
|
62
|
+
<summary>Case format and scoring</summary>
|
|
63
|
+
<p>Cases use the same YAML as the corpus files. <code>expect.rubric</code> describes what the judge scores; <code>tools_called</code> and <code>must_not</code> are checked without a model.</p>
|
|
64
|
+
<p>Import cases with <code>insika evals:import</code>. Configure judges in Settings.</p>
|
|
65
|
+
</details>
|
|
66
|
+
<%
|
|
67
|
+
# Erubi's tag scanner is a plain text scanner: it has no notion of Ruby
|
|
68
|
+
# comments, strings or heredocs, so it closes a code tag on the first
|
|
69
|
+
# literal percent-angle-bracket close marker it sees, wherever it is.
|
|
70
|
+
# A heredoc opened here therefore has to open, hold its whole body and
|
|
71
|
+
# close ALL inside this one tag, with that close marker appearing
|
|
72
|
+
# nowhere in between (not even in a comment) - otherwise the template
|
|
73
|
+
# text and its compiled buffer-append boilerplate between two tags
|
|
74
|
+
# gets spliced into the middle of the heredoc's own string value,
|
|
75
|
+
# which is how compiler internals ended up leaking into this textarea.
|
|
76
|
+
default_case_yaml = <<~YAML
|
|
77
|
+
id: my-agent-coupon
|
|
78
|
+
agent: my-agent
|
|
79
|
+
turns:
|
|
80
|
+
- user: "tem cupom de desconto?"
|
|
81
|
+
expect:
|
|
82
|
+
tools_called:
|
|
83
|
+
- search_voucher
|
|
84
|
+
must_not:
|
|
85
|
+
- pii_leak
|
|
86
|
+
- tool_error
|
|
87
|
+
rubric: |
|
|
88
|
+
Consults the active coupon with the tool and says what it is — does NOT
|
|
89
|
+
invent a code or a discount. If there is none, says so kindly.
|
|
90
|
+
min_score: 0.7
|
|
91
|
+
YAML
|
|
92
|
+
%>
|
|
93
|
+
<form id="set-golden" method="post" action="/studio/evals" data-controller="dirty-guard">
|
|
62
94
|
<%== csrf_tag %>
|
|
63
95
|
<input type="hidden" name="id" value="<%= @case&.id %>">
|
|
64
96
|
<label>case (YAML)
|
|
65
|
-
<textarea name="yaml" rows="22" spellcheck="false" class="mono"><%= @case_yaml ||
|
|
66
|
-
id: my-agent-coupon
|
|
67
|
-
agent: my-agent
|
|
68
|
-
turns:
|
|
69
|
-
- user: "tem cupom de desconto?"
|
|
70
|
-
expect:
|
|
71
|
-
tools_called:
|
|
72
|
-
- search_voucher
|
|
73
|
-
must_not:
|
|
74
|
-
- pii_leak
|
|
75
|
-
- tool_error
|
|
76
|
-
rubric: |
|
|
77
|
-
Consults the active coupon with the tool and says what it is — does NOT
|
|
78
|
-
invent a code or a discount. If there is none, says so kindly.
|
|
79
|
-
min_score: 0.7
|
|
80
|
-
YAML
|
|
81
|
-
</textarea>
|
|
97
|
+
<textarea name="yaml" rows="22" spellcheck="false" class="mono"><%= @case_yaml || default_case_yaml %></textarea>
|
|
82
98
|
</label>
|
|
83
99
|
</form>
|
|
84
100
|
</div>
|
|
85
101
|
</section>
|
|
86
102
|
</div>
|
|
103
|
+
|
|
104
|
+
</div>
|