diff --git a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/EmbeddableChat.tsx b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/EmbeddableChat.tsx
index bdcda6d6..970827db 100644
--- a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/EmbeddableChat.tsx
+++ b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/EmbeddableChat.tsx
@@ -44,6 +44,14 @@ type EmbeddableChatProps = {
* a commander opened in /chat had `context: 'chat'` and silently lost the control.
*/
isCommander?: boolean;
+ /**
+ * A detached loop this conversation armed, shown above the composer beside the background tasks.
+ *
+ * Passed in rather than derived, because a watcher is not a background task: it has no task id and is
+ * not in `messages` at all — the platform knows about it from a pid file, and only the screen holding
+ * the commander's metadata can say what it watches.
+ */
+ watcher?: { label: string; running: boolean };
/**
* Refuse input entirely, with a reason the composer shows.
*
diff --git a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts
index 7e46354a..5bbebba1 100644
--- a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts
+++ b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts
@@ -52,6 +52,7 @@ type UseEmbeddableChatParams = {
contextId?: string;
/** This session is a commander, per `GET /chat/sessions/:id`. See the note where it is surfaced. */
isCommander?: boolean;
+ watcher?: { label: string; running: boolean };
emptyState?: ReactNode;
};
@@ -624,6 +625,7 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams, onMessageComp
would lose the brand-new composer, which has no session yet.
*/
isCommander: params.context === 'command-hq' || params.isCommander === true,
+ watcher: params.watcher,
emptyState: params.emptyState,
/*
Claude's transcript id, which is what a commander is keyed on everywhere — the Command HQ row, the
diff --git a/src/workspaces/officerdev/src/apps/Chat/components/BackgroundTaskTray.tsx b/src/workspaces/officerdev/src/apps/Chat/components/BackgroundTaskTray.tsx
index 59e86208..ac2dbf66 100644
--- a/src/workspaces/officerdev/src/apps/Chat/components/BackgroundTaskTray.tsx
+++ b/src/workspaces/officerdev/src/apps/Chat/components/BackgroundTaskTray.tsx
@@ -243,6 +243,28 @@ const TaskStrip = ({ label, children, trailing, divided }: TaskStripProps) => (
);
+/**
+ * The commander's own watcher, docked in the same strip as the background tasks.
+ *
+ * A watcher is not a background TASK — it is a detached loop that outlives the turn that armed it, so it
+ * has no task id and never appears in `messages`. It is the same KIND of thing to the person reading:
+ * something running on their behalf that they want to see without scrolling. So it reads the same, sits
+ * in the same place, and says what it is watching rather than that something is watching.
+ */
+export const WatcherStrip = ({ label, running }: { label: string; running: boolean }) => (
+