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
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
["general", "General", "streaming, timeouts"],
|
|
5
5
|
["models", "Model defaults", "platform model layer (Chat › Agent › platform)"],
|
|
6
6
|
["edge", "Edge limits", "rate-limit per chat, token ceiling per agent"],
|
|
7
|
+
["burst", "Burst policy", "what happens to a message that lands on a busy session"],
|
|
8
|
+
["agents", "Agent defaults", "what an agent with no value of its own runs with"],
|
|
7
9
|
["evals", "Evals", "who grades a rubric, and how a panel agrees"],
|
|
8
10
|
["llm", "LLM providers", "keys, base URL, models per provider"],
|
|
9
11
|
["demo", "Demo data", "see every loop working at once"]
|
|
10
12
|
] %>
|
|
13
|
+
<header class="page-head">
|
|
14
|
+
<h1>Settings<span class="sub">Platform defaults · agents can override supported settings</span></h1>
|
|
15
|
+
</header>
|
|
11
16
|
<div class="drill">
|
|
12
17
|
<aside class="drill-pane drill-master">
|
|
13
18
|
<div class="drill-pane-head"><div class="title"><strong>Settings</strong></div></div>
|
|
@@ -25,11 +30,11 @@
|
|
|
25
30
|
<% if @section == "general" %>
|
|
26
31
|
<div class="drill-pane-head">
|
|
27
32
|
<div class="title"><strong>General</strong></div>
|
|
28
|
-
<button type="submit" form="set-general" class="btn primary" data-turbo-submits-with="Saving…">Save</button>
|
|
33
|
+
<button type="submit" form="set-general" class="btn primary" data-turbo-submits-with="Saving…">Save general settings</button>
|
|
29
34
|
</div>
|
|
30
35
|
<div class="drill-pane-body">
|
|
31
|
-
<p class="muted">
|
|
32
|
-
<form id="set-general" method="post" action="/studio/settings">
|
|
36
|
+
<p class="muted">Save these platform defaults to apply them on future turns.</p>
|
|
37
|
+
<form id="set-general" method="post" action="/studio/settings" data-controller="dirty-guard">
|
|
33
38
|
<%== csrf_tag %>
|
|
34
39
|
<label class="check">
|
|
35
40
|
<input type="checkbox" name="streaming" value="1"<%== " checked" if @settings["streaming"] %>>
|
|
@@ -43,7 +48,8 @@
|
|
|
43
48
|
<label>Memory TTL (days)<input type="text" name="memory_ttl_days" value="<%= @settings["memory_ttl_days"] %>" inputmode="numeric" placeholder="blank = off"></label>
|
|
44
49
|
</div>
|
|
45
50
|
<p class="muted">Memory TTL: how many days a customer memory cell lives before the daily sweep prunes it (per-fact <code>expires_at</code> overrides always win). Editing here sets the platform default for <strong>every</strong> cell; a per-tenant map authored in the settings record is replaced by this save.</p>
|
|
46
|
-
<
|
|
51
|
+
<details class="newfile">
|
|
52
|
+
<summary>Conversation compaction</summary>
|
|
47
53
|
<label class="check">
|
|
48
54
|
<input type="checkbox" name="compaction_enabled" value="1"<%== " checked" if @settings.dig("compaction", "enabled") %>>
|
|
49
55
|
in-session compaction — summarize old turns past the threshold
|
|
@@ -52,50 +58,58 @@
|
|
|
52
58
|
<label>keep_last <span class="muted">(messages kept verbatim)</span><input type="text" name="compaction_keep_last" value="<%= @settings.dig("compaction", "keep_last") %>" inputmode="numeric"></label>
|
|
53
59
|
<label>compact_after <span class="muted">(uncompacted messages that trigger it)</span><input type="text" name="compaction_compact_after" value="<%= @settings.dig("compaction", "compact_after") %>" inputmode="numeric"></label>
|
|
54
60
|
</div>
|
|
55
|
-
<label>compaction model <span class="muted">(blank = the platform utility_model)</span
|
|
61
|
+
<label>compaction model <span class="muted">(blank = the platform utility_model)</span><%== model_select("compaction_model_ref", @settings.dig("compaction", "model"), "platform utility_model") %></label>
|
|
56
62
|
<p class="muted">When a session's uncompacted transcript grows past <code>compact_after</code> messages, everything but the last <code>keep_last</code> is summarized by the cheap model into one <code><conversation_summary></code> fragment; the tail stays verbatim and the boundary is stable (the prompt cache holds after it). Runs post-turn, off the critical path.</p>
|
|
63
|
+
</details>
|
|
57
64
|
</form>
|
|
58
65
|
</div>
|
|
59
66
|
|
|
60
67
|
<% elsif @section == "models" %>
|
|
61
68
|
<div class="drill-pane-head">
|
|
62
69
|
<div class="title"><strong>Model defaults</strong></div>
|
|
63
|
-
<button type="submit" form="set-models" class="btn primary" data-turbo-submits-with="Saving…">Save</button>
|
|
70
|
+
<button type="submit" form="set-models" class="btn primary" data-turbo-submits-with="Saving…">Save model defaults</button>
|
|
64
71
|
</div>
|
|
65
72
|
<div class="drill-pane-body">
|
|
66
73
|
<p class="muted">The platform model layer, resolved at turn start as <strong>Chat pin › Agent model › platform default</strong>. An agent with no model of its own inherits <code>default_model</code>.</p>
|
|
67
|
-
<form id="set-models" method="post" action="/studio/settings/models">
|
|
74
|
+
<form id="set-models" method="post" action="/studio/settings/models" data-controller="dirty-guard">
|
|
68
75
|
<%== csrf_tag %>
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
</
|
|
73
|
-
<
|
|
74
|
-
|
|
76
|
+
<p class="muted">The lists below are the models of the configured LLM providers — add a provider or a model there to see it here.</p>
|
|
77
|
+
<label>default_model <span class="muted">(agents without their own model)</span>
|
|
78
|
+
<%== model_select("default_model_ref", model_ref_of(@settings["default_model"], @settings["default_provider"]), "none") %>
|
|
79
|
+
</label>
|
|
80
|
+
<fieldset class="subgroup">
|
|
81
|
+
<legend>fallback_models <span class="muted">— tried in this order; blank slots are skipped</span></legend>
|
|
82
|
+
<%== model_slots("fallback_refs", @settings["fallback_models"], min: 5) %>
|
|
83
|
+
</fieldset>
|
|
84
|
+
<label>utility_model <span class="muted">(cheap internal tasks — titles, distillation)</span>
|
|
85
|
+
<%== model_select("utility_model_ref", @settings["utility_model"], "none") %>
|
|
86
|
+
</label>
|
|
75
87
|
<label>reasoning <span class="muted">(global default — off/on/effort; blank = provider default)</span>
|
|
76
88
|
<%== thinking_select("thinking", @settings["thinking"], "provider default") %>
|
|
77
89
|
</label>
|
|
78
90
|
</form>
|
|
79
91
|
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
<
|
|
92
|
+
<details class="newfile">
|
|
93
|
+
<summary>Reasoning overrides by model</summary>
|
|
94
|
+
<p class="muted">Saved separately from platform model defaults. Per-model reasoning defaults — the layer between global and agent (resolved <strong>Chat › Agent › Model › Global</strong>). One per line.</p>
|
|
95
|
+
<form id="set-model-params" method="post" action="/studio/settings/model-params" data-controller="dirty-guard">
|
|
83
96
|
<%== csrf_tag %>
|
|
84
97
|
<label>model_params <span class="muted">(one per line: <code>provider/model | off|on|low|medium|high</code>)</span>
|
|
85
98
|
<textarea name="model_params" rows="4" placeholder="deepseek/deepseek-v4-flash | off"><%= model_params_text(@settings["model_params"]) %></textarea>
|
|
86
99
|
</label>
|
|
87
100
|
<div class="actions"><button type="submit" class="btn primary" data-turbo-submits-with="Saving…">Save per-model</button></div>
|
|
88
101
|
</form>
|
|
102
|
+
</details>
|
|
89
103
|
</div>
|
|
90
104
|
|
|
91
105
|
<% elsif @section == "edge" %>
|
|
92
106
|
<div class="drill-pane-head">
|
|
93
107
|
<div class="title"><strong>Edge limits</strong></div>
|
|
94
|
-
<button type="submit" form="set-edge" class="btn primary" data-turbo-submits-with="Saving…">Save</button>
|
|
108
|
+
<button type="submit" form="set-edge" class="btn primary" data-turbo-submits-with="Saving…">Save edge limits</button>
|
|
95
109
|
</div>
|
|
96
110
|
<div class="drill-pane-body">
|
|
97
111
|
<p class="muted">The named place where volume/cost abuse is cut, <strong>before any LLM spend</strong>. Blank/0 = off (opt-in). A breached turn completes gracefully with the safe reply below. Per-agent overrides live in each agent's config.</p>
|
|
98
|
-
<form id="set-edge" method="post" action="/studio/settings/edge">
|
|
112
|
+
<form id="set-edge" method="post" action="/studio/settings/edge" data-controller="dirty-guard">
|
|
99
113
|
<%== csrf_tag %>
|
|
100
114
|
<div class="grid-2">
|
|
101
115
|
<label>chat_rate_limit <span class="muted">(turn attempts per chat per window)</span><input type="text" name="chat_rate_limit" value="<%= @settings.dig("edge", "chat_rate_limit") %>" inputmode="numeric" placeholder="off"></label>
|
|
@@ -107,14 +121,64 @@
|
|
|
107
121
|
</form>
|
|
108
122
|
</div>
|
|
109
123
|
|
|
124
|
+
<% elsif @section == "burst" %>
|
|
125
|
+
<div class="drill-pane-head">
|
|
126
|
+
<div class="title"><strong>Burst policy</strong></div>
|
|
127
|
+
<button type="submit" form="set-queue" class="btn primary" data-turbo-submits-with="Saving…">Save burst policy</button>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="drill-pane-body">
|
|
130
|
+
<p class="muted">What happens to a message that lands on a session that is <strong>already busy</strong>: <code>followup</code> waits in line (today's behavior), <code>collect</code> merges fragments that arrived before the turn started, <code>steer</code> also appends into the run in flight at a tool boundary, <code>interrupt</code> abandons it. This is the <strong>platform</strong> answer — every agent still overrides it in its own config, and a session var pins one conversation. Blank = no platform default (falls through to followup / no window / steer_max 5).</p>
|
|
131
|
+
<form id="set-queue" method="post" action="/studio/settings/queue" data-controller="dirty-guard">
|
|
132
|
+
<%== csrf_tag %>
|
|
133
|
+
<label>queue_mode
|
|
134
|
+
<select name="queue_mode">
|
|
135
|
+
<option value=""<%== " selected" if @settings.dig("queue", "queue_mode").nil? %>>— none (followup)</option>
|
|
136
|
+
<% Insika::QueuePolicy::MODES.each do |mode| %>
|
|
137
|
+
<option value="<%= mode %>"<%== " selected" if @settings.dig("queue", "queue_mode").to_s == mode.to_s %>><%= mode %></option>
|
|
138
|
+
<% end %>
|
|
139
|
+
</select>
|
|
140
|
+
</label>
|
|
141
|
+
<div class="grid-2">
|
|
142
|
+
<label>debounce_ms <span class="muted">(quiet window at the door before the turn starts; blank/0 = none)</span><input type="text" name="debounce_ms" value="<%= @settings.dig("queue", "debounce_ms") %>" inputmode="numeric" placeholder="0"></label>
|
|
143
|
+
<label>debounce_max_ms <span class="muted">(ceiling on the TOTAL wait; blank = 10000)</span><input type="text" name="debounce_max_ms" value="<%= @settings.dig("queue", "debounce_max_ms") %>" inputmode="numeric" placeholder="10000"></label>
|
|
144
|
+
<label>steer_max_messages <span class="muted">(messages ONE running turn may absorb; blank = 5, 0 = never steer)</span><input type="text" name="steer_max_messages" value="<%= @settings.dig("queue", "steer_max_messages") %>" inputmode="numeric" placeholder="5"></label>
|
|
145
|
+
</div>
|
|
146
|
+
<label>steer_join <span class="muted">(template framing an absorbed message — at the door and mid-run; must carry <%= Insika::QueuePolicy::JOIN_PLACEHOLDER %>; blank = the raw text)</span><input type="text" name="steer_join" value="<%= @settings.dig("queue", "steer_join") %>" placeholder="the customer just added: <%= Insika::QueuePolicy::JOIN_PLACEHOLDER %>"></label>
|
|
147
|
+
</form>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<% elsif @section == "agents" %>
|
|
151
|
+
<div class="drill-pane-head">
|
|
152
|
+
<div class="title"><strong>Agent defaults</strong></div>
|
|
153
|
+
<button type="submit" form="set-agent-defaults" class="btn primary" data-turbo-submits-with="Saving…">Save agent defaults</button>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="drill-pane-body">
|
|
156
|
+
<p class="muted">An agent with no value of its own for one of these sections runs with this one. An agent that sets the section keeps its own, whole. In an agent's config, the <em>use the platform default</em> box says which one applies.</p>
|
|
157
|
+
<form id="set-agent-defaults" method="post" action="/studio/settings/agent-defaults" data-controller="dirty-guard">
|
|
158
|
+
<%== csrf_tag %>
|
|
159
|
+
<%== render("_cfg_reliability") %>
|
|
160
|
+
<%== render("_cfg_budget") %>
|
|
161
|
+
<%== render("_agent_config_retrieval") %>
|
|
162
|
+
<%== render("_cfg_guardrails") %>
|
|
163
|
+
<%== render("_cfg_grounding") %>
|
|
164
|
+
<%== render("_cfg_followup") %>
|
|
165
|
+
<%== render("_cfg_distill") %>
|
|
166
|
+
<%== render("_cfg_harvest") %>
|
|
167
|
+
<fieldset class="subgroup">
|
|
168
|
+
<legend>alerts</legend>
|
|
169
|
+
<%== render("_cfg_alerts") %>
|
|
170
|
+
</fieldset>
|
|
171
|
+
</form>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
110
174
|
<% elsif @section == "evals" %>
|
|
111
175
|
<div class="drill-pane-head">
|
|
112
176
|
<div class="title"><strong>Evals</strong></div>
|
|
113
|
-
<button type="submit" form="set-evals" class="btn primary" data-turbo-submits-with="Saving…">Save</button>
|
|
177
|
+
<button type="submit" form="set-evals" class="btn primary" data-turbo-submits-with="Saving…">Save eval settings</button>
|
|
114
178
|
</div>
|
|
115
179
|
<div class="drill-pane-body">
|
|
116
180
|
<p class="muted">Who grades a <a href="/studio/evals">case</a>'s rubric. <strong>One model per line</strong> as <code>provider/model</code> — several lines make a PANEL, and two different models disagreeing is the signal worth having (sampling one model N times mostly measures its own variance). Empty = deterministic assertions only, and rubric'd cases read as <code>judge_pending</code>.</p>
|
|
117
|
-
<form id="set-evals" method="post" action="/studio/settings/evals">
|
|
181
|
+
<form id="set-evals" method="post" action="/studio/settings/evals" data-controller="dirty-guard">
|
|
118
182
|
<%== csrf_tag %>
|
|
119
183
|
<label>judges<textarea name="judges" rows="4" spellcheck="false" placeholder="deepseek/deepseek-v4-flash openai/gpt-5-mini"><%= Array(@settings.dig("evals", "judges")).map { |j| [j["provider"], j["model"]].compact.reject(&:empty?).join("/") }.join("\n") %></textarea></label>
|
|
120
184
|
<div class="grid-2">
|
|
@@ -185,7 +249,7 @@
|
|
|
185
249
|
<button type="submit" class="ghost danger" data-turbo-submits-with="Removing…">Remove</button>
|
|
186
250
|
</form>
|
|
187
251
|
</div>
|
|
188
|
-
<form method="post" action="/studio/settings/providers">
|
|
252
|
+
<form method="post" action="/studio/settings/providers" data-controller="dirty-guard">
|
|
189
253
|
<%== csrf_tag %>
|
|
190
254
|
<input type="hidden" name="api" value="<%= p["api"] %>">
|
|
191
255
|
<div class="grid-2">
|
|
@@ -201,7 +265,7 @@
|
|
|
201
265
|
|
|
202
266
|
<details class="card newfile"<%== " open" if @providers.empty? %>>
|
|
203
267
|
<summary>+ new provider</summary>
|
|
204
|
-
<form method="post" action="/studio/settings/providers">
|
|
268
|
+
<form method="post" action="/studio/settings/providers" data-controller="dirty-guard">
|
|
205
269
|
<%== csrf_tag %>
|
|
206
270
|
<div class="grid-2">
|
|
207
271
|
<label>api <span class="muted">(slug, e.g. deepseek)</span><input type="text" name="api" placeholder="openai" required></label>
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
agent's own version of the skill, not the shared one. %>
|
|
5
5
|
<% scope = @scope_agent %>
|
|
6
6
|
<% form_action = scope ? "/studio/agents/#{Rack::Utils.escape(scope)}/skills/#{Rack::Utils.escape(@selected.to_s)}" : "/studio/skills" %>
|
|
7
|
+
<header class="page-head">
|
|
8
|
+
<h1>Skills<span class="sub">Reusable instructions and agent availability</span></h1>
|
|
9
|
+
</header>
|
|
7
10
|
<div class="drill" data-controller="list-filter">
|
|
8
11
|
<aside class="drill-pane drill-master">
|
|
9
12
|
<div class="drill-pane-head">
|
|
@@ -13,15 +16,26 @@
|
|
|
13
16
|
<div class="drill-search">
|
|
14
17
|
<input type="search" class="filter-input" placeholder="Search skills…" aria-label="Filter skills"
|
|
15
18
|
data-list-filter-target="query" data-action="input->list-filter#filter keydown->list-filter#clear">
|
|
19
|
+
<label class="switch" title="show only skills no agent can load">
|
|
20
|
+
<input type="checkbox" id="only-unused">
|
|
21
|
+
<span class="switch-track" aria-hidden="true"></span>
|
|
22
|
+
<span class="switch-label">only unused</span>
|
|
23
|
+
</label>
|
|
16
24
|
</div>
|
|
17
25
|
<nav class="drill-pane-body drill-list" aria-label="skills">
|
|
18
|
-
<% @
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
<% @skill_groups.each do |label, skills| %>
|
|
27
|
+
<details class="drill-group" data-list-filter-target="group" open>
|
|
28
|
+
<summary class="drill-group-label"><%= label %> <span class="count"><%= skills.size %></span></summary>
|
|
29
|
+
<% skills.each do |s| %>
|
|
30
|
+
<% users = @skill_reach[s.name].size %>
|
|
31
|
+
<a class="drill-item<%== ' active' if @selected == s.name %>"
|
|
32
|
+
href="/studio/skills/<%= Rack::Utils.escape(s.name) %>"<%== " data-unused" if users.zero? %>
|
|
33
|
+
data-list-filter-target="item" data-filter-text="<%= s.name %> <%= s.description %>">
|
|
34
|
+
<div class="drill-item-name"><span class="txt" title="<%= s.name %>"><%= s.name %></span><% if @stored.include?(s.name) %><span class="pill info">authored</span><% end %></div>
|
|
35
|
+
<div class="drill-item-sub"><span<%== ' class="warn-text"' if users.zero? %>><%= users %> <%= users == 1 ? "agent" : "agents" %></span> · <%= s.description %></div>
|
|
36
|
+
</a>
|
|
37
|
+
<% end %>
|
|
38
|
+
</details>
|
|
25
39
|
<% end %>
|
|
26
40
|
<div class="empty" data-list-filter-target="empty" hidden>No skills match your filter.</div>
|
|
27
41
|
</nav>
|
|
@@ -67,12 +81,36 @@
|
|
|
67
81
|
<input type="text" name="name" placeholder="my-skill" required>
|
|
68
82
|
</label>
|
|
69
83
|
<% end %>
|
|
70
|
-
<
|
|
84
|
+
<p class="muted"><%= scope ? "Saving updates this agent’s version only." : "Saving updates the shared skill. Agent availability is saved separately below." %></p>
|
|
85
|
+
<label for="skill-content">SKILL.md <span class="muted">YAML frontmatter (<code>name</code>, <code>description</code>) + body</span></label>
|
|
71
86
|
<div data-controller="code-editor" data-code-editor-language-value="markdown" data-code-editor-preview-value="true">
|
|
72
|
-
<textarea name="content" data-code-editor-target="source" rows="18"><%= @skill_content %></textarea>
|
|
87
|
+
<textarea id="skill-content" name="content" data-code-editor-target="source" rows="18"><%= @skill_content %></textarea>
|
|
73
88
|
</div>
|
|
74
89
|
</form>
|
|
75
90
|
|
|
91
|
+
<%# A version was in use from its save until the next one: match a turn's
|
|
92
|
+
time against these to see the text that turn ran. %>
|
|
93
|
+
<% if Array(@skill_versions).size > 1 %>
|
|
94
|
+
<details class="drill-section versions">
|
|
95
|
+
<summary>Versions <span class="count"><%= @skill_versions.size %></span></summary>
|
|
96
|
+
<% restore_action = scope ? "/studio/agents/#{Rack::Utils.escape(scope)}/skills/#{Rack::Utils.escape(@selected.to_s)}/restore" : "/studio/skills/#{Rack::Utils.escape(@selected.to_s)}/restore" %>
|
|
97
|
+
<% @skill_versions.each_with_index do |v, k| %>
|
|
98
|
+
<details class="versions">
|
|
99
|
+
<summary class="mono"><%= v["from"] || "?" %> → <%= v["to"] || "now" %></summary>
|
|
100
|
+
<pre><%= v["content"] %></pre>
|
|
101
|
+
<% unless k.zero? %>
|
|
102
|
+
<%# k - 1: the list leads with the current text, the store's history does not %>
|
|
103
|
+
<form method="post" action="<%= restore_action %>">
|
|
104
|
+
<%== csrf_tag %>
|
|
105
|
+
<input type="hidden" name="version" value="<%= k - 1 %>">
|
|
106
|
+
<button type="submit" class="ghost" data-turbo-confirm="Restore this version? The current text stays in the history.">Restore</button>
|
|
107
|
+
</form>
|
|
108
|
+
<% end %>
|
|
109
|
+
</details>
|
|
110
|
+
<% end %>
|
|
111
|
+
</details>
|
|
112
|
+
<% end %>
|
|
113
|
+
|
|
76
114
|
<% if !new && !scope %>
|
|
77
115
|
<div class="drill-section">
|
|
78
116
|
<div class="drill-section-head">
|
|
@@ -81,7 +119,7 @@
|
|
|
81
119
|
is in the prompt every turn. Both are the AGENT's decision, so one shared skill can be
|
|
82
120
|
always-on for one and discretionary for another. An "all" allowlist is left untouched.</span>
|
|
83
121
|
</div>
|
|
84
|
-
<form method="post" action="/studio/skills/<%= Rack::Utils.escape(@selected) %>/agents">
|
|
122
|
+
<form method="post" action="/studio/skills/<%= Rack::Utils.escape(@selected) %>/agents" data-controller="dirty-guard">
|
|
85
123
|
<%== csrf_tag %>
|
|
86
124
|
<% if @agents.empty? %>
|
|
87
125
|
<p class="muted">No agents configured.</p>
|
|
@@ -117,21 +155,17 @@
|
|
|
117
155
|
edit the allowlist, which throws the sharing away. %>
|
|
118
156
|
<% others = @agents.reject { |a| Array((@specialized || {})[@selected]).include?(a.id) } %>
|
|
119
157
|
<% if others.any? %>
|
|
120
|
-
<
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
<span class="muted">give ONE agent its own version of this skill, seeded from the body above — same name, no fork</span>
|
|
124
|
-
</div>
|
|
158
|
+
<details class="drill-section newfile">
|
|
159
|
+
<summary>Specialize for one agent</summary>
|
|
160
|
+
<p class="muted">Create an agent-specific version from the saved shared skill. Save edits above before specializing.</p>
|
|
125
161
|
<form method="post" action="/studio/skills/<%= Rack::Utils.escape(@selected) %>/specialize">
|
|
126
162
|
<%== csrf_tag %>
|
|
127
163
|
<label>agent
|
|
128
|
-
|
|
129
|
-
<% others.each do |a| %><option value="<%= a.id %>"><%= a.id %></option><% end %>
|
|
130
|
-
</select>
|
|
164
|
+
<%== render("_agent_picker", locals: { name: "agent_id", options: others.map(&:id), value: nil }) %>
|
|
131
165
|
</label>
|
|
132
166
|
<div class="actions"><button type="submit" data-turbo-submits-with="Creating…">Specialize for this agent</button></div>
|
|
133
167
|
</form>
|
|
134
|
-
</
|
|
168
|
+
</details>
|
|
135
169
|
<% end %>
|
|
136
170
|
<% end %>
|
|
137
171
|
</div>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<header class="page-head">
|
|
2
2
|
<div>
|
|
3
|
-
<nav class="crumbs"><a href="/studio/agents">agents</a
|
|
3
|
+
<nav class="crumbs"><a href="/studio/agents">agents</a></nav>
|
|
4
4
|
<h1>System files<span class="sub"><%= @system_files.size %> file(s) · apply to every agent</span></h1>
|
|
5
5
|
</div>
|
|
6
6
|
</header>
|
|
7
7
|
|
|
8
|
-
<p class="muted matrix-hint">
|
|
8
|
+
<p class="muted matrix-hint">Rules shared by <strong>every agent</strong>, loaded before each agent’s own prompts. Save each file separately; previous versions can be restored below.</p>
|
|
9
9
|
|
|
10
10
|
<% if @system_files.empty? %>
|
|
11
11
|
<div class="empty">No system files. Create one below (e.g. <code>HOUSE_RULES.md</code>) for a rule that applies to every agent.</div>
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
<form method="post" action="/studio/system-files" data-controller="dirty-guard">
|
|
25
25
|
<%== csrf_tag %>
|
|
26
26
|
<input type="hidden" name="file" value="<%= sf[:name] %>">
|
|
27
|
-
<div data-controller="code-editor" data-code-editor-language-value="markdown">
|
|
28
|
-
<textarea name="content" data-code-editor-target="source" rows="10"><%= sf[:content] %></textarea>
|
|
27
|
+
<div data-controller="code-editor" data-code-editor-language-value="markdown" data-code-editor-preview-value="true">
|
|
28
|
+
<textarea aria-label="Content of <%= sf[:name] %>" name="content" data-code-editor-target="source" rows="10"><%= sf[:content] %></textarea>
|
|
29
29
|
</div>
|
|
30
30
|
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Saving…">Save file</button></div>
|
|
31
31
|
</form>
|
|
@@ -50,15 +50,15 @@
|
|
|
50
50
|
</div>
|
|
51
51
|
<% end %>
|
|
52
52
|
|
|
53
|
-
<details class="card newfile"
|
|
53
|
+
<details class="card newfile"<%== " open" if @system_files.empty? %>>
|
|
54
54
|
<summary>+ new system file</summary>
|
|
55
55
|
<form method="post" action="/studio/system-files" data-controller="dirty-guard">
|
|
56
56
|
<%== csrf_tag %>
|
|
57
57
|
<label>file name
|
|
58
58
|
<input type="text" name="file" placeholder="HOUSE_RULES.md" required>
|
|
59
59
|
</label>
|
|
60
|
-
<div data-controller="code-editor" data-code-editor-language-value="markdown">
|
|
61
|
-
<textarea name="content" data-code-editor-target="source" rows="8" placeholder="# House rules Every agent must..."></textarea>
|
|
60
|
+
<div data-controller="code-editor" data-code-editor-language-value="markdown" data-code-editor-preview-value="true">
|
|
61
|
+
<textarea aria-label="New system file content" name="content" data-code-editor-target="source" rows="8" placeholder="# House rules Every agent must..."></textarea>
|
|
62
62
|
</div>
|
|
63
63
|
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Creating…">Create file</button></div>
|
|
64
64
|
</form>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<header class="page-head">
|
|
1
|
+
<header class="page-head operation-head">
|
|
2
2
|
<div>
|
|
3
|
-
<nav class="crumbs"><a href="/studio/tasks">tasks</a
|
|
3
|
+
<nav class="crumbs"><a href="/studio/tasks">tasks</a></nav>
|
|
4
4
|
<h1>Task
|
|
5
5
|
<span class="copy-id" data-controller="clipboard" data-clipboard-text-value="<%= @task.id %>">
|
|
6
6
|
<span class="mono"><%= @task.id[0, 16] %></span>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
</header>
|
|
13
13
|
|
|
14
|
-
<div class="card">
|
|
14
|
+
<div class="card operation-summary">
|
|
15
15
|
<div class="actions">
|
|
16
16
|
<span class="pill <%= status_class(@task.status) %>"><%= @task.status %></span>
|
|
17
17
|
<% if @task.session_id %>
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
<div class="card">
|
|
41
41
|
<div class="skill-name mono"><%= pa.tool %></div>
|
|
42
42
|
<div class="skill-desc muted">turn <%= pa.turn %> · requested <%= pa.requested_at %></div>
|
|
43
|
+
<p class="muted">Review the requested arguments before allowing this tool to run.</p>
|
|
43
44
|
<pre><%= message_content(pa.args) %></pre>
|
|
44
45
|
<div class="actions">
|
|
45
46
|
<form method="post" action="/studio/approvals/<%= Rack::Utils.escape(pa.id) %>">
|
|
@@ -61,12 +62,7 @@
|
|
|
61
62
|
</section>
|
|
62
63
|
<% end %>
|
|
63
64
|
|
|
64
|
-
<section class="section">
|
|
65
|
-
<div class="section-head"><h2>Command</h2></div>
|
|
66
|
-
<div class="card"><pre><%= message_content(@task.command) %></pre></div>
|
|
67
|
-
</section>
|
|
68
|
-
|
|
69
|
-
<section class="section">
|
|
65
|
+
<section class="section operation-history">
|
|
70
66
|
<div class="section-head"><h2>Executions <span class="muted">· <%= Array(@task.executions).size %></span></h2></div>
|
|
71
67
|
<% if Array(@task.executions).empty? %>
|
|
72
68
|
<div class="empty">No executions yet.</div>
|
|
@@ -88,9 +84,14 @@
|
|
|
88
84
|
<% end %>
|
|
89
85
|
</section>
|
|
90
86
|
|
|
87
|
+
<details class="card operation-diagnostics">
|
|
88
|
+
<summary>Command</summary>
|
|
89
|
+
<div class="card"><pre><%= message_content(@task.command) %></pre></div>
|
|
90
|
+
</details>
|
|
91
|
+
|
|
91
92
|
<% if @task.timing && !@task.timing.empty? %>
|
|
92
|
-
<
|
|
93
|
-
<
|
|
93
|
+
<details class="card operation-diagnostics">
|
|
94
|
+
<summary>Timing</summary>
|
|
94
95
|
<div class="card">
|
|
95
96
|
<ul class="kv">
|
|
96
97
|
<% @task.timing.each do |key, value| %>
|
|
@@ -98,11 +99,11 @@
|
|
|
98
99
|
<% end %>
|
|
99
100
|
</ul>
|
|
100
101
|
</div>
|
|
101
|
-
</
|
|
102
|
+
</details>
|
|
102
103
|
<% end %>
|
|
103
104
|
|
|
104
|
-
<
|
|
105
|
-
<
|
|
105
|
+
<details class="card operation-diagnostics">
|
|
106
|
+
<summary>Latest checkpoint</summary>
|
|
106
107
|
<% if @checkpoint.nil? %>
|
|
107
108
|
<div class="empty">No checkpoint recorded.</div>
|
|
108
109
|
<% else %>
|
|
@@ -115,4 +116,33 @@
|
|
|
115
116
|
</ul>
|
|
116
117
|
</div>
|
|
117
118
|
<% end %>
|
|
118
|
-
</
|
|
119
|
+
</details>
|
|
120
|
+
|
|
121
|
+
<details class="card operation-diagnostics">
|
|
122
|
+
<% failures = @llm_trace["entries"].count { |entry| entry["status"] == "failed" || entry["exception_class"] } %>
|
|
123
|
+
<summary>Model requests<% if failures.positive? %> <span class="status err"><%= failures %> failed events</span><% end %></summary>
|
|
124
|
+
<p class="muted">Native retries are attempt usage, separate from task executions. Reported cost is diagnostic; aggregate spend uses turn accounting. Tokens: input / output / cache read / cache write / thinking.</p>
|
|
125
|
+
<% if @llm_trace["truncated"] %><p class="muted">Latest 200 events shown; earlier diagnostics were discarded.</p><% end %>
|
|
126
|
+
<% if @llm_trace["entries"].empty? %>
|
|
127
|
+
<div class="empty">No model request diagnostics recorded.</div>
|
|
128
|
+
<% else %>
|
|
129
|
+
<div class="card">
|
|
130
|
+
<table class="grid">
|
|
131
|
+
<thead><tr><th>Event / request</th><th>Operation</th><th>Provider / model</th><th>Request duration</th><th>Outcome</th><th>Tokens</th><th>Reported cost</th></tr></thead>
|
|
132
|
+
<tbody>
|
|
133
|
+
<% @llm_trace["entries"].each do |entry| %>
|
|
134
|
+
<tr data-llm-event="<%= entry["type"] %>">
|
|
135
|
+
<td><%= entry["type"] == "llm_request" ? "Request" : "Attempt usage" %><br><span class="mono"><%= entry["request_id"] %></span></td>
|
|
136
|
+
<td><%= entry["operation"] || "—" %></td>
|
|
137
|
+
<td><%= entry["provider"] || "—" %> / <%= entry["model"] || "—" %></td>
|
|
138
|
+
<td><%= entry["duration_ms"] ? format("%.2f ms", entry["duration_ms"]) : "—" %></td>
|
|
139
|
+
<td><%= entry["status"] || "—" %><% if entry["exception_class"] %> (<%= entry["exception_class"] %>)<% end %></td>
|
|
140
|
+
<td><%= %w[input_tokens output_tokens cache_read_tokens cache_write_tokens thinking_tokens].map { |key| entry[key] || "—" }.join(" / ") %></td>
|
|
141
|
+
<td><%= entry["cost"].nil? ? "—" : "$#{entry['cost']}" %></td>
|
|
142
|
+
</tr>
|
|
143
|
+
<% end %>
|
|
144
|
+
</tbody>
|
|
145
|
+
</table>
|
|
146
|
+
</div>
|
|
147
|
+
<% end %>
|
|
148
|
+
</details>
|
|
@@ -1,36 +1,53 @@
|
|
|
1
|
-
<header class="page-head">
|
|
1
|
+
<header class="page-head operation-head">
|
|
2
2
|
<div>
|
|
3
|
-
|
|
4
|
-
<h1>Tasks<span class="sub"
|
|
3
|
+
<%== crumbs_for("Tasks") %>
|
|
4
|
+
<h1>Tasks<span class="sub">most recent first · page <%= @page %></span></h1>
|
|
5
5
|
</div>
|
|
6
6
|
<div class="head-actions">
|
|
7
7
|
<%== agent_filter_form("/studio/tasks", @agent) %>
|
|
8
|
-
<a class="btn" href="/studio/approvals">
|
|
8
|
+
<a class="btn" href="/studio/approvals">Open approvals</a>
|
|
9
9
|
</div>
|
|
10
10
|
</header>
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
<p class="muted">Open a task to review its execution history, resolve approvals or control a running turn.</p>
|
|
13
|
+
|
|
14
|
+
<% if @tasks.empty? && @page == 1 %>
|
|
13
15
|
<div class="empty">No tasks yet. A task is born when an agent runs a turn.</div>
|
|
16
|
+
<% elsif @tasks.empty? %>
|
|
17
|
+
<div class="empty">No tasks on this page. <a href="<%= tasks_page_path(1) %>">Back to the most recent</a>.</div>
|
|
14
18
|
<% else %>
|
|
15
19
|
<div data-controller="list-filter">
|
|
16
20
|
<div class="filter-bar">
|
|
17
|
-
<input type="search" class="filter-input" placeholder="Filter
|
|
21
|
+
<input type="search" class="filter-input" placeholder="Filter this page by id, status or session…" aria-label="Filter tasks"
|
|
18
22
|
data-list-filter-target="query" data-action="input->list-filter#filter keydown->list-filter#clear">
|
|
19
23
|
<span class="filter-count mono muted" data-list-filter-target="count"></span>
|
|
20
24
|
</div>
|
|
21
|
-
<div class="card">
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
<div class="card operation-table">
|
|
26
|
+
<table class="grid">
|
|
27
|
+
<thead>
|
|
28
|
+
<tr>
|
|
29
|
+
<th>Task</th><th>Status</th><th>session</th><th class="num">executions</th><th class="time">updated</th>
|
|
30
|
+
</tr>
|
|
31
|
+
</thead>
|
|
32
|
+
<tbody>
|
|
33
|
+
<% @tasks.each do |t| %>
|
|
34
|
+
<tr data-list-filter-target="item" data-filter-text="<%= t.id %> <%= t.status %> <%= t.session_id %>">
|
|
35
|
+
<td><a href="<%= task_path(t.id) %>"><span class="mono"><%= t.id[0, 12] %></span></a></td>
|
|
36
|
+
<td><span class="status <%= status_class(t.status) %>"><%= t.status %></span></td>
|
|
37
|
+
<td><% if t.session_id %><a class="mono" href="/studio/sessions/<%= Rack::Utils.escape(t.session_id) %>"><%= t.session_id[0, 12] %></a><% else %><span class="muted">—</span><% end %></td>
|
|
38
|
+
<td class="num"><%= Array(t.executions).size %></td>
|
|
39
|
+
<td class="time"><span title="<%= t.updated_at %>"><%= time_ago(t.updated_at) %></span></td>
|
|
40
|
+
</tr>
|
|
41
|
+
<% end %>
|
|
42
|
+
</tbody>
|
|
43
|
+
</table>
|
|
33
44
|
</div>
|
|
34
45
|
<div class="empty" data-list-filter-target="empty" hidden>No tasks match your filter.</div>
|
|
46
|
+
<% if @page > 1 || @more %>
|
|
47
|
+
<nav class="pager" aria-label="Task pages">
|
|
48
|
+
<% if @page > 1 %><a class="btn" href="<%= tasks_page_path(@page - 1) %>" rel="prev">← Newer</a><% end %>
|
|
49
|
+
<% if @more %><a class="btn" href="<%= tasks_page_path(@page + 1) %>" rel="next">Older →</a><% end %>
|
|
50
|
+
</nav>
|
|
51
|
+
<% end %>
|
|
35
52
|
</div>
|
|
36
53
|
<% end %>
|