feat(projects): Threads im Hauptchat verankert (Stefan-Konzept)

Projekte sind benannte Thema-Bündel die voice-gesteuert via Brain-Tools
geöffnet/verlassen werden. Default-Mode bleibt der Hauptthread — Projekte
sind eine optionale Bühne. Anchored-not-replaced: App-Open landet immer
im Hauptchat, Projekte sind nur sichtbar wenn aktiv betreten.

Brain:
- projects.py: CRUD + Fuzzy-Find + Active-State-Pointer
  (/shared/config/projects.json + active_project.txt).
- conversation.py: Turn.project_id-Feld + window(project_id) Filter.
- agent.py: 6 Meta-Tools — project_create / _enter / _exit / _list /
  _summary / _end. chat() liest aktive Projekt-ID, taggt User+Assistant-
  Turns damit, filtert das LLM-Window auf Projekt-Kontext und ergaenzt
  den System-Prompt um den aktiven Projekt-Hinweis. touch_project pflegt
  last_activity_at + turn_count.
- main.py: REST-Endpoints /projects/{status,list,create,switch,
  {id}/end,{id}/archive, PATCH /{id}}.

Bridge + RVS:
- aria_bridge.py: project_changed Event-Propagation Brain → RVS-Broadcast
  damit App + Diagnostic ihre Banner refreshen.
- rvs/server.js: project_changed in ALLOWED_TYPES.

App:
- brainApi.ts: Project-Type + 6 API-Methoden.
- ProjectsBrowser.tsx (neue Komponente, ~340 Zeilen): Status-Header,
  Hauptchat als Erster-Eintrag, Projekt-Liste mit Aktiv-Marker, Long-Press
  zum Editieren, Modals fuer Neu/Edit/End/Archiv.
- ChatScreen.tsx: Banner unterhalb des Status-Bars zeigt aktives Projekt
  oder „Hauptchat" — Tap öffnet ProjectsBrowser als Modal. Aktive Projekt-
  Info wird bei Mount + bei project_changed-Events refreshed.
- SettingsScreen.tsx: Neue Section 📁 „Projekte" zeigt ProjectsBrowser inline.

Diagnostic:
- Neue Sektion im Brain-Tab mit Liste, Aktiv-Marker, Beenden/Archivieren
  pro Zeile, Modal fuer Neu. Lädt automatisch bei Brain-Tab + bei
  project_changed-Event-Broadcast.

Was bewusst NICHT drin ist (Folgeschritte):
- Per-Message Filter im Chat-Verlauf (zeigt aktuell alle Bubbles, Banner
  zeigt Kontext) — App müsste Chat-History per project_id filtern.
- Files-by-Project Tagging.
- Inline-Collapse-Bloecke im Chat-Verlauf.
- Sub-Projekte (Stefan-Entscheidung: weglassen, „Mama-tauglich").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 13:51:26 +02:00
parent f714cfc336
commit fc0f91d1e6
11 changed files with 1239 additions and 19 deletions
+69
View File
@@ -151,6 +151,24 @@ export interface OAuthAppConfig {
token_url?: string | null;
}
/** Projekt — Stefans Threading-Konzept im Hauptchat. */
export interface Project {
id: string;
name: string;
description: string;
status: 'active' | 'ended' | 'archived';
created_at: number;
updated_at: number;
last_activity_at: number;
turn_count: number;
}
export interface ProjectStatus {
active_id: string;
active: Project | null;
projects: Project[];
}
/** Skill-Manifest wie aus Brain `/skills/list` zurueckkommt. */
export interface Skill {
name: string;
@@ -521,6 +539,57 @@ export const brainApi = {
timeoutMs: 15000,
});
},
// ── Projekte ───────────────────────────────────────────────────
/** Kompletter Status: aktives Projekt + Liste. */
getProjectStatus(): Promise<ProjectStatus> {
return _send('/projects/status');
},
/** Nur die Liste — fuer Sidebar/Drawer. */
listProjects(includeArchived: boolean = false): Promise<Project[]> {
return _send(`/projects/list${includeArchived ? '?include_archived=true' : ''}`)
.then((r: any) => r?.projects || []);
},
/** Neues Projekt anlegen — wird automatisch aktiviert. */
createProject(body: { name: string; description?: string }): Promise<Project> {
return _send('/projects/create', {
method: 'POST',
body: { description: '', ...body },
});
},
/** Aktives Projekt wechseln. Leerer projectId = Hauptthread. */
switchProject(projectId: string): Promise<ProjectStatus> {
return _send('/projects/switch', {
method: 'POST',
body: { project_id: projectId },
});
},
/** Projekt als beendet markieren (bleibt sichtbar, aktiv ist dann der Hauptthread). */
endProject(projectId: string): Promise<Project> {
return _send(`/projects/${encodeURIComponent(projectId)}/end`, {
method: 'POST',
});
},
/** Projekt archivieren (verschwindet aus der Default-Liste). */
archiveProject(projectId: string): Promise<{ id: string; status: string }> {
return _send(`/projects/${encodeURIComponent(projectId)}/archive`, {
method: 'POST',
});
},
/** Projekt-Metadaten patchen (name / description). */
updateProject(projectId: string, patch: Partial<Pick<Project, 'name' | 'description'>>): Promise<Project> {
return _send(`/projects/${encodeURIComponent(projectId)}`, {
method: 'PATCH',
body: patch,
});
},
};
export default brainApi;