Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ The **Send message next** field lets you insert a single prompt into the active

Open the gear tab in the popup for queue settings and logs.

- **Deep research aware**: keeps queue completion detection aware of ChatGPT Deep Research flows. It is enabled by default.
- **Unlimited retry and wait**: allows the automation to keep waiting/retrying instead of stopping at the normal retry boundary. It is disabled by default.
- **Deep research aware**: uses a longer finite wait and stale-progress timeout for ChatGPT Deep Research. It is enabled by default and does not wait forever unless **Unlimited retry and wait** is also enabled.
- **Unlimited retry and wait**: the only setting that removes the finite retry/wait bound. Automatic retries still wait between attempts and remain interruptible. It is disabled by default.
- **Automation log**: refresh, copy, or clear the stored automation log for troubleshooting.

If you enable unlimited retry/wait, a queue can remain active for much longer during a stuck or repeatedly failing ChatGPT state.
Expand Down
465 changes: 393 additions & 72 deletions background.js

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions background.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ const {
handleGetQueueDebugLogs,
handleClearQueueDebugLogs,
resumeDurableQueues,
restoreDurableJobs,
getDurableJobsState,
MAX_QUEUE_DEBUG_LOG_ENTRIES
} = require('./background.js');

Expand Down Expand Up @@ -269,6 +271,53 @@ test('durable recovery still rewrites an unconfirmed command safely', async () =
assert.deepStrictEqual(localStorageData.queueDurableJobs[7].queue, ['recover-me']);
});

test('durable recovery preserves retry budget and does not duplicate a retry-wait command', async () => {
await resetQueueFixture();
localStorageData.queueDurableJobs = {
29: {
tabId: 29,
provider: 'chatgpt',
conversationId: 'retry-wait',
conversationType: 'existing',
targetKey: 'chatgpt:c:retry-wait',
queue: ['later'],
currentMessage: 'current-command',
isRunning: true,
isPaused: false,
currentPhase: 'retry-wait',
totalMessages: 2,
completedCount: 0,
currentCommandNumber: 1,
retryAttemptCount: 2,
lastRetryableReason: 'Timed out waiting for ChatGPT response.',
retryClass: 'timeout',
retryMode: 'finite',
nextRetryDelayMs: 4000,
nextRetryAt: Date.now() + 8000,
waitStartedAt: 42,
lastResearchProgressAt: 84,
sawDeepResearch: true,
startedAt: 1,
updatedAt: 1
}
};

const restored = restoreDurableJobs(localStorageData.queueDurableJobs);
const job = restored[0];
assert.strictEqual(job.currentMessage, 'current-command');
assert.strictEqual(job.currentPhase, 'retry-wait');
assert.strictEqual(job.retryAttemptCount, 2);
assert.strictEqual(job.waitStartedAt, 42);
assert.strictEqual(job.sawDeepResearch, true);
assert.deepStrictEqual(job.queue, ['later']);

jobs.set(29, job);
const durable = getDurableJobsState();
assert.strictEqual(durable[29].retryAttemptCount, 2);
assert.strictEqual(durable[29].currentMessage, 'current-command');
assert.deepStrictEqual(durable[29].queue, ['later']);
});

