@quanta-intellect/vessel-browser 0.1.67 → 0.1.68

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/out/main/index.js CHANGED
@@ -74,7 +74,7 @@ const defaults = {
74
74
  expiresAt: ""
75
75
  }
76
76
  };
77
- const SAVE_DEBOUNCE_MS$5 = 150;
77
+ const SAVE_DEBOUNCE_MS$6 = 150;
78
78
  const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
79
79
  const logger$j = createLogger("Settings");
80
80
  const SETTABLE_KEYS = new Set(Object.keys(defaults));
@@ -232,7 +232,7 @@ function saveSettings() {
232
232
  if (saveDirty) {
233
233
  void persistNow();
234
234
  }
235
- }, SAVE_DEBOUNCE_MS$5);
235
+ }, SAVE_DEBOUNCE_MS$6);
236
236
  }
237
237
  function setSetting(key, value) {
238
238
  loadSettings();
@@ -871,7 +871,7 @@ function createDebouncedJsonPersistence({
871
871
  }
872
872
  let state$4 = null;
873
873
  const listeners$2 = /* @__PURE__ */ new Set();
874
- const SAVE_DEBOUNCE_MS$4 = 250;
874
+ const SAVE_DEBOUNCE_MS$5 = 250;
875
875
  function getHighlightsPath() {
876
876
  return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
877
877
  }
@@ -889,15 +889,15 @@ function load$4() {
889
889
  });
890
890
  return state$4;
891
891
  }
