world: solid team + project orbs, hide ROOT, per-topic repo landmarks
Three fixes to the Live viz per user feedback:
1) Hide the ROOT sentinel — it was rendering at the origin as a red disc
with no label. It's a physics anchor, not a real orb. Skipping it in
nodes/edges/labels loops removes the mystery red circle.
2) Emit `repo:<topic_id>` project orbs for active research topics from
the world SSE loop. Labeled with the topic title, tier "repo" gets its
own soft sky-blue palette and a landmark radius (r=11 between company
and team). Assigned agents get a low-weight (0.15) convergence touch
so pawns cluster around their project's orb even at rest — no wait
for a file op to see the affiliation.
3) Solid at rest, bloom on interaction. Two engine changes:
- Structural + repo orbs now have a soft 0.08 glow at heat=0 (down
from 0.22 ambient) so the disc reads as solid until agents heat it.
- `world.touch` heat is now weight-scaled (`+w*0.6`) instead of a
flat `+0.5` regardless of intent. Soft convergence stays soft;
file ops still explode.
Click a `repo:` orb → the existing Commit F focus mode already treats
that prefix as a subtree root, so users drop straight into the
Gource-style repo detail view with only the files their agents are
touching.
Follow-ups queued: teardown of repo orbs when a topic reaches 'published'
(currently they persist until the SSE loop's status filter drops them,
which is correct); loops equivalent (loop:<id> landmark orbs).
This commit is contained in:
@@ -61,6 +61,37 @@ async fn active_runs(pool: &PgPool, ws: WorkspaceId) -> Vec<(String, String)> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Active research topics + their assigned agents. Each returned row is a
|
||||
/// `(topic_id, title, agent_id)` — one row per (topic, agent) pair. Emitted
|
||||
/// from the SSE loop as `repo:<topic_id>` project orbs so the World shows a
|
||||
/// clickable, labeled landmark for every in-flight R&D initiative — no need
|
||||
/// for a file touch to land first.
|
||||
async fn active_research_topics(
|
||||
pool: &PgPool,
|
||||
ws: WorkspaceId,
|
||||
) -> Vec<(String, String, String)> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT t.id::text AS topic_id, t.title AS title, ra.agent_id::text AS agent_id
|
||||
FROM research_topics t
|
||||
JOIN research_topic_agents ra ON ra.topic_id = t.id
|
||||
WHERE t.workspace_id = $1
|
||||
AND t.status IN ('processing', 'reviewing', 'publishing')",
|
||||
)
|
||||
.bind(ws.as_uuid())
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
rows.into_iter()
|
||||
.map(|r| {
|
||||
(
|
||||
r.get::<String, _>("topic_id"),
|
||||
r.get::<String, _>("title"),
|
||||
r.get::<String, _>("agent_id"),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A short human label for a tool's input (for the tool-call target).
|
||||
fn summarize_input(input: &Value) -> String {
|
||||
for k in ["target", "path", "url", "query", "name", "file", "command"] {
|
||||
@@ -361,6 +392,27 @@ pub async fn world_live(State(state): State<AppState>, Authed(user): Authed) ->
|
||||
}
|
||||
}
|
||||
|
||||
// Active research topics → landmark project orbs. One `repo:<id>`
|
||||
// per topic, labeled with the topic title so users can click it
|
||||
// and drop into the repo-focus (Gource) view before any files are
|
||||
// touched. Assigned agents gently converge on their topic's orb
|
||||
// so the affinity is visible even in idle windows.
|
||||
let research = active_research_topics(&pool, ws).await;
|
||||
let mut seen_topics = std::collections::HashSet::new();
|
||||
for (topic_id, title, agent_id) in &research {
|
||||
let node_id = format!("repo:{topic_id}");
|
||||
if seen_topics.insert(topic_id.clone()) {
|
||||
yield sse(
|
||||
"node.activity",
|
||||
json!({ "nodeId": node_id, "label": title, "kind": "service", "heat": 0.0 }),
|
||||
);
|
||||
}
|
||||
yield sse(
|
||||
"world.touch",
|
||||
json!({ "agentId": agent_id, "nodeId": node_id, "kind": "service", "weight": 0.15 }),
|
||||
);
|
||||
}
|
||||
|
||||
// Real convergence: each running agent beams toward its active-run node.
|
||||
for (run_id, agent_id) in &runs {
|
||||
let node_id = format!("run:{}", &run_id[..run_id.len().min(8)]);
|
||||
|
||||
Reference in New Issue
Block a user