command hq: auto-name a commander from its watcher's own task description
The initial title (the mechanics preamble, truncated, when a background job is
the very first message) is expected — that's just Claude Code naming the
session from its first message, same as always. But by the time the watcher's
task actually starts, the harness has already handed it a real description
("Watch google.com reachability" for a ping watcher), and that reads far
better than the preamble ever will.
Hooked into the exact moment pendingArmWatcher is consumed — the FIRST arm,
never a self-re-arm, which the watcher-loop code already had to distinguish
for its own reasons. Resolves a fresh ChatIdentity via resolveHomeDir and
calls renameClaudeSession with msg.description. Best-effort: a failure is
logged and does not touch watcherArmed.
Known gap, noted in the doc rather than fixed: the detail panel's title bar
is fetched once on mount and won't live-refresh when this fires while it's
open. The grid's cards don't have this problem since GET /commanders already
polls every 15s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
||||
recordCommandHqTurn,
|
||||
} from 'officerdb';
|
||||
import { resolveHomeDir } from '@@/user-home';
|
||||
import { renameClaudeSession, type ChatIdentity } from './claude-sessions';
|
||||
import { claudeLoginState } from '@@/os-user-claude';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { logger } from './logger';
|
||||
@@ -539,6 +540,39 @@ function createMessageHandler(
|
||||
void armCommandHqWatcher(userId, transcriptId, taskId).catch((err: unknown) => {
|
||||
logger.warn('Could not arm commander watcher', { sessionId, transcriptId, error: String(err) });
|
||||
});
|
||||
|
||||
/*
|
||||
Auto-name the commander from the agent's own description of what it just started —
|
||||
"Watch google.com reachability" reads better than the mechanics-preamble text every fresh
|
||||
commander is titled with initially (expected, not a bug: that IS the first message, and
|
||||
Claude Code names sessions from it the same way it always has). This is the one moment we
|
||||
know for certain this is the FIRST task the watcher produced, not a self-re-arm — the
|
||||
`else` branch below is exactly what handles every later one, and never reaches here.
|
||||
*/
|
||||
const description = msg.description?.trim();
|
||||
if (description) {
|
||||
const capturedTranscriptId = transcriptId;
|
||||
const capturedCwd = session.cwd;
|
||||
const capturedEmail = session.email;
|
||||
void resolveHomeDir(userId)
|
||||
.then((resolved) => {
|
||||
if (!resolved.ok) return;
|
||||
const who: ChatIdentity = {
|
||||
email: capturedEmail,
|
||||
home: resolved.home,
|
||||
osUser: resolved.osUser,
|
||||
isOwner: resolved.isOwner,
|
||||
};
|
||||
renameClaudeSession(who, capturedCwd, capturedTranscriptId, description);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
logger.warn('Could not auto-name commander from watcher description', {
|
||||
sessionId,
|
||||
transcriptId: capturedTranscriptId,
|
||||
error: String(err),
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Not a fresh arm — but if this commander is ALREADY armed, a new task starting is exactly what
|
||||
// the agent's own self-re-arm produces (`handoffContract`: same turn, same command, new task id).
|
||||
|
||||
Reference in New Issue
Block a user