892
- const persistence$4 = createDebouncedJsonPersistence({
893
- debounceMs: SAVE_DEBOUNCE_MS$4,
892
+ const persistence$5 = createDebouncedJsonPersistence({
893
+ debounceMs: SAVE_DEBOUNCE_MS$5,
894
894
  filePath: getHighlightsPath(),
895
895
  getValue: () => state$4,
896
896
  logLabel: "highlights",
897
897
  resetOnSchedule: true
898
898
  });
899
899
  function save$2() {
900
- persistence$4.schedule();
900
+ persistence$5.schedule();
901
901
  }
902
902
  function emit$2() {
903
903
  if (!state$4) return;
@@ -979,7 +979,7 @@ function clearHighlightsForUrl(url) {
979
979
  return removed;
980
980
  }
981
981
  function flushPersist$4() {
982
- return persistence$4.flush();
982
+ return persistence$5.flush();
983
983
  }
984
984
  const SKIP_TAGS_JS = "var SKIP_TAGS = {SCRIPT:1,STYLE:1,NOSCRIPT:1,TEMPLATE:1,IFRAME:1,SVG:1};";
985
985
  const CONTENT_ROOTS_JS = `
@@ -1551,7 +1551,7 @@ function persistHighlight(url, text) {
1551
1551
  return { success: true, text: capped, id: highlight.id };
1552
1552
  }
1553
1553
  const MAX_HISTORY_ENTRIES = 5e3;
1554
- const SAVE_DEBOUNCE_MS$3 = 250;
1554
+ const SAVE_DEBOUNCE_MS$4 = 250;
1555
1555
  let state$3 = null;
1556
1556
  const listeners$1 = /* @__PURE__ */ new Set();
1557
1557
  function getHistoryPath() {
@@ -1571,14 +1571,14 @@ function load$3() {
1571
1571
  });
1572
1572
  return state$3;
1573
1573
  }
1574
- const persistence$3 = createDebouncedJsonPersistence({
1575
- debounceMs: SAVE_DEBOUNCE_MS$3,
1574
+ const persistence$4 = createDebouncedJsonPersistence({
1575
+ debounceMs: SAVE_DEBOUNCE_MS$4,
1576
1576
  filePath: getHistoryPath(),
1577
1577
  getValue: () => state$3,
1578
1578
  logLabel: "history"
1579
1579
  });
1580
1580
  function save$1() {
1581
- persistence$3.schedule();
1581
+ persistence$4.schedule();
1582
1582
  }
1583
1583
  function emit$1() {
1584
1584
  if (!state$3) return;
@@ -1635,7 +1635,7 @@ function clearAll$1() {
1635
1635
  emit$1();
1636
1636
  }
1637
1637
  function flushPersist$3() {
1638
- return persistence$3.flush();
1638
+ return persistence$4.flush();
1639
1639
  }
1640
1640
  const MAX_CONSOLE_ENTRIES = 500;
1641
1641
  const MAX_NETWORK_ENTRIES = 200;
@@ -2684,6 +2684,8 @@ const Channels = {
2684
2684
  AGENT_APPROVAL_RESOLVE: "agent:approval-resolve",
2685
2685
  AGENT_CHECKPOINT_CREATE: "agent:checkpoint-create",
2686
2686
  AGENT_CHECKPOINT_RESTORE: "agent:checkpoint-restore",
2687
+ AGENT_CHECKPOINT_UPDATE_NOTE: "agent:checkpoint-update-note",
2688
+ AGENT_UNDO_LAST_ACTION: "agent:undo-last-action",
2687
2689
  AGENT_SESSION_CAPTURE: "agent:session-capture",
2688
2690
  AGENT_SESSION_RESTORE: "agent:session-restore",
2689
2691
  // Content
@@ -2691,6 +2693,7 @@ const Channels = {
2691
2693
  READER_MODE_TOGGLE: "reader:toggle",
2692
2694
  // UI state
2693
2695
  SIDEBAR_TOGGLE: "ui:sidebar-toggle",
2696
+ SIDEBAR_NAVIGATE: "ui:sidebar-navigate",
2694
2697
  SIDEBAR_RESIZE: "ui:sidebar-resize",
2695
2698
  SIDEBAR_RESIZE_START: "ui:sidebar-resize-start",
2696
2699
  SIDEBAR_RESIZE_COMMIT: "ui:sidebar-resize-commit",
@@ -2707,6 +2710,7 @@ const Channels = {
2707
2710
  BOOKMARKS_GET: "bookmarks:get",
2708
2711
  BOOKMARKS_UPDATE: "bookmarks:update",
2709
2712
  BOOKMARK_SAVE: "bookmarks:save",
2713
+ BOOKMARK_UPDATE: "bookmarks:update-item",
2710
2714
  BOOKMARK_REMOVE: "bookmarks:remove",
2711
2715
  BOOKMARK_ADD_CONTEXT_TO_CHAT: "bookmarks:add-context-to-chat",
2712
2716
  FOLDER_CREATE: "bookmarks:folder-create",
@@ -2786,6 +2790,7 @@ const Channels = {
2786
2790
  PAGE_DIFF_ACTIVITY: "page:diff-activity",
2787
2791
  PAGE_CHANGED: "page:changed",
2788
2792
  PAGE_DIFF_GET: "page:diff-get",
2793
+ PAGE_DIFF_HISTORY: "page:diff-history",
2789
2794
  PAGE_DIFF_DIRTY: "page:diff-dirty"
2790
2795
  };
2791
2796
  const MAX_DETAIL_ITEMS = 3;
@@ -3101,7 +3106,7 @@ function isTrackablePageUrl(rawUrl) {
3101
3106
  return false;
3102
3107
  }
3103
3108
  }
3104
- const SAVE_DEBOUNCE_MS$2 = 500;
3109
+ const SAVE_DEBOUNCE_MS$3 = 500;
3105
3110
  const MAX_TEXT_LENGTH = 8e3;
3106
3111
  let snapshots = null;
3107
3112
  function getFilePath$1() {
@@ -3139,8 +3144,8 @@ function load$2() {
3139
3144
  });
3140
3145
  return snapshots;
3141
3146
  }
3142
- const persistence$2 = createDebouncedJsonPersistence({
3143
- debounceMs: SAVE_DEBOUNCE_MS$2,
3147
+ const persistence$3 = createDebouncedJsonPersistence({
3148
+ debounceMs: SAVE_DEBOUNCE_MS$3,
3144
3149
  filePath: getFilePath$1(),
3145
3150
  getValue: () => snapshots,
3146
3151
  logLabel: "page snapshots",
@@ -3168,11 +3173,11 @@ function saveSnapshot(rawUrl, title, textContent, headings) {
3168
3173
  };
3169
3174
  s.delete(key);
3170
3175
  s.set(key, snapshot);
3171
- persistence$2.schedule();
3176
+ persistence$3.schedule();
3172
3177
  return snapshot;
3173
3178
  }
3174
3179
  function flushPersist$2() {
3175
- return persistence$2.flush();
3180
+ return persistence$3.flush();
3176
3181
  }
3177
3182
  const SEARCH_ENGINE_HOSTS = [
3178
3183
  "google.",
@@ -5355,8 +5360,32 @@ function normalizePageContent(value) {
5355
5360
  pageSchema: page.pageSchema
5356
5361
  };
5357
5362
  }
5363
+ function normalizePageDiffHistoryItem(value) {
5364
+ if (!value || typeof value !== "object") return null;
5365
+ const raw = value;
5366
+ if (typeof raw.detectedAt !== "string" || typeof raw.summary !== "string") {
5367
+ return null;
5368
+ }
5369
+ return {
5370
+ detectedAt: raw.detectedAt,
5371
+ summary: raw.summary
5372
+ };
5373
+ }
5374
+ function prunePageDiffHistory(items, options) {
5375
+ const cutoff = (options.now ?? Date.now()) - options.maxAgeDays * 24 * 60 * 60 * 1e3;
5376
+ return items.filter((item) => {
5377
+ const detectedAt = Date.parse(item.detectedAt);
5378
+ return Number.isFinite(detectedAt) && detectedAt >= cutoff;
5379
+ }).sort(
5380
+ (left, right) => Date.parse(left.detectedAt) - Date.parse(right.detectedAt)
5381
+ ).slice(-options.maxItems);
5382
+ }
5383
+ function appendPageDiffHistoryItem(items, next, options) {
5384
+ return prunePageDiffHistory([...items, next], options);
5385
+ }
5358
5386
  const latestPageDiffs = /* @__PURE__ */ new Map();
5359
5387
  const recentPageDiffBursts = /* @__PURE__ */ new Map();
5388
+ let historyLoaded = false;
5360
5389
  const pendingPageSnapshotTimers = /* @__PURE__ */ new Map();
5361
5390
  const pendingPageSnapshotDueAt = /* @__PURE__ */ new Map();
5362
5391
  const lastMutationSnapshotAt = /* @__PURE__ */ new Map();
@@ -5377,10 +5406,81 @@ function attachDestroyCleanup(wc) {
5377
5406
  cleanupTimersForWcId(wc.id);
5378
5407
  });
5379
5408
  }
5409
+ const MAX_PERSISTED_DIFF_BURSTS = 50;
5410
+ const MAX_HISTORY_DAYS = 30;
5411
+ const SAVE_DEBOUNCE_MS$2 = 500;
5412
+ function getHistoryFilePath() {
5413
+ return path.join(electron.app.getPath("userData"), "vessel-page-diff-history.json");
5414
+ }
5415
+ function loadHistory() {
5416
+ if (historyLoaded) return recentPageDiffBursts;
5417
+ historyLoaded = true;
5418
+ const loaded = loadJsonFile({
5419
+ filePath: getHistoryFilePath(),
5420
+ fallback: /* @__PURE__ */ new Map(),
5421
+ secure: true,
5422
+ parse: (raw) => {
5423
+ const next = /* @__PURE__ */ new Map();
5424
+ if (!Array.isArray(raw)) return next;
5425
+ for (const entry of raw) {
5426
+ if (!entry || typeof entry !== "object") continue;
5427
+ const record = entry;
5428
+ if (typeof record.url !== "string" || !Array.isArray(record.bursts)) {
5429
+ continue;
5430
+ }
5431
+ next.set(
5432
+ record.url,
5433
+ prunePageDiffHistory(
5434
+ record.bursts.map((item) => normalizePageDiffHistoryItem(item)).filter((item) => item !== null),
5435
+ {
5436
+ maxAgeDays: MAX_HISTORY_DAYS,
5437
+ maxItems: MAX_PERSISTED_DIFF_BURSTS
5438
+ }
5439
+ )
5440
+ );
5441
+ }
5442
+ return next;
5443
+ }
5444
+ });
5445
+ for (const [key, bursts] of loaded.entries()) {
5446
+ recentPageDiffBursts.set(key, bursts);
5447
+ }
5448
+ return recentPageDiffBursts;
5449
+ }
5450
+ const persistence$2 = createDebouncedJsonPersistence({
5451
+ debounceMs: SAVE_DEBOUNCE_MS$2,
5452
+ filePath: getHistoryFilePath(),
5453
+ getValue: () => recentPageDiffBursts,
5454
+ logLabel: "page diff history",
5455
+ secure: true,
5456
+ serialize: (value) => Array.from(value.entries()).map(([url, bursts]) => ({
5457
+ url,
5458
+ bursts
5459
+ }))
5460
+ });
5380
5461
  function getLatestPageDiff(rawUrl) {
5381
5462
  if (!shouldTrackSnapshotUrl(rawUrl)) return null;
5382
5463
  return latestPageDiffs.get(normalizeUrl(rawUrl)) ?? null;
5383
5464
  }
5465
+ function getPageDiffBursts(rawUrl) {
5466
+ if (!shouldTrackSnapshotUrl(rawUrl)) return [];
5467
+ const key = normalizeUrl(rawUrl);
5468
+ const history = loadHistory();
5469
+ const bursts = prunePageDiffHistory(history.get(key) ?? [], {
5470
+ maxAgeDays: MAX_HISTORY_DAYS,
5471
+ maxItems: MAX_PERSISTED_DIFF_BURSTS
5472
+ });
5473
+ const current = history.get(key) ?? [];
5474
+ if (current.length !== bursts.length) {
5475
+ if (bursts.length > 0) {
5476
+ history.set(key, bursts);
5477
+ } else {
5478
+ history.delete(key);
5479
+ }
5480
+ persistence$2.schedule();
5481
+ }
5482
+ return bursts.slice().reverse();
5483
+ }
5384
5484
  function summarizeDiffBurst(diff) {
5385
5485
  const items = diff.changes.slice(0, 2).map((change) => `${change.section}: ${change.summary}`);
5386
5486
  return items.join(" | ");
@@ -5391,16 +5491,21 @@ function enrichWithBurstHistory(key, diff) {
5391
5491
  detectedAt,
5392
5492
  summary: summarizeDiffBurst(diff)
5393
5493
  };
5394
- const bursts = [...recentPageDiffBursts.get(key) || [], nextBurst].slice(
5395
- -5
5396
- );
5397
- recentPageDiffBursts.set(key, bursts);
5494
+ const history = loadHistory();
5495
+ const bursts = appendPageDiffHistoryItem(history.get(key) ?? [], nextBurst, {
5496
+ maxAgeDays: MAX_HISTORY_DAYS,
5497
+ maxItems: MAX_PERSISTED_DIFF_BURSTS,
5498
+ now: Date.parse(detectedAt)
5499
+ });
5500
+ history.set(key, bursts);
5501
+ persistence$2.schedule();
5502
+ const recentBursts = bursts.slice(-5);
5398
5503
  return {
5399
5504
  ...diff,
5400
5505
  burstCount: bursts.length,
5401
5506
  firstDetectedAt: bursts[0]?.detectedAt,
5402
5507
  lastDetectedAt: bursts[bursts.length - 1]?.detectedAt,
5403
- recentBursts: bursts
5508
+ recentBursts: recentBursts.slice().reverse()
5404
5509
  };
5405
5510
  }
5406
5511
  async function capturePageSnapshot(url, wc, sendToRendererViews) {
@@ -5421,11 +5526,9 @@ async function capturePageSnapshot(url, wc, sendToRendererViews) {
5421
5526
  sendToRendererViews(Channels.PAGE_CHANGED, enrichedDiff);
5422
5527
  } else {
5423
5528
  latestPageDiffs.delete(key);
5424
- recentPageDiffBursts.delete(key);
5425
5529
  }
5426
5530
  } else {
5427
5531
  latestPageDiffs.delete(key);
5428
- recentPageDiffBursts.delete(key);
5429
5532
  }
5430
5533
  saveSnapshot(url, title, textContent, headings);
5431
5534
  } catch {
@@ -10080,6 +10183,13 @@ const TOOL_DEFINITIONS = [
10080
10183
  tier: 2,
10081
10184
  hiddenByDefault: true
10082
10185
  },
10186
+ // --- Undo ---
10187
+ {
10188
+ name: "undo_last_action",
10189
+ title: "Undo Last Action",
10190
+ description: "Undo the most recent agent action by restoring the browser to its state before that action ran. Works for click, type, submit, navigate, and similar mutating actions. Returns the name of the undone action, or an error if nothing can be undone.",
10191
+ tier: 1
10192
+ },
10083
10193
  // --- Speedee System: Suggestion Engine ---
10084
10194
  {
10085
10195
  name: "suggest",
@@ -10604,6 +10714,60 @@ function getBookmarkSearchMatch(args) {
10604
10714
  }
10605
10715
  return { matchedFields, score };
10606
10716
  }
10717
+ function normalizeOptionalString(value) {
10718
+ if (typeof value !== "string") return void 0;
10719
+ const trimmed = value.trim();
10720
+ return trimmed || void 0;
10721
+ }
10722
+ function normalizeKeyFields(value) {
10723
+ if (!Array.isArray(value)) return void 0;
10724
+ const normalized = value.filter((field) => typeof field === "string").map((field) => field.trim()).filter(Boolean);
10725
+ return normalized.length > 0 ? normalized : void 0;
10726
+ }
10727
+ function normalizeAgentHints(value) {
10728
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
10729
+ return void 0;
10730
+ }
10731
+ const normalized = Object.fromEntries(
10732
+ Object.entries(value).map(([key, hint]) => [key.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
10733
+ );
10734
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
10735
+ }
10736
+ function hasOwn(value, key) {
10737
+ return Object.prototype.hasOwnProperty.call(value, key);
10738
+ }
10739
+ function normalizeBookmarkMetadata(input) {
10740
+ const normalized = {};
10741
+ const intent = normalizeOptionalString(input.intent);
10742
+ const expectedContent = normalizeOptionalString(input.expectedContent);
10743
+ const keyFields = normalizeKeyFields(input.keyFields);
10744
+ const agentHints = normalizeAgentHints(input.agentHints);
10745
+ if (intent !== void 0) normalized.intent = intent;
10746
+ if (expectedContent !== void 0) {
10747
+ normalized.expectedContent = expectedContent;
10748
+ }
10749
+ if (keyFields !== void 0) normalized.keyFields = keyFields;
10750
+ if (agentHints !== void 0) normalized.agentHints = agentHints;
10751
+ return normalized;
10752
+ }
10753
+ function normalizeBookmarkMetadataUpdate(input) {
10754
+ const normalized = {};
10755
+ if (hasOwn(input, "intent")) {
10756
+ normalized.intent = normalizeOptionalString(input.intent);
10757
+ }
10758
+ if (hasOwn(input, "expectedContent")) {
10759
+ normalized.expectedContent = normalizeOptionalString(
10760
+ input.expectedContent
10761
+ );
10762
+ }
10763
+ if (hasOwn(input, "keyFields")) {
10764
+ normalized.keyFields = normalizeKeyFields(input.keyFields);
10765
+ }
10766
+ if (hasOwn(input, "agentHints")) {
10767
+ normalized.agentHints = normalizeAgentHints(input.agentHints);
10768
+ }
10769
+ return normalized;
10770
+ }
10607
10771
  const UNSORTED_ID = "unsorted";
10608
10772
  const ARCHIVE_FOLDER_NAME = "Archive";
10609
10773
  const SAVE_DEBOUNCE_MS$1 = 250;
@@ -10642,6 +10806,13 @@ const persistence$1 = createDebouncedJsonPersistence({
10642
10806
  function save() {
10643
10807
  persistence$1.schedule();
10644
10808
  }
10809
+ function assignDefinedBookmarkFields(bookmark, fields) {
10810
+ if (!fields) return;
10811
+ for (const [key, value] of Object.entries(fields)) {
10812
+ if (value === void 0) continue;
10813
+ Object.assign(bookmark, { [key]: value });
10814
+ }
10815
+ }
10645
10816
  function emit() {
10646
10817
  if (!state$1) return;
10647
10818
  const snapshot = cloneState(state$1);
@@ -10815,9 +10986,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
10815
10986
  if (note !== void 0) {
10816
10987
  bookmark2.note = note.trim() || void 0;
10817
10988
  }
10818
- if (options?.extra) {
10819
- Object.assign(bookmark2, options.extra);
10820
- }
10989
+ assignDefinedBookmarkFields(bookmark2, options?.extra);
10821
10990
  bookmark2.savedAt = (/* @__PURE__ */ new Date()).toISOString();
10822
10991
  save();
10823
10992
  emit();
@@ -10859,6 +11028,12 @@ function updateBookmark(id, updates) {
10859
11028
  load$1();
10860
11029
  const bookmark = state$1.bookmarks.find((item) => item.id === id);
10861
11030
  if (!bookmark) return null;
11031
+ const metadataUpdates = normalizeBookmarkMetadataUpdate({
11032
+ intent: updates.intent,
11033
+ expectedContent: updates.expectedContent,
11034
+ keyFields: updates.keyFields,
11035
+ agentHints: updates.agentHints
11036
+ });
10862
11037
  if (typeof updates.title === "string") {
10863
11038
  const trimmed = updates.title.trim();
10864
11039
  bookmark.title = trimmed || bookmark.url;
@@ -10870,20 +11045,20 @@ function updateBookmark(id, updates) {
10870
11045
  if (typeof updates.folderId === "string") {
10871
11046
  bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$1.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
10872
11047
  }
10873
- if (typeof updates.intent === "string") {
10874
- bookmark.intent = updates.intent.trim() || void 0;
11048
+ if ("intent" in metadataUpdates) {
11049
+ bookmark.intent = metadataUpdates.intent;
10875
11050
  }
10876
- if (typeof updates.expectedContent === "string") {
10877
- bookmark.expectedContent = updates.expectedContent.trim() || void 0;
11051
+ if ("expectedContent" in metadataUpdates) {
11052
+ bookmark.expectedContent = metadataUpdates.expectedContent;
10878
11053
  }
10879
- if (updates.keyFields !== void 0) {
10880
- bookmark.keyFields = updates.keyFields;
11054
+ if ("keyFields" in metadataUpdates) {
11055
+ bookmark.keyFields = metadataUpdates.keyFields;
10881
11056
  }
10882
11057
  if (updates.pageSchema !== void 0) {
10883
11058
  bookmark.pageSchema = updates.pageSchema;
10884
11059
  }
10885
- if (updates.agentHints !== void 0) {
10886
- bookmark.agentHints = updates.agentHints;
11060
+ if ("agentHints" in metadataUpdates) {
11061
+ bookmark.agentHints = metadataUpdates.agentHints;
10887
11062
  }
10888
11063
  save();
10889
11064
  emit();
@@ -11878,33 +12053,6 @@ function formatCompactToolResult(name, result) {
11878
12053
  return limitText(result, 18, 1400);
11879
12054
  }
11880
12055
  }
11881
- function normalizeOptionalString(value) {
11882
- if (typeof value !== "string") return void 0;
11883
- const trimmed = value.trim();
11884
- return trimmed || void 0;
11885
- }
11886
- function normalizeKeyFields(value) {
11887
- if (!Array.isArray(value)) return void 0;
11888
- const normalized = value.filter((field) => typeof field === "string").map((field) => field.trim()).filter(Boolean);
11889
- return normalized.length > 0 ? normalized : void 0;
11890
- }
11891
- function normalizeAgentHints(value) {
11892
- if (!value || typeof value !== "object" || Array.isArray(value)) {
11893
- return void 0;
11894
- }
11895
- const normalized = Object.fromEntries(
11896
- Object.entries(value).map(([key, hint]) => [key.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
11897
- );
11898
- return Object.keys(normalized).length > 0 ? normalized : void 0;
11899
- }
11900
- function normalizeBookmarkMetadata(input) {
11901
- return {
11902
- intent: normalizeOptionalString(input.intent),
11903
- expectedContent: normalizeOptionalString(input.expectedContent),
11904
- keyFields: normalizeKeyFields(input.keyFields),
11905
- agentHints: normalizeAgentHints(input.agentHints)
11906
- };
11907
- }
11908
12056
  const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
11909
12057
  const HUGGING_FACE_MODEL_TASKS = [
11910
12058
  {
@@ -16056,6 +16204,11 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
16056
16204
  ctx.runtime.clearFlow();
16057
16205
  return "Workflow ended.";
16058
16206
  }
16207
+ case "undo_last_action": {
16208
+ const undone = ctx.runtime.undoLastAction();
16209
+ if (!undone) return "Nothing to undo. No undo snapshots available.";
16210
+ return `Undid action: ${undone}. Browser restored to state before that action.`;
16211
+ }
16059
16212
  case "suggest": {
16060
16213
  if (!wc) return "No active tab. Use navigate to open a page.";
16061
16214
  let page;
@@ -20066,6 +20219,23 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
20066
20219
  return asTextResponse("Workflow ended.");
20067
20220
  }
20068
20221
  );
20222
+ server.registerTool(
20223
+ "undo_last_action",
20224
+ {
20225
+ title: "Undo Last Action",
20226
+ description: "Undo the most recent agent action by restoring the browser to its state before that action ran. Works for click, type, submit, navigate, and similar mutating actions."
20227
+ },
20228
+ async () => {
20229
+ const undone = runtime2.undoLastAction();
20230
+ if (!undone)
20231
+ return asTextResponse(
20232
+ "Nothing to undo. No undo snapshots available."
20233
+ );
20234
+ return asTextResponse(
20235
+ `Undid action: ${undone}. Browser restored to state before that action.`
20236
+ );
20237
+ }
20238
+ );
20069
20239
  server.registerTool(
20070
20240
  "suggest",
20071
20241
  {
@@ -21939,6 +22109,19 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
21939
22109
  if (!wc) return null;
21940
22110
  return getLatestPageDiff(wc.getURL());
21941
22111
  });
22112
+ electron.ipcMain.handle(Channels.PAGE_DIFF_HISTORY, () => {
22113
+ try {
22114
+ if (!isPremiumActiveState(getPremiumState())) {
22115
+ return { error: "Premium required" };
22116
+ }
22117
+ const activeTab = windowState.tabManager.getActiveTab();
22118
+ const wc = activeTab?.view.webContents;
22119
+ if (!wc) return [];
22120
+ return getPageDiffBursts(wc.getURL());
22121
+ } catch {
22122
+ return [];
22123
+ }
22124
+ });
21942
22125
  electron.ipcMain.on(Channels.PAGE_DIFF_ACTIVITY, (event) => {
21943
22126
  const wc = event.sender;
21944
22127
  if (!wc || wc.isDestroyed()) return;
@@ -22299,6 +22482,20 @@ function registerIpcHandlers(windowState, runtime2) {
22299
22482
  width: windowState.uiState.sidebarWidth
22300
22483
  };
22301
22484
  });
22485
+ electron.ipcMain.handle(Channels.SIDEBAR_NAVIGATE, (_, tab) => {
22486
+ assertString(tab, "tab");
22487
+ if (!windowState.uiState.sidebarOpen) {
22488
+ windowState.uiState.sidebarOpen = true;
22489
+ layoutViews(windowState);
22490
+ }
22491
+ if (!sidebarView.webContents.isDestroyed()) {
22492
+ sidebarView.webContents.send(Channels.SIDEBAR_NAVIGATE, tab);
22493
+ }
22494
+ return {
22495
+ open: windowState.uiState.sidebarOpen,
22496
+ width: windowState.uiState.sidebarWidth
22497
+ };
22498
+ });
22302
22499
  electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_START, () => {
22303
22500
  sidebarResizeActive = true;
22304
22501
  clearSidebarResizeRecoveryTimer();
@@ -22397,6 +22594,14 @@ function registerIpcHandlers(windowState, runtime2) {
22397
22594
  Channels.AGENT_CHECKPOINT_RESTORE,
22398
22595
  (_, checkpointId) => runtime2.restoreCheckpoint(checkpointId)
22399
22596
  );
22597
+ electron.ipcMain.handle(
22598
+ Channels.AGENT_CHECKPOINT_UPDATE_NOTE,
22599
+ (_, checkpointId, note) => runtime2.updateCheckpointNote(checkpointId, note || "")
22600
+ );
22601
+ electron.ipcMain.handle(
22602
+ Channels.AGENT_UNDO_LAST_ACTION,
22603
+ () => runtime2.undoLastAction()
22604
+ );
22400
22605
  electron.ipcMain.handle(
22401
22606
  Channels.AGENT_SESSION_CAPTURE,
22402
22607
  (_, note) => runtime2.captureSession(note)
@@ -22436,6 +22641,13 @@ function registerIpcHandlers(windowState, runtime2) {
22436
22641
  return result.bookmark;
22437
22642
  }
22438
22643
  );
22644
+ electron.ipcMain.handle(
22645
+ Channels.BOOKMARK_UPDATE,
22646
+ (_, id, updates) => {
22647
+ trackBookmarkAction("save");
22648
+ return updateBookmark(id, updates);
22649
+ }
22650
+ );
22439
22651
  electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (_, id) => {
22440
22652
  trackBookmarkAction("remove");
22441
22653
  return removeBookmark(id);
@@ -22725,6 +22937,41 @@ function registerIpcHandlers(windowState, runtime2) {
22725
22937
  registerAutofillHandlers(windowState);
22726
22938
  registerPageDiffHandlers(windowState, sendToRendererViews);
22727
22939
  }
22940
+ const UNDOABLE_ACTIONS = /* @__PURE__ */ new Set([
22941
+ "accept_cookies",
22942
+ "clear_overlays",
22943
+ "click",
22944
+ "close_tab",
22945
+ "create_tab",
22946
+ "dismiss_popup",
22947
+ "fill_form",
22948
+ "focus",
22949
+ "go_back",
22950
+ "go_forward",
22951
+ "load_session",
22952
+ "login",
22953
+ "navigate",
22954
+ "open_bookmark",
22955
+ "paginate",
22956
+ "press_key",
22957
+ "reload",
22958
+ "restore_checkpoint",
22959
+ "scroll",
22960
+ "scroll_to_element",
22961
+ "search",
22962
+ "select_option",
22963
+ "set_ad_blocking",
22964
+ "submit_form",
22965
+ "switch_tab",
22966
+ "type_text"
22967
+ ]);
22968
+ function isUndoableAction(name) {
22969
+ return UNDOABLE_ACTIONS.has(name);
22970
+ }
22971
+ function isUndoableResult(result) {
22972
+ const normalized = result.trim().toLowerCase();
22973
+ return normalized.length > 0 && !normalized.startsWith("error:") && !normalized.startsWith("nothing to ") && !normalized.startsWith("no active ") && !normalized.startsWith("action rejected:");
22974
+ }
22728
22975
  function makeStep(label, status = "pending") {
22729
22976
  return { label, status };
22730
22977
  }
@@ -23121,6 +23368,7 @@ class AgentRuntime {
23121
23368
  state;
23122
23369
  updateListener = null;
23123
23370
  pendingResolvers = /* @__PURE__ */ new Map();
23371
+ undoSnapshots = [];
23124
23372
  setUpdateListener(listener) {
23125
23373
  this.updateListener = listener;
23126
23374
  if (listener) {
@@ -23130,6 +23378,8 @@ class AgentRuntime {
23130
23378
  getState() {
23131
23379
  const snapshot = clone(this.state);
23132
23380
  snapshot.mcpStatus = getMcpStatus();
23381
+ snapshot.canUndo = this.canUndo();
23382
+ snapshot.undoInfo = this.getUndoInfo();
23133
23383
  return snapshot;
23134
23384
  }
23135
23385
  onTabStateChanged() {
@@ -23172,6 +23422,37 @@ class AgentRuntime {
23172
23422
  this.captureSession(`Restored ${checkpoint.name}`);
23173
23423
  return clone(checkpoint);
23174
23424
  }
23425
+ updateCheckpointNote(checkpointId, note) {
23426
+ const index = this.state.checkpoints.findIndex((item) => item.id === checkpointId);
23427
+ if (index === -1) return null;
23428
+ this.state.checkpoints[index] = {
23429
+ ...this.state.checkpoints[index],
23430
+ note: note.trim() || void 0
23431
+ };
23432
+ this.emit();
23433
+ return clone(this.state.checkpoints[index]);
23434
+ }
23435
+ canUndo() {
23436
+ return this.undoSnapshots.length > 0;
23437
+ }
23438
+ getUndoInfo() {
23439
+ const latest = this.undoSnapshots[this.undoSnapshots.length - 1];
23440
+ if (!latest) return null;
23441
+ return { actionName: latest.actionName, capturedAt: latest.capturedAt };
23442
+ }
23443
+ undoLastAction() {
23444
+ const snapshot = this.undoSnapshots.at(-1);
23445
+ if (!snapshot) return null;
23446
+ try {
23447
+ this.tabManager.restoreSession(snapshot.snapshot);
23448
+ this.undoSnapshots.pop();
23449
+ } catch (error) {
23450
+ logger$3.error("Failed to restore undo snapshot", error);
23451
+ return null;
23452
+ }
23453
+ this.captureSession(`Undid ${snapshot.actionName}`);
23454
+ return snapshot.actionName;
23455
+ }
23175
23456
  captureSession(note) {
23176
23457
  const snapshot = this.tabManager.snapshotSession(note);
23177
23458
  this.state.session = snapshot;
@@ -23321,6 +23602,7 @@ ${progress}
23321
23602
  args = {},
23322
23603
  tabId = null,
23323
23604
  dangerous = false,
23605
+ undoable,
23324
23606
  executor
23325
23607
  }) {
23326
23608
  const action = this.startAction({
@@ -23374,8 +23656,13 @@ ${progress}
23374
23656
  streamId: transcriptStreamId,
23375
23657
  mode: "replace"
23376
23658
  });
23659
+ const shouldCaptureUndo = undoable ?? isUndoableAction(name);
23660
+ const undoSnapshot = shouldCaptureUndo ? this.createUndoSnapshot(name) : null;
23377
23661
  try {
23378
23662
  const result = await executor();
23663
+ if (undoSnapshot && isUndoableResult(result)) {
23664
+ this.pushUndoSnapshot(undoSnapshot);
23665
+ }
23379
23666
  this.finishAction(action.id, "completed", summarizeText(result));
23380
23667
  this.publishTranscript({
23381
23668
  source,
@@ -23402,6 +23689,21 @@ ${progress}
23402
23689
  throw error;
23403
23690
  }
23404
23691
  }
23692
+ createUndoSnapshot(name) {
23693
+ return {
23694
+ id: crypto$2.randomUUID(),
23695
+ actionName: name,
23696
+ snapshot: this.tabManager.snapshotSession(
23697
+ `Auto-checkpoint before ${name}`
23698
+ ),
23699
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
23700
+ };
23701
+ }
23702
+ pushUndoSnapshot(snapshot) {
23703
+ this.undoSnapshots = [...this.undoSnapshots, snapshot].slice(
23704
+ -10
23705
+ );
23706
+ }
23405
23707
  resolveApproval(approvalId, approved) {
23406
23708
  const approval = this.state.supervisor.pendingApprovals.find(
23407
23709
  (item) => item.id === approvalId