ai-browser-bridge 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/bridge.js +1113 -186
- package/dist/bridge.js.map +1 -1
- package/package.json +1 -1
package/dist/bridge.js
CHANGED
|
@@ -257,9 +257,9 @@ var ProviderConfig = Config.string("BRIDGE_PROVIDER").pipe(
|
|
|
257
257
|
|
|
258
258
|
// src/features/terminal/cliOperations.ts
|
|
259
259
|
import { execFile as execFile3 } from "child_process";
|
|
260
|
-
import { mkdir as
|
|
261
|
-
import { basename as basename3, dirname as
|
|
262
|
-
import { Schema as
|
|
260
|
+
import { mkdir as mkdir8, readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
|
|
261
|
+
import { basename as basename3, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute3, join as join12, relative as relative3, resolve as resolve8 } from "path";
|
|
262
|
+
import { Schema as Schema9 } from "effect";
|
|
263
263
|
import { render } from "ink";
|
|
264
264
|
import React from "react";
|
|
265
265
|
|
|
@@ -289,89 +289,6 @@ var isSameChatGptConversation = (pageUrl, targetIdOrUrl) => {
|
|
|
289
289
|
return targetId === currentId;
|
|
290
290
|
};
|
|
291
291
|
|
|
292
|
-
// src/features/providers/chatgpt/chatgptPage.ts
|
|
293
|
-
import { mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
294
|
-
import { homedir } from "os";
|
|
295
|
-
import path from "path";
|
|
296
|
-
|
|
297
|
-
// src/features/providers/providerErrors.ts
|
|
298
|
-
import { Data } from "effect";
|
|
299
|
-
var UnknownProviderError = class extends Data.TaggedError("UnknownProviderError") {
|
|
300
|
-
get message() {
|
|
301
|
-
return `Unknown provider "${this.value}". Valid providers: ${this.validProviders.join(", ")}.`;
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
var GuestSessionError = class extends Data.TaggedError("GuestSessionError") {
|
|
305
|
-
get message() {
|
|
306
|
-
return `${this.providerId} is not signed in: ${this.reason}`;
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
// src/features/providers/renderStallWatchdog.ts
|
|
311
|
-
var stallReloadWatchdogFor = (options = {}) => {
|
|
312
|
-
const stallMs = options.stallMs === void 0 ? RENDER_STALL_RELOAD_MS : options.stallMs;
|
|
313
|
-
const maxReloads = options.maxReloads === void 0 ? MAX_STALL_RELOADS : options.maxReloads;
|
|
314
|
-
const now = options.now === void 0 ? Date.now : options.now;
|
|
315
|
-
let lastProgressAt = now();
|
|
316
|
-
let reloadsUsed = 0;
|
|
317
|
-
return {
|
|
318
|
-
noteProgress() {
|
|
319
|
-
lastProgressAt = now();
|
|
320
|
-
},
|
|
321
|
-
async maybeReload(page) {
|
|
322
|
-
if (reloadsUsed >= maxReloads) return false;
|
|
323
|
-
if (now() - lastProgressAt < stallMs) return false;
|
|
324
|
-
try {
|
|
325
|
-
await page.reload({ waitUntil: "domcontentloaded" });
|
|
326
|
-
} catch {
|
|
327
|
-
}
|
|
328
|
-
if (options.waitAfterReload !== void 0) {
|
|
329
|
-
try {
|
|
330
|
-
await options.waitAfterReload(page);
|
|
331
|
-
} catch {
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
reloadsUsed += 1;
|
|
335
|
-
lastProgressAt = now();
|
|
336
|
-
if (options.onReload !== void 0) {
|
|
337
|
-
options.onReload(reloadsUsed);
|
|
338
|
-
}
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
// src/features/providers/streamingGuard.ts
|
|
345
|
-
var IDLE_POLL_MS = 300;
|
|
346
|
-
var IDLE_CONFIRMATIONS = 2;
|
|
347
|
-
var DEFAULT_IDLE_TIMEOUT_MS = 3e5;
|
|
348
|
-
var isResponseGenerating = async (page, stopSelector) => {
|
|
349
|
-
if (stopSelector.length === 0) return false;
|
|
350
|
-
try {
|
|
351
|
-
return await page.locator(stopSelector).first().isVisible();
|
|
352
|
-
} catch {
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
var waitForResponseIdle = async (page, stopSelector, timeoutMs = DEFAULT_IDLE_TIMEOUT_MS) => {
|
|
357
|
-
if (stopSelector.length === 0) return;
|
|
358
|
-
const startedAt = Date.now();
|
|
359
|
-
let clearStreak = 0;
|
|
360
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
361
|
-
if (await isResponseGenerating(page, stopSelector)) {
|
|
362
|
-
clearStreak = 0;
|
|
363
|
-
} else {
|
|
364
|
-
clearStreak += 1;
|
|
365
|
-
if (clearStreak >= IDLE_CONFIRMATIONS) return;
|
|
366
|
-
}
|
|
367
|
-
try {
|
|
368
|
-
await page.waitForTimeout(IDLE_POLL_MS);
|
|
369
|
-
} catch {
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
throw new Error("Timed out waiting for the current response to finish before sending.");
|
|
373
|
-
};
|
|
374
|
-
|
|
375
292
|
// src/features/providers/chatgpt/chatgptConversationSearch.ts
|
|
376
293
|
var CHATGPT_SEARCH = {
|
|
377
294
|
trigger: 'button[aria-label="Search"]',
|
|
@@ -420,19 +337,40 @@ var openChatSearch = async (page) => {
|
|
|
420
337
|
return dialog;
|
|
421
338
|
};
|
|
422
339
|
var searchEntriesIn = async (dialog) => {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
340
|
+
return dialog.locator(CHATGPT_SEARCH.resultLink).evaluateAll(
|
|
341
|
+
(links, titleSelector) => links.flatMap((link) => {
|
|
342
|
+
const href = link.getAttribute("href");
|
|
343
|
+
if (href === null) return [];
|
|
344
|
+
const titleNode = link.querySelector(titleSelector);
|
|
345
|
+
const fallbackTitle = link instanceof HTMLElement ? link.innerText.split("\n")[0] : "";
|
|
346
|
+
let rawTitle = fallbackTitle;
|
|
347
|
+
if (titleNode !== null && titleNode.textContent !== null) rawTitle = titleNode.textContent;
|
|
348
|
+
if (rawTitle === void 0) rawTitle = "";
|
|
349
|
+
const title = rawTitle.trim();
|
|
350
|
+
return title ? [{ href, title }] : [];
|
|
351
|
+
}),
|
|
352
|
+
CHATGPT_SEARCH.resultTitle
|
|
353
|
+
);
|
|
432
354
|
};
|
|
433
355
|
var searchEntriesSignature = async (dialog) => {
|
|
434
356
|
return JSON.stringify(await searchEntriesIn(dialog));
|
|
435
357
|
};
|
|
358
|
+
var acknowledgeChatGptHistoryRateLimit = async (page) => {
|
|
359
|
+
if (page.isClosed()) return false;
|
|
360
|
+
const modal = page.locator(CHATGPT_SEARCH.rateLimit).first();
|
|
361
|
+
if (!await modal.isVisible()) return false;
|
|
362
|
+
const acknowledge = modal.getByRole("button", { name: "Got it", exact: true }).first();
|
|
363
|
+
if (await acknowledge.isVisible()) {
|
|
364
|
+
await acknowledge.evaluate((button) => {
|
|
365
|
+
if (button instanceof HTMLElement) button.click();
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
await modal.waitFor({ state: "hidden", timeout: 3e3 });
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
};
|
|
436
374
|
var assertSearchAvailable = async (page) => {
|
|
437
375
|
if (!await page.locator(CHATGPT_SEARCH.rateLimit).isVisible()) return;
|
|
438
376
|
throw new Error(
|
|
@@ -528,6 +466,89 @@ var searchChatGptConversations = async (page, input) => {
|
|
|
528
466
|
}
|
|
529
467
|
};
|
|
530
468
|
|
|
469
|
+
// src/features/providers/chatgpt/chatgptPage.ts
|
|
470
|
+
import { mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
471
|
+
import { homedir } from "os";
|
|
472
|
+
import path from "path";
|
|
473
|
+
|
|
474
|
+
// src/features/providers/providerErrors.ts
|
|
475
|
+
import { Data } from "effect";
|
|
476
|
+
var UnknownProviderError = class extends Data.TaggedError("UnknownProviderError") {
|
|
477
|
+
get message() {
|
|
478
|
+
return `Unknown provider "${this.value}". Valid providers: ${this.validProviders.join(", ")}.`;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
var GuestSessionError = class extends Data.TaggedError("GuestSessionError") {
|
|
482
|
+
get message() {
|
|
483
|
+
return `${this.providerId} is not signed in: ${this.reason}`;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/features/providers/renderStallWatchdog.ts
|
|
488
|
+
var stallReloadWatchdogFor = (options = {}) => {
|
|
489
|
+
const stallMs = options.stallMs === void 0 ? RENDER_STALL_RELOAD_MS : options.stallMs;
|
|
490
|
+
const maxReloads = options.maxReloads === void 0 ? MAX_STALL_RELOADS : options.maxReloads;
|
|
491
|
+
const now = options.now === void 0 ? Date.now : options.now;
|
|
492
|
+
let lastProgressAt = now();
|
|
493
|
+
let reloadsUsed = 0;
|
|
494
|
+
return {
|
|
495
|
+
noteProgress() {
|
|
496
|
+
lastProgressAt = now();
|
|
497
|
+
},
|
|
498
|
+
async maybeReload(page) {
|
|
499
|
+
if (reloadsUsed >= maxReloads) return false;
|
|
500
|
+
if (now() - lastProgressAt < stallMs) return false;
|
|
501
|
+
try {
|
|
502
|
+
await page.reload({ waitUntil: "domcontentloaded" });
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
if (options.waitAfterReload !== void 0) {
|
|
506
|
+
try {
|
|
507
|
+
await options.waitAfterReload(page);
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
reloadsUsed += 1;
|
|
512
|
+
lastProgressAt = now();
|
|
513
|
+
if (options.onReload !== void 0) {
|
|
514
|
+
options.onReload(reloadsUsed);
|
|
515
|
+
}
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// src/features/providers/streamingGuard.ts
|
|
522
|
+
var IDLE_POLL_MS = 300;
|
|
523
|
+
var IDLE_CONFIRMATIONS = 2;
|
|
524
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 3e5;
|
|
525
|
+
var isResponseGenerating = async (page, stopSelector) => {
|
|
526
|
+
if (stopSelector.length === 0) return false;
|
|
527
|
+
try {
|
|
528
|
+
return await page.locator(stopSelector).first().isVisible();
|
|
529
|
+
} catch {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
var waitForResponseIdle = async (page, stopSelector, timeoutMs = DEFAULT_IDLE_TIMEOUT_MS) => {
|
|
534
|
+
if (stopSelector.length === 0) return;
|
|
535
|
+
const startedAt = Date.now();
|
|
536
|
+
let clearStreak = 0;
|
|
537
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
538
|
+
if (await isResponseGenerating(page, stopSelector)) {
|
|
539
|
+
clearStreak = 0;
|
|
540
|
+
} else {
|
|
541
|
+
clearStreak += 1;
|
|
542
|
+
if (clearStreak >= IDLE_CONFIRMATIONS) return;
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
await page.waitForTimeout(IDLE_POLL_MS);
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
throw new Error("Timed out waiting for the current response to finish before sending.");
|
|
550
|
+
};
|
|
551
|
+
|
|
531
552
|
// src/features/providers/chatgpt/chatgptPage.ts
|
|
532
553
|
var EDIT_BUTTON_SELECTORS = [
|
|
533
554
|
'button[data-testid="edit-turn-button"]',
|
|
@@ -1835,8 +1856,8 @@ var deleteConnectorAppBySummary = async (ctx) => {
|
|
|
1835
1856
|
if (entry.name !== ctx.target.name) continue;
|
|
1836
1857
|
await entry.button.click({ timeout: 3e3, force: true });
|
|
1837
1858
|
await ctx.page.waitForTimeout(1e3);
|
|
1838
|
-
const
|
|
1839
|
-
if (!
|
|
1859
|
+
const open2 = await readOpenConnectorSummary({ page: ctx.page });
|
|
1860
|
+
if (!open2 || !sameConnectorApp({ a: open2, b: ctx.target })) continue;
|
|
1840
1861
|
return deleteOpenConnectorIfPresent({ page: ctx.page });
|
|
1841
1862
|
}
|
|
1842
1863
|
}
|
|
@@ -2414,19 +2435,122 @@ var newConversation = async (page) => {
|
|
|
2414
2435
|
await page.goto("https://chatgpt.com/");
|
|
2415
2436
|
await page.waitForSelector("#prompt-textarea, [contenteditable]", { timeout: 3e4 });
|
|
2416
2437
|
};
|
|
2438
|
+
var parseChatGptSidebarConversationLink = (link, options = {}) => {
|
|
2439
|
+
const href = link.href === null ? "" : link.href.trim();
|
|
2440
|
+
const title = link.title.trim();
|
|
2441
|
+
if (!href || !title) return null;
|
|
2442
|
+
const sidebarLabel = link.ariaLabel === null ? "" : link.ariaLabel;
|
|
2443
|
+
const outsideLooseChats = href.includes("/g/") || /\bchat in project\b/i.test(sidebarLabel) || /\bpinned conversation\b/i.test(sidebarLabel);
|
|
2444
|
+
if (options.orphans && outsideLooseChats) return null;
|
|
2445
|
+
const parsedUrl = new URL(href, "https://chatgpt.com");
|
|
2446
|
+
const pathParts = parsedUrl.pathname.split("/").filter(Boolean);
|
|
2447
|
+
const lastPathPart = pathParts.pop();
|
|
2448
|
+
const id2 = lastPathPart === void 0 ? "" : lastPathPart;
|
|
2449
|
+
if (!id2) return null;
|
|
2450
|
+
return { id: id2, title, url: parsedUrl.toString() };
|
|
2451
|
+
};
|
|
2417
2452
|
var parseSidebarLink = async (ctx) => {
|
|
2418
2453
|
const href = await ctx.link.getAttribute("href");
|
|
2419
2454
|
const title = await ctx.link.innerText();
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2455
|
+
const ariaLabel = await ctx.link.getAttribute("aria-label");
|
|
2456
|
+
return parseChatGptSidebarConversationLink({ href, title, ariaLabel }, { orphans: ctx.orphans });
|
|
2457
|
+
};
|
|
2458
|
+
var scanOrphanSidebarConversations = async (page) => {
|
|
2459
|
+
await page.locator(`${SELECTORS.sidebarConversation}:not([aria-label*="pinned conversation" i])`).first().waitFor({ state: "attached", timeout: 1e4 }).catch(() => {
|
|
2460
|
+
});
|
|
2461
|
+
const sidebar = page.locator('nav[aria-label="Chat history"]').first();
|
|
2462
|
+
if (await sidebar.count() === 0) return [];
|
|
2463
|
+
const collectScrollAndReadState = (element) => {
|
|
2464
|
+
const scan = element.__bridgeOrphanScan;
|
|
2465
|
+
if (!scan) throw new Error("ChatGPT sidebar scan was not initialized.");
|
|
2466
|
+
const sidebarRect = element.getBoundingClientRect();
|
|
2467
|
+
for (const link of element.querySelectorAll('a[href^="/c/"]')) {
|
|
2468
|
+
const linkRect = link.getBoundingClientRect();
|
|
2469
|
+
if (linkRect.bottom < sidebarRect.top || linkRect.top > sidebarRect.bottom) continue;
|
|
2470
|
+
const href = link.getAttribute("href");
|
|
2471
|
+
if (!href) continue;
|
|
2472
|
+
let title = link.innerText;
|
|
2473
|
+
if (!title) {
|
|
2474
|
+
const textContent = link.textContent;
|
|
2475
|
+
title = textContent === null ? "" : textContent;
|
|
2476
|
+
}
|
|
2477
|
+
scan.links.set(href, {
|
|
2478
|
+
href,
|
|
2479
|
+
title,
|
|
2480
|
+
ariaLabel: link.getAttribute("aria-label")
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
const state = {
|
|
2484
|
+
size: scan.links.size,
|
|
2485
|
+
scrollHeight: element.scrollHeight,
|
|
2486
|
+
atBottom: element.scrollTop + element.clientHeight >= element.scrollHeight - 4
|
|
2487
|
+
};
|
|
2488
|
+
const step = Math.max(Math.floor(element.clientHeight * 0.8), 320);
|
|
2489
|
+
element.scrollTop = Math.min(element.scrollTop + step, element.scrollHeight);
|
|
2490
|
+
return state;
|
|
2491
|
+
};
|
|
2492
|
+
await sidebar.evaluate((element) => {
|
|
2493
|
+
element.__bridgeOrphanScan = {
|
|
2494
|
+
originalScrollTop: element.scrollTop,
|
|
2495
|
+
links: /* @__PURE__ */ new Map()
|
|
2496
|
+
};
|
|
2497
|
+
element.scrollTop = 0;
|
|
2498
|
+
});
|
|
2499
|
+
await new Promise((resolve9) => setTimeout(resolve9, 250));
|
|
2500
|
+
let stableBottomPasses = 0;
|
|
2501
|
+
let links = [];
|
|
2502
|
+
try {
|
|
2503
|
+
let reachedStableBottom = false;
|
|
2504
|
+
let before = await sidebar.evaluate(collectScrollAndReadState);
|
|
2505
|
+
for (let pass = 0; pass < 240; pass += 1) {
|
|
2506
|
+
await new Promise((resolve9) => setTimeout(resolve9, 300));
|
|
2507
|
+
const after = await sidebar.evaluate(collectScrollAndReadState);
|
|
2508
|
+
const unchanged = after.atBottom && after.size === before.size && after.scrollHeight <= before.scrollHeight + 1;
|
|
2509
|
+
stableBottomPasses = unchanged ? stableBottomPasses + 1 : 0;
|
|
2510
|
+
if (stableBottomPasses >= 8) {
|
|
2511
|
+
reachedStableBottom = true;
|
|
2512
|
+
break;
|
|
2513
|
+
}
|
|
2514
|
+
before = after;
|
|
2515
|
+
}
|
|
2516
|
+
if (!reachedStableBottom) {
|
|
2517
|
+
throw new Error("ChatGPT sidebar scan did not reach the end of the chat history.");
|
|
2518
|
+
}
|
|
2519
|
+
links = await sidebar.evaluate((element) => {
|
|
2520
|
+
const scan = element.__bridgeOrphanScan;
|
|
2521
|
+
return scan ? [...scan.links.values()] : [];
|
|
2522
|
+
});
|
|
2523
|
+
} finally {
|
|
2524
|
+
await sidebar.evaluate((element) => {
|
|
2525
|
+
const scan = element.__bridgeOrphanScan;
|
|
2526
|
+
if (scan) element.scrollTop = scan.originalScrollTop;
|
|
2527
|
+
delete element.__bridgeOrphanScan;
|
|
2528
|
+
}).catch(() => {
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
const conversations = /* @__PURE__ */ new Map();
|
|
2532
|
+
for (const link of links) {
|
|
2533
|
+
const conversation = parseChatGptSidebarConversationLink(link, { orphans: true });
|
|
2534
|
+
if (conversation) conversations.set(conversation.id, conversation);
|
|
2535
|
+
}
|
|
2536
|
+
return [...conversations.values()];
|
|
2424
2537
|
};
|
|
2425
|
-
var
|
|
2538
|
+
var readAllOrphanSidebarConversations = async (page) => {
|
|
2539
|
+
const scanPage = await page.context().newPage();
|
|
2540
|
+
try {
|
|
2541
|
+
await scanPage.goto("https://chatgpt.com/", { waitUntil: "domcontentloaded" });
|
|
2542
|
+
return await scanOrphanSidebarConversations(scanPage);
|
|
2543
|
+
} finally {
|
|
2544
|
+
await scanPage.close().catch(() => {
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
};
|
|
2548
|
+
var readSidebarConversations = async (page, options = {}) => {
|
|
2549
|
+
if (options.orphans) return readAllOrphanSidebarConversations(page);
|
|
2426
2550
|
const links = await page.locator(SELECTORS.sidebarConversation).all();
|
|
2427
2551
|
const conversations = [];
|
|
2428
2552
|
for (const link of links) {
|
|
2429
|
-
const entry = await parseSidebarLink({ link });
|
|
2553
|
+
const entry = await parseSidebarLink({ link, orphans: Boolean(options.orphans) });
|
|
2430
2554
|
if (entry) conversations.push(entry);
|
|
2431
2555
|
}
|
|
2432
2556
|
return conversations;
|
|
@@ -3076,10 +3200,20 @@ var WORKSPACE = {
|
|
|
3076
3200
|
newProjectButton: 'button[aria-label="New project"]',
|
|
3077
3201
|
projectNameInput: 'input[name="projectName"]',
|
|
3078
3202
|
projectFolderIcon: '[data-testid="project-folder-icon"]',
|
|
3079
|
-
|
|
3203
|
+
projectDirectoryRow: '[data-testid="project-directory-scroll-root"] [role="row"]:has([role="gridcell"])',
|
|
3204
|
+
projectConversationLink: 'main a[href*="/c/"]',
|
|
3205
|
+
chatLink: 'nav a[href*="/c/"]',
|
|
3080
3206
|
activeConversationMenu: '[data-testid="conversation-options-button"]',
|
|
3081
3207
|
menuItem: '[role="menuitem"]'
|
|
3082
3208
|
};
|
|
3209
|
+
var projectNameFromDirectoryRowText = (text) => {
|
|
3210
|
+
const [name, detail] = text.split("\n");
|
|
3211
|
+
if (name === void 0) return null;
|
|
3212
|
+
const projectName = name.trim();
|
|
3213
|
+
if (!projectName) return null;
|
|
3214
|
+
if (projectName === "Name" && detail !== void 0 && detail.trim() === "Modified") return null;
|
|
3215
|
+
return projectName;
|
|
3216
|
+
};
|
|
3083
3217
|
var PROJECT_ROWS_SOURCE = String.raw`
|
|
3084
3218
|
(() => {
|
|
3085
3219
|
const clean = (s) => (s || "").replace(/\s+/g, " ").trim();
|
|
@@ -3121,13 +3255,146 @@ var chatGptProjectRemovalState = (namedRemoval, genericRemoval) => {
|
|
|
3121
3255
|
if (genericRemoval) return "current-project-unknown";
|
|
3122
3256
|
return "not-filed";
|
|
3123
3257
|
};
|
|
3258
|
+
var chatGptProjectNameFromConversationAriaLabel = (label) => {
|
|
3259
|
+
const marker = ", chat in project ";
|
|
3260
|
+
const markerIndex = label.lastIndexOf(marker);
|
|
3261
|
+
if (markerIndex < 0) return null;
|
|
3262
|
+
const project = label.slice(markerIndex + marker.length).trim();
|
|
3263
|
+
if (!project) return null;
|
|
3264
|
+
return project;
|
|
3265
|
+
};
|
|
3124
3266
|
var listProjects = async (page) => {
|
|
3125
3267
|
await ensureOnProjectsPage(page);
|
|
3268
|
+
const directoryRows = page.locator(WORKSPACE.projectDirectoryRow);
|
|
3269
|
+
await directoryRows.first().waitFor({ timeout: 5e3 }).catch(() => {
|
|
3270
|
+
});
|
|
3271
|
+
const directoryNames = (await directoryRows.allInnerTexts()).map(projectNameFromDirectoryRowText).filter((name) => name !== null);
|
|
3272
|
+
if (directoryNames.length > 0) {
|
|
3273
|
+
return Array.from(new Set(directoryNames)).map((name) => ({ name }));
|
|
3274
|
+
}
|
|
3126
3275
|
await page.locator(WORKSPACE.projectFolderIcon).first().waitFor({ timeout: 5e3 }).catch(() => {
|
|
3127
3276
|
});
|
|
3128
3277
|
const names = await page.evaluate(PROJECT_ROWS_SOURCE);
|
|
3129
3278
|
return names.map((name) => ({ name }));
|
|
3130
3279
|
};
|
|
3280
|
+
var openProject = async (page, project) => {
|
|
3281
|
+
await ensureOnProjectsPage(page);
|
|
3282
|
+
const directoryRows = page.locator(WORKSPACE.projectDirectoryRow);
|
|
3283
|
+
await directoryRows.first().waitFor({ state: "visible", timeout: 8e3 }).catch(() => {
|
|
3284
|
+
});
|
|
3285
|
+
const directoryIndex = (await directoryRows.allInnerTexts()).findIndex(
|
|
3286
|
+
(rowText) => projectNameFromDirectoryRowText(rowText) === project
|
|
3287
|
+
);
|
|
3288
|
+
if (directoryIndex >= 0) {
|
|
3289
|
+
await directoryRows.nth(directoryIndex).evaluate((row) => {
|
|
3290
|
+
if (row instanceof HTMLElement) row.click();
|
|
3291
|
+
});
|
|
3292
|
+
const heading = page.locator("main").getByText(project, { exact: true }).first();
|
|
3293
|
+
try {
|
|
3294
|
+
await heading.waitFor({ state: "visible", timeout: 8e3 });
|
|
3295
|
+
return true;
|
|
3296
|
+
} catch {
|
|
3297
|
+
return false;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
try {
|
|
3301
|
+
await page.locator(WORKSPACE.projectFolderIcon).first().waitFor({
|
|
3302
|
+
state: "visible",
|
|
3303
|
+
timeout: 8e3
|
|
3304
|
+
});
|
|
3305
|
+
} catch {
|
|
3306
|
+
return false;
|
|
3307
|
+
}
|
|
3308
|
+
const projectRows = page.locator(WORKSPACE.projectFolderIcon).locator('xpath=ancestor::*[@role="row" or self::a or self::li][1]');
|
|
3309
|
+
const rowCount = await projectRows.count();
|
|
3310
|
+
for (let index = 0; index < rowCount; index += 1) {
|
|
3311
|
+
const projectRow = projectRows.nth(index);
|
|
3312
|
+
const [projectName] = (await projectRow.innerText()).split("\n");
|
|
3313
|
+
if (projectName?.trim() !== project) continue;
|
|
3314
|
+
await projectRow.evaluate((row) => {
|
|
3315
|
+
if (row instanceof HTMLElement) row.click();
|
|
3316
|
+
});
|
|
3317
|
+
const heading = page.locator("main").getByText(project, { exact: true }).first();
|
|
3318
|
+
try {
|
|
3319
|
+
await heading.waitFor({ state: "visible", timeout: 8e3 });
|
|
3320
|
+
return true;
|
|
3321
|
+
} catch {
|
|
3322
|
+
return false;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
return false;
|
|
3326
|
+
};
|
|
3327
|
+
var projectPageContainsConversation = async (page, conversation) => {
|
|
3328
|
+
const identifier = stripConversationId(conversation);
|
|
3329
|
+
const identifierIsId = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(identifier);
|
|
3330
|
+
for (let poll = 0; poll < 50; poll += 1) {
|
|
3331
|
+
const found = await page.locator(WORKSPACE.projectConversationLink).evaluateAll(
|
|
3332
|
+
(links, target) => {
|
|
3333
|
+
for (const link of links) {
|
|
3334
|
+
const href = link.getAttribute("href");
|
|
3335
|
+
if (href !== null && target.identifierIsId && href.includes(`/c/${target.identifier}`)) {
|
|
3336
|
+
return true;
|
|
3337
|
+
}
|
|
3338
|
+
const text = link instanceof HTMLElement ? link.innerText : link.textContent;
|
|
3339
|
+
let normalizedText = "";
|
|
3340
|
+
if (text !== null) normalizedText = text;
|
|
3341
|
+
const title = normalizedText.split("\n")[0]?.trim();
|
|
3342
|
+
if (!target.identifierIsId && title === target.identifier) return true;
|
|
3343
|
+
}
|
|
3344
|
+
return false;
|
|
3345
|
+
},
|
|
3346
|
+
{ identifier, identifierIsId }
|
|
3347
|
+
);
|
|
3348
|
+
if (found) return true;
|
|
3349
|
+
const loadMore = page.getByText("Load more conversations", { exact: true }).first();
|
|
3350
|
+
if (!await loadMore.isVisible()) {
|
|
3351
|
+
if (poll >= 19) return false;
|
|
3352
|
+
await page.waitForTimeout(500);
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
await loadMore.evaluate((label) => {
|
|
3356
|
+
let clickTarget = label;
|
|
3357
|
+
const button = label.closest("button");
|
|
3358
|
+
if (button !== null) clickTarget = button;
|
|
3359
|
+
if (clickTarget instanceof HTMLElement) clickTarget.click();
|
|
3360
|
+
});
|
|
3361
|
+
await page.waitForTimeout(700);
|
|
3362
|
+
}
|
|
3363
|
+
return false;
|
|
3364
|
+
};
|
|
3365
|
+
var projectContainsConversation = async (page, project, conversation) => {
|
|
3366
|
+
const originalUrl = page.url();
|
|
3367
|
+
try {
|
|
3368
|
+
if (!await openProject(page, project)) return false;
|
|
3369
|
+
await page.waitForTimeout(800);
|
|
3370
|
+
return await projectPageContainsConversation(page, conversation);
|
|
3371
|
+
} finally {
|
|
3372
|
+
if (page.url() !== originalUrl) await gotoStable(page, originalUrl);
|
|
3373
|
+
}
|
|
3374
|
+
};
|
|
3375
|
+
var conversationLinkIdentifiesProject = async (page, conversation, project) => {
|
|
3376
|
+
const identifier = stripConversationId(conversation);
|
|
3377
|
+
const identifierIsId = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(identifier);
|
|
3378
|
+
let links = page.locator(WORKSPACE.chatLink);
|
|
3379
|
+
if (identifierIsId) links = page.locator(`nav a[href*="/c/${identifier}"]`);
|
|
3380
|
+
else links = links.filter({ hasText: exactName(identifier) });
|
|
3381
|
+
const count = await links.count();
|
|
3382
|
+
for (let index = 0; index < count; index += 1) {
|
|
3383
|
+
const ariaLabel = await links.nth(index).getAttribute("aria-label");
|
|
3384
|
+
if (ariaLabel === null) continue;
|
|
3385
|
+
if (chatGptProjectNameFromConversationAriaLabel(ariaLabel) === project) return true;
|
|
3386
|
+
}
|
|
3387
|
+
return false;
|
|
3388
|
+
};
|
|
3389
|
+
var activeConversationHeaderIdentifiesProject = async (page, project) => {
|
|
3390
|
+
if (!/\/g\/g-p-[^/]+\/c\//i.test(page.url())) return false;
|
|
3391
|
+
try {
|
|
3392
|
+
await page.locator("header").getByText(project, { exact: true }).first().waitFor({ state: "visible", timeout: 2e3 });
|
|
3393
|
+
return true;
|
|
3394
|
+
} catch {
|
|
3395
|
+
return false;
|
|
3396
|
+
}
|
|
3397
|
+
};
|
|
3131
3398
|
var gotoStable = async (page, url) => {
|
|
3132
3399
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3133
3400
|
try {
|
|
@@ -3277,38 +3544,65 @@ var deleteProject = async (page, project) => {
|
|
|
3277
3544
|
};
|
|
3278
3545
|
var moveChatToProject = async (page, input) => {
|
|
3279
3546
|
const base = { chat: input.chat, project: input.project, moved: false };
|
|
3547
|
+
const conversationId = stripConversationId(input.chat);
|
|
3548
|
+
const identifierIsId = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(conversationId);
|
|
3549
|
+
if (identifierIsId && chatGptConversationIdFromUrl(page.url()) !== conversationId) {
|
|
3550
|
+
await gotoStable(page, `https://chatgpt.com/c/${conversationId}`);
|
|
3551
|
+
await waitForActiveConversationMenu(page, conversationId);
|
|
3552
|
+
}
|
|
3553
|
+
if (await activeConversationHeaderIdentifiesProject(page, input.project) || await conversationLinkIdentifiesProject(page, input.chat, input.project)) {
|
|
3554
|
+
return {
|
|
3555
|
+
...base,
|
|
3556
|
+
alreadyFiled: true,
|
|
3557
|
+
reason: `already in project "${input.project}"`
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3280
3560
|
if (!await openConversationMenu(page, input.chat)) {
|
|
3281
3561
|
return { ...base, reason: "could not open the chat \u22EF menu" };
|
|
3282
3562
|
}
|
|
3283
3563
|
const visibleMenuItems2 = page.locator(`${WORKSPACE.menuItem}:visible`);
|
|
3284
3564
|
const moveItem = visibleMenuItems2.filter({ hasText: /^Move to project$/i }).first();
|
|
3285
|
-
|
|
3565
|
+
try {
|
|
3566
|
+
await moveItem.waitFor({ state: "visible", timeout: 5e3 });
|
|
3567
|
+
} catch {
|
|
3286
3568
|
await dismissMenu(page);
|
|
3287
3569
|
return { ...base, reason: "no 'Move to project' option (GPT- or project-owned chat)" };
|
|
3288
3570
|
}
|
|
3289
3571
|
const targetOf = () => page.locator(`${WORKSPACE.menuItem}:visible`).filter({ hasText: exactName(input.project) }).first();
|
|
3572
|
+
const removalStateOf = async () => {
|
|
3573
|
+
const namedRemoval = page.locator(`${WORKSPACE.menuItem}:visible`).filter({
|
|
3574
|
+
hasText: new RegExp(`^Remove from ${escapeRegExp(input.project)}$`, "i")
|
|
3575
|
+
});
|
|
3576
|
+
const genericRemoval = page.locator(`${WORKSPACE.menuItem}:visible`).filter({ hasText: /^Remove from project$/i });
|
|
3577
|
+
return chatGptProjectRemovalState(
|
|
3578
|
+
await namedRemoval.count() > 0,
|
|
3579
|
+
await genericRemoval.count() > 0
|
|
3580
|
+
);
|
|
3581
|
+
};
|
|
3582
|
+
const newProjectItem = page.locator(`${WORKSPACE.menuItem}:visible`).filter({ hasText: /^New project$/i });
|
|
3290
3583
|
await moveItem.hover().catch(() => {
|
|
3291
3584
|
});
|
|
3292
3585
|
let target = targetOf();
|
|
3293
3586
|
let visible = false;
|
|
3294
|
-
|
|
3295
|
-
|
|
3587
|
+
let removalState = "not-filed";
|
|
3588
|
+
let submenuVisible = false;
|
|
3589
|
+
for (let attempt = 0; attempt < 2 && !visible && removalState === "not-filed"; attempt += 1) {
|
|
3590
|
+
if (attempt > 0 && !submenuVisible) await moveItem.click().catch(() => {
|
|
3296
3591
|
});
|
|
3297
|
-
for (let poll = 0; poll <
|
|
3298
|
-
|
|
3299
|
-
|
|
3592
|
+
for (let poll = 0; poll < 40 && !visible && removalState === "not-filed"; poll += 1) {
|
|
3593
|
+
try {
|
|
3594
|
+
await target.waitFor({ state: "visible", timeout: 350 });
|
|
3595
|
+
visible = true;
|
|
3596
|
+
} catch {
|
|
3597
|
+
visible = false;
|
|
3598
|
+
}
|
|
3599
|
+
removalState = await removalStateOf();
|
|
3600
|
+
submenuVisible = submenuVisible || await newProjectItem.count() > 0;
|
|
3601
|
+
if (!visible && removalState === "not-filed") await page.waitForTimeout(150);
|
|
3300
3602
|
target = targetOf();
|
|
3301
3603
|
}
|
|
3302
3604
|
}
|
|
3303
3605
|
if (!visible) {
|
|
3304
|
-
const namedRemoval = page.locator(`${WORKSPACE.menuItem}:visible`).filter({
|
|
3305
|
-
hasText: new RegExp(`^Remove from ${escapeRegExp(input.project)}$`, "i")
|
|
3306
|
-
});
|
|
3307
|
-
const genericRemoval = page.locator(`${WORKSPACE.menuItem}:visible`).filter({ hasText: /^Remove from project$/i });
|
|
3308
|
-
const removalState = chatGptProjectRemovalState(
|
|
3309
|
-
await namedRemoval.count() > 0,
|
|
3310
|
-
await genericRemoval.count() > 0
|
|
3311
|
-
);
|
|
3312
3606
|
await dismissMenu(page);
|
|
3313
3607
|
if (removalState === "already-filed") {
|
|
3314
3608
|
return {
|
|
@@ -3318,6 +3612,16 @@ var moveChatToProject = async (page, input) => {
|
|
|
3318
3612
|
};
|
|
3319
3613
|
}
|
|
3320
3614
|
if (removalState === "current-project-unknown") {
|
|
3615
|
+
let confirmed2 = await conversationLinkIdentifiesProject(page, input.chat, input.project);
|
|
3616
|
+
if (!confirmed2)
|
|
3617
|
+
confirmed2 = await projectContainsConversation(page, input.project, input.chat);
|
|
3618
|
+
if (confirmed2) {
|
|
3619
|
+
return {
|
|
3620
|
+
...base,
|
|
3621
|
+
alreadyFiled: true,
|
|
3622
|
+
reason: `already in project "${input.project}"`
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3321
3625
|
return {
|
|
3322
3626
|
...base,
|
|
3323
3627
|
reason: `chat is already in a project, but ChatGPT did not identify which one`
|
|
@@ -3336,6 +3640,16 @@ var moveChatToProject = async (page, input) => {
|
|
|
3336
3640
|
return { ...base, reason: `could not select project "${input.project}"` };
|
|
3337
3641
|
}
|
|
3338
3642
|
await page.waitForTimeout(800);
|
|
3643
|
+
let confirmed = await activeConversationHeaderIdentifiesProject(page, input.project);
|
|
3644
|
+
if (!confirmed)
|
|
3645
|
+
confirmed = await conversationLinkIdentifiesProject(page, input.chat, input.project);
|
|
3646
|
+
if (!confirmed) confirmed = await projectContainsConversation(page, input.project, input.chat);
|
|
3647
|
+
if (!confirmed) {
|
|
3648
|
+
return {
|
|
3649
|
+
...base,
|
|
3650
|
+
reason: `ChatGPT did not confirm the move into project "${input.project}"`
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3339
3653
|
return { ...base, moved: true };
|
|
3340
3654
|
};
|
|
3341
3655
|
var archiveChat = async (page, chat) => {
|
|
@@ -3354,24 +3668,19 @@ var archiveChat = async (page, chat) => {
|
|
|
3354
3668
|
};
|
|
3355
3669
|
var findChatRow = async (page, chat) => {
|
|
3356
3670
|
const id2 = stripConversationId(chat);
|
|
3357
|
-
const byHref = page.locator(`nav a[href
|
|
3671
|
+
const byHref = page.locator(`nav a[href*="/c/${id2}"]`).first();
|
|
3358
3672
|
const byTitle = page.locator(WORKSPACE.chatLink, { hasText: chat }).first();
|
|
3359
3673
|
if (await byHref.count() > 0) return byHref;
|
|
3360
3674
|
const looksLikeId = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(id2);
|
|
3361
3675
|
if (looksLikeId && !page.url().includes(`/c/${id2}`)) {
|
|
3362
3676
|
await gotoStable(page, `https://chatgpt.com/c/${id2}`);
|
|
3363
|
-
await page.waitForTimeout(700);
|
|
3364
3677
|
if (await byHref.count() > 0) return byHref;
|
|
3678
|
+
if (await waitForActiveConversationMenu(page, id2)) return null;
|
|
3365
3679
|
}
|
|
3366
3680
|
if (await byTitle.count() > 0) return byTitle;
|
|
3367
|
-
|
|
3368
|
-
if (activeConversationId === id2) {
|
|
3369
|
-
const activeMenu = page.locator(WORKSPACE.activeConversationMenu).first();
|
|
3370
|
-
const activeMenuVisible = await activeMenu.waitFor({ state: "visible", timeout: 2e3 }).then(() => true).catch(() => false);
|
|
3371
|
-
if (activeMenuVisible) return null;
|
|
3372
|
-
}
|
|
3681
|
+
if (looksLikeId && await waitForActiveConversationMenu(page, id2)) return null;
|
|
3373
3682
|
await page.evaluate(() => {
|
|
3374
|
-
const anchor = document.querySelector('nav a[href
|
|
3683
|
+
const anchor = document.querySelector('nav a[href*="/c/"]');
|
|
3375
3684
|
let el = anchor ? anchor.closest("nav") : document.querySelector("nav");
|
|
3376
3685
|
while (el && el.scrollHeight <= el.clientHeight + 20) el = el.parentElement;
|
|
3377
3686
|
if (el !== null) {
|
|
@@ -3387,7 +3696,7 @@ var findChatRow = async (page, chat) => {
|
|
|
3387
3696
|
if (await byHref.count() > 0) return byHref;
|
|
3388
3697
|
if (await byTitle.count() > 0) return byTitle;
|
|
3389
3698
|
await page.evaluate(() => {
|
|
3390
|
-
const anchor = document.querySelector('nav a[href
|
|
3699
|
+
const anchor = document.querySelector('nav a[href*="/c/"]');
|
|
3391
3700
|
let el = anchor ? anchor.closest("nav") : document.querySelector("nav");
|
|
3392
3701
|
while (el && el.scrollHeight <= el.clientHeight + 20) el = el.parentElement;
|
|
3393
3702
|
if (el !== null) {
|
|
@@ -3428,13 +3737,25 @@ var openChatMenu = async (page, row) => {
|
|
|
3428
3737
|
}
|
|
3429
3738
|
};
|
|
3430
3739
|
var openConversationMenu = async (page, chat) => {
|
|
3740
|
+
let targetConversationId = stripConversationId(chat);
|
|
3741
|
+
const targetLooksLikeId = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(targetConversationId);
|
|
3742
|
+
if (targetLooksLikeId && chatGptConversationIdFromUrl(page.url()) === targetConversationId && await openActiveConversationMenu(page, targetConversationId)) {
|
|
3743
|
+
return true;
|
|
3744
|
+
}
|
|
3431
3745
|
const row = await findChatRow(page, chat);
|
|
3432
3746
|
if (row !== null) return openChatMenu(page, row);
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3747
|
+
if (!targetLooksLikeId) {
|
|
3748
|
+
const results = await searchChatGptConversations(page, { query: chat, limit: 20 });
|
|
3749
|
+
const exactResult = results.find((result) => result.title.trim() === chat.trim());
|
|
3750
|
+
if (exactResult === void 0) return false;
|
|
3751
|
+
targetConversationId = exactResult.id;
|
|
3752
|
+
await gotoStable(page, exactResult.url);
|
|
3753
|
+
}
|
|
3754
|
+
return openActiveConversationMenu(page, targetConversationId);
|
|
3755
|
+
};
|
|
3756
|
+
var openActiveConversationMenu = async (page, targetConversationId) => {
|
|
3757
|
+
if (!await waitForActiveConversationMenu(page, targetConversationId)) return false;
|
|
3436
3758
|
const trigger = page.locator(WORKSPACE.activeConversationMenu).first();
|
|
3437
|
-
if (!await trigger.isVisible()) return false;
|
|
3438
3759
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3439
3760
|
if (attempt === 0) await trigger.click().catch(() => {
|
|
3440
3761
|
});
|
|
@@ -3451,11 +3772,32 @@ var openConversationMenu = async (page, chat) => {
|
|
|
3451
3772
|
).catch(() => {
|
|
3452
3773
|
});
|
|
3453
3774
|
}
|
|
3454
|
-
|
|
3775
|
+
let menuVisible = false;
|
|
3776
|
+
try {
|
|
3777
|
+
await page.locator(`${WORKSPACE.menuItem}:visible`).first().waitFor({ state: "visible", timeout: 4e3 });
|
|
3778
|
+
menuVisible = true;
|
|
3779
|
+
} catch {
|
|
3780
|
+
menuVisible = false;
|
|
3781
|
+
}
|
|
3455
3782
|
if (menuVisible) return true;
|
|
3456
3783
|
}
|
|
3457
3784
|
return false;
|
|
3458
3785
|
};
|
|
3786
|
+
var waitForActiveConversationMenu = async (page, conversationId) => {
|
|
3787
|
+
const trigger = page.locator(WORKSPACE.activeConversationMenu).first();
|
|
3788
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
3789
|
+
const activeConversationId = chatGptConversationIdFromUrl(page.url());
|
|
3790
|
+
if (activeConversationId === conversationId) {
|
|
3791
|
+
try {
|
|
3792
|
+
await trigger.waitFor({ state: "visible", timeout: 500 });
|
|
3793
|
+
return true;
|
|
3794
|
+
} catch {
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
await page.waitForTimeout(250);
|
|
3798
|
+
}
|
|
3799
|
+
return false;
|
|
3800
|
+
};
|
|
3459
3801
|
var dismissMenu = async (page) => {
|
|
3460
3802
|
await page.keyboard.press("Escape").catch(() => {
|
|
3461
3803
|
});
|
|
@@ -3532,16 +3874,16 @@ var listClips = async (page) => {
|
|
|
3532
3874
|
return clips;
|
|
3533
3875
|
};
|
|
3534
3876
|
var downloadClip = async (page, clipId, outDir) => {
|
|
3535
|
-
const { mkdir:
|
|
3536
|
-
const { join:
|
|
3877
|
+
const { mkdir: mkdir9, writeFile: writeFile7 } = await import("fs/promises");
|
|
3878
|
+
const { join: join13 } = await import("path");
|
|
3537
3879
|
const clipResponse = await page.request.get(clipUrlFromId(clipId));
|
|
3538
3880
|
if (!clipResponse.ok()) {
|
|
3539
3881
|
throw new Error(`Flow clip download failed (${clipResponse.status()}) for ${clipId}`);
|
|
3540
3882
|
}
|
|
3541
3883
|
const clipBytes = await clipResponse.body();
|
|
3542
|
-
await
|
|
3543
|
-
const dest =
|
|
3544
|
-
await
|
|
3884
|
+
await mkdir9(outDir, { recursive: true });
|
|
3885
|
+
const dest = join13(outDir, `${clipId}.mp4`);
|
|
3886
|
+
await writeFile7(dest, clipBytes);
|
|
3545
3887
|
return dest;
|
|
3546
3888
|
};
|
|
3547
3889
|
var tagAndClick = async (input) => {
|
|
@@ -3764,8 +4106,8 @@ var CLIP_SETTLE_MS = 1e4;
|
|
|
3764
4106
|
var RENDER_FAILURE_CONFIRM_MS = 25e3;
|
|
3765
4107
|
var MIN_GRID_CLIP_PX = 100;
|
|
3766
4108
|
var CLIP_HOLD_MS = 3e3;
|
|
3767
|
-
var delay = (ms) => new Promise((
|
|
3768
|
-
setTimeout(
|
|
4109
|
+
var delay = (ms) => new Promise((resolve9) => {
|
|
4110
|
+
setTimeout(resolve9, ms);
|
|
3769
4111
|
});
|
|
3770
4112
|
var pollFor = async (predicate, options) => {
|
|
3771
4113
|
const startedAt = Date.now();
|
|
@@ -8968,7 +9310,7 @@ var attachTunnelLifecycle = (handlers) => {
|
|
|
8968
9310
|
});
|
|
8969
9311
|
});
|
|
8970
9312
|
};
|
|
8971
|
-
var waitForTunnelUrl = (cloudflared) => new Promise((
|
|
9313
|
+
var waitForTunnelUrl = (cloudflared) => new Promise((resolve9, reject) => {
|
|
8972
9314
|
const state = { publicUrl: void 0, settled: false };
|
|
8973
9315
|
const settle = (outcome) => {
|
|
8974
9316
|
if (state.settled) return;
|
|
@@ -8977,7 +9319,7 @@ var waitForTunnelUrl = (cloudflared) => new Promise((resolve8, reject) => {
|
|
|
8977
9319
|
reject(outcome.error);
|
|
8978
9320
|
return;
|
|
8979
9321
|
}
|
|
8980
|
-
|
|
9322
|
+
resolve9(outcome.url);
|
|
8981
9323
|
};
|
|
8982
9324
|
const urlTimeout = setTimeout(() => {
|
|
8983
9325
|
settle({ kind: "error", error: new Error("Timed out waiting for tunnel URL") });
|
|
@@ -9473,9 +9815,9 @@ var Orchestrator = class {
|
|
|
9473
9815
|
}
|
|
9474
9816
|
});
|
|
9475
9817
|
}
|
|
9476
|
-
async listConversations() {
|
|
9818
|
+
async listConversations(options) {
|
|
9477
9819
|
if (this.page === null) return [];
|
|
9478
|
-
return this.provider.readSidebarConversations(this.page);
|
|
9820
|
+
return this.provider.readSidebarConversations(this.page, options);
|
|
9479
9821
|
}
|
|
9480
9822
|
async searchConversations(input) {
|
|
9481
9823
|
if (this.page === null) return [];
|
|
@@ -9566,18 +9908,18 @@ var mcpConnectorUrl = (tunnelUrl) => {
|
|
|
9566
9908
|
return `${tunnelUrl.replace(/\/+$/, "")}/mcp`;
|
|
9567
9909
|
};
|
|
9568
9910
|
var currentGitBranch = (repoPath) => {
|
|
9569
|
-
return new Promise((
|
|
9911
|
+
return new Promise((resolve9) => {
|
|
9570
9912
|
execFile2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: repoPath }, (error, stdout) => {
|
|
9571
9913
|
if (error) {
|
|
9572
|
-
|
|
9914
|
+
resolve9(void 0);
|
|
9573
9915
|
return;
|
|
9574
9916
|
}
|
|
9575
9917
|
const branch = stdout.trim();
|
|
9576
9918
|
if (branch.length === 0) {
|
|
9577
|
-
|
|
9919
|
+
resolve9(void 0);
|
|
9578
9920
|
return;
|
|
9579
9921
|
}
|
|
9580
|
-
|
|
9922
|
+
resolve9(branch);
|
|
9581
9923
|
});
|
|
9582
9924
|
});
|
|
9583
9925
|
};
|
|
@@ -10084,12 +10426,12 @@ var DEFAULT_FANOUT_TIMEOUT_MS = 3e5;
|
|
|
10084
10426
|
var DEFAULT_FANOUT_MAX_REPLY_CHARS = 2e3;
|
|
10085
10427
|
var DEFAULT_FANOUT_LIMIT = 20;
|
|
10086
10428
|
var withTimeout = (promise, ms, label) => {
|
|
10087
|
-
return new Promise((
|
|
10429
|
+
return new Promise((resolve9, reject) => {
|
|
10088
10430
|
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
10089
10431
|
promise.then(
|
|
10090
10432
|
(value) => {
|
|
10091
10433
|
clearTimeout(timer);
|
|
10092
|
-
|
|
10434
|
+
resolve9(value);
|
|
10093
10435
|
},
|
|
10094
10436
|
(error) => {
|
|
10095
10437
|
clearTimeout(timer);
|
|
@@ -10796,15 +11138,298 @@ var serveAskGatewayStdio = async (deps) => {
|
|
|
10796
11138
|
const mcpServer = askGatewayServerFor(deps);
|
|
10797
11139
|
const transport = new StdioServerTransport();
|
|
10798
11140
|
await mcpServer.connect(transport);
|
|
10799
|
-
await new Promise((
|
|
11141
|
+
await new Promise((resolve9) => {
|
|
10800
11142
|
const priorOnClose = transport.onclose;
|
|
10801
11143
|
transport.onclose = () => {
|
|
10802
11144
|
if (priorOnClose !== void 0) priorOnClose();
|
|
10803
|
-
|
|
11145
|
+
resolve9();
|
|
10804
11146
|
};
|
|
10805
11147
|
});
|
|
10806
11148
|
};
|
|
10807
11149
|
|
|
11150
|
+
// src/features/terminal/chatOrganizationQueue.ts
|
|
11151
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
11152
|
+
import { existsSync } from "fs";
|
|
11153
|
+
import { mkdir as mkdir7, open, readFile as readFile10, rename, unlink, writeFile as writeFile5 } from "fs/promises";
|
|
11154
|
+
import { dirname as dirname3, isAbsolute, join as join11, resolve as resolve7 } from "path";
|
|
11155
|
+
import { Schema as Schema8 } from "effect";
|
|
11156
|
+
var ChatOrganizationTaskSchema = Schema8.Struct({
|
|
11157
|
+
conversation: Schema8.String.pipe(Schema8.minLength(1)),
|
|
11158
|
+
project: Schema8.String.pipe(Schema8.minLength(1))
|
|
11159
|
+
});
|
|
11160
|
+
var ChatOrganizationTasksSchema = Schema8.Array(ChatOrganizationTaskSchema).pipe(
|
|
11161
|
+
Schema8.minItems(1)
|
|
11162
|
+
);
|
|
11163
|
+
var PendingQueueItemSchema = Schema8.Struct({
|
|
11164
|
+
status: Schema8.Literal("pending"),
|
|
11165
|
+
conversation: Schema8.String,
|
|
11166
|
+
project: Schema8.String,
|
|
11167
|
+
attempts: Schema8.Number.pipe(Schema8.int(), Schema8.nonNegative()),
|
|
11168
|
+
lastReason: Schema8.optional(Schema8.String)
|
|
11169
|
+
});
|
|
11170
|
+
var CompletedQueueItemSchema = Schema8.Struct({
|
|
11171
|
+
status: Schema8.Literal("moved", "already-filed"),
|
|
11172
|
+
conversation: Schema8.String,
|
|
11173
|
+
project: Schema8.String,
|
|
11174
|
+
attempts: Schema8.Number.pipe(Schema8.int(), Schema8.positive()),
|
|
11175
|
+
completedAt: Schema8.String
|
|
11176
|
+
});
|
|
11177
|
+
var FailedQueueItemSchema = Schema8.Struct({
|
|
11178
|
+
status: Schema8.Literal("failed"),
|
|
11179
|
+
conversation: Schema8.String,
|
|
11180
|
+
project: Schema8.String,
|
|
11181
|
+
attempts: Schema8.Number.pipe(Schema8.int(), Schema8.positive()),
|
|
11182
|
+
reason: Schema8.String,
|
|
11183
|
+
completedAt: Schema8.String
|
|
11184
|
+
});
|
|
11185
|
+
var ChatOrganizationQueueItemSchema = Schema8.Union(
|
|
11186
|
+
PendingQueueItemSchema,
|
|
11187
|
+
CompletedQueueItemSchema,
|
|
11188
|
+
FailedQueueItemSchema
|
|
11189
|
+
);
|
|
11190
|
+
var ChatOrganizationQueueSchema = Schema8.Struct({
|
|
11191
|
+
version: Schema8.Literal(1),
|
|
11192
|
+
fingerprint: Schema8.String,
|
|
11193
|
+
createdAt: Schema8.String,
|
|
11194
|
+
updatedAt: Schema8.String,
|
|
11195
|
+
items: Schema8.Array(ChatOrganizationQueueItemSchema).pipe(Schema8.minItems(1))
|
|
11196
|
+
});
|
|
11197
|
+
var ORGANIZATION_INTERVAL = /^(?<minimum>\d+)(?:-(?<maximum>\d+))?$/u;
|
|
11198
|
+
var chatOrganizationIntervalFrom = (value, defaultSeconds) => {
|
|
11199
|
+
if (value === void 0) {
|
|
11200
|
+
return { minimumSeconds: defaultSeconds, maximumSeconds: defaultSeconds };
|
|
11201
|
+
}
|
|
11202
|
+
const match = ORGANIZATION_INTERVAL.exec(value.trim());
|
|
11203
|
+
const minimumText = match?.groups?.minimum;
|
|
11204
|
+
const maximumText = match?.groups?.maximum;
|
|
11205
|
+
if (minimumText === void 0) {
|
|
11206
|
+
throw new Error("--interval must be whole seconds or a range such as 10-20.");
|
|
11207
|
+
}
|
|
11208
|
+
const minimumSeconds = Number.parseInt(minimumText, 10);
|
|
11209
|
+
let maximumSeconds = minimumSeconds;
|
|
11210
|
+
if (maximumText !== void 0) maximumSeconds = Number.parseInt(maximumText, 10);
|
|
11211
|
+
if (minimumSeconds <= 0 || maximumSeconds <= 0 || minimumSeconds > maximumSeconds) {
|
|
11212
|
+
throw new Error("--interval must be a positive ascending range such as 10-20.");
|
|
11213
|
+
}
|
|
11214
|
+
return { minimumSeconds, maximumSeconds };
|
|
11215
|
+
};
|
|
11216
|
+
var nextChatOrganizationIntervalSeconds = (interval, randomFraction) => {
|
|
11217
|
+
if (interval.minimumSeconds === interval.maximumSeconds) return interval.minimumSeconds;
|
|
11218
|
+
const width = interval.maximumSeconds - interval.minimumSeconds + 1;
|
|
11219
|
+
return interval.minimumSeconds + Math.floor(randomFraction * width);
|
|
11220
|
+
};
|
|
11221
|
+
var chatOrganizationIntervalLabel = (interval) => {
|
|
11222
|
+
if (interval.minimumSeconds === interval.maximumSeconds) return `${interval.minimumSeconds}s`;
|
|
11223
|
+
return `${interval.minimumSeconds}-${interval.maximumSeconds}s`;
|
|
11224
|
+
};
|
|
11225
|
+
var readOrganizationPlanSource = async (spec, launchDirectory) => {
|
|
11226
|
+
const trimmed = spec.trim();
|
|
11227
|
+
if (trimmed.startsWith("[")) return trimmed;
|
|
11228
|
+
let sourcePath = trimmed;
|
|
11229
|
+
if (trimmed.startsWith("@")) sourcePath = trimmed.slice(1);
|
|
11230
|
+
let absolutePath = sourcePath;
|
|
11231
|
+
if (!isAbsolute(sourcePath)) absolutePath = resolve7(launchDirectory, sourcePath);
|
|
11232
|
+
return readFile10(absolutePath, "utf8");
|
|
11233
|
+
};
|
|
11234
|
+
var normalizedOrganizationTasks = (tasks) => {
|
|
11235
|
+
const normalized = [];
|
|
11236
|
+
for (const task of tasks) {
|
|
11237
|
+
const conversation = task.conversation.trim();
|
|
11238
|
+
const project = task.project.trim();
|
|
11239
|
+
if (!conversation) throw new Error("Every organization task needs a Conversation id or title.");
|
|
11240
|
+
if (!project) throw new Error("Every organization task needs a Project name.");
|
|
11241
|
+
normalized.push({ conversation, project });
|
|
11242
|
+
}
|
|
11243
|
+
return normalized;
|
|
11244
|
+
};
|
|
11245
|
+
var loadChatOrganizationTasks = async (spec, launchDirectory) => {
|
|
11246
|
+
if (!spec.trim()) throw new Error("--plan needs a file, @file, or inline JSON array.");
|
|
11247
|
+
const source = await readOrganizationPlanSource(spec, launchDirectory);
|
|
11248
|
+
let parsed;
|
|
11249
|
+
try {
|
|
11250
|
+
parsed = JSON.parse(source);
|
|
11251
|
+
} catch (error) {
|
|
11252
|
+
throw new Error(
|
|
11253
|
+
`Organization plan is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
11254
|
+
);
|
|
11255
|
+
}
|
|
11256
|
+
const tasks = Schema8.decodeUnknownSync(ChatOrganizationTasksSchema)(parsed);
|
|
11257
|
+
return normalizedOrganizationTasks(tasks);
|
|
11258
|
+
};
|
|
11259
|
+
var organizationPlanFingerprint = (tasks) => {
|
|
11260
|
+
return createHash2("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
11261
|
+
};
|
|
11262
|
+
var chatOrganizationQueuePath = (repoRoot, tasks) => {
|
|
11263
|
+
return join11(
|
|
11264
|
+
bridgeDir(repoRoot),
|
|
11265
|
+
"chat-organization-queues",
|
|
11266
|
+
`${organizationPlanFingerprint(tasks)}.json`
|
|
11267
|
+
);
|
|
11268
|
+
};
|
|
11269
|
+
var newChatOrganizationQueue = (tasks, timestamp) => {
|
|
11270
|
+
return {
|
|
11271
|
+
version: 1,
|
|
11272
|
+
fingerprint: organizationPlanFingerprint(tasks),
|
|
11273
|
+
createdAt: timestamp,
|
|
11274
|
+
updatedAt: timestamp,
|
|
11275
|
+
items: tasks.map((task) => ({
|
|
11276
|
+
status: "pending",
|
|
11277
|
+
conversation: task.conversation,
|
|
11278
|
+
project: task.project,
|
|
11279
|
+
attempts: 0
|
|
11280
|
+
}))
|
|
11281
|
+
};
|
|
11282
|
+
};
|
|
11283
|
+
var persistChatOrganizationQueue = async (queuePath, queue) => {
|
|
11284
|
+
await mkdir7(dirname3(queuePath), { recursive: true });
|
|
11285
|
+
const pendingPath = `${queuePath}.${process.pid}.${randomUUID4()}.pending`;
|
|
11286
|
+
try {
|
|
11287
|
+
await writeFile5(pendingPath, `${JSON.stringify(queue, null, 2)}
|
|
11288
|
+
`, "utf8");
|
|
11289
|
+
await rename(pendingPath, queuePath);
|
|
11290
|
+
} finally {
|
|
11291
|
+
try {
|
|
11292
|
+
await unlink(pendingPath);
|
|
11293
|
+
} catch {
|
|
11294
|
+
}
|
|
11295
|
+
}
|
|
11296
|
+
};
|
|
11297
|
+
var errorCodeOf = (error) => {
|
|
11298
|
+
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
|
|
11299
|
+
if (typeof error.code !== "string") return void 0;
|
|
11300
|
+
return error.code;
|
|
11301
|
+
};
|
|
11302
|
+
var removeStaleQueueLock = async (lockPath) => {
|
|
11303
|
+
let owner;
|
|
11304
|
+
try {
|
|
11305
|
+
owner = JSON.parse(await readFile10(lockPath, "utf8"));
|
|
11306
|
+
} catch {
|
|
11307
|
+
return false;
|
|
11308
|
+
}
|
|
11309
|
+
if (typeof owner !== "object" || owner === null || !("pid" in owner)) return false;
|
|
11310
|
+
if (typeof owner.pid !== "number" || !Number.isInteger(owner.pid)) return false;
|
|
11311
|
+
try {
|
|
11312
|
+
process.kill(owner.pid, 0);
|
|
11313
|
+
return false;
|
|
11314
|
+
} catch (error) {
|
|
11315
|
+
if (errorCodeOf(error) !== "ESRCH") return false;
|
|
11316
|
+
}
|
|
11317
|
+
await unlink(lockPath);
|
|
11318
|
+
return true;
|
|
11319
|
+
};
|
|
11320
|
+
var acquireChatOrganizationQueueLock = async (queuePath) => {
|
|
11321
|
+
await mkdir7(dirname3(queuePath), { recursive: true });
|
|
11322
|
+
const lockPath = `${queuePath}.lock`;
|
|
11323
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
11324
|
+
try {
|
|
11325
|
+
const handle = await open(lockPath, "wx");
|
|
11326
|
+
await handle.writeFile(
|
|
11327
|
+
`${JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
11328
|
+
`
|
|
11329
|
+
);
|
|
11330
|
+
let released = false;
|
|
11331
|
+
return async () => {
|
|
11332
|
+
if (released) return;
|
|
11333
|
+
released = true;
|
|
11334
|
+
await handle.close();
|
|
11335
|
+
await unlink(lockPath);
|
|
11336
|
+
};
|
|
11337
|
+
} catch (error) {
|
|
11338
|
+
if (errorCodeOf(error) !== "EEXIST") throw error;
|
|
11339
|
+
if (attempt === 0 && await removeStaleQueueLock(lockPath)) continue;
|
|
11340
|
+
throw new Error(`Organization queue is already running: ${lockPath}`);
|
|
11341
|
+
}
|
|
11342
|
+
}
|
|
11343
|
+
throw new Error(`Could not acquire organization queue lock: ${lockPath}`);
|
|
11344
|
+
};
|
|
11345
|
+
var openChatOrganizationQueue = async (input) => {
|
|
11346
|
+
const path2 = chatOrganizationQueuePath(input.repoRoot, input.tasks);
|
|
11347
|
+
if (!input.restart && existsSync(path2)) {
|
|
11348
|
+
const stored = JSON.parse(await readFile10(path2, "utf8"));
|
|
11349
|
+
const queue2 = Schema8.decodeUnknownSync(ChatOrganizationQueueSchema)(stored);
|
|
11350
|
+
const expectedFingerprint = organizationPlanFingerprint(input.tasks);
|
|
11351
|
+
if (queue2.fingerprint !== expectedFingerprint) {
|
|
11352
|
+
throw new Error(`Stored queue fingerprint does not match ${path2}. Use --restart.`);
|
|
11353
|
+
}
|
|
11354
|
+
return { path: path2, queue: queue2 };
|
|
11355
|
+
}
|
|
11356
|
+
const queue = newChatOrganizationQueue(input.tasks, input.timestamp);
|
|
11357
|
+
await persistChatOrganizationQueue(path2, queue);
|
|
11358
|
+
return { path: path2, queue };
|
|
11359
|
+
};
|
|
11360
|
+
var nextChatOrganizationQueueIndex = (queue) => {
|
|
11361
|
+
const index = queue.items.findIndex((item) => item.status === "pending");
|
|
11362
|
+
if (index < 0) return void 0;
|
|
11363
|
+
return index;
|
|
11364
|
+
};
|
|
11365
|
+
var completeChatOrganizationQueueItem = (input) => {
|
|
11366
|
+
const current = input.queue.items[input.index];
|
|
11367
|
+
if (current === void 0 || current.status !== "pending") return input.queue;
|
|
11368
|
+
const items = [...input.queue.items];
|
|
11369
|
+
items[input.index] = {
|
|
11370
|
+
status: input.status,
|
|
11371
|
+
conversation: current.conversation,
|
|
11372
|
+
project: current.project,
|
|
11373
|
+
attempts: current.attempts + 1,
|
|
11374
|
+
completedAt: input.timestamp
|
|
11375
|
+
};
|
|
11376
|
+
return { ...input.queue, updatedAt: input.timestamp, items };
|
|
11377
|
+
};
|
|
11378
|
+
var deferChatOrganizationQueueItem = (input) => {
|
|
11379
|
+
const current = input.queue.items[input.index];
|
|
11380
|
+
if (current === void 0 || current.status !== "pending") return input.queue;
|
|
11381
|
+
const items = [...input.queue.items];
|
|
11382
|
+
items[input.index] = {
|
|
11383
|
+
status: "pending",
|
|
11384
|
+
conversation: current.conversation,
|
|
11385
|
+
project: current.project,
|
|
11386
|
+
attempts: current.attempts,
|
|
11387
|
+
lastReason: input.reason
|
|
11388
|
+
};
|
|
11389
|
+
return { ...input.queue, updatedAt: input.timestamp, items };
|
|
11390
|
+
};
|
|
11391
|
+
var failChatOrganizationQueueItem = (input) => {
|
|
11392
|
+
const current = input.queue.items[input.index];
|
|
11393
|
+
if (current === void 0 || current.status !== "pending") return input.queue;
|
|
11394
|
+
const attempts = current.attempts + 1;
|
|
11395
|
+
const items = [...input.queue.items];
|
|
11396
|
+
if (attempts >= input.maxAttempts) {
|
|
11397
|
+
items[input.index] = {
|
|
11398
|
+
status: "failed",
|
|
11399
|
+
conversation: current.conversation,
|
|
11400
|
+
project: current.project,
|
|
11401
|
+
attempts,
|
|
11402
|
+
reason: input.reason,
|
|
11403
|
+
completedAt: input.timestamp
|
|
11404
|
+
};
|
|
11405
|
+
} else {
|
|
11406
|
+
items[input.index] = {
|
|
11407
|
+
status: "pending",
|
|
11408
|
+
conversation: current.conversation,
|
|
11409
|
+
project: current.project,
|
|
11410
|
+
attempts,
|
|
11411
|
+
lastReason: input.reason
|
|
11412
|
+
};
|
|
11413
|
+
}
|
|
11414
|
+
return { ...input.queue, updatedAt: input.timestamp, items };
|
|
11415
|
+
};
|
|
11416
|
+
var summarizeChatOrganizationQueue = (queue) => {
|
|
11417
|
+
const summary = {
|
|
11418
|
+
total: queue.items.length,
|
|
11419
|
+
pending: 0,
|
|
11420
|
+
moved: 0,
|
|
11421
|
+
alreadyFiled: 0,
|
|
11422
|
+
failed: 0
|
|
11423
|
+
};
|
|
11424
|
+
for (const item of queue.items) {
|
|
11425
|
+
if (item.status === "pending") summary.pending += 1;
|
|
11426
|
+
else if (item.status === "moved") summary.moved += 1;
|
|
11427
|
+
else if (item.status === "already-filed") summary.alreadyFiled += 1;
|
|
11428
|
+
else summary.failed += 1;
|
|
11429
|
+
}
|
|
11430
|
+
return summary;
|
|
11431
|
+
};
|
|
11432
|
+
|
|
10808
11433
|
// src/features/terminal/providerLabel.ts
|
|
10809
11434
|
var providerDisplayName = (id2) => {
|
|
10810
11435
|
return PROVIDER_CONFIG[id2].displayName;
|
|
@@ -11385,7 +12010,7 @@ var IGNORED_COMPLETION_ENTRIES = /* @__PURE__ */ new Set([
|
|
|
11385
12010
|
]);
|
|
11386
12011
|
|
|
11387
12012
|
// src/features/terminal/tui/composer/fileMentionCompletion.ts
|
|
11388
|
-
import { isAbsolute, sep as sep2 } from "path";
|
|
12013
|
+
import { isAbsolute as isAbsolute2, sep as sep2 } from "path";
|
|
11389
12014
|
var findActiveFileMention = (input) => {
|
|
11390
12015
|
const position = input.cursor === void 0 ? input.input.length : input.cursor;
|
|
11391
12016
|
return parseActiveFileMention({ text: input.input, position });
|
|
@@ -11411,7 +12036,7 @@ var normalizePartialPath = (partial) => {
|
|
|
11411
12036
|
return partial.replaceAll("\\", "/").replaceAll(sep2, "/");
|
|
11412
12037
|
};
|
|
11413
12038
|
var isUnsafePartial = (partial) => {
|
|
11414
|
-
if (
|
|
12039
|
+
if (isAbsolute2(partial) || partial.startsWith("/")) return true;
|
|
11415
12040
|
return partial.split("/").filter(Boolean).includes("..");
|
|
11416
12041
|
};
|
|
11417
12042
|
var compareCompletionMatches = (left, right) => {
|
|
@@ -12927,8 +13552,8 @@ var sessionIdFrom = async (params) => {
|
|
|
12927
13552
|
return latest.metadata.id;
|
|
12928
13553
|
};
|
|
12929
13554
|
var repositoryFileFrom = (params) => {
|
|
12930
|
-
if (
|
|
12931
|
-
const rel = relative3(
|
|
13555
|
+
if (isAbsolute3(params.input)) {
|
|
13556
|
+
const rel = relative3(resolve8(params.repoRoot), resolve8(params.input));
|
|
12932
13557
|
return repositoryPath(params.repoRoot, rel || ".");
|
|
12933
13558
|
}
|
|
12934
13559
|
return repositoryPath(params.repoRoot, params.input);
|
|
@@ -12965,8 +13590,8 @@ var parseCaptureUrl = (url) => {
|
|
|
12965
13590
|
};
|
|
12966
13591
|
var prepareScreenshotDir = async (repoPath) => {
|
|
12967
13592
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12968
|
-
const dir =
|
|
12969
|
-
await
|
|
13593
|
+
const dir = join12(screenshotsDir(repoPath), stamp);
|
|
13594
|
+
await mkdir8(dir, { recursive: true });
|
|
12970
13595
|
return dir;
|
|
12971
13596
|
};
|
|
12972
13597
|
var captureWithPlaywright = async (params) => {
|
|
@@ -12998,7 +13623,7 @@ var captureViewport = async (params) => {
|
|
|
12998
13623
|
return file;
|
|
12999
13624
|
};
|
|
13000
13625
|
var writeViewportScreenshot = async (input) => {
|
|
13001
|
-
const file =
|
|
13626
|
+
const file = join12(input.dir, `${input.viewport.name}.png`);
|
|
13002
13627
|
await input.page.screenshot({ path: file, fullPage: true });
|
|
13003
13628
|
return file;
|
|
13004
13629
|
};
|
|
@@ -13016,16 +13641,16 @@ var sessionExportFromParts = async (input) => {
|
|
|
13016
13641
|
if (session !== void 0 && session !== null) {
|
|
13017
13642
|
return {
|
|
13018
13643
|
sessionId: session.metadata.id,
|
|
13019
|
-
outputPath: input.parts[1] ?
|
|
13644
|
+
outputPath: input.parts[1] ? resolve8(input.parts[1]) : void 0
|
|
13020
13645
|
};
|
|
13021
13646
|
}
|
|
13022
13647
|
return {
|
|
13023
13648
|
sessionId: await sessionIdFrom({ args: "", ctx: input.ctx }),
|
|
13024
|
-
outputPath:
|
|
13649
|
+
outputPath: resolve8(first)
|
|
13025
13650
|
};
|
|
13026
13651
|
};
|
|
13027
13652
|
var defaultExportPath = (params) => {
|
|
13028
|
-
return
|
|
13653
|
+
return join12(exportsDir(params.repoPath), `${params.sessionId}.md`);
|
|
13029
13654
|
};
|
|
13030
13655
|
var exportContentForPath = (params) => {
|
|
13031
13656
|
const extension = extname2(params.path).toLowerCase();
|
|
@@ -13329,8 +13954,8 @@ var writeSessionExport = async (params) => {
|
|
|
13329
13954
|
};
|
|
13330
13955
|
var persistSessionExport = async (input) => {
|
|
13331
13956
|
const content = exportContentForPath({ path: input.targetPath, exported: input.exported });
|
|
13332
|
-
await
|
|
13333
|
-
await
|
|
13957
|
+
await mkdir8(dirname4(input.targetPath), { recursive: true });
|
|
13958
|
+
await writeFile6(input.targetPath, content, "utf-8");
|
|
13334
13959
|
console.log(`Exported ${input.sessionId} to ${input.targetPath}`);
|
|
13335
13960
|
};
|
|
13336
13961
|
var listCheckpointsCommand = async (_args, ctx) => {
|
|
@@ -13710,7 +14335,7 @@ var runBrowserStatus = async (options = {}) => {
|
|
|
13710
14335
|
process.exit(0);
|
|
13711
14336
|
};
|
|
13712
14337
|
var cacheProfileRoot = (options) => {
|
|
13713
|
-
return options.profile ?
|
|
14338
|
+
return options.profile ? resolve8(options.profile) : bridgeChromeProfileRoot();
|
|
13714
14339
|
};
|
|
13715
14340
|
var runCacheList = async (options) => {
|
|
13716
14341
|
const inventory = await inventoryChromeCache({
|
|
@@ -13850,8 +14475,8 @@ var readFanoutSource = async (spec) => {
|
|
|
13850
14475
|
const trimmed = spec.trim();
|
|
13851
14476
|
if (trimmed.startsWith("[")) return trimmed;
|
|
13852
14477
|
const path2 = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
13853
|
-
const absolute =
|
|
13854
|
-
return
|
|
14478
|
+
const absolute = isAbsolute3(path2) ? path2 : resolve8(process.cwd(), path2);
|
|
14479
|
+
return readFile11(absolute, "utf8");
|
|
13855
14480
|
};
|
|
13856
14481
|
var loadFanoutTasks = async (spec) => {
|
|
13857
14482
|
if (!spec.trim()) return fail("--fan-out needs a task file, @file, or inline JSON array.");
|
|
@@ -13865,7 +14490,7 @@ var loadFanoutTasks = async (spec) => {
|
|
|
13865
14490
|
return fail(`--fan-out is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
13866
14491
|
}
|
|
13867
14492
|
try {
|
|
13868
|
-
return
|
|
14493
|
+
return Schema9.decodeUnknownSync(FanoutTasksSchema)(parsed);
|
|
13869
14494
|
} catch (err) {
|
|
13870
14495
|
return fail(
|
|
13871
14496
|
`--fan-out does not match the task schema (need a non-empty array of {prompt, provider?, conversation?, label?, isolate?}): ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -14100,7 +14725,7 @@ var finishAskRun = async (input) => {
|
|
|
14100
14725
|
var startAskEngine = async (input) => {
|
|
14101
14726
|
const withTools = Boolean(input.options.tools) && input.supportsMcpConnector;
|
|
14102
14727
|
return startEngine({
|
|
14103
|
-
repoPath: input.options.repo ?
|
|
14728
|
+
repoPath: input.options.repo ? resolve8(input.options.repo) : void 0,
|
|
14104
14729
|
provider: input.provider,
|
|
14105
14730
|
mcpPort: input.options.port ? Number(input.options.port) : void 0,
|
|
14106
14731
|
withBrowser: true,
|
|
@@ -14125,7 +14750,7 @@ var debugPortFromOption = (value) => {
|
|
|
14125
14750
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
14126
14751
|
};
|
|
14127
14752
|
var profileRootFromOption = (value) => {
|
|
14128
|
-
return value ?
|
|
14753
|
+
return value ? resolve8(value) : void 0;
|
|
14129
14754
|
};
|
|
14130
14755
|
var runAskTurn = async (input) => {
|
|
14131
14756
|
await applyAskPreflight({ engine: input.engine, options: input.options });
|
|
@@ -14143,7 +14768,7 @@ var attachAskFiles = async (input) => {
|
|
|
14143
14768
|
const resolved = paths.map((target) => {
|
|
14144
14769
|
const rel = repositoryFileFrom({ repoRoot, input: target });
|
|
14145
14770
|
assertImagePath(rel);
|
|
14146
|
-
return
|
|
14771
|
+
return resolve8(repoRoot, rel);
|
|
14147
14772
|
});
|
|
14148
14773
|
await input.engine.getOrchestrator().attachFiles(resolved);
|
|
14149
14774
|
};
|
|
@@ -14250,7 +14875,7 @@ var downloadAfterExtract = async (input) => {
|
|
|
14250
14875
|
};
|
|
14251
14876
|
var startDownloadEngine = async (options) => {
|
|
14252
14877
|
return startEngine({
|
|
14253
|
-
repoPath: options.repo ?
|
|
14878
|
+
repoPath: options.repo ? resolve8(options.repo) : void 0,
|
|
14254
14879
|
provider: providerIdFrom(options.provider),
|
|
14255
14880
|
mcpPort: options.port ? Number(options.port) : void 0,
|
|
14256
14881
|
withBrowser: true,
|
|
@@ -14279,7 +14904,7 @@ var assertChatgptWorkspace = (options) => {
|
|
|
14279
14904
|
};
|
|
14280
14905
|
var startWorkspaceSession = async (options) => {
|
|
14281
14906
|
const engine = await startEngine({
|
|
14282
|
-
repoPath: options.repo ?
|
|
14907
|
+
repoPath: options.repo ? resolve8(options.repo) : void 0,
|
|
14283
14908
|
provider: "chatgpt",
|
|
14284
14909
|
mcpPort: options.port ? Number(options.port) : void 0,
|
|
14285
14910
|
withBrowser: true,
|
|
@@ -14351,13 +14976,20 @@ var runProjectDelete = async (name, options) => {
|
|
|
14351
14976
|
var runChatList = async (options) => {
|
|
14352
14977
|
assertChatgptWorkspace(options);
|
|
14353
14978
|
const { engine } = await startWorkspaceSession(options);
|
|
14354
|
-
const chats = await engine.getOrchestrator().listConversations();
|
|
14979
|
+
const chats = await engine.getOrchestrator().listConversations({ orphans: Boolean(options.orphans) });
|
|
14355
14980
|
await engine.shutdown({ closeBrowser: false });
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
else
|
|
14360
|
-
`)
|
|
14981
|
+
let output = "";
|
|
14982
|
+
if (options.json) output = `${JSON.stringify(chats)}
|
|
14983
|
+
`;
|
|
14984
|
+
else if (chats.length === 0) output = "No conversations.\n";
|
|
14985
|
+
else output = `${chats.map((chat) => `${chat.id} ${chat.title}`).join("\n")}
|
|
14986
|
+
`;
|
|
14987
|
+
await new Promise((resolve9, reject) => {
|
|
14988
|
+
process.stdout.write(output, (error) => {
|
|
14989
|
+
if (error) reject(error);
|
|
14990
|
+
else resolve9();
|
|
14991
|
+
});
|
|
14992
|
+
});
|
|
14361
14993
|
process.exit(0);
|
|
14362
14994
|
};
|
|
14363
14995
|
var runChatSearch = async (query, options) => {
|
|
@@ -14444,6 +15076,292 @@ var runChatMove = async (chat, options) => {
|
|
|
14444
15076
|
writeMoveOutcomes(outcomes, options);
|
|
14445
15077
|
process.exit(outcomes.every((outcome) => outcome.moved || outcome.alreadyFiled) ? 0 : 1);
|
|
14446
15078
|
};
|
|
15079
|
+
var DEFAULT_ORGANIZATION_INTERVAL_SECONDS = 30;
|
|
15080
|
+
var DEFAULT_ORGANIZATION_COOLDOWN_SECONDS = 300;
|
|
15081
|
+
var DEFAULT_ORGANIZATION_MAX_ATTEMPTS = 3;
|
|
15082
|
+
var MAX_CONSECUTIVE_ORGANIZATION_COOLDOWNS = 12;
|
|
15083
|
+
var RATE_LIMIT_REASON = "ChatGPT temporarily limited access to Conversation history.";
|
|
15084
|
+
var positiveIntegerOption = (value, optionName, defaultValue) => {
|
|
15085
|
+
if (value === void 0) return defaultValue;
|
|
15086
|
+
if (!/^[1-9]\d*$/u.test(value.trim())) {
|
|
15087
|
+
return fail(`${optionName} must be a positive whole number.`);
|
|
15088
|
+
}
|
|
15089
|
+
const parsed = Number.parseInt(value, 10);
|
|
15090
|
+
return parsed;
|
|
15091
|
+
};
|
|
15092
|
+
var organizationIntervalOption = (value) => {
|
|
15093
|
+
try {
|
|
15094
|
+
return chatOrganizationIntervalFrom(value, DEFAULT_ORGANIZATION_INTERVAL_SECONDS);
|
|
15095
|
+
} catch (error) {
|
|
15096
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
15097
|
+
}
|
|
15098
|
+
};
|
|
15099
|
+
var waitForOrganizationInterval = async (seconds) => {
|
|
15100
|
+
await new Promise((complete) => setTimeout(complete, seconds * 1e3));
|
|
15101
|
+
};
|
|
15102
|
+
var writeChatOrganizationProgress = (message, options) => {
|
|
15103
|
+
if (options.json) {
|
|
15104
|
+
process.stderr.write(`${message}
|
|
15105
|
+
`);
|
|
15106
|
+
return;
|
|
15107
|
+
}
|
|
15108
|
+
process.stdout.write(`${message}
|
|
15109
|
+
`);
|
|
15110
|
+
};
|
|
15111
|
+
var moveFailureReason = (outcome) => {
|
|
15112
|
+
if (outcome.reason?.trim()) return outcome.reason;
|
|
15113
|
+
return "ChatGPT did not confirm the Conversation move.";
|
|
15114
|
+
};
|
|
15115
|
+
var writeChatOrganizationSummary = (input) => {
|
|
15116
|
+
const summary = summarizeChatOrganizationQueue(input.queue);
|
|
15117
|
+
if (input.options.json) {
|
|
15118
|
+
process.stdout.write(`${JSON.stringify({ queuePath: input.queuePath, summary })}
|
|
15119
|
+
`);
|
|
15120
|
+
return;
|
|
15121
|
+
}
|
|
15122
|
+
process.stdout.write(
|
|
15123
|
+
`${[
|
|
15124
|
+
"Organization queue finished.",
|
|
15125
|
+
`Moved: ${summary.moved}`,
|
|
15126
|
+
`Already filed: ${summary.alreadyFiled}`,
|
|
15127
|
+
`Failed: ${summary.failed}`,
|
|
15128
|
+
`Queue state: ${input.queuePath}`
|
|
15129
|
+
].join("\n")}
|
|
15130
|
+
`
|
|
15131
|
+
);
|
|
15132
|
+
};
|
|
15133
|
+
var writeChatOrganizationDryRun = (input) => {
|
|
15134
|
+
const preview = {
|
|
15135
|
+
dryRun: true,
|
|
15136
|
+
queuePath: input.queuePath,
|
|
15137
|
+
interval: input.interval,
|
|
15138
|
+
cooldownSeconds: input.cooldownSeconds,
|
|
15139
|
+
maxAttempts: input.maxAttempts,
|
|
15140
|
+
tasks: input.tasks
|
|
15141
|
+
};
|
|
15142
|
+
if (input.options.json) {
|
|
15143
|
+
process.stdout.write(`${JSON.stringify(preview)}
|
|
15144
|
+
`);
|
|
15145
|
+
return;
|
|
15146
|
+
}
|
|
15147
|
+
process.stdout.write(
|
|
15148
|
+
`${[
|
|
15149
|
+
`Organization queue dry run: ${input.tasks.length} Conversations`,
|
|
15150
|
+
`Interval: ${chatOrganizationIntervalLabel(input.interval)}`,
|
|
15151
|
+
`Rate-limit cooldown: ${input.cooldownSeconds}s`,
|
|
15152
|
+
`Attempts per Conversation: ${input.maxAttempts}`,
|
|
15153
|
+
`Resume state: ${input.queuePath}`,
|
|
15154
|
+
...input.tasks.map((task) => ` ${task.conversation} -> ${task.project}`)
|
|
15155
|
+
].join("\n")}
|
|
15156
|
+
`
|
|
15157
|
+
);
|
|
15158
|
+
};
|
|
15159
|
+
var recordChatOrganizationAttempt = async (input) => {
|
|
15160
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
15161
|
+
let queue;
|
|
15162
|
+
if (input.rateLimited) {
|
|
15163
|
+
queue = deferChatOrganizationQueueItem({
|
|
15164
|
+
queue: input.queue,
|
|
15165
|
+
index: input.index,
|
|
15166
|
+
reason: RATE_LIMIT_REASON,
|
|
15167
|
+
timestamp
|
|
15168
|
+
});
|
|
15169
|
+
} else if (input.moveOutcome?.moved) {
|
|
15170
|
+
queue = completeChatOrganizationQueueItem({
|
|
15171
|
+
queue: input.queue,
|
|
15172
|
+
index: input.index,
|
|
15173
|
+
status: "moved",
|
|
15174
|
+
timestamp
|
|
15175
|
+
});
|
|
15176
|
+
} else if (input.moveOutcome?.alreadyFiled) {
|
|
15177
|
+
queue = completeChatOrganizationQueueItem({
|
|
15178
|
+
queue: input.queue,
|
|
15179
|
+
index: input.index,
|
|
15180
|
+
status: "already-filed",
|
|
15181
|
+
timestamp
|
|
15182
|
+
});
|
|
15183
|
+
} else {
|
|
15184
|
+
let reason = "ChatGPT did not complete the Conversation move.";
|
|
15185
|
+
if (input.moveOutcome !== void 0) reason = moveFailureReason(input.moveOutcome);
|
|
15186
|
+
else if (input.thrownReason !== void 0) reason = input.thrownReason;
|
|
15187
|
+
queue = failChatOrganizationQueueItem({
|
|
15188
|
+
queue: input.queue,
|
|
15189
|
+
index: input.index,
|
|
15190
|
+
reason,
|
|
15191
|
+
maxAttempts: input.maxAttempts,
|
|
15192
|
+
timestamp
|
|
15193
|
+
});
|
|
15194
|
+
}
|
|
15195
|
+
await persistChatOrganizationQueue(input.queuePath, queue);
|
|
15196
|
+
return queue;
|
|
15197
|
+
};
|
|
15198
|
+
var runChatOrganize = async (options) => {
|
|
15199
|
+
assertChatgptWorkspace(options);
|
|
15200
|
+
const plan = options.plan;
|
|
15201
|
+
if (plan === void 0 || !plan.trim()) {
|
|
15202
|
+
return fail("Usage: bridge chat organize --plan <fileOrJson>");
|
|
15203
|
+
}
|
|
15204
|
+
const interval = organizationIntervalOption(options.interval);
|
|
15205
|
+
const cooldownSeconds = positiveIntegerOption(
|
|
15206
|
+
options.cooldown,
|
|
15207
|
+
"--cooldown",
|
|
15208
|
+
DEFAULT_ORGANIZATION_COOLDOWN_SECONDS
|
|
15209
|
+
);
|
|
15210
|
+
const maxAttempts = positiveIntegerOption(
|
|
15211
|
+
options.maxAttempts,
|
|
15212
|
+
"--max-attempts",
|
|
15213
|
+
DEFAULT_ORGANIZATION_MAX_ATTEMPTS
|
|
15214
|
+
);
|
|
15215
|
+
let tasks;
|
|
15216
|
+
try {
|
|
15217
|
+
tasks = await loadChatOrganizationTasks(plan, process.cwd());
|
|
15218
|
+
} catch (error) {
|
|
15219
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
15220
|
+
}
|
|
15221
|
+
const repoRoot = repositoryRoot(options.repo);
|
|
15222
|
+
const queuePath = chatOrganizationQueuePath(repoRoot, tasks);
|
|
15223
|
+
if (options.dryRun) {
|
|
15224
|
+
writeChatOrganizationDryRun({
|
|
15225
|
+
tasks,
|
|
15226
|
+
queuePath,
|
|
15227
|
+
interval,
|
|
15228
|
+
cooldownSeconds,
|
|
15229
|
+
maxAttempts,
|
|
15230
|
+
options
|
|
15231
|
+
});
|
|
15232
|
+
process.exit(0);
|
|
15233
|
+
}
|
|
15234
|
+
let releaseQueueLock;
|
|
15235
|
+
try {
|
|
15236
|
+
releaseQueueLock = await acquireChatOrganizationQueueLock(queuePath);
|
|
15237
|
+
} catch (error) {
|
|
15238
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
15239
|
+
}
|
|
15240
|
+
let queue;
|
|
15241
|
+
try {
|
|
15242
|
+
const opened = await openChatOrganizationQueue({
|
|
15243
|
+
repoRoot,
|
|
15244
|
+
tasks,
|
|
15245
|
+
restart: Boolean(options.restart),
|
|
15246
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
15247
|
+
});
|
|
15248
|
+
queue = opened.queue;
|
|
15249
|
+
} catch (error) {
|
|
15250
|
+
await releaseQueueLock();
|
|
15251
|
+
return fail(
|
|
15252
|
+
`Could not open the organization queue: ${error instanceof Error ? error.message : String(error)}`
|
|
15253
|
+
);
|
|
15254
|
+
}
|
|
15255
|
+
const initialSummary = summarizeChatOrganizationQueue(queue);
|
|
15256
|
+
writeChatOrganizationProgress(
|
|
15257
|
+
`Organization queue: ${initialSummary.pending} pending of ${initialSummary.total}; state ${queuePath}`,
|
|
15258
|
+
options
|
|
15259
|
+
);
|
|
15260
|
+
if (initialSummary.pending === 0) {
|
|
15261
|
+
writeChatOrganizationSummary({ queuePath, queue, options });
|
|
15262
|
+
await releaseQueueLock();
|
|
15263
|
+
process.exit(initialSummary.failed === 0 ? 0 : 1);
|
|
15264
|
+
}
|
|
15265
|
+
let workspace;
|
|
15266
|
+
try {
|
|
15267
|
+
workspace = await startWorkspaceSession(options);
|
|
15268
|
+
} catch (error) {
|
|
15269
|
+
await releaseQueueLock();
|
|
15270
|
+
throw error;
|
|
15271
|
+
}
|
|
15272
|
+
const { engine, page } = workspace;
|
|
15273
|
+
try {
|
|
15274
|
+
let index = nextChatOrganizationQueueIndex(queue);
|
|
15275
|
+
let consecutiveRateLimitCooldowns = 0;
|
|
15276
|
+
while (index !== void 0) {
|
|
15277
|
+
const item = queue.items[index];
|
|
15278
|
+
if (item === void 0 || item.status !== "pending") {
|
|
15279
|
+
index = nextChatOrganizationQueueIndex(queue);
|
|
15280
|
+
continue;
|
|
15281
|
+
}
|
|
15282
|
+
if (await acknowledgeChatGptHistoryRateLimit(page)) {
|
|
15283
|
+
consecutiveRateLimitCooldowns += 1;
|
|
15284
|
+
if (consecutiveRateLimitCooldowns >= MAX_CONSECUTIVE_ORGANIZATION_COOLDOWNS) {
|
|
15285
|
+
writeChatOrganizationProgress(
|
|
15286
|
+
`ChatGPT remained rate-limited after ${consecutiveRateLimitCooldowns} cooldowns; leaving the queue pending for a later resume.`,
|
|
15287
|
+
options
|
|
15288
|
+
);
|
|
15289
|
+
break;
|
|
15290
|
+
}
|
|
15291
|
+
writeChatOrganizationProgress(
|
|
15292
|
+
`ChatGPT rate limit detected; cooling down for ${cooldownSeconds}s before continuing.`,
|
|
15293
|
+
options
|
|
15294
|
+
);
|
|
15295
|
+
await waitForOrganizationInterval(cooldownSeconds);
|
|
15296
|
+
continue;
|
|
15297
|
+
}
|
|
15298
|
+
consecutiveRateLimitCooldowns = 0;
|
|
15299
|
+
writeChatOrganizationProgress(
|
|
15300
|
+
`Moving "${item.conversation}" -> ${item.project} (attempt ${item.attempts + 1}/${maxAttempts})`,
|
|
15301
|
+
options
|
|
15302
|
+
);
|
|
15303
|
+
let moveOutcome;
|
|
15304
|
+
let thrownReason;
|
|
15305
|
+
try {
|
|
15306
|
+
moveOutcome = await moveChatToProject(page, {
|
|
15307
|
+
chat: item.conversation,
|
|
15308
|
+
project: item.project
|
|
15309
|
+
});
|
|
15310
|
+
} catch (error) {
|
|
15311
|
+
thrownReason = error instanceof Error ? error.message : String(error);
|
|
15312
|
+
}
|
|
15313
|
+
const rateLimited = await acknowledgeChatGptHistoryRateLimit(page);
|
|
15314
|
+
queue = await recordChatOrganizationAttempt({
|
|
15315
|
+
queue,
|
|
15316
|
+
queuePath,
|
|
15317
|
+
index,
|
|
15318
|
+
maxAttempts,
|
|
15319
|
+
rateLimited,
|
|
15320
|
+
moveOutcome,
|
|
15321
|
+
thrownReason
|
|
15322
|
+
});
|
|
15323
|
+
const recorded = queue.items[index];
|
|
15324
|
+
if (recorded?.status === "moved") {
|
|
15325
|
+
writeChatOrganizationProgress(`Moved "${recorded.conversation}".`, options);
|
|
15326
|
+
} else if (recorded?.status === "already-filed") {
|
|
15327
|
+
writeChatOrganizationProgress(`Already filed "${recorded.conversation}".`, options);
|
|
15328
|
+
} else if (recorded?.status === "failed") {
|
|
15329
|
+
writeChatOrganizationProgress(
|
|
15330
|
+
`Failed "${recorded.conversation}" after ${recorded.attempts} attempts: ${recorded.reason}`,
|
|
15331
|
+
options
|
|
15332
|
+
);
|
|
15333
|
+
} else if (recorded?.status === "pending") {
|
|
15334
|
+
let reason = "unknown failure";
|
|
15335
|
+
if (recorded.lastReason !== void 0) reason = recorded.lastReason;
|
|
15336
|
+
writeChatOrganizationProgress(`Will retry "${recorded.conversation}": ${reason}`, options);
|
|
15337
|
+
}
|
|
15338
|
+
let waitSeconds = nextChatOrganizationIntervalSeconds(interval, Math.random());
|
|
15339
|
+
if (rateLimited) {
|
|
15340
|
+
consecutiveRateLimitCooldowns += 1;
|
|
15341
|
+
waitSeconds = cooldownSeconds;
|
|
15342
|
+
}
|
|
15343
|
+
index = nextChatOrganizationQueueIndex(queue);
|
|
15344
|
+
if (index !== void 0) {
|
|
15345
|
+
writeChatOrganizationProgress(
|
|
15346
|
+
`Waiting ${waitSeconds}s before the next UI operation.`,
|
|
15347
|
+
options
|
|
15348
|
+
);
|
|
15349
|
+
await waitForOrganizationInterval(waitSeconds);
|
|
15350
|
+
}
|
|
15351
|
+
}
|
|
15352
|
+
} finally {
|
|
15353
|
+
try {
|
|
15354
|
+
await engine.shutdown({ closeBrowser: false });
|
|
15355
|
+
} finally {
|
|
15356
|
+
await releaseQueueLock();
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
15359
|
+
const summary = summarizeChatOrganizationQueue(queue);
|
|
15360
|
+
writeChatOrganizationSummary({ queuePath, queue, options });
|
|
15361
|
+
let exitCode = 0;
|
|
15362
|
+
if (summary.failed > 0 || summary.pending > 0) exitCode = 1;
|
|
15363
|
+
process.exit(exitCode);
|
|
15364
|
+
};
|
|
14447
15365
|
var writeArchiveOutcomes = (outcomes, options) => {
|
|
14448
15366
|
if (options.json) {
|
|
14449
15367
|
process.stdout.write(`${JSON.stringify(outcomes)}
|
|
@@ -14556,7 +15474,7 @@ var runTui = async (opts) => {
|
|
|
14556
15474
|
console.log(`
|
|
14557
15475
|
Starting ai-browser-bridge (${label})...`);
|
|
14558
15476
|
const engine = await startEngine({
|
|
14559
|
-
repoPath: opts.repo ?
|
|
15477
|
+
repoPath: opts.repo ? resolve8(opts.repo) : void 0,
|
|
14560
15478
|
provider,
|
|
14561
15479
|
mcpPort: opts.port ? Number(opts.port) : void 0,
|
|
14562
15480
|
withBrowser: opts.browser !== false,
|
|
@@ -14679,7 +15597,7 @@ var runChatgptInspect = async (options) => {
|
|
|
14679
15597
|
};
|
|
14680
15598
|
var startFlowSession = async (options) => {
|
|
14681
15599
|
const engine = await startEngine({
|
|
14682
|
-
repoPath: options.repo ?
|
|
15600
|
+
repoPath: options.repo ? resolve8(options.repo) : void 0,
|
|
14683
15601
|
provider: "flow",
|
|
14684
15602
|
mcpPort: options.port ? Number(options.port) : void 0,
|
|
14685
15603
|
withBrowser: true,
|
|
@@ -14689,7 +15607,7 @@ var startFlowSession = async (options) => {
|
|
|
14689
15607
|
});
|
|
14690
15608
|
return { engine, page: requireBrowserPage(engine) };
|
|
14691
15609
|
};
|
|
14692
|
-
var defaultFlowOutDir = (repoRoot) =>
|
|
15610
|
+
var defaultFlowOutDir = (repoRoot) => join12(downloadsDir(repoRoot), "flow");
|
|
14693
15611
|
var requireClipId = (options, verb) => {
|
|
14694
15612
|
const id2 = options.id?.[0];
|
|
14695
15613
|
if (id2 === void 0 || id2 === "") return fail(`Usage: bridge flow ${verb} --id <clipId>`);
|
|
@@ -14721,7 +15639,7 @@ var runFlowDownload = async (options) => {
|
|
|
14721
15639
|
const { engine, page } = await startFlowSession(options);
|
|
14722
15640
|
const clips = await listClips(page);
|
|
14723
15641
|
const targets = options.id && options.id.length > 0 ? options.id : clips.map((clip) => clip.id);
|
|
14724
|
-
const outDir = options.out ?
|
|
15642
|
+
const outDir = options.out ? resolve8(options.out) : defaultFlowOutDir(engine.config.repoPath);
|
|
14725
15643
|
const results = [];
|
|
14726
15644
|
for (const id2 of targets) {
|
|
14727
15645
|
try {
|
|
@@ -14746,13 +15664,13 @@ var runFlowDownload = async (options) => {
|
|
|
14746
15664
|
process.exit(results.every((download) => download.ok) ? 0 : 1);
|
|
14747
15665
|
};
|
|
14748
15666
|
var runFlowGenerate = async (options) => {
|
|
14749
|
-
const startFramePath = options.start ?
|
|
15667
|
+
const startFramePath = options.start ? resolve8(options.start) : "";
|
|
14750
15668
|
const prompt = options.prompt === void 0 ? "" : options.prompt.trim();
|
|
14751
15669
|
if (!startFramePath || !prompt) {
|
|
14752
15670
|
fail("Usage: bridge flow generate --start <imagePath> --prompt <text> [--out <dir>]");
|
|
14753
15671
|
}
|
|
14754
15672
|
const { engine, page } = await startFlowSession(options);
|
|
14755
|
-
const outDir = options.out ?
|
|
15673
|
+
const outDir = options.out ? resolve8(options.out) : defaultFlowOutDir(engine.config.repoPath);
|
|
14756
15674
|
try {
|
|
14757
15675
|
const clip = await generateClipFromFrame(page, {
|
|
14758
15676
|
startFramePath,
|
|
@@ -14902,7 +15820,7 @@ var subcommandOpts = (command) => {
|
|
|
14902
15820
|
// src/features/terminal/registerCli.ts
|
|
14903
15821
|
var PROVIDER_OPTION = `Browser provider: ${PROVIDER_IDS.join(", ")} (default: ${DEFAULT_PROVIDER})`;
|
|
14904
15822
|
var registerCliCommands = (program) => {
|
|
14905
|
-
program.name("bridge").description("Terminal CLI that bridges ChatGPT or Gemini with local tools via MCP").version("0.
|
|
15823
|
+
program.name("bridge").description("Terminal CLI that bridges ChatGPT or Gemini with local tools via MCP").version("0.5.1").option("-r, --repo <path>", "Path to the target repository (default: cwd)").option("-p, --port <number>", "MCP server port (default: 8765)").option("--provider <name>", PROVIDER_OPTION).option("--no-browser", "Skip Chrome browser connection").action(async (_options, command) => {
|
|
14906
15824
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
14907
15825
|
command.outputHelp({ error: true });
|
|
14908
15826
|
process.exitCode = 1;
|
|
@@ -15031,6 +15949,15 @@ var registerWorkspaceCommands = (program) => {
|
|
|
15031
15949
|
withWorkspaceFlags(chat.command("archive [idOrTitle...]")).description("Archive one or more conversations (reversible \u2014 hides from the sidebar)").option("--id <id...>", "Archive several conversations by id in one session").action(
|
|
15032
15950
|
(chatParts, _options, command) => runChatArchive(chatParts.join(" "), command.optsWithGlobals())
|
|
15033
15951
|
);
|
|
15952
|
+
withWorkspaceFlags(chat.command("organize")).description("Run a resumable, rate-safe Conversation organization queue").requiredOption(
|
|
15953
|
+
"--plan <fileOrJson>",
|
|
15954
|
+
"JSON array of {conversation, project} tasks (inline, @file, or a path)"
|
|
15955
|
+
).option(
|
|
15956
|
+
"--interval <secondsOrRange>",
|
|
15957
|
+
"Seconds between UI operations, fixed or random range such as 10-20 (default: 30)"
|
|
15958
|
+
).option("--cooldown <seconds>", "Seconds to wait after rate limiting (default: 300)").option("--max-attempts <count>", "Attempts per Conversation before failing (default: 3)").option("--restart", "Restart this plan instead of resuming its persisted queue").option("--dry-run", "Validate and show the queue without opening Chrome or writing state").action(
|
|
15959
|
+
(_options, command) => runChatOrganize(command.optsWithGlobals())
|
|
15960
|
+
);
|
|
15034
15961
|
const task = program.command("task").description("List or schedule ChatGPT Tasks (ChatGPT only)");
|
|
15035
15962
|
withWorkspaceFlags(task.command("list")).description("List ChatGPT Scheduled tasks").action(
|
|
15036
15963
|
(_options, command) => runTaskList(command.optsWithGlobals())
|