test('batched logs expose pending entries and preserve order and trimming', async () => {
await resetQueueFixture();
const clearResponse = await invokeHandler(handleClearQueueDebugLogs);
Expand Down
2 changes: 1 addition & 1 deletion docs/project-memory/architecture.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Architecture

- `manifest.json` defines a Manifest V3 browser extension. `background.js` is the service worker; `popup.html`/`popup.js` provide the main UI; `options.html` is the settings page; `content.js` and `styles.css` run on supported ChatGPT, Gemini, and Claude hosts.
- `background.js` owns per-tab queue jobs in a `Map`, accepts runtime messages for start, enqueue, retry, stop, status, and diagnostics, injects prompt scripts into the selected tab, waits for response state, and records durable queue state in local storage.
- `background.js` owns per-tab queue jobs in a `Map`, accepts runtime messages for start, enqueue, retry, stop, status, and diagnostics, injects prompt scripts into the selected tab, waits for response state with a finite retry/wait policy, and records durable queue state including retry and Deep Research wait metadata in local storage.
- `popup.js` selects supported provider tabs through provider-neutral discovery, stores reusable messages and sequences in local storage, stores optimizer and queue settings in sync storage, resolves placeholders, and renders queue instances, status, and diagnostics through runtime messages.
- `content.js` owns Enter-to-queue on supported providers and the optimizer on ChatGPT only. It discovers conversation turns through primary and fallback selectors, hides older messages, lazily restores images, adds a load-more banner, observes DOM changes, and can queue Enter-submitted text while the provider is generating.
- Queue response detection in `background.js` is DOM-driven through injected functions and provider adapters. It recognizes generating/streaming indicators, selected error markers, and deep-research progress; prompt submission finds a contenteditable input and a supported send-button selector for the active provider.
Expand Down
4 changes: 2 additions & 2 deletions docs/project-memory/decisions.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Decisions

- Queue ownership is keyed by tab ID, allowing independent queues while preventing a second sequence from starting on a tab that already has a running or paused queue (`background.js`).
- A command is not completed until `waitForTabResponse()` observes the response cycle or applies its explicit fallback. Failed send/wait phases pause the queue; the retry setting can requeue the current command after a 15-second delay (`background.js`).
- Queue state is persisted in local storage and periodically woken with an alarms entry. Non-critical snapshots are coalesced and unchanged snapshots are skipped, while start, enqueue, send/recovery, pause, stop, completion, tab removal, and worker-wake boundaries force durable writes. On worker wake, an unconfirmed `sending` or `retry-wait` command is put back at the front before processing resumes (`background.js`).
- A command is not completed until `waitForTabResponse()` observes the response cycle or applies its explicit fallback. Failed send/wait phases pause the queue after a finite default of three automatic retries with exponential backoff. `queueUnlimitedRetryWait` is the only explicit unlimited wait/retry mode and still uses a non-zero delay; Deep Research awareness may use a longer progress-aware wait but cannot remove the final bound (`background.js`).
- Queue state is persisted in local storage and periodically woken with an alarms entry. Non-critical snapshots are coalesced and unchanged snapshots are skipped, while start, enqueue, send/recovery, pause, stop, completion, tab removal, and worker-wake boundaries force durable writes. Snapshots include retry budget and Deep Research wait/progress timestamps so a worker wake does not reset them. On worker wake, an unconfirmed `sending` command is put back at the front; `retry-wait` keeps the current command and remaining delay (`background.js`).
- Queue debug logs remain ordered and bounded while buffered entries flush in batches; logical reads flush pending entries, and clear operations use a generation barrier so an older pending batch cannot repopulate cleared logs (`background.js`).
- The optimizer uses layered selectors and fallback discovery because the page is controlled by ChatGPT. Its windowing keeps at least eight recent messages visible and caps the discovered message set at 1,200 (`content.js`).
- Extension API helpers try callback and promise forms so popup, content, and background code can use the same operations across supported browser API variants (`popup.js`, `content.js`, `background.js`).
Expand Down
4 changes: 2 additions & 2 deletions docs/project-memory/known-failures.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Known failures and limits

- Prompt submission depends on each provider’s current contenteditable and send-button selectors. If either is absent or disabled, the injected send operation returns an error and the queue pauses (`background.js`, `provider-adapter.js`).
- Response detection depends on provider generating, streaming, status, and error markers. An injected-script failure, a detected error/retry state, or a default ten-minute wait timeout pauses the queue (`background.js`).
- Response detection depends on provider generating, streaming, status, and error markers. An injected-script failure, a detected error/retry state, a stalled Deep Research wait, or a finite wait timeout pauses the queue after the automatic retry budget is exhausted (`background.js`).
- In the default wait mode, if no generating indicator appears for five seconds, the worker records that it assumed completion and advances. This is an intentional fallback but can misclassify a response when the page exposes no recognized indicator (`background.js`).
- Deep-research-aware waiting only extends the default timeout after recognized research activity has been observed. Unrecognized page wording does not extend the wait (`background.js`).
- Deep-research-aware waiting uses a longer finite maximum duration and a stale-progress timeout. Progress updates reset the stale timer, but only explicit unlimited retry/wait removes the final bound (`background.js`).
- If a worker restart finds only legacy running-job state and no recoverable durable queue, it records that the in-memory queue was lost and clears the stale state (`background.js`).
- Gemini and Claude optimizer/message-window support is intentionally unsupported; queue Enter interception still runs on those providers (`provider-adapter.js`, `content.js`).
- Chrome packaging is skipped when a Chrome executable is unavailable. Firefox persistent installation can be rejected for unsigned release packages; the fallback requires a working Firefox, npm/web-ext, and profile setup (`Installers/install_chatgpt_queue_optimizer.py`, `Installers/README.md`).
Expand Down
Loading
Loading