feat: file-backed goal objectives + compaction accounting fixes
Compaction fixes (observed in a real long run): - the summary message's zeroed tokens froze the goal counter at its pre-compaction value; segments now close with the previously displayed total as a continuity floor - audits and continuations after a summary tail now take execution params (provider/model/agent/variant) from the newest non-summary assistant turn instead of inheriting agent 'compaction' and the summarize model File-backed objectives: - the objective text lives in <data-dir>/goals/<sessionId>.md, keyed by session id (one goal per session, a new goal overwrites the file); metadata carries only an objectiveFile flag so session.updated fanout stays light, and never a path — ids are pattern-validated before any filesystem access - limit raised to 5000 chars, no snapshot field: the UI fetches content via PUT/GET/DELETE /api/goals/objective/:sessionId (behind the blanket /api auth gate), writes the file before stamping metadata, and falls back to an inline objective when the write fails - the loop reads the file fresh on every tick, so objectives are live-editable mid-goal; a missing file falls back to the inline text - scheduled goal tasks write the objective file server-side; VS Code degrades to the audit note (route unavailable there by design)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useSession } from '@/sync/sync-context';
|
||||
import { getSessionGoal, type SessionGoalPayload } from '@/lib/sessionGoalMetadata';
|
||||
import { fetchGoalObjectiveContent } from '@/lib/sessionGoalActions';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
|
||||
export interface SessionGoalState {
|
||||
@@ -19,3 +21,30 @@ export function useSessionGoal(sessionId: string, directory?: string): SessionGo
|
||||
enabled,
|
||||
};
|
||||
}
|
||||
|
||||
// Effective objective text for display. Inline goals return the metadata
|
||||
// text directly; file-backed goals fetch the server-side file once per
|
||||
// goal edit (keyed by id + updatedAt). Display-only: a failed fetch yields
|
||||
// null and callers degrade gracefully (e.g. VS Code, where the OpenChamber
|
||||
// route is unavailable — the strip then shows only the audit note).
|
||||
export function useGoalObjectiveContent(sessionId: string, goal: SessionGoalPayload | null): string | null {
|
||||
const [fetched, setFetched] = React.useState<string | null>(null);
|
||||
const fetchKey = goal?.objectiveFile ? `${sessionId}:${goal.id}:${goal.updatedAt}` : '';
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!fetchKey) {
|
||||
setFetched(null);
|
||||
return undefined;
|
||||
}
|
||||
let alive = true;
|
||||
void fetchGoalObjectiveContent(sessionId).then((content) => {
|
||||
if (alive) setFetched(content);
|
||||
});
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, [fetchKey, sessionId]);
|
||||
|
||||
if (!goal) return null;
|
||||
return goal.objectiveFile ? fetched : goal.objective;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user