feat: Read My Mind phase 2, the predictor and the brain button

The feature as pitched in docs/readmymind-plan.md: pressing the header
brain button predicts the prompt you were about to type, from the case's
intent profile plus everything the session already knows.

Backend:
- readmymind-context.ts: pure budgeted context assembler (9 ranked
  sources: pending approval dialog, user goals, last assistant turn tail,
  recent prompts, tool activity, git workspace signals, away context,
  sibling sessions, rethink state; 30 KB budget, whole-section drop from
  the bottom of the ranking, trust tiers stated in the prompt)
- readmymind-collectors.ts: transcript tail reader (the live watcher
  keeps only a 500-char snippet) and git signal collection (execFile,
  2s timeout, skipped for remote-SSH cases)
- readmymind-predictor.ts: one-shot claude -p in a throwaway tmux
  session, opus by default (readMyMindModel setting), strict JSON
  contract with 1-3 suggestions (continue / verify / redirect), newline
  stripping, 90s timeout; mutable singleton so route tests can stub it
- POST /api/sessions/:id/readmymind: claude-mode only (400), one
  prediction in flight per session (409 CONFLICT), rethink body
  { steer, rejected }; ownership via findSessionOrFail

Frontend:
- readmymind-ui.js (loadorder 11.3): header brain button, marker-hidden
  until readMyMindEnabled is ON, desktop only (phone key is phase 3);
  modal with editable suggestion + rationale and Send / Insert /
  Rethink / Dismiss; suggestion text rendered via value/textContent only
  and nothing ever auto-sends
- App Settings -> Panels checkbox for readMyMindEnabled; en + zh-CN
  strings

Verified end to end against a live isolated instance: transcript
capture, a real opus prediction grounded in the stated goals, rethink
steering, the 409, and the browser modal incl. Insert leaving the text
unsubmitted on the composer. 41 new unit/route tests; full test:ci
sweep green (4680 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-09 23:05:11 +02:00
parent 8a6570e22d
commit 94abcf29dc
26 changed files with 1783 additions and 24 deletions
+15
View File
@@ -87,6 +87,7 @@ import {
} from './session-listener-wiring.js';
import { sessionWaits, hooksAvailableForMode } from './session-wait-registry.js';
import { intentStore } from '../intent-store.js';
import { AI_CHECK_MODEL } from '../config/ai-defaults.js';
import { approvalInbox } from './approval-inbox.js';
import {
wireRespawnListeners,
@@ -639,6 +640,8 @@ export class WebServer extends EventEmitter {
getLightSessionsState: this.getLightSessionsState.bind(this),
startTranscriptWatcher: this.startTranscriptWatcher.bind(this),
stopTranscriptWatcher: this.stopTranscriptWatcher.bind(this),
getTranscriptPath: (sessionId: string) => this.transcriptWatchers.get(sessionId)?.getPath() ?? null,
getReadMyMindModel: this.getReadMyMindModel.bind(this),
// InfraPort
mux: this.mux,
runSummaryTrackers: this.runSummaryTrackers,
@@ -1696,6 +1699,18 @@ export class WebServer extends EventEmitter {
return settings.agentSkillEnabled === true;
}
/**
* Read My Mind predictor model (docs/readmymind-plan.md): `readMyMindModel`
* setting, defaulting to the AI-checker opus model. Prediction quality is
* the product and runs only on an explicit press, so the cost profile is
* nothing like the idle checker's.
*/
private async getReadMyMindModel(): Promise<string> {
const settings = await this.readSettings();
const model = typeof settings.readMyMindModel === 'string' ? settings.readMyMindModel.trim() : '';
return model || AI_CHECK_MODEL;
}
// Helper to get model configuration from settings
private async getModelConfig(): Promise<{
defaultModel?: string;