+
+ {
+ const v = Number(event.currentTarget.value)
+ if (Number.isFinite(v) && v >= 1) props.controller.setRecapWindowDays(v)
+ }}
+ onBlur={() => props.controller.commitRecapWindowDays()}
+ placeholder={String(props.controller.defaults()?.recapWindowDays ?? 7)}
+ aria-label={language.t("settings.general.row.dataStorage.recapWindow.title")}
+ />
+
+
)
}
diff --git a/packages/app-bundle/overlay/packages/app/src/context/settings.tsx b/packages/app-bundle/overlay/packages/app/src/context/settings.tsx
index 299cbc27..f9899484 100644
--- a/packages/app-bundle/overlay/packages/app/src/context/settings.tsx
+++ b/packages/app-bundle/overlay/packages/app/src/context/settings.tsx
@@ -49,6 +49,7 @@ export interface Settings {
storage: {
databasePath: string
configDir: string
+ recapWindowDays: number
}
developer: {
enabled: boolean
@@ -214,6 +215,7 @@ const defaultSettings: Settings = {
storage: {
databasePath: "",
configDir: "",
+ recapWindowDays: 7,
},
developer: {
enabled: false,
@@ -560,6 +562,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setConfigDir(value: string) {
setStore("storage", "configDir", value)
},
+ recapWindowDays: withFallback(() => store.storage?.recapWindowDays, defaultSettings.storage.recapWindowDays),
+ setRecapWindowDays(value: number) {
+ setStore("storage", "recapWindowDays", value)
+ },
},
developer: {
enabled: withFallback(() => store.developer?.enabled, defaultSettings.developer.enabled),
diff --git a/packages/app-bundle/overlay/packages/app/src/i18n/en.ts b/packages/app-bundle/overlay/packages/app/src/i18n/en.ts
index a12f75f8..897b66bd 100644
--- a/packages/app-bundle/overlay/packages/app/src/i18n/en.ts
+++ b/packages/app-bundle/overlay/packages/app/src/i18n/en.ts
@@ -1046,6 +1046,9 @@ export const dict = {
"settings.general.row.dataStorage.configDir.description":
"Override the opencode configuration directory. Leave empty to use the default location.",
"settings.general.row.dataStorage.configDir.error.invalidPath": "Directory does not exist",
+ "settings.general.row.dataStorage.recapWindow.title": "Session recap window",
+ "settings.general.row.dataStorage.recapWindow.description":
+ "How many days of recent sessions to include in the context prompt. Requires a server restart to take effect.",
"settings.general.row.releaseNotes.title": "Release notes",
"settings.general.row.releaseNotes.description": "Show What's New popups after updates",
diff --git a/packages/extension/opencode-plugin/session_recap.ts b/packages/extension/opencode-plugin/session_recap.ts
index 762f8fca..6b0992f1 100644
--- a/packages/extension/opencode-plugin/session_recap.ts
+++ b/packages/extension/opencode-plugin/session_recap.ts
@@ -46,6 +46,16 @@ export const RECAP_WINDOW_DAYS = 7;
export const MAX_RECAPS = 10;
export const MIN_ASSISTANT_MESSAGES = 2;
+/** Resolve the effective recap window in days. Reads AMICODE_SESSION_RECAP_WINDOW_DAYS
+ * from the environment; falls back to RECAP_WINDOW_DAYS if unset or invalid. */
+export function resolveWindowDays(): number {
+ const raw = process.env.AMICODE_SESSION_RECAP_WINDOW_DAYS;
+ if (raw == null || raw.trim() === "") return RECAP_WINDOW_DAYS;
+ const parsed = Number(raw);
+ if (!Number.isFinite(parsed) || parsed <= 0) return RECAP_WINDOW_DAYS;
+ return parsed;
+}
+
// Titles that indicate noise sessions (internal housekeeping)
export const NOISE_TITLE_PREFIXES = ["Compaction", "compaction"];
@@ -168,8 +178,9 @@ export function composeRecapText(userTexts: string[], outcomes: string[]): strin
// ── Markdown composition (pure, testable) ────────────────────────────────────
-export function composeMarkdown(recaps: SessionRecap[]): string {
- const lines = ["## Recent sessions (last 7 days)", ""];
+export function composeMarkdown(recaps: SessionRecap[], windowDays?: number): string {
+ const days = windowDays ?? resolveWindowDays();
+ const lines = [`## Recent sessions (last ${days} days)`, ""];
for (const r of recaps) {
const date = new Date(r.created);
@@ -263,7 +274,8 @@ export function buildRecentSessionsBlock(currentSessionId?: string): string | nu
}
try {
- const cutoff = Date.now() - RECAP_WINDOW_DAYS * 24 * 60 * 60 * 1000;
+ const windowDays = resolveWindowDays();
+ const cutoff = Date.now() - windowDays * 24 * 60 * 60 * 1000;
// Query recent sessions: non-subagent, non-archived, within window
const sessions = db.prepare(`
@@ -320,7 +332,7 @@ export function buildRecentSessionsBlock(currentSessionId?: string): string | nu
if (recaps.length === 0) return null;
- return composeMarkdown(recaps);
+ return composeMarkdown(recaps, windowDays);
} catch (e) {
console.error(`[session-recap] failed: ${e instanceof Error ? e.message : String(e)}`);
return null;
diff --git a/packages/extension/package.json b/packages/extension/package.json
index 9581e360..2dd7d9ff 100644
--- a/packages/extension/package.json
+++ b/packages/extension/package.json
@@ -251,6 +251,12 @@
"default": "",
"description": "Override the session database path. The value is injected as OPENCODE_DB into the spawned server process. Empty = opencode uses its XDG default (~/.local/share/opencode/opencode.db)."
},
+ "amicode.sessionRecapWindowDays": {
+ "type": "number",
+ "default": 7,
+ "minimum": 1,
+ "description": "How many days of recent sessions to show in the context prompt. Injected as AMICODE_SESSION_RECAP_WINDOW_DAYS."
+ },
"amicode.configDir": {
"type": "string",
"default": "",
diff --git a/packages/extension/src/chat_bridge.ts b/packages/extension/src/chat_bridge.ts
index d225030b..d6b3411b 100644
--- a/packages/extension/src/chat_bridge.ts
+++ b/packages/extension/src/chat_bridge.ts
@@ -727,6 +727,7 @@ export function handleAmicodeBridgeMessage(msg: unknown, io: BridgeIo): boolean
kind: "data-storage-defaults",
databasePath: shorten(defaultDbPath),
configDir: shorten(defaultConfigDir),
+ recapWindowDays: 7,
tab: msg.tab,
});
return true;
@@ -739,6 +740,9 @@ export function handleAmicodeBridgeMessage(msg: unknown, io: BridgeIo): boolean
const configDir = typeof (msg as { configDir?: unknown }).configDir === "string"
? (msg as unknown as { configDir: string }).configDir.trim().replace(/^~/, os.homedir())
: "";
+ const recapWindowDays = typeof (msg as { recapWindowDays?: unknown }).recapWindowDays === "number"
+ ? (msg as unknown as { recapWindowDays: number }).recapWindowDays
+ : 7;
const reply: {
source: "amicode"; kind: "data-storage-status"; tab?: string;
@@ -804,6 +808,12 @@ export function handleAmicodeBridgeMessage(msg: unknown, io: BridgeIo): boolean
"configDir", configDir, vscode.ConfigurationTarget.Global,
);
}
+ // Recap window: always valid (clamped to >= 1 on the client side)
+ if (recapWindowDays >= 1) {
+ void vscode.workspace.getConfiguration("amicode").update(
+ "sessionRecapWindowDays", recapWindowDays, vscode.ConfigurationTarget.Global,
+ );
+ }
// Restart the server so it picks up the new env vars
if (reply.databaseValid && reply.configValid) {
diff --git a/packages/extension/src/extension.ts b/packages/extension/src/extension.ts
index f9cd058c..ff643e66 100644
--- a/packages/extension/src/extension.ts
+++ b/packages/extension/src/extension.ts
@@ -316,8 +316,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise