insika 0.0.1 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +361 -0
- data/LICENSE +21 -0
- data/README.md +136 -2
- data/bin/insika +366 -0
- data/docs/AGENTS.md +618 -0
- data/docs/ARCHITECTURE.md +333 -0
- data/docs/BENCHMARK.md +114 -0
- data/docs/CHANNELS.md +453 -0
- data/docs/CONTEXT.md +117 -0
- data/docs/DEPLOY.md +354 -0
- data/docs/EMBEDDING.md +198 -0
- data/docs/EVALS.md +273 -0
- data/docs/LOADTEST.md +232 -0
- data/docs/OBSERVABILITY.md +374 -0
- data/docs/PLUGINS.md +211 -0
- data/docs/REFINEMENT.md +477 -0
- data/docs/RELEASING.md +70 -0
- data/docs/RUNNING-LOCAL.md +153 -0
- data/docs/SANDBOX.md +114 -0
- data/docs/SECURITY.md +375 -0
- data/docs/SKILLS.md +284 -0
- data/docs/TOOLS.md +302 -0
- data/docs/WHY.md +137 -0
- data/docs/WORKFLOWS.md +225 -0
- data/docs/build.md +14 -0
- data/docs/index.md +68 -0
- data/docs/onboarding/start.md +126 -0
- data/docs/operate.md +12 -0
- data/docs/ship.md +10 -0
- data/docs/understand.md +10 -0
- data/lib/insika/agent_file_store.rb +125 -0
- data/lib/insika/agent_profile.rb +255 -0
- data/lib/insika/alert_dispatcher.rb +139 -0
- data/lib/insika/allowlist.rb +28 -0
- data/lib/insika/baseline_store.rb +74 -0
- data/lib/insika/budget_ledger.rb +135 -0
- data/lib/insika/capability/resolved_tool.rb +34 -0
- data/lib/insika/capability_registry.rb +112 -0
- data/lib/insika/channel_delivery.rb +153 -0
- data/lib/insika/channel_registry.rb +30 -0
- data/lib/insika/channels/relay.rb +178 -0
- data/lib/insika/channels/web/widget.js +283 -0
- data/lib/insika/channels/web.rb +211 -0
- data/lib/insika/channels/webhook.rb +58 -0
- data/lib/insika/chat_builder.rb +303 -0
- data/lib/insika/checkpoint.rb +13 -0
- data/lib/insika/checkpoint_store.rb +153 -0
- data/lib/insika/circuit_state.rb +114 -0
- data/lib/insika/coercion.rb +58 -0
- data/lib/insika/command.rb +32 -0
- data/lib/insika/command_bus.rb +39 -0
- data/lib/insika/commands/agent_payload.rb +43 -0
- data/lib/insika/commands/approve_action.rb +46 -0
- data/lib/insika/commands/cancel_task.rb +33 -0
- data/lib/insika/commands/create_agent.rb +54 -0
- data/lib/insika/commands/create_session.rb +67 -0
- data/lib/insika/commands/delete_agent.rb +33 -0
- data/lib/insika/commands/delete_agent_file.rb +50 -0
- data/lib/insika/commands/delete_data_tool.rb +33 -0
- data/lib/insika/commands/delete_llm_provider.rb +36 -0
- data/lib/insika/commands/delete_mcp.rb +30 -0
- data/lib/insika/commands/delete_skill.rb +43 -0
- data/lib/insika/commands/delete_system_file.rb +29 -0
- data/lib/insika/commands/gate_refinement.rb +245 -0
- data/lib/insika/commands/import_mcp_tools.rb +48 -0
- data/lib/insika/commands/import_tools.rb +81 -0
- data/lib/insika/commands/issue_tenant_token.rb +41 -0
- data/lib/insika/commands/memory_add_note.rb +32 -0
- data/lib/insika/commands/memory_forget_fact.rb +32 -0
- data/lib/insika/commands/memory_put_fact.rb +35 -0
- data/lib/insika/commands/pause_task.rb +29 -0
- data/lib/insika/commands/resolve_refinement.rb +126 -0
- data/lib/insika/commands/restore_agent_file.rb +36 -0
- data/lib/insika/commands/restore_data_tool.rb +34 -0
- data/lib/insika/commands/restore_system_file.rb +31 -0
- data/lib/insika/commands/resume_task.rb +85 -0
- data/lib/insika/commands/revoke_token.rb +39 -0
- data/lib/insika/commands/rotate_tenant_token.rb +43 -0
- data/lib/insika/commands/run_refinement.rb +133 -0
- data/lib/insika/commands/send_message.rb +150 -0
- data/lib/insika/commands/set_agent_tools.rb +39 -0
- data/lib/insika/commands/set_skill_agents.rb +112 -0
- data/lib/insika/commands/trigger_workflow.rb +80 -0
- data/lib/insika/commands/update_agent.rb +49 -0
- data/lib/insika/commands/update_settings.rb +33 -0
- data/lib/insika/commands/upsert_llm_provider.rb +34 -0
- data/lib/insika/commands/upsert_mcp.rb +32 -0
- data/lib/insika/commands/write_agent_file.rb +57 -0
- data/lib/insika/commands/write_data_tool.rb +43 -0
- data/lib/insika/commands/write_golden.rb +58 -0
- data/lib/insika/commands/write_skill.rb +60 -0
- data/lib/insika/commands/write_system_file.rb +31 -0
- data/lib/insika/config_store.rb +89 -0
- data/lib/insika/context/builder.rb +166 -0
- data/lib/insika/context/catalog_provider.rb +23 -0
- data/lib/insika/context/fragment.rb +43 -0
- data/lib/insika/context/priority.rb +30 -0
- data/lib/insika/context/provider.rb +19 -0
- data/lib/insika/context/providers/memory.rb +60 -0
- data/lib/insika/context/providers/prompt.rb +105 -0
- data/lib/insika/context/providers/request.rb +32 -0
- data/lib/insika/context/providers/session.rb +123 -0
- data/lib/insika/context/providers/skill.rb +24 -0
- data/lib/insika/context/providers/skill_trigger.rb +128 -0
- data/lib/insika/context/providers/tool_search.rb +20 -0
- data/lib/insika/context_trace_store.rb +92 -0
- data/lib/insika/delegation_store.rb +153 -0
- data/lib/insika/doctor.rb +539 -0
- data/lib/insika/dsl/definition.rb +55 -0
- data/lib/insika/dsl/runtime.rb +382 -0
- data/lib/insika/dsl/server_boot.rb +98 -0
- data/lib/insika/dsl/system.rb +93 -0
- data/lib/insika/dsl/workflow_adapter.rb +59 -0
- data/lib/insika/dsl.rb +364 -0
- data/lib/insika/edge_limiter.rb +268 -0
- data/lib/insika/egress_guard.rb +75 -0
- data/lib/insika/env_schema.rb +249 -0
- data/lib/insika/errors.rb +201 -0
- data/lib/insika/evals/assertions.rb +247 -0
- data/lib/insika/evals/baseline.rb +69 -0
- data/lib/insika/evals/golden.rb +172 -0
- data/lib/insika/evals/judge.rb +225 -0
- data/lib/insika/evals/pairwise.rb +178 -0
- data/lib/insika/evals/report.rb +115 -0
- data/lib/insika/evals/runner.rb +141 -0
- data/lib/insika/evals/transport.rb +178 -0
- data/lib/insika/event.rb +18 -0
- data/lib/insika/event_stream.rb +132 -0
- data/lib/insika/executor.rb +1995 -0
- data/lib/insika/frontmatter.rb +42 -0
- data/lib/insika/golden_store.rb +145 -0
- data/lib/insika/hooks.rb +48 -0
- data/lib/insika/http_client.rb +63 -0
- data/lib/insika/inbound_log.rb +84 -0
- data/lib/insika/llm_configurator.rb +99 -0
- data/lib/insika/llm_provider_store.rb +83 -0
- data/lib/insika/loop_detector.rb +143 -0
- data/lib/insika/mcp_http_client.rb +67 -0
- data/lib/insika/mcp_store.rb +115 -0
- data/lib/insika/mcp_tool_ingestor.rb +143 -0
- data/lib/insika/memory_store.rb +93 -0
- data/lib/insika/message_origin.rb +76 -0
- data/lib/insika/middleware.rb +36 -0
- data/lib/insika/model_policy.rb +52 -0
- data/lib/insika/model_resolver.rb +176 -0
- data/lib/insika/model_selection.rb +115 -0
- data/lib/insika/onboarding.rb +208 -0
- data/lib/insika/outbox_store.rb +166 -0
- data/lib/insika/overlay_tool_registry.rb +102 -0
- data/lib/insika/pack.rb +102 -0
- data/lib/insika/pack_importer.rb +123 -0
- data/lib/insika/pending_action_store.rb +120 -0
- data/lib/insika/plugin/loader.rb +356 -0
- data/lib/insika/plugin.rb +35 -0
- data/lib/insika/policy/engine.rb +83 -0
- data/lib/insika/policy/policy.rb +120 -0
- data/lib/insika/policy_registry.rb +23 -0
- data/lib/insika/profile_source.rb +143 -0
- data/lib/insika/prompt_catalog.rb +61 -0
- data/lib/insika/provider_error_classifier.rb +160 -0
- data/lib/insika/queue_policy.rb +167 -0
- data/lib/insika/recovery.rb +168 -0
- data/lib/insika/refinement/candidate.rb +159 -0
- data/lib/insika/refinement/evidence_collector.rb +371 -0
- data/lib/insika/refinement/gate.rb +234 -0
- data/lib/insika/refinement/panel.rb +222 -0
- data/lib/insika/refinement/proposer.rb +262 -0
- data/lib/insika/refinement_store.rb +295 -0
- data/lib/insika/registry.rb +59 -0
- data/lib/insika/reliability.rb +185 -0
- data/lib/insika/safety/config.rb +109 -0
- data/lib/insika/safety/detectors.rb +176 -0
- data/lib/insika/safety/factory.rb +102 -0
- data/lib/insika/safety/input_guardrail.rb +102 -0
- data/lib/insika/safety/moderator.rb +94 -0
- data/lib/insika/safety/output_filter.rb +79 -0
- data/lib/insika/safety/output_validator.rb +101 -0
- data/lib/insika/safety/safe_responses.rb +47 -0
- data/lib/insika/sandbox/boundary.rb +93 -0
- data/lib/insika/sandbox/docker.rb +74 -0
- data/lib/insika/sandbox/local.rb +33 -0
- data/lib/insika/sandbox/runner.rb +80 -0
- data/lib/insika/sandbox.rb +85 -0
- data/lib/insika/schema_guard.rb +147 -0
- data/lib/insika/secret_masking.rb +34 -0
- data/lib/insika/server/a2a/agent_card.rb +27 -0
- data/lib/insika/server/a2a/app.rb +112 -0
- data/lib/insika/server/a2a/client.rb +101 -0
- data/lib/insika/server/a2a/errors.rb +32 -0
- data/lib/insika/server/a2a/http.rb +42 -0
- data/lib/insika/server/a2a/message.rb +27 -0
- data/lib/insika/server/a2a/protocol.rb +45 -0
- data/lib/insika/server/a2a/remotes.rb +25 -0
- data/lib/insika/server/a2a/task_projection.rb +40 -0
- data/lib/insika/server/app.rb +1022 -0
- data/lib/insika/server/boot.rb +119 -0
- data/lib/insika/server/rack_app.rb +118 -0
- data/lib/insika/server/responses.rb +165 -0
- data/lib/insika/server/sse_body.rb +96 -0
- data/lib/insika/server/tenant_auth.rb +61 -0
- data/lib/insika/session_actor.rb +162 -0
- data/lib/insika/session_store.rb +143 -0
- data/lib/insika/settings_store.rb +154 -0
- data/lib/insika/shutdown.rb +125 -0
- data/lib/insika/skill_catalog.rb +220 -0
- data/lib/insika/skill_store.rb +127 -0
- data/lib/insika/steer_injector.rb +110 -0
- data/lib/insika/store.rb +52 -0
- data/lib/insika/stores/memory.rb +123 -0
- data/lib/insika/stores/sqlite.rb +183 -0
- data/lib/insika/studio/app.rb +1693 -0
- data/lib/insika/studio/assets/dist/application.css +1 -0
- data/lib/insika/studio/assets/dist/application.js +70 -0
- data/lib/insika/studio/forms.rb +335 -0
- data/lib/insika/studio/nav_icons.rb +31 -0
- data/lib/insika/studio/views/_message.erb +44 -0
- data/lib/insika/studio/views/agent_detail.erb +285 -0
- data/lib/insika/studio/views/agents.erb +63 -0
- data/lib/insika/studio/views/approvals.erb +41 -0
- data/lib/insika/studio/views/chats.erb +34 -0
- data/lib/insika/studio/views/evals.erb +83 -0
- data/lib/insika/studio/views/home.erb +72 -0
- data/lib/insika/studio/views/layout.erb +94 -0
- data/lib/insika/studio/views/login.erb +17 -0
- data/lib/insika/studio/views/mcp.erb +91 -0
- data/lib/insika/studio/views/not_found.erb +5 -0
- data/lib/insika/studio/views/playground.erb +47 -0
- data/lib/insika/studio/views/refinement.erb +234 -0
- data/lib/insika/studio/views/session.erb +137 -0
- data/lib/insika/studio/views/settings.erb +168 -0
- data/lib/insika/studio/views/skills.erb +141 -0
- data/lib/insika/studio/views/system_files.erb +65 -0
- data/lib/insika/studio/views/task.erb +105 -0
- data/lib/insika/studio/views/tasks.erb +33 -0
- data/lib/insika/studio/views/tool_edit.erb +107 -0
- data/lib/insika/studio/views/tools.erb +89 -0
- data/lib/insika/subagent_graph.rb +96 -0
- data/lib/insika/system_file_store.rb +96 -0
- data/lib/insika/task_actor.rb +128 -0
- data/lib/insika/task_store.rb +250 -0
- data/lib/insika/telemetry/pricing.rb +104 -0
- data/lib/insika/telemetry/recorder.rb +228 -0
- data/lib/insika/telemetry.rb +127 -0
- data/lib/insika/testing/store_contract.rb +270 -0
- data/lib/insika/tick.rb +122 -0
- data/lib/insika/token_estimator.rb +16 -0
- data/lib/insika/token_store.rb +168 -0
- data/lib/insika/tool_assembly.rb +140 -0
- data/lib/insika/tool_catalog.rb +89 -0
- data/lib/insika/tool_definition.rb +518 -0
- data/lib/insika/tool_envelope.rb +140 -0
- data/lib/insika/tool_manifest.rb +218 -0
- data/lib/insika/tool_output_compressor.rb +100 -0
- data/lib/insika/tool_registry.rb +21 -0
- data/lib/insika/tool_store.rb +135 -0
- data/lib/insika/tool_trace_store.rb +92 -0
- data/lib/insika/tools/a2a_remote.rb +48 -0
- data/lib/insika/tools/agent_enum.rb +68 -0
- data/lib/insika/tools/concurrency.rb +54 -0
- data/lib/insika/tools/data_defined_tool.rb +219 -0
- data/lib/insika/tools/load_skill.rb +99 -0
- data/lib/insika/tools/remember.rb +53 -0
- data/lib/insika/tools/stuck_signal.rb +44 -0
- data/lib/insika/tools/subagent.rb +75 -0
- data/lib/insika/tools/subagents.rb +77 -0
- data/lib/insika/tools/tool_search.rb +94 -0
- data/lib/insika/turn_output.rb +139 -0
- data/lib/insika/turn_state.rb +162 -0
- data/lib/insika/turn_timing.rb +56 -0
- data/lib/insika/usage_ledger.rb +47 -0
- data/lib/insika/version.rb +3 -1
- data/lib/insika/wiring/graph.rb +249 -0
- data/lib/insika/workflow.rb +185 -0
- data/lib/insika/workflow_registry.rb +33 -0
- data/lib/insika.rb +220 -4
- metadata +412 -8
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<%# Settings — drill-down (T2): section list | selected section's form(s).
|
|
2
|
+
@section (general|models|llm) comes from ?s= and drives the detail. %>
|
|
3
|
+
<% sections = [
|
|
4
|
+
["general", "General", "streaming, timeouts"],
|
|
5
|
+
["models", "Model defaults", "platform model layer (Chat › Agent › platform)"],
|
|
6
|
+
["edge", "Edge limits", "rate-limit per chat, token ceiling per agent"],
|
|
7
|
+
["evals", "Evals", "who grades a rubric, and how a panel agrees"],
|
|
8
|
+
["llm", "LLM providers", "keys, base URL, models per provider"]
|
|
9
|
+
] %>
|
|
10
|
+
<div class="drill">
|
|
11
|
+
<aside class="drill-pane drill-master">
|
|
12
|
+
<div class="drill-pane-head"><div class="title"><strong>Settings</strong></div></div>
|
|
13
|
+
<nav class="drill-pane-body drill-list" aria-label="settings sections">
|
|
14
|
+
<% sections.each do |key, label, sub| %>
|
|
15
|
+
<a class="drill-item<%== ' active' if @section == key %>" href="/studio/settings?s=<%= key %>">
|
|
16
|
+
<div class="drill-item-name"><span class="txt"><%= label %></span><% if key == "llm" %><span class="pill info plain"><%= @providers.size %></span><% end %></div>
|
|
17
|
+
<div class="drill-item-sub"><%= sub %></div>
|
|
18
|
+
</a>
|
|
19
|
+
<% end %>
|
|
20
|
+
</nav>
|
|
21
|
+
</aside>
|
|
22
|
+
|
|
23
|
+
<section class="drill-pane drill-detail">
|
|
24
|
+
<% if @section == "general" %>
|
|
25
|
+
<div class="drill-pane-head">
|
|
26
|
+
<div class="title"><strong>General</strong></div>
|
|
27
|
+
<button type="submit" form="set-general" class="btn primary" data-turbo-submits-with="Saving…">Save</button>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="drill-pane-body">
|
|
30
|
+
<p class="muted">Streaming and timeouts — saved via <code>update_settings</code> (durable, applies next turn).</p>
|
|
31
|
+
<form id="set-general" method="post" action="/studio/settings">
|
|
32
|
+
<%== csrf_tag %>
|
|
33
|
+
<label class="check">
|
|
34
|
+
<input type="checkbox" name="streaming" value="1"<%== " checked" if @settings["streaming"] %>>
|
|
35
|
+
stream model responses
|
|
36
|
+
</label>
|
|
37
|
+
<div class="grid-2">
|
|
38
|
+
<label>request_timeout (s)<input type="text" name="request_timeout" value="<%= @settings["request_timeout"] %>" inputmode="numeric"></label>
|
|
39
|
+
<label>max_retries<input type="text" name="max_retries" value="<%= @settings["max_retries"] %>" inputmode="numeric"></label>
|
|
40
|
+
<label>turn_timeout (s)<input type="text" name="turn_timeout" value="<%= @settings["turn_timeout"] %>" inputmode="numeric"></label>
|
|
41
|
+
<label>tool_timeout (s)<input type="text" name="tool_timeout" value="<%= @settings["tool_timeout"] %>" inputmode="numeric"></label>
|
|
42
|
+
</div>
|
|
43
|
+
</form>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<% elsif @section == "models" %>
|
|
47
|
+
<div class="drill-pane-head">
|
|
48
|
+
<div class="title"><strong>Model defaults</strong></div>
|
|
49
|
+
<button type="submit" form="set-models" class="btn primary" data-turbo-submits-with="Saving…">Save</button>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="drill-pane-body">
|
|
52
|
+
<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>
|
|
53
|
+
<form id="set-models" method="post" action="/studio/settings/models">
|
|
54
|
+
<%== csrf_tag %>
|
|
55
|
+
<div class="grid-2">
|
|
56
|
+
<label>default_model <span class="muted">(agents without their own model)</span><input type="text" name="default_model" value="<%= @settings["default_model"] %>" placeholder="deepseek-v4-flash"></label>
|
|
57
|
+
<label>default_provider <span class="muted">(optional)</span><input type="text" name="default_provider" value="<%= @settings["default_provider"] %>" placeholder="deepseek"></label>
|
|
58
|
+
</div>
|
|
59
|
+
<label>fallback_models <span class="muted">(ordered chain; <code>provider/model</code> or <code>model</code>, comma or newline)</span><input type="text" name="fallback_models" value="<%= Array(@settings["fallback_models"]).join(", ") %>" placeholder="deepseek/deepseek-v4-flash, openai/gpt-4o-mini"></label>
|
|
60
|
+
<label>utility_model <span class="muted">(cheap internal tasks — titles, distillation)</span><input type="text" name="utility_model" value="<%= @settings["utility_model"] %>" placeholder="deepseek-v4-flash"></label>
|
|
61
|
+
<label>reasoning <span class="muted">(global default — off/on/effort; blank = provider default)</span>
|
|
62
|
+
<%== thinking_select("thinking", @settings["thinking"], "provider default") %>
|
|
63
|
+
</label>
|
|
64
|
+
</form>
|
|
65
|
+
|
|
66
|
+
<hr>
|
|
67
|
+
<p class="muted">Per-model reasoning defaults — the layer between global and agent (resolved <strong>Chat › Agent › Model › Global</strong>). One per line.</p>
|
|
68
|
+
<form id="set-model-params" method="post" action="/studio/settings/model-params">
|
|
69
|
+
<%== csrf_tag %>
|
|
70
|
+
<label>model_params <span class="muted">(one per line: <code>provider/model | off|on|low|medium|high</code>)</span>
|
|
71
|
+
<textarea name="model_params" rows="4" placeholder="deepseek/deepseek-v4-flash | off"><%= model_params_text(@settings["model_params"]) %></textarea>
|
|
72
|
+
</label>
|
|
73
|
+
<div class="actions"><button type="submit" class="btn primary" data-turbo-submits-with="Saving…">Save per-model</button></div>
|
|
74
|
+
</form>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<% elsif @section == "edge" %>
|
|
78
|
+
<div class="drill-pane-head">
|
|
79
|
+
<div class="title"><strong>Edge limits</strong></div>
|
|
80
|
+
<button type="submit" form="set-edge" class="btn primary" data-turbo-submits-with="Saving…">Save</button>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="drill-pane-body">
|
|
83
|
+
<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>
|
|
84
|
+
<form id="set-edge" method="post" action="/studio/settings/edge">
|
|
85
|
+
<%== csrf_tag %>
|
|
86
|
+
<div class="grid-2">
|
|
87
|
+
<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>
|
|
88
|
+
<label>chat_rate_window (s)<input type="text" name="chat_rate_window" value="<%= @settings.dig("edge", "chat_rate_window") %>" inputmode="numeric" placeholder="60"></label>
|
|
89
|
+
<label>agent_token_ceiling <span class="muted">(total tokens per agent per window)</span><input type="text" name="agent_token_ceiling" value="<%= @settings.dig("edge", "agent_token_ceiling") %>" inputmode="numeric" placeholder="off"></label>
|
|
90
|
+
<label>agent_token_window (s)<input type="text" name="agent_token_window" value="<%= @settings.dig("edge", "agent_token_window") %>" inputmode="numeric" placeholder="86400"></label>
|
|
91
|
+
</div>
|
|
92
|
+
<label>limit_response <span class="muted">(safe reply on breach; blank = neutral built-in)</span><input type="text" name="limit_response" value="<%= @settings.dig("edge", "limit_response") %>" placeholder="Estou recebendo muitas mensagens agora…"></label>
|
|
93
|
+
</form>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<% elsif @section == "evals" %>
|
|
97
|
+
<div class="drill-pane-head">
|
|
98
|
+
<div class="title"><strong>Evals</strong></div>
|
|
99
|
+
<button type="submit" form="set-evals" class="btn primary" data-turbo-submits-with="Saving…">Save</button>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="drill-pane-body">
|
|
102
|
+
<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>
|
|
103
|
+
<form id="set-evals" method="post" action="/studio/settings/evals">
|
|
104
|
+
<%== csrf_tag %>
|
|
105
|
+
<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>
|
|
106
|
+
<div class="grid-2">
|
|
107
|
+
<label>aggregate <span class="muted">(how the panel's scores combine)</span>
|
|
108
|
+
<select name="aggregate">
|
|
109
|
+
<% %w[median mean min].each do |k| %>
|
|
110
|
+
<option value="<%= k %>"<%== ' selected' if @settings.dig("evals", "aggregate") == k %>><%= k %></option>
|
|
111
|
+
<% end %>
|
|
112
|
+
</select>
|
|
113
|
+
</label>
|
|
114
|
+
<label>min_agreement <span class="muted">(fraction that must pass; 1.0 = unanimous)</span><input type="text" name="min_agreement" value="<%= @settings.dig("evals", "min_agreement") %>" inputmode="decimal" placeholder="0.5"></label>
|
|
115
|
+
<label>quorum <span class="muted">(samples per judge)</span><input type="text" name="quorum" value="<%= @settings.dig("evals", "quorum") %>" inputmode="numeric" placeholder="1"></label>
|
|
116
|
+
<label>tolerance <span class="muted">(max score drop before it's a regression)</span><input type="text" name="tolerance" value="<%= @settings.dig("evals", "tolerance") %>" inputmode="decimal" placeholder="0.05"></label>
|
|
117
|
+
</div>
|
|
118
|
+
</form>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<% else %>
|
|
122
|
+
<div class="drill-pane-head">
|
|
123
|
+
<div class="title"><strong>LLM providers <span class="count"><%= @providers.size %></span></strong></div>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="drill-pane-body">
|
|
126
|
+
<p class="muted">Key/base/models per provider. The <code>api_key</code> never returns in plaintext — the field shows <code><%= secret_sentinel %></code> when a key is saved; resubmitting untouched preserves it, clearing removes it. Applied at runtime (no restart).</p>
|
|
127
|
+
<% @providers.each do |p| %>
|
|
128
|
+
<div class="card promptfile">
|
|
129
|
+
<div class="promptfile-head">
|
|
130
|
+
<strong class="mono"><%= p["api"] %></strong>
|
|
131
|
+
<form method="post" action="/studio/settings/providers/delete" data-turbo-confirm="Remove provider "<%= p["api"] %>"?">
|
|
132
|
+
<%== csrf_tag %>
|
|
133
|
+
<input type="hidden" name="api" value="<%= p["api"] %>">
|
|
134
|
+
<button type="submit" class="ghost danger" data-turbo-submits-with="Removing…">Remove</button>
|
|
135
|
+
</form>
|
|
136
|
+
</div>
|
|
137
|
+
<form method="post" action="/studio/settings/providers">
|
|
138
|
+
<%== csrf_tag %>
|
|
139
|
+
<input type="hidden" name="api" value="<%= p["api"] %>">
|
|
140
|
+
<div class="grid-2">
|
|
141
|
+
<label>base_url<input type="text" name="base_url" value="<%= p["base_url"] %>" placeholder="https://api.deepseek.com/v1"></label>
|
|
142
|
+
<label>auth_header<input type="text" name="auth_header" value="<%= p["auth_header"] %>" placeholder="Authorization"></label>
|
|
143
|
+
</div>
|
|
144
|
+
<label>api_key<input type="password" name="api_key" value="<%= p["api_key"] %>" autocomplete="off"></label>
|
|
145
|
+
<label>models <span class="muted">(comma or newline)</span><input type="text" name="models" value="<%= Array(p["models"]).join(", ") %>" placeholder="deepseek-v4-flash, deepseek-reasoner"></label>
|
|
146
|
+
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Saving…">Save provider</button></div>
|
|
147
|
+
</form>
|
|
148
|
+
</div>
|
|
149
|
+
<% end %>
|
|
150
|
+
|
|
151
|
+
<details class="card newfile"<%== " open" if @providers.empty? %>>
|
|
152
|
+
<summary>+ new provider</summary>
|
|
153
|
+
<form method="post" action="/studio/settings/providers">
|
|
154
|
+
<%== csrf_tag %>
|
|
155
|
+
<div class="grid-2">
|
|
156
|
+
<label>api <span class="muted">(slug, e.g. deepseek)</span><input type="text" name="api" placeholder="openai" required></label>
|
|
157
|
+
<label>base_url<input type="text" name="base_url" placeholder="https://api.openai.com/v1"></label>
|
|
158
|
+
</div>
|
|
159
|
+
<label>auth_header<input type="text" name="auth_header" placeholder="Authorization"></label>
|
|
160
|
+
<label>api_key<input type="password" name="api_key" autocomplete="off" placeholder="sk-…"></label>
|
|
161
|
+
<label>models <span class="muted">(comma or newline)</span><input type="text" name="models" placeholder="gpt-4o, gpt-4o-mini"></label>
|
|
162
|
+
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Creating…">Create provider</button></div>
|
|
163
|
+
</form>
|
|
164
|
+
</details>
|
|
165
|
+
</div>
|
|
166
|
+
<% end %>
|
|
167
|
+
</section>
|
|
168
|
+
</div>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<%# Skills — drill-down (T2): master list | detail editor + availability.
|
|
2
|
+
Every skill route renders THIS view (index / new / :name); @selected drives
|
|
3
|
+
which skill is open (nil = none, "" = new). @scope_agent set = editing THAT
|
|
4
|
+
agent's own version of the skill, not the shared one. %>
|
|
5
|
+
<% scope = @scope_agent %>
|
|
6
|
+
<% form_action = scope ? "/studio/agents/#{Rack::Utils.escape(scope)}/skills/#{Rack::Utils.escape(@selected.to_s)}" : "/studio/skills" %>
|
|
7
|
+
<div class="drill" data-controller="list-filter">
|
|
8
|
+
<aside class="drill-pane drill-master">
|
|
9
|
+
<div class="drill-pane-head">
|
|
10
|
+
<div class="title"><strong>Skills <span class="count"><%= @skills.size %></span></strong></div>
|
|
11
|
+
<a class="btn primary btn-sm" href="/studio/skills/new">+ new</a>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="drill-search">
|
|
14
|
+
<input type="search" class="filter-input" placeholder="Search skills…" aria-label="Filter skills"
|
|
15
|
+
data-list-filter-target="query" data-action="input->list-filter#filter keydown->list-filter#clear">
|
|
16
|
+
</div>
|
|
17
|
+
<nav class="drill-pane-body drill-list" aria-label="skills">
|
|
18
|
+
<% @skills.each do |s| %>
|
|
19
|
+
<a class="drill-item<%== ' active' if @selected == s.name %>"
|
|
20
|
+
href="/studio/skills/<%= Rack::Utils.escape(s.name) %>"
|
|
21
|
+
data-list-filter-target="item" data-filter-text="<%= s.name %> <%= s.description %>">
|
|
22
|
+
<div class="drill-item-name"><span class="txt"><%= s.name %></span><% if @stored.include?(s.name) %><span class="pill info">authored</span><% end %></div>
|
|
23
|
+
<div class="drill-item-sub"><%= s.description %></div>
|
|
24
|
+
</a>
|
|
25
|
+
<% end %>
|
|
26
|
+
<div class="empty" data-list-filter-target="empty" hidden>No skills match your filter.</div>
|
|
27
|
+
</nav>
|
|
28
|
+
</aside>
|
|
29
|
+
|
|
30
|
+
<section class="drill-pane drill-detail">
|
|
31
|
+
<% if @selected.nil? %>
|
|
32
|
+
<div class="drill-empty">
|
|
33
|
+
<div>
|
|
34
|
+
<h2>Skills</h2>
|
|
35
|
+
<p>Pick a skill on the left to edit its <code>SKILL.md</code> and choose which agents can <code>load_skill</code> it — or create a new one.</p>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<% else %>
|
|
39
|
+
<% new = @selected == "" %>
|
|
40
|
+
<div class="drill-pane-head">
|
|
41
|
+
<div class="title">
|
|
42
|
+
<nav class="crumbs"><a href="/studio/skills">skills</a> / <span><%= new ? "new" : @selected %></span><% if scope %> / <span>for <%= scope %></span><% end %></nav>
|
|
43
|
+
<strong class="mono"><%= new ? "New skill" : @selected %><% if scope %> <span class="pill warn">specialized for <%= scope %></span><% end %></strong>
|
|
44
|
+
</div>
|
|
45
|
+
<button type="submit" form="skill-form" class="btn primary" data-turbo-submits-with="Saving…">Save skill</button>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="drill-pane-body">
|
|
48
|
+
<%# The specialization is one MORE record, not a fork: the name stays the bare
|
|
49
|
+
shared name everywhere the model and the operator see it, and deleting the
|
|
50
|
+
override falls back to the shared body. %>
|
|
51
|
+
<% if scope %>
|
|
52
|
+
<div class="drill-section">
|
|
53
|
+
<p class="muted">This is <strong><%= scope %></strong>'s own version of <code><%= @selected %></code>. Every
|
|
54
|
+
other agent keeps the <a href="/studio/skills/<%= Rack::Utils.escape(@selected.to_s) %>">shared skill</a>,
|
|
55
|
+
and the name does not change — the allowlist, <code><available_skills></code> and
|
|
56
|
+
<code>load_skill</code> all still say <code><%= @selected %></code>.</p>
|
|
57
|
+
<form method="post" action="/studio/agents/<%= Rack::Utils.escape(scope) %>/skills/<%= Rack::Utils.escape(@selected.to_s) %>/delete">
|
|
58
|
+
<%== csrf_tag %>
|
|
59
|
+
<div class="actions"><button type="submit" data-turbo-confirm="Remove this specialization? <%= scope %> falls back to the shared skill.">Stop specializing</button></div>
|
|
60
|
+
</form>
|
|
61
|
+
</div>
|
|
62
|
+
<% end %>
|
|
63
|
+
<form id="skill-form" method="post" action="<%= form_action %>" data-controller="dirty-guard">
|
|
64
|
+
<%== csrf_tag %>
|
|
65
|
+
<% if !new && !scope %><input type="hidden" name="name" value="<%= @selected %>"><% end %>
|
|
66
|
+
<% if new %>
|
|
67
|
+
<label>name <span class="muted">(canonical — same as <code>name:</code> in the frontmatter)</span>
|
|
68
|
+
<input type="text" name="name" placeholder="my-skill" required>
|
|
69
|
+
</label>
|
|
70
|
+
<% end %>
|
|
71
|
+
<label>SKILL.md <span class="muted">YAML frontmatter (<code>name</code>, <code>description</code>) + body</span></label>
|
|
72
|
+
<div data-controller="code-editor" data-code-editor-language-value="markdown" data-code-editor-preview-value="true">
|
|
73
|
+
<textarea name="content" data-code-editor-target="source" rows="18"><%= @skill_content %></textarea>
|
|
74
|
+
</div>
|
|
75
|
+
</form>
|
|
76
|
+
|
|
77
|
+
<% if !new && !scope %>
|
|
78
|
+
<div class="drill-section">
|
|
79
|
+
<div class="drill-section-head">
|
|
80
|
+
<h3>Availability</h3>
|
|
81
|
+
<span class="muted">per agent: <strong>allowed</strong> = can <code>load_skill</code> it; <strong>always on</strong> = the body
|
|
82
|
+
is in the prompt every turn. Both are the AGENT's decision, so one shared skill can be
|
|
83
|
+
always-on for one and discretionary for another. An "all" allowlist is left untouched.</span>
|
|
84
|
+
</div>
|
|
85
|
+
<form method="post" action="/studio/skills/<%= Rack::Utils.escape(@selected) %>/agents">
|
|
86
|
+
<%== csrf_tag %>
|
|
87
|
+
<% if @agents.empty? %>
|
|
88
|
+
<p class="muted">No agents configured.</p>
|
|
89
|
+
<% else %>
|
|
90
|
+
<div class="check-grid agent-grid">
|
|
91
|
+
<% @agents.each do |a| %>
|
|
92
|
+
<% own = Array((@specialized || {})[@selected]).include?(a.id) %>
|
|
93
|
+
<div class="agent-check">
|
|
94
|
+
<div class="agent-check-name">
|
|
95
|
+
<span class="mono"><%= a.id %></span>
|
|
96
|
+
<% if own %>
|
|
97
|
+
<a class="pill warn" href="/studio/agents/<%= Rack::Utils.escape(a.id) %>/skills/<%= Rack::Utils.escape(@selected) %>">own version</a>
|
|
98
|
+
<% end %>
|
|
99
|
+
</div>
|
|
100
|
+
<label class="check tool-check">
|
|
101
|
+
<input type="checkbox" name="agent_ids[]" value="<%= a.id %>"<%== " checked" if skill_enabled_for?(a, @selected) %>>
|
|
102
|
+
<span class="mono">allowed</span>
|
|
103
|
+
</label>
|
|
104
|
+
<label class="check tool-check">
|
|
105
|
+
<input type="checkbox" name="eager_ids[]" value="<%= a.id %>"<%== " checked" if skill_eager_for?(a, @selected) %>>
|
|
106
|
+
<span class="mono">always on</span>
|
|
107
|
+
</label>
|
|
108
|
+
</div>
|
|
109
|
+
<% end %>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Applying…">Apply to agents</button></div>
|
|
112
|
+
<% end %>
|
|
113
|
+
</form>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<%# Specializing is what keeps a shared skill shared: without it the only way
|
|
117
|
+
to give one agent a different version was to fork it under a new name and
|
|
118
|
+
edit the allowlist, which throws the sharing away. %>
|
|
119
|
+
<% others = @agents.reject { |a| Array((@specialized || {})[@selected]).include?(a.id) } %>
|
|
120
|
+
<% if others.any? %>
|
|
121
|
+
<div class="drill-section">
|
|
122
|
+
<div class="drill-section-head">
|
|
123
|
+
<h3>Specialize</h3>
|
|
124
|
+
<span class="muted">give ONE agent its own version of this skill, seeded from the body above — same name, no fork</span>
|
|
125
|
+
</div>
|
|
126
|
+
<form method="post" action="/studio/skills/<%= Rack::Utils.escape(@selected) %>/specialize">
|
|
127
|
+
<%== csrf_tag %>
|
|
128
|
+
<label>agent
|
|
129
|
+
<select name="agent_id" required>
|
|
130
|
+
<% others.each do |a| %><option value="<%= a.id %>"><%= a.id %></option><% end %>
|
|
131
|
+
</select>
|
|
132
|
+
</label>
|
|
133
|
+
<div class="actions"><button type="submit" data-turbo-submits-with="Creating…">Specialize for this agent</button></div>
|
|
134
|
+
</form>
|
|
135
|
+
</div>
|
|
136
|
+
<% end %>
|
|
137
|
+
<% end %>
|
|
138
|
+
</div>
|
|
139
|
+
<% end %>
|
|
140
|
+
</section>
|
|
141
|
+
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<header class="page-head">
|
|
2
|
+
<div>
|
|
3
|
+
<nav class="crumbs"><a href="/studio/agents">agents</a> / <span>system files</span></nav>
|
|
4
|
+
<h1>System files<span class="sub"><%= @system_files.size %> file(s) · apply to every agent</span></h1>
|
|
5
|
+
</div>
|
|
6
|
+
</header>
|
|
7
|
+
|
|
8
|
+
<p class="muted matrix-hint">GLOBAL prompts/rules: the runtime injects these files <strong>before</strong> each BIA's own identity, every turn. Store-backed and versioned (like per-agent prompts). Empty = no global injection (the prompt stays exactly as before).</p>
|
|
9
|
+
|
|
10
|
+
<% if @system_files.empty? %>
|
|
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>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<% @system_files.each do |sf| %>
|
|
15
|
+
<div class="card promptfile">
|
|
16
|
+
<div class="promptfile-head">
|
|
17
|
+
<strong class="mono"><%= sf[:name] %></strong>
|
|
18
|
+
<form method="post" action="/studio/system-files/delete" data-turbo-confirm="Remove system file "<%= sf[:name] %>"? This can't be undone.">
|
|
19
|
+
<%== csrf_tag %>
|
|
20
|
+
<input type="hidden" name="file" value="<%= sf[:name] %>">
|
|
21
|
+
<button type="submit" class="ghost danger" data-turbo-submits-with="Removing…">Remove</button>
|
|
22
|
+
</form>
|
|
23
|
+
</div>
|
|
24
|
+
<form method="post" action="/studio/system-files" data-controller="dirty-guard">
|
|
25
|
+
<%== csrf_tag %>
|
|
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>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Saving…">Save file</button></div>
|
|
31
|
+
</form>
|
|
32
|
+
<% unless sf[:versions].empty? %>
|
|
33
|
+
<details class="versions">
|
|
34
|
+
<summary><%= sf[:versions].size %> previous version(s)</summary>
|
|
35
|
+
<ul>
|
|
36
|
+
<% sf[:versions].each_with_index do |v, i| %>
|
|
37
|
+
<li>
|
|
38
|
+
<span class="muted mono"><%= v["at"] %></span>
|
|
39
|
+
<form method="post" action="/studio/system-files/restore">
|
|
40
|
+
<%== csrf_tag %>
|
|
41
|
+
<input type="hidden" name="file" value="<%= sf[:name] %>">
|
|
42
|
+
<input type="hidden" name="version" value="<%= i %>">
|
|
43
|
+
<button type="submit" class="ghost">Restore</button>
|
|
44
|
+
</form>
|
|
45
|
+
</li>
|
|
46
|
+
<% end %>
|
|
47
|
+
</ul>
|
|
48
|
+
</details>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
|
|
53
|
+
<details class="card newfile">
|
|
54
|
+
<summary>+ new system file</summary>
|
|
55
|
+
<form method="post" action="/studio/system-files" data-controller="dirty-guard">
|
|
56
|
+
<%== csrf_tag %>
|
|
57
|
+
<label>file name
|
|
58
|
+
<input type="text" name="file" placeholder="HOUSE_RULES.md" required>
|
|
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>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Creating…">Create file</button></div>
|
|
64
|
+
</form>
|
|
65
|
+
</details>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<header class="page-head">
|
|
2
|
+
<div>
|
|
3
|
+
<nav class="crumbs"><a href="/studio/tasks">tasks</a> / <span>task</span></nav>
|
|
4
|
+
<h1>Task
|
|
5
|
+
<span class="copy-id" data-controller="clipboard" data-clipboard-text-value="<%= @task.id %>">
|
|
6
|
+
<span class="mono"><%= @task.id[0, 16] %></span>
|
|
7
|
+
<button type="button" class="copy-btn" data-clipboard-target="button" data-action="clipboard#copy" aria-label="Copy full task id">copy id</button>
|
|
8
|
+
</span>
|
|
9
|
+
<span class="sub">status <%= @task.status %> · updated <%= @task.updated_at %></span>
|
|
10
|
+
</h1>
|
|
11
|
+
</div>
|
|
12
|
+
</header>
|
|
13
|
+
|
|
14
|
+
<div class="card">
|
|
15
|
+
<div class="actions">
|
|
16
|
+
<span class="pill <%= status_class(@task.status) %>"><%= @task.status %></span>
|
|
17
|
+
<% if @task.session_id %>
|
|
18
|
+
<span class="muted">session <a class="mono" href="/studio/sessions/<%= Rack::Utils.escape(@task.session_id) %>"><%= @task.session_id[0, 12] %></a></span>
|
|
19
|
+
<% end %>
|
|
20
|
+
<form method="post" action="<%= task_path(@task.id) %>/pause">
|
|
21
|
+
<%== csrf_tag %>
|
|
22
|
+
<button type="submit">Pause</button>
|
|
23
|
+
</form>
|
|
24
|
+
<form method="post" action="<%= task_path(@task.id) %>/resume">
|
|
25
|
+
<%== csrf_tag %>
|
|
26
|
+
<button type="submit" class="primary">Resume</button>
|
|
27
|
+
</form>
|
|
28
|
+
<form method="post" action="<%= task_path(@task.id) %>/cancel"
|
|
29
|
+
data-turbo-confirm="Cancel this task? In-flight work stops and it can't be resumed.">
|
|
30
|
+
<%== csrf_tag %>
|
|
31
|
+
<button type="submit" class="danger">Cancel</button>
|
|
32
|
+
</form>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<% unless Array(@pending).empty? %>
|
|
37
|
+
<section class="section">
|
|
38
|
+
<div class="section-head"><h2>Pending approvals</h2></div>
|
|
39
|
+
<% @pending.each do |pa| %>
|
|
40
|
+
<div class="card">
|
|
41
|
+
<div class="skill-name mono"><%= pa.tool %></div>
|
|
42
|
+
<div class="skill-desc muted">turn <%= pa.turn %> · requested <%= pa.requested_at %></div>
|
|
43
|
+
<pre><%= message_content(pa.args) %></pre>
|
|
44
|
+
<div class="actions">
|
|
45
|
+
<form method="post" action="/studio/approvals/<%= Rack::Utils.escape(pa.id) %>">
|
|
46
|
+
<%== csrf_tag %>
|
|
47
|
+
<input type="hidden" name="decision" value="approved">
|
|
48
|
+
<input type="hidden" name="back" value="<%= task_path(@task.id) %>">
|
|
49
|
+
<button type="submit" class="primary">Approve</button>
|
|
50
|
+
</form>
|
|
51
|
+
<form method="post" action="/studio/approvals/<%= Rack::Utils.escape(pa.id) %>"
|
|
52
|
+
data-turbo-confirm="Reject this action? The turn resumes as if the tool was denied.">
|
|
53
|
+
<%== csrf_tag %>
|
|
54
|
+
<input type="hidden" name="decision" value="rejected">
|
|
55
|
+
<input type="hidden" name="back" value="<%= task_path(@task.id) %>">
|
|
56
|
+
<button type="submit" class="danger">Reject</button>
|
|
57
|
+
</form>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<% end %>
|
|
61
|
+
</section>
|
|
62
|
+
<% end %>
|
|
63
|
+
|
|
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">
|
|
70
|
+
<div class="section-head"><h2>Executions <span class="muted">· <%= Array(@task.executions).size %></span></h2></div>
|
|
71
|
+
<% if Array(@task.executions).empty? %>
|
|
72
|
+
<div class="empty">No executions yet.</div>
|
|
73
|
+
<% else %>
|
|
74
|
+
<div class="card">
|
|
75
|
+
<ul class="kv">
|
|
76
|
+
<% @task.executions.each do |e| %>
|
|
77
|
+
<li>
|
|
78
|
+
<span class="k mono">#<%= e.attempt %></span>
|
|
79
|
+
<span class="v">
|
|
80
|
+
<span class="pill <%= status_class(e.outcome) %>"><%= e.outcome || "—" %></span>
|
|
81
|
+
<span class="muted mono"><%= e.started_at %> → <%= e.finished_at || "…" %></span>
|
|
82
|
+
<% if e.error %><pre><%= e.error %></pre><% end %>
|
|
83
|
+
</span>
|
|
84
|
+
</li>
|
|
85
|
+
<% end %>
|
|
86
|
+
</ul>
|
|
87
|
+
</div>
|
|
88
|
+
<% end %>
|
|
89
|
+
</section>
|
|
90
|
+
|
|
91
|
+
<section class="section">
|
|
92
|
+
<div class="section-head"><h2>Latest checkpoint</h2></div>
|
|
93
|
+
<% if @checkpoint.nil? %>
|
|
94
|
+
<div class="empty">No checkpoint recorded.</div>
|
|
95
|
+
<% else %>
|
|
96
|
+
<div class="card">
|
|
97
|
+
<ul class="kv">
|
|
98
|
+
<li><span class="k">turn</span><span class="v mono"><%= @checkpoint.turn %></span></li>
|
|
99
|
+
<li><span class="k">messages</span><span class="v mono"><%= Array(@checkpoint.messages).size %></span></li>
|
|
100
|
+
<li><span class="k">side effects</span><span class="v mono"><%= Array(@checkpoint.completed_side_effects).size %></span></li>
|
|
101
|
+
<li><span class="k">created</span><span class="v mono"><%= @checkpoint.created_at %></span></li>
|
|
102
|
+
</ul>
|
|
103
|
+
</div>
|
|
104
|
+
<% end %>
|
|
105
|
+
</section>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<header class="page-head">
|
|
2
|
+
<div>
|
|
3
|
+
<nav class="crumbs"><span>tasks</span></nav>
|
|
4
|
+
<h1>Tasks<span class="sub"><%= @tasks.size %> task(s)</span></h1>
|
|
5
|
+
</div>
|
|
6
|
+
<a class="btn" href="/studio/approvals">Approvals →</a>
|
|
7
|
+
</header>
|
|
8
|
+
|
|
9
|
+
<% if @tasks.empty? %>
|
|
10
|
+
<div class="empty">No tasks yet. A task is born when an agent runs a turn.</div>
|
|
11
|
+
<% else %>
|
|
12
|
+
<div data-controller="list-filter">
|
|
13
|
+
<div class="filter-bar">
|
|
14
|
+
<input type="search" class="filter-input" placeholder="Filter tasks by id, status or session…" aria-label="Filter tasks"
|
|
15
|
+
data-list-filter-target="query" data-action="input->list-filter#filter keydown->list-filter#clear">
|
|
16
|
+
<span class="filter-count mono muted" data-list-filter-target="count"></span>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="card">
|
|
19
|
+
<ul class="history">
|
|
20
|
+
<% @tasks.each do |t| %>
|
|
21
|
+
<li data-list-filter-target="item" data-filter-text="<%= t.id %> <%= t.status %> <%= t.session_id %>">
|
|
22
|
+
<a href="<%= task_path(t.id) %>"><span class="mono"><%= t.id[0, 12] %></span></a>
|
|
23
|
+
<span class="pill <%= status_class(t.status) %>"><%= t.status %></span>
|
|
24
|
+
<span class="preview muted">
|
|
25
|
+
<% if t.session_id %>session <span class="mono"><%= t.session_id[0, 12] %></span> · <% end %><%= Array(t.executions).size %> execution(s) · updated <%= t.updated_at %>
|
|
26
|
+
</span>
|
|
27
|
+
</li>
|
|
28
|
+
<% end %>
|
|
29
|
+
</ul>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="empty" data-list-filter-target="empty" hidden>No tasks match your filter.</div>
|
|
32
|
+
</div>
|
|
33
|
+
<% end %>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<% new = @tool_name.empty? %>
|
|
2
|
+
<header class="page-head">
|
|
3
|
+
<div>
|
|
4
|
+
<nav class="crumbs"><a href="/studio/tools">tools</a> / <span><%= new ? "new (data tool)" : @tool_name %></span></nav>
|
|
5
|
+
<h1><%= new ? "New data tool" : @tool_name %><span class="sub">an HTTP call described by data — no code</span></h1>
|
|
6
|
+
</div>
|
|
7
|
+
</header>
|
|
8
|
+
|
|
9
|
+
<p class="muted matrix-hint">The tool makes an HTTP request when the model calls it. Use <code>{{param}}</code> in any field (url, query, headers, body) to insert arguments — https only, private destinations are blocked. Credential headers listed in <strong>secret_headers</strong> never return in plaintext: they appear as <code><%= secret_sentinel %></code> (keeping it preserves the secret).</p>
|
|
10
|
+
|
|
11
|
+
<form class="card" method="post" action="<%= new ? "/studio/tools/def" : "/studio/tools/def/#{Rack::Utils.escape(@tool_name)}" %>" data-controller="dirty-guard">
|
|
12
|
+
<%== csrf_tag %>
|
|
13
|
+
|
|
14
|
+
<div class="grid-2">
|
|
15
|
+
<label>name <span class="muted">(identifier for the model, <code>[a-z0-9_]</code>)</span>
|
|
16
|
+
<% if new %>
|
|
17
|
+
<input type="text" name="name" value="<%= @form[:name] %>" placeholder="lookup_zip" required>
|
|
18
|
+
<% else %>
|
|
19
|
+
<input type="text" name="name" value="<%= @tool_name %>" readonly>
|
|
20
|
+
<% end %>
|
|
21
|
+
</label>
|
|
22
|
+
<label>method
|
|
23
|
+
<select name="method">
|
|
24
|
+
<% %w[GET HEAD POST PUT PATCH DELETE].each do |m| %>
|
|
25
|
+
<option value="<%= m %>"<%== " selected" if @form[:method] == m %>><%= m %></option>
|
|
26
|
+
<% end %>
|
|
27
|
+
</select>
|
|
28
|
+
</label>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<label>description <span class="muted">(the model reads this to decide when to use it)</span>
|
|
32
|
+
<input type="text" name="description" value="<%= @form[:description] %>" placeholder="Look up a ZIP code and return the city" required>
|
|
33
|
+
</label>
|
|
34
|
+
|
|
35
|
+
<label>url <span class="muted">(https; use <code>{{param}}</code>)</span>
|
|
36
|
+
<input type="text" name="url" value="<%= @form[:url] %>" placeholder="https://viacep.com.br/ws/{{cep}}/json" required>
|
|
37
|
+
</label>
|
|
38
|
+
|
|
39
|
+
<label>parameters <span class="muted">(one per line: <code>name | type | required|optional | description</code> — type is <code>string</code>/<code>number</code>/<code>integer</code>/<code>boolean</code> or <code>array:string</code>. For a list of objects or a nested object, paste a full <strong>JSON Schema</strong> instead: any text starting with <code>{</code> is read as one.)</span>
|
|
40
|
+
<textarea name="parameters" rows="6" placeholder="cep | string | required | The ZIP code to look up"><%= @form[:parameters] %></textarea>
|
|
41
|
+
</label>
|
|
42
|
+
|
|
43
|
+
<div class="grid-2">
|
|
44
|
+
<label>query <span class="muted">(one <code>key=value</code> per line)</span>
|
|
45
|
+
<textarea name="query" rows="3" placeholder="term={{q}}"><%= @form[:query] %></textarea>
|
|
46
|
+
</label>
|
|
47
|
+
<label>headers <span class="muted">(one <code>Key=value</code> per line)</span>
|
|
48
|
+
<textarea name="headers" rows="3" placeholder="Authorization=Bearer {{token}}"><%= @form[:headers] %></textarea>
|
|
49
|
+
</label>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<label>secret_headers <span class="muted">(header names to mask, comma-separated)</span>
|
|
53
|
+
<input type="text" name="secret_headers" value="<%= @form[:secret_headers] %>" placeholder="Authorization">
|
|
54
|
+
</label>
|
|
55
|
+
|
|
56
|
+
<label>body <span class="muted">(template; a <code>{{param}}</code> string is JSON-escaped)</span>
|
|
57
|
+
<div data-controller="code-editor" data-code-editor-language-value="json">
|
|
58
|
+
<textarea name="body" data-code-editor-target="source" rows="6"><%= @form[:body] %></textarea>
|
|
59
|
+
</div>
|
|
60
|
+
</label>
|
|
61
|
+
|
|
62
|
+
<div class="grid-2">
|
|
63
|
+
<label>response
|
|
64
|
+
<select name="extract">
|
|
65
|
+
<% [["body_raw", "raw body"], ["json_path", "JSON field"], ["status", "status only"]].each do |v, lbl| %>
|
|
66
|
+
<option value="<%= v %>"<%== " selected" if @form[:extract] == v %>><%= lbl %></option>
|
|
67
|
+
<% end %>
|
|
68
|
+
</select>
|
|
69
|
+
</label>
|
|
70
|
+
<label>json_path <span class="muted">(e.g. <code>localidade</code> or <code>a.b.c</code>)</span>
|
|
71
|
+
<input type="text" name="path" value="<%= @form[:path] %>" placeholder="localidade">
|
|
72
|
+
</label>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<label>timeout <span class="muted">(seconds, optional)</span>
|
|
76
|
+
<input type="number" name="timeout" value="<%= @form[:timeout] %>" min="1" placeholder="30">
|
|
77
|
+
</label>
|
|
78
|
+
|
|
79
|
+
<div class="actions">
|
|
80
|
+
<button type="submit" class="primary" data-turbo-submits-with="<%= new ? "Creating…" : "Saving…" %>"><%= new ? "Create tool" : "Save tool" %></button>
|
|
81
|
+
<a class="btn ghost" href="/studio/tools">← back</a>
|
|
82
|
+
</div>
|
|
83
|
+
</form>
|
|
84
|
+
|
|
85
|
+
<% unless new %>
|
|
86
|
+
<form class="card" method="post" action="/studio/tools/def/<%= Rack::Utils.escape(@tool_name) %>/delete" data-turbo-confirm="Remove tool "<%= @tool_name %>"? Agents stop seeing it next turn.">
|
|
87
|
+
<%== csrf_tag %>
|
|
88
|
+
<div class="promptfile-head">
|
|
89
|
+
<span class="muted">Remove this tool (agents stop seeing it next turn)</span>
|
|
90
|
+
<button type="submit" class="ghost danger" data-turbo-submits-with="Removing…">Remove tool</button>
|
|
91
|
+
</div>
|
|
92
|
+
</form>
|
|
93
|
+
|
|
94
|
+
<% if @versions.any? %>
|
|
95
|
+
<div class="card">
|
|
96
|
+
<h2 class="eyebrow">versions</h2>
|
|
97
|
+
<% @versions.each_with_index do |v, i| %>
|
|
98
|
+
<form class="promptfile-head" method="post" action="/studio/tools/def/<%= Rack::Utils.escape(@tool_name) %>/restore">
|
|
99
|
+
<%== csrf_tag %>
|
|
100
|
+
<input type="hidden" name="index" value="<%= i %>">
|
|
101
|
+
<span class="mono muted"><%= v["at"] %></span>
|
|
102
|
+
<button type="submit" class="ghost">Restore</button>
|
|
103
|
+
</form>
|
|
104
|
+
<% end %>
|
|
105
|
+
</div>
|
|
106
|
+
<% end %>
|
|
107
|
+
<% end %>
|