@dv.nghiem/flowdeck 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/memory-hook.d.ts +7 -0
- package/dist/hooks/memory-hook.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +264 -90
- package/dist/services/memory-store.d.ts +34 -1
- package/dist/services/memory-store.d.ts.map +1 -1
- package/dist/services/memory-store.test.d.ts +2 -0
- package/dist/services/memory-store.test.d.ts.map +1 -0
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-status.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,13 @@ import { type Session } from "../services/memory-store";
|
|
|
2
2
|
export declare function onSessionCreated(directory: string, contentSessionId: string, prompt?: string): Session;
|
|
3
3
|
export declare function onToolExecuted(contentSessionId: string, toolName: string, toolInput: Record<string, unknown>, toolResponse: string | null, directory: string): void;
|
|
4
4
|
export declare function onMessageUpdated(contentSessionId: string, role: string, content: string, directory: string): void;
|
|
5
|
+
/**
|
|
6
|
+
* Called when OpenCode compacts a session (session.compacted event).
|
|
7
|
+
*
|
|
8
|
+
* Previously this silently dropped the summary if the session was not in
|
|
9
|
+
* activeSessions (e.g. after plugin reload or cross-process sessions). Now it
|
|
10
|
+
* falls back to a DB lookup so the summary is never lost.
|
|
11
|
+
*/
|
|
5
12
|
export declare function onSessionCompact(contentSessionId: string, summary: string): void;
|
|
6
13
|
export declare function onSessionEnd(contentSessionId: string, lastMessage?: string): void;
|
|
7
14
|
export declare function getSessionContext(directory: string, contentSessionId: string): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-hook.d.ts","sourceRoot":"","sources":["../../src/hooks/memory-hook.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"memory-hook.d.ts","sourceRoot":"","sources":["../../src/hooks/memory-hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,OAAO,EAGb,MAAM,0BAA0B,CAAA;AAgGjC,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAYtG;AAED,wBAAgB,cAAc,CAC5B,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAChB,IAAI,CA4BN;AAED,wBAAgB,gBAAgB,CAC9B,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,IAAI,CA6BN;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA+BhF;AAED,wBAAgB,YAAY,CAAC,gBAAgB,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CA2BjF;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG;IAC9E,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,OAAO,EAAE,CAAA;CAC5B,CAIA;AAED,wBAAgB,YAAY,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED,eAAO,MAAM,UAAU;;;;;;;;CAQtB,CAAA"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAkGjD,QAAA,MAAM,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAkGjD,QAAA,MAAM,MAAM,EAAE,MAsPb,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1709,23 +1709,34 @@ import { Database } from "bun:sqlite";
|
|
|
1709
1709
|
import { existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
|
|
1710
1710
|
import { join as join15 } from "path";
|
|
1711
1711
|
import { homedir } from "os";
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
function ensureDir() {
|
|
1715
|
-
if (!existsSync15(MEMORY_DIR)) {
|
|
1716
|
-
mkdirSync10(MEMORY_DIR, { recursive: true });
|
|
1717
|
-
}
|
|
1712
|
+
function resolveMemoryDir() {
|
|
1713
|
+
return process.env.FLOWDECK_MEMORY_DIR ?? join15(homedir(), ".flowdeck-memory");
|
|
1718
1714
|
}
|
|
1715
|
+
var JS_RETRY_COUNT = 3;
|
|
1716
|
+
var JS_RETRY_BASE_MS = 50;
|
|
1719
1717
|
var db = null;
|
|
1718
|
+
function debugLog(msg) {
|
|
1719
|
+
if (process.env.FLOWDECK_MEMORY_DEBUG) {
|
|
1720
|
+
console.error(`[FlowDeck Memory] ${msg}`);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1720
1723
|
function getDb() {
|
|
1721
1724
|
if (!db) {
|
|
1722
|
-
|
|
1723
|
-
|
|
1725
|
+
const dir = resolveMemoryDir();
|
|
1726
|
+
if (!existsSync15(dir))
|
|
1727
|
+
mkdirSync10(dir, { recursive: true });
|
|
1728
|
+
const dbPath = join15(dir, "memory.db");
|
|
1729
|
+
db = new Database(dbPath);
|
|
1730
|
+
debugLog(`DB opened: ${dbPath}`);
|
|
1724
1731
|
initializeSchema(db);
|
|
1725
1732
|
}
|
|
1726
1733
|
return db;
|
|
1727
1734
|
}
|
|
1728
1735
|
function initializeSchema(database) {
|
|
1736
|
+
database.run("PRAGMA journal_mode = WAL");
|
|
1737
|
+
database.run("PRAGMA busy_timeout = 5000");
|
|
1738
|
+
database.run("PRAGMA synchronous = NORMAL");
|
|
1739
|
+
database.run("PRAGMA wal_autocheckpoint = 1000");
|
|
1729
1740
|
const schema = `
|
|
1730
1741
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
1731
1742
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -1753,6 +1764,7 @@ function initializeSchema(database) {
|
|
|
1753
1764
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1754
1765
|
session_id INTEGER NOT NULL UNIQUE,
|
|
1755
1766
|
content TEXT NOT NULL,
|
|
1767
|
+
metadata TEXT,
|
|
1756
1768
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1757
1769
|
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
1758
1770
|
);
|
|
@@ -1764,6 +1776,49 @@ function initializeSchema(database) {
|
|
|
1764
1776
|
CREATE INDEX IF NOT EXISTS idx_sessions_directory ON sessions(directory);
|
|
1765
1777
|
`;
|
|
1766
1778
|
database.run(schema);
|
|
1779
|
+
const summaryColumns = database.prepare("PRAGMA table_info(summaries)").all().map((c) => c.name);
|
|
1780
|
+
if (!summaryColumns.includes("metadata")) {
|
|
1781
|
+
database.run("ALTER TABLE summaries ADD COLUMN metadata TEXT");
|
|
1782
|
+
debugLog("Migrated summaries table: added metadata column");
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
function isBusyError(err) {
|
|
1786
|
+
if (!err || typeof err !== "object")
|
|
1787
|
+
return false;
|
|
1788
|
+
const e = err;
|
|
1789
|
+
return e.code === "SQLITE_BUSY" || (e.message?.includes("database is locked") ?? false);
|
|
1790
|
+
}
|
|
1791
|
+
function sleepSync(ms) {
|
|
1792
|
+
try {
|
|
1793
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1794
|
+
} catch {
|
|
1795
|
+
const end = Date.now() + ms;
|
|
1796
|
+
while (Date.now() < end) {}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
function executeWrite(fn, context) {
|
|
1800
|
+
for (let attempt = 0;attempt <= JS_RETRY_COUNT; attempt++) {
|
|
1801
|
+
const start = Date.now();
|
|
1802
|
+
try {
|
|
1803
|
+
const result = fn();
|
|
1804
|
+
const duration = Date.now() - start;
|
|
1805
|
+
if (attempt > 0) {
|
|
1806
|
+
debugLog(`${context}: succeeded after ${attempt} JS retr${attempt === 1 ? "y" : "ies"} (${duration}ms)`);
|
|
1807
|
+
} else {
|
|
1808
|
+
debugLog(`${context}: completed in ${duration}ms`);
|
|
1809
|
+
}
|
|
1810
|
+
return result;
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
if (isBusyError(err) && attempt < JS_RETRY_COUNT) {
|
|
1813
|
+
const delay = JS_RETRY_BASE_MS * (attempt + 1);
|
|
1814
|
+
debugLog(`${context}: SQLITE_BUSY — JS retry ${attempt + 1}/${JS_RETRY_COUNT} after ${delay}ms`);
|
|
1815
|
+
sleepSync(delay);
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1818
|
+
throw err;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
throw new Error(`${context}: exhausted all retries`);
|
|
1767
1822
|
}
|
|
1768
1823
|
function serializeToolInput(input) {
|
|
1769
1824
|
if (!input)
|
|
@@ -1791,7 +1846,7 @@ function initSession(contentSessionId, project, directory) {
|
|
|
1791
1846
|
database.prepare("UPDATE sessions SET last_active_at = ?, prompt_count = prompt_count + 1 WHERE id = ?").run(now, existing.id);
|
|
1792
1847
|
return { ...existing, last_active_at: now, prompt_count: (existing.prompt_count || 0) + 1 };
|
|
1793
1848
|
}
|
|
1794
|
-
const result = database.prepare("INSERT INTO sessions (content_session_id, project, directory, created_at, last_active_at) VALUES (?, ?, ?, ?, ?)").run(contentSessionId, project, directory, now, now);
|
|
1849
|
+
const result = database.prepare("INSERT INTO sessions (content_session_id, project, directory, created_at, last_active_at, prompt_count) VALUES (?, ?, ?, ?, ?, ?)").run(contentSessionId, project, directory, now, now, 1);
|
|
1795
1850
|
return {
|
|
1796
1851
|
id: result.lastInsertRowid,
|
|
1797
1852
|
content_session_id: contentSessionId,
|
|
@@ -1805,27 +1860,38 @@ function initSession(contentSessionId, project, directory) {
|
|
|
1805
1860
|
function storeObservation(sessionId, toolName, toolInput, toolResponse, directory) {
|
|
1806
1861
|
const database = getDb();
|
|
1807
1862
|
const now = new Date().toISOString();
|
|
1808
|
-
const
|
|
1809
|
-
|
|
1863
|
+
const serializedInput = serializeToolInput(toolInput);
|
|
1864
|
+
const truncatedResponse = toolResponse ? toolResponse.slice(0, 1e4) : null;
|
|
1865
|
+
const result = executeWrite(database.transaction(() => {
|
|
1866
|
+
const r = database.prepare("INSERT INTO observations (session_id, tool_name, tool_input, tool_response, directory, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(sessionId, toolName, serializedInput, truncatedResponse, directory, now);
|
|
1867
|
+
database.prepare("UPDATE sessions SET last_active_at = ? WHERE id = ?").run(now, sessionId);
|
|
1868
|
+
return r;
|
|
1869
|
+
}), `storeObservation(${toolName})`);
|
|
1810
1870
|
return {
|
|
1811
1871
|
id: result.lastInsertRowid,
|
|
1812
1872
|
session_id: sessionId,
|
|
1813
1873
|
tool_name: toolName,
|
|
1814
|
-
tool_input: parseToolInput(
|
|
1815
|
-
tool_response:
|
|
1874
|
+
tool_input: parseToolInput(serializedInput),
|
|
1875
|
+
tool_response: truncatedResponse,
|
|
1816
1876
|
directory,
|
|
1817
1877
|
created_at: now
|
|
1818
1878
|
};
|
|
1819
1879
|
}
|
|
1820
|
-
function storeSummary(sessionId, content) {
|
|
1880
|
+
function storeSummary(sessionId, content, metadata) {
|
|
1821
1881
|
const database = getDb();
|
|
1822
1882
|
const now = new Date().toISOString();
|
|
1823
|
-
|
|
1824
|
-
|
|
1883
|
+
const serializedMetadata = metadata ? JSON.stringify(metadata) : null;
|
|
1884
|
+
const id = executeWrite(database.transaction(() => {
|
|
1885
|
+
database.prepare("INSERT OR REPLACE INTO summaries (session_id, content, metadata, created_at) VALUES (?, ?, ?, ?)").run(sessionId, content, serializedMetadata, now);
|
|
1886
|
+
database.prepare("UPDATE sessions SET summary = ? WHERE id = ?").run(content.slice(0, 2000), sessionId);
|
|
1887
|
+
return database.prepare("SELECT last_insert_rowid() as id").get().id;
|
|
1888
|
+
}), `storeSummary(session=${sessionId})`);
|
|
1889
|
+
debugLog(`storeSummary: wrote ${content.length} chars${metadata ? ` + ${JSON.stringify(metadata).length}B metadata` : ""} for session ${sessionId}`);
|
|
1825
1890
|
return {
|
|
1826
|
-
id
|
|
1891
|
+
id,
|
|
1827
1892
|
session_id: sessionId,
|
|
1828
1893
|
content,
|
|
1894
|
+
metadata: metadata ?? null,
|
|
1829
1895
|
created_at: now
|
|
1830
1896
|
};
|
|
1831
1897
|
}
|
|
@@ -1844,6 +1910,20 @@ function getObservationsForSession(sessionId) {
|
|
|
1844
1910
|
tool_input: parseToolInput(obs.tool_input)
|
|
1845
1911
|
}));
|
|
1846
1912
|
}
|
|
1913
|
+
function getSessionSummary(sessionId) {
|
|
1914
|
+
const database = getDb();
|
|
1915
|
+
const row = database.prepare("SELECT * FROM summaries WHERE session_id = ?").get(sessionId);
|
|
1916
|
+
if (!row)
|
|
1917
|
+
return null;
|
|
1918
|
+
return {
|
|
1919
|
+
...row,
|
|
1920
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
function getSessionByContentSessionId(contentSessionId) {
|
|
1924
|
+
const database = getDb();
|
|
1925
|
+
return database.prepare("SELECT * FROM sessions WHERE content_session_id = ?").get(contentSessionId) || null;
|
|
1926
|
+
}
|
|
1847
1927
|
function getRecentObservations(directory, limit = 50) {
|
|
1848
1928
|
const database = getDb();
|
|
1849
1929
|
const rows = database.prepare(`SELECT o.*, s.project, s.content_session_id, s.created_at as session_created
|
|
@@ -1905,11 +1985,15 @@ function getContextForDirectory(directory, maxObservations = 20) {
|
|
|
1905
1985
|
lines.push(`Output: ${preview}${observation.tool_response.length > 300 ? "..." : ""}`);
|
|
1906
1986
|
}
|
|
1907
1987
|
}
|
|
1908
|
-
const
|
|
1988
|
+
const summaryRows = getDb().prepare(`SELECT su.* FROM summaries su
|
|
1909
1989
|
JOIN sessions s ON su.session_id = s.id
|
|
1910
1990
|
WHERE s.directory = ?
|
|
1911
1991
|
ORDER BY su.created_at DESC
|
|
1912
1992
|
LIMIT 3`).all(directory);
|
|
1993
|
+
const summaries = summaryRows.map((r) => ({
|
|
1994
|
+
...r,
|
|
1995
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : null
|
|
1996
|
+
}));
|
|
1913
1997
|
if (summaries.length > 0) {
|
|
1914
1998
|
lines.push(`
|
|
1915
1999
|
## Session Summaries`);
|
|
@@ -1917,12 +2001,20 @@ function getContextForDirectory(directory, maxObservations = 20) {
|
|
|
1917
2001
|
const date = sum.created_at ? new Date(sum.created_at).toLocaleDateString() : "unknown";
|
|
1918
2002
|
lines.push(`
|
|
1919
2003
|
### [${date}]`);
|
|
1920
|
-
lines.push(sum.content.slice(0,
|
|
2004
|
+
lines.push(sum.content.slice(0, 2000));
|
|
1921
2005
|
}
|
|
1922
2006
|
}
|
|
1923
2007
|
return lines.join(`
|
|
1924
2008
|
`);
|
|
1925
2009
|
}
|
|
2010
|
+
function getDbSettings() {
|
|
2011
|
+
const database = getDb();
|
|
2012
|
+
const journalMode = database.prepare("PRAGMA journal_mode").get().journal_mode;
|
|
2013
|
+
const busyTimeout = database.prepare("PRAGMA busy_timeout").get().timeout;
|
|
2014
|
+
const synchronous = database.prepare("PRAGMA synchronous").get().synchronous;
|
|
2015
|
+
const walAutocheckpoint = database.prepare("PRAGMA wal_autocheckpoint").get().wal_autocheckpoint;
|
|
2016
|
+
return { journal_mode: journalMode, busy_timeout: busyTimeout, synchronous, wal_autocheckpoint: walAutocheckpoint };
|
|
2017
|
+
}
|
|
1926
2018
|
|
|
1927
2019
|
// src/tools/memory-search.ts
|
|
1928
2020
|
var memorySearchTool = tool16({
|
|
@@ -1942,8 +2034,14 @@ var memorySearchTool = tool16({
|
|
|
1942
2034
|
return JSON.stringify({ error: "Session not found", session_id: args.session_id });
|
|
1943
2035
|
}
|
|
1944
2036
|
const observations = getObservationsForSession(targetSession.id);
|
|
2037
|
+
const summary = getSessionSummary(targetSession.id);
|
|
1945
2038
|
return JSON.stringify({
|
|
1946
2039
|
session: targetSession,
|
|
2040
|
+
summary: summary ? {
|
|
2041
|
+
content: summary.content,
|
|
2042
|
+
metadata: summary.metadata,
|
|
2043
|
+
created_at: summary.created_at
|
|
2044
|
+
} : null,
|
|
1947
2045
|
observations: observations.map((o) => ({
|
|
1948
2046
|
tool_name: o.tool_name,
|
|
1949
2047
|
tool_input: o.tool_input,
|
|
@@ -1990,69 +2088,57 @@ var memorySearchTool = tool16({
|
|
|
1990
2088
|
|
|
1991
2089
|
// src/tools/memory-status.ts
|
|
1992
2090
|
import { tool as tool17 } from "@opencode-ai/plugin";
|
|
1993
|
-
import { Database as Database2 } from "bun:sqlite";
|
|
1994
2091
|
import { existsSync as existsSync16 } from "fs";
|
|
1995
2092
|
import { join as join16 } from "path";
|
|
1996
2093
|
import { homedir as homedir2 } from "os";
|
|
1997
|
-
|
|
2094
|
+
function resolveDbPath() {
|
|
2095
|
+
return join16(process.env.FLOWDECK_MEMORY_DIR ?? join16(homedir2(), ".flowdeck-memory"), "memory.db");
|
|
2096
|
+
}
|
|
1998
2097
|
var memoryStatusTool = tool17({
|
|
1999
2098
|
description: "Check FlowDeck memory database status, statistics, and recent sessions",
|
|
2000
2099
|
args: {},
|
|
2001
|
-
async execute(_args,
|
|
2100
|
+
async execute(_args, context) {
|
|
2101
|
+
const directory = context?.directory ?? process.cwd();
|
|
2102
|
+
const dbPath = resolveDbPath();
|
|
2002
2103
|
try {
|
|
2003
|
-
const exists = existsSync16(
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
if (exists) {
|
|
2011
|
-
try {
|
|
2012
|
-
const db2 = new Database2(DB_PATH2);
|
|
2013
|
-
const sessions = db2.prepare("SELECT COUNT(*) as count FROM sessions").get();
|
|
2014
|
-
const observations = db2.prepare("SELECT COUNT(*) as count FROM observations").get();
|
|
2015
|
-
const summaries = db2.prepare("SELECT COUNT(*) as count FROM summaries").get();
|
|
2016
|
-
const recentSessions = db2.prepare(`
|
|
2017
|
-
SELECT
|
|
2018
|
-
id,
|
|
2019
|
-
content_session_id,
|
|
2020
|
-
project,
|
|
2021
|
-
directory,
|
|
2022
|
-
created_at,
|
|
2023
|
-
last_active_at,
|
|
2024
|
-
prompt_count
|
|
2025
|
-
FROM sessions
|
|
2026
|
-
ORDER BY last_active_at DESC
|
|
2027
|
-
LIMIT 5
|
|
2028
|
-
`).all();
|
|
2029
|
-
result.statistics = {
|
|
2030
|
-
sessions: sessions.count,
|
|
2031
|
-
observations: observations.count,
|
|
2032
|
-
summaries: summaries.count,
|
|
2033
|
-
recent_sessions: recentSessions.map((s) => {
|
|
2034
|
-
const obsCount = db2.prepare("SELECT COUNT(*) as count FROM observations WHERE session_id = ?").get(s.id);
|
|
2035
|
-
return {
|
|
2036
|
-
project: s.project,
|
|
2037
|
-
directory: s.directory,
|
|
2038
|
-
observations_in_session: obsCount.count,
|
|
2039
|
-
last_active: s.last_active_at,
|
|
2040
|
-
prompt_count: s.prompt_count
|
|
2041
|
-
};
|
|
2042
|
-
})
|
|
2043
|
-
};
|
|
2044
|
-
db2.close();
|
|
2045
|
-
} catch (err) {
|
|
2046
|
-
result.status = "ERROR";
|
|
2047
|
-
result.statistics = { error: String(err) };
|
|
2048
|
-
}
|
|
2104
|
+
const exists = existsSync16(dbPath);
|
|
2105
|
+
if (!exists) {
|
|
2106
|
+
return JSON.stringify({
|
|
2107
|
+
database_exists: false,
|
|
2108
|
+
path: dbPath,
|
|
2109
|
+
status: "NOT_INITIALIZED"
|
|
2110
|
+
}, null, 2);
|
|
2049
2111
|
}
|
|
2050
|
-
|
|
2112
|
+
const settings = getDbSettings();
|
|
2113
|
+
const recentSessions = getRecentSessions(directory, 5);
|
|
2114
|
+
const sessionStats = recentSessions.map((s) => {
|
|
2115
|
+
const observations = getObservationsForSession(s.id);
|
|
2116
|
+
const summary = getSessionSummary(s.id);
|
|
2117
|
+
return {
|
|
2118
|
+
project: s.project,
|
|
2119
|
+
directory: s.directory,
|
|
2120
|
+
content_session_id: s.content_session_id,
|
|
2121
|
+
observations_in_session: observations.length,
|
|
2122
|
+
last_active: s.last_active_at,
|
|
2123
|
+
prompt_count: s.prompt_count,
|
|
2124
|
+
has_summary: !!summary,
|
|
2125
|
+
summary_length: summary?.content.length ?? 0,
|
|
2126
|
+
summary_preview: summary?.content.slice(0, 200) ?? null,
|
|
2127
|
+
handoff_metadata: summary?.metadata ?? null
|
|
2128
|
+
};
|
|
2129
|
+
});
|
|
2130
|
+
return JSON.stringify({
|
|
2131
|
+
database_exists: true,
|
|
2132
|
+
path: dbPath,
|
|
2133
|
+
status: "ACTIVE",
|
|
2134
|
+
pragma_settings: settings,
|
|
2135
|
+
recent_sessions_in_directory: sessionStats
|
|
2136
|
+
}, null, 2);
|
|
2051
2137
|
} catch (err) {
|
|
2052
2138
|
return JSON.stringify({
|
|
2053
2139
|
status: "ERROR",
|
|
2054
2140
|
error: String(err),
|
|
2055
|
-
path:
|
|
2141
|
+
path: dbPath
|
|
2056
2142
|
}, null, 2);
|
|
2057
2143
|
}
|
|
2058
2144
|
}
|
|
@@ -2060,7 +2146,7 @@ var memoryStatusTool = tool17({
|
|
|
2060
2146
|
|
|
2061
2147
|
// src/hooks/memory-hook.ts
|
|
2062
2148
|
var MAX_TOOL_RESPONSE = 1e4;
|
|
2063
|
-
var
|
|
2149
|
+
var MAX_SUMMARY_STORAGE = 50000;
|
|
2064
2150
|
var activeSessions = new Map;
|
|
2065
2151
|
function extractProjectFromDirectory(directory) {
|
|
2066
2152
|
const parts = directory.split("/");
|
|
@@ -2071,6 +2157,59 @@ function truncate(str, max) {
|
|
|
2071
2157
|
return str || "";
|
|
2072
2158
|
return str.slice(0, max);
|
|
2073
2159
|
}
|
|
2160
|
+
function buildHandoffMetadata(sessionId, directory, summaryText, observations) {
|
|
2161
|
+
const fileTools = new Set(["edit", "create", "view", "read", "hash-edit", "str-replace-editor"]);
|
|
2162
|
+
const importantFilesSet = new Set;
|
|
2163
|
+
for (const obs of observations) {
|
|
2164
|
+
if (fileTools.has(obs.tool_name) && obs.tool_input) {
|
|
2165
|
+
const path = obs.tool_input.path;
|
|
2166
|
+
if (path)
|
|
2167
|
+
importantFilesSet.add(path);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
const toolNamesUsed = [...new Set(observations.map((o) => o.tool_name).filter((t) => t !== "assistant_message"))];
|
|
2171
|
+
function extractBullets(text) {
|
|
2172
|
+
return text.split(`
|
|
2173
|
+
`).filter((l) => /^\s*[-*]/.test(l)).map((l) => l.replace(/^\s*[-*]\s+/, "").trim()).filter(Boolean);
|
|
2174
|
+
}
|
|
2175
|
+
const sections = {};
|
|
2176
|
+
let currentSection = "";
|
|
2177
|
+
const currentLines = [];
|
|
2178
|
+
for (const line of summaryText.split(`
|
|
2179
|
+
`)) {
|
|
2180
|
+
const header = line.match(/^##\s+\d+\.\s+(.+)/);
|
|
2181
|
+
if (header) {
|
|
2182
|
+
if (currentSection)
|
|
2183
|
+
sections[currentSection] = currentLines.join(`
|
|
2184
|
+
`).trim();
|
|
2185
|
+
currentSection = header[1].trim();
|
|
2186
|
+
currentLines.length = 0;
|
|
2187
|
+
} else {
|
|
2188
|
+
currentLines.push(line);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (currentSection)
|
|
2192
|
+
sections[currentSection] = currentLines.join(`
|
|
2193
|
+
`).trim();
|
|
2194
|
+
const completed = extractBullets(sections["Work Completed"] ?? "");
|
|
2195
|
+
const pending = extractBullets(sections["Remaining Tasks"] ?? "");
|
|
2196
|
+
return {
|
|
2197
|
+
workflow_name: extractProjectFromDirectory(directory),
|
|
2198
|
+
current_status: "compacted",
|
|
2199
|
+
current_stage: null,
|
|
2200
|
+
completed_stages: completed,
|
|
2201
|
+
pending_stages: pending,
|
|
2202
|
+
key_decisions: [],
|
|
2203
|
+
blockers: [],
|
|
2204
|
+
important_files: [...importantFilesSet].slice(0, 30),
|
|
2205
|
+
approvals: [],
|
|
2206
|
+
open_questions: [],
|
|
2207
|
+
next_steps: pending.slice(0, 5),
|
|
2208
|
+
tool_names_used: toolNamesUsed.slice(0, 20),
|
|
2209
|
+
observation_count: observations.length,
|
|
2210
|
+
updated_at: new Date().toISOString()
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2074
2213
|
function onSessionCreated(directory, contentSessionId, prompt) {
|
|
2075
2214
|
const project = extractProjectFromDirectory(directory);
|
|
2076
2215
|
const session = initSession(contentSessionId, project, directory);
|
|
@@ -2095,7 +2234,11 @@ function onToolExecuted(contentSessionId, toolName, toolInput, toolResponse, dir
|
|
|
2095
2234
|
};
|
|
2096
2235
|
activeSessions.set(contentSessionId, ctx);
|
|
2097
2236
|
}
|
|
2098
|
-
|
|
2237
|
+
try {
|
|
2238
|
+
storeObservation(ctx.sessionId, truncate(toolName, 200), toolInput, toolResponse ? truncate(toolResponse, MAX_TOOL_RESPONSE) : null, directory);
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
console.warn(`[FlowDeck Memory] Failed to store observation for tool "${toolName}":`, err);
|
|
2241
|
+
}
|
|
2099
2242
|
}
|
|
2100
2243
|
function onMessageUpdated(contentSessionId, role, content, directory) {
|
|
2101
2244
|
if (role !== "assistant")
|
|
@@ -2114,33 +2257,64 @@ function onMessageUpdated(contentSessionId, role, content, directory) {
|
|
|
2114
2257
|
};
|
|
2115
2258
|
activeSessions.set(contentSessionId, ctx);
|
|
2116
2259
|
}
|
|
2117
|
-
|
|
2260
|
+
try {
|
|
2261
|
+
storeObservation(ctx.sessionId, "assistant_message", { role }, truncate(content, MAX_TOOL_RESPONSE), directory);
|
|
2262
|
+
} catch (err) {
|
|
2263
|
+
console.warn("[FlowDeck Memory] Failed to store assistant message observation:", err);
|
|
2264
|
+
}
|
|
2118
2265
|
}
|
|
2119
2266
|
function onSessionCompact(contentSessionId, summary) {
|
|
2120
|
-
|
|
2121
|
-
if (!ctx)
|
|
2122
|
-
|
|
2123
|
-
|
|
2267
|
+
let ctx = activeSessions.get(contentSessionId);
|
|
2268
|
+
if (!ctx) {
|
|
2269
|
+
const dbSession = getSessionByContentSessionId(contentSessionId);
|
|
2270
|
+
if (!dbSession) {
|
|
2271
|
+
console.warn(`[FlowDeck Memory] onSessionCompact: no session found for contentSessionId=${contentSessionId} — summary discarded`);
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
ctx = {
|
|
2275
|
+
sessionId: dbSession.id,
|
|
2276
|
+
contentSessionId,
|
|
2277
|
+
project: dbSession.project,
|
|
2278
|
+
directory: dbSession.directory
|
|
2279
|
+
};
|
|
2280
|
+
activeSessions.set(contentSessionId, ctx);
|
|
2281
|
+
}
|
|
2282
|
+
const storedContent = truncate(summary, MAX_SUMMARY_STORAGE);
|
|
2283
|
+
try {
|
|
2284
|
+
const observations = getObservationsForSession(ctx.sessionId);
|
|
2285
|
+
const metadata = buildHandoffMetadata(ctx.sessionId, ctx.directory, summary, observations);
|
|
2286
|
+
storeSummary(ctx.sessionId, storedContent, metadata);
|
|
2287
|
+
} catch (err) {
|
|
2288
|
+
console.warn(`[FlowDeck Memory] Failed to store compaction summary for session ${ctx.sessionId}:`, err);
|
|
2289
|
+
}
|
|
2124
2290
|
}
|
|
2125
2291
|
function onSessionEnd(contentSessionId, lastMessage) {
|
|
2126
|
-
|
|
2127
|
-
if (!ctx)
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2292
|
+
let ctx = activeSessions.get(contentSessionId);
|
|
2293
|
+
if (!ctx) {
|
|
2294
|
+
const dbSession = getSessionByContentSessionId(contentSessionId);
|
|
2295
|
+
if (dbSession) {
|
|
2296
|
+
ctx = {
|
|
2297
|
+
sessionId: dbSession.id,
|
|
2298
|
+
contentSessionId,
|
|
2299
|
+
project: dbSession.project,
|
|
2300
|
+
directory: dbSession.directory
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
if (ctx && lastMessage && lastMessage.trim()) {
|
|
2305
|
+
try {
|
|
2306
|
+
const observations = getObservationsForSession(ctx.sessionId);
|
|
2307
|
+
const metadata = buildHandoffMetadata(ctx.sessionId, ctx.directory, lastMessage, observations);
|
|
2308
|
+
storeSummary(ctx.sessionId, truncate(lastMessage, MAX_SUMMARY_STORAGE), metadata);
|
|
2309
|
+
} catch (err) {
|
|
2310
|
+
console.warn(`[FlowDeck Memory] Failed to store end-of-session summary for session ${ctx?.sessionId}:`, err);
|
|
2311
|
+
}
|
|
2131
2312
|
}
|
|
2132
2313
|
activeSessions.delete(contentSessionId);
|
|
2133
2314
|
}
|
|
2134
2315
|
function getSessionContext(directory, contentSessionId) {
|
|
2135
2316
|
const context = getContextForDirectory(directory, 30);
|
|
2136
2317
|
const previousSessions = getRecentSessions(directory, 5);
|
|
2137
|
-
if (previousSessions.length > 0 && activeSessions.has(contentSessionId)) {
|
|
2138
|
-
const ctx = activeSessions.get(contentSessionId);
|
|
2139
|
-
for (const prev of previousSessions) {
|
|
2140
|
-
if (prev.content_session_id === contentSessionId)
|
|
2141
|
-
continue;
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
2318
|
return { context, previousSessions };
|
|
2145
2319
|
}
|
|
2146
2320
|
function clearSession(contentSessionId) {
|
|
@@ -6423,7 +6597,7 @@ var plugin = async (input, _options) => {
|
|
|
6423
6597
|
const delEvent = event?.event ?? event;
|
|
6424
6598
|
const sessionId = delEvent?.sessionID ?? delEvent?.sessionId ?? "";
|
|
6425
6599
|
if (sessionId) {
|
|
6426
|
-
memoryHook.
|
|
6600
|
+
memoryHook.onSessionEnd(sessionId);
|
|
6427
6601
|
}
|
|
6428
6602
|
}
|
|
6429
6603
|
} catch (err) {
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured handoff artifact persisted alongside the text summary.
|
|
3
|
+
* Derived from observations + LLM compaction output.
|
|
4
|
+
*/
|
|
5
|
+
export interface HandoffMetadata {
|
|
6
|
+
workflow_name: string | null;
|
|
7
|
+
current_status: string;
|
|
8
|
+
current_stage: string | null;
|
|
9
|
+
completed_stages: string[];
|
|
10
|
+
pending_stages: string[];
|
|
11
|
+
key_decisions: string[];
|
|
12
|
+
blockers: string[];
|
|
13
|
+
important_files: string[];
|
|
14
|
+
approvals: string[];
|
|
15
|
+
open_questions: string[];
|
|
16
|
+
next_steps: string[];
|
|
17
|
+
tool_names_used: string[];
|
|
18
|
+
observation_count: number;
|
|
19
|
+
updated_at: string;
|
|
20
|
+
}
|
|
1
21
|
export interface Observation {
|
|
2
22
|
id?: number;
|
|
3
23
|
session_id: number;
|
|
@@ -21,6 +41,7 @@ export interface Summary {
|
|
|
21
41
|
id?: number;
|
|
22
42
|
session_id: number;
|
|
23
43
|
content: string;
|
|
44
|
+
metadata?: HandoffMetadata | null;
|
|
24
45
|
created_at?: string;
|
|
25
46
|
}
|
|
26
47
|
export interface SearchResult {
|
|
@@ -29,12 +50,24 @@ export interface SearchResult {
|
|
|
29
50
|
}
|
|
30
51
|
export declare function initSession(contentSessionId: string, project: string, directory: string): Session;
|
|
31
52
|
export declare function storeObservation(sessionId: number, toolName: string, toolInput: unknown, toolResponse: string | null, directory: string): Observation;
|
|
32
|
-
export declare function storeSummary(sessionId: number, content: string): Summary;
|
|
53
|
+
export declare function storeSummary(sessionId: number, content: string, metadata?: HandoffMetadata | null): Summary;
|
|
33
54
|
export declare function getRecentSessions(directory: string, limit?: number): Session[];
|
|
34
55
|
export declare function getObservationsForSession(sessionId: number): Observation[];
|
|
35
56
|
export declare function getSessionSummary(sessionId: number): Summary | null;
|
|
57
|
+
export declare function getSessionByContentSessionId(contentSessionId: string): Session | null;
|
|
36
58
|
export declare function getRecentObservations(directory: string, limit?: number): SearchResult[];
|
|
37
59
|
export declare function searchObservations(directory: string, query: string, limit?: number): SearchResult[];
|
|
38
60
|
export declare function getContextForDirectory(directory: string, maxObservations?: number): string;
|
|
39
61
|
export declare function closeDatabase(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Returns current connection-level PRAGMA values.
|
|
64
|
+
* Intended for diagnostics and tests — queries via the shared singleton so the
|
|
65
|
+
* values reflect what this module actually set, not a second connection.
|
|
66
|
+
*/
|
|
67
|
+
export declare function getDbSettings(): {
|
|
68
|
+
journal_mode: string;
|
|
69
|
+
busy_timeout: number;
|
|
70
|
+
synchronous: number;
|
|
71
|
+
wal_autocheckpoint: number;
|
|
72
|
+
};
|
|
40
73
|
//# sourceMappingURL=memory-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../src/services/memory-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../src/services/memory-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-store.test.d.ts","sourceRoot":"","sources":["../../src/services/memory-store.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-search.d.ts","sourceRoot":"","sources":["../../src/tools/memory-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"memory-search.d.ts","sourceRoot":"","sources":["../../src/tools/memory-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAQ/D,eAAO,MAAM,gBAAgB,EAAE,cAgF7B,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-status.d.ts","sourceRoot":"","sources":["../../src/tools/memory-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"memory-status.d.ts","sourceRoot":"","sources":["../../src/tools/memory-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAU/D,eAAO,MAAM,gBAAgB,EAAE,cAkE7B,CAAA"}
|