@agentrhq/webcmd 0.3.0 → 0.3.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 +13 -2
- package/cli-manifest.json +217 -13
- package/clis/chatgpt/ask.js +1 -1
- package/clis/chatgpt/ask.test.js +11 -0
- package/clis/chatgpt/commands.test.js +102 -1
- package/clis/chatgpt/deep-research-result.js +45 -3
- package/clis/chatgpt/image.js +22 -2
- package/clis/chatgpt/project-file-add.js +24 -1
- package/clis/chatgpt/utils.js +258 -52
- package/clis/chatgpt/utils.test.js +259 -1
- package/clis/claude/ask.js +21 -1
- package/clis/facebook/feed.js +110 -24
- package/clis/facebook/feed.test.js +62 -0
- package/clis/facebook/search.js +185 -38
- package/clis/facebook/search.test.js +95 -50
- package/clis/instagram/explore.js +30 -12
- package/clis/instagram/explore.test.js +77 -0
- package/clis/instagram/post.js +14 -1
- package/clis/instagram/reel.js +13 -1
- package/clis/linkedin/company.js +153 -0
- package/clis/linkedin/company.test.js +111 -0
- package/clis/linkedin/connections.js +135 -0
- package/clis/linkedin/connections.test.js +141 -0
- package/clis/mercury/reimbursement-draft.js +12 -1
- package/clis/twitter/article-evaluate.test.js +27 -0
- package/clis/twitter/article.js +73 -16
- package/clis/twitter/article.test.js +209 -0
- package/clis/twitter/download.js +6 -1
- package/clis/twitter/post.js +14 -1
- package/clis/twitter/profile.js +1 -1
- package/clis/twitter/profile.test.js +8 -0
- package/clis/twitter/quote.js +11 -1
- package/clis/twitter/reply.js +11 -1
- package/dist/src/browser/base-page.d.ts +26 -0
- package/dist/src/browser/base-page.js +23 -8
- package/dist/src/browser/base-page.test.js +45 -12
- package/dist/src/browser/command-catalog.d.ts +6 -0
- package/dist/src/browser/command-catalog.js +259 -0
- package/dist/src/browser/command-catalog.test.d.ts +1 -0
- package/dist/src/browser/command-catalog.test.js +79 -0
- package/dist/src/browser/runtime/local-cloak/darwin-background-launch.d.ts +17 -0
- package/dist/src/browser/runtime/local-cloak/darwin-background-launch.js +106 -0
- package/dist/src/browser/runtime/local-cloak/darwin-background-launch.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/darwin-background-launch.test.js +65 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +7 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.js +23 -9
- package/dist/src/browser/runtime/local-cloak/session-manager.test.js +36 -0
- package/dist/src/browser/target-resolver.d.ts +1 -0
- package/dist/src/browser/target-resolver.js +99 -4
- package/dist/src/browser/target-resolver.test.js +141 -1
- package/dist/src/build-manifest.d.ts +6 -0
- package/dist/src/build-manifest.js +18 -1
- package/dist/src/build-manifest.test.js +57 -1
- package/dist/src/builtin-command-surface.d.ts +9 -0
- package/dist/src/builtin-command-surface.js +16 -0
- package/dist/src/check-hosted-contract.test.d.ts +1 -0
- package/dist/src/check-hosted-contract.test.js +56 -0
- package/dist/src/cli.js +58 -106
- package/dist/src/cli.test.js +71 -1
- package/dist/src/command-presentation.d.ts +95 -0
- package/dist/src/command-presentation.js +486 -0
- package/dist/src/command-presentation.test.d.ts +1 -0
- package/dist/src/command-presentation.test.js +97 -0
- package/dist/src/command-surface.d.ts +43 -0
- package/dist/src/command-surface.js +205 -0
- package/dist/src/command-surface.test.d.ts +1 -0
- package/dist/src/command-surface.test.js +406 -0
- package/dist/src/commanderAdapter.d.ts +5 -1
- package/dist/src/commanderAdapter.js +12 -55
- package/dist/src/commanderAdapter.test.js +15 -3
- package/dist/src/community-plugin-sync.d.ts +11 -0
- package/dist/src/community-plugin-sync.js +138 -0
- package/dist/src/community-plugin-sync.test.d.ts +1 -0
- package/dist/src/community-plugin-sync.test.js +123 -0
- package/dist/src/completion-fast.d.ts +4 -4
- package/dist/src/completion-fast.js +15 -34
- package/dist/src/completion-shared.d.ts +4 -0
- package/dist/src/completion-shared.js +38 -0
- package/dist/src/completion.js +3 -27
- package/dist/src/completion.test.js +31 -2
- package/dist/src/docs-sync-review-cli.test.d.ts +1 -0
- package/dist/src/docs-sync-review-cli.test.js +322 -0
- package/dist/src/docs-sync-review.d.ts +117 -0
- package/dist/src/docs-sync-review.js +475 -0
- package/dist/src/docs-sync-review.test.d.ts +1 -0
- package/dist/src/docs-sync-review.test.js +324 -0
- package/dist/src/errors.d.ts +12 -3
- package/dist/src/errors.js +11 -7
- package/dist/src/errors.test.js +14 -1
- package/dist/src/execution.d.ts +1 -2
- package/dist/src/execution.js +4 -45
- package/dist/src/generate-release-notes-cli.test.js +7 -2
- package/dist/src/help.d.ts +4 -0
- package/dist/src/help.js +50 -255
- package/dist/src/help.test.js +27 -1
- package/dist/src/hosted/args.d.ts +2 -7
- package/dist/src/hosted/args.js +2 -99
- package/dist/src/hosted/args.test.js +15 -1
- package/dist/src/hosted/availability.d.ts +13 -0
- package/dist/src/hosted/availability.js +16 -0
- package/dist/src/hosted/availability.test.d.ts +1 -0
- package/dist/src/hosted/availability.test.js +180 -0
- package/dist/src/hosted/browser-args.d.ts +18 -0
- package/dist/src/hosted/browser-args.js +152 -0
- package/dist/src/hosted/browser-args.test.d.ts +1 -0
- package/dist/src/hosted/browser-args.test.js +182 -0
- package/dist/src/hosted/client.d.ts +31 -2
- package/dist/src/hosted/client.js +441 -53
- package/dist/src/hosted/client.test.js +651 -32
- package/dist/src/hosted/config.d.ts +7 -2
- package/dist/src/hosted/config.js +26 -4
- package/dist/src/hosted/config.test.js +34 -1
- package/dist/src/hosted/contract.d.ts +94 -0
- package/dist/src/hosted/contract.js +208 -0
- package/dist/src/hosted/contract.test.d.ts +1 -0
- package/dist/src/hosted/contract.test.js +361 -0
- package/dist/src/hosted/credentials.d.ts +38 -0
- package/dist/src/hosted/credentials.js +248 -0
- package/dist/src/hosted/credentials.test.d.ts +1 -0
- package/dist/src/hosted/credentials.test.js +93 -0
- package/dist/src/hosted/file-contract.test.d.ts +1 -0
- package/dist/src/hosted/file-contract.test.js +174 -0
- package/dist/src/hosted/files.d.ts +29 -0
- package/dist/src/hosted/files.js +296 -0
- package/dist/src/hosted/files.test.d.ts +1 -0
- package/dist/src/hosted/files.test.js +231 -0
- package/dist/src/hosted/main-lifecycle.test.d.ts +1 -0
- package/dist/src/hosted/main-lifecycle.test.js +213 -0
- package/dist/src/hosted/manifest.d.ts +5 -0
- package/dist/src/hosted/manifest.js +21 -66
- package/dist/src/hosted/manifest.test.js +120 -2
- package/dist/src/hosted/output-parity.test.d.ts +1 -0
- package/dist/src/hosted/output-parity.test.js +108 -0
- package/dist/src/hosted/root-command-surface.test.d.ts +1 -0
- package/dist/src/hosted/root-command-surface.test.js +646 -0
- package/dist/src/hosted/runner.d.ts +7 -0
- package/dist/src/hosted/runner.js +554 -253
- package/dist/src/hosted/runner.test.js +1471 -53
- package/dist/src/hosted/setup.d.ts +3 -2
- package/dist/src/hosted/setup.js +45 -15
- package/dist/src/hosted/setup.test.js +133 -6
- package/dist/src/hosted/types.d.ts +101 -23
- package/dist/src/main.js +120 -108
- package/dist/src/manifest-types.d.ts +2 -0
- package/dist/src/output.d.ts +17 -2
- package/dist/src/output.js +88 -81
- package/dist/src/output.test.js +141 -51
- package/dist/src/pipeline/executor.test.js +1 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/plugin-create-cli.test.d.ts +1 -0
- package/dist/src/plugin-create-cli.test.js +37 -0
- package/dist/src/plugin-manifest.d.ts +12 -0
- package/dist/src/plugin-manifest.js +16 -0
- package/dist/src/plugin-manifest.test.js +19 -1
- package/dist/src/plugin-scaffold.d.ts +4 -1
- package/dist/src/plugin-scaffold.js +4 -1
- package/dist/src/plugin-scaffold.test.js +23 -8
- package/dist/src/plugin.js +4 -1
- package/dist/src/plugin.test.js +13 -0
- package/dist/src/registry.d.ts +2 -0
- package/dist/src/release-notes.js +12 -4
- package/dist/src/release-notes.test.js +27 -15
- package/dist/src/root-command-surface.d.ts +31 -0
- package/dist/src/root-command-surface.js +106 -0
- package/dist/src/serialization.d.ts +1 -16
- package/dist/src/serialization.js +5 -55
- package/dist/src/stream-write.d.ts +12 -0
- package/dist/src/stream-write.js +91 -0
- package/dist/src/stream-write.test.d.ts +1 -0
- package/dist/src/stream-write.test.js +186 -0
- package/dist/src/types.d.ts +5 -0
- package/dist/src/utils.d.ts +1 -1
- package/dist/src/utils.js +2 -8
- package/dist/src/utils.test.js +50 -0
- package/hosted-contract.json +36748 -0
- package/package.json +6 -1
- package/scripts/check-hosted-contract.mjs +108 -0
- package/scripts/docs-sync-review.ts +332 -0
- package/scripts/sync-community-plugins.ts +14 -0
- package/skills/webcmd-autofix/SKILL.md +26 -37
- package/skills/webcmd-usage/SKILL.md +7 -0
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { JSDOM } from 'jsdom';
|
|
5
5
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@agentrhq/webcmd/errors';
|
|
7
|
-
import { __test__, getChatGPTDetailRows, getChatGPTImageAssets, getChatGPTResponsePairCounts, getChatGPTVisibleImageUrls, getCurrentChatGPTModel, getCurrentChatGPTTool, isGenerating, navigateToProject, openChatGPTConversation, prepareChatGPTImagePaths, selectChatGPTModel, selectChatGPTTool, sendChatGPTMessage, uploadChatGPTImages, waitForChatGPTDetailRows, waitForChatGPTImages, waitForChatGPTResponse } from './utils.js';
|
|
7
|
+
import { __test__, getChatGPTDetailRows, getChatGPTImageAssets, getChatGPTResponsePairCounts, getChatGPTVisibleImageUrls, getCurrentChatGPTModel, getCurrentChatGPTTool, getVisibleMessages, isGenerating, navigateToProject, openChatGPTConversation, prepareChatGPTImagePaths, selectChatGPTModel, selectChatGPTTool, sendChatGPTMessage, uploadChatGPTImages, waitForChatGPTDeepResearchResult, waitForChatGPTDetailRows, waitForChatGPTImages, waitForChatGPTResponse } from './utils.js';
|
|
8
8
|
|
|
9
9
|
const tempDirs = [];
|
|
10
10
|
|
|
@@ -20,6 +20,7 @@ function createPageMock({ location = '', generating = [], imageUrls = [] } = {})
|
|
|
20
20
|
let imageIndex = 0;
|
|
21
21
|
return {
|
|
22
22
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
23
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
23
24
|
goto: vi.fn().mockResolvedValue(undefined),
|
|
24
25
|
evaluate: vi.fn((script) => {
|
|
25
26
|
if (script === 'window.location.href') return Promise.resolve(location);
|
|
@@ -51,6 +52,7 @@ function createDomEvaluatePage(html) {
|
|
|
51
52
|
dom,
|
|
52
53
|
goto: vi.fn().mockResolvedValue(undefined),
|
|
53
54
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
55
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
54
56
|
evaluate: vi.fn((script) => Promise.resolve(dom.window.eval(script))),
|
|
55
57
|
};
|
|
56
58
|
}
|
|
@@ -188,6 +190,37 @@ function makeDeepResearchPayload(report = makeDeepResearchReport(), { conversati
|
|
|
188
190
|
return payload;
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
function makeDeepResearchProgressPayload(status = 'waiting_for_user_response_on_plan') {
|
|
194
|
+
return {
|
|
195
|
+
mapping: {
|
|
196
|
+
progress_node: {
|
|
197
|
+
message: {
|
|
198
|
+
metadata: {
|
|
199
|
+
chatgpt_sdk: {
|
|
200
|
+
widget_state: JSON.stringify({
|
|
201
|
+
status,
|
|
202
|
+
waiting_for_user_response_on_plan_until: '2026-07-02T02:29:48.298274Z',
|
|
203
|
+
plan: {
|
|
204
|
+
plan_id: 'plan-demo',
|
|
205
|
+
title: 'Research plan',
|
|
206
|
+
steps: [{ id: 'step-1', title: 'Collect sources', status: 'pending' }],
|
|
207
|
+
},
|
|
208
|
+
step_statuses_by_plan: { 'step-1': 'pending' },
|
|
209
|
+
}),
|
|
210
|
+
response_metadata: {
|
|
211
|
+
async_task_conversation_id: 'async-conversation-123',
|
|
212
|
+
'openai/widgetSessionId': 'widget-session-123',
|
|
213
|
+
'openai/asyncStatus': 7,
|
|
214
|
+
venus_message_type: 'initial_loading_message',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
191
224
|
describe('chatgpt deep research result extraction', () => {
|
|
192
225
|
it('extracts report markdown and sources from conversation widget_state', () => {
|
|
193
226
|
const result = __test__.extractDeepResearchFromConversationPayload(makeDeepResearchPayload());
|
|
@@ -251,9 +284,150 @@ describe('chatgpt deep research result extraction', () => {
|
|
|
251
284
|
.toThrow(CommandExecutionError);
|
|
252
285
|
});
|
|
253
286
|
|
|
287
|
+
it('extracts waiting-for-user progress from widget metadata without a report', () => {
|
|
288
|
+
const result = __test__.extractDeepResearchFromConversationPayload(makeDeepResearchProgressPayload());
|
|
289
|
+
|
|
290
|
+
expect(result).toMatchObject({
|
|
291
|
+
status: 'waiting_for_user',
|
|
292
|
+
method: 'conversation-widget-progress',
|
|
293
|
+
asyncTaskConversationId: 'async-conversation-123',
|
|
294
|
+
widgetSessionId: 'widget-session-123',
|
|
295
|
+
asyncStatus: 7,
|
|
296
|
+
venusMessageType: 'initial_loading_message',
|
|
297
|
+
venusStatus: 'waiting_for_user_response_on_plan',
|
|
298
|
+
waitingForUserUntil: '2026-07-02T02:29:48.298274Z',
|
|
299
|
+
planId: 'plan-demo',
|
|
300
|
+
planTitle: 'Research plan',
|
|
301
|
+
});
|
|
302
|
+
expect(result.report).toBe('');
|
|
303
|
+
expect(result.progress.planSteps).toEqual([
|
|
304
|
+
{ id: 'step-1', title: 'Collect sources', status: 'pending' },
|
|
305
|
+
]);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('ignores unrelated SDK response metadata without Deep Research identity', () => {
|
|
309
|
+
const payload = {
|
|
310
|
+
mapping: {
|
|
311
|
+
app_widget: {
|
|
312
|
+
message: {
|
|
313
|
+
metadata: {
|
|
314
|
+
chatgpt_sdk: {
|
|
315
|
+
response_metadata: {
|
|
316
|
+
'openai/widgetSessionId': 'unrelated-widget',
|
|
317
|
+
'openai/asyncStatus': 1,
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
expect(__test__.extractDeepResearchFromConversationPayload(payload)).toBeNull();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('prefers the current Deep Research progress over stale actionable progress', () => {
|
|
330
|
+
const stale = makeDeepResearchProgressPayload().mapping.progress_node;
|
|
331
|
+
stale.message.create_time = 1;
|
|
332
|
+
const current = makeDeepResearchProgressPayload('running').mapping.progress_node;
|
|
333
|
+
current.message.create_time = 2;
|
|
334
|
+
const payload = {
|
|
335
|
+
current_node: 'running_node',
|
|
336
|
+
mapping: {
|
|
337
|
+
waiting_node: { ...stale, parent: null },
|
|
338
|
+
running_node: { ...current, parent: 'waiting_node' },
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
expect(__test__.extractDeepResearchFromConversationPayload(payload)).toMatchObject({
|
|
343
|
+
status: 'running',
|
|
344
|
+
venusStatus: 'running',
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
254
348
|
it('ignores short widget previews that are not completed reports', () => {
|
|
255
349
|
expect(__test__.extractDeepResearchFromConversationPayload(makeDeepResearchPayload('short preview'))).toBeNull();
|
|
256
350
|
});
|
|
351
|
+
|
|
352
|
+
it('stops waiting immediately when widget state needs user input', async () => {
|
|
353
|
+
const page = {
|
|
354
|
+
getCookies: vi.fn().mockResolvedValue([]),
|
|
355
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
356
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
357
|
+
evaluate: vi.fn((script) => {
|
|
358
|
+
const source = String(script);
|
|
359
|
+
if (source.includes("document.querySelectorAll('iframe')")) {
|
|
360
|
+
return Promise.resolve({
|
|
361
|
+
url: 'https://chatgpt.com/c/requested123',
|
|
362
|
+
title: 'ChatGPT',
|
|
363
|
+
iframes: [],
|
|
364
|
+
deepResearchIframe: null,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (source.includes('Stop generating') || source.includes('Thinking')) return Promise.resolve(false);
|
|
368
|
+
if (source.includes('/backend-api/conversation/')) {
|
|
369
|
+
return Promise.resolve({
|
|
370
|
+
ok: true,
|
|
371
|
+
status: 200,
|
|
372
|
+
contentType: 'application/json',
|
|
373
|
+
text: JSON.stringify(makeDeepResearchProgressPayload()),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return Promise.resolve(undefined);
|
|
377
|
+
}),
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const result = await waitForChatGPTDeepResearchResult(page, {
|
|
381
|
+
conversationId: 'requested123',
|
|
382
|
+
timeoutSeconds: 180,
|
|
383
|
+
stableSeconds: 3,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(result.status).toBe('waiting_for_user');
|
|
387
|
+
expect(result.venusStatus).toBe('waiting_for_user_response_on_plan');
|
|
388
|
+
expect(page.sleep).not.toHaveBeenCalled();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('continues waiting through a transient missing state before progress appears', async () => {
|
|
392
|
+
let conversationReads = 0;
|
|
393
|
+
const page = {
|
|
394
|
+
getCookies: vi.fn().mockResolvedValue([]),
|
|
395
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
396
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
397
|
+
evaluate: vi.fn((script) => {
|
|
398
|
+
const source = String(script);
|
|
399
|
+
if (source.includes("document.querySelectorAll('iframe')")) {
|
|
400
|
+
return Promise.resolve({
|
|
401
|
+
url: 'https://chatgpt.com/c/requested123',
|
|
402
|
+
title: 'ChatGPT',
|
|
403
|
+
iframes: [],
|
|
404
|
+
deepResearchIframe: null,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (source.includes('Stop generating') || source.includes('Thinking')) return Promise.resolve(false);
|
|
408
|
+
if (source.includes('/backend-api/conversation/')) {
|
|
409
|
+
conversationReads += 1;
|
|
410
|
+
return Promise.resolve({
|
|
411
|
+
ok: true,
|
|
412
|
+
status: 200,
|
|
413
|
+
contentType: 'application/json',
|
|
414
|
+
text: JSON.stringify(conversationReads === 1 ? { mapping: {} } : makeDeepResearchProgressPayload()),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return Promise.resolve(undefined);
|
|
418
|
+
}),
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const result = await waitForChatGPTDeepResearchResult(page, {
|
|
422
|
+
conversationId: 'requested123',
|
|
423
|
+
timeoutSeconds: 180,
|
|
424
|
+
stableSeconds: 3,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
expect(result.status).toBe('waiting_for_user');
|
|
428
|
+
expect(page.sleep).toHaveBeenCalledWith(3);
|
|
429
|
+
expect(conversationReads).toBe(2);
|
|
430
|
+
});
|
|
257
431
|
});
|
|
258
432
|
|
|
259
433
|
describe('chatgpt model selection validation', () => {
|
|
@@ -662,6 +836,7 @@ describe('chatgpt detail completion state', () => {
|
|
|
662
836
|
function createDetailPageMock({ generating = false, messages = [] } = {}) {
|
|
663
837
|
return {
|
|
664
838
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
839
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
665
840
|
evaluate: vi.fn((script) => {
|
|
666
841
|
if (script.includes('Stop generating') || script.includes('Thinking')) {
|
|
667
842
|
return Promise.resolve(generating);
|
|
@@ -721,6 +896,7 @@ describe('chatgpt ask response extraction boundary', () => {
|
|
|
721
896
|
let messageIndex = 0;
|
|
722
897
|
return {
|
|
723
898
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
899
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
724
900
|
evaluate: vi.fn((script) => {
|
|
725
901
|
if (script === 'window.location.href') return Promise.resolve(url);
|
|
726
902
|
if (script.includes('Stop generating') || script.includes('Thinking')) {
|
|
@@ -856,6 +1032,43 @@ describe('chatgpt ask response extraction boundary', () => {
|
|
|
856
1032
|
conversationUrl: 'https://chatgpt.com/c/demo',
|
|
857
1033
|
})).rejects.toThrow(/navigated away from the target conversation/);
|
|
858
1034
|
});
|
|
1035
|
+
|
|
1036
|
+
it('polls with pure sleeps instead of DOM-stable numeric waits', async () => {
|
|
1037
|
+
mockAdvancingClock();
|
|
1038
|
+
const page = createResponseWaitPage([[], [], []]);
|
|
1039
|
+
|
|
1040
|
+
await expect(waitForChatGPTResponse(page, 0, 'unmatched prompt', 4, {}))
|
|
1041
|
+
.rejects.toThrow(/chatgpt ask timed out/);
|
|
1042
|
+
|
|
1043
|
+
expect(page.sleep).toHaveBeenCalled();
|
|
1044
|
+
expect(page.wait).not.toHaveBeenCalled();
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
it('uses textContent instead of layout-triggering innerText in text-only polls', async () => {
|
|
1048
|
+
const page = createDomEvaluatePage(`
|
|
1049
|
+
<article data-testid="conversation-turn-2">
|
|
1050
|
+
<div data-message-author-role="assistant">
|
|
1051
|
+
<div class="markdown">done answer</div>
|
|
1052
|
+
</div>
|
|
1053
|
+
</article>
|
|
1054
|
+
`);
|
|
1055
|
+
for (const node of page.dom.window.document.querySelectorAll('*')) {
|
|
1056
|
+
node.getBoundingClientRect = () => ({ width: 120, height: 36 });
|
|
1057
|
+
}
|
|
1058
|
+
Object.defineProperty(page.dom.window.HTMLElement.prototype, 'innerText', {
|
|
1059
|
+
configurable: true,
|
|
1060
|
+
get() {
|
|
1061
|
+
throw new Error('innerText should not be read during text-only polls');
|
|
1062
|
+
},
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
await expect(getVisibleMessages(page, { textOnly: true })).resolves.toEqual([{
|
|
1066
|
+
Index: 1,
|
|
1067
|
+
Role: 'Assistant',
|
|
1068
|
+
Text: 'done answer',
|
|
1069
|
+
Html: '',
|
|
1070
|
+
}]);
|
|
1071
|
+
});
|
|
859
1072
|
});
|
|
860
1073
|
|
|
861
1074
|
describe('chatgpt generation state', () => {
|
|
@@ -869,6 +1082,47 @@ describe('chatgpt generation state', () => {
|
|
|
869
1082
|
|
|
870
1083
|
await expect(isGenerating(page)).resolves.toBe(true);
|
|
871
1084
|
});
|
|
1085
|
+
|
|
1086
|
+
it('detects a plain-text Thinking pill in the latest message turn', async () => {
|
|
1087
|
+
const page = createDomEvaluatePage(`
|
|
1088
|
+
<div data-message-author-role="assistant">
|
|
1089
|
+
<div>partial answer</div>
|
|
1090
|
+
<div>Thinking</div>
|
|
1091
|
+
</div>
|
|
1092
|
+
`);
|
|
1093
|
+
|
|
1094
|
+
await expect(isGenerating(page)).resolves.toBe(true);
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
it('stays idle when Thinking is only the selected composer model', async () => {
|
|
1098
|
+
const page = createDomEvaluatePage(`
|
|
1099
|
+
<article data-testid="conversation-turn-2">
|
|
1100
|
+
<div data-message-author-role="assistant"><div class="markdown">done answer</div></div>
|
|
1101
|
+
</article>
|
|
1102
|
+
<form>
|
|
1103
|
+
<div id="prompt-textarea" contenteditable="true"></div>
|
|
1104
|
+
<button aria-label="Thinking">Thinking</button>
|
|
1105
|
+
</form>
|
|
1106
|
+
`);
|
|
1107
|
+
|
|
1108
|
+
await expect(isGenerating(page)).resolves.toBe(false);
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
it('ignores finished answer text that merely mentions Thinking', async () => {
|
|
1112
|
+
const page = createDomEvaluatePage(`
|
|
1113
|
+
<article data-testid="conversation-turn-2">
|
|
1114
|
+
<div data-message-author-role="assistant">
|
|
1115
|
+
<div class="markdown"><p>The answer discusses Thinking mode.</p></div>
|
|
1116
|
+
</div>
|
|
1117
|
+
</article>
|
|
1118
|
+
`);
|
|
1119
|
+
Object.defineProperty(page.dom.window.document.body, 'innerText', {
|
|
1120
|
+
configurable: true,
|
|
1121
|
+
get: () => 'The answer discusses Thinking mode.',
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
await expect(isGenerating(page)).resolves.toBe(false);
|
|
1125
|
+
});
|
|
872
1126
|
});
|
|
873
1127
|
|
|
874
1128
|
describe('chatgpt current model detection', () => {
|
|
@@ -1203,6 +1457,7 @@ describe('chatgpt image upload helper', () => {
|
|
|
1203
1457
|
const page = {
|
|
1204
1458
|
setFileInput: vi.fn().mockResolvedValue(undefined),
|
|
1205
1459
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
1460
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
1206
1461
|
evaluate: vi.fn().mockResolvedValue(true),
|
|
1207
1462
|
};
|
|
1208
1463
|
|
|
@@ -1254,6 +1509,7 @@ describe('chatgpt image upload helper', () => {
|
|
|
1254
1509
|
const page = {
|
|
1255
1510
|
setFileInput: vi.fn().mockRejectedValue(new Error('No element found')),
|
|
1256
1511
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
1512
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
1257
1513
|
evaluate: vi.fn((script) => {
|
|
1258
1514
|
if (String(script).includes('new DataTransfer()')) {
|
|
1259
1515
|
return Promise.resolve({ ok: true });
|
|
@@ -1289,6 +1545,7 @@ describe('chatgpt image upload helper', () => {
|
|
|
1289
1545
|
const page = {
|
|
1290
1546
|
setFileInput: vi.fn().mockResolvedValue(undefined),
|
|
1291
1547
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
1548
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
1292
1549
|
evaluate: vi.fn((script) => Promise.resolve(dom.window.eval(String(script)))),
|
|
1293
1550
|
};
|
|
1294
1551
|
|
|
@@ -1319,6 +1576,7 @@ describe('chatgpt image upload helper', () => {
|
|
|
1319
1576
|
const page = {
|
|
1320
1577
|
setFileInput: vi.fn().mockResolvedValue(undefined),
|
|
1321
1578
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
1579
|
+
sleep: vi.fn().mockResolvedValue(undefined),
|
|
1322
1580
|
evaluate: vi.fn((script) => Promise.resolve(dom.window.eval(String(script)))),
|
|
1323
1581
|
};
|
|
1324
1582
|
|
package/clis/claude/ask.js
CHANGED
|
@@ -23,7 +23,27 @@ export const askCommand = cli({
|
|
|
23
23
|
{ name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
|
|
24
24
|
{ name: 'model', default: 'sonnet', choices: ['sonnet', 'opus', 'haiku'], help: 'Model to use: sonnet, opus, or haiku' },
|
|
25
25
|
{ name: 'think', type: 'boolean', default: false, help: 'Enable Adaptive thinking' },
|
|
26
|
-
{
|
|
26
|
+
{
|
|
27
|
+
name: 'file',
|
|
28
|
+
help: 'Attach a file (image, PDF, text) with the prompt',
|
|
29
|
+
file: {
|
|
30
|
+
direction: 'input',
|
|
31
|
+
pathKind: 'file',
|
|
32
|
+
multiple: false,
|
|
33
|
+
contentTypes: [
|
|
34
|
+
'application/pdf',
|
|
35
|
+
'text/plain',
|
|
36
|
+
'text/markdown',
|
|
37
|
+
'text/csv',
|
|
38
|
+
'application/json',
|
|
39
|
+
'image/jpeg',
|
|
40
|
+
'image/png',
|
|
41
|
+
'image/gif',
|
|
42
|
+
'image/webp',
|
|
43
|
+
],
|
|
44
|
+
maxBytes: 26_214_400,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
27
47
|
],
|
|
28
48
|
columns: ['response'],
|
|
29
49
|
|
package/clis/facebook/feed.js
CHANGED
|
@@ -24,7 +24,26 @@ function buildFeedExtractScript(limit) {
|
|
|
24
24
|
const limit = ${limit};
|
|
25
25
|
|
|
26
26
|
function clean(value) {
|
|
27
|
-
|
|
27
|
+
// Strip zero-width / bidi control chars first: Facebook injects them into
|
|
28
|
+
// decoy nodes to poison scrapers. See issue #2089.
|
|
29
|
+
return String(value || '')
|
|
30
|
+
.replace(/[\\u200b-\\u200f\\u202a-\\u202e\\u2060\\ufeff]/g, '')
|
|
31
|
+
.replace(/\\s+/g, ' ')
|
|
32
|
+
.trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// FB anti-scrape decoys: long spaceless digit runs, spaced single-char
|
|
36
|
+
// strings ("a b c d e"), and hidden-domain .com spam. Real author/content
|
|
37
|
+
// text never looks like this.
|
|
38
|
+
function isDecoyText(text) {
|
|
39
|
+
if (!text) return true;
|
|
40
|
+
if (/^\\d{8,}$/.test(text)) return true;
|
|
41
|
+
if (/^(?:\\S ){4,}\\S$/.test(text) && text.replace(/\\s/g, '').length <= 12) return true;
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isReelsOrCarouselChrome(text) {
|
|
46
|
+
return /^(Reels|Reels and short videos|People you may know)/i.test(text);
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
function textOf(el) {
|
|
@@ -39,36 +58,35 @@ function buildFeedExtractScript(limit) {
|
|
|
39
58
|
const path = window.location && window.location.pathname ? window.location.pathname : '';
|
|
40
59
|
const body = textOf(document.body);
|
|
41
60
|
return /^\\/(login|checkpoint)(\\/|$|\\.php)/.test(path)
|
|
42
|
-
|| /^(Log in to Facebook|
|
|
61
|
+
|| /^(Log in to Facebook|Facebook Log in|Log in Facebook)/i.test(body)
|
|
43
62
|
|| /You must log in to continue/i.test(body);
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
function isExplicitEmptyFeed() {
|
|
47
66
|
const body = textOf(document.body);
|
|
48
|
-
return /No posts available|Nothing to show|No
|
|
67
|
+
return /No posts available|Nothing to show|No more posts|No posts yet/i.test(body);
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
function isSuggestionOrChrome(text) {
|
|
52
|
-
return /^(People you may know|People You May Know
|
|
53
|
-
|| /^(Suggested for you|Suggested Groups|Suggested
|
|
71
|
+
return /^(People you may know|People You May Know)/i.test(text)
|
|
72
|
+
|| /^(Suggested for you|Suggested Groups|Suggested content)/i.test(text);
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
function isSponsored(text) {
|
|
57
|
-
return /(^|\\s)(Sponsored|
|
|
76
|
+
return /(^|\\s)(Sponsored|Advertisement)(\\s|$)/i.test(text);
|
|
58
77
|
}
|
|
59
78
|
|
|
60
79
|
function isActionText(text) {
|
|
61
|
-
return /^(Like|Comment|Share|Send|Follow
|
|
80
|
+
return /^(Like|Comment|Share|Send|Follow)$/i.test(text);
|
|
62
81
|
}
|
|
63
82
|
|
|
64
83
|
function isMetricText(text) {
|
|
65
84
|
return /^(All:|All reactions:)/i.test(text)
|
|
66
85
|
|| /\\b(likes?|reactions?|comments?|shares?)\\b/i.test(text)
|
|
67
|
-
|| /(comments|shares)$/.test(text);
|
|
68
86
|
}
|
|
69
87
|
|
|
70
88
|
function isTimestampText(text) {
|
|
71
|
-
return /^(\\d+\\s*(s|m|h|d|w|mo|yr|min|sec|second|minute|hour|day|week|month|year)s?|Just now|Yesterday
|
|
89
|
+
return /^(\\d+\\s*(s|m|h|d|w|mo|yr|min|sec|second|minute|hour|day|week|month|year)s?|Just now|Yesterday)(\\s*[·•.])?$/i.test(text);
|
|
72
90
|
}
|
|
73
91
|
|
|
74
92
|
function postUrlFrom(root) {
|
|
@@ -86,9 +104,9 @@ function buildFeedExtractScript(limit) {
|
|
|
86
104
|
const kinds = new Set();
|
|
87
105
|
for (const el of root.querySelectorAll('[aria-label]')) {
|
|
88
106
|
const label = labelOf(el);
|
|
89
|
-
if (/^(Like
|
|
90
|
-
if (/^(Comment
|
|
91
|
-
if (/^(Share
|
|
107
|
+
if (/^(Like)$/i.test(label)) kinds.add('like');
|
|
108
|
+
if (/^(Comment)$/i.test(label)) kinds.add('comment');
|
|
109
|
+
if (/^(Share)$/i.test(label)) kinds.add('share');
|
|
92
110
|
}
|
|
93
111
|
return kinds;
|
|
94
112
|
}
|
|
@@ -116,6 +134,8 @@ function buildFeedExtractScript(limit) {
|
|
|
116
134
|
&& !isActionText(text)
|
|
117
135
|
&& !isMetricText(text)
|
|
118
136
|
&& !isTimestampText(text)
|
|
137
|
+
&& !isDecoyText(text)
|
|
138
|
+
&& !/^[\\d\\s.,-]+$/.test(text) // reject all-digit decoy names, but keep "Class of 2024" (#2089)
|
|
119
139
|
&& !/\\/groups\\/|\\/watch\\/|\\/reel\\/|\\/events\\/|\\/friends\\//i.test(href)) {
|
|
120
140
|
return text;
|
|
121
141
|
}
|
|
@@ -129,7 +149,8 @@ function buildFeedExtractScript(limit) {
|
|
|
129
149
|
if (text.length <= 10) return false;
|
|
130
150
|
if (isSuggestionOrChrome(text) || isSponsored(text)) return false;
|
|
131
151
|
if (isActionText(text) || isMetricText(text) || isTimestampText(text)) return false;
|
|
132
|
-
if (
|
|
152
|
+
if (isDecoyText(text) || isReelsOrCarouselChrome(text)) return false;
|
|
153
|
+
if (/^(See more|More)$/i.test(text)) return false;
|
|
133
154
|
return true;
|
|
134
155
|
});
|
|
135
156
|
}
|
|
@@ -147,13 +168,11 @@ function buildFeedExtractScript(limit) {
|
|
|
147
168
|
if (!author && !content) return null;
|
|
148
169
|
if (!content && !postUrl && kinds.size < 2) return null;
|
|
149
170
|
|
|
150
|
-
const likesMatch = fullText.match(/All reactions:\\s*(\\d[\\d,.\\
|
|
171
|
+
const likesMatch = fullText.match(/All reactions:\\s*(\\d[\\d,.\\sKMk]*)/)
|
|
151
172
|
|| fullText.match(/All:\\s*(\\d[\\d,.KMk]*)/)
|
|
152
173
|
|| fullText.match(/(\\d[\\d,.KMk]*)\\s*(?:likes?|reactions?)/i);
|
|
153
|
-
const commentsMatch = fullText.match(/([\\d,.]
|
|
154
|
-
|
|
155
|
-
const sharesMatch = fullText.match(/([\\d,.]+\\s*[localized text]?)\\s*shares/)
|
|
156
|
-
|| fullText.match(/(\\d[\\d,.KMk]*)\\s*shares?/i);
|
|
174
|
+
const commentsMatch = fullText.match(/(\\d[\\d,.KMk]*)\\s*comments?/i);
|
|
175
|
+
const sharesMatch = fullText.match(/(\\d[\\d,.KMk]*)\\s*shares?/i);
|
|
157
176
|
|
|
158
177
|
return {
|
|
159
178
|
index,
|
|
@@ -170,10 +189,51 @@ function buildFeedExtractScript(limit) {
|
|
|
170
189
|
.filter((el) => textOf(el).length > 30);
|
|
171
190
|
}
|
|
172
191
|
|
|
192
|
+
// Modern Facebook no longer wraps posts in [role="article"] nor exposes the
|
|
193
|
+
// Like/Comment/Share aria-labels the fallback keyed on. Every post still
|
|
194
|
+
// carries one "Actions for this post" control (the ⋯ menu). Anchor on it and
|
|
195
|
+
// walk up to the HIGHEST ancestor that still contains exactly one such
|
|
196
|
+
// control — that bounds the node to a single post. See issue #2089.
|
|
197
|
+
function actionMenuAnchors() {
|
|
198
|
+
// Post menus only — NOT "Actions for this comment", so the anchor set,
|
|
199
|
+
// countMenus, and loadFeedPosts all key on the same thing (#2089).
|
|
200
|
+
return Array.from(document.querySelectorAll('[aria-label]')).filter((el) =>
|
|
201
|
+
/^(Actions for this post)$/i.test(labelOf(el)));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function actionAnchoredContainers() {
|
|
205
|
+
const menus = actionMenuAnchors();
|
|
206
|
+
const seen = new WeakSet();
|
|
207
|
+
const containers = [];
|
|
208
|
+
const countMenus = (node) => {
|
|
209
|
+
let n = 0;
|
|
210
|
+
for (const el of node.querySelectorAll('[aria-label]')) {
|
|
211
|
+
if (/^(Actions for this post)$/i.test(labelOf(el))) n += 1;
|
|
212
|
+
}
|
|
213
|
+
return n;
|
|
214
|
+
};
|
|
215
|
+
const LANDMARK_ROLES = new Set(['main', 'feed', 'banner', 'navigation', 'complementary', 'contentinfo', 'region']);
|
|
216
|
+
for (const menu of menus) {
|
|
217
|
+
let node = menu.parentElement;
|
|
218
|
+
let best = null;
|
|
219
|
+
for (let depth = 0; depth < 22 && node && node !== document.body && node !== document.documentElement; depth += 1, node = node.parentElement) {
|
|
220
|
+
// Never let a single-post page climb the container up to a page
|
|
221
|
+
// landmark (role=main/feed/...) and emit the whole region as a post.
|
|
222
|
+
if (LANDMARK_ROLES.has(node.getAttribute('role') || '')) break;
|
|
223
|
+
if (countMenus(node) === 1) best = node; else break;
|
|
224
|
+
}
|
|
225
|
+
if (best && !seen.has(best) && textOf(best).length > 30) {
|
|
226
|
+
seen.add(best);
|
|
227
|
+
containers.push(best);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return containers;
|
|
231
|
+
}
|
|
232
|
+
|
|
173
233
|
function fallbackContainers() {
|
|
174
234
|
const main = document.querySelector('[role="main"]');
|
|
175
235
|
if (!main) return [];
|
|
176
|
-
const buttons = Array.from(main.querySelectorAll('[aria-label="Like"], [aria-label="
|
|
236
|
+
const buttons = Array.from(main.querySelectorAll('[aria-label="Like"], [aria-label="Comment"], [aria-label="Share"]'));
|
|
177
237
|
const seen = new WeakSet();
|
|
178
238
|
const containers = [];
|
|
179
239
|
for (const button of buttons) {
|
|
@@ -210,7 +270,8 @@ function buildFeedExtractScript(limit) {
|
|
|
210
270
|
if (isAuthPage()) return { status: 'auth', rows: [], diagnostics: {} };
|
|
211
271
|
|
|
212
272
|
const primary = primaryContainers();
|
|
213
|
-
const
|
|
273
|
+
const actionAnchored = actionAnchoredContainers();
|
|
274
|
+
const combined = dedupe([...primary, ...actionAnchored, ...fallbackContainers()]);
|
|
214
275
|
const rows = [];
|
|
215
276
|
for (const container of combined) {
|
|
216
277
|
const row = extractPost(container, rows.length + 1);
|
|
@@ -224,13 +285,36 @@ function buildFeedExtractScript(limit) {
|
|
|
224
285
|
diagnostics: {
|
|
225
286
|
articleCount: document.querySelectorAll('[role="article"]').length,
|
|
226
287
|
primaryCount: primary.length,
|
|
227
|
-
|
|
288
|
+
actionMenuCount: actionMenuAnchors().length,
|
|
289
|
+
fallbackActionCount: document.querySelectorAll('[role="main"] [aria-label="Like"], [role="main"] [aria-label="Comment"]').length,
|
|
228
290
|
mainTextLength: textOf(document.querySelector('[role="main"]')).length,
|
|
229
291
|
},
|
|
230
292
|
};
|
|
231
293
|
})()`;
|
|
232
294
|
}
|
|
233
295
|
|
|
296
|
+
// Facebook streams feed posts in lazily as you scroll, so a single extraction
|
|
297
|
+
// off the initial viewport under-returns. Scroll a bounded number of times
|
|
298
|
+
// until enough post action-menus are present (or we stop growing). See #2089.
|
|
299
|
+
async function loadFeedPosts(page, limit) {
|
|
300
|
+
const scrollStep = `(async () => {
|
|
301
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
302
|
+
window.scrollTo(0, document.body.scrollHeight);
|
|
303
|
+
await sleep(800);
|
|
304
|
+
return document.querySelectorAll('[aria-label]').length
|
|
305
|
+
? document.querySelectorAll('[role="article"]').length
|
|
306
|
+
+ Array.from(document.querySelectorAll('[aria-label]')).filter((el) => /^(Actions for this post)$/i.test((el.getAttribute('aria-label') || '').trim())).length
|
|
307
|
+
: 0;
|
|
308
|
+
})()`;
|
|
309
|
+
let prev = -1;
|
|
310
|
+
for (let i = 0; i < 8; i += 1) {
|
|
311
|
+
let count = 0;
|
|
312
|
+
try { count = Number(unwrapBrowserResult(await page.evaluate(scrollStep))) || 0; } catch { break; }
|
|
313
|
+
if (count >= limit || count === prev) break;
|
|
314
|
+
prev = count;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
234
318
|
async function getFacebookFeed(page, kwargs) {
|
|
235
319
|
const limit = requireLimit(kwargs.limit ?? 10);
|
|
236
320
|
try {
|
|
@@ -238,10 +322,12 @@ async function getFacebookFeed(page, kwargs) {
|
|
|
238
322
|
} catch (err) {
|
|
239
323
|
throw new CommandExecutionError(
|
|
240
324
|
`Failed to navigate to facebook feed: ${err instanceof Error ? err.message : err}`,
|
|
241
|
-
'Check that facebook.com is reachable and the browser
|
|
325
|
+
'Check that facebook.com is reachable and the Cloak browser session is running.',
|
|
242
326
|
);
|
|
243
327
|
}
|
|
244
328
|
|
|
329
|
+
await loadFeedPosts(page, limit);
|
|
330
|
+
|
|
245
331
|
let payload;
|
|
246
332
|
try {
|
|
247
333
|
payload = unwrapBrowserResult(await page.evaluate(buildFeedExtractScript(limit)));
|
|
@@ -257,7 +343,7 @@ async function getFacebookFeed(page, kwargs) {
|
|
|
257
343
|
}
|
|
258
344
|
|
|
259
345
|
if (payload.status === 'auth') {
|
|
260
|
-
throw new AuthRequiredError('www.facebook.com', '
|
|
346
|
+
throw new AuthRequiredError('www.facebook.com', 'Log in to Facebook in the active Cloak browser session before retrying.');
|
|
261
347
|
}
|
|
262
348
|
|
|
263
349
|
if (payload.rows.length > 0) {
|
|
@@ -269,7 +355,7 @@ async function getFacebookFeed(page, kwargs) {
|
|
|
269
355
|
}
|
|
270
356
|
|
|
271
357
|
const diagnostics = payload.diagnostics || {};
|
|
272
|
-
if (diagnostics.articleCount || diagnostics.fallbackActionCount || diagnostics.mainTextLength > 200) {
|
|
358
|
+
if (diagnostics.articleCount || diagnostics.actionMenuCount || diagnostics.fallbackActionCount || diagnostics.mainTextLength > 200) {
|
|
273
359
|
throw new CommandExecutionError(
|
|
274
360
|
'facebook feed page rendered but no feed rows could be extracted',
|
|
275
361
|
`Diagnostics: articles=${diagnostics.articleCount || 0}, actions=${diagnostics.fallbackActionCount || 0}, mainTextLength=${diagnostics.mainTextLength || 0}.`,
|