@hasna/conversations 0.2.25 → 0.2.26
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/bin/hook.js +7 -0
- package/bin/index.js +352 -6
- package/bin/mcp.js +319 -5
- package/dashboard/dist/assets/index-Bw0wMcXE.js +186 -0
- package/dashboard/dist/assets/index-CF_GDtNp.css +1 -0
- package/dashboard/dist/index.html +13 -0
- package/dashboard/dist/logo.jpg +0 -0
- package/dist/index.js +30 -0
- package/dist/lib/messages.d.ts +2 -0
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/tools/cloud.test.d.ts +1 -0
- package/package.json +2 -2
package/bin/mcp.js
CHANGED
|
@@ -17662,6 +17662,7 @@ function getDb() {
|
|
|
17662
17662
|
db.exec(`
|
|
17663
17663
|
CREATE TABLE IF NOT EXISTS messages (
|
|
17664
17664
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17665
|
+
uuid TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))),
|
|
17665
17666
|
session_id TEXT NOT NULL,
|
|
17666
17667
|
from_agent TEXT NOT NULL,
|
|
17667
17668
|
to_agent TEXT NOT NULL,
|
|
@@ -17681,6 +17682,7 @@ function getDb() {
|
|
|
17681
17682
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_agent)");
|
|
17682
17683
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at)");
|
|
17683
17684
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_space ON messages(space)");
|
|
17685
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_uuid ON messages(uuid)");
|
|
17684
17686
|
db.exec(`
|
|
17685
17687
|
CREATE TABLE IF NOT EXISTS projects (
|
|
17686
17688
|
id TEXT PRIMARY KEY,
|
|
@@ -17828,6 +17830,11 @@ function getDb() {
|
|
|
17828
17830
|
db.exec("ALTER TABLE messages ADD COLUMN project_id TEXT");
|
|
17829
17831
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_project ON messages(project_id)");
|
|
17830
17832
|
}
|
|
17833
|
+
if (!colNames2.includes("uuid")) {
|
|
17834
|
+
db.exec("ALTER TABLE messages ADD COLUMN uuid TEXT");
|
|
17835
|
+
db.exec("UPDATE messages SET uuid = lower(hex(randomblob(16))) WHERE uuid IS NULL");
|
|
17836
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_uuid ON messages(uuid)");
|
|
17837
|
+
}
|
|
17831
17838
|
const presenceCols = db.prepare("PRAGMA table_info(agent_presence)").all();
|
|
17832
17839
|
const presenceColNames = presenceCols.map((c) => c.name);
|
|
17833
17840
|
if (!presenceColNames.includes("id")) {
|
|
@@ -17932,6 +17939,187 @@ var init_db = __esm(() => {
|
|
|
17932
17939
|
init_dist();
|
|
17933
17940
|
});
|
|
17934
17941
|
|
|
17942
|
+
// src/lib/pg-migrations.ts
|
|
17943
|
+
var exports_pg_migrations = {};
|
|
17944
|
+
__export(exports_pg_migrations, {
|
|
17945
|
+
PG_MIGRATIONS: () => PG_MIGRATIONS
|
|
17946
|
+
});
|
|
17947
|
+
var PG_MIGRATIONS;
|
|
17948
|
+
var init_pg_migrations = __esm(() => {
|
|
17949
|
+
PG_MIGRATIONS = [
|
|
17950
|
+
`
|
|
17951
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
17952
|
+
id TEXT PRIMARY KEY,
|
|
17953
|
+
name TEXT NOT NULL UNIQUE,
|
|
17954
|
+
description TEXT,
|
|
17955
|
+
path TEXT,
|
|
17956
|
+
created_by TEXT NOT NULL,
|
|
17957
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
17958
|
+
metadata TEXT,
|
|
17959
|
+
tags TEXT,
|
|
17960
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
17961
|
+
repository TEXT,
|
|
17962
|
+
settings TEXT
|
|
17963
|
+
);
|
|
17964
|
+
CREATE INDEX IF NOT EXISTS idx_projects_name ON projects(name);
|
|
17965
|
+
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
|
|
17966
|
+
|
|
17967
|
+
CREATE TABLE IF NOT EXISTS spaces (
|
|
17968
|
+
name TEXT PRIMARY KEY,
|
|
17969
|
+
description TEXT,
|
|
17970
|
+
parent_id TEXT REFERENCES spaces(name),
|
|
17971
|
+
project_id TEXT REFERENCES projects(id),
|
|
17972
|
+
created_by TEXT NOT NULL,
|
|
17973
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
17974
|
+
archived_at TEXT,
|
|
17975
|
+
topic TEXT
|
|
17976
|
+
);
|
|
17977
|
+
CREATE INDEX IF NOT EXISTS idx_spaces_parent ON spaces(parent_id);
|
|
17978
|
+
CREATE INDEX IF NOT EXISTS idx_spaces_project ON spaces(project_id);
|
|
17979
|
+
|
|
17980
|
+
CREATE TABLE IF NOT EXISTS space_members (
|
|
17981
|
+
space TEXT NOT NULL REFERENCES spaces(name),
|
|
17982
|
+
agent TEXT NOT NULL,
|
|
17983
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
17984
|
+
PRIMARY KEY (space, agent)
|
|
17985
|
+
);
|
|
17986
|
+
|
|
17987
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
17988
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
17989
|
+
uuid TEXT NOT NULL DEFAULT gen_random_uuid()::text UNIQUE,
|
|
17990
|
+
session_id TEXT NOT NULL,
|
|
17991
|
+
from_agent TEXT NOT NULL,
|
|
17992
|
+
to_agent TEXT NOT NULL,
|
|
17993
|
+
space TEXT,
|
|
17994
|
+
project_id TEXT,
|
|
17995
|
+
content TEXT NOT NULL,
|
|
17996
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
17997
|
+
working_dir TEXT,
|
|
17998
|
+
repository TEXT,
|
|
17999
|
+
branch TEXT,
|
|
18000
|
+
metadata TEXT,
|
|
18001
|
+
edited_at TEXT,
|
|
18002
|
+
pinned_at TEXT,
|
|
18003
|
+
blocking BOOLEAN NOT NULL DEFAULT FALSE,
|
|
18004
|
+
attachments TEXT,
|
|
18005
|
+
reply_to BIGINT,
|
|
18006
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18007
|
+
read_at TEXT
|
|
18008
|
+
);
|
|
18009
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
|
|
18010
|
+
CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_agent);
|
|
18011
|
+
CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at);
|
|
18012
|
+
CREATE INDEX IF NOT EXISTS idx_messages_space ON messages(space);
|
|
18013
|
+
CREATE INDEX IF NOT EXISTS idx_messages_pinned ON messages(pinned_at);
|
|
18014
|
+
CREATE INDEX IF NOT EXISTS idx_messages_blocking ON messages(blocking);
|
|
18015
|
+
CREATE INDEX IF NOT EXISTS idx_messages_reply_to ON messages(reply_to);
|
|
18016
|
+
CREATE INDEX IF NOT EXISTS idx_messages_project ON messages(project_id);
|
|
18017
|
+
|
|
18018
|
+
CREATE TABLE IF NOT EXISTS agent_presence (
|
|
18019
|
+
id TEXT NOT NULL DEFAULT '',
|
|
18020
|
+
agent TEXT PRIMARY KEY,
|
|
18021
|
+
session_id TEXT,
|
|
18022
|
+
role TEXT NOT NULL DEFAULT 'agent',
|
|
18023
|
+
project_id TEXT,
|
|
18024
|
+
status TEXT NOT NULL DEFAULT 'online',
|
|
18025
|
+
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18026
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18027
|
+
metadata TEXT
|
|
18028
|
+
);
|
|
18029
|
+
|
|
18030
|
+
CREATE TABLE IF NOT EXISTS resource_locks (
|
|
18031
|
+
resource_type TEXT NOT NULL,
|
|
18032
|
+
resource_id TEXT NOT NULL,
|
|
18033
|
+
agent_id TEXT NOT NULL,
|
|
18034
|
+
lock_type TEXT NOT NULL DEFAULT 'advisory',
|
|
18035
|
+
locked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18036
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
18037
|
+
UNIQUE(resource_type, resource_id, lock_type)
|
|
18038
|
+
);
|
|
18039
|
+
CREATE INDEX IF NOT EXISTS idx_locks_resource ON resource_locks(resource_type, resource_id);
|
|
18040
|
+
CREATE INDEX IF NOT EXISTS idx_locks_agent ON resource_locks(agent_id);
|
|
18041
|
+
CREATE INDEX IF NOT EXISTS idx_locks_expires ON resource_locks(expires_at);
|
|
18042
|
+
|
|
18043
|
+
CREATE TABLE IF NOT EXISTS reactions (
|
|
18044
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
18045
|
+
message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
18046
|
+
agent TEXT NOT NULL,
|
|
18047
|
+
emoji TEXT NOT NULL,
|
|
18048
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18049
|
+
UNIQUE(message_id, agent, emoji)
|
|
18050
|
+
);
|
|
18051
|
+
CREATE INDEX IF NOT EXISTS idx_reactions_message ON reactions(message_id);
|
|
18052
|
+
|
|
18053
|
+
CREATE TABLE IF NOT EXISTS message_read_receipts (
|
|
18054
|
+
message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
18055
|
+
agent TEXT NOT NULL,
|
|
18056
|
+
read_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18057
|
+
PRIMARY KEY (message_id, agent)
|
|
18058
|
+
);
|
|
18059
|
+
CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id);
|
|
18060
|
+
CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent);
|
|
18061
|
+
|
|
18062
|
+
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
18063
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
18064
|
+
message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
18065
|
+
mentioned_agent TEXT NOT NULL,
|
|
18066
|
+
from_agent TEXT NOT NULL,
|
|
18067
|
+
space TEXT,
|
|
18068
|
+
notified_at TEXT,
|
|
18069
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
18070
|
+
);
|
|
18071
|
+
CREATE INDEX IF NOT EXISTS idx_mentions_agent ON message_mentions(mentioned_agent);
|
|
18072
|
+
CREATE INDEX IF NOT EXISTS idx_mentions_message ON message_mentions(message_id);
|
|
18073
|
+
CREATE INDEX IF NOT EXISTS idx_mentions_notified ON message_mentions(notified_at);
|
|
18074
|
+
|
|
18075
|
+
-- Full-text search using PostgreSQL tsvector
|
|
18076
|
+
ALTER TABLE messages ADD COLUMN IF NOT EXISTS search_vector tsvector;
|
|
18077
|
+
CREATE INDEX IF NOT EXISTS idx_messages_search ON messages USING GIN(search_vector);
|
|
18078
|
+
|
|
18079
|
+
CREATE OR REPLACE FUNCTION messages_search_vector_update() RETURNS trigger AS $$
|
|
18080
|
+
BEGIN
|
|
18081
|
+
NEW.search_vector :=
|
|
18082
|
+
setweight(to_tsvector('english', COALESCE(NEW.content, '')), 'A') ||
|
|
18083
|
+
setweight(to_tsvector('english', COALESCE(NEW.from_agent, '')), 'B') ||
|
|
18084
|
+
setweight(to_tsvector('english', COALESCE(NEW.to_agent, '')), 'B') ||
|
|
18085
|
+
setweight(to_tsvector('english', COALESCE(NEW.space, '')), 'C');
|
|
18086
|
+
RETURN NEW;
|
|
18087
|
+
END;
|
|
18088
|
+
$$ LANGUAGE plpgsql;
|
|
18089
|
+
|
|
18090
|
+
DROP TRIGGER IF EXISTS messages_search_vector_trigger ON messages;
|
|
18091
|
+
CREATE TRIGGER messages_search_vector_trigger
|
|
18092
|
+
BEFORE INSERT OR UPDATE OF content, from_agent, to_agent, space ON messages
|
|
18093
|
+
FOR EACH ROW EXECUTE FUNCTION messages_search_vector_update();
|
|
18094
|
+
|
|
18095
|
+
-- Backfill existing rows
|
|
18096
|
+
UPDATE messages SET search_vector =
|
|
18097
|
+
setweight(to_tsvector('english', COALESCE(content, '')), 'A') ||
|
|
18098
|
+
setweight(to_tsvector('english', COALESCE(from_agent, '')), 'B') ||
|
|
18099
|
+
setweight(to_tsvector('english', COALESCE(to_agent, '')), 'B') ||
|
|
18100
|
+
setweight(to_tsvector('english', COALESCE(space, '')), 'C')
|
|
18101
|
+
WHERE search_vector IS NULL;
|
|
18102
|
+
|
|
18103
|
+
-- Feedback table
|
|
18104
|
+
CREATE TABLE IF NOT EXISTS feedback (
|
|
18105
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
18106
|
+
message TEXT NOT NULL,
|
|
18107
|
+
email TEXT,
|
|
18108
|
+
category TEXT DEFAULT 'general',
|
|
18109
|
+
version TEXT,
|
|
18110
|
+
machine_id TEXT,
|
|
18111
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
18112
|
+
);
|
|
18113
|
+
|
|
18114
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
18115
|
+
id INTEGER PRIMARY KEY,
|
|
18116
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
18117
|
+
);
|
|
18118
|
+
INSERT INTO _migrations (id) VALUES (1) ON CONFLICT DO NOTHING;
|
|
18119
|
+
`
|
|
18120
|
+
];
|
|
18121
|
+
});
|
|
18122
|
+
|
|
17935
18123
|
// node_modules/zod/v3/helpers/util.js
|
|
17936
18124
|
var util;
|
|
17937
18125
|
(function(util2) {
|
|
@@ -40016,7 +40204,30 @@ function guessMimeType(name) {
|
|
|
40016
40204
|
};
|
|
40017
40205
|
return mimeMap[ext || ""] || "application/octet-stream";
|
|
40018
40206
|
}
|
|
40207
|
+
var MAX_MESSAGE_BYTES = 65536;
|
|
40208
|
+
var RATE_LIMIT_MAX = 60;
|
|
40209
|
+
var RATE_LIMIT_WINDOW_MS = 60000;
|
|
40210
|
+
var _rateLimitCounters = new Map;
|
|
40211
|
+
function checkRateLimit(agentId) {
|
|
40212
|
+
const dbPath = process.env.CONVERSATIONS_DB_PATH ?? process.env.HASNA_CONVERSATIONS_DB_PATH ?? "";
|
|
40213
|
+
if (dbPath === ":memory:" || dbPath.includes("test") || dbPath.includes("tmp"))
|
|
40214
|
+
return;
|
|
40215
|
+
const now = Date.now();
|
|
40216
|
+
const entry = _rateLimitCounters.get(agentId);
|
|
40217
|
+
if (!entry || now - entry.windowStart > RATE_LIMIT_WINDOW_MS) {
|
|
40218
|
+
_rateLimitCounters.set(agentId, { count: 1, windowStart: now });
|
|
40219
|
+
return;
|
|
40220
|
+
}
|
|
40221
|
+
entry.count++;
|
|
40222
|
+
if (entry.count > RATE_LIMIT_MAX) {
|
|
40223
|
+
throw new Error(`Rate limit exceeded: ${agentId} may send at most ${RATE_LIMIT_MAX} messages per minute.`);
|
|
40224
|
+
}
|
|
40225
|
+
}
|
|
40019
40226
|
function sendMessage(opts) {
|
|
40227
|
+
if (Buffer.byteLength(opts.content, "utf8") > MAX_MESSAGE_BYTES) {
|
|
40228
|
+
throw new Error(`Message content exceeds maximum size of ${MAX_MESSAGE_BYTES} bytes (64 KB).`);
|
|
40229
|
+
}
|
|
40230
|
+
checkRateLimit(opts.from);
|
|
40020
40231
|
const db2 = getDb();
|
|
40021
40232
|
const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
|
|
40022
40233
|
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID().slice(0, 8)}`);
|
|
@@ -43555,7 +43766,16 @@ function registerAdvancedTools(server, pkgVersion) {
|
|
|
43555
43766
|
}
|
|
43556
43767
|
|
|
43557
43768
|
// src/mcp/tools/cloud.ts
|
|
43558
|
-
var
|
|
43769
|
+
var SYNC_EXCLUDED = new Set([
|
|
43770
|
+
"messages",
|
|
43771
|
+
"reactions",
|
|
43772
|
+
"message_read_receipts",
|
|
43773
|
+
"message_mentions",
|
|
43774
|
+
"messages_fts",
|
|
43775
|
+
"_sync_conflicts",
|
|
43776
|
+
"_migrations"
|
|
43777
|
+
]);
|
|
43778
|
+
var CONFLICT_TABLES = new Set(["spaces", "projects", "agent_presence"]);
|
|
43559
43779
|
async function detectAndLogConflicts(local, cloud, table) {
|
|
43560
43780
|
if (!CONFLICT_TABLES.has(table))
|
|
43561
43781
|
return 0;
|
|
@@ -43638,7 +43858,7 @@ function registerCloudSyncTools(server) {
|
|
|
43638
43858
|
const localPath = cloudGetDbPath("conversations");
|
|
43639
43859
|
const local = new SqliteAdapter2(localPath);
|
|
43640
43860
|
const cloud = new PgAdapterAsync2(getConnectionString2("conversations"));
|
|
43641
|
-
const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables2(local).filter((t) => !
|
|
43861
|
+
const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables2(local).filter((t) => !SYNC_EXCLUDED.has(t));
|
|
43642
43862
|
let totalConflicts = 0;
|
|
43643
43863
|
for (const table of tableList) {
|
|
43644
43864
|
totalConflicts += await detectAndLogConflicts(local, cloud, table);
|
|
@@ -43683,7 +43903,7 @@ function registerCloudSyncTools(server) {
|
|
|
43683
43903
|
tableList = tablesStr.split(",").map((t) => t.trim());
|
|
43684
43904
|
} else {
|
|
43685
43905
|
try {
|
|
43686
|
-
tableList = (await listPgTables2(cloud)).filter((t) => !
|
|
43906
|
+
tableList = (await listPgTables2(cloud)).filter((t) => !SYNC_EXCLUDED.has(t));
|
|
43687
43907
|
} catch {
|
|
43688
43908
|
local.close();
|
|
43689
43909
|
await cloud.close();
|
|
@@ -43705,6 +43925,100 @@ function registerCloudSyncTools(server) {
|
|
|
43705
43925
|
if (errors3.length > 0)
|
|
43706
43926
|
lines.push(`Errors: ${errors3.join("; ")}`);
|
|
43707
43927
|
return { content: [{ type: "text", text: lines.join(`
|
|
43928
|
+
`) }] };
|
|
43929
|
+
} catch (e) {
|
|
43930
|
+
return { content: [{ type: "text", text: formatError2(e) }], isError: true };
|
|
43931
|
+
}
|
|
43932
|
+
});
|
|
43933
|
+
server.tool("conversations_cloud_sync", "Bidirectional cloud sync \u2014 pull remote changes then push local changes. Detects and logs conflicts.", {
|
|
43934
|
+
tables: exports_external.string().optional().describe("Comma-separated table names (default: all syncable tables)")
|
|
43935
|
+
}, async ({ tables: tablesStr }) => {
|
|
43936
|
+
try {
|
|
43937
|
+
const {
|
|
43938
|
+
getCloudConfig: getCloudConfig2,
|
|
43939
|
+
getConnectionString: getConnectionString2,
|
|
43940
|
+
syncPush: syncPush2,
|
|
43941
|
+
syncPull: syncPull2,
|
|
43942
|
+
listSqliteTables: listSqliteTables2,
|
|
43943
|
+
listPgTables: listPgTables2,
|
|
43944
|
+
SqliteAdapter: SqliteAdapter2,
|
|
43945
|
+
PgAdapterAsync: PgAdapterAsync2,
|
|
43946
|
+
getDbPath: cloudGetDbPath
|
|
43947
|
+
} = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
43948
|
+
const config2 = getCloudConfig2();
|
|
43949
|
+
if (config2.mode === "local") {
|
|
43950
|
+
return { content: [{ type: "text", text: "Error: cloud mode not configured." }], isError: true };
|
|
43951
|
+
}
|
|
43952
|
+
const local = new SqliteAdapter2(cloudGetDbPath("conversations"));
|
|
43953
|
+
const cloud = new PgAdapterAsync2(getConnectionString2("conversations"));
|
|
43954
|
+
let tableList;
|
|
43955
|
+
if (tablesStr) {
|
|
43956
|
+
tableList = tablesStr.split(",").map((t) => t.trim());
|
|
43957
|
+
} else {
|
|
43958
|
+
const localTables = new Set(listSqliteTables2(local).filter((t) => !SYNC_EXCLUDED.has(t)));
|
|
43959
|
+
let remoteTables;
|
|
43960
|
+
try {
|
|
43961
|
+
remoteTables = new Set((await listPgTables2(cloud)).filter((t) => !SYNC_EXCLUDED.has(t)));
|
|
43962
|
+
} catch {
|
|
43963
|
+
local.close();
|
|
43964
|
+
await cloud.close();
|
|
43965
|
+
return { content: [{ type: "text", text: "Error: failed to list cloud tables." }], isError: true };
|
|
43966
|
+
}
|
|
43967
|
+
tableList = [...new Set([...localTables, ...remoteTables])];
|
|
43968
|
+
}
|
|
43969
|
+
let totalConflicts = 0;
|
|
43970
|
+
for (const table of tableList) {
|
|
43971
|
+
totalConflicts += await detectAndLogConflicts(local, cloud, table);
|
|
43972
|
+
}
|
|
43973
|
+
const pullResults = await syncPull2(cloud, local, { tables: tableList });
|
|
43974
|
+
const pullTotal = pullResults.reduce((s, r) => s + r.rowsWritten, 0);
|
|
43975
|
+
const pushResults = await syncPush2(local, cloud, { tables: tableList });
|
|
43976
|
+
const pushTotal = pushResults.reduce((s, r) => s + r.rowsWritten, 0);
|
|
43977
|
+
local.close();
|
|
43978
|
+
await cloud.close();
|
|
43979
|
+
const allErrors = [
|
|
43980
|
+
...pullResults.flatMap((r) => r.errors.map((e) => `pull: ${e}`)),
|
|
43981
|
+
...pushResults.flatMap((r) => r.errors.map((e) => `push: ${e}`))
|
|
43982
|
+
];
|
|
43983
|
+
const lines = [
|
|
43984
|
+
`Sync complete: pulled ${pullTotal} rows, pushed ${pushTotal} rows across ${tableList.length} table(s).`
|
|
43985
|
+
];
|
|
43986
|
+
if (totalConflicts > 0)
|
|
43987
|
+
lines.push(`Conflicts detected: ${totalConflicts} (logged to _sync_conflicts)`);
|
|
43988
|
+
if (allErrors.length > 0)
|
|
43989
|
+
lines.push(`Errors: ${allErrors.join("; ")}`);
|
|
43990
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
43991
|
+
`) }] };
|
|
43992
|
+
} catch (e) {
|
|
43993
|
+
return { content: [{ type: "text", text: formatError2(e) }], isError: true };
|
|
43994
|
+
}
|
|
43995
|
+
});
|
|
43996
|
+
server.tool("conversations_cloud_migrate", "Run PostgreSQL migrations against the configured RDS instance to initialize the cloud schema", {
|
|
43997
|
+
dry_run: exports_external.boolean().optional().describe("Print SQL without executing")
|
|
43998
|
+
}, async ({ dry_run }) => {
|
|
43999
|
+
try {
|
|
44000
|
+
const { getCloudConfig: getCloudConfig2, getConnectionString: getConnectionString2, PgAdapterAsync: PgAdapterAsync2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
44001
|
+
const { PG_MIGRATIONS: PG_MIGRATIONS2 } = await Promise.resolve().then(() => (init_pg_migrations(), exports_pg_migrations));
|
|
44002
|
+
const config2 = getCloudConfig2();
|
|
44003
|
+
if (config2.mode === "local") {
|
|
44004
|
+
return { content: [{ type: "text", text: "Error: cloud mode not configured." }], isError: true };
|
|
44005
|
+
}
|
|
44006
|
+
if (dry_run) {
|
|
44007
|
+
return { content: [{ type: "text", text: PG_MIGRATIONS2.join(`
|
|
44008
|
+
|
|
44009
|
+
---
|
|
44010
|
+
|
|
44011
|
+
`) }] };
|
|
44012
|
+
}
|
|
44013
|
+
const pg = new PgAdapterAsync2(getConnectionString2("conversations"));
|
|
44014
|
+
const lines = [];
|
|
44015
|
+
for (let i = 0;i < PG_MIGRATIONS2.length; i++) {
|
|
44016
|
+
await pg.run(PG_MIGRATIONS2[i]);
|
|
44017
|
+
lines.push(`Migration ${i + 1}/${PG_MIGRATIONS2.length}: applied`);
|
|
44018
|
+
}
|
|
44019
|
+
await pg.close();
|
|
44020
|
+
lines.push("All migrations applied successfully.");
|
|
44021
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
43708
44022
|
`) }] };
|
|
43709
44023
|
} catch (e) {
|
|
43710
44024
|
return { content: [{ type: "text", text: formatError2(e) }], isError: true };
|
|
@@ -43738,7 +44052,7 @@ function formatError2(e) {
|
|
|
43738
44052
|
// package.json
|
|
43739
44053
|
var package_default = {
|
|
43740
44054
|
name: "@hasna/conversations",
|
|
43741
|
-
version: "0.2.
|
|
44055
|
+
version: "0.2.26",
|
|
43742
44056
|
description: "Real-time CLI messaging for AI agents",
|
|
43743
44057
|
type: "module",
|
|
43744
44058
|
bin: {
|
|
@@ -43767,7 +44081,7 @@ var package_default = {
|
|
|
43767
44081
|
test: "bun test",
|
|
43768
44082
|
dev: "bun run ./src/cli/index.tsx",
|
|
43769
44083
|
typecheck: "tsc --noEmit",
|
|
43770
|
-
prepublishOnly: "bun run build",
|
|
44084
|
+
prepublishOnly: "bun run build:dashboard && bun run build",
|
|
43771
44085
|
postinstall: "mkdir -p $HOME/.hasna/conversations $HOME/.hasna/conversations/training 2>/dev/null || true"
|
|
43772
44086
|
},
|
|
43773
44087
|
keywords: [
|