the status dot was on the wrong axis
a commander watching its folder perfectly showed AMBER, titled "Armed, not currently running", because green was keyed on `running` — which is an agent TURN in progress, not the loop. so the healthy steady state, which is what a commander is in for weeks at a time, was painted as the failure colour and the owner reasonably concluded nothing was watching. the failure amber was designed for could not happen either: watcherArmed is computed from the live pid, so a dead loop turns it off rather than leaving the pair mismatched. the server now reports `watcherRunning` separately — the loop pid is alive, right now — which is what makes the distinction available at all. green is watching, amber is a row that says armed while its loop is gone, grey is a commander that never watched. and the flow: after arming, do not wait for the backlog. the pipeline was proved on one item, so the rest working is expected rather than news — say it is armed and move to the next question. a failure in the backlog is worth saying; a success is not. then ask what comes next, because a watcher that quietly does one job is a complete answer and the conversation should offer it rather than assume it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -608,8 +608,10 @@ function MetaBar({ sessionId, title, isConnected, isGenerating, onDisconnect }:
|
||||
</span>
|
||||
{commander.watcherArmed ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<span className={`h-1.5 w-1.5 rounded-full ${commander.running ? 'bg-success' : 'bg-warning'}`} />
|
||||
watcher armed
|
||||
<span
|
||||
className={`h-1.5 w-1.5 rounded-full ${commander.watcherRunning ? 'bg-success' : 'bg-warning'}`}
|
||||
/>
|
||||
{commander.watcherRunning ? 'watching' : 'armed, loop not running'}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => disarmWatcher.mutate()}
|
||||
|
||||
@@ -15,14 +15,21 @@ import { useTranslation } from '../../locales';
|
||||
// `docs/navigation-audit.md`.
|
||||
|
||||
/**
|
||||
* Three states, not two — the pair `COMMS/command-hq/mobile-4.md` proposed and the design record adopted:
|
||||
* green running, amber armed-but-not (the failure `watcherArmed && !running` exists to make visible), grey
|
||||
* neither. `running` alone cannot tell "never meant to watch" from "was watching and stopped"; `watcherArmed`
|
||||
* alone cannot tell "briefly between fire and re-arm" (normal) from "silently dead". Only the pair says it.
|
||||
* Three states, on the right axis.
|
||||
*
|
||||
* Green is the LOOP being alive — a commander watching its folder, which is the whole point of one and its
|
||||
* normal condition for weeks at a time. Amber is a row that says armed while the loop is gone: the genuine
|
||||
* failure, and the only thing worth a warning colour. Grey is a commander that was never watching.
|
||||
*
|
||||
* This used to key green on `running`, which is an agent TURN in progress. So a perfectly healthy watcher
|
||||
* with nobody chatting to it showed amber, titled "Armed, not currently running", and the owner reasonably
|
||||
* concluded nothing was running. The failure amber was designed for could not happen either: `watcherArmed`
|
||||
* is computed from the live pid, so a dead loop turns it off rather than leaving the pair mismatched. The
|
||||
* server reports `watcherRunning` separately now, which is what makes the distinction available at all.
|
||||
*/
|
||||
function StatusDot({ running, watcherArmed }: { running: boolean; watcherArmed: boolean }) {
|
||||
const color = running ? 'bg-success' : watcherArmed ? 'bg-warning' : 'bg-muted-foreground/40';
|
||||
const title = running ? 'Running' : watcherArmed ? 'Armed, not currently running' : 'Not running';
|
||||
function StatusDot({ watcherRunning, watcherArmed }: { watcherRunning: boolean; watcherArmed: boolean }) {
|
||||
const color = watcherRunning ? 'bg-success' : watcherArmed ? 'bg-warning' : 'bg-muted-foreground/40';
|
||||
const title = watcherRunning ? 'Watching' : watcherArmed ? 'Armed, but its loop is not running' : 'Not watching';
|
||||
return <span className={`inline-block h-2 w-2 shrink-0 rounded-full ${color}`} title={title} />;
|
||||
}
|
||||
|
||||
@@ -63,7 +70,7 @@ const Card = ({ commander, to, selected, onDeleted }: CardProps) => {
|
||||
<Link to={to} className="absolute inset-0" aria-label={commander.title} />
|
||||
|
||||
<div className="flex items-center gap-2 pr-6">
|
||||
<StatusDot running={commander.running} watcherArmed={commander.watcherArmed} />
|
||||
<StatusDot watcherRunning={commander.watcherRunning === true} watcherArmed={commander.watcherArmed} />
|
||||
<span className="truncate text-sm font-medium">{commander.title}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
|
||||
@@ -41,6 +41,8 @@ export type CommandHqCommander = {
|
||||
tagIds: number[];
|
||||
running: boolean;
|
||||
watcherArmed: boolean;
|
||||
/** The loop process is alive right now. Distinct from `running`, which is an agent TURN. */
|
||||
watcherRunning?: boolean;
|
||||
watcherArmedAt: string | null;
|
||||
watcherStoppedAt: string | null;
|
||||
watcherLastFiredAt: string | null;
|
||||
@@ -58,6 +60,8 @@ export type CommandHqCommanderDetail = {
|
||||
tagIds: number[];
|
||||
running: boolean;
|
||||
watcherArmed: boolean;
|
||||
/** The loop process is alive right now. Distinct from `running`, which is an agent TURN. */
|
||||
watcherRunning?: boolean;
|
||||
watcherArmedAt: string | null;
|
||||
watcherStoppedAt: string | null;
|
||||
watcherLastFiredAt: string | null;
|
||||
|
||||
Reference in New Issue
Block a user