mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-08 02:37:45 +02:00
[eric] canvas: tether labels wear the glass capsule, pill widgets go dark, sub-agents stop leaking their prompt as a title, stale plans yield to live steps
This commit is contained in:
@@ -70,17 +70,16 @@ const TetherLayer: React.FC<TetherLayerProps> = ({ tethers, c }) => {
|
||||
markerEnd="url(#tether-arrow)"
|
||||
/>
|
||||
{t.label && (
|
||||
// Same glass capsule language as the narrator pills; the old white accent-bordered box read as a stray form element.
|
||||
<g transform={`translate(${t.labelX},${t.labelY})`}>
|
||||
<rect
|
||||
x={-4}
|
||||
y={-14}
|
||||
width={t.label.length * 7.5 + 8}
|
||||
height={20}
|
||||
rx={4}
|
||||
fill={c.bg.surface}
|
||||
stroke={c.accent.primary}
|
||||
strokeWidth={1}
|
||||
opacity={0.95}
|
||||
x={-6}
|
||||
y={-15}
|
||||
width={t.label.length * 7.5 + 12}
|
||||
height={22}
|
||||
rx={11}
|
||||
fill="rgba(24,14,32,0.85)"
|
||||
opacity={0.98}
|
||||
/>
|
||||
<text
|
||||
x={t.label.length * 7.5 / 2}
|
||||
@@ -89,7 +88,7 @@ const TetherLayer: React.FC<TetherLayerProps> = ({ tethers, c }) => {
|
||||
fontSize={11}
|
||||
fontWeight={600}
|
||||
fontFamily="inherit"
|
||||
fill={c.accent.primary}
|
||||
fill="rgba(255,255,255,0.92)"
|
||||
>
|
||||
{t.label}
|
||||
</text>
|
||||
|
||||
@@ -40,10 +40,14 @@ function AgentNarratorPill({ label, running, todos, liveSteps, artifact, askPair
|
||||
// Live tool steps window to the most recent, since earlier ones are history, not plan.
|
||||
const visibleSteps = running && !visibleTodos.length ? (liveSteps || []).slice(-MAX_VISIBLE_TODOS) : [];
|
||||
const earlierSteps = running && !visibleTodos.length ? Math.max(0, (liveSteps?.length || 0) - visibleSteps.length) : 0;
|
||||
// A plan/progress widget the agent posted once goes STALE while work continues; live steps outrank
|
||||
// it until the turn ends. Answer-shaped widgets (weather, tables) still win the ladder.
|
||||
const staleplan = running && artifact && /plan|progress/i.test(artifactName(artifact)) && visibleSteps.length > 0;
|
||||
const shownArtifact = staleplan ? null : artifact;
|
||||
const ring = selected || highlighted ? { outline: '2px solid #3b82f6', outlineOffset: '2px' } : undefined;
|
||||
const liveAsk = askPair && sessionId ? askPair : null;
|
||||
// One key per ladder state so a state CHANGE remounts the artifact and replays the one-shot entrance; nothing loops.
|
||||
const artifactKey = liveAsk ? `ask-${liveAsk.id}` : artifact ? 'widget' : browserShot ? 'shot' : visibleTodos.length > 0 ? 'todos' : visibleSteps.length > 0 ? 'steps' : running ? 'thinking' : finalText ? 'final' : 'none';
|
||||
const artifactKey = liveAsk ? `ask-${liveAsk.id}` : shownArtifact ? 'widget' : browserShot ? 'shot' : visibleTodos.length > 0 ? 'todos' : visibleSteps.length > 0 ? 'steps' : running ? 'thinking' : finalText ? 'final' : 'none';
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -91,9 +95,9 @@ function AgentNarratorPill({ label, running, todos, liveSteps, artifact, askPair
|
||||
<PillArtifactFrame key={artifactKey} name="question">
|
||||
<AskUiBubble pair={liveAsk} sessionId={sessionId!} isPending suppressReveal />
|
||||
</PillArtifactFrame>
|
||||
) : artifact ? (
|
||||
<PillArtifactFrame key={artifactKey} name={artifactName(artifact)}>
|
||||
<ShowUiWidgetView payload={artifact} ambient />
|
||||
) : shownArtifact ? (
|
||||
<PillArtifactFrame key={artifactKey} name={artifactName(shownArtifact)}>
|
||||
<ShowUiWidgetView payload={shownArtifact} ambient />
|
||||
</PillArtifactFrame>
|
||||
) : browserShot ? (
|
||||
<Box
|
||||
|
||||
@@ -80,7 +80,8 @@ function PillArtifactFrame({ name, children }: Props): React.ReactElement {
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="osw-artifact"
|
||||
// The "dark" class scopes the vendored tool-ui theme: pill artifacts always sit on the dark glass surface, so a light widget card here read as a white slab.
|
||||
className="osw-artifact dark"
|
||||
onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
|
||||
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
onDoubleClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
|
||||
@@ -23,7 +23,9 @@ export function extractLiveSteps(messages: Array<{ role: string; content: unknow
|
||||
const tool = String(body.tool || '');
|
||||
if (!tool || HIDDEN_TOOLS.test(tool)) continue;
|
||||
const done = messages[i + 1]?.role === 'tool_result';
|
||||
const lbl = getToolLabelWithInput(tool, body.input, (m as { id?: string }).id);
|
||||
// MCP names arrive prefixed ("web__WebSearch"); the checklist speaks the bare action's language.
|
||||
const bare = tool.replace(/^.*__/, '');
|
||||
const lbl = getToolLabelWithInput(bare, body.input, (m as { id?: string }).id);
|
||||
const label = done ? lbl.past : lbl.present;
|
||||
// Consecutive same-verb steps merge so "Read a file" x8 doesn't fill the card.
|
||||
const prev = steps[steps.length - 1];
|
||||
|
||||
@@ -35,6 +35,8 @@ export function displayChatTitle(session: AgentSession | null | undefined): stri
|
||||
if (session.name && !isLegacyAutoName(session.name)) {
|
||||
return session.name;
|
||||
}
|
||||
// A spawned sub-agent's first message is its parent's raw instruction; titling the card with it reads as a leak.
|
||||
if (session.parent_session_id) return 'Sub-agent';
|
||||
const firstUserMsg = session.messages?.find((m) => m.role === 'user');
|
||||
const firstUserContent = firstUserMsg && typeof firstUserMsg.content === 'string'
|
||||
? firstUserMsg.content
|
||||
|
||||
Reference in New Issue
Block a user