@agent-native/core 0.22.45 → 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.
Files changed (138) hide show
  1. package/dist/a2a/artifact-response.js +1 -1
  2. package/dist/a2a/artifact-response.js.map +1 -1
  3. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  4. package/dist/agent/engine/builder-engine.js +5 -6
  5. package/dist/agent/engine/builder-engine.js.map +1 -1
  6. package/dist/agent/production-agent.d.ts.map +1 -1
  7. package/dist/agent/production-agent.js +12 -4
  8. package/dist/agent/production-agent.js.map +1 -1
  9. package/dist/agent/run-manager.d.ts +9 -2
  10. package/dist/agent/run-manager.d.ts.map +1 -1
  11. package/dist/agent/run-manager.js +9 -2
  12. package/dist/agent/run-manager.js.map +1 -1
  13. package/dist/cli/app-skill.d.ts +139 -0
  14. package/dist/cli/app-skill.d.ts.map +1 -0
  15. package/dist/cli/app-skill.js +960 -0
  16. package/dist/cli/app-skill.js.map +1 -0
  17. package/dist/cli/create.d.ts.map +1 -1
  18. package/dist/cli/create.js +13 -4
  19. package/dist/cli/create.js.map +1 -1
  20. package/dist/cli/index.js +24 -0
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/skills.d.ts +39 -0
  23. package/dist/cli/skills.d.ts.map +1 -0
  24. package/dist/cli/skills.js +363 -0
  25. package/dist/cli/skills.js.map +1 -0
  26. package/dist/cli/templates-meta.d.ts.map +1 -1
  27. package/dist/cli/templates-meta.js +9 -6
  28. package/dist/cli/templates-meta.js.map +1 -1
  29. package/dist/cli/workspace-dev.d.ts.map +1 -1
  30. package/dist/cli/workspace-dev.js +103 -9
  31. package/dist/cli/workspace-dev.js.map +1 -1
  32. package/dist/client/AgentPanel.d.ts +2 -0
  33. package/dist/client/AgentPanel.d.ts.map +1 -1
  34. package/dist/client/AgentPanel.js +2 -2
  35. package/dist/client/AgentPanel.js.map +1 -1
  36. package/dist/client/AssistantChat.d.ts +13 -0
  37. package/dist/client/AssistantChat.d.ts.map +1 -1
  38. package/dist/client/AssistantChat.js +66 -22
  39. package/dist/client/AssistantChat.js.map +1 -1
  40. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  41. package/dist/client/MultiTabAssistantChat.js +17 -1
  42. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  43. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  44. package/dist/client/agent-chat-adapter.js +70 -10
  45. package/dist/client/agent-chat-adapter.js.map +1 -1
  46. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  47. package/dist/client/extensions/ExtensionViewer.js +157 -2
  48. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  49. package/dist/client/index.d.ts +1 -1
  50. package/dist/client/index.d.ts.map +1 -1
  51. package/dist/client/index.js.map +1 -1
  52. package/dist/client/sse-event-processor.d.ts +6 -0
  53. package/dist/client/sse-event-processor.d.ts.map +1 -1
  54. package/dist/client/sse-event-processor.js +9 -2
  55. package/dist/client/sse-event-processor.js.map +1 -1
  56. package/dist/client/use-chat-threads.d.ts +8 -1
  57. package/dist/client/use-chat-threads.d.ts.map +1 -1
  58. package/dist/client/use-chat-threads.js +37 -7
  59. package/dist/client/use-chat-threads.js.map +1 -1
  60. package/dist/client/use-chat-threads.spec.js +59 -0
  61. package/dist/client/use-chat-threads.spec.js.map +1 -1
  62. package/dist/deploy/workspace-deploy.js +6 -0
  63. package/dist/deploy/workspace-deploy.js.map +1 -1
  64. package/dist/extensions/actions.d.ts.map +1 -1
  65. package/dist/extensions/actions.js +112 -2
  66. package/dist/extensions/actions.js.map +1 -1
  67. package/dist/extensions/routes.d.ts.map +1 -1
  68. package/dist/extensions/routes.js +37 -2
  69. package/dist/extensions/routes.js.map +1 -1
  70. package/dist/extensions/schema.d.ts +275 -0
  71. package/dist/extensions/schema.d.ts.map +1 -1
  72. package/dist/extensions/schema.js +53 -1
  73. package/dist/extensions/schema.js.map +1 -1
  74. package/dist/extensions/store.d.ts +40 -0
  75. package/dist/extensions/store.d.ts.map +1 -1
  76. package/dist/extensions/store.js +367 -3
  77. package/dist/extensions/store.js.map +1 -1
  78. package/dist/mcp-client/index.d.ts +1 -1
  79. package/dist/mcp-client/index.d.ts.map +1 -1
  80. package/dist/mcp-client/index.js +1 -1
  81. package/dist/mcp-client/index.js.map +1 -1
  82. package/dist/mcp-client/routes.d.ts +1 -0
  83. package/dist/mcp-client/routes.d.ts.map +1 -1
  84. package/dist/mcp-client/routes.js +52 -0
  85. package/dist/mcp-client/routes.js.map +1 -1
  86. package/dist/mcp-client/workspace-servers.d.ts +15 -0
  87. package/dist/mcp-client/workspace-servers.d.ts.map +1 -0
  88. package/dist/mcp-client/workspace-servers.js +297 -0
  89. package/dist/mcp-client/workspace-servers.js.map +1 -0
  90. package/dist/resources/handlers.d.ts.map +1 -1
  91. package/dist/resources/handlers.js +38 -25
  92. package/dist/resources/handlers.js.map +1 -1
  93. package/dist/resources/store.d.ts +11 -3
  94. package/dist/resources/store.d.ts.map +1 -1
  95. package/dist/resources/store.js +220 -9
  96. package/dist/resources/store.js.map +1 -1
  97. package/dist/scripts/call-agent.js +1 -1
  98. package/dist/scripts/call-agent.js.map +1 -1
  99. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  100. package/dist/server/agent-chat-plugin.js +25 -6
  101. package/dist/server/agent-chat-plugin.js.map +1 -1
  102. package/dist/server/agent-discovery.d.ts.map +1 -1
  103. package/dist/server/agent-discovery.js +34 -9
  104. package/dist/server/agent-discovery.js.map +1 -1
  105. package/dist/server/auth-marketing.d.ts.map +1 -1
  106. package/dist/server/auth-marketing.js +8 -5
  107. package/dist/server/auth-marketing.js.map +1 -1
  108. package/dist/server/auth.d.ts.map +1 -1
  109. package/dist/server/auth.js +6 -2
  110. package/dist/server/auth.js.map +1 -1
  111. package/dist/templates/default/AGENTS.md +12 -4
  112. package/dist/templates/default/DEVELOPING.md +7 -5
  113. package/dist/templates/workspace-core/AGENTS.md +7 -0
  114. package/dist/templates/workspace-root/AGENTS.md +6 -0
  115. package/docs/content/creating-templates.md +14 -9
  116. package/docs/content/database.md +44 -17
  117. package/docs/content/deployment.md +15 -7
  118. package/docs/content/dispatch.md +7 -1
  119. package/docs/content/embedding-sdk.md +79 -0
  120. package/docs/content/extensions.md +5 -0
  121. package/docs/content/key-concepts.md +15 -17
  122. package/docs/content/mcp-clients.md +30 -0
  123. package/docs/content/multi-app-workspace.md +3 -2
  124. package/docs/content/multi-tenancy.md +4 -4
  125. package/docs/content/server.md +10 -7
  126. package/docs/content/skills-guide.md +75 -0
  127. package/docs/content/template-analytics.md +1 -1
  128. package/docs/content/template-assets.md +130 -0
  129. package/docs/content/template-dispatch.md +3 -2
  130. package/docs/content/template-slides.md +2 -2
  131. package/docs/content/workspace-management.md +2 -2
  132. package/docs/content/workspace.md +11 -9
  133. package/package.json +1 -1
  134. package/src/templates/default/AGENTS.md +12 -4
  135. package/src/templates/default/DEVELOPING.md +7 -5
  136. package/src/templates/workspace-core/AGENTS.md +7 -0
  137. package/src/templates/workspace-root/AGENTS.md +6 -0
  138. package/docs/content/template-images.md +0 -55
@@ -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));