From aba5ecc62a981b2e3a421d2e5d3c4789ba0e3610 Mon Sep 17 00:00:00 2001 From: jnjpng Date: Mon, 16 Mar 2026 15:34:08 -0700 Subject: [PATCH] feat(statusline): include memfs metadata in command payload (#1389) Co-authored-by: Letta Code --- src/cli/App.tsx | 25 +++++++++++++++++ src/cli/helpers/statusLinePayload.ts | 32 ++++++++++++++++++++++ src/cli/helpers/statusLineSchema.ts | 6 ++++ src/cli/hooks/useConfigurableStatusLine.ts | 12 ++++++++ src/tests/cli/statusline-payload.test.ts | 20 ++++++++++++++ 5 files changed, 95 insertions(+) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 51ec61e..47b964c 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -2493,6 +2493,12 @@ export default function App({ // Configurable status line hook const sessionStatsSnapshot = sessionStatsRef.current.getSnapshot(); const contextWindowSize = llmConfigRef.current?.context_window; + const reflectionSettings = getReflectionSettings(); + const memfsEnabled = settingsManager.isMemfsEnabled(agentId); + const memfsDirectory = + memfsEnabled && agentId && agentId !== "loading" + ? getMemoryFilesystemRoot(agentId) + : null; const statusLine = useConfigurableStatusLine({ modelId: llmConfigRef.current?.model ?? null, modelDisplayName: currentModelDisplay, @@ -2511,6 +2517,12 @@ export default function App({ totalOutputTokens: sessionStatsSnapshot.usage.completionTokens, contextWindowSize, usedContextTokens: contextTrackerRef.current.lastContextTokens, + stepCount: sessionStatsSnapshot.usage.stepCount, + turnCount: contextTrackerRef.current.currentTurnId, + reflectionMode: reflectionSettings.trigger, + reflectionStepCount: reflectionSettings.stepCount, + memfsEnabled, + memfsDirectory, permissionMode: uiPermissionMode, networkPhase, terminalWidth: chromeColumns, @@ -7625,6 +7637,19 @@ export default function App({ contextWindowSize: llmConfigRef.current?.context_window, usedContextTokens: contextTrackerRef.current.lastContextTokens, + stepCount: stats.usage.stepCount, + turnCount: contextTrackerRef.current.currentTurnId, + reflectionMode: getReflectionSettings().trigger, + reflectionStepCount: getReflectionSettings().stepCount, + memfsEnabled: + agentId !== "loading" + ? settingsManager.isMemfsEnabled(agentId) + : false, + memfsDirectory: + agentId !== "loading" && + settingsManager.isMemfsEnabled(agentId) + ? getMemoryFilesystemRoot(agentId) + : null, permissionMode: uiPermissionMode, networkPhase, terminalWidth: chromeColumns, diff --git a/src/cli/helpers/statusLinePayload.ts b/src/cli/helpers/statusLinePayload.ts index 309915b..7d7ac16 100644 --- a/src/cli/helpers/statusLinePayload.ts +++ b/src/cli/helpers/statusLinePayload.ts @@ -18,6 +18,12 @@ export interface StatusLinePayloadBuildInput { totalOutputTokens?: number; contextWindowSize?: number; usedContextTokens?: number; + stepCount?: number; + turnCount?: number; + reflectionMode?: "off" | "step-count" | "compaction-event" | null; + reflectionStepCount?: number; + memfsEnabled?: boolean; + memfsDirectory?: string | null; permissionMode?: string; networkPhase?: "upload" | "download" | "error" | null; terminalWidth?: number; @@ -82,6 +88,16 @@ export interface StatusLinePayload { id: string | null; name: string | null; }; + step_count: number; + turn_count: number; + reflection: { + mode: "off" | "step-count" | "compaction-event" | null; + step_count: number; + }; + memfs: { + enabled: boolean; + memory_dir: string | null; + }; permission_mode: string | null; network_phase: "upload" | "download" | "error" | null; terminal_width: number | null; @@ -128,6 +144,12 @@ export function buildStatusLinePayload( 0, Math.floor(input.usedContextTokens ?? 0), ); + const stepCount = Math.max(0, Math.floor(input.stepCount ?? 0)); + const turnCount = Math.max(0, Math.floor(input.turnCount ?? 0)); + const reflectionStepCount = Math.max( + 0, + Math.floor(input.reflectionStepCount ?? 0), + ); const percentages = contextWindowSize > 0 @@ -175,6 +197,16 @@ export function buildStatusLinePayload( id: input.agentId ?? null, name: input.agentName ?? null, }, + step_count: stepCount, + turn_count: turnCount, + reflection: { + mode: input.reflectionMode ?? null, + step_count: reflectionStepCount, + }, + memfs: { + enabled: input.memfsEnabled ?? false, + memory_dir: input.memfsDirectory ?? null, + }, permission_mode: input.permissionMode ?? null, network_phase: input.networkPhase ?? null, terminal_width: input.terminalWidth ?? null, diff --git a/src/cli/helpers/statusLineSchema.ts b/src/cli/helpers/statusLineSchema.ts index 29a4ba2..70365b3 100644 --- a/src/cli/helpers/statusLineSchema.ts +++ b/src/cli/helpers/statusLineSchema.ts @@ -15,6 +15,12 @@ export const STATUSLINE_NATIVE_FIELDS: StatusLineFieldSpec[] = [ { path: "model.display_name" }, { path: "agent.id" }, { path: "agent.name" }, + { path: "step_count" }, + { path: "turn_count" }, + { path: "reflection.mode" }, + { path: "reflection.step_count" }, + { path: "memfs.enabled" }, + { path: "memfs.memory_dir" }, { path: "cost.total_duration_ms" }, { path: "cost.total_api_duration_ms" }, { path: "context_window.context_window_size" }, diff --git a/src/cli/hooks/useConfigurableStatusLine.ts b/src/cli/hooks/useConfigurableStatusLine.ts index 9e42459..f201c62 100644 --- a/src/cli/hooks/useConfigurableStatusLine.ts +++ b/src/cli/hooks/useConfigurableStatusLine.ts @@ -36,6 +36,12 @@ export interface StatusLineInputs { totalOutputTokens?: number; contextWindowSize?: number; usedContextTokens?: number; + stepCount?: number; + turnCount?: number; + reflectionMode?: "off" | "step-count" | "compaction-event" | null; + reflectionStepCount?: number; + memfsEnabled?: boolean; + memfsDirectory?: string | null; permissionMode?: string; networkPhase?: "upload" | "download" | "error" | null; terminalWidth?: number; @@ -79,6 +85,12 @@ function toPayloadInput(inputs: StatusLineInputs): StatusLinePayloadBuildInput { totalOutputTokens: inputs.totalOutputTokens, contextWindowSize: inputs.contextWindowSize, usedContextTokens: inputs.usedContextTokens, + stepCount: inputs.stepCount, + turnCount: inputs.turnCount, + reflectionMode: inputs.reflectionMode, + reflectionStepCount: inputs.reflectionStepCount, + memfsEnabled: inputs.memfsEnabled, + memfsDirectory: inputs.memfsDirectory, permissionMode: inputs.permissionMode, networkPhase: inputs.networkPhase, terminalWidth: inputs.terminalWidth, diff --git a/src/tests/cli/statusline-payload.test.ts b/src/tests/cli/statusline-payload.test.ts index f7084c3..34d2baa 100644 --- a/src/tests/cli/statusline-payload.test.ts +++ b/src/tests/cli/statusline-payload.test.ts @@ -19,6 +19,12 @@ describe("statusLinePayload", () => { totalOutputTokens: 450, contextWindowSize: 200_000, usedContextTokens: 40_000, + stepCount: 7, + turnCount: 3, + reflectionMode: "step-count", + reflectionStepCount: 10, + memfsEnabled: true, + memfsDirectory: "/Users/test/.letta/agents/agent-123/memory", permissionMode: "default", networkPhase: "download", terminalWidth: 120, @@ -31,6 +37,14 @@ describe("statusLinePayload", () => { expect(payload.model.display_name).toBe("Sonnet"); expect(payload.context_window.used_percentage).toBe(20); expect(payload.context_window.remaining_percentage).toBe(80); + expect(payload.step_count).toBe(7); + expect(payload.turn_count).toBe(3); + expect(payload.reflection.mode).toBe("step-count"); + expect(payload.reflection.step_count).toBe(10); + expect(payload.memfs.enabled).toBe(true); + expect(payload.memfs.memory_dir).toBe( + "/Users/test/.letta/agents/agent-123/memory", + ); expect(payload.permission_mode).toBe("default"); expect(payload.network_phase).toBe("download"); expect(payload.terminal_width).toBe(120); @@ -45,6 +59,12 @@ describe("statusLinePayload", () => { expect(payload.transcript_path).toBeNull(); expect(payload.output_style.name).toBeNull(); expect(payload.vim).toBeNull(); + expect(payload.step_count).toBe(0); + expect(payload.turn_count).toBe(0); + expect(payload.reflection.mode).toBeNull(); + expect(payload.reflection.step_count).toBe(0); + expect(payload.memfs.enabled).toBe(false); + expect(payload.memfs.memory_dir).toBeNull(); expect(payload.cost.total_cost_usd).toBeNull(); expect(payload.context_window.current_usage).toBeNull(); });