a spawned session says who started it
The other half of making a spawned agent visible. The commander's pane now lists its children; opening one gave no way back — no link to the conversation that owns it, and nothing to tell it apart from a chat somebody had started themselves and forgotten. `parentCommanderOf` is the row `ingestSpawnedChildren` already writes, read from the other end. So this needed no table and no write path: a commander's children were recorded because deleting one has to take them with it, and that record answers "who is my parent" for free. Nothing had asked. `GET /chat/sessions/:id` carries `spawnedBy`, and the chat panel renders one line above the transcript linking back to the commander. Null for almost every session, which is the normal case rather than a miss — a chat somebody opened has no parent — so the bar renders nothing at all rather than saying so. Never fatal: a session must open when this lookup will not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { useWorkspace } from '../../components/Workspace';
|
||||
import { useActiveChatSession, useFilesRefresh, usePreviewRefresh } from '../../channels';
|
||||
import { useChat } from '../../hooks/useChat';
|
||||
@@ -21,8 +22,30 @@ type ResumeState = {
|
||||
offset: number;
|
||||
/** Carried so the composer offers a commander's own controls here, not only in Command HQ. */
|
||||
isCommander: boolean;
|
||||
/** The commander that started this, when one did — see `SpawnedByBar`. */
|
||||
spawnedBy?: { sessionId: string; name: string | null } | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* "Started by <commander>", above a transcript that was spawned rather than opened.
|
||||
*
|
||||
* A spawned agent has always rendered here perfectly — it writes an ordinary transcript and this view reads
|
||||
* it like any other. What it could never say was where it came from, so somebody who followed a link into
|
||||
* one had no way back to the conversation that owns it, and no way to tell it apart from a chat they had
|
||||
* started themselves and forgotten.
|
||||
*
|
||||
* Renders nothing for the overwhelming majority of sessions, which have no parent. That is the normal case
|
||||
* and it does not want a bar saying so.
|
||||
*/
|
||||
const SpawnedByBar = ({ parent }: { parent: { sessionId: string; name: string | null } }) => (
|
||||
<div className="shrink-0 border-b border-border px-3 py-1.5 text-xs text-muted-foreground">
|
||||
Started by{' '}
|
||||
<Link to={`/command-hq/${parent.sessionId}`} className="text-foreground/80 underline hover:text-foreground">
|
||||
{parent.name ?? 'a commander'}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Transcripts get extremely long, so only the tail is loaded up front; the chat pages older messages in
|
||||
// on scroll. Matches the /chat screen.
|
||||
const CHAT_TAIL = 20;
|
||||
@@ -105,6 +128,7 @@ const ChatPanelInner = ({
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<AgentIdentityBar panel={agentPanel} />
|
||||
{resume?.spawnedBy && <SpawnedByBar parent={resume.spawnedBy} />}
|
||||
<EmbeddableChat
|
||||
className="min-h-0 flex-1"
|
||||
chat={chat}
|
||||
@@ -234,6 +258,7 @@ export const ChatPanelWrapper = ({ panelId, promptPrefix }: ChatPanelWrapperProp
|
||||
total: detail.total,
|
||||
offset: detail.offset,
|
||||
isCommander: detail.isCommander === true,
|
||||
spawnedBy: detail.spawnedBy ?? null,
|
||||
});
|
||||
} catch {
|
||||
// A stale id — the transcript was deleted or pruned from under us. Open a NEW conversation rather
|
||||
|
||||
@@ -104,6 +104,14 @@ export type ClaudeSessionDetail = {
|
||||
* to decide it from the route, so the same commander opened in /chat quietly lost it.
|
||||
*/
|
||||
isCommander?: boolean;
|
||||
/**
|
||||
* The commander that spawned this session, if one did.
|
||||
*
|
||||
* Read from the row a commander already keeps of its children — so this is the parent side of a link that
|
||||
* existed for a different reason (deleting a commander has to take its children with it). Absent for
|
||||
* almost every conversation, and absence means "nobody spawned this", never "the lookup failed".
|
||||
*/
|
||||
spawnedBy?: { sessionId: string; name: string | null; firstSeenAt: string; finishedAt: string | null } | null;
|
||||
};
|
||||
|
||||
export type ClaudePwd = { cwd: string; sessionCount: number; updatedAt: string; isDefault: boolean };
|
||||
|
||||
Reference in New Issue
Block a user