@quanta-intellect/vessel-browser 0.1.65 → 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/README.md +12 -7
- package/out/main/index.js +393 -75
- package/out/preload/index.js +17 -2
- package/out/renderer/assets/{index-BQjjFSb1.js → index-BCEB2epC.js} +745 -406
- package/out/renderer/assets/{index-4OgPv5GF.css → index-B_C8ayic.css} +13 -0
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -74,7 +74,7 @@ const defaults = {
|
|
|
74
74
|
expiresAt: ""
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
|
-
const SAVE_DEBOUNCE_MS$
|
|
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$
|
|
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$
|
|
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$
|
|
893
|
-
debounceMs: SAVE_DEBOUNCE_MS$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
1575
|
-
debounceMs: SAVE_DEBOUNCE_MS$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3143
|
-
debounceMs: SAVE_DEBOUNCE_MS$
|
|
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$
|
|
3176
|
+
persistence$3.schedule();
|
|
3172
3177
|
return snapshot;
|
|
3173
3178
|
}
|
|
3174
3179
|
function flushPersist$2() {
|
|
3175
|
-
return persistence$
|
|
3180
|
+
return persistence$3.flush();
|
|
3176
3181
|
}
|
|
3177
3182
|
const SEARCH_ENGINE_HOSTS = [
|
|
3178
3183
|
"google.",
|
|
@@ -4419,6 +4424,13 @@ function inferPageSchema(page) {
|
|
|
4419
4424
|
confidence
|
|
4420
4425
|
};
|
|
4421
4426
|
}
|
|
4427
|
+
const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS$1 = 1500;
|
|
4428
|
+
const EXTRACT_SCRIPT_TIMEOUT_MS = 3e3;
|
|
4429
|
+
const EXTRACT_TIMEOUT_BASE_MS = 12e3;
|
|
4430
|
+
const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
4431
|
+
const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
4432
|
+
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
4433
|
+
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
4422
4434
|
const logger$e = createLogger("Extractor");
|
|
4423
4435
|
const EMPTY_PAGE_CONTENT = {
|
|
4424
4436
|
title: "",
|
|
@@ -5136,8 +5148,7 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
5136
5148
|
function delay(ms) {
|
|
5137
5149
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5138
5150
|
}
|
|
5139
|
-
|
|
5140
|
-
async function waitForDomReady(webContents, timeoutMs = 1500) {
|
|
5151
|
+
async function waitForDomReady(webContents, timeoutMs = DEFAULT_PAGE_SCRIPT_TIMEOUT_MS$1) {
|
|
5141
5152
|
const deadline = Date.now() + timeoutMs;
|
|
5142
5153
|
while (Date.now() < deadline) {
|
|
5143
5154
|
const readyState = await executeScript(
|
|
@@ -5163,7 +5174,10 @@ async function executeScript(webContents, script) {
|
|
|
5163
5174
|
return await Promise.race([
|
|
5164
5175
|
webContents.executeJavaScript(script),
|
|
5165
5176
|
new Promise((resolve) => {
|
|
5166
|
-
timer = setTimeout(
|
|
5177
|
+
timer = setTimeout(
|
|
5178
|
+
() => resolve(null),
|
|
5179
|
+
EXTRACT_SCRIPT_TIMEOUT_MS
|
|
5180
|
+
);
|
|
5167
5181
|
})
|
|
5168
5182
|
]);
|
|
5169
5183
|
} catch (err) {
|
|
@@ -5260,8 +5274,6 @@ function mergePageContent(candidates, webContents) {
|
|
|
5260
5274
|
url: mergedBase.url || webContents.getURL() || ""
|
|
5261
5275
|
};
|
|
5262
5276
|
}
|
|
5263
|
-
const EXTRACT_TIMEOUT_BASE_MS = 12e3;
|
|
5264
|
-
const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
5265
5277
|
async function estimateExtractionTimeout(webContents) {
|
|
5266
5278
|
try {
|
|
5267
5279
|
const elementCount = await executeScript(
|
|
@@ -5348,8 +5360,32 @@ function normalizePageContent(value) {
|
|
|
5348
5360
|
pageSchema: page.pageSchema
|
|
5349
5361
|
};
|
|
5350
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
|
+
}
|
|
5351
5386
|
const latestPageDiffs = /* @__PURE__ */ new Map();
|
|
5352
5387
|
const recentPageDiffBursts = /* @__PURE__ */ new Map();
|
|
5388
|
+
let historyLoaded = false;
|
|
5353
5389
|
const pendingPageSnapshotTimers = /* @__PURE__ */ new Map();
|
|
5354
5390
|
const pendingPageSnapshotDueAt = /* @__PURE__ */ new Map();
|
|
5355
5391
|
const lastMutationSnapshotAt = /* @__PURE__ */ new Map();
|
|
@@ -5370,12 +5406,81 @@ function attachDestroyCleanup(wc) {
|
|
|
5370
5406
|
cleanupTimersForWcId(wc.id);
|
|
5371
5407
|
});
|
|
5372
5408
|
}
|
|
5373
|
-
const
|
|
5374
|
-
const
|
|
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
|
+
});
|
|
5375
5461
|
function getLatestPageDiff(rawUrl) {
|
|
5376
5462
|
if (!shouldTrackSnapshotUrl(rawUrl)) return null;
|
|
5377
5463
|
return latestPageDiffs.get(normalizeUrl(rawUrl)) ?? null;
|
|
5378
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
|
+
}
|
|
5379
5484
|
function summarizeDiffBurst(diff) {
|
|
5380
5485
|
const items = diff.changes.slice(0, 2).map((change) => `${change.section}: ${change.summary}`);
|
|
5381
5486
|
return items.join(" | ");
|
|
@@ -5386,16 +5491,21 @@ function enrichWithBurstHistory(key, diff) {
|
|
|
5386
5491
|
detectedAt,
|
|
5387
5492
|
summary: summarizeDiffBurst(diff)
|
|
5388
5493
|
};
|
|
5389
|
-
const
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
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);
|
|
5393
5503
|
return {
|
|
5394
5504
|
...diff,
|
|
5395
5505
|
burstCount: bursts.length,
|
|
5396
5506
|
firstDetectedAt: bursts[0]?.detectedAt,
|
|
5397
5507
|
lastDetectedAt: bursts[bursts.length - 1]?.detectedAt,
|
|
5398
|
-
recentBursts:
|
|
5508
|
+
recentBursts: recentBursts.slice().reverse()
|
|
5399
5509
|
};
|
|
5400
5510
|
}
|
|
5401
5511
|
async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
@@ -5416,11 +5526,9 @@ async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
|
5416
5526
|
sendToRendererViews(Channels.PAGE_CHANGED, enrichedDiff);
|
|
5417
5527
|
} else {
|
|
5418
5528
|
latestPageDiffs.delete(key);
|
|
5419
|
-
recentPageDiffBursts.delete(key);
|
|
5420
5529
|
}
|
|
5421
5530
|
} else {
|
|
5422
5531
|
latestPageDiffs.delete(key);
|
|
5423
|
-
recentPageDiffBursts.delete(key);
|
|
5424
5532
|
}
|
|
5425
5533
|
saveSnapshot(url, title, textContent, headings);
|
|
5426
5534
|
} catch {
|
|
@@ -5429,8 +5537,8 @@ async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
|
5429
5537
|
function computeNextSnapshotDueAt(wcId, now, delayMs) {
|
|
5430
5538
|
const lastCaptureAt = lastMutationSnapshotAt.get(wcId) || 0;
|
|
5431
5539
|
const lastActivityAt = lastMutationActivityAt.get(wcId) || 0;
|
|
5432
|
-
const earliestAllowedAt = lastCaptureAt +
|
|
5433
|
-
const stableAfterActivityAt = lastActivityAt ? lastActivityAt +
|
|
5540
|
+
const earliestAllowedAt = lastCaptureAt + MUTATION_CAPTURE_INTERVAL_MS;
|
|
5541
|
+
const stableAfterActivityAt = lastActivityAt ? lastActivityAt + MUTATION_SETTLE_AFTER_MS : 0;
|
|
5434
5542
|
return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
|
|
5435
5543
|
}
|
|
5436
5544
|
function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
@@ -6135,7 +6243,7 @@ class AnthropicProvider {
|
|
|
6135
6243
|
let textContent = "";
|
|
6136
6244
|
const toolUseBlocks = [];
|
|
6137
6245
|
let currentToolUse = null;
|
|
6138
|
-
const STREAM_IDLE_TIMEOUT_MS =
|
|
6246
|
+
const STREAM_IDLE_TIMEOUT_MS = AGENT_STREAM_IDLE_TIMEOUT_MS;
|
|
6139
6247
|
let idleTimer = null;
|
|
6140
6248
|
const resetIdleTimer = () => {
|
|
6141
6249
|
if (idleTimer) clearTimeout(idleTimer);
|
|
@@ -6991,9 +7099,16 @@ class OpenAICompatProvider {
|
|
|
6991
7099
|
constructor(config) {
|
|
6992
7100
|
const meta = PROVIDERS[config.id];
|
|
6993
7101
|
const baseURL = config.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
|
|
7102
|
+
const isOpenRouter = baseURL.includes("openrouter.ai");
|
|
6994
7103
|
this.client = new OpenAI({
|
|
6995
7104
|
apiKey: config.apiKey || "ollama",
|
|
6996
|
-
baseURL
|
|
7105
|
+
baseURL,
|
|
7106
|
+
...isOpenRouter && {
|
|
7107
|
+
defaultHeaders: {
|
|
7108
|
+
"HTTP-Referer": "https://github.com/unmodeled/vessel-browser",
|
|
7109
|
+
"X-Title": "Vessel"
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
6997
7112
|
});
|
|
6998
7113
|
this.providerId = config.id;
|
|
6999
7114
|
this.model = config.model || meta?.defaultModel || "gpt-4o";
|
|
@@ -10068,6 +10183,13 @@ const TOOL_DEFINITIONS = [
|
|
|
10068
10183
|
tier: 2,
|
|
10069
10184
|
hiddenByDefault: true
|
|
10070
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
|
+
},
|
|
10071
10193
|
// --- Speedee System: Suggestion Engine ---
|
|
10072
10194
|
{
|
|
10073
10195
|
name: "suggest",
|
|
@@ -10592,6 +10714,60 @@ function getBookmarkSearchMatch(args) {
|
|
|
10592
10714
|
}
|
|
10593
10715
|
return { matchedFields, score };
|
|
10594
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
|
+
}
|
|
10595
10771
|
const UNSORTED_ID = "unsorted";
|
|
10596
10772
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
10597
10773
|
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
@@ -10630,6 +10806,13 @@ const persistence$1 = createDebouncedJsonPersistence({
|
|
|
10630
10806
|
function save() {
|
|
10631
10807
|
persistence$1.schedule();
|
|
10632
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
|
+
}
|
|
10633
10816
|
function emit() {
|
|
10634
10817
|
if (!state$1) return;
|
|
10635
10818
|
const snapshot = cloneState(state$1);
|
|
@@ -10803,9 +10986,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
10803
10986
|
if (note !== void 0) {
|
|
10804
10987
|
bookmark2.note = note.trim() || void 0;
|
|
10805
10988
|
}
|
|
10806
|
-
|
|
10807
|
-
Object.assign(bookmark2, options.extra);
|
|
10808
|
-
}
|
|
10989
|
+
assignDefinedBookmarkFields(bookmark2, options?.extra);
|
|
10809
10990
|
bookmark2.savedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10810
10991
|
save();
|
|
10811
10992
|
emit();
|
|
@@ -10847,6 +11028,12 @@ function updateBookmark(id, updates) {
|
|
|
10847
11028
|
load$1();
|
|
10848
11029
|
const bookmark = state$1.bookmarks.find((item) => item.id === id);
|
|
10849
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
|
+
});
|
|
10850
11037
|
if (typeof updates.title === "string") {
|
|
10851
11038
|
const trimmed = updates.title.trim();
|
|
10852
11039
|
bookmark.title = trimmed || bookmark.url;
|
|
@@ -10858,20 +11045,20 @@ function updateBookmark(id, updates) {
|
|
|
10858
11045
|
if (typeof updates.folderId === "string") {
|
|
10859
11046
|
bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$1.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
10860
11047
|
}
|
|
10861
|
-
if (
|
|
10862
|
-
bookmark.intent =
|
|
11048
|
+
if ("intent" in metadataUpdates) {
|
|
11049
|
+
bookmark.intent = metadataUpdates.intent;
|
|
10863
11050
|
}
|
|
10864
|
-
if (
|
|
10865
|
-
bookmark.expectedContent =
|
|
11051
|
+
if ("expectedContent" in metadataUpdates) {
|
|
11052
|
+
bookmark.expectedContent = metadataUpdates.expectedContent;
|
|
10866
11053
|
}
|
|
10867
|
-
if (
|
|
10868
|
-
bookmark.keyFields =
|
|
11054
|
+
if ("keyFields" in metadataUpdates) {
|
|
11055
|
+
bookmark.keyFields = metadataUpdates.keyFields;
|
|
10869
11056
|
}
|
|
10870
11057
|
if (updates.pageSchema !== void 0) {
|
|
10871
11058
|
bookmark.pageSchema = updates.pageSchema;
|
|
10872
11059
|
}
|
|
10873
|
-
if (
|
|
10874
|
-
bookmark.agentHints =
|
|
11060
|
+
if ("agentHints" in metadataUpdates) {
|
|
11061
|
+
bookmark.agentHints = metadataUpdates.agentHints;
|
|
10875
11062
|
}
|
|
10876
11063
|
save();
|
|
10877
11064
|
emit();
|
|
@@ -11231,9 +11418,13 @@ function formatDeadLinkMessage(label, result) {
|
|
|
11231
11418
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
11232
11419
|
}
|
|
11233
11420
|
const logger$b = createLogger("Screenshot");
|
|
11421
|
+
const SCREENSHOT_RETRY_COUNT = 3;
|
|
11422
|
+
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
11234
11423
|
async function captureScreenshot(wc) {
|
|
11235
|
-
for (let attempt = 0; attempt <
|
|
11236
|
-
await new Promise(
|
|
11424
|
+
for (let attempt = 0; attempt < SCREENSHOT_RETRY_COUNT; attempt += 1) {
|
|
11425
|
+
await new Promise(
|
|
11426
|
+
(resolve) => setTimeout(resolve, SCREENSHOT_RETRY_BASE_DELAY_MS * (attempt + 1))
|
|
11427
|
+
);
|
|
11237
11428
|
try {
|
|
11238
11429
|
const image = await wc.capturePage();
|
|
11239
11430
|
if (!image.isEmpty()) {
|
|
@@ -11862,33 +12053,6 @@ function formatCompactToolResult(name, result) {
|
|
|
11862
12053
|
return limitText(result, 18, 1400);
|
|
11863
12054
|
}
|
|
11864
12055
|
}
|
|
11865
|
-
function normalizeOptionalString(value) {
|
|
11866
|
-
if (typeof value !== "string") return void 0;
|
|
11867
|
-
const trimmed = value.trim();
|
|
11868
|
-
return trimmed || void 0;
|
|
11869
|
-
}
|
|
11870
|
-
function normalizeKeyFields(value) {
|
|
11871
|
-
if (!Array.isArray(value)) return void 0;
|
|
11872
|
-
const normalized = value.filter((field) => typeof field === "string").map((field) => field.trim()).filter(Boolean);
|
|
11873
|
-
return normalized.length > 0 ? normalized : void 0;
|
|
11874
|
-
}
|
|
11875
|
-
function normalizeAgentHints(value) {
|
|
11876
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11877
|
-
return void 0;
|
|
11878
|
-
}
|
|
11879
|
-
const normalized = Object.fromEntries(
|
|
11880
|
-
Object.entries(value).map(([key, hint]) => [key.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
|
|
11881
|
-
);
|
|
11882
|
-
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
11883
|
-
}
|
|
11884
|
-
function normalizeBookmarkMetadata(input) {
|
|
11885
|
-
return {
|
|
11886
|
-
intent: normalizeOptionalString(input.intent),
|
|
11887
|
-
expectedContent: normalizeOptionalString(input.expectedContent),
|
|
11888
|
-
keyFields: normalizeKeyFields(input.keyFields),
|
|
11889
|
-
agentHints: normalizeAgentHints(input.agentHints)
|
|
11890
|
-
};
|
|
11891
|
-
}
|
|
11892
12056
|
const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
|
|
11893
12057
|
const HUGGING_FACE_MODEL_TASKS = [
|
|
11894
12058
|
{
|
|
@@ -16040,6 +16204,11 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
16040
16204
|
ctx.runtime.clearFlow();
|
|
16041
16205
|
return "Workflow ended.";
|
|
16042
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
|
+
}
|
|
16043
16212
|
case "suggest": {
|
|
16044
16213
|
if (!wc) return "No active tab. Use navigate to open a page.";
|
|
16045
16214
|
let page;
|
|
@@ -20050,6 +20219,23 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
20050
20219
|
return asTextResponse("Workflow ended.");
|
|
20051
20220
|
}
|
|
20052
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
|
+
);
|
|
20053
20239
|
server.registerTool(
|
|
20054
20240
|
"suggest",
|
|
20055
20241
|
{
|
|
@@ -21923,6 +22109,19 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
|
21923
22109
|
if (!wc) return null;
|
|
21924
22110
|
return getLatestPageDiff(wc.getURL());
|
|
21925
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
|
+
});
|
|
21926
22125
|
electron.ipcMain.on(Channels.PAGE_DIFF_ACTIVITY, (event) => {
|
|
21927
22126
|
const wc = event.sender;
|
|
21928
22127
|
if (!wc || wc.isDestroyed()) return;
|
|
@@ -22283,6 +22482,20 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22283
22482
|
width: windowState.uiState.sidebarWidth
|
|
22284
22483
|
};
|
|
22285
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
|
+
});
|
|
22286
22499
|
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_START, () => {
|
|
22287
22500
|
sidebarResizeActive = true;
|
|
22288
22501
|
clearSidebarResizeRecoveryTimer();
|
|
@@ -22381,6 +22594,14 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22381
22594
|
Channels.AGENT_CHECKPOINT_RESTORE,
|
|
22382
22595
|
(_, checkpointId) => runtime2.restoreCheckpoint(checkpointId)
|
|
22383
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
|
+
);
|
|
22384
22605
|
electron.ipcMain.handle(
|
|
22385
22606
|
Channels.AGENT_SESSION_CAPTURE,
|
|
22386
22607
|
(_, note) => runtime2.captureSession(note)
|
|
@@ -22420,6 +22641,13 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22420
22641
|
return result.bookmark;
|
|
22421
22642
|
}
|
|
22422
22643
|
);
|
|
22644
|
+
electron.ipcMain.handle(
|
|
22645
|
+
Channels.BOOKMARK_UPDATE,
|
|
22646
|
+
(_, id, updates) => {
|
|
22647
|
+
trackBookmarkAction("save");
|
|
22648
|
+
return updateBookmark(id, updates);
|
|
22649
|
+
}
|
|
22650
|
+
);
|
|
22423
22651
|
electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (_, id) => {
|
|
22424
22652
|
trackBookmarkAction("remove");
|
|
22425
22653
|
return removeBookmark(id);
|
|
@@ -22709,6 +22937,41 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22709
22937
|
registerAutofillHandlers(windowState);
|
|
22710
22938
|
registerPageDiffHandlers(windowState, sendToRendererViews);
|
|
22711
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
|
+
}
|
|
22712
22975
|
function makeStep(label, status = "pending") {
|
|
22713
22976
|
return { label, status };
|
|
22714
22977
|
}
|
|
@@ -23105,6 +23368,7 @@ class AgentRuntime {
|
|
|
23105
23368
|
state;
|
|
23106
23369
|
updateListener = null;
|
|
23107
23370
|
pendingResolvers = /* @__PURE__ */ new Map();
|
|
23371
|
+
undoSnapshots = [];
|
|
23108
23372
|
setUpdateListener(listener) {
|
|
23109
23373
|
this.updateListener = listener;
|
|
23110
23374
|
if (listener) {
|
|
@@ -23114,6 +23378,8 @@ class AgentRuntime {
|
|
|
23114
23378
|
getState() {
|
|
23115
23379
|
const snapshot = clone(this.state);
|
|
23116
23380
|
snapshot.mcpStatus = getMcpStatus();
|
|
23381
|
+
snapshot.canUndo = this.canUndo();
|
|
23382
|
+
snapshot.undoInfo = this.getUndoInfo();
|
|
23117
23383
|
return snapshot;
|
|
23118
23384
|
}
|
|
23119
23385
|
onTabStateChanged() {
|
|
@@ -23156,6 +23422,37 @@ class AgentRuntime {
|
|
|
23156
23422
|
this.captureSession(`Restored ${checkpoint.name}`);
|
|
23157
23423
|
return clone(checkpoint);
|
|
23158
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
|
+
}
|
|
23159
23456
|
captureSession(note) {
|
|
23160
23457
|
const snapshot = this.tabManager.snapshotSession(note);
|
|
23161
23458
|
this.state.session = snapshot;
|
|
@@ -23305,6 +23602,7 @@ ${progress}
|
|
|
23305
23602
|
args = {},
|
|
23306
23603
|
tabId = null,
|
|
23307
23604
|
dangerous = false,
|
|
23605
|
+
undoable,
|
|
23308
23606
|
executor
|
|
23309
23607
|
}) {
|
|
23310
23608
|
const action = this.startAction({
|
|
@@ -23358,8 +23656,13 @@ ${progress}
|
|
|
23358
23656
|
streamId: transcriptStreamId,
|
|
23359
23657
|
mode: "replace"
|
|
23360
23658
|
});
|
|
23659
|
+
const shouldCaptureUndo = undoable ?? isUndoableAction(name);
|
|
23660
|
+
const undoSnapshot = shouldCaptureUndo ? this.createUndoSnapshot(name) : null;
|
|
23361
23661
|
try {
|
|
23362
23662
|
const result = await executor();
|
|
23663
|
+
if (undoSnapshot && isUndoableResult(result)) {
|
|
23664
|
+
this.pushUndoSnapshot(undoSnapshot);
|
|
23665
|
+
}
|
|
23363
23666
|
this.finishAction(action.id, "completed", summarizeText(result));
|
|
23364
23667
|
this.publishTranscript({
|
|
23365
23668
|
source,
|
|
@@ -23386,6 +23689,21 @@ ${progress}
|
|
|
23386
23689
|
throw error;
|
|
23387
23690
|
}
|
|
23388
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
|
+
}
|
|
23389
23707
|
resolveApproval(approvalId, approved) {
|
|
23390
23708
|
const approval = this.state.supervisor.pendingApprovals.find(
|
|
23391
23709
|
(item) => item.id === approvalId
|