@agent-native/core 0.23.0 → 0.24.1

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.
Files changed (58) hide show
  1. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  2. package/dist/agent/engine/builder-engine.js +5 -6
  3. package/dist/agent/engine/builder-engine.js.map +1 -1
  4. package/dist/agent/run-manager.d.ts +9 -2
  5. package/dist/agent/run-manager.d.ts.map +1 -1
  6. package/dist/agent/run-manager.js +9 -2
  7. package/dist/agent/run-manager.js.map +1 -1
  8. package/dist/cli/workspace-dev.d.ts.map +1 -1
  9. package/dist/cli/workspace-dev.js +101 -9
  10. package/dist/cli/workspace-dev.js.map +1 -1
  11. package/dist/client/AssistantChat.d.ts +4 -0
  12. package/dist/client/AssistantChat.d.ts.map +1 -1
  13. package/dist/client/AssistantChat.js +51 -15
  14. package/dist/client/AssistantChat.js.map +1 -1
  15. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  16. package/dist/client/MultiTabAssistantChat.js +2 -1
  17. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  18. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  19. package/dist/client/agent-chat-adapter.js +70 -10
  20. package/dist/client/agent-chat-adapter.js.map +1 -1
  21. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  22. package/dist/client/extensions/ExtensionViewer.js +157 -2
  23. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  24. package/dist/client/sse-event-processor.d.ts +6 -0
  25. package/dist/client/sse-event-processor.d.ts.map +1 -1
  26. package/dist/client/sse-event-processor.js +9 -2
  27. package/dist/client/sse-event-processor.js.map +1 -1
  28. package/dist/client/use-chat-threads.d.ts +3 -0
  29. package/dist/client/use-chat-threads.d.ts.map +1 -1
  30. package/dist/client/use-chat-threads.js +23 -4
  31. package/dist/client/use-chat-threads.js.map +1 -1
  32. package/dist/client/use-chat-threads.spec.js +59 -0
  33. package/dist/client/use-chat-threads.spec.js.map +1 -1
  34. package/dist/extensions/actions.d.ts.map +1 -1
  35. package/dist/extensions/actions.js +112 -2
  36. package/dist/extensions/actions.js.map +1 -1
  37. package/dist/extensions/routes.d.ts.map +1 -1
  38. package/dist/extensions/routes.js +37 -2
  39. package/dist/extensions/routes.js.map +1 -1
  40. package/dist/extensions/schema.d.ts +275 -0
  41. package/dist/extensions/schema.d.ts.map +1 -1
  42. package/dist/extensions/schema.js +53 -1
  43. package/dist/extensions/schema.js.map +1 -1
  44. package/dist/extensions/store.d.ts +40 -0
  45. package/dist/extensions/store.d.ts.map +1 -1
  46. package/dist/extensions/store.js +367 -3
  47. package/dist/extensions/store.js.map +1 -1
  48. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  49. package/dist/server/agent-chat-plugin.js +4 -0
  50. package/dist/server/agent-chat-plugin.js.map +1 -1
  51. package/dist/server/auth.d.ts.map +1 -1
  52. package/dist/server/auth.js +6 -2
  53. package/dist/server/auth.js.map +1 -1
  54. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  55. package/dist/server/core-routes-plugin.js +9 -13
  56. package/dist/server/core-routes-plugin.js.map +1 -1
  57. package/docs/content/extensions.md +5 -0
  58. 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":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzE,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,EAAE;IACvC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACtD,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC9C,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACtD,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACtD,GAAG,cAAc,EAAE;CACpB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;AAEhE,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC,wBAAwB,EAAE;IAC5D,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IACtC,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE;IACzC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;CACvD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;EAWnC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;EAWtC,CAAC;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG;;;;;;;;EAQzC,CAAC;AAEH,MAAM,CAAC,MAAM,8BAA8B,GAAG;;;;;;;;EAQ5C,CAAC;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC,WAAW,EAAE;IAC9C,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IACtC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IACxC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;IACvB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC;IACpE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAC9C,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC;IACrB,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC;IAChE,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACtD,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;CACvD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;EAYvC,CAAC;AAEH,MAAM,CAAC,MAAM,4BAA4B,GAAG;;;;;;;;;;;;EAY1C,CAAC;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG;yDACY,CAAC;AAE1D,MAAM,CAAC,MAAM,gCAAgC,GAAG;yDACS,CAAC;AAE1D,MAAM,CAAC,MAAM,iCAAiC,GAAG,+CAA+C,CAAC;AACjG,MAAM,CAAC,MAAM,oCAAoC,GAAG,+CAA+C,CAAC;AAEpG,MAAM,CAAC,MAAM,0BAA0B,GAAG,mEAAmE,CAAC;AAC9G,MAAM,CAAC,MAAM,wBAAwB,GAAG,4DAA4D,CAAC;AACrG,MAAM,CAAC,MAAM,4BAA4B,GAAG,uEAAuE,CAAC;AACpH,MAAM,CAAC,MAAM,mCAAmC,GAAG,kFAAkF,CAAC;AAEtI,MAAM,CAAC,MAAM,0BAA0B,GAAG;;;;;EAKxC,CAAC;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG;;;;;EAK3C,CAAC;AAEH,MAAM,CAAC,MAAM,gCAAgC,GAAG;mDACG,CAAC;AAEpD,MAAM,CAAC,MAAM,+BAA+B,GAAG;0CACL,CAAC;AAE3C,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAC9E,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,0EAA0E;AAC1E,wEAAwE;AACxE,uEAAuE;AACvE,6EAA6E;AAC7E,mEAAmE;AAEnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC,eAAe,EAAE;IACtD,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE;IAC3C,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IACtC,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE;IAC3C,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;CACvD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG;;;;;;EAM3C,CAAC;AAEH,MAAM,CAAC,MAAM,gCAAgC,GAAG;;;;;;EAM9C,CAAC;AAEH,MAAM,CAAC,MAAM,mCAAmC,GAAG,8FAA8F,CAAC","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":"AA2CA,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAC1B,MAAM,oBAAoB,CAAC;AAM5B,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAsE5D;AAwFD,wBAAgB,2BAA2B,SAiB1C;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;CAC1C;AAyDD,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAGlC;AA0CD,wBAAsB,gCAAgC,CACpD,EAAE,EAAE,MAAM,EACV,aAAa,GAAE,qBAAqB,EAAO,GAC1C,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,cAAc,CAClC,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,YAAY,EAAE,CAAC,CAazB;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAI3E;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,YAAY,CAAC,CAuBvB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;CAC3C;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA+B9B;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAoC9B;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBlE;AAED,wBAAsB,mCAAmC,IAAI,OAAO,CAClE,GAAG,CAAC,MAAM,CAAC,CACZ,CAWA;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAehE;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWlE"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/extensions/store.ts"],"names":[],"mappings":"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"}
@@ -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({ extensions, extensionShares, extensionHides });
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 existingContent = existingRows[0].content;
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));