@agent-native/core 0.23.0 → 0.24.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/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +5 -6
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/run-manager.d.ts +9 -2
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +9 -2
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/cli/workspace-dev.d.ts.map +1 -1
- package/dist/cli/workspace-dev.js +101 -9
- package/dist/cli/workspace-dev.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +4 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +51 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +2 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +70 -10
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +157 -2
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +6 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +9 -2
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts +3 -0
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +23 -4
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/use-chat-threads.spec.js +59 -0
- package/dist/client/use-chat-threads.spec.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +112 -2
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +37 -2
- package/dist/extensions/routes.js.map +1 -1
- package/dist/extensions/schema.d.ts +275 -0
- package/dist/extensions/schema.d.ts.map +1 -1
- package/dist/extensions/schema.js +53 -1
- package/dist/extensions/schema.js.map +1 -1
- package/dist/extensions/store.d.ts +40 -0
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +367 -3
- package/dist/extensions/store.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +4 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +6 -2
- package/dist/server/auth.js.map +1 -1
- package/docs/content/extensions.md +5 -0
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* renamed to `extensions`/`extension*`; the DB-side names stay so existing
|
|
16
16
|
* deployed rows remain readable.
|
|
17
17
|
*/
|
|
18
|
-
import { table, text, now } from "../db/schema.js";
|
|
18
|
+
import { table, text, integer, now } from "../db/schema.js";
|
|
19
19
|
import { ownableColumns, createSharesTable } from "../sharing/schema.js";
|
|
20
20
|
export const extensions = table("tools", {
|
|
21
21
|
id: text("id").primaryKey(),
|
|
@@ -34,6 +34,22 @@ export const extensionHides = table("tool_hidden_extensions", {
|
|
|
34
34
|
ownerEmail: text("owner_email").notNull(),
|
|
35
35
|
createdAt: text("created_at").notNull().default(now()),
|
|
36
36
|
});
|
|
37
|
+
export const extensionHistory = table("tool_history", {
|
|
38
|
+
id: text("id").primaryKey(),
|
|
39
|
+
extensionId: text("tool_id").notNull(),
|
|
40
|
+
version: integer("version").notNull(),
|
|
41
|
+
operation: text("operation").notNull(),
|
|
42
|
+
summary: text("summary").notNull().default(""),
|
|
43
|
+
name: text("name").notNull(),
|
|
44
|
+
description: text("description").notNull().default(""),
|
|
45
|
+
content: text("content").notNull().default(""),
|
|
46
|
+
icon: text("icon"),
|
|
47
|
+
actorEmail: text("actor_email"),
|
|
48
|
+
ownerEmail: text("owner_email").notNull().default("local@localhost"),
|
|
49
|
+
orgId: text("org_id"),
|
|
50
|
+
visibility: text("visibility").notNull().default("private"),
|
|
51
|
+
createdAt: text("created_at").notNull().default(now()),
|
|
52
|
+
});
|
|
37
53
|
export const EXTENSIONS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tools (
|
|
38
54
|
id TEXT PRIMARY KEY,
|
|
39
55
|
name TEXT NOT NULL,
|
|
@@ -141,6 +157,42 @@ export const EXTENSION_HIDES_UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXIS
|
|
|
141
157
|
ON tool_hidden_extensions (owner_email, tool_id)`;
|
|
142
158
|
export const EXTENSION_HIDES_OWNER_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_hidden_extensions_owner_idx
|
|
143
159
|
ON tool_hidden_extensions (owner_email)`;
|
|
160
|
+
export const EXTENSION_HISTORY_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_history (
|
|
161
|
+
id TEXT PRIMARY KEY,
|
|
162
|
+
tool_id TEXT NOT NULL,
|
|
163
|
+
version INTEGER NOT NULL,
|
|
164
|
+
operation TEXT NOT NULL,
|
|
165
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
166
|
+
name TEXT NOT NULL,
|
|
167
|
+
description TEXT NOT NULL DEFAULT '',
|
|
168
|
+
content TEXT NOT NULL DEFAULT '',
|
|
169
|
+
icon TEXT,
|
|
170
|
+
actor_email TEXT,
|
|
171
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
172
|
+
org_id TEXT,
|
|
173
|
+
visibility TEXT NOT NULL DEFAULT 'private',
|
|
174
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
175
|
+
)`;
|
|
176
|
+
export const EXTENSION_HISTORY_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_history (
|
|
177
|
+
id TEXT PRIMARY KEY,
|
|
178
|
+
tool_id TEXT NOT NULL,
|
|
179
|
+
version INTEGER NOT NULL,
|
|
180
|
+
operation TEXT NOT NULL,
|
|
181
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
182
|
+
name TEXT NOT NULL,
|
|
183
|
+
description TEXT NOT NULL DEFAULT '',
|
|
184
|
+
content TEXT NOT NULL DEFAULT '',
|
|
185
|
+
icon TEXT,
|
|
186
|
+
actor_email TEXT,
|
|
187
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
188
|
+
org_id TEXT,
|
|
189
|
+
visibility TEXT NOT NULL DEFAULT 'private',
|
|
190
|
+
created_at TEXT NOT NULL DEFAULT now()
|
|
191
|
+
)`;
|
|
192
|
+
export const EXTENSION_HISTORY_VERSION_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_history_tool_version_idx
|
|
193
|
+
ON tool_history (tool_id, version)`;
|
|
194
|
+
export const EXTENSION_HISTORY_CREATED_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_history_tool_created_idx
|
|
195
|
+
ON tool_history (tool_id, created_at)`;
|
|
144
196
|
// ---------------------------------------------------------------------------
|
|
145
197
|
// extension_consents — vestigial, kept for additive-schema compliance
|
|
146
198
|
// ---------------------------------------------------------------------------
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/extensions/schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Drizzle schema for the framework extensions system.\n *\n * Extensions are mini Alpine.js apps that run inside sandboxed iframes. They\n * can call external APIs via a server-side proxy that resolves `${keys.NAME}`\n * secret references. Extensions use the standard sharing model (private by\n * default, shareable with org/others).\n *\n * The tables are auto-created at server boot via `ensureTable()` in store.ts,\n * following the same pattern as `app_secrets`.\n *\n * NOTE: physical SQL table/column names stay as `tools`, `tool_data`,\n * `tool_shares`, `tool_consents`, `tool_id`, etc. — additive-only schema\n * policy means we never rename DB-level identifiers. The JS/TS surface is\n * renamed to `extensions`/`extension*`; the DB-side names stay so existing\n * deployed rows remain readable.\n */\n\nimport { table, text, now } from \"../db/schema.js\";\nimport { ownableColumns, createSharesTable } from \"../sharing/schema.js\";\n\nexport const extensions = table(\"tools\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n description: text(\"description\").notNull().default(\"\"),\n content: text(\"content\").notNull().default(\"\"),\n icon: text(\"icon\"),\n createdAt: text(\"created_at\").notNull().default(now()),\n updatedAt: text(\"updated_at\").notNull().default(now()),\n ...ownableColumns(),\n});\n\nexport const extensionShares = createSharesTable(\"tool_shares\");\n\nexport const extensionHides = table(\"tool_hidden_extensions\", {\n id: text(\"id\").primaryKey(),\n extensionId: text(\"tool_id\").notNull(),\n ownerEmail: text(\"owner_email\").notNull(),\n createdAt: text(\"created_at\").notNull().default(now()),\n});\n\nexport const EXTENSIONS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tools (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n description TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n icon TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n org_id TEXT,\n visibility TEXT NOT NULL DEFAULT 'private'\n)`;\n\nexport const EXTENSIONS_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tools (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n description TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n icon TEXT,\n created_at TEXT NOT NULL DEFAULT now(),\n updated_at TEXT NOT NULL DEFAULT now(),\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n org_id TEXT,\n visibility TEXT NOT NULL DEFAULT 'private'\n)`;\n\nexport const EXTENSION_SHARES_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_shares (\n id TEXT PRIMARY KEY,\n resource_id TEXT NOT NULL,\n principal_type TEXT NOT NULL,\n principal_id TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'viewer',\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_SHARES_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_shares (\n id TEXT PRIMARY KEY,\n resource_id TEXT NOT NULL,\n principal_type TEXT NOT NULL,\n principal_id TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'viewer',\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const extensionData = table(\"tool_data\", {\n id: text(\"id\").primaryKey(),\n extensionId: text(\"tool_id\").notNull(),\n collection: text(\"collection\").notNull(),\n itemId: text(\"item_id\"),\n data: text(\"data\").notNull(),\n ownerEmail: text(\"owner_email\").notNull().default(\"local@localhost\"),\n scope: text(\"scope\").notNull().default(\"user\"),\n orgId: text(\"org_id\"),\n scopeKey: text(\"scope_key\").notNull().default(\"local@localhost\"),\n createdAt: text(\"created_at\").notNull().default(now()),\n updatedAt: text(\"updated_at\").notNull().default(now()),\n});\n\nexport const EXTENSION_DATA_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_data (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n collection TEXT NOT NULL,\n item_id TEXT,\n data TEXT NOT NULL,\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n scope TEXT NOT NULL DEFAULT 'user',\n org_id TEXT,\n scope_key TEXT NOT NULL DEFAULT 'local@localhost',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_DATA_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_data (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n collection TEXT NOT NULL,\n item_id TEXT,\n data TEXT NOT NULL,\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n scope TEXT NOT NULL DEFAULT 'user',\n org_id TEXT,\n scope_key TEXT NOT NULL DEFAULT 'local@localhost',\n created_at TEXT NOT NULL DEFAULT now(),\n updated_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const EXTENSION_DATA_ITEM_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_data_scoped_item_idx\n ON tool_data (tool_id, collection, scope_key, item_id)`;\n\nexport const EXTENSION_DATA_ITEM_INDEX_SQL_PG = `CREATE UNIQUE INDEX IF NOT EXISTS tool_data_scoped_item_idx\n ON tool_data (tool_id, collection, scope_key, item_id)`;\n\nexport const EXTENSION_DATA_DROP_OLD_INDEX_SQL = `DROP INDEX IF EXISTS tool_data_scope_item_idx`;\nexport const EXTENSION_DATA_DROP_OLD_INDEX_SQL_PG = `DROP INDEX IF EXISTS tool_data_scope_item_idx`;\n\nexport const EXTENSIONS_OWNER_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tools_owner_idx ON tools (owner_email)`;\nexport const EXTENSIONS_ORG_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tools_org_idx ON tools (org_id)`;\nexport const EXTENSIONS_UPDATED_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tools_updated_at_idx ON tools (updated_at)`;\nexport const EXTENSION_SHARES_RESOURCE_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_shares_resource_idx ON tool_shares (resource_id)`;\n\nexport const EXTENSION_HIDES_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_hidden_extensions (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_HIDES_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_hidden_extensions (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const EXTENSION_HIDES_UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_hidden_extensions_user_tool_idx\n ON tool_hidden_extensions (owner_email, tool_id)`;\n\nexport const EXTENSION_HIDES_OWNER_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_hidden_extensions_owner_idx\n ON tool_hidden_extensions (owner_email)`;\n\n// ---------------------------------------------------------------------------\n// extension_consents — vestigial, kept for additive-schema compliance\n// ---------------------------------------------------------------------------\n//\n// Originally added for an audit-C1 per-(viewer, extension, content_hash)\n// consent gate that prompted viewers to \"Run anyway\" before non-author\n// extensions could execute. We removed the runtime gate after settling on\n// intra-org trust (extensions are shared between trusted teammates; the\n// org-level access controls are sufficient). The table is kept here so\n// deploys that already ran the migration stay healthy — additive-only schema\n// policy means we never drop. Physical name stays `tool_consents`.\n\nexport const extensionConsents = table(\"tool_consents\", {\n viewerEmail: text(\"viewer_email\").notNull(),\n extensionId: text(\"tool_id\").notNull(),\n contentHash: text(\"content_hash\").notNull(),\n grantedAt: text(\"granted_at\").notNull().default(now()),\n});\n\nexport const EXTENSION_CONSENTS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_consents (\n viewer_email TEXT NOT NULL,\n tool_id TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n granted_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (viewer_email, tool_id, content_hash)\n)`;\n\nexport const EXTENSION_CONSENTS_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_consents (\n viewer_email TEXT NOT NULL,\n tool_id TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n granted_at TEXT NOT NULL DEFAULT now(),\n PRIMARY KEY (viewer_email, tool_id, content_hash)\n)`;\n\nexport const EXTENSION_CONSENTS_VIEWER_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_consents_viewer_idx ON tool_consents (viewer_email, tool_id)`;\n"]}
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/extensions/schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Drizzle schema for the framework extensions system.\n *\n * Extensions are mini Alpine.js apps that run inside sandboxed iframes. They\n * can call external APIs via a server-side proxy that resolves `${keys.NAME}`\n * secret references. Extensions use the standard sharing model (private by\n * default, shareable with org/others).\n *\n * The tables are auto-created at server boot via `ensureTable()` in store.ts,\n * following the same pattern as `app_secrets`.\n *\n * NOTE: physical SQL table/column names stay as `tools`, `tool_data`,\n * `tool_shares`, `tool_consents`, `tool_id`, etc. — additive-only schema\n * policy means we never rename DB-level identifiers. The JS/TS surface is\n * renamed to `extensions`/`extension*`; the DB-side names stay so existing\n * deployed rows remain readable.\n */\n\nimport { table, text, integer, now } from \"../db/schema.js\";\nimport { ownableColumns, createSharesTable } from \"../sharing/schema.js\";\n\nexport const extensions = table(\"tools\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n description: text(\"description\").notNull().default(\"\"),\n content: text(\"content\").notNull().default(\"\"),\n icon: text(\"icon\"),\n createdAt: text(\"created_at\").notNull().default(now()),\n updatedAt: text(\"updated_at\").notNull().default(now()),\n ...ownableColumns(),\n});\n\nexport const extensionShares = createSharesTable(\"tool_shares\");\n\nexport const extensionHides = table(\"tool_hidden_extensions\", {\n id: text(\"id\").primaryKey(),\n extensionId: text(\"tool_id\").notNull(),\n ownerEmail: text(\"owner_email\").notNull(),\n createdAt: text(\"created_at\").notNull().default(now()),\n});\n\nexport const extensionHistory = table(\"tool_history\", {\n id: text(\"id\").primaryKey(),\n extensionId: text(\"tool_id\").notNull(),\n version: integer(\"version\").notNull(),\n operation: text(\"operation\").notNull(),\n summary: text(\"summary\").notNull().default(\"\"),\n name: text(\"name\").notNull(),\n description: text(\"description\").notNull().default(\"\"),\n content: text(\"content\").notNull().default(\"\"),\n icon: text(\"icon\"),\n actorEmail: text(\"actor_email\"),\n ownerEmail: text(\"owner_email\").notNull().default(\"local@localhost\"),\n orgId: text(\"org_id\"),\n visibility: text(\"visibility\").notNull().default(\"private\"),\n createdAt: text(\"created_at\").notNull().default(now()),\n});\n\nexport const EXTENSIONS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tools (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n description TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n icon TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n org_id TEXT,\n visibility TEXT NOT NULL DEFAULT 'private'\n)`;\n\nexport const EXTENSIONS_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tools (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n description TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n icon TEXT,\n created_at TEXT NOT NULL DEFAULT now(),\n updated_at TEXT NOT NULL DEFAULT now(),\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n org_id TEXT,\n visibility TEXT NOT NULL DEFAULT 'private'\n)`;\n\nexport const EXTENSION_SHARES_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_shares (\n id TEXT PRIMARY KEY,\n resource_id TEXT NOT NULL,\n principal_type TEXT NOT NULL,\n principal_id TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'viewer',\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_SHARES_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_shares (\n id TEXT PRIMARY KEY,\n resource_id TEXT NOT NULL,\n principal_type TEXT NOT NULL,\n principal_id TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'viewer',\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const extensionData = table(\"tool_data\", {\n id: text(\"id\").primaryKey(),\n extensionId: text(\"tool_id\").notNull(),\n collection: text(\"collection\").notNull(),\n itemId: text(\"item_id\"),\n data: text(\"data\").notNull(),\n ownerEmail: text(\"owner_email\").notNull().default(\"local@localhost\"),\n scope: text(\"scope\").notNull().default(\"user\"),\n orgId: text(\"org_id\"),\n scopeKey: text(\"scope_key\").notNull().default(\"local@localhost\"),\n createdAt: text(\"created_at\").notNull().default(now()),\n updatedAt: text(\"updated_at\").notNull().default(now()),\n});\n\nexport const EXTENSION_DATA_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_data (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n collection TEXT NOT NULL,\n item_id TEXT,\n data TEXT NOT NULL,\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n scope TEXT NOT NULL DEFAULT 'user',\n org_id TEXT,\n scope_key TEXT NOT NULL DEFAULT 'local@localhost',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_DATA_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_data (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n collection TEXT NOT NULL,\n item_id TEXT,\n data TEXT NOT NULL,\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n scope TEXT NOT NULL DEFAULT 'user',\n org_id TEXT,\n scope_key TEXT NOT NULL DEFAULT 'local@localhost',\n created_at TEXT NOT NULL DEFAULT now(),\n updated_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const EXTENSION_DATA_ITEM_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_data_scoped_item_idx\n ON tool_data (tool_id, collection, scope_key, item_id)`;\n\nexport const EXTENSION_DATA_ITEM_INDEX_SQL_PG = `CREATE UNIQUE INDEX IF NOT EXISTS tool_data_scoped_item_idx\n ON tool_data (tool_id, collection, scope_key, item_id)`;\n\nexport const EXTENSION_DATA_DROP_OLD_INDEX_SQL = `DROP INDEX IF EXISTS tool_data_scope_item_idx`;\nexport const EXTENSION_DATA_DROP_OLD_INDEX_SQL_PG = `DROP INDEX IF EXISTS tool_data_scope_item_idx`;\n\nexport const EXTENSIONS_OWNER_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tools_owner_idx ON tools (owner_email)`;\nexport const EXTENSIONS_ORG_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tools_org_idx ON tools (org_id)`;\nexport const EXTENSIONS_UPDATED_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tools_updated_at_idx ON tools (updated_at)`;\nexport const EXTENSION_SHARES_RESOURCE_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_shares_resource_idx ON tool_shares (resource_id)`;\n\nexport const EXTENSION_HIDES_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_hidden_extensions (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_HIDES_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_hidden_extensions (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const EXTENSION_HIDES_UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_hidden_extensions_user_tool_idx\n ON tool_hidden_extensions (owner_email, tool_id)`;\n\nexport const EXTENSION_HIDES_OWNER_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_hidden_extensions_owner_idx\n ON tool_hidden_extensions (owner_email)`;\n\nexport const EXTENSION_HISTORY_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_history (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n version INTEGER NOT NULL,\n operation TEXT NOT NULL,\n summary TEXT NOT NULL DEFAULT '',\n name TEXT NOT NULL,\n description TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n icon TEXT,\n actor_email TEXT,\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n org_id TEXT,\n visibility TEXT NOT NULL DEFAULT 'private',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_HISTORY_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_history (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n version INTEGER NOT NULL,\n operation TEXT NOT NULL,\n summary TEXT NOT NULL DEFAULT '',\n name TEXT NOT NULL,\n description TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n icon TEXT,\n actor_email TEXT,\n owner_email TEXT NOT NULL DEFAULT 'local@localhost',\n org_id TEXT,\n visibility TEXT NOT NULL DEFAULT 'private',\n created_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const EXTENSION_HISTORY_VERSION_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_history_tool_version_idx\n ON tool_history (tool_id, version)`;\n\nexport const EXTENSION_HISTORY_CREATED_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_history_tool_created_idx\n ON tool_history (tool_id, created_at)`;\n\n// ---------------------------------------------------------------------------\n// extension_consents — vestigial, kept for additive-schema compliance\n// ---------------------------------------------------------------------------\n//\n// Originally added for an audit-C1 per-(viewer, extension, content_hash)\n// consent gate that prompted viewers to \"Run anyway\" before non-author\n// extensions could execute. We removed the runtime gate after settling on\n// intra-org trust (extensions are shared between trusted teammates; the\n// org-level access controls are sufficient). The table is kept here so\n// deploys that already ran the migration stay healthy — additive-only schema\n// policy means we never drop. Physical name stays `tool_consents`.\n\nexport const extensionConsents = table(\"tool_consents\", {\n viewerEmail: text(\"viewer_email\").notNull(),\n extensionId: text(\"tool_id\").notNull(),\n contentHash: text(\"content_hash\").notNull(),\n grantedAt: text(\"granted_at\").notNull().default(now()),\n});\n\nexport const EXTENSION_CONSENTS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_consents (\n viewer_email TEXT NOT NULL,\n tool_id TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n granted_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (viewer_email, tool_id, content_hash)\n)`;\n\nexport const EXTENSION_CONSENTS_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_consents (\n viewer_email TEXT NOT NULL,\n tool_id TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n granted_at TEXT NOT NULL DEFAULT now(),\n PRIMARY KEY (viewer_email, tool_id, content_hash)\n)`;\n\nexport const EXTENSION_CONSENTS_VIEWER_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_consents_viewer_idx ON tool_consents (viewer_email, tool_id)`;\n"]}
|
|
@@ -14,6 +14,39 @@ export interface ExtensionRow {
|
|
|
14
14
|
orgId: string | null;
|
|
15
15
|
visibility: "private" | "org" | "public";
|
|
16
16
|
}
|
|
17
|
+
export type ExtensionHistoryOperation = "create" | "baseline" | "metadata-update" | "content-update" | "restore";
|
|
18
|
+
export interface ExtensionHistoryEntry {
|
|
19
|
+
id: string;
|
|
20
|
+
extensionId: string;
|
|
21
|
+
version: number;
|
|
22
|
+
operation: ExtensionHistoryOperation | string;
|
|
23
|
+
summary: string;
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
content?: string;
|
|
27
|
+
icon: string | null;
|
|
28
|
+
actorEmail: string | null;
|
|
29
|
+
ownerEmail: string;
|
|
30
|
+
orgId: string | null;
|
|
31
|
+
visibility: "private" | "org" | "public";
|
|
32
|
+
createdAt: string;
|
|
33
|
+
persisted: boolean;
|
|
34
|
+
contentLength: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ExtensionHistoryDiffLine {
|
|
37
|
+
type: "equal" | "insert" | "delete";
|
|
38
|
+
text: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ExtensionHistoryDetail {
|
|
41
|
+
entry: ExtensionHistoryEntry;
|
|
42
|
+
previous: ExtensionHistoryEntry | null;
|
|
43
|
+
diff: ExtensionHistoryDiffLine[];
|
|
44
|
+
stats: {
|
|
45
|
+
addedLines: number;
|
|
46
|
+
deletedLines: number;
|
|
47
|
+
changed: boolean;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
17
50
|
export declare function getExtensionChangeTargets(id: string): Promise<ExtensionChangeTarget[]>;
|
|
18
51
|
export declare function notifyExtensionChangeForResource(id: string, beforeTargets?: ExtensionChangeTarget[]): Promise<void>;
|
|
19
52
|
export interface ListExtensionsOptions {
|
|
@@ -21,6 +54,13 @@ export interface ListExtensionsOptions {
|
|
|
21
54
|
}
|
|
22
55
|
export declare function listExtensions(options?: ListExtensionsOptions): Promise<ExtensionRow[]>;
|
|
23
56
|
export declare function getExtension(id: string): Promise<ExtensionRow | null>;
|
|
57
|
+
export interface ListExtensionHistoryOptions {
|
|
58
|
+
limit?: number;
|
|
59
|
+
includeContent?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export declare function listExtensionHistory(id: string, options?: ListExtensionHistoryOptions): Promise<ExtensionHistoryEntry[]>;
|
|
62
|
+
export declare function getExtensionHistoryVersion(id: string, versionValue: number | string): Promise<ExtensionHistoryDetail | null>;
|
|
63
|
+
export declare function restoreExtensionHistoryVersion(id: string, versionValue: number | string): Promise<ExtensionRow | null>;
|
|
24
64
|
export interface CreateExtensionData {
|
|
25
65
|
name: string;
|
|
26
66
|
description?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/extensions/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/extensions/store.ts"],"names":[],"mappings":"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"}
|
package/dist/extensions/store.js
CHANGED
|
@@ -7,10 +7,15 @@ import { recordChange } from "../server/poll.js";
|
|
|
7
7
|
import { accessFilter, assertAccess, resolveAccess, ForbiddenError, } from "../sharing/access.js";
|
|
8
8
|
import { getRequestUserEmail, getRequestOrgId, } from "../server/request-context.js";
|
|
9
9
|
import { registerShareableResource } from "../sharing/registry.js";
|
|
10
|
-
import { extensions, extensionHides, extensionShares, EXTENSIONS_CREATE_SQL, EXTENSIONS_CREATE_SQL_PG, EXTENSION_SHARES_CREATE_SQL, EXTENSION_SHARES_CREATE_SQL_PG, EXTENSION_DATA_CREATE_SQL, EXTENSION_DATA_CREATE_SQL_PG, EXTENSION_DATA_ITEM_INDEX_SQL, EXTENSION_DATA_ITEM_INDEX_SQL_PG, EXTENSION_DATA_DROP_OLD_INDEX_SQL, EXTENSION_DATA_DROP_OLD_INDEX_SQL_PG, EXTENSIONS_OWNER_INDEX_SQL, EXTENSIONS_ORG_INDEX_SQL, EXTENSIONS_UPDATED_INDEX_SQL, EXTENSION_SHARES_RESOURCE_INDEX_SQL, EXTENSION_HIDES_CREATE_SQL, EXTENSION_HIDES_CREATE_SQL_PG, EXTENSION_HIDES_UNIQUE_INDEX_SQL, EXTENSION_HIDES_OWNER_INDEX_SQL, EXTENSION_CONSENTS_CREATE_SQL, EXTENSION_CONSENTS_CREATE_SQL_PG, EXTENSION_CONSENTS_VIEWER_INDEX_SQL, } from "./schema.js";
|
|
10
|
+
import { extensions, extensionHides, extensionShares, extensionHistory, EXTENSIONS_CREATE_SQL, EXTENSIONS_CREATE_SQL_PG, EXTENSION_SHARES_CREATE_SQL, EXTENSION_SHARES_CREATE_SQL_PG, EXTENSION_DATA_CREATE_SQL, EXTENSION_DATA_CREATE_SQL_PG, EXTENSION_DATA_ITEM_INDEX_SQL, EXTENSION_DATA_ITEM_INDEX_SQL_PG, EXTENSION_DATA_DROP_OLD_INDEX_SQL, EXTENSION_DATA_DROP_OLD_INDEX_SQL_PG, EXTENSIONS_OWNER_INDEX_SQL, EXTENSIONS_ORG_INDEX_SQL, EXTENSIONS_UPDATED_INDEX_SQL, EXTENSION_SHARES_RESOURCE_INDEX_SQL, EXTENSION_HIDES_CREATE_SQL, EXTENSION_HIDES_CREATE_SQL_PG, EXTENSION_HIDES_UNIQUE_INDEX_SQL, EXTENSION_HIDES_OWNER_INDEX_SQL, EXTENSION_HISTORY_CREATE_SQL, EXTENSION_HISTORY_CREATE_SQL_PG, EXTENSION_HISTORY_VERSION_INDEX_SQL, EXTENSION_HISTORY_CREATED_INDEX_SQL, EXTENSION_CONSENTS_CREATE_SQL, EXTENSION_CONSENTS_CREATE_SQL_PG, EXTENSION_CONSENTS_VIEWER_INDEX_SQL, } from "./schema.js";
|
|
11
11
|
import { EXTENSION_CHANGE_MARKER_KEY, extensionChangeMarkerSession, extensionChangeMarkerValue, } from "./change-marker.js";
|
|
12
12
|
import { applyExtensionContentUpdate, } from "./content-patch.js";
|
|
13
|
-
const getDb = createGetDb({
|
|
13
|
+
const getDb = createGetDb({
|
|
14
|
+
extensions,
|
|
15
|
+
extensionShares,
|
|
16
|
+
extensionHides,
|
|
17
|
+
extensionHistory,
|
|
18
|
+
});
|
|
14
19
|
let _initPromise;
|
|
15
20
|
export async function ensureExtensionsTables() {
|
|
16
21
|
if (!_initPromise) {
|
|
@@ -34,6 +39,9 @@ export async function ensureExtensionsTables() {
|
|
|
34
39
|
await retryOnDdlRace(() => client.execute(pg ? EXTENSION_HIDES_CREATE_SQL_PG : EXTENSION_HIDES_CREATE_SQL));
|
|
35
40
|
await retryOnDdlRace(() => client.execute(EXTENSION_HIDES_UNIQUE_INDEX_SQL));
|
|
36
41
|
await retryOnDdlRace(() => client.execute(EXTENSION_HIDES_OWNER_INDEX_SQL));
|
|
42
|
+
await retryOnDdlRace(() => client.execute(pg ? EXTENSION_HISTORY_CREATE_SQL_PG : EXTENSION_HISTORY_CREATE_SQL));
|
|
43
|
+
await retryOnDdlRace(() => client.execute(EXTENSION_HISTORY_VERSION_INDEX_SQL));
|
|
44
|
+
await retryOnDdlRace(() => client.execute(EXTENSION_HISTORY_CREATED_INDEX_SQL));
|
|
37
45
|
// tool_consents was introduced for an audit-C1 per-viewer consent
|
|
38
46
|
// gate that we removed once we settled on intra-org trust as the
|
|
39
47
|
// baseline. The table is kept (additive — never drop) so deploys
|
|
@@ -214,6 +222,256 @@ export async function notifyExtensionChangeForResource(id, beforeTargets = []) {
|
|
|
214
222
|
...(await extensionChangeTargetsForId(id)),
|
|
215
223
|
]);
|
|
216
224
|
}
|
|
225
|
+
function extensionHistoryEntryFromRaw(row, includeContent) {
|
|
226
|
+
const content = row.content ?? "";
|
|
227
|
+
const visibility = normalizeVisibility(row.visibility);
|
|
228
|
+
return {
|
|
229
|
+
id: row.id,
|
|
230
|
+
extensionId: String(row.tool_id ?? row.extensionId ?? ""),
|
|
231
|
+
version: Number(row.version) || 1,
|
|
232
|
+
operation: row.operation,
|
|
233
|
+
summary: row.summary ?? "",
|
|
234
|
+
name: row.name,
|
|
235
|
+
description: row.description ?? "",
|
|
236
|
+
...(includeContent ? { content } : {}),
|
|
237
|
+
icon: row.icon ?? null,
|
|
238
|
+
actorEmail: row.actor_email ?? row.actorEmail ?? null,
|
|
239
|
+
ownerEmail: row.owner_email ?? row.ownerEmail ?? "",
|
|
240
|
+
orgId: row.org_id ?? row.orgId ?? null,
|
|
241
|
+
visibility,
|
|
242
|
+
createdAt: row.created_at ?? row.createdAt ?? new Date(0).toISOString(),
|
|
243
|
+
persisted: true,
|
|
244
|
+
contentLength: content.length,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function extensionHistoryEntryFromExtension(row, includeContent) {
|
|
248
|
+
return {
|
|
249
|
+
id: `current:${row.id}`,
|
|
250
|
+
extensionId: row.id,
|
|
251
|
+
version: 1,
|
|
252
|
+
operation: "baseline",
|
|
253
|
+
summary: "Current version",
|
|
254
|
+
name: row.name,
|
|
255
|
+
description: row.description,
|
|
256
|
+
...(includeContent ? { content: row.content } : {}),
|
|
257
|
+
icon: row.icon,
|
|
258
|
+
actorEmail: null,
|
|
259
|
+
ownerEmail: row.ownerEmail,
|
|
260
|
+
orgId: row.orgId,
|
|
261
|
+
visibility: row.visibility,
|
|
262
|
+
createdAt: row.updatedAt,
|
|
263
|
+
persisted: false,
|
|
264
|
+
contentLength: row.content.length,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function normalizeVisibility(value) {
|
|
268
|
+
return value === "org" || value === "public" ? value : "private";
|
|
269
|
+
}
|
|
270
|
+
function currentActorEmail() {
|
|
271
|
+
return getRequestUserEmail() ?? null;
|
|
272
|
+
}
|
|
273
|
+
function clampHistoryLimit(value) {
|
|
274
|
+
const limit = Number(value ?? 50);
|
|
275
|
+
if (!Number.isFinite(limit))
|
|
276
|
+
return 50;
|
|
277
|
+
return Math.min(Math.max(1, Math.floor(limit)), 100);
|
|
278
|
+
}
|
|
279
|
+
async function historyVersionCount(extensionId) {
|
|
280
|
+
const result = await getDbExec().execute({
|
|
281
|
+
sql: `SELECT MAX(version) AS version FROM tool_history WHERE tool_id = ?`,
|
|
282
|
+
args: [extensionId],
|
|
283
|
+
});
|
|
284
|
+
const value = result.rows?.[0]?.version;
|
|
285
|
+
const version = Number(value ?? 0);
|
|
286
|
+
return Number.isFinite(version) ? version : 0;
|
|
287
|
+
}
|
|
288
|
+
async function hasExtensionHistory(extensionId) {
|
|
289
|
+
const result = await getDbExec().execute({
|
|
290
|
+
sql: `SELECT id FROM tool_history WHERE tool_id = ? LIMIT 1`,
|
|
291
|
+
args: [extensionId],
|
|
292
|
+
});
|
|
293
|
+
return (result.rows?.length ?? 0) > 0;
|
|
294
|
+
}
|
|
295
|
+
async function recordExtensionHistorySnapshot(row, operation, summary) {
|
|
296
|
+
const version = (await historyVersionCount(row.id)) + 1;
|
|
297
|
+
const now = new Date().toISOString();
|
|
298
|
+
const historyId = randomUUID();
|
|
299
|
+
await getDbExec().execute({
|
|
300
|
+
sql: `INSERT INTO tool_history (
|
|
301
|
+
id, tool_id, version, operation, summary, name, description, content,
|
|
302
|
+
icon, actor_email, owner_email, org_id, visibility, created_at
|
|
303
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
304
|
+
args: [
|
|
305
|
+
historyId,
|
|
306
|
+
row.id,
|
|
307
|
+
version,
|
|
308
|
+
operation,
|
|
309
|
+
summary,
|
|
310
|
+
row.name,
|
|
311
|
+
row.description,
|
|
312
|
+
row.content,
|
|
313
|
+
row.icon,
|
|
314
|
+
currentActorEmail(),
|
|
315
|
+
row.ownerEmail,
|
|
316
|
+
row.orgId,
|
|
317
|
+
row.visibility,
|
|
318
|
+
now,
|
|
319
|
+
],
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
id: historyId,
|
|
323
|
+
extensionId: row.id,
|
|
324
|
+
version,
|
|
325
|
+
operation,
|
|
326
|
+
summary,
|
|
327
|
+
name: row.name,
|
|
328
|
+
description: row.description,
|
|
329
|
+
content: row.content,
|
|
330
|
+
icon: row.icon,
|
|
331
|
+
actorEmail: currentActorEmail(),
|
|
332
|
+
ownerEmail: row.ownerEmail,
|
|
333
|
+
orgId: row.orgId,
|
|
334
|
+
visibility: row.visibility,
|
|
335
|
+
createdAt: now,
|
|
336
|
+
persisted: true,
|
|
337
|
+
contentLength: row.content.length,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async function ensureExtensionHistoryBaseline(row) {
|
|
341
|
+
if (await hasExtensionHistory(row.id))
|
|
342
|
+
return;
|
|
343
|
+
await recordExtensionHistorySnapshot(row, "baseline", "Saved starting version");
|
|
344
|
+
}
|
|
345
|
+
function summarizeMetadataChange(before, after) {
|
|
346
|
+
const changes = [];
|
|
347
|
+
if (before.name !== after.name) {
|
|
348
|
+
changes.push(`Renamed from "${before.name}" to "${after.name}"`);
|
|
349
|
+
}
|
|
350
|
+
if (before.description !== after.description) {
|
|
351
|
+
changes.push("Updated description");
|
|
352
|
+
}
|
|
353
|
+
if (before.icon !== after.icon) {
|
|
354
|
+
changes.push("Updated icon");
|
|
355
|
+
}
|
|
356
|
+
if (before.visibility !== after.visibility) {
|
|
357
|
+
changes.push(`Changed visibility to ${after.visibility}`);
|
|
358
|
+
}
|
|
359
|
+
return changes.join("; ") || "Updated details";
|
|
360
|
+
}
|
|
361
|
+
function summarizeContentChange(beforeContent, afterContent) {
|
|
362
|
+
const stats = diffStats(createLineDiff(beforeContent, afterContent));
|
|
363
|
+
if (!stats.changed)
|
|
364
|
+
return "Saved content";
|
|
365
|
+
return `Updated content (+${stats.addedLines} -${stats.deletedLines} lines)`;
|
|
366
|
+
}
|
|
367
|
+
function parseHistoryVersion(version) {
|
|
368
|
+
const parsed = Number(version);
|
|
369
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
370
|
+
return null;
|
|
371
|
+
return parsed;
|
|
372
|
+
}
|
|
373
|
+
async function getPersistedHistoryEntry(extensionId, version, includeContent) {
|
|
374
|
+
const result = await getDbExec().execute({
|
|
375
|
+
sql: `SELECT id, tool_id, version, operation, summary, name, description,
|
|
376
|
+
content, icon, actor_email, owner_email, org_id, visibility, created_at
|
|
377
|
+
FROM tool_history
|
|
378
|
+
WHERE tool_id = ? AND version = ?
|
|
379
|
+
LIMIT 1`,
|
|
380
|
+
args: [extensionId, version],
|
|
381
|
+
});
|
|
382
|
+
const row = result.rows?.[0];
|
|
383
|
+
return row ? extensionHistoryEntryFromRaw(row, includeContent) : null;
|
|
384
|
+
}
|
|
385
|
+
function splitLines(text) {
|
|
386
|
+
if (!text)
|
|
387
|
+
return [];
|
|
388
|
+
const parts = text.split("\n");
|
|
389
|
+
return parts
|
|
390
|
+
.map((line, index) => (index < parts.length - 1 ? `${line}\n` : line))
|
|
391
|
+
.filter((line) => line.length > 0);
|
|
392
|
+
}
|
|
393
|
+
function createLineDiff(beforeText, afterText) {
|
|
394
|
+
const before = splitLines(beforeText);
|
|
395
|
+
const after = splitLines(afterText);
|
|
396
|
+
if (before.length === 0 && after.length === 0)
|
|
397
|
+
return [];
|
|
398
|
+
const cells = before.length * after.length;
|
|
399
|
+
if (cells > 40_000)
|
|
400
|
+
return createBoundaryDiff(before, after);
|
|
401
|
+
const dp = Array.from({ length: before.length + 1 }, () => Array(after.length + 1).fill(0));
|
|
402
|
+
for (let i = before.length - 1; i >= 0; i -= 1) {
|
|
403
|
+
for (let j = after.length - 1; j >= 0; j -= 1) {
|
|
404
|
+
dp[i][j] =
|
|
405
|
+
before[i] === after[j]
|
|
406
|
+
? dp[i + 1][j + 1] + 1
|
|
407
|
+
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const diff = [];
|
|
411
|
+
let i = 0;
|
|
412
|
+
let j = 0;
|
|
413
|
+
while (i < before.length && j < after.length) {
|
|
414
|
+
if (before[i] === after[j]) {
|
|
415
|
+
diff.push({ type: "equal", text: before[i] });
|
|
416
|
+
i += 1;
|
|
417
|
+
j += 1;
|
|
418
|
+
}
|
|
419
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
420
|
+
diff.push({ type: "delete", text: before[i] });
|
|
421
|
+
i += 1;
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
diff.push({ type: "insert", text: after[j] });
|
|
425
|
+
j += 1;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
while (i < before.length) {
|
|
429
|
+
diff.push({ type: "delete", text: before[i] });
|
|
430
|
+
i += 1;
|
|
431
|
+
}
|
|
432
|
+
while (j < after.length) {
|
|
433
|
+
diff.push({ type: "insert", text: after[j] });
|
|
434
|
+
j += 1;
|
|
435
|
+
}
|
|
436
|
+
return diff;
|
|
437
|
+
}
|
|
438
|
+
function createBoundaryDiff(before, after) {
|
|
439
|
+
let prefix = 0;
|
|
440
|
+
while (prefix < before.length &&
|
|
441
|
+
prefix < after.length &&
|
|
442
|
+
before[prefix] === after[prefix]) {
|
|
443
|
+
prefix += 1;
|
|
444
|
+
}
|
|
445
|
+
let suffix = 0;
|
|
446
|
+
while (suffix + prefix < before.length &&
|
|
447
|
+
suffix + prefix < after.length &&
|
|
448
|
+
before[before.length - 1 - suffix] === after[after.length - 1 - suffix]) {
|
|
449
|
+
suffix += 1;
|
|
450
|
+
}
|
|
451
|
+
return [
|
|
452
|
+
...before
|
|
453
|
+
.slice(0, prefix)
|
|
454
|
+
.map((text) => ({ type: "equal", text })),
|
|
455
|
+
...before
|
|
456
|
+
.slice(prefix, before.length - suffix)
|
|
457
|
+
.map((text) => ({ type: "delete", text })),
|
|
458
|
+
...after
|
|
459
|
+
.slice(prefix, after.length - suffix)
|
|
460
|
+
.map((text) => ({ type: "insert", text })),
|
|
461
|
+
...before
|
|
462
|
+
.slice(before.length - suffix)
|
|
463
|
+
.map((text) => ({ type: "equal", text })),
|
|
464
|
+
];
|
|
465
|
+
}
|
|
466
|
+
function diffStats(diff) {
|
|
467
|
+
const addedLines = diff.filter((line) => line.type === "insert").length;
|
|
468
|
+
const deletedLines = diff.filter((line) => line.type === "delete").length;
|
|
469
|
+
return {
|
|
470
|
+
addedLines,
|
|
471
|
+
deletedLines,
|
|
472
|
+
changed: addedLines > 0 || deletedLines > 0,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
217
475
|
export async function listExtensions(options = {}) {
|
|
218
476
|
await ensureExtensionsTables();
|
|
219
477
|
const db = getDb();
|
|
@@ -233,6 +491,95 @@ export async function getExtension(id) {
|
|
|
233
491
|
const access = await resolveAccess("extension", id);
|
|
234
492
|
return access?.resource ?? null;
|
|
235
493
|
}
|
|
494
|
+
export async function listExtensionHistory(id, options = {}) {
|
|
495
|
+
await ensureExtensionsTables();
|
|
496
|
+
const extension = await getExtension(id);
|
|
497
|
+
if (!extension)
|
|
498
|
+
return [];
|
|
499
|
+
const includeContent = options.includeContent === true;
|
|
500
|
+
const limit = clampHistoryLimit(options.limit);
|
|
501
|
+
const result = await getDbExec().execute({
|
|
502
|
+
sql: `SELECT id, tool_id, version, operation, summary, name, description,
|
|
503
|
+
content, icon, actor_email, owner_email, org_id, visibility, created_at
|
|
504
|
+
FROM tool_history
|
|
505
|
+
WHERE tool_id = ?
|
|
506
|
+
ORDER BY version DESC
|
|
507
|
+
LIMIT ?`,
|
|
508
|
+
args: [id, limit],
|
|
509
|
+
});
|
|
510
|
+
const entries = (result.rows ?? []).map((row) => extensionHistoryEntryFromRaw(row, includeContent));
|
|
511
|
+
if (entries.length > 0)
|
|
512
|
+
return entries;
|
|
513
|
+
return [extensionHistoryEntryFromExtension(extension, includeContent)];
|
|
514
|
+
}
|
|
515
|
+
export async function getExtensionHistoryVersion(id, versionValue) {
|
|
516
|
+
await ensureExtensionsTables();
|
|
517
|
+
const extension = await getExtension(id);
|
|
518
|
+
if (!extension)
|
|
519
|
+
return null;
|
|
520
|
+
const version = parseHistoryVersion(versionValue);
|
|
521
|
+
if (!version)
|
|
522
|
+
return null;
|
|
523
|
+
const persisted = await getPersistedHistoryEntry(id, version, true);
|
|
524
|
+
const entry = persisted ??
|
|
525
|
+
(!(await hasExtensionHistory(id)) && version === 1
|
|
526
|
+
? extensionHistoryEntryFromExtension(extension, true)
|
|
527
|
+
: null);
|
|
528
|
+
if (!entry)
|
|
529
|
+
return null;
|
|
530
|
+
const previous = version > 1 ? await getPersistedHistoryEntry(id, version - 1, true) : null;
|
|
531
|
+
const previousContent = previous?.content ?? "";
|
|
532
|
+
const currentContent = entry.content ?? "";
|
|
533
|
+
const diff = createLineDiff(previousContent, currentContent);
|
|
534
|
+
return {
|
|
535
|
+
entry,
|
|
536
|
+
previous,
|
|
537
|
+
diff,
|
|
538
|
+
stats: diffStats(diff),
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
export async function restoreExtensionHistoryVersion(id, versionValue) {
|
|
542
|
+
await ensureExtensionsTables();
|
|
543
|
+
await assertAccess("extension", id, "editor");
|
|
544
|
+
const version = parseHistoryVersion(versionValue);
|
|
545
|
+
if (!version)
|
|
546
|
+
return null;
|
|
547
|
+
const existingRows = await getDb()
|
|
548
|
+
.select()
|
|
549
|
+
.from(extensions)
|
|
550
|
+
.where(eq(extensions.id, id));
|
|
551
|
+
const existing = existingRows[0];
|
|
552
|
+
if (!existing)
|
|
553
|
+
return null;
|
|
554
|
+
await ensureExtensionHistoryBaseline(existing);
|
|
555
|
+
const target = await getPersistedHistoryEntry(id, version, true);
|
|
556
|
+
if (!target)
|
|
557
|
+
return null;
|
|
558
|
+
const beforeTargets = await extensionChangeTargetsForId(id);
|
|
559
|
+
await getDb()
|
|
560
|
+
.update(extensions)
|
|
561
|
+
.set({
|
|
562
|
+
name: target.name,
|
|
563
|
+
description: target.description,
|
|
564
|
+
content: target.content ?? "",
|
|
565
|
+
icon: target.icon,
|
|
566
|
+
updatedAt: new Date().toISOString(),
|
|
567
|
+
})
|
|
568
|
+
.where(eq(extensions.id, id));
|
|
569
|
+
const rows = await getDb()
|
|
570
|
+
.select()
|
|
571
|
+
.from(extensions)
|
|
572
|
+
.where(eq(extensions.id, id));
|
|
573
|
+
const row = rows[0] ?? null;
|
|
574
|
+
if (row) {
|
|
575
|
+
await recordExtensionHistorySnapshot(row, "restore", `Restored version ${version}`);
|
|
576
|
+
await notifyExtensionChanged([
|
|
577
|
+
...beforeTargets,
|
|
578
|
+
...(await extensionChangeTargetsForRow(row)),
|
|
579
|
+
]);
|
|
580
|
+
}
|
|
581
|
+
return row;
|
|
582
|
+
}
|
|
236
583
|
export async function createExtension(data) {
|
|
237
584
|
await ensureExtensionsTables();
|
|
238
585
|
const db = getDb();
|
|
@@ -255,6 +602,7 @@ export async function createExtension(data) {
|
|
|
255
602
|
visibility: "private",
|
|
256
603
|
};
|
|
257
604
|
await db.insert(extensions).values(row);
|
|
605
|
+
await recordExtensionHistorySnapshot(row, "create", "Created extension");
|
|
258
606
|
await notifyExtensionChanged([{ owner: row.ownerEmail }]);
|
|
259
607
|
return row;
|
|
260
608
|
}
|
|
@@ -281,10 +629,19 @@ export async function updateExtension(id, data) {
|
|
|
281
629
|
updates.icon = data.icon;
|
|
282
630
|
if (data.visibility !== undefined)
|
|
283
631
|
updates.visibility = data.visibility;
|
|
632
|
+
const existingRows = await db
|
|
633
|
+
.select()
|
|
634
|
+
.from(extensions)
|
|
635
|
+
.where(eq(extensions.id, id));
|
|
636
|
+
const existing = existingRows[0];
|
|
637
|
+
if (!existing)
|
|
638
|
+
return null;
|
|
639
|
+
await ensureExtensionHistoryBaseline(existing);
|
|
284
640
|
await db.update(extensions).set(updates).where(eq(extensions.id, id));
|
|
285
641
|
const rows = await db.select().from(extensions).where(eq(extensions.id, id));
|
|
286
642
|
const row = rows[0] ?? null;
|
|
287
643
|
if (row) {
|
|
644
|
+
await recordExtensionHistorySnapshot(row, "metadata-update", summarizeMetadataChange(existing, row));
|
|
288
645
|
await notifyExtensionChanged([
|
|
289
646
|
...beforeTargets,
|
|
290
647
|
...(await extensionChangeTargetsForRow(row)),
|
|
@@ -308,7 +665,9 @@ export async function updateExtensionContent(id, opts) {
|
|
|
308
665
|
.where(eq(extensions.id, id));
|
|
309
666
|
if (!existingRows[0])
|
|
310
667
|
return null;
|
|
311
|
-
const
|
|
668
|
+
const existing = existingRows[0];
|
|
669
|
+
const existingContent = existing.content;
|
|
670
|
+
await ensureExtensionHistoryBaseline(existing);
|
|
312
671
|
const update = await applyExtensionContentUpdate(existingContent, opts);
|
|
313
672
|
const beforeTargets = await extensionChangeTargetsForId(id);
|
|
314
673
|
await db
|
|
@@ -318,6 +677,7 @@ export async function updateExtensionContent(id, opts) {
|
|
|
318
677
|
const rows = await db.select().from(extensions).where(eq(extensions.id, id));
|
|
319
678
|
const row = rows[0] ?? null;
|
|
320
679
|
if (row) {
|
|
680
|
+
await recordExtensionHistorySnapshot(row, "content-update", summarizeContentChange(existingContent, row.content));
|
|
321
681
|
await notifyExtensionChanged([
|
|
322
682
|
...beforeTargets,
|
|
323
683
|
...(await extensionChangeTargetsForRow(row)),
|
|
@@ -340,6 +700,10 @@ export async function deleteExtension(id) {
|
|
|
340
700
|
sql: `DELETE FROM tool_data WHERE tool_id = ?`,
|
|
341
701
|
args: [id],
|
|
342
702
|
});
|
|
703
|
+
await getDbExec().execute({
|
|
704
|
+
sql: `DELETE FROM tool_history WHERE tool_id = ?`,
|
|
705
|
+
args: [id],
|
|
706
|
+
});
|
|
343
707
|
const { cascadeDeleteExtensionSlots } = await import("./slots/store.js");
|
|
344
708
|
await cascadeDeleteExtensionSlots(id);
|
|
345
709
|
await db.delete(extensions).where(eq(extensions.id, id));
|