@ykurilov/agent-chat-mcp 0.56.0 → 0.58.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/CHANGELOG.md +30 -0
- package/dist/postgres.js +6 -11
- package/dist/postgres.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @ykurilov/agent-chat-mcp
|
|
2
2
|
|
|
3
|
+
## 0.58.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 760962e: Хранилища пакетов на PostgreSQL можно открывать одновременно на совсем пустой базе. Раньше пакеты разом создавали
|
|
8
|
+
схему `agent_chat`, проигравший падал на `CREATE SCHEMA IF NOT EXISTS`, и его хранилище не работало до перезапуска.
|
|
9
|
+
Теперь схема создаётся под общей блокировкой всех пакетов, она держится только на создании схемы. Миграции идут под
|
|
10
|
+
своей блокировкой пакета: разные пакеты друг друга не ждут, взаимной блокировки нет. Столкновение «такое уже есть»
|
|
11
|
+
повторяется само. Новое в core: `migratePostgresSchema({ connect, lockKey, migrate })` — для своих пакетов на той же
|
|
12
|
+
схеме.
|
|
13
|
+
|
|
14
|
+
### Как обновить приложение
|
|
15
|
+
|
|
16
|
+
Ничего делать не нужно.
|
|
17
|
+
- Updated dependencies [332d90e]
|
|
18
|
+
- Updated dependencies [2ed600d]
|
|
19
|
+
- Updated dependencies [760962e]
|
|
20
|
+
- @ykurilov/agent-chat-core@0.58.0
|
|
21
|
+
- @ykurilov/agent-chat-web-tools@0.58.0
|
|
22
|
+
|
|
23
|
+
## 0.57.0
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [ade99cc]
|
|
28
|
+
- Updated dependencies [86afa20]
|
|
29
|
+
- Updated dependencies [624c10c]
|
|
30
|
+
- @ykurilov/agent-chat-core@0.57.0
|
|
31
|
+
- @ykurilov/agent-chat-web-tools@0.57.0
|
|
32
|
+
|
|
3
33
|
## 0.56.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/dist/postgres.js
CHANGED
|
@@ -13,6 +13,7 @@ import { fileURLToPath } from "url";
|
|
|
13
13
|
import { desc, eq, lt, sql } from "drizzle-orm";
|
|
14
14
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
15
15
|
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
16
|
+
import { migratePostgresSchema } from "@ykurilov/agent-chat-core";
|
|
16
17
|
|
|
17
18
|
// src/store/postgres-schema.ts
|
|
18
19
|
import { bigint, boolean, index, integer, pgSchema, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
|
|
@@ -99,17 +100,11 @@ function toRow(server) {
|
|
|
99
100
|
};
|
|
100
101
|
}
|
|
101
102
|
async function migrateToLatest(pool, migrationsFolder) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} finally {
|
|
108
|
-
await client.query("select pg_advisory_unlock($1)", [MCP_MIGRATION_LOCK_KEY]);
|
|
109
|
-
}
|
|
110
|
-
} finally {
|
|
111
|
-
client.release();
|
|
112
|
-
}
|
|
103
|
+
await migratePostgresSchema({
|
|
104
|
+
connect: () => pool.connect(),
|
|
105
|
+
lockKey: MCP_MIGRATION_LOCK_KEY,
|
|
106
|
+
migrate: (client) => migrate(drizzle(client), { migrationsFolder, migrationsSchema: "agent_chat", migrationsTable: MCP_MIGRATIONS_TABLE })
|
|
107
|
+
});
|
|
113
108
|
}
|
|
114
109
|
function createPostgresStore(pool, migrationsFolder) {
|
|
115
110
|
const db = drizzle(pool);
|
package/dist/postgres.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/postgres.ts","../src/store/postgres-store.ts","../src/store/postgres-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport type { Pool } from \"pg\";\nimport { createPostgresStore } from \"./store/postgres-store\";\nimport type { McpStore } from \"./types\";\n\nexport { MCP_MIGRATION_LOCK_KEY, MCP_MIGRATIONS_TABLE } from \"./store/migration-settings\";\nexport { mcpCalls, mcpServers } from \"./store/postgres-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/postgres`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/postgres\", import.meta.url));\n\n/** Серверы MCP и журнал вызовов в PostgreSQL на пуле приложения (схема `agent_chat`). Пул закрывает приложение. */\nexport function createPostgresMcpStore(options: { pool: Pool }): McpStore {\n return createPostgresStore(options.pool, MIGRATIONS_FOLDER);\n}\n","import { desc, eq, lt, sql } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { migrate } from \"drizzle-orm/node-postgres/migrator\";\nimport type { Pool } from \"pg\";\nimport type { McpStore, StoredMcpServer } from \"../types\";\nimport { MCP_MIGRATION_LOCK_KEY, MCP_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { mcpCalls, mcpRevision, mcpServers } from \"./postgres-schema\";\nimport { callRecord, parsedServerColumns, serverTextColumns } from \"./rows\";\n\ntype ServerRow = typeof mcpServers.$inferSelect;\n\n/** PostgreSQL не хранит символ NUL в тексте: в записи его не бывает, но чужой сервер мог прислать. */\nconst withoutNul = (text: string) => text.replaceAll(\"\\u0000\", \"\");\n\nfunction toServer(row: ServerRow): StoredMcpServer {\n return {\n id: row.id,\n name: row.name,\n prefix: row.prefix,\n status: row.status as StoredMcpServer[\"status\"],\n transport: row.transport as StoredMcpServer[\"transport\"],\n viaProxy: row.viaProxy,\n timeoutMs: row.timeoutMs,\n secretUrl: row.secretUrl,\n displayUrl: row.displayUrl,\n urlHasHiddenParts: row.urlHasHiddenParts,\n secretHeaders: row.secretHeaders,\n ...parsedServerColumns(row),\n toolsUpdatedAt: row.toolsUpdatedAt?.toISOString() ?? null,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toRow(server: StoredMcpServer): ServerRow {\n const text = serverTextColumns(server);\n return {\n ...server,\n name: withoutNul(server.name),\n headerNames: text.headerNames,\n // В JSON-тексте NUL записан как \\u0000 — шесть обычных знаков, базе он не мешает.\n tools: text.tools,\n toolSettings: text.toolSettings,\n lastCheck: text.lastCheck,\n toolsUpdatedAt: server.toolsUpdatedAt === null ? null : new Date(server.toolsUpdatedAt),\n createdAt: new Date(server.createdAt),\n updatedAt: new Date(server.updatedAt),\n };\n}\n\n/**\n * Приводит таблицы к последней миграции на одном соединении под блокировкой базы: несколько серверов стартуют\n * сразу — второй ждёт первого и видит применённые миграции.\n */\nasync function migrateToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n const client = await pool.connect();\n try {\n await client.query(\"select pg_advisory_lock($1)\", [MCP_MIGRATION_LOCK_KEY]);\n try {\n await migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: MCP_MIGRATIONS_TABLE });\n } finally {\n await client.query(\"select pg_advisory_unlock($1)\", [MCP_MIGRATION_LOCK_KEY]);\n }\n } finally {\n client.release();\n }\n}\n\n/** Хранилище в PostgreSQL (схема `agent_chat`) на пуле приложения. Миграция — при первом обращении; пул закрывает приложение. */\nexport function createPostgresStore(pool: Pool, migrationsFolder: string): McpStore {\n const db = drizzle(pool);\n const ready = migrateToLatest(pool, migrationsFolder);\n // Ошибку миграции увидит первое обращение; здесь — чтобы не было «необработанного отказа».\n ready.catch(() => {});\n\n /** Новая отметка версии — в той же транзакции, что и правка сервера. */\n const bumpRevision = (tx: Pick<typeof db, \"insert\">) =>\n tx\n .insert(mcpRevision)\n .values({ id: 1, revision: 1 })\n .onConflictDoUpdate({ target: mcpRevision.id, set: { revision: sql`${mcpRevision.revision} + 1` } });\n\n return {\n async listServers() {\n await ready;\n return (await db.select().from(mcpServers).orderBy(mcpServers.createdAt, mcpServers.id)).map(toServer);\n },\n async getServer(id) {\n await ready;\n const [row] = await db.select().from(mcpServers).where(eq(mcpServers.id, id));\n return row ? toServer(row) : null;\n },\n async saveServer(server) {\n await ready;\n const row = toRow(server);\n await db.transaction(async (tx) => {\n await tx.insert(mcpServers).values(row).onConflictDoUpdate({ target: mcpServers.id, set: row });\n await bumpRevision(tx);\n });\n },\n async deleteServer(id) {\n await ready;\n await db.transaction(async (tx) => {\n await tx.delete(mcpCalls).where(eq(mcpCalls.serverId, id));\n await tx.delete(mcpServers).where(eq(mcpServers.id, id));\n await bumpRevision(tx);\n });\n },\n async revision() {\n await ready;\n const [row] = await db.select().from(mcpRevision).where(eq(mcpRevision.id, 1));\n return row?.revision ?? 0;\n },\n async recordCall(call) {\n await ready;\n await db.insert(mcpCalls).values({ ...call, error: call.error === undefined ? null : withoutNul(call.error), startedAt: new Date(call.startedAt) });\n },\n async listCalls({ serverId, limit }) {\n await ready;\n const rows = await db\n .select()\n .from(mcpCalls)\n .where(serverId === undefined ? undefined : eq(mcpCalls.serverId, serverId))\n .orderBy(desc(mcpCalls.startedAt), desc(mcpCalls.id))\n .limit(limit);\n return rows.map((row) => callRecord({ ...row, startedAt: row.startedAt.toISOString() }));\n },\n async deleteCallsBefore(before) {\n await ready;\n const removed = await db.delete(mcpCalls).where(lt(mcpCalls.startedAt, new Date(before))).returning({ id: mcpCalls.id });\n return removed.length;\n },\n };\n}\n","import { bigint, boolean, index, integer, pgSchema, text, timestamp, uniqueIndex } from \"drizzle-orm/pg-core\";\n\n/** Та же схема базы, что у хранилища чатов: у приложения одна база и одна схема `agent_chat`. */\nconst agentChatSchema = pgSchema(\"agent_chat\");\n\nconst moment = (name: string) => timestamp(name, { mode: \"date\", precision: 3, withTimezone: true });\n\n/**\n * Серверы MCP. Снимок инструментов и настройки — JSON-текстом, не `jsonb`: `jsonb` переставляет ключи, а от порядка\n * ключей схемы зависит кэш модели.\n */\nexport const mcpServers = agentChatSchema.table(\n \"mcp_servers\",\n {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n prefix: text(\"prefix\").notNull(),\n status: text(\"status\").notNull(),\n transport: text(\"transport\").notNull(),\n viaProxy: boolean(\"via_proxy\").notNull(),\n timeoutMs: integer(\"timeout_ms\").notNull(),\n secretUrl: text(\"secret_url\").notNull(),\n displayUrl: text(\"display_url\").notNull(),\n urlHasHiddenParts: boolean(\"url_has_hidden_parts\").notNull(),\n secretHeaders: text(\"secret_headers\"),\n headerNames: text(\"header_names\").notNull(),\n tools: text(\"tools\").notNull(),\n toolsUpdatedAt: moment(\"tools_updated_at\"),\n toolSettings: text(\"tool_settings\").notNull(),\n lastCheck: text(\"last_check\"),\n createdAt: moment(\"created_at\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n },\n (table) => [uniqueIndex(\"mcp_servers_prefix_idx\").on(table.prefix)],\n);\n\n/** Журнал вызовов: без входа и результата. */\nexport const mcpCalls = agentChatSchema.table(\n \"mcp_calls\",\n {\n id: text(\"id\").primaryKey(),\n serverId: text(\"server_id\").notNull(),\n toolName: text(\"tool_name\").notNull(),\n ownerId: text(\"owner_id\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n startedAt: moment(\"started_at\").notNull(),\n durationMs: integer(\"duration_ms\").notNull(),\n ok: boolean(\"ok\").notNull(),\n error: text(\"error\"),\n },\n (table) => [index(\"mcp_calls_started_idx\").on(table.startedAt), index(\"mcp_calls_server_started_idx\").on(table.serverId, table.startedAt)],\n);\n\n/** Отметка версии серверов: одна строка, число растёт при каждой записи и удалении сервера. */\nexport const mcpRevision = agentChatSchema.table(\"mcp_revision\", {\n id: integer(\"id\").primaryKey(),\n revision: bigint(\"revision\", { mode: \"number\" }).notNull(),\n});\n"],"mappings":"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","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../src/postgres.ts","../src/store/postgres-store.ts","../src/store/postgres-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport type { Pool } from \"pg\";\nimport { createPostgresStore } from \"./store/postgres-store\";\nimport type { McpStore } from \"./types\";\n\nexport { MCP_MIGRATION_LOCK_KEY, MCP_MIGRATIONS_TABLE } from \"./store/migration-settings\";\nexport { mcpCalls, mcpServers } from \"./store/postgres-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/postgres`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/postgres\", import.meta.url));\n\n/** Серверы MCP и журнал вызовов в PostgreSQL на пуле приложения (схема `agent_chat`). Пул закрывает приложение. */\nexport function createPostgresMcpStore(options: { pool: Pool }): McpStore {\n return createPostgresStore(options.pool, MIGRATIONS_FOLDER);\n}\n","import { desc, eq, lt, sql } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { migrate } from \"drizzle-orm/node-postgres/migrator\";\nimport { migratePostgresSchema } from \"@ykurilov/agent-chat-core\";\nimport type { Pool } from \"pg\";\nimport type { McpStore, StoredMcpServer } from \"../types\";\nimport { MCP_MIGRATION_LOCK_KEY, MCP_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { mcpCalls, mcpRevision, mcpServers } from \"./postgres-schema\";\nimport { callRecord, parsedServerColumns, serverTextColumns } from \"./rows\";\n\ntype ServerRow = typeof mcpServers.$inferSelect;\n\n/** PostgreSQL не хранит символ NUL в тексте: в записи его не бывает, но чужой сервер мог прислать. */\nconst withoutNul = (text: string) => text.replaceAll(\"\\u0000\", \"\");\n\nfunction toServer(row: ServerRow): StoredMcpServer {\n return {\n id: row.id,\n name: row.name,\n prefix: row.prefix,\n status: row.status as StoredMcpServer[\"status\"],\n transport: row.transport as StoredMcpServer[\"transport\"],\n viaProxy: row.viaProxy,\n timeoutMs: row.timeoutMs,\n secretUrl: row.secretUrl,\n displayUrl: row.displayUrl,\n urlHasHiddenParts: row.urlHasHiddenParts,\n secretHeaders: row.secretHeaders,\n ...parsedServerColumns(row),\n toolsUpdatedAt: row.toolsUpdatedAt?.toISOString() ?? null,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toRow(server: StoredMcpServer): ServerRow {\n const text = serverTextColumns(server);\n return {\n ...server,\n name: withoutNul(server.name),\n headerNames: text.headerNames,\n // В JSON-тексте NUL записан как \\u0000 — шесть обычных знаков, базе он не мешает.\n tools: text.tools,\n toolSettings: text.toolSettings,\n lastCheck: text.lastCheck,\n toolsUpdatedAt: server.toolsUpdatedAt === null ? null : new Date(server.toolsUpdatedAt),\n createdAt: new Date(server.createdAt),\n updatedAt: new Date(server.updatedAt),\n };\n}\n\n/**\n * Приводит таблицы к последней миграции. Схема `agent_chat` создаётся под общей блокировкой всех пакетов, миграции — под своей:\n * копии пакета ждут друг друга, а разные пакеты стартуют разом на пустой базе без гонки за схему.\n */\nasync function migrateToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n await migratePostgresSchema({\n connect: () => pool.connect(),\n lockKey: MCP_MIGRATION_LOCK_KEY,\n migrate: (client) => migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: MCP_MIGRATIONS_TABLE }),\n });\n}\n\n/** Хранилище в PostgreSQL (схема `agent_chat`) на пуле приложения. Миграция — при первом обращении; пул закрывает приложение. */\nexport function createPostgresStore(pool: Pool, migrationsFolder: string): McpStore {\n const db = drizzle(pool);\n const ready = migrateToLatest(pool, migrationsFolder);\n // Ошибку миграции увидит первое обращение; здесь — чтобы не было «необработанного отказа».\n ready.catch(() => {});\n\n /** Новая отметка версии — в той же транзакции, что и правка сервера. */\n const bumpRevision = (tx: Pick<typeof db, \"insert\">) =>\n tx\n .insert(mcpRevision)\n .values({ id: 1, revision: 1 })\n .onConflictDoUpdate({ target: mcpRevision.id, set: { revision: sql`${mcpRevision.revision} + 1` } });\n\n return {\n async listServers() {\n await ready;\n return (await db.select().from(mcpServers).orderBy(mcpServers.createdAt, mcpServers.id)).map(toServer);\n },\n async getServer(id) {\n await ready;\n const [row] = await db.select().from(mcpServers).where(eq(mcpServers.id, id));\n return row ? toServer(row) : null;\n },\n async saveServer(server) {\n await ready;\n const row = toRow(server);\n await db.transaction(async (tx) => {\n await tx.insert(mcpServers).values(row).onConflictDoUpdate({ target: mcpServers.id, set: row });\n await bumpRevision(tx);\n });\n },\n async deleteServer(id) {\n await ready;\n await db.transaction(async (tx) => {\n await tx.delete(mcpCalls).where(eq(mcpCalls.serverId, id));\n await tx.delete(mcpServers).where(eq(mcpServers.id, id));\n await bumpRevision(tx);\n });\n },\n async revision() {\n await ready;\n const [row] = await db.select().from(mcpRevision).where(eq(mcpRevision.id, 1));\n return row?.revision ?? 0;\n },\n async recordCall(call) {\n await ready;\n await db.insert(mcpCalls).values({ ...call, error: call.error === undefined ? null : withoutNul(call.error), startedAt: new Date(call.startedAt) });\n },\n async listCalls({ serverId, limit }) {\n await ready;\n const rows = await db\n .select()\n .from(mcpCalls)\n .where(serverId === undefined ? undefined : eq(mcpCalls.serverId, serverId))\n .orderBy(desc(mcpCalls.startedAt), desc(mcpCalls.id))\n .limit(limit);\n return rows.map((row) => callRecord({ ...row, startedAt: row.startedAt.toISOString() }));\n },\n async deleteCallsBefore(before) {\n await ready;\n const removed = await db.delete(mcpCalls).where(lt(mcpCalls.startedAt, new Date(before))).returning({ id: mcpCalls.id });\n return removed.length;\n },\n };\n}\n","import { bigint, boolean, index, integer, pgSchema, text, timestamp, uniqueIndex } from \"drizzle-orm/pg-core\";\n\n/** Та же схема базы, что у хранилища чатов: у приложения одна база и одна схема `agent_chat`. */\nconst agentChatSchema = pgSchema(\"agent_chat\");\n\nconst moment = (name: string) => timestamp(name, { mode: \"date\", precision: 3, withTimezone: true });\n\n/**\n * Серверы MCP. Снимок инструментов и настройки — JSON-текстом, не `jsonb`: `jsonb` переставляет ключи, а от порядка\n * ключей схемы зависит кэш модели.\n */\nexport const mcpServers = agentChatSchema.table(\n \"mcp_servers\",\n {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n prefix: text(\"prefix\").notNull(),\n status: text(\"status\").notNull(),\n transport: text(\"transport\").notNull(),\n viaProxy: boolean(\"via_proxy\").notNull(),\n timeoutMs: integer(\"timeout_ms\").notNull(),\n secretUrl: text(\"secret_url\").notNull(),\n displayUrl: text(\"display_url\").notNull(),\n urlHasHiddenParts: boolean(\"url_has_hidden_parts\").notNull(),\n secretHeaders: text(\"secret_headers\"),\n headerNames: text(\"header_names\").notNull(),\n tools: text(\"tools\").notNull(),\n toolsUpdatedAt: moment(\"tools_updated_at\"),\n toolSettings: text(\"tool_settings\").notNull(),\n lastCheck: text(\"last_check\"),\n createdAt: moment(\"created_at\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n },\n (table) => [uniqueIndex(\"mcp_servers_prefix_idx\").on(table.prefix)],\n);\n\n/** Журнал вызовов: без входа и результата. */\nexport const mcpCalls = agentChatSchema.table(\n \"mcp_calls\",\n {\n id: text(\"id\").primaryKey(),\n serverId: text(\"server_id\").notNull(),\n toolName: text(\"tool_name\").notNull(),\n ownerId: text(\"owner_id\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n startedAt: moment(\"started_at\").notNull(),\n durationMs: integer(\"duration_ms\").notNull(),\n ok: boolean(\"ok\").notNull(),\n error: text(\"error\"),\n },\n (table) => [index(\"mcp_calls_started_idx\").on(table.startedAt), index(\"mcp_calls_server_started_idx\").on(table.serverId, table.startedAt)],\n);\n\n/** Отметка версии серверов: одна строка, число растёт при каждой записи и удалении сервера. */\nexport const mcpRevision = agentChatSchema.table(\"mcp_revision\", {\n id: integer(\"id\").primaryKey(),\n revision: bigint(\"revision\", { mode: \"number\" }).notNull(),\n});\n"],"mappings":"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","names":["text"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ykurilov/agent-chat-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"ai": "7.0.122",
|
|
50
50
|
"drizzle-orm": "^0.45.3",
|
|
51
51
|
"undici": "^7.30.0",
|
|
52
|
-
"@ykurilov/agent-chat-core": "0.
|
|
53
|
-
"@ykurilov/agent-chat-web-tools": "0.
|
|
52
|
+
"@ykurilov/agent-chat-core": "0.58.0",
|
|
53
|
+
"@ykurilov/agent-chat-web-tools": "0.58.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"better-sqlite3": "^13.0.3",
|