@hasna/conversations 0.0.8 → 0.1.0
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/README.md +222 -100
- package/bin/index.js +2349 -283
- package/bin/mcp.js +1124 -111
- package/dashboard/dist/assets/index-B6bl8Jzt.css +1 -0
- package/dashboard/dist/assets/index-Dbrg7by9.js +183 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/components/ChatView.d.ts +2 -2
- package/dist/cli/components/SessionList.d.ts +2 -2
- package/dist/index.d.ts +7 -5
- package/dist/index.js +700 -93
- package/dist/lib/messages.d.ts +22 -2
- package/dist/lib/poll.d.ts +3 -3
- package/dist/lib/presence.d.ts +7 -0
- package/dist/lib/projects.d.ts +27 -0
- package/dist/lib/projects.test.d.ts +1 -0
- package/dist/lib/spaces.d.ts +29 -0
- package/dist/lib/spaces.test.d.ts +1 -0
- package/dist/mcp/index.d.ts +1 -1
- package/dist/server/serve.d.ts +1 -1
- package/dist/types.d.ts +45 -7
- package/package.json +1 -1
- package/dashboard/dist/assets/index-C5hQqoWV.js +0 -163
- package/dashboard/dist/assets/index-Dr54QXlJ.css +0 -1
- package/dist/lib/channels.d.ts +0 -8
- /package/dist/lib/{channels.test.d.ts → presence.test.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1853,7 +1853,7 @@ function getDb() {
|
|
|
1853
1853
|
session_id TEXT NOT NULL,
|
|
1854
1854
|
from_agent TEXT NOT NULL,
|
|
1855
1855
|
to_agent TEXT NOT NULL,
|
|
1856
|
-
|
|
1856
|
+
space TEXT,
|
|
1857
1857
|
content TEXT NOT NULL,
|
|
1858
1858
|
priority TEXT NOT NULL DEFAULT 'normal',
|
|
1859
1859
|
working_dir TEXT,
|
|
@@ -1867,27 +1867,106 @@ function getDb() {
|
|
|
1867
1867
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id)");
|
|
1868
1868
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_agent)");
|
|
1869
1869
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at)");
|
|
1870
|
-
db.exec("CREATE INDEX IF NOT EXISTS
|
|
1871
|
-
const cols = db.prepare("PRAGMA table_info(messages)").all();
|
|
1872
|
-
if (!cols.some((c) => c.name === "channel")) {
|
|
1873
|
-
db.exec("ALTER TABLE messages ADD COLUMN channel TEXT");
|
|
1874
|
-
}
|
|
1870
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_space ON messages(space)");
|
|
1875
1871
|
db.exec(`
|
|
1876
|
-
CREATE TABLE IF NOT EXISTS
|
|
1872
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
1873
|
+
id TEXT PRIMARY KEY,
|
|
1874
|
+
name TEXT NOT NULL UNIQUE,
|
|
1875
|
+
description TEXT,
|
|
1876
|
+
path TEXT,
|
|
1877
|
+
created_by TEXT NOT NULL,
|
|
1878
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1879
|
+
metadata TEXT,
|
|
1880
|
+
tags TEXT,
|
|
1881
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
1882
|
+
repository TEXT,
|
|
1883
|
+
settings TEXT
|
|
1884
|
+
)
|
|
1885
|
+
`);
|
|
1886
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_projects_name ON projects(name)");
|
|
1887
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status)");
|
|
1888
|
+
db.exec(`
|
|
1889
|
+
CREATE TABLE IF NOT EXISTS spaces (
|
|
1877
1890
|
name TEXT PRIMARY KEY,
|
|
1878
1891
|
description TEXT,
|
|
1892
|
+
parent_id TEXT REFERENCES spaces(name),
|
|
1893
|
+
project_id TEXT REFERENCES projects(id),
|
|
1879
1894
|
created_by TEXT NOT NULL,
|
|
1880
|
-
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
1895
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1896
|
+
archived_at TEXT
|
|
1881
1897
|
)
|
|
1882
1898
|
`);
|
|
1899
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_spaces_parent ON spaces(parent_id)");
|
|
1900
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_spaces_project ON spaces(project_id)");
|
|
1883
1901
|
db.exec(`
|
|
1884
|
-
CREATE TABLE IF NOT EXISTS
|
|
1885
|
-
|
|
1902
|
+
CREATE TABLE IF NOT EXISTS space_members (
|
|
1903
|
+
space TEXT NOT NULL REFERENCES spaces(name),
|
|
1886
1904
|
agent TEXT NOT NULL,
|
|
1887
1905
|
joined_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1888
|
-
PRIMARY KEY (
|
|
1906
|
+
PRIMARY KEY (space, agent)
|
|
1907
|
+
)
|
|
1908
|
+
`);
|
|
1909
|
+
db.exec(`
|
|
1910
|
+
CREATE TABLE IF NOT EXISTS agent_presence (
|
|
1911
|
+
agent TEXT PRIMARY KEY,
|
|
1912
|
+
status TEXT NOT NULL DEFAULT 'online',
|
|
1913
|
+
last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1914
|
+
metadata TEXT
|
|
1889
1915
|
)
|
|
1890
1916
|
`);
|
|
1917
|
+
const existingTables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
|
|
1918
|
+
const tableNames = existingTables.map((t) => t.name);
|
|
1919
|
+
if (tableNames.includes("channels") && tableNames.includes("spaces")) {
|
|
1920
|
+
const spaceCount = db.prepare("SELECT COUNT(*) as c FROM spaces").get().c;
|
|
1921
|
+
const channelCount = db.prepare("SELECT COUNT(*) as c FROM channels").get().c;
|
|
1922
|
+
if (channelCount > 0 && spaceCount === 0) {
|
|
1923
|
+
db.exec("BEGIN");
|
|
1924
|
+
try {
|
|
1925
|
+
db.exec(`
|
|
1926
|
+
INSERT OR IGNORE INTO spaces (name, description, created_by, created_at)
|
|
1927
|
+
SELECT name, description, created_by, created_at FROM channels
|
|
1928
|
+
`);
|
|
1929
|
+
if (tableNames.includes("channel_members")) {
|
|
1930
|
+
db.exec(`
|
|
1931
|
+
INSERT OR IGNORE INTO space_members (space, agent, joined_at)
|
|
1932
|
+
SELECT channel, agent, joined_at FROM channel_members
|
|
1933
|
+
`);
|
|
1934
|
+
}
|
|
1935
|
+
db.exec("COMMIT");
|
|
1936
|
+
} catch (e) {
|
|
1937
|
+
db.exec("ROLLBACK");
|
|
1938
|
+
throw e;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
db.exec("DROP TABLE IF EXISTS channel_members");
|
|
1942
|
+
db.exec("DROP TABLE IF EXISTS channels");
|
|
1943
|
+
}
|
|
1944
|
+
const msgCols = db.prepare("PRAGMA table_info(messages)").all();
|
|
1945
|
+
const colNames = msgCols.map((c) => c.name);
|
|
1946
|
+
if (colNames.includes("channel") && !colNames.includes("space")) {
|
|
1947
|
+
db.exec("ALTER TABLE messages ADD COLUMN space TEXT");
|
|
1948
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_space ON messages(space)");
|
|
1949
|
+
db.exec("UPDATE messages SET space = channel WHERE channel IS NOT NULL");
|
|
1950
|
+
db.exec(`
|
|
1951
|
+
UPDATE messages
|
|
1952
|
+
SET session_id = 'space:' || substr(session_id, 9)
|
|
1953
|
+
WHERE session_id LIKE 'channel:%'
|
|
1954
|
+
`);
|
|
1955
|
+
}
|
|
1956
|
+
const spaceCols = db.prepare("PRAGMA table_info(spaces)").all();
|
|
1957
|
+
const spaceColNames = spaceCols.map((c) => c.name);
|
|
1958
|
+
if (!spaceColNames.includes("archived_at")) {
|
|
1959
|
+
db.exec("ALTER TABLE spaces ADD COLUMN archived_at TEXT");
|
|
1960
|
+
}
|
|
1961
|
+
const msgCols2 = db.prepare("PRAGMA table_info(messages)").all();
|
|
1962
|
+
const colNames2 = msgCols2.map((c) => c.name);
|
|
1963
|
+
if (!colNames2.includes("edited_at")) {
|
|
1964
|
+
db.exec("ALTER TABLE messages ADD COLUMN edited_at TEXT");
|
|
1965
|
+
}
|
|
1966
|
+
if (!colNames2.includes("pinned_at")) {
|
|
1967
|
+
db.exec("ALTER TABLE messages ADD COLUMN pinned_at TEXT");
|
|
1968
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_pinned ON messages(pinned_at)");
|
|
1969
|
+
}
|
|
1891
1970
|
return db;
|
|
1892
1971
|
}
|
|
1893
1972
|
function closeDb() {
|
|
@@ -1900,21 +1979,31 @@ function closeDb() {
|
|
|
1900
1979
|
// src/lib/messages.ts
|
|
1901
1980
|
import { randomUUID } from "crypto";
|
|
1902
1981
|
function parseMessage(row) {
|
|
1982
|
+
let metadata = null;
|
|
1983
|
+
if (row.metadata) {
|
|
1984
|
+
try {
|
|
1985
|
+
metadata = JSON.parse(row.metadata);
|
|
1986
|
+
} catch {
|
|
1987
|
+
metadata = null;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1903
1990
|
return {
|
|
1904
1991
|
...row,
|
|
1905
|
-
metadata
|
|
1992
|
+
metadata
|
|
1906
1993
|
};
|
|
1907
1994
|
}
|
|
1908
1995
|
function sendMessage(opts) {
|
|
1909
1996
|
const db2 = getDb();
|
|
1910
|
-
const
|
|
1997
|
+
const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
|
|
1998
|
+
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID().slice(0, 8)}`);
|
|
1911
1999
|
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
2000
|
+
const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
|
|
1912
2001
|
const stmt = db2.prepare(`
|
|
1913
|
-
INSERT INTO messages (session_id, from_agent, to_agent,
|
|
2002
|
+
INSERT INTO messages (session_id, from_agent, to_agent, space, content, priority, working_dir, repository, branch, metadata)
|
|
1914
2003
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1915
2004
|
RETURNING *
|
|
1916
2005
|
`);
|
|
1917
|
-
const row = stmt.get(sessionId, opts.from, opts.to, opts.
|
|
2006
|
+
const row = stmt.get(sessionId, opts.from, opts.to, opts.space || null, opts.content, normalizedPriority, opts.working_dir || null, opts.repository || null, opts.branch || null, metadata);
|
|
1918
2007
|
return parseMessage(row);
|
|
1919
2008
|
}
|
|
1920
2009
|
function readMessages(opts = {}) {
|
|
@@ -1933,20 +2022,25 @@ function readMessages(opts = {}) {
|
|
|
1933
2022
|
conditions.push("to_agent = ?");
|
|
1934
2023
|
params.push(opts.to);
|
|
1935
2024
|
}
|
|
1936
|
-
if (opts.
|
|
1937
|
-
conditions.push("
|
|
1938
|
-
params.push(opts.
|
|
2025
|
+
if (opts.space) {
|
|
2026
|
+
conditions.push("space = ?");
|
|
2027
|
+
params.push(opts.space);
|
|
1939
2028
|
}
|
|
1940
2029
|
if (opts.since) {
|
|
1941
2030
|
conditions.push("created_at > ?");
|
|
1942
2031
|
params.push(opts.since);
|
|
1943
2032
|
}
|
|
2033
|
+
if (opts.since_id !== undefined) {
|
|
2034
|
+
conditions.push("id > ?");
|
|
2035
|
+
params.push(opts.since_id);
|
|
2036
|
+
}
|
|
1944
2037
|
if (opts.unread_only) {
|
|
1945
2038
|
conditions.push("read_at IS NULL");
|
|
1946
2039
|
}
|
|
1947
2040
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1948
|
-
const limit = opts.limit ? `LIMIT ${opts.limit}` : "";
|
|
1949
|
-
const
|
|
2041
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? `LIMIT ${Math.floor(opts.limit)}` : "";
|
|
2042
|
+
const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
|
|
2043
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} ${limit}`).all(...params);
|
|
1950
2044
|
return rows.map(parseMessage);
|
|
1951
2045
|
}
|
|
1952
2046
|
function markRead(ids, reader) {
|
|
@@ -1964,10 +2058,10 @@ function markSessionRead(sessionId, reader) {
|
|
|
1964
2058
|
const result = stmt.run(sessionId, reader);
|
|
1965
2059
|
return result.changes;
|
|
1966
2060
|
}
|
|
1967
|
-
function
|
|
2061
|
+
function markSpaceRead(spaceName, reader) {
|
|
1968
2062
|
const db2 = getDb();
|
|
1969
|
-
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE
|
|
1970
|
-
const result = stmt.run(
|
|
2063
|
+
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE space = ? AND from_agent != ? AND read_at IS NULL`);
|
|
2064
|
+
const result = stmt.run(spaceName, reader);
|
|
1971
2065
|
return result.changes;
|
|
1972
2066
|
}
|
|
1973
2067
|
function getMessageById(id) {
|
|
@@ -1975,6 +2069,130 @@ function getMessageById(id) {
|
|
|
1975
2069
|
const row = db2.prepare("SELECT * FROM messages WHERE id = ?").get(id);
|
|
1976
2070
|
return row ? parseMessage(row) : null;
|
|
1977
2071
|
}
|
|
2072
|
+
function markAllRead(agent) {
|
|
2073
|
+
const db2 = getDb();
|
|
2074
|
+
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE to_agent = ? AND read_at IS NULL`);
|
|
2075
|
+
const result = stmt.run(agent);
|
|
2076
|
+
return result.changes;
|
|
2077
|
+
}
|
|
2078
|
+
function escapeCsvField(value) {
|
|
2079
|
+
if (value === null || value === undefined)
|
|
2080
|
+
return "";
|
|
2081
|
+
const str = String(value);
|
|
2082
|
+
if (str.includes(",") || str.includes('"') || str.includes(`
|
|
2083
|
+
`) || str.includes("\r")) {
|
|
2084
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
2085
|
+
}
|
|
2086
|
+
return str;
|
|
2087
|
+
}
|
|
2088
|
+
function exportMessages(opts) {
|
|
2089
|
+
const db2 = getDb();
|
|
2090
|
+
const conditions = [];
|
|
2091
|
+
const params = [];
|
|
2092
|
+
if (opts?.space) {
|
|
2093
|
+
conditions.push("space = ?");
|
|
2094
|
+
params.push(opts.space);
|
|
2095
|
+
}
|
|
2096
|
+
if (opts?.session_id) {
|
|
2097
|
+
conditions.push("session_id = ?");
|
|
2098
|
+
params.push(opts.session_id);
|
|
2099
|
+
}
|
|
2100
|
+
if (opts?.from) {
|
|
2101
|
+
conditions.push("from_agent = ?");
|
|
2102
|
+
params.push(opts.from);
|
|
2103
|
+
}
|
|
2104
|
+
if (opts?.since) {
|
|
2105
|
+
conditions.push("created_at >= ?");
|
|
2106
|
+
params.push(opts.since);
|
|
2107
|
+
}
|
|
2108
|
+
if (opts?.until) {
|
|
2109
|
+
conditions.push("created_at <= ?");
|
|
2110
|
+
params.push(opts.until);
|
|
2111
|
+
}
|
|
2112
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2113
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ASC, id ASC`).all(...params);
|
|
2114
|
+
const messages = rows.map(parseMessage);
|
|
2115
|
+
const format = opts?.format ?? "json";
|
|
2116
|
+
if (format === "csv") {
|
|
2117
|
+
const headers = "id,session_id,from_agent,to_agent,space,content,priority,created_at,read_at";
|
|
2118
|
+
const lines = messages.map((m) => [
|
|
2119
|
+
String(m.id),
|
|
2120
|
+
escapeCsvField(m.session_id),
|
|
2121
|
+
escapeCsvField(m.from_agent),
|
|
2122
|
+
escapeCsvField(m.to_agent),
|
|
2123
|
+
escapeCsvField(m.space),
|
|
2124
|
+
escapeCsvField(m.content),
|
|
2125
|
+
escapeCsvField(m.priority),
|
|
2126
|
+
escapeCsvField(m.created_at),
|
|
2127
|
+
escapeCsvField(m.read_at)
|
|
2128
|
+
].join(","));
|
|
2129
|
+
return [headers, ...lines].join(`
|
|
2130
|
+
`);
|
|
2131
|
+
}
|
|
2132
|
+
return JSON.stringify(messages, null, 2);
|
|
2133
|
+
}
|
|
2134
|
+
function deleteMessage(id, agent) {
|
|
2135
|
+
const db2 = getDb();
|
|
2136
|
+
const stmt = db2.prepare("DELETE FROM messages WHERE id = ? AND from_agent = ?");
|
|
2137
|
+
const result = stmt.run(id, agent);
|
|
2138
|
+
return result.changes > 0;
|
|
2139
|
+
}
|
|
2140
|
+
function editMessage(id, agent, newContent) {
|
|
2141
|
+
const db2 = getDb();
|
|
2142
|
+
const stmt = db2.prepare(`UPDATE messages SET content = ?, edited_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE id = ? AND from_agent = ? RETURNING *`);
|
|
2143
|
+
const row = stmt.get(newContent, id, agent);
|
|
2144
|
+
return row ? parseMessage(row) : null;
|
|
2145
|
+
}
|
|
2146
|
+
function pinMessage(id) {
|
|
2147
|
+
const db2 = getDb();
|
|
2148
|
+
const stmt = db2.prepare(`UPDATE messages SET pinned_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE id = ? RETURNING *`);
|
|
2149
|
+
const row = stmt.get(id);
|
|
2150
|
+
return row ? parseMessage(row) : null;
|
|
2151
|
+
}
|
|
2152
|
+
function unpinMessage(id) {
|
|
2153
|
+
const db2 = getDb();
|
|
2154
|
+
const stmt = db2.prepare(`UPDATE messages SET pinned_at = NULL WHERE id = ? RETURNING *`);
|
|
2155
|
+
const row = stmt.get(id);
|
|
2156
|
+
return row ? parseMessage(row) : null;
|
|
2157
|
+
}
|
|
2158
|
+
function getPinnedMessages(opts) {
|
|
2159
|
+
const db2 = getDb();
|
|
2160
|
+
const conditions = ["pinned_at IS NOT NULL"];
|
|
2161
|
+
const params = [];
|
|
2162
|
+
if (opts?.space) {
|
|
2163
|
+
conditions.push("space = ?");
|
|
2164
|
+
params.push(opts.space);
|
|
2165
|
+
}
|
|
2166
|
+
if (opts?.session_id) {
|
|
2167
|
+
conditions.push("session_id = ?");
|
|
2168
|
+
params.push(opts.session_id);
|
|
2169
|
+
}
|
|
2170
|
+
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
2171
|
+
const limit = Number.isFinite(opts?.limit) && opts.limit > 0 ? `LIMIT ${Math.floor(opts.limit)}` : "";
|
|
2172
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY pinned_at DESC, id DESC ${limit}`).all(...params);
|
|
2173
|
+
return rows.map(parseMessage);
|
|
2174
|
+
}
|
|
2175
|
+
function searchMessages(opts) {
|
|
2176
|
+
const db2 = getDb();
|
|
2177
|
+
const conditions = ["content LIKE ?"];
|
|
2178
|
+
const params = [`%${opts.query}%`];
|
|
2179
|
+
if (opts.space) {
|
|
2180
|
+
conditions.push("space = ?");
|
|
2181
|
+
params.push(opts.space);
|
|
2182
|
+
}
|
|
2183
|
+
if (opts.from) {
|
|
2184
|
+
conditions.push("from_agent = ?");
|
|
2185
|
+
params.push(opts.from);
|
|
2186
|
+
}
|
|
2187
|
+
if (opts.to) {
|
|
2188
|
+
conditions.push("to_agent = ?");
|
|
2189
|
+
params.push(opts.to);
|
|
2190
|
+
}
|
|
2191
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 50;
|
|
2192
|
+
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
2193
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
|
|
2194
|
+
return rows.map(parseMessage);
|
|
2195
|
+
}
|
|
1978
2196
|
// src/lib/sessions.ts
|
|
1979
2197
|
function listSessions(agent) {
|
|
1980
2198
|
const db2 = getDb();
|
|
@@ -2029,85 +2247,395 @@ function getSession(sessionId) {
|
|
|
2029
2247
|
unread_count: row.unread_count
|
|
2030
2248
|
};
|
|
2031
2249
|
}
|
|
2032
|
-
// src/lib/
|
|
2033
|
-
function
|
|
2250
|
+
// src/lib/spaces.ts
|
|
2251
|
+
function getSpaceDepth(spaceName) {
|
|
2034
2252
|
const db2 = getDb();
|
|
2035
|
-
|
|
2036
|
-
|
|
2253
|
+
let depth = 0;
|
|
2254
|
+
let current = spaceName;
|
|
2255
|
+
for (let i = 0;i < 10; i++) {
|
|
2256
|
+
const row = db2.prepare("SELECT parent_id FROM spaces WHERE name = ?").get(current);
|
|
2257
|
+
if (!row || !row.parent_id)
|
|
2258
|
+
break;
|
|
2259
|
+
depth++;
|
|
2260
|
+
current = row.parent_id;
|
|
2261
|
+
}
|
|
2262
|
+
return depth;
|
|
2263
|
+
}
|
|
2264
|
+
function createSpace(name, createdBy, options) {
|
|
2265
|
+
const db2 = getDb();
|
|
2266
|
+
if (options?.parent_id) {
|
|
2267
|
+
const parentExists = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(options.parent_id);
|
|
2268
|
+
if (!parentExists) {
|
|
2269
|
+
throw new Error(`Parent space not found: ${options.parent_id}`);
|
|
2270
|
+
}
|
|
2271
|
+
const parentDepth = getSpaceDepth(options.parent_id);
|
|
2272
|
+
if (parentDepth >= 2) {
|
|
2273
|
+
throw new Error("Maximum space nesting depth is 3 levels");
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
if (options?.project_id) {
|
|
2277
|
+
const projectExists = db2.prepare("SELECT id FROM projects WHERE id = ?").get(options.project_id);
|
|
2278
|
+
if (!projectExists) {
|
|
2279
|
+
throw new Error(`Project not found: ${options.project_id}`);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
const row = db2.prepare("INSERT INTO spaces (name, description, parent_id, project_id, created_by) VALUES (?, ?, ?, ?, ?) RETURNING *").get(name, options?.description || null, options?.parent_id || null, options?.project_id || null, createdBy);
|
|
2283
|
+
db2.prepare("INSERT OR IGNORE INTO space_members (space, agent) VALUES (?, ?)").run(name, createdBy);
|
|
2037
2284
|
return row;
|
|
2038
2285
|
}
|
|
2039
|
-
function
|
|
2286
|
+
function listSpaces(options) {
|
|
2040
2287
|
const db2 = getDb();
|
|
2288
|
+
const conditions = [];
|
|
2289
|
+
const params = [];
|
|
2290
|
+
if (options?.project_id) {
|
|
2291
|
+
conditions.push("s.project_id = ?");
|
|
2292
|
+
params.push(options.project_id);
|
|
2293
|
+
}
|
|
2294
|
+
if (options?.parent_id !== undefined) {
|
|
2295
|
+
if (options.parent_id === null) {
|
|
2296
|
+
conditions.push("s.parent_id IS NULL");
|
|
2297
|
+
} else {
|
|
2298
|
+
conditions.push("s.parent_id = ?");
|
|
2299
|
+
params.push(options.parent_id);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
if (!options?.include_archived) {
|
|
2303
|
+
conditions.push("s.archived_at IS NULL");
|
|
2304
|
+
}
|
|
2305
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2041
2306
|
const rows = db2.prepare(`
|
|
2042
2307
|
SELECT
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2308
|
+
s.name,
|
|
2309
|
+
s.description,
|
|
2310
|
+
s.parent_id,
|
|
2311
|
+
s.project_id,
|
|
2312
|
+
s.created_by,
|
|
2313
|
+
s.created_at,
|
|
2314
|
+
s.archived_at,
|
|
2315
|
+
(SELECT COUNT(*) FROM space_members WHERE space = s.name) AS member_count,
|
|
2316
|
+
(SELECT COUNT(*) FROM messages WHERE space = s.name) AS message_count
|
|
2317
|
+
FROM spaces s
|
|
2318
|
+
${where}
|
|
2319
|
+
ORDER BY s.name ASC
|
|
2320
|
+
`).all(...params);
|
|
2052
2321
|
return rows;
|
|
2053
2322
|
}
|
|
2054
|
-
function
|
|
2323
|
+
function getSpace(name) {
|
|
2055
2324
|
const db2 = getDb();
|
|
2056
2325
|
const row = db2.prepare(`
|
|
2057
2326
|
SELECT
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2327
|
+
s.name,
|
|
2328
|
+
s.description,
|
|
2329
|
+
s.parent_id,
|
|
2330
|
+
s.project_id,
|
|
2331
|
+
s.created_by,
|
|
2332
|
+
s.created_at,
|
|
2333
|
+
s.archived_at,
|
|
2334
|
+
(SELECT COUNT(*) FROM space_members WHERE space = s.name) AS member_count,
|
|
2335
|
+
(SELECT COUNT(*) FROM messages WHERE space = s.name) AS message_count
|
|
2336
|
+
FROM spaces s
|
|
2337
|
+
WHERE s.name = ?
|
|
2066
2338
|
`).get(name);
|
|
2067
2339
|
return row;
|
|
2068
2340
|
}
|
|
2069
|
-
function
|
|
2341
|
+
function joinSpace(spaceName, agent) {
|
|
2070
2342
|
const db2 = getDb();
|
|
2071
|
-
const
|
|
2072
|
-
if (!
|
|
2343
|
+
const space = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(spaceName);
|
|
2344
|
+
if (!space)
|
|
2073
2345
|
return false;
|
|
2074
|
-
db2.prepare("INSERT OR IGNORE INTO
|
|
2346
|
+
db2.prepare("INSERT OR IGNORE INTO space_members (space, agent) VALUES (?, ?)").run(spaceName, agent);
|
|
2075
2347
|
return true;
|
|
2076
2348
|
}
|
|
2077
|
-
function
|
|
2349
|
+
function leaveSpace(spaceName, agent) {
|
|
2078
2350
|
const db2 = getDb();
|
|
2079
|
-
const result = db2.prepare("DELETE FROM
|
|
2351
|
+
const result = db2.prepare("DELETE FROM space_members WHERE space = ? AND agent = ?").run(spaceName, agent);
|
|
2080
2352
|
return result.changes > 0;
|
|
2081
2353
|
}
|
|
2082
|
-
function
|
|
2354
|
+
function getSpaceMembers(spaceName) {
|
|
2355
|
+
const db2 = getDb();
|
|
2356
|
+
return db2.prepare("SELECT space, agent, joined_at FROM space_members WHERE space = ? ORDER BY joined_at ASC").all(spaceName);
|
|
2357
|
+
}
|
|
2358
|
+
function updateSpace(name, updates) {
|
|
2359
|
+
const db2 = getDb();
|
|
2360
|
+
const existing = db2.prepare("SELECT * FROM spaces WHERE name = ?").get(name);
|
|
2361
|
+
if (!existing) {
|
|
2362
|
+
throw new Error(`Space not found: ${name}`);
|
|
2363
|
+
}
|
|
2364
|
+
if (updates.parent_id !== undefined && updates.parent_id !== existing.parent_id) {
|
|
2365
|
+
if (updates.parent_id !== null) {
|
|
2366
|
+
const parentExists = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(updates.parent_id);
|
|
2367
|
+
if (!parentExists) {
|
|
2368
|
+
throw new Error(`Parent space not found: ${updates.parent_id}`);
|
|
2369
|
+
}
|
|
2370
|
+
const parentDepth = getSpaceDepth(updates.parent_id);
|
|
2371
|
+
if (parentDepth >= 2) {
|
|
2372
|
+
throw new Error("Maximum space nesting depth is 3 levels");
|
|
2373
|
+
}
|
|
2374
|
+
if (updates.parent_id === name) {
|
|
2375
|
+
throw new Error("A space cannot be its own parent");
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
if (updates.project_id !== undefined && updates.project_id !== existing.project_id) {
|
|
2380
|
+
if (updates.project_id !== null) {
|
|
2381
|
+
const projectExists = db2.prepare("SELECT id FROM projects WHERE id = ?").get(updates.project_id);
|
|
2382
|
+
if (!projectExists) {
|
|
2383
|
+
throw new Error(`Project not found: ${updates.project_id}`);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
const sets = [];
|
|
2388
|
+
const params = [];
|
|
2389
|
+
if (updates.description !== undefined) {
|
|
2390
|
+
sets.push("description = ?");
|
|
2391
|
+
params.push(updates.description);
|
|
2392
|
+
}
|
|
2393
|
+
if (updates.parent_id !== undefined) {
|
|
2394
|
+
sets.push("parent_id = ?");
|
|
2395
|
+
params.push(updates.parent_id);
|
|
2396
|
+
}
|
|
2397
|
+
if (updates.project_id !== undefined) {
|
|
2398
|
+
sets.push("project_id = ?");
|
|
2399
|
+
params.push(updates.project_id);
|
|
2400
|
+
}
|
|
2401
|
+
if (sets.length === 0) {
|
|
2402
|
+
return existing;
|
|
2403
|
+
}
|
|
2404
|
+
params.push(name);
|
|
2405
|
+
const row = db2.prepare(`UPDATE spaces SET ${sets.join(", ")} WHERE name = ? RETURNING *`).get(...params);
|
|
2406
|
+
return row;
|
|
2407
|
+
}
|
|
2408
|
+
function archiveSpace(name) {
|
|
2083
2409
|
const db2 = getDb();
|
|
2084
|
-
|
|
2410
|
+
const existing = db2.prepare("SELECT * FROM spaces WHERE name = ?").get(name);
|
|
2411
|
+
if (!existing) {
|
|
2412
|
+
throw new Error(`Space not found: ${name}`);
|
|
2413
|
+
}
|
|
2414
|
+
const row = db2.prepare("UPDATE spaces SET archived_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE name = ? RETURNING *").get(name);
|
|
2415
|
+
return row;
|
|
2085
2416
|
}
|
|
2086
|
-
function
|
|
2417
|
+
function unarchiveSpace(name) {
|
|
2087
2418
|
const db2 = getDb();
|
|
2088
|
-
const
|
|
2419
|
+
const existing = db2.prepare("SELECT * FROM spaces WHERE name = ?").get(name);
|
|
2420
|
+
if (!existing) {
|
|
2421
|
+
throw new Error(`Space not found: ${name}`);
|
|
2422
|
+
}
|
|
2423
|
+
const row = db2.prepare("UPDATE spaces SET archived_at = NULL WHERE name = ? RETURNING *").get(name);
|
|
2424
|
+
return row;
|
|
2425
|
+
}
|
|
2426
|
+
function isSpaceMember(spaceName, agent) {
|
|
2427
|
+
const db2 = getDb();
|
|
2428
|
+
const row = db2.prepare("SELECT 1 FROM space_members WHERE space = ? AND agent = ?").get(spaceName, agent);
|
|
2089
2429
|
return !!row;
|
|
2090
2430
|
}
|
|
2431
|
+
// src/lib/projects.ts
|
|
2432
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2433
|
+
function parseProject(row) {
|
|
2434
|
+
let metadata = null;
|
|
2435
|
+
if (row.metadata) {
|
|
2436
|
+
try {
|
|
2437
|
+
metadata = JSON.parse(row.metadata);
|
|
2438
|
+
} catch {
|
|
2439
|
+
metadata = null;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
let tags = [];
|
|
2443
|
+
if (row.tags) {
|
|
2444
|
+
try {
|
|
2445
|
+
tags = JSON.parse(row.tags);
|
|
2446
|
+
} catch {
|
|
2447
|
+
tags = [];
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
let settings = null;
|
|
2451
|
+
if (row.settings) {
|
|
2452
|
+
try {
|
|
2453
|
+
settings = JSON.parse(row.settings);
|
|
2454
|
+
} catch {
|
|
2455
|
+
settings = null;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
return {
|
|
2459
|
+
id: row.id,
|
|
2460
|
+
name: row.name,
|
|
2461
|
+
description: row.description || null,
|
|
2462
|
+
path: row.path || null,
|
|
2463
|
+
created_by: row.created_by,
|
|
2464
|
+
created_at: row.created_at,
|
|
2465
|
+
metadata,
|
|
2466
|
+
tags,
|
|
2467
|
+
status: row.status || "active",
|
|
2468
|
+
repository: row.repository || null,
|
|
2469
|
+
settings
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
function createProject(opts) {
|
|
2473
|
+
const db2 = getDb();
|
|
2474
|
+
const id = randomUUID2();
|
|
2475
|
+
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
2476
|
+
const tags = opts.tags ? JSON.stringify(opts.tags) : null;
|
|
2477
|
+
const settings = opts.settings ? JSON.stringify(opts.settings) : null;
|
|
2478
|
+
const row = db2.prepare(`
|
|
2479
|
+
INSERT INTO projects (id, name, description, path, created_by, metadata, tags, repository, settings)
|
|
2480
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2481
|
+
RETURNING *
|
|
2482
|
+
`).get(id, opts.name, opts.description || null, opts.path || null, opts.created_by, metadata, tags, opts.repository || null, settings);
|
|
2483
|
+
return parseProject(row);
|
|
2484
|
+
}
|
|
2485
|
+
function listProjects(opts) {
|
|
2486
|
+
const db2 = getDb();
|
|
2487
|
+
const conditions = [];
|
|
2488
|
+
const params = [];
|
|
2489
|
+
if (opts?.status) {
|
|
2490
|
+
conditions.push("p.status = ?");
|
|
2491
|
+
params.push(opts.status);
|
|
2492
|
+
}
|
|
2493
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2494
|
+
const rows = db2.prepare(`
|
|
2495
|
+
SELECT
|
|
2496
|
+
p.*,
|
|
2497
|
+
(SELECT COUNT(*) FROM spaces WHERE project_id = p.id) AS space_count
|
|
2498
|
+
FROM projects p
|
|
2499
|
+
${where}
|
|
2500
|
+
ORDER BY p.name ASC
|
|
2501
|
+
`).all(...params);
|
|
2502
|
+
return rows.map((row) => ({
|
|
2503
|
+
...parseProject(row),
|
|
2504
|
+
space_count: row.space_count
|
|
2505
|
+
}));
|
|
2506
|
+
}
|
|
2507
|
+
function getProject(id) {
|
|
2508
|
+
const db2 = getDb();
|
|
2509
|
+
const row = db2.prepare(`
|
|
2510
|
+
SELECT
|
|
2511
|
+
p.*,
|
|
2512
|
+
(SELECT COUNT(*) FROM spaces WHERE project_id = p.id) AS space_count
|
|
2513
|
+
FROM projects p
|
|
2514
|
+
WHERE p.id = ?
|
|
2515
|
+
`).get(id);
|
|
2516
|
+
if (!row)
|
|
2517
|
+
return null;
|
|
2518
|
+
return {
|
|
2519
|
+
...parseProject(row),
|
|
2520
|
+
space_count: row.space_count
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
function getProjectByName(name) {
|
|
2524
|
+
const db2 = getDb();
|
|
2525
|
+
const row = db2.prepare(`
|
|
2526
|
+
SELECT
|
|
2527
|
+
p.*,
|
|
2528
|
+
(SELECT COUNT(*) FROM spaces WHERE project_id = p.id) AS space_count
|
|
2529
|
+
FROM projects p
|
|
2530
|
+
WHERE p.name = ?
|
|
2531
|
+
`).get(name);
|
|
2532
|
+
if (!row)
|
|
2533
|
+
return null;
|
|
2534
|
+
return {
|
|
2535
|
+
...parseProject(row),
|
|
2536
|
+
space_count: row.space_count
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
function updateProject(id, updates) {
|
|
2540
|
+
const db2 = getDb();
|
|
2541
|
+
const existing = db2.prepare("SELECT * FROM projects WHERE id = ?").get(id);
|
|
2542
|
+
if (!existing) {
|
|
2543
|
+
throw new Error(`Project not found: ${id}`);
|
|
2544
|
+
}
|
|
2545
|
+
const sets = [];
|
|
2546
|
+
const params = [];
|
|
2547
|
+
if (updates.name !== undefined) {
|
|
2548
|
+
sets.push("name = ?");
|
|
2549
|
+
params.push(updates.name);
|
|
2550
|
+
}
|
|
2551
|
+
if (updates.description !== undefined) {
|
|
2552
|
+
sets.push("description = ?");
|
|
2553
|
+
params.push(updates.description);
|
|
2554
|
+
}
|
|
2555
|
+
if (updates.path !== undefined) {
|
|
2556
|
+
sets.push("path = ?");
|
|
2557
|
+
params.push(updates.path);
|
|
2558
|
+
}
|
|
2559
|
+
if (updates.metadata !== undefined) {
|
|
2560
|
+
sets.push("metadata = ?");
|
|
2561
|
+
params.push(JSON.stringify(updates.metadata));
|
|
2562
|
+
}
|
|
2563
|
+
if (updates.tags !== undefined) {
|
|
2564
|
+
sets.push("tags = ?");
|
|
2565
|
+
params.push(JSON.stringify(updates.tags));
|
|
2566
|
+
}
|
|
2567
|
+
if (updates.status !== undefined) {
|
|
2568
|
+
sets.push("status = ?");
|
|
2569
|
+
params.push(updates.status);
|
|
2570
|
+
}
|
|
2571
|
+
if (updates.repository !== undefined) {
|
|
2572
|
+
sets.push("repository = ?");
|
|
2573
|
+
params.push(updates.repository);
|
|
2574
|
+
}
|
|
2575
|
+
if (updates.settings !== undefined) {
|
|
2576
|
+
sets.push("settings = ?");
|
|
2577
|
+
params.push(JSON.stringify(updates.settings));
|
|
2578
|
+
}
|
|
2579
|
+
if (sets.length === 0) {
|
|
2580
|
+
return parseProject(existing);
|
|
2581
|
+
}
|
|
2582
|
+
params.push(id);
|
|
2583
|
+
const row = db2.prepare(`UPDATE projects SET ${sets.join(", ")} WHERE id = ? RETURNING *`).get(...params);
|
|
2584
|
+
return parseProject(row);
|
|
2585
|
+
}
|
|
2586
|
+
function deleteProject(id) {
|
|
2587
|
+
const db2 = getDb();
|
|
2588
|
+
const spaceCount = db2.prepare("SELECT COUNT(*) as c FROM spaces WHERE project_id = ?").get(id).c;
|
|
2589
|
+
if (spaceCount > 0) {
|
|
2590
|
+
throw new Error(`Cannot delete project: ${spaceCount} space(s) still reference it`);
|
|
2591
|
+
}
|
|
2592
|
+
const result = db2.prepare("DELETE FROM projects WHERE id = ?").run(id);
|
|
2593
|
+
return result.changes > 0;
|
|
2594
|
+
}
|
|
2091
2595
|
// src/lib/poll.ts
|
|
2092
2596
|
var import_react = __toESM(require_react(), 1);
|
|
2093
2597
|
function startPolling(opts) {
|
|
2094
2598
|
const interval = opts.interval_ms ?? 200;
|
|
2095
|
-
let lastSeen = new Date().toISOString();
|
|
2096
2599
|
let stopped = false;
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
const
|
|
2600
|
+
let inFlight = false;
|
|
2601
|
+
let lastSeenId = 0;
|
|
2602
|
+
const seedLastSeen = () => {
|
|
2603
|
+
const latest = readMessages({
|
|
2101
2604
|
session_id: opts.session_id,
|
|
2102
2605
|
to: opts.to_agent,
|
|
2103
|
-
|
|
2104
|
-
|
|
2606
|
+
space: opts.space,
|
|
2607
|
+
order: "desc",
|
|
2608
|
+
limit: 1
|
|
2105
2609
|
});
|
|
2106
|
-
if (
|
|
2107
|
-
|
|
2108
|
-
opts.on_messages(messages);
|
|
2610
|
+
if (latest.length > 0) {
|
|
2611
|
+
lastSeenId = latest[0].id;
|
|
2109
2612
|
}
|
|
2110
2613
|
};
|
|
2614
|
+
const poll = () => {
|
|
2615
|
+
if (stopped || inFlight)
|
|
2616
|
+
return;
|
|
2617
|
+
inFlight = true;
|
|
2618
|
+
try {
|
|
2619
|
+
const messages = readMessages({
|
|
2620
|
+
session_id: opts.session_id,
|
|
2621
|
+
to: opts.to_agent,
|
|
2622
|
+
space: opts.space,
|
|
2623
|
+
since_id: lastSeenId,
|
|
2624
|
+
order: "asc"
|
|
2625
|
+
});
|
|
2626
|
+
if (messages.length > 0) {
|
|
2627
|
+
lastSeenId = messages[messages.length - 1].id;
|
|
2628
|
+
try {
|
|
2629
|
+
opts.on_messages(messages);
|
|
2630
|
+
} catch (error) {
|
|
2631
|
+
console.error("Polling callback error:", error);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
} finally {
|
|
2635
|
+
inFlight = false;
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
seedLastSeen();
|
|
2111
2639
|
const timer = setInterval(poll, interval);
|
|
2112
2640
|
return {
|
|
2113
2641
|
stop: () => {
|
|
@@ -2116,62 +2644,141 @@ function startPolling(opts) {
|
|
|
2116
2644
|
}
|
|
2117
2645
|
};
|
|
2118
2646
|
}
|
|
2119
|
-
function
|
|
2647
|
+
function useSpaceMessages(spaceName) {
|
|
2120
2648
|
const [messages, setMessages] = import_react.useState([]);
|
|
2121
|
-
const initialLoad = import_react.useRef(false);
|
|
2122
2649
|
import_react.useEffect(() => {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
setMessages(existing);
|
|
2126
|
-
initialLoad.current = true;
|
|
2127
|
-
}
|
|
2650
|
+
const existing = readMessages({ space: spaceName });
|
|
2651
|
+
setMessages(existing);
|
|
2128
2652
|
const { stop } = startPolling({
|
|
2129
|
-
|
|
2653
|
+
space: spaceName,
|
|
2130
2654
|
interval_ms: 200,
|
|
2131
2655
|
on_messages: (newMessages) => {
|
|
2132
2656
|
setMessages((prev) => [...prev, ...newMessages]);
|
|
2133
2657
|
}
|
|
2134
2658
|
});
|
|
2135
2659
|
return stop;
|
|
2136
|
-
}, [
|
|
2660
|
+
}, [spaceName]);
|
|
2137
2661
|
return messages;
|
|
2138
2662
|
}
|
|
2139
2663
|
// src/lib/identity.ts
|
|
2140
2664
|
function resolveIdentity(explicit) {
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2665
|
+
const explicitValue = explicit?.trim();
|
|
2666
|
+
if (explicitValue)
|
|
2667
|
+
return explicitValue;
|
|
2668
|
+
const envValue = process.env.CONVERSATIONS_AGENT_ID?.trim();
|
|
2669
|
+
if (envValue)
|
|
2670
|
+
return envValue;
|
|
2145
2671
|
return "user";
|
|
2146
2672
|
}
|
|
2147
2673
|
function requireIdentity(explicit) {
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2674
|
+
const explicitValue = explicit?.trim();
|
|
2675
|
+
if (explicitValue)
|
|
2676
|
+
return explicitValue;
|
|
2677
|
+
const envValue = process.env.CONVERSATIONS_AGENT_ID?.trim();
|
|
2678
|
+
if (envValue)
|
|
2679
|
+
return envValue;
|
|
2152
2680
|
throw new Error("Agent identity required. Set CONVERSATIONS_AGENT_ID env var or pass --from flag.");
|
|
2153
2681
|
}
|
|
2682
|
+
// src/lib/presence.ts
|
|
2683
|
+
var ONLINE_THRESHOLD_SECONDS = 60;
|
|
2684
|
+
function parsePresence(row) {
|
|
2685
|
+
let metadata = null;
|
|
2686
|
+
if (row.metadata) {
|
|
2687
|
+
try {
|
|
2688
|
+
metadata = JSON.parse(row.metadata);
|
|
2689
|
+
} catch {
|
|
2690
|
+
metadata = null;
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
const lastSeenAt = row.last_seen_at;
|
|
2694
|
+
const lastSeenMs = new Date(lastSeenAt + "Z").getTime();
|
|
2695
|
+
const nowMs = Date.now();
|
|
2696
|
+
const online = nowMs - lastSeenMs < ONLINE_THRESHOLD_SECONDS * 1000;
|
|
2697
|
+
return {
|
|
2698
|
+
agent: row.agent,
|
|
2699
|
+
status: row.status,
|
|
2700
|
+
last_seen_at: lastSeenAt,
|
|
2701
|
+
online,
|
|
2702
|
+
metadata
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
function heartbeat(agent, status, metadata) {
|
|
2706
|
+
const db2 = getDb();
|
|
2707
|
+
const metadataJson = metadata ? JSON.stringify(metadata) : null;
|
|
2708
|
+
const resolvedStatus = status || "online";
|
|
2709
|
+
db2.prepare(`
|
|
2710
|
+
INSERT INTO agent_presence (agent, status, last_seen_at, metadata)
|
|
2711
|
+
VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
|
|
2712
|
+
ON CONFLICT(agent) DO UPDATE SET
|
|
2713
|
+
status = excluded.status,
|
|
2714
|
+
last_seen_at = excluded.last_seen_at,
|
|
2715
|
+
metadata = excluded.metadata
|
|
2716
|
+
`).run(agent, resolvedStatus, metadataJson);
|
|
2717
|
+
}
|
|
2718
|
+
function getPresence(agent) {
|
|
2719
|
+
const db2 = getDb();
|
|
2720
|
+
const row = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(agent);
|
|
2721
|
+
return row ? parsePresence(row) : null;
|
|
2722
|
+
}
|
|
2723
|
+
function listAgents(opts) {
|
|
2724
|
+
const db2 = getDb();
|
|
2725
|
+
let query = "SELECT * FROM agent_presence";
|
|
2726
|
+
const params = [];
|
|
2727
|
+
if (opts?.online_only) {
|
|
2728
|
+
query += " WHERE last_seen_at > strftime('%Y-%m-%dT%H:%M:%f', 'now', '-60 seconds')";
|
|
2729
|
+
}
|
|
2730
|
+
query += " ORDER BY last_seen_at DESC";
|
|
2731
|
+
const rows = db2.prepare(query).all(...params);
|
|
2732
|
+
return rows.map(parsePresence);
|
|
2733
|
+
}
|
|
2734
|
+
function removePresence(agent) {
|
|
2735
|
+
const db2 = getDb();
|
|
2736
|
+
const result = db2.prepare("DELETE FROM agent_presence WHERE agent = ?").run(agent);
|
|
2737
|
+
return result.changes > 0;
|
|
2738
|
+
}
|
|
2154
2739
|
export {
|
|
2155
|
-
|
|
2740
|
+
useSpaceMessages,
|
|
2741
|
+
updateSpace,
|
|
2742
|
+
updateProject,
|
|
2743
|
+
unpinMessage,
|
|
2744
|
+
unarchiveSpace,
|
|
2156
2745
|
startPolling,
|
|
2157
2746
|
sendMessage,
|
|
2747
|
+
searchMessages,
|
|
2158
2748
|
resolveIdentity,
|
|
2159
2749
|
requireIdentity,
|
|
2750
|
+
removePresence,
|
|
2160
2751
|
readMessages,
|
|
2752
|
+
pinMessage,
|
|
2753
|
+
markSpaceRead,
|
|
2161
2754
|
markSessionRead,
|
|
2162
2755
|
markRead,
|
|
2163
|
-
|
|
2756
|
+
markAllRead,
|
|
2757
|
+
listSpaces,
|
|
2164
2758
|
listSessions,
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2759
|
+
listProjects,
|
|
2760
|
+
listAgents,
|
|
2761
|
+
leaveSpace,
|
|
2762
|
+
joinSpace,
|
|
2763
|
+
isSpaceMember,
|
|
2764
|
+
heartbeat,
|
|
2765
|
+
getSpaceMembers,
|
|
2766
|
+
getSpaceDepth,
|
|
2767
|
+
getSpace,
|
|
2169
2768
|
getSession,
|
|
2769
|
+
getProjectByName,
|
|
2770
|
+
getProject,
|
|
2771
|
+
getPresence,
|
|
2772
|
+
getPinnedMessages,
|
|
2170
2773
|
getMessageById,
|
|
2171
2774
|
getDbPath,
|
|
2172
2775
|
getDb,
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2776
|
+
exportMessages,
|
|
2777
|
+
editMessage,
|
|
2778
|
+
deleteProject,
|
|
2779
|
+
deleteMessage,
|
|
2780
|
+
createSpace,
|
|
2781
|
+
createProject,
|
|
2782
|
+
closeDb,
|
|
2783
|
+
archiveSpace
|
|
2177
2784
|
};
|