feat(run): show externally started Claude sessions in the active-runs list
The Workspace active-runs list only knew about runs this dashboard spawned, so two `claude` sessions started by hand in terminal tabs showed up nowhere — the list read "no active runs" while two agents were working. Poll GET /api/sessions?status=active alongside the run list and merge those sessions in as live rows, deduped against dashboard runs by session_id and filtered to local sources with a cwd (a remote-source or cwd-less session cannot be resumed on this machine). External rows get no Attach action: the dashboard owns no tmux session for them, so there is no PTY to bridge. They offer Resume, which reuses the existing ensure-lane + start-with-resumeSessionId path to spawn a new tmux-backed `claude --resume` in that folder — a second process on the same transcript, not a view of the original terminal.
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
* the selected lane's pipeline map, and run configuration/console/history below.
|
||||
* Runs are tied to lanes: starting a run posts to POST /api/lanes/:id/start.
|
||||
* When a cwd isn't owned by any lane, calls POST /api/lanes/ensure first.
|
||||
* Polls GET /api/sessions?status=active alongside the run list so Claude Code
|
||||
* sessions started outside the dashboard also appear in the active-runs list.
|
||||
*
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
@@ -42,7 +44,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Plus } from "lucide-react";
|
||||
import { api } from "../lib/api";
|
||||
import type { CwdSuggestion, RunListResponse } from "../lib/api";
|
||||
import type { Lane, LaneFeature, LaneCounts, ProofFeature, WSMessage } from "../lib/types";
|
||||
import type { Lane, LaneFeature, LaneCounts, ProofFeature, Session, WSMessage } from "../lib/types";
|
||||
import { eventBus } from "../lib/eventBus";
|
||||
import { LaneConsolePane } from "../components/run/LaneConsolePane";
|
||||
import PipelineMap from "../components/lanes/PipelineMap";
|
||||
@@ -93,6 +95,7 @@ function ConsoleArea({
|
||||
binaryStatus,
|
||||
cwdSuggestions,
|
||||
activeRuns,
|
||||
externalSessions,
|
||||
wsConnected,
|
||||
defaultCwd,
|
||||
onHasActiveRunChange,
|
||||
@@ -108,6 +111,7 @@ function ConsoleArea({
|
||||
binaryStatus: { found: boolean; path: string | null } | null;
|
||||
cwdSuggestions: CwdSuggestion[];
|
||||
activeRuns: RunListResponse | null;
|
||||
externalSessions: Session[];
|
||||
wsConnected: boolean;
|
||||
defaultCwd: string;
|
||||
onHasActiveRunChange: (val: boolean) => void;
|
||||
@@ -128,6 +132,7 @@ function ConsoleArea({
|
||||
binaryStatus={binaryStatus}
|
||||
cwdSuggestions={cwdSuggestions}
|
||||
activeRuns={activeRuns}
|
||||
externalSessions={externalSessions}
|
||||
wsConnected={wsConnected}
|
||||
defaultCwd={defaultCwd}
|
||||
onHasActiveRunChange={onHasActiveRunChange}
|
||||
@@ -149,6 +154,7 @@ function ConsoleArea({
|
||||
binaryStatus={binaryStatus}
|
||||
cwdSuggestions={cwdSuggestions}
|
||||
activeRuns={activeRuns}
|
||||
externalSessions={externalSessions}
|
||||
wsConnected={wsConnected}
|
||||
defaultCwd={defaultCwd}
|
||||
/>
|
||||
@@ -180,6 +186,10 @@ export function Workspace() {
|
||||
// Run state kept at page level: shared across every pane, or drives the
|
||||
// lane strip itself rather than any one pane's form.
|
||||
const [activeRuns, setActiveRuns] = useState<RunListResponse | null>(null);
|
||||
// Claude Code sessions running outside the dashboard (a `claude` the user
|
||||
// started in a terminal tab). They are not dashboard runs, so the run list
|
||||
// would otherwise show nothing while two agents are working.
|
||||
const [externalSessions, setExternalSessions] = useState<Session[]>([]);
|
||||
const [binaryStatus, setBinaryStatus] = useState<{ found: boolean; path: string | null } | null>(
|
||||
null
|
||||
);
|
||||
@@ -236,10 +246,6 @@ export function Workspace() {
|
||||
// `claude` is missing from PATH — leave the probe unresolved rather than
|
||||
// showing a misleading "claude missing" banner for an unrelated fault.
|
||||
.catch(() => undefined);
|
||||
api.run
|
||||
.list()
|
||||
.then(setActiveRuns)
|
||||
.catch(() => undefined);
|
||||
api.lanes
|
||||
.pipelines()
|
||||
.then((r) => setPipelineTemplates(r.pipelines))
|
||||
@@ -320,6 +326,10 @@ export function Workspace() {
|
||||
.list()
|
||||
.then(setActiveRuns)
|
||||
.catch(() => undefined);
|
||||
api.sessions
|
||||
.list({ status: "active", limit: 50, sort_by: "updated_at", sort_desc: true })
|
||||
.then((r) => setExternalSessions(r.sessions))
|
||||
.catch(() => undefined);
|
||||
}, []);
|
||||
|
||||
// Background poll so the run list and history reflect external changes
|
||||
@@ -327,6 +337,7 @@ export function Workspace() {
|
||||
// no WS event fires. Lighter than typical WS gaps; aggressive enough that
|
||||
// status flips appear within seconds without needing a manual refresh.
|
||||
useEffect(() => {
|
||||
refreshList();
|
||||
const tick = setInterval(() => {
|
||||
refreshList();
|
||||
}, 5000);
|
||||
@@ -706,6 +717,7 @@ export function Workspace() {
|
||||
binaryStatus={binaryStatus}
|
||||
cwdSuggestions={cwdSuggestions}
|
||||
activeRuns={activeRuns}
|
||||
externalSessions={externalSessions}
|
||||
wsConnected={wsConnected}
|
||||
defaultCwd={defaultCwd}
|
||||
onHasActiveRunChange={setPaneHasActiveRun}
|
||||
@@ -730,6 +742,7 @@ export function Workspace() {
|
||||
binaryStatus={binaryStatus}
|
||||
cwdSuggestions={cwdSuggestions}
|
||||
activeRuns={activeRuns}
|
||||
externalSessions={externalSessions}
|
||||
wsConnected={wsConnected}
|
||||
defaultCwd={defaultCwd}
|
||||
onHasActiveRunChange={setPaneHasActiveRun}
|
||||
|
||||
Reference in New Issue
Block a user