ai-browser-bridge 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bridge.js CHANGED
@@ -11150,7 +11150,7 @@ var serveAskGatewayStdio = async (deps) => {
11150
11150
  // src/features/terminal/chatOrganizationQueue.ts
11151
11151
  import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
11152
11152
  import { existsSync } from "fs";
11153
- import { mkdir as mkdir7, open, readFile as readFile10, rename, unlink, writeFile as writeFile5 } from "fs/promises";
11153
+ import { mkdir as mkdir7, open, readdir as readdir5, readFile as readFile10, rename, unlink, writeFile as writeFile5 } from "fs/promises";
11154
11154
  import { dirname as dirname3, isAbsolute, join as join11, resolve as resolve7 } from "path";
11155
11155
  import { Schema as Schema8 } from "effect";
11156
11156
  var ChatOrganizationTaskSchema = Schema8.Struct({
@@ -11187,12 +11187,27 @@ var ChatOrganizationQueueItemSchema = Schema8.Union(
11187
11187
  CompletedQueueItemSchema,
11188
11188
  FailedQueueItemSchema
11189
11189
  );
11190
+ var ChatOrganizationPacingSchema = Schema8.Struct({
11191
+ currentSeconds: Schema8.Number.pipe(Schema8.int(), Schema8.positive()),
11192
+ successStreak: Schema8.Number.pipe(Schema8.int(), Schema8.nonNegative()),
11193
+ rateLimitCount: Schema8.Number.pipe(Schema8.int(), Schema8.nonNegative())
11194
+ });
11195
+ var ChatOrganizationVerificationSchema = Schema8.Struct({
11196
+ complete: Schema8.Boolean,
11197
+ inventoryComplete: Schema8.Boolean,
11198
+ remainingOrphans: Schema8.Number.pipe(Schema8.int(), Schema8.nonNegative()),
11199
+ plannedStillLoose: Schema8.Array(Schema8.String),
11200
+ verifiedAt: Schema8.String,
11201
+ error: Schema8.optional(Schema8.String)
11202
+ });
11190
11203
  var ChatOrganizationQueueSchema = Schema8.Struct({
11191
11204
  version: Schema8.Literal(1),
11192
11205
  fingerprint: Schema8.String,
11193
11206
  createdAt: Schema8.String,
11194
11207
  updatedAt: Schema8.String,
11195
- items: Schema8.Array(ChatOrganizationQueueItemSchema).pipe(Schema8.minItems(1))
11208
+ items: Schema8.Array(ChatOrganizationQueueItemSchema).pipe(Schema8.minItems(1)),
11209
+ pacing: Schema8.optional(ChatOrganizationPacingSchema),
11210
+ verification: Schema8.optional(ChatOrganizationVerificationSchema)
11196
11211
  });
11197
11212
  var ORGANIZATION_INTERVAL = /^(?<minimum>\d+)(?:-(?<maximum>\d+))?$/u;
11198
11213
  var CLOSED_BROWSER_SESSION = /(?:target page, context or browser has been closed|browser has been closed|context has been closed|page has been closed)/iu;
@@ -11223,6 +11238,40 @@ var nextChatOrganizationIntervalSeconds = (interval, randomFraction) => {
11223
11238
  const width = interval.maximumSeconds - interval.minimumSeconds + 1;
11224
11239
  return interval.minimumSeconds + Math.floor(randomFraction * width);
11225
11240
  };
11241
+ var initialChatOrganizationPacing = (currentSeconds) => ({
11242
+ currentSeconds,
11243
+ successStreak: 0,
11244
+ rateLimitCount: 0
11245
+ });
11246
+ var constrainChatOrganizationPacing = (pacing, options) => ({
11247
+ ...pacing,
11248
+ currentSeconds: Math.min(
11249
+ options.maximumSeconds,
11250
+ Math.max(options.minimumSeconds, pacing.currentSeconds)
11251
+ ),
11252
+ successStreak: Math.min(pacing.successStreak, options.speedUpAfter - 1)
11253
+ });
11254
+ var adaptiveChatOrganizationPacingAfterSuccess = (pacing, options) => {
11255
+ const successStreak = pacing.successStreak + 1;
11256
+ if (successStreak < options.speedUpAfter) return { ...pacing, successStreak };
11257
+ return {
11258
+ ...pacing,
11259
+ currentSeconds: Math.min(
11260
+ pacing.currentSeconds,
11261
+ Math.max(options.minimumSeconds, Math.floor(pacing.currentSeconds * 0.8))
11262
+ ),
11263
+ successStreak: 0
11264
+ };
11265
+ };
11266
+ var adaptiveChatOrganizationPacingAfterRateLimit = (pacing, options) => ({
11267
+ currentSeconds: Math.min(options.maximumSeconds, Math.ceil(pacing.currentSeconds * 1.5)),
11268
+ successStreak: 0,
11269
+ rateLimitCount: pacing.rateLimitCount + 1
11270
+ });
11271
+ var nextAdaptiveChatOrganizationIntervalSeconds = (pacing, randomFraction) => {
11272
+ const jitter = Math.max(1, Math.ceil(pacing.currentSeconds * 0.2));
11273
+ return pacing.currentSeconds + Math.floor(randomFraction * (jitter + 1));
11274
+ };
11226
11275
  var chatOrganizationIntervalLabel = (interval) => {
11227
11276
  if (interval.minimumSeconds === interval.maximumSeconds) return `${interval.minimumSeconds}s`;
11228
11277
  return `${interval.minimumSeconds}-${interval.maximumSeconds}s`;
@@ -11299,11 +11348,98 @@ var persistChatOrganizationQueue = async (queuePath, queue) => {
11299
11348
  }
11300
11349
  }
11301
11350
  };
11351
+ var updateChatOrganizationQueuePacing = (input) => ({
11352
+ ...input.queue,
11353
+ pacing: input.pacing,
11354
+ updatedAt: input.timestamp
11355
+ });
11356
+ var updateChatOrganizationQueueVerification = (input) => ({
11357
+ ...input.queue,
11358
+ verification: input.verification,
11359
+ updatedAt: input.timestamp
11360
+ });
11302
11361
  var errorCodeOf = (error) => {
11303
11362
  if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
11304
11363
  if (typeof error.code !== "string") return void 0;
11305
11364
  return error.code;
11306
11365
  };
11366
+ var chatOrganizationPausePath = (queuePath) => `${queuePath}.pause`;
11367
+ var chatOrganizationQueueIsPaused = async (queuePath) => existsSync(chatOrganizationPausePath(queuePath));
11368
+ var pauseChatOrganizationQueue = async (queuePath, timestamp) => {
11369
+ await mkdir7(dirname3(queuePath), { recursive: true });
11370
+ await writeFile5(
11371
+ chatOrganizationPausePath(queuePath),
11372
+ `${JSON.stringify({ pausedAt: timestamp })}
11373
+ `,
11374
+ "utf8"
11375
+ );
11376
+ };
11377
+ var clearChatOrganizationQueuePause = async (queuePath) => {
11378
+ try {
11379
+ await unlink(chatOrganizationPausePath(queuePath));
11380
+ } catch (error) {
11381
+ if (errorCodeOf(error) !== "ENOENT") throw error;
11382
+ }
11383
+ };
11384
+ var organizationQueueIsRunning = async (queuePath) => {
11385
+ let owner;
11386
+ try {
11387
+ owner = JSON.parse(await readFile10(`${queuePath}.lock`, "utf8"));
11388
+ } catch {
11389
+ return { running: false };
11390
+ }
11391
+ if (typeof owner !== "object" || owner === null || !("pid" in owner)) {
11392
+ return { running: false };
11393
+ }
11394
+ if (typeof owner.pid !== "number" || !Number.isInteger(owner.pid)) return { running: false };
11395
+ try {
11396
+ process.kill(owner.pid, 0);
11397
+ return { running: true, ownerPid: owner.pid };
11398
+ } catch (error) {
11399
+ if (errorCodeOf(error) === "ESRCH") return { running: false, ownerPid: owner.pid };
11400
+ return { running: true, ownerPid: owner.pid };
11401
+ }
11402
+ };
11403
+ var readChatOrganizationQueue = async (queuePath) => {
11404
+ const stored = JSON.parse(await readFile10(queuePath, "utf8"));
11405
+ return Schema8.decodeUnknownSync(ChatOrganizationQueueSchema)(stored);
11406
+ };
11407
+ var resolveChatOrganizationQueue = async (repoRoot, selector) => {
11408
+ const queueDirectory = join11(bridgeDir(repoRoot), "chat-organization-queues");
11409
+ let queuePath;
11410
+ if (selector?.trim()) {
11411
+ const trimmed = selector.trim();
11412
+ if (isAbsolute(trimmed) || trimmed.includes("/")) queuePath = resolve7(repoRoot, trimmed);
11413
+ else queuePath = join11(queueDirectory, trimmed.endsWith(".json") ? trimmed : `${trimmed}.json`);
11414
+ } else {
11415
+ let names;
11416
+ try {
11417
+ names = (await readdir5(queueDirectory)).filter((name) => /^[a-f0-9]{16}\.json$/u.test(name));
11418
+ } catch (error) {
11419
+ if (errorCodeOf(error) === "ENOENT") names = [];
11420
+ else throw error;
11421
+ }
11422
+ const queues = await Promise.all(
11423
+ names.map(async (name) => {
11424
+ const path2 = join11(queueDirectory, name);
11425
+ return { path: path2, queue: await readChatOrganizationQueue(path2) };
11426
+ })
11427
+ );
11428
+ queues.sort((left, right) => right.queue.updatedAt.localeCompare(left.queue.updatedAt));
11429
+ queuePath = queues[0]?.path;
11430
+ }
11431
+ if (queuePath === void 0 || !existsSync(queuePath)) {
11432
+ throw new Error("No persisted ChatGPT organization queue was found.");
11433
+ }
11434
+ const queue = await readChatOrganizationQueue(queuePath);
11435
+ const ownership = await organizationQueueIsRunning(queuePath);
11436
+ return {
11437
+ path: queuePath,
11438
+ queue,
11439
+ paused: await chatOrganizationQueueIsPaused(queuePath),
11440
+ ...ownership
11441
+ };
11442
+ };
11307
11443
  var removeStaleQueueLock = async (lockPath) => {
11308
11444
  let owner;
11309
11445
  try {
@@ -11434,6 +11570,47 @@ var summarizeChatOrganizationQueue = (queue) => {
11434
11570
  }
11435
11571
  return summary;
11436
11572
  };
11573
+ var conversationIdentity = (value) => {
11574
+ const trimmed = value.trim();
11575
+ const conversationPath = /\/c\/(?<id>[^/?#]+)/u.exec(trimmed)?.groups?.id;
11576
+ if (conversationPath !== void 0) return conversationPath;
11577
+ return trimmed;
11578
+ };
11579
+ var reopenChatOrganizationQueueItems = (input) => {
11580
+ const targets = new Set(input.conversations.map(conversationIdentity));
11581
+ let changed = false;
11582
+ const items = input.queue.items.map((item) => {
11583
+ if (item.status !== "moved" && item.status !== "already-filed" || item.attempts >= input.maxAttempts || !targets.has(conversationIdentity(item.conversation))) {
11584
+ return item;
11585
+ }
11586
+ changed = true;
11587
+ return {
11588
+ status: "pending",
11589
+ conversation: item.conversation,
11590
+ project: item.project,
11591
+ attempts: item.attempts,
11592
+ lastReason: "Full-history audit found the Conversation still loose."
11593
+ };
11594
+ });
11595
+ if (!changed) return input.queue;
11596
+ return { ...input.queue, items, updatedAt: input.timestamp };
11597
+ };
11598
+ var chatOrganizationVerificationFrom = (input) => {
11599
+ const orphanIdentities = /* @__PURE__ */ new Set();
11600
+ for (const orphan of input.orphans) {
11601
+ orphanIdentities.add(conversationIdentity(orphan.id));
11602
+ orphanIdentities.add(orphan.title.trim());
11603
+ }
11604
+ const plannedStillLoose = input.queue.items.filter((item) => orphanIdentities.has(conversationIdentity(item.conversation))).map((item) => item.conversation);
11605
+ const summary = summarizeChatOrganizationQueue(input.queue);
11606
+ return {
11607
+ complete: summary.pending === 0 && summary.failed === 0 && plannedStillLoose.length === 0,
11608
+ inventoryComplete: true,
11609
+ remainingOrphans: input.orphans.length,
11610
+ plannedStillLoose,
11611
+ verifiedAt: input.timestamp
11612
+ };
11613
+ };
11437
11614
 
11438
11615
  // src/features/terminal/providerLabel.ts
11439
11616
  var providerDisplayName = (id2) => {
@@ -12000,7 +12177,7 @@ var applyInputSuggestion = (input, group, index = 0) => {
12000
12177
  };
12001
12178
 
12002
12179
  // src/features/terminal/tui/composer/fileAutocompleteComplete.ts
12003
- import { readdir as readdir5 } from "fs/promises";
12180
+ import { readdir as readdir6 } from "fs/promises";
12004
12181
 
12005
12182
  // src/features/terminal/tui/composer/fileAutocompleteTypes.ts
12006
12183
  var DEFAULT_COMPLETION_LIMIT = 20;
@@ -12105,7 +12282,7 @@ var completionSearchDirectory = (input) => {
12105
12282
  };
12106
12283
  var readCompletionMatches = async (input) => {
12107
12284
  try {
12108
- const dirents = await readdir5(input.absoluteSearchDir, { withFileTypes: true });
12285
+ const dirents = await readdir6(input.absoluteSearchDir, { withFileTypes: true });
12109
12286
  return dirents.filter((dirent) => dirent.isDirectory() || dirent.isFile()).filter((dirent) => !IGNORED_COMPLETION_ENTRIES.has(dirent.name)).filter((dirent) => input.namePrefix.startsWith(".") || !dirent.name.startsWith(".")).filter((dirent) => dirent.name.startsWith(input.namePrefix)).map((dirent) => mapDirent({ dirent, dirPrefix: input.dirPrefix })).sort((left, right) => compareCompletionMatches(left, right)).slice(0, input.limit);
12110
12287
  } catch {
12111
12288
  return [];
@@ -12146,7 +12323,7 @@ var hasTrailingWhitespace = (input) => {
12146
12323
  };
12147
12324
 
12148
12325
  // src/features/terminal/tui/suggestions/repoPathSuggestions.ts
12149
- import { readdir as readdir6 } from "fs/promises";
12326
+ import { readdir as readdir7 } from "fs/promises";
12150
12327
  import { extname, sep as sep3 } from "path";
12151
12328
  var IGNORED_COMPLETION_ENTRIES2 = /* @__PURE__ */ new Set([
12152
12329
  ".git",
@@ -12191,7 +12368,7 @@ var searchDirectory = (input) => {
12191
12368
  };
12192
12369
  var listMatchingEntries = async (input) => {
12193
12370
  try {
12194
- const entries = await readdir6(input.absoluteSearchDir, { withFileTypes: true });
12371
+ const entries = await readdir7(input.absoluteSearchDir, { withFileTypes: true });
12195
12372
  return entries.filter((entry) => isCompletableEntry({ name: entry.name, namePrefix: input.namePrefix })).filter((entry) => matchesKind({ entry, kind: input.kind, namePrefix: input.namePrefix })).map((entry) => pathEntrySuggestion(entry.name, input.dirPrefix, entry.isDirectory())).sort(comparePathSuggestions).slice(0, input.limit);
12196
12373
  } catch {
12197
12374
  return [];
@@ -15082,6 +15259,9 @@ var runChatMove = async (chat, options) => {
15082
15259
  process.exit(outcomes.every((outcome) => outcome.moved || outcome.alreadyFiled) ? 0 : 1);
15083
15260
  };
15084
15261
  var DEFAULT_ORGANIZATION_INTERVAL_SECONDS = 60;
15262
+ var DEFAULT_ORGANIZATION_MIN_INTERVAL_SECONDS = 15;
15263
+ var DEFAULT_ORGANIZATION_MAX_INTERVAL_SECONDS = 180;
15264
+ var DEFAULT_ORGANIZATION_SPEED_UP_AFTER = 3;
15085
15265
  var DEFAULT_ORGANIZATION_COOLDOWN_SECONDS = 600;
15086
15266
  var DEFAULT_ORGANIZATION_MAX_ATTEMPTS = 4;
15087
15267
  var MAX_CONSECUTIVE_ORGANIZATION_COOLDOWNS = 12;
@@ -15102,8 +15282,16 @@ var organizationIntervalOption = (value) => {
15102
15282
  return fail(error instanceof Error ? error.message : String(error));
15103
15283
  }
15104
15284
  };
15105
- var waitForOrganizationInterval = async (seconds) => {
15106
- await new Promise((complete) => setTimeout(complete, seconds * 1e3));
15285
+ var waitForOrganizationInterval = async (seconds, queuePath) => {
15286
+ let remainingMilliseconds = seconds * 1e3;
15287
+ while (remainingMilliseconds > 0) {
15288
+ if (await chatOrganizationQueueIsPaused(queuePath)) return "paused";
15289
+ const slice = Math.min(1e3, remainingMilliseconds);
15290
+ await new Promise((complete) => setTimeout(complete, slice));
15291
+ remainingMilliseconds -= slice;
15292
+ }
15293
+ if (await chatOrganizationQueueIsPaused(queuePath)) return "paused";
15294
+ return "elapsed";
15107
15295
  };
15108
15296
  var writeChatOrganizationProgress = (message, options) => {
15109
15297
  if (options.json) {
@@ -15121,18 +15309,29 @@ var moveFailureReason = (outcome) => {
15121
15309
  var writeChatOrganizationSummary = (input) => {
15122
15310
  const summary = summarizeChatOrganizationQueue(input.queue);
15123
15311
  if (input.options.json) {
15124
- process.stdout.write(`${JSON.stringify({ queuePath: input.queuePath, summary })}
15125
- `);
15312
+ process.stdout.write(
15313
+ `${JSON.stringify({
15314
+ queuePath: input.queuePath,
15315
+ state: input.paused ? "paused" : "finished",
15316
+ summary,
15317
+ pacing: input.queue.pacing,
15318
+ verification: input.queue.verification
15319
+ })}
15320
+ `
15321
+ );
15126
15322
  return;
15127
15323
  }
15128
15324
  process.stdout.write(
15129
15325
  `${[
15130
- "Organization queue finished.",
15326
+ input.paused ? "Organization queue paused." : "Organization queue finished.",
15131
15327
  `Moved: ${summary.moved}`,
15132
15328
  `Already filed: ${summary.alreadyFiled}`,
15329
+ `Pending: ${summary.pending}`,
15133
15330
  `Failed: ${summary.failed}`,
15331
+ input.queue.pacing === void 0 ? void 0 : `Adaptive pace: ${input.queue.pacing.currentSeconds}s base (${input.queue.pacing.rateLimitCount} rate limits)`,
15332
+ input.queue.verification === void 0 ? void 0 : `Full-history verification: ${input.queue.verification.complete ? "complete" : "incomplete"}; ${input.queue.verification.remainingOrphans} intentional/remaining orphans`,
15134
15333
  `Queue state: ${input.queuePath}`
15135
- ].join("\n")}
15334
+ ].filter((line) => line !== void 0).join("\n")}
15136
15335
  `
15137
15336
  );
15138
15337
  };
@@ -15143,6 +15342,10 @@ var writeChatOrganizationDryRun = (input) => {
15143
15342
  interval: input.interval,
15144
15343
  cooldownSeconds: input.cooldownSeconds,
15145
15344
  maxAttempts: input.maxAttempts,
15345
+ adaptive: input.adaptive,
15346
+ minimumIntervalSeconds: input.minimumIntervalSeconds,
15347
+ maximumIntervalSeconds: input.maximumIntervalSeconds,
15348
+ speedUpAfter: input.speedUpAfter,
15146
15349
  tasks: input.tasks
15147
15350
  };
15148
15351
  if (input.options.json) {
@@ -15154,6 +15357,7 @@ var writeChatOrganizationDryRun = (input) => {
15154
15357
  `${[
15155
15358
  `Organization queue dry run: ${input.tasks.length} Conversations`,
15156
15359
  `Interval: ${chatOrganizationIntervalLabel(input.interval)}`,
15360
+ `Adaptive pacing: ${input.adaptive ? `yes (${input.minimumIntervalSeconds}-${input.maximumIntervalSeconds}s; speed up after ${input.speedUpAfter} successes)` : "no"}`,
15157
15361
  `Rate-limit cooldown: ${input.cooldownSeconds}s`,
15158
15362
  `Attempts per Conversation: ${input.maxAttempts}`,
15159
15363
  `Resume state: ${input.queuePath}`,
@@ -15208,6 +15412,132 @@ var recordChatOrganizationAttempt = async (input) => {
15208
15412
  await persistChatOrganizationQueue(input.queuePath, queue);
15209
15413
  return queue;
15210
15414
  };
15415
+ var persistChatOrganizationPacing = async (input) => {
15416
+ const queue = updateChatOrganizationQueuePacing({
15417
+ queue: input.queue,
15418
+ pacing: input.pacing,
15419
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
15420
+ });
15421
+ await persistChatOrganizationQueue(input.queuePath, queue);
15422
+ return queue;
15423
+ };
15424
+ var verifyCompletedChatOrganizationQueue = async (input) => {
15425
+ writeChatOrganizationProgress(
15426
+ "Verifying the completed plan against an exhaustive full-history orphan scan.",
15427
+ input.options
15428
+ );
15429
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
15430
+ let verification;
15431
+ try {
15432
+ const orphans = await input.workspace.engine.getOrchestrator().listConversations({ orphans: true });
15433
+ verification = chatOrganizationVerificationFrom({
15434
+ queue: input.queue,
15435
+ orphans,
15436
+ timestamp
15437
+ });
15438
+ } catch (error) {
15439
+ verification = {
15440
+ complete: false,
15441
+ inventoryComplete: false,
15442
+ remainingOrphans: 0,
15443
+ plannedStillLoose: [],
15444
+ verifiedAt: timestamp,
15445
+ error: error instanceof Error ? error.message : String(error)
15446
+ };
15447
+ }
15448
+ let queue = updateChatOrganizationQueueVerification({
15449
+ queue: input.queue,
15450
+ verification,
15451
+ timestamp
15452
+ });
15453
+ if (verification.plannedStillLoose.length > 0) {
15454
+ queue = reopenChatOrganizationQueueItems({
15455
+ queue,
15456
+ conversations: verification.plannedStillLoose,
15457
+ maxAttempts: input.maxAttempts,
15458
+ timestamp
15459
+ });
15460
+ }
15461
+ await persistChatOrganizationQueue(input.queuePath, queue);
15462
+ if (verification.inventoryComplete) {
15463
+ writeChatOrganizationProgress(
15464
+ `Full-history scan reached the stable end: ${verification.remainingOrphans} loose Conversations remain; ${verification.plannedStillLoose.length} planned Conversations are still loose.`,
15465
+ input.options
15466
+ );
15467
+ } else {
15468
+ writeChatOrganizationProgress(
15469
+ `Full-history verification could not prove completion: ${verification.error ?? "unknown scan failure"}`,
15470
+ input.options
15471
+ );
15472
+ }
15473
+ return queue;
15474
+ };
15475
+ var organizationQueueState = (input) => {
15476
+ if (input.running) return input.paused ? "pausing" : "running";
15477
+ if (input.paused) return "paused";
15478
+ if (input.summary.pending === 0) return input.summary.failed > 0 ? "failed" : "completed";
15479
+ return "idle";
15480
+ };
15481
+ var runChatOrganizeStatus = async (options) => {
15482
+ const selected = await resolveChatOrganizationQueue(repositoryRoot(options.repo), options.queue);
15483
+ const summary = summarizeChatOrganizationQueue(selected.queue);
15484
+ const state = organizationQueueState({
15485
+ paused: selected.paused,
15486
+ running: selected.running,
15487
+ summary
15488
+ });
15489
+ const status = {
15490
+ state,
15491
+ queuePath: selected.path,
15492
+ ownerPid: selected.ownerPid,
15493
+ summary,
15494
+ pacing: selected.queue.pacing,
15495
+ verification: selected.queue.verification
15496
+ };
15497
+ if (options.json) process.stdout.write(`${JSON.stringify(status)}
15498
+ `);
15499
+ else {
15500
+ process.stdout.write(
15501
+ `${[
15502
+ `Organization queue: ${state}`,
15503
+ `Moved: ${summary.moved}`,
15504
+ `Already filed: ${summary.alreadyFiled}`,
15505
+ `Pending: ${summary.pending}`,
15506
+ `Failed: ${summary.failed}`,
15507
+ selected.queue.pacing === void 0 ? "Adaptive pace: not initialized" : `Adaptive pace: ${selected.queue.pacing.currentSeconds}s base (${selected.queue.pacing.successStreak} success streak; ${selected.queue.pacing.rateLimitCount} rate limits)`,
15508
+ selected.queue.verification === void 0 ? "Full-history verification: not run" : `Full-history verification: ${selected.queue.verification.complete ? "complete" : "incomplete"}`,
15509
+ `Queue state: ${selected.path}`
15510
+ ].join("\n")}
15511
+ `
15512
+ );
15513
+ }
15514
+ };
15515
+ var runChatOrganizePause = async (options) => {
15516
+ const selected = await resolveChatOrganizationQueue(repositoryRoot(options.repo), options.queue);
15517
+ await pauseChatOrganizationQueue(selected.path, (/* @__PURE__ */ new Date()).toISOString());
15518
+ const message = selected.running ? "Pause requested; the active UI operation will finish before the queue releases its lock." : "Organization queue paused.";
15519
+ if (options.json) {
15520
+ process.stdout.write(
15521
+ `${JSON.stringify({ state: selected.running ? "pausing" : "paused", queuePath: selected.path })}
15522
+ `
15523
+ );
15524
+ } else process.stdout.write(`${message}
15525
+ Queue state: ${selected.path}
15526
+ `);
15527
+ };
15528
+ var runChatOrganizeResume = async (options) => {
15529
+ assertChatgptWorkspace(options);
15530
+ const selected = await resolveChatOrganizationQueue(repositoryRoot(options.repo), options.queue);
15531
+ if (selected.running) return fail(`Organization queue is already running: ${selected.path}`);
15532
+ await clearChatOrganizationQueuePause(selected.path);
15533
+ const plan = JSON.stringify(
15534
+ selected.queue.items.map((item) => ({
15535
+ conversation: item.conversation,
15536
+ project: item.project
15537
+ }))
15538
+ );
15539
+ return runChatOrganize({ ...options, plan, restart: false, dryRun: false });
15540
+ };
15211
15541
  var runChatOrganize = async (options) => {
15212
15542
  assertChatgptWorkspace(options);
15213
15543
  const plan = options.plan;
@@ -15215,6 +15545,25 @@ var runChatOrganize = async (options) => {
15215
15545
  return fail("Usage: bridge chat organize --plan <fileOrJson>");
15216
15546
  }
15217
15547
  const interval = organizationIntervalOption(options.interval);
15548
+ const adaptive = options.adaptive !== false;
15549
+ const minimumIntervalSeconds = positiveIntegerOption(
15550
+ options.minInterval,
15551
+ "--min-interval",
15552
+ DEFAULT_ORGANIZATION_MIN_INTERVAL_SECONDS
15553
+ );
15554
+ const maximumIntervalSeconds = positiveIntegerOption(
15555
+ options.maxInterval,
15556
+ "--max-interval",
15557
+ DEFAULT_ORGANIZATION_MAX_INTERVAL_SECONDS
15558
+ );
15559
+ if (maximumIntervalSeconds < minimumIntervalSeconds) {
15560
+ return fail("--max-interval must be greater than or equal to --min-interval.");
15561
+ }
15562
+ const speedUpAfter = positiveIntegerOption(
15563
+ options.speedUpAfter,
15564
+ "--speed-up-after",
15565
+ DEFAULT_ORGANIZATION_SPEED_UP_AFTER
15566
+ );
15218
15567
  const cooldownSeconds = positiveIntegerOption(
15219
15568
  options.cooldown,
15220
15569
  "--cooldown",
@@ -15240,6 +15589,10 @@ var runChatOrganize = async (options) => {
15240
15589
  interval,
15241
15590
  cooldownSeconds,
15242
15591
  maxAttempts,
15592
+ adaptive,
15593
+ minimumIntervalSeconds,
15594
+ maximumIntervalSeconds,
15595
+ speedUpAfter,
15243
15596
  options
15244
15597
  });
15245
15598
  process.exit(0);
@@ -15259,6 +15612,26 @@ var runChatOrganize = async (options) => {
15259
15612
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
15260
15613
  });
15261
15614
  queue = opened.queue;
15615
+ if (adaptive) {
15616
+ const pacing = queue.pacing === void 0 ? initialChatOrganizationPacing(
15617
+ Math.min(
15618
+ maximumIntervalSeconds,
15619
+ Math.max(
15620
+ minimumIntervalSeconds,
15621
+ nextChatOrganizationIntervalSeconds(interval, Math.random())
15622
+ )
15623
+ )
15624
+ ) : constrainChatOrganizationPacing(queue.pacing, {
15625
+ minimumSeconds: minimumIntervalSeconds,
15626
+ maximumSeconds: maximumIntervalSeconds,
15627
+ speedUpAfter
15628
+ });
15629
+ queue = await persistChatOrganizationPacing({
15630
+ queue,
15631
+ queuePath,
15632
+ pacing
15633
+ });
15634
+ }
15262
15635
  } catch (error) {
15263
15636
  await releaseQueueLock();
15264
15637
  return fail(
@@ -15270,10 +15643,13 @@ var runChatOrganize = async (options) => {
15270
15643
  `Organization queue: ${initialSummary.pending} pending of ${initialSummary.total}; state ${queuePath}`,
15271
15644
  options
15272
15645
  );
15273
- if (initialSummary.pending === 0) {
15646
+ const needsVerification = options.verify !== false && queue.verification?.complete !== true;
15647
+ if (initialSummary.pending === 0 && !needsVerification) {
15274
15648
  writeChatOrganizationSummary({ queuePath, queue, options });
15275
15649
  await releaseQueueLock();
15276
- process.exit(initialSummary.failed === 0 ? 0 : 1);
15650
+ process.exit(
15651
+ initialSummary.failed === 0 && (options.verify === false || queue.verification?.complete === true) ? 0 : 1
15652
+ );
15277
15653
  }
15278
15654
  let workspace;
15279
15655
  try {
@@ -15283,106 +15659,171 @@ var runChatOrganize = async (options) => {
15283
15659
  throw error;
15284
15660
  }
15285
15661
  let workspaceWasShutdown = false;
15662
+ let paused = false;
15286
15663
  try {
15287
- let index = nextChatOrganizationQueueIndex(queue);
15288
15664
  let consecutiveRateLimitCooldowns = 0;
15289
- while (index !== void 0) {
15290
- const item = queue.items[index];
15291
- if (item === void 0 || item.status !== "pending") {
15292
- index = nextChatOrganizationQueueIndex(queue);
15293
- continue;
15294
- }
15295
- if (await acknowledgeChatGptHistoryRateLimit(workspace.page)) {
15296
- consecutiveRateLimitCooldowns += 1;
15297
- if (consecutiveRateLimitCooldowns >= MAX_CONSECUTIVE_ORGANIZATION_COOLDOWNS) {
15665
+ let halted = false;
15666
+ while (!paused && !halted) {
15667
+ let index = nextChatOrganizationQueueIndex(queue);
15668
+ while (index !== void 0) {
15669
+ if (await chatOrganizationQueueIsPaused(queuePath)) {
15670
+ paused = true;
15298
15671
  writeChatOrganizationProgress(
15299
- `ChatGPT remained rate-limited after ${consecutiveRateLimitCooldowns} cooldowns; leaving the queue pending for a later resume.`,
15672
+ "Organization queue pause acknowledged; no new UI operation will start.",
15300
15673
  options
15301
15674
  );
15302
15675
  break;
15303
15676
  }
15677
+ const item = queue.items[index];
15678
+ if (item === void 0 || item.status !== "pending") {
15679
+ index = nextChatOrganizationQueueIndex(queue);
15680
+ continue;
15681
+ }
15682
+ if (await acknowledgeChatGptHistoryRateLimit(workspace.page)) {
15683
+ consecutiveRateLimitCooldowns += 1;
15684
+ if (adaptive && queue.pacing !== void 0) {
15685
+ queue = await persistChatOrganizationPacing({
15686
+ queue,
15687
+ queuePath,
15688
+ pacing: adaptiveChatOrganizationPacingAfterRateLimit(queue.pacing, {
15689
+ maximumSeconds: maximumIntervalSeconds
15690
+ })
15691
+ });
15692
+ }
15693
+ if (consecutiveRateLimitCooldowns >= MAX_CONSECUTIVE_ORGANIZATION_COOLDOWNS) {
15694
+ writeChatOrganizationProgress(
15695
+ `ChatGPT remained rate-limited after ${consecutiveRateLimitCooldowns} cooldowns; leaving the queue pending for a later resume.`,
15696
+ options
15697
+ );
15698
+ halted = true;
15699
+ break;
15700
+ }
15701
+ writeChatOrganizationProgress(
15702
+ `ChatGPT rate limit detected; cooling down for ${cooldownSeconds}s before continuing${queue.pacing === void 0 ? "." : ` at a slower ${queue.pacing.currentSeconds}s adaptive pace.`}`,
15703
+ options
15704
+ );
15705
+ if (await waitForOrganizationInterval(cooldownSeconds, queuePath) === "paused") {
15706
+ paused = true;
15707
+ break;
15708
+ }
15709
+ continue;
15710
+ }
15711
+ consecutiveRateLimitCooldowns = 0;
15304
15712
  writeChatOrganizationProgress(
15305
- `ChatGPT rate limit detected; cooling down for ${cooldownSeconds}s before continuing.`,
15306
- options
15307
- );
15308
- await waitForOrganizationInterval(cooldownSeconds);
15309
- continue;
15310
- }
15311
- consecutiveRateLimitCooldowns = 0;
15312
- writeChatOrganizationProgress(
15313
- `Moving "${item.conversation}" -> ${item.project} (attempt ${item.attempts + 1}/${maxAttempts})`,
15314
- options
15315
- );
15316
- let moveOutcome;
15317
- let thrownReason;
15318
- try {
15319
- moveOutcome = await moveChatToProject(workspace.page, {
15320
- chat: item.conversation,
15321
- project: item.project
15322
- });
15323
- } catch (error) {
15324
- thrownReason = error instanceof Error ? error.message : String(error);
15325
- }
15326
- const sessionClosed = chatOrganizationSessionWasClosed(thrownReason);
15327
- const rateLimited = await acknowledgeChatGptHistoryRateLimit(workspace.page);
15328
- queue = await recordChatOrganizationAttempt({
15329
- queue,
15330
- queuePath,
15331
- index,
15332
- maxAttempts,
15333
- sessionClosed,
15334
- rateLimited,
15335
- moveOutcome,
15336
- thrownReason
15337
- });
15338
- if (sessionClosed) {
15339
- writeChatOrganizationProgress(
15340
- "ChatGPT browser session closed; reopening it without consuming a retry attempt.",
15713
+ `Moving "${item.conversation}" -> ${item.project} (attempt ${item.attempts + 1}/${maxAttempts})`,
15341
15714
  options
15342
15715
  );
15343
- await workspace.engine.shutdown({ closeBrowser: false });
15344
- workspaceWasShutdown = true;
15716
+ let moveOutcome;
15717
+ let thrownReason;
15345
15718
  try {
15346
- workspace = await startWorkspaceSession(options);
15347
- workspaceWasShutdown = false;
15719
+ moveOutcome = await moveChatToProject(workspace.page, {
15720
+ chat: item.conversation,
15721
+ project: item.project
15722
+ });
15348
15723
  } catch (error) {
15724
+ thrownReason = error instanceof Error ? error.message : String(error);
15725
+ }
15726
+ const sessionClosed = chatOrganizationSessionWasClosed(thrownReason);
15727
+ const rateLimited = await acknowledgeChatGptHistoryRateLimit(workspace.page);
15728
+ queue = await recordChatOrganizationAttempt({
15729
+ queue,
15730
+ queuePath,
15731
+ index,
15732
+ maxAttempts,
15733
+ sessionClosed,
15734
+ rateLimited,
15735
+ moveOutcome,
15736
+ thrownReason
15737
+ });
15738
+ if (adaptive && queue.pacing !== void 0) {
15739
+ let pacing = queue.pacing;
15740
+ if (rateLimited) {
15741
+ pacing = adaptiveChatOrganizationPacingAfterRateLimit(pacing, {
15742
+ maximumSeconds: maximumIntervalSeconds
15743
+ });
15744
+ } else if (moveOutcome?.moved || moveOutcome?.alreadyFiled) {
15745
+ pacing = adaptiveChatOrganizationPacingAfterSuccess(pacing, {
15746
+ minimumSeconds: minimumIntervalSeconds,
15747
+ speedUpAfter
15748
+ });
15749
+ } else if (pacing.successStreak > 0) {
15750
+ pacing = { ...pacing, successStreak: 0 };
15751
+ }
15752
+ queue = await persistChatOrganizationPacing({ queue, queuePath, pacing });
15753
+ }
15754
+ if (sessionClosed) {
15349
15755
  writeChatOrganizationProgress(
15350
- `Could not reopen the ChatGPT browser session; leaving the queue pending: ${error instanceof Error ? error.message : String(error)}`,
15756
+ "ChatGPT browser session closed; reopening it without consuming a retry attempt.",
15351
15757
  options
15352
15758
  );
15353
- break;
15759
+ await workspace.engine.shutdown({ closeBrowser: false });
15760
+ workspaceWasShutdown = true;
15761
+ try {
15762
+ workspace = await startWorkspaceSession(options);
15763
+ workspaceWasShutdown = false;
15764
+ } catch (error) {
15765
+ writeChatOrganizationProgress(
15766
+ `Could not reopen the ChatGPT browser session; leaving the queue pending: ${error instanceof Error ? error.message : String(error)}`,
15767
+ options
15768
+ );
15769
+ halted = true;
15770
+ break;
15771
+ }
15772
+ index = nextChatOrganizationQueueIndex(queue);
15773
+ continue;
15774
+ }
15775
+ const recorded = queue.items[index];
15776
+ if (recorded?.status === "moved") {
15777
+ writeChatOrganizationProgress(`Moved "${recorded.conversation}".`, options);
15778
+ } else if (recorded?.status === "already-filed") {
15779
+ writeChatOrganizationProgress(`Already filed "${recorded.conversation}".`, options);
15780
+ } else if (recorded?.status === "failed") {
15781
+ writeChatOrganizationProgress(
15782
+ `Failed "${recorded.conversation}" after ${recorded.attempts} attempts: ${recorded.reason}`,
15783
+ options
15784
+ );
15785
+ } else if (recorded?.status === "pending") {
15786
+ let reason = "unknown failure";
15787
+ if (recorded.lastReason !== void 0) reason = recorded.lastReason;
15788
+ writeChatOrganizationProgress(
15789
+ `Will retry "${recorded.conversation}": ${reason}`,
15790
+ options
15791
+ );
15792
+ }
15793
+ let waitSeconds = adaptive && queue.pacing !== void 0 ? nextAdaptiveChatOrganizationIntervalSeconds(queue.pacing, Math.random()) : nextChatOrganizationIntervalSeconds(interval, Math.random());
15794
+ if (rateLimited) {
15795
+ consecutiveRateLimitCooldowns += 1;
15796
+ waitSeconds = cooldownSeconds;
15354
15797
  }
15355
15798
  index = nextChatOrganizationQueueIndex(queue);
15356
- continue;
15357
- }
15358
- const recorded = queue.items[index];
15359
- if (recorded?.status === "moved") {
15360
- writeChatOrganizationProgress(`Moved "${recorded.conversation}".`, options);
15361
- } else if (recorded?.status === "already-filed") {
15362
- writeChatOrganizationProgress(`Already filed "${recorded.conversation}".`, options);
15363
- } else if (recorded?.status === "failed") {
15364
- writeChatOrganizationProgress(
15365
- `Failed "${recorded.conversation}" after ${recorded.attempts} attempts: ${recorded.reason}`,
15366
- options
15367
- );
15368
- } else if (recorded?.status === "pending") {
15369
- let reason = "unknown failure";
15370
- if (recorded.lastReason !== void 0) reason = recorded.lastReason;
15371
- writeChatOrganizationProgress(`Will retry "${recorded.conversation}": ${reason}`, options);
15372
- }
15373
- let waitSeconds = nextChatOrganizationIntervalSeconds(interval, Math.random());
15374
- if (rateLimited) {
15375
- consecutiveRateLimitCooldowns += 1;
15376
- waitSeconds = cooldownSeconds;
15799
+ if (index !== void 0) {
15800
+ writeChatOrganizationProgress(
15801
+ `Waiting ${waitSeconds}s before the next UI operation${adaptive ? " (adaptive)." : "."}`,
15802
+ options
15803
+ );
15804
+ if (await waitForOrganizationInterval(waitSeconds, queuePath) === "paused") {
15805
+ paused = true;
15806
+ break;
15807
+ }
15808
+ }
15377
15809
  }
15378
- index = nextChatOrganizationQueueIndex(queue);
15379
- if (index !== void 0) {
15380
- writeChatOrganizationProgress(
15381
- `Waiting ${waitSeconds}s before the next UI operation.`,
15382
- options
15383
- );
15384
- await waitForOrganizationInterval(waitSeconds);
15810
+ if (paused || halted) break;
15811
+ if (options.verify !== false && summarizeChatOrganizationQueue(queue).pending === 0) {
15812
+ queue = await verifyCompletedChatOrganizationQueue({
15813
+ queue,
15814
+ queuePath,
15815
+ workspace,
15816
+ options,
15817
+ maxAttempts
15818
+ });
15385
15819
  }
15820
+ const reopened = nextChatOrganizationQueueIndex(queue);
15821
+ if (reopened === void 0) break;
15822
+ writeChatOrganizationProgress(
15823
+ "The full-history audit found planned Conversations still loose; retrying them automatically.",
15824
+ options
15825
+ );
15826
+ consecutiveRateLimitCooldowns = 0;
15386
15827
  }
15387
15828
  } finally {
15388
15829
  try {
@@ -15392,9 +15833,12 @@ var runChatOrganize = async (options) => {
15392
15833
  }
15393
15834
  }
15394
15835
  const summary = summarizeChatOrganizationQueue(queue);
15395
- writeChatOrganizationSummary({ queuePath, queue, options });
15836
+ writeChatOrganizationSummary({ queuePath, queue, options, paused });
15837
+ if (paused) process.exit(0);
15396
15838
  let exitCode = 0;
15397
- if (summary.failed > 0 || summary.pending > 0) exitCode = 1;
15839
+ if (summary.failed > 0 || summary.pending > 0 || options.verify !== false && queue.verification?.complete !== true) {
15840
+ exitCode = 1;
15841
+ }
15398
15842
  process.exit(exitCode);
15399
15843
  };
15400
15844
  var writeArchiveOutcomes = (outcomes, options) => {
@@ -15855,7 +16299,7 @@ var subcommandOpts = (command) => {
15855
16299
  // src/features/terminal/registerCli.ts
15856
16300
  var PROVIDER_OPTION = `Browser provider: ${PROVIDER_IDS.join(", ")} (default: ${DEFAULT_PROVIDER})`;
15857
16301
  var registerCliCommands = (program) => {
15858
- program.name("bridge").description("Terminal CLI that bridges ChatGPT or Gemini with local tools via MCP").version("0.5.2").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) => {
16302
+ program.name("bridge").description("Terminal CLI that bridges ChatGPT or Gemini with local tools via MCP").version("0.6.0").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) => {
15859
16303
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
15860
16304
  command.outputHelp({ error: true });
15861
16305
  process.exitCode = 1;
@@ -15957,6 +16401,15 @@ var withWorkspaceFlags = (command) => {
15957
16401
  "Chrome user-data-dir to drive (parallel accounts; default shared bridge profile)"
15958
16402
  ).option("--json", "Emit JSON instead of human-readable lines");
15959
16403
  };
16404
+ var withOrganizationPacingFlags = (command) => {
16405
+ return command.option(
16406
+ "--interval <secondsOrRange>",
16407
+ "Initial seconds between UI operations, fixed or random range such as 60-90 (default: 60)"
16408
+ ).option("--no-adaptive", "Keep the initial interval instead of adapting to success/limits").option("--min-interval <seconds>", "Fastest adaptive base interval (default: 15)").option("--max-interval <seconds>", "Slowest adaptive base interval (default: 180)").option(
16409
+ "--speed-up-after <count>",
16410
+ "Successful moves before reducing the adaptive interval (default: 3)"
16411
+ ).option("--cooldown <seconds>", "Seconds to wait after rate limiting (default: 600)").option("--max-attempts <count>", "Attempts per Conversation before failing (default: 4)").option("--no-verify", "Skip the exhaustive orphan scan when the queue finishes");
16412
+ };
15960
16413
  var registerWorkspaceCommands = (program) => {
15961
16414
  const project = program.command("project").description("Manage ChatGPT Projects (ChatGPT only)");
15962
16415
  withWorkspaceFlags(project.command("list")).description("List ChatGPT Projects").action(
@@ -15984,15 +16437,21 @@ var registerWorkspaceCommands = (program) => {
15984
16437
  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(
15985
16438
  (chatParts, _options, command) => runChatArchive(chatParts.join(" "), command.optsWithGlobals())
15986
16439
  );
15987
- withWorkspaceFlags(chat.command("organize")).description("Run a resumable, rate-safe Conversation organization queue").requiredOption(
16440
+ const organize = withOrganizationPacingFlags(withWorkspaceFlags(chat.command("organize"))).description("Run a resumable, rate-safe Conversation organization queue").option(
15988
16441
  "--plan <fileOrJson>",
15989
16442
  "JSON array of {conversation, project} tasks (inline, @file, or a path)"
15990
- ).option(
15991
- "--interval <secondsOrRange>",
15992
- "Seconds between UI operations, fixed or random range such as 60-90 (default: 60)"
15993
- ).option("--cooldown <seconds>", "Seconds to wait after rate limiting (default: 600)").option("--max-attempts <count>", "Attempts per Conversation before failing (default: 4)").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(
16443
+ ).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(
15994
16444
  (_options, command) => runChatOrganize(command.optsWithGlobals())
15995
16445
  );
16446
+ withWorkspaceFlags(organize.command("status")).description("Show the latest organization queue state and adaptive pace").option("--queue <pathOrFingerprint>", "Select a queue instead of the latest one").action(
16447
+ (_options, command) => runChatOrganizeStatus(command.optsWithGlobals())
16448
+ );
16449
+ withWorkspaceFlags(organize.command("pause")).description("Pause the latest queue after its active UI operation").option("--queue <pathOrFingerprint>", "Select a queue instead of the latest one").action(
16450
+ (_options, command) => runChatOrganizePause(command.optsWithGlobals())
16451
+ );
16452
+ withOrganizationPacingFlags(withWorkspaceFlags(organize.command("resume"))).description("Resume the latest persisted organization queue").option("--queue <pathOrFingerprint>", "Select a queue instead of the latest one").action(
16453
+ (_options, command) => runChatOrganizeResume(command.optsWithGlobals())
16454
+ );
15996
16455
  const task = program.command("task").description("List or schedule ChatGPT Tasks (ChatGPT only)");
15997
16456
  withWorkspaceFlags(task.command("list")).description("List ChatGPT Scheduled tasks").action(
15998
16457
  (_options, command) => runTaskList(command.optsWithGlobals())