@nextclaw/x-harness 0.2.54 → 0.2.56
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/SOURCE.md +2 -2
- package/dist/browser-dom-editing.js +4 -2
- package/dist/browser-dom-submission.d.ts +0 -1
- package/dist/browser-dom-submission.js +2 -33
- package/dist/growth-publishing.js +1 -1
- package/dist/platform-collections.js +43 -31
- package/dist/platform-dependencies.d.ts +3 -1
- package/dist/platform-writing.d.ts +3 -1
- package/dist/platform-writing.js +3 -2
- package/dist/platform.d.ts +3 -1
- package/dist/readers-navigation.js +1 -1
- package/dist/readers-posts.js +1 -0
- package/generated/observation.mjs +2 -1
- package/package.json +2 -2
package/SOURCE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Corresponding source
|
|
2
2
|
|
|
3
|
-
Version 0.2.
|
|
3
|
+
Version 0.2.56, commit 84766d97c35a9cecbee5b8c6461aaa2e28d02c81.
|
|
4
4
|
|
|
5
|
-
Complete source, build scripts and dependencies: https://github.com/Peiiii/x-harness/tree/
|
|
5
|
+
Complete source, build scripts and dependencies: https://github.com/Peiiii/x-harness/tree/84766d97c35a9cecbee5b8c6461aaa2e28d02c81
|
|
6
6
|
|
|
7
7
|
Clone this revision, run npm ci, npm run bootstrap and npm run release:build.
|
|
8
8
|
|
|
@@ -196,9 +196,11 @@ export class DOMEditing {
|
|
|
196
196
|
throw new Error("翻页须有已观察原帖链接");
|
|
197
197
|
const anchor = await this.#unique(this.#tab.playwright
|
|
198
198
|
.locator(`a[href=${JSON.stringify(primary)}]`)
|
|
199
|
-
.filter({
|
|
199
|
+
.filter({ has: this.#tab.playwright.locator("time") }));
|
|
200
200
|
this.#session.current = null;
|
|
201
|
-
|
|
201
|
+
// A retained timestamp can be far below the viewport. Validate it, then
|
|
202
|
+
// scroll the page without waiting for that offscreen link to take focus.
|
|
203
|
+
await (await this.#unique(this.#tab.playwright.locator("body"))).press("PageDown");
|
|
202
204
|
return { completed: true };
|
|
203
205
|
return { completed: false, loc, submittedEditor };
|
|
204
206
|
}
|
|
@@ -3,6 +3,5 @@ export declare class DOMSubmission {
|
|
|
3
3
|
#private;
|
|
4
4
|
constructor(dependencies: Pick<DOMDependencies, "submitAX" | "session" | "tab" | "observe" | "unique" | "locator">);
|
|
5
5
|
finishDOMAction(command: any, prepared: any, query: any, result: any): Promise<void>;
|
|
6
|
-
private waitSubmittedEditor;
|
|
7
6
|
}
|
|
8
7
|
export declare function createDOMSubmission(...args: ConstructorParameters<typeof DOMSubmission>): (command: any, prepared: any, query: any, result: any) => Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { xURL
|
|
1
|
+
import { xURL } from "./platform-rules.js";
|
|
2
2
|
import { valueOf } from "./browser-common.js";
|
|
3
3
|
export class DOMSubmission {
|
|
4
4
|
#submitAX;
|
|
@@ -29,7 +29,6 @@ export class DOMSubmission {
|
|
|
29
29
|
: null;
|
|
30
30
|
let nativeSubmitIndex;
|
|
31
31
|
if (this.#submitAX &&
|
|
32
|
-
command.activation !== "keyboard" &&
|
|
33
32
|
["submitPost", "submitReply"].includes(command.operation)) {
|
|
34
33
|
const native = await this.#submitAX.observe({ mode: "composer" });
|
|
35
34
|
const editors = native.evidence?.filter((e) => /^(?:文本输入区(?:域)?|文本区域|文本栏) /.test(e.label)) ?? [];
|
|
@@ -89,7 +88,7 @@ export class DOMSubmission {
|
|
|
89
88
|
// Inline replies keep the editor mounted. Wait for this submitted text to clear before
|
|
90
89
|
// receipt navigation/reload can interrupt the actual submission request.
|
|
91
90
|
if (submittedEditor)
|
|
92
|
-
await
|
|
91
|
+
await submittedEditor.waitFor({ state: "hidden", timeoutMs: 10000 });
|
|
93
92
|
if (["submitPost", "submitReply"].includes(command.operation)) {
|
|
94
93
|
try {
|
|
95
94
|
await this.#tab.playwright.waitForLoadState({
|
|
@@ -105,36 +104,6 @@ export class DOMSubmission {
|
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
|
-
async waitSubmittedEditor(editor, submitted) {
|
|
109
|
-
const deadline = Date.now() + 10000;
|
|
110
|
-
for (let attempt = 0; attempt < 4; attempt++) {
|
|
111
|
-
const remaining = deadline - Date.now();
|
|
112
|
-
if (remaining <= 0)
|
|
113
|
-
throw Error("submitted-editor-clear-timeout");
|
|
114
|
-
try {
|
|
115
|
-
await editor.waitFor({ state: "hidden", timeoutMs: remaining });
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
if (!/timed out|timeout|deadline exceeded/i.test(error.message) ||
|
|
120
|
-
attempt === 3 ||
|
|
121
|
-
Date.now() >= deadline)
|
|
122
|
-
throw error;
|
|
123
|
-
// Resume only the wait, never the submission or a navigation that could interrupt it.
|
|
124
|
-
const current = await this.#observe({ view: "composer" });
|
|
125
|
-
if (current.alerts?.length ||
|
|
126
|
-
!current.accountHandle ||
|
|
127
|
-
!submitted.accountHandle ||
|
|
128
|
-
!sameHandle(current.accountHandle, submitted.accountHandle) ||
|
|
129
|
-
xURL(current.page).href !== xURL(submitted.page).href ||
|
|
130
|
-
(typeof current.editorText === "string" &&
|
|
131
|
-
current.editorText.trim() &&
|
|
132
|
-
(!sameText(current.editorText, submitted.editorText) ||
|
|
133
|
-
current.replyToId !== submitted.replyToId)))
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
107
|
}
|
|
139
108
|
export function createDOMSubmission(...args) {
|
|
140
109
|
return new DOMSubmission(...args).finishDOMAction;
|
|
@@ -83,7 +83,7 @@ export class IndependentPublishing {
|
|
|
83
83
|
const baseline = await this.#platform.findSubmission({
|
|
84
84
|
...action,
|
|
85
85
|
intentAt: this.#stamp(),
|
|
86
|
-
});
|
|
86
|
+
}, { phase: "baseline" });
|
|
87
87
|
if (baseline.needsStateCheck || baseline.alerts?.length)
|
|
88
88
|
throw new Error("publication-baseline-unconfirmed");
|
|
89
89
|
token = `${action.id}:${this.#stamp()}:${Math.random()}`;
|
|
@@ -158,38 +158,50 @@ export class PlatformCollections {
|
|
|
158
158
|
}
|
|
159
159
|
async advanceSourceWindow(previous, seen) {
|
|
160
160
|
const renderedIds = previous.renderedIds ?? [...seen];
|
|
161
|
-
|
|
162
|
-
let
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
161
|
+
let advanced;
|
|
162
|
+
for (let step = 0; step < 10; step++) {
|
|
163
|
+
await this.#browser.act({ operation: "nextPosts" });
|
|
164
|
+
advanced = await this.#ready({ view: "posts", limit: 5 });
|
|
165
|
+
if (advanced.needsStateCheck || advanced.alerts?.length)
|
|
166
|
+
return advanced;
|
|
167
|
+
let timedOut = false;
|
|
168
|
+
try {
|
|
169
|
+
// Wait for a newly rendered card, even if that source was processed on an
|
|
170
|
+
// earlier run. Global processed IDs do not describe the current DOM window.
|
|
171
|
+
await this.#browser.act({
|
|
172
|
+
operation: "waitForPosts",
|
|
173
|
+
excludeIds: renderedIds,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (!/timed out|timeout/i.test(error.message))
|
|
178
|
+
throw error;
|
|
179
|
+
timedOut = true;
|
|
180
|
+
}
|
|
181
|
+
advanced = await this.#ready({ view: "posts", limit: 5 });
|
|
182
|
+
if (timedOut &&
|
|
183
|
+
advanced.atRenderedTail &&
|
|
184
|
+
!advanced.loading &&
|
|
185
|
+
advanced.firstRenderedId === previous.firstRenderedId &&
|
|
186
|
+
advanced.renderedArticles === previous.renderedArticles)
|
|
187
|
+
advanced.paginationBoundary = {
|
|
188
|
+
kind: "stable-rendered-tail",
|
|
189
|
+
lastId: advanced.renderedIds?.at(-1),
|
|
190
|
+
renderedCount: advanced.renderedArticles,
|
|
191
|
+
waitMs: 5000,
|
|
192
|
+
scopeComplete: false,
|
|
193
|
+
};
|
|
194
|
+
if (advanced.paginationBoundary ||
|
|
195
|
+
!Number.isFinite(advanced.viewportScrollTop) ||
|
|
196
|
+
advanced.renderedArticles !== previous.renderedArticles ||
|
|
197
|
+
(advanced.renderedIds ?? advanced.posts?.map((p) => p.id))?.some((id) => !renderedIds.includes(id)))
|
|
198
|
+
return advanced;
|
|
178
199
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
advanced.renderedArticles === previous.renderedArticles)
|
|
185
|
-
advanced.paginationBoundary = {
|
|
186
|
-
kind: "stable-rendered-tail",
|
|
187
|
-
lastId: advanced.renderedIds?.at(-1),
|
|
188
|
-
renderedCount: advanced.renderedArticles,
|
|
189
|
-
waitMs: 5000,
|
|
190
|
-
scopeComplete: false,
|
|
191
|
-
};
|
|
192
|
-
return advanced;
|
|
200
|
+
return {
|
|
201
|
+
...advanced,
|
|
202
|
+
needsStateCheck: true,
|
|
203
|
+
missing: ["bounded-scroll-window-exhausted"],
|
|
204
|
+
};
|
|
193
205
|
}
|
|
194
206
|
async handleReadCreator(args) {
|
|
195
207
|
if (typeof this.#browser.collectCreator !== "function")
|
|
@@ -31,7 +31,9 @@ export interface PlatformDependencies {
|
|
|
31
31
|
readFeedback: ({ targets, replyLimit }?: any) => Promise<any>;
|
|
32
32
|
readPerson: (target: any) => Promise<any>;
|
|
33
33
|
readPost: (target: any) => Promise<any>;
|
|
34
|
-
findSubmission: (action: any
|
|
34
|
+
findSubmission: (action: any, context?: {
|
|
35
|
+
phase?: "baseline";
|
|
36
|
+
}) => Promise<any>;
|
|
35
37
|
submitFollow: (action: any) => Promise<any>;
|
|
36
38
|
submitPost: (action: any) => Promise<any>;
|
|
37
39
|
submitReply: (action: any) => Promise<any>;
|
|
@@ -2,7 +2,9 @@ import type { PlatformDependencies } from "./platform-dependencies.js";
|
|
|
2
2
|
export declare class PlatformWriting {
|
|
3
3
|
#private;
|
|
4
4
|
compose: (action: any) => Promise<any>;
|
|
5
|
-
findSubmission: (action: any
|
|
5
|
+
findSubmission: (action: any, context?: {
|
|
6
|
+
phase?: "baseline";
|
|
7
|
+
}) => Promise<any>;
|
|
6
8
|
submitFollow: (action: any) => Promise<any>;
|
|
7
9
|
submitPost: (action: any) => Promise<any>;
|
|
8
10
|
submitReply: (action: any) => Promise<any>;
|
package/dist/platform-writing.js
CHANGED
|
@@ -58,7 +58,7 @@ export class PlatformWriting {
|
|
|
58
58
|
...(action.activation ? { activation: action.activation } : {}),
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
async handleFindSubmission(action) {
|
|
61
|
+
async handleFindSubmission(action, context = {}) {
|
|
62
62
|
if (action.kind === "follow") {
|
|
63
63
|
let value = await this.#followStage(action, "verify-read", () => this.#readPerson(action));
|
|
64
64
|
const verificationAlerts = [...(value.alerts ?? [])];
|
|
@@ -117,7 +117,8 @@ export class PlatformWriting {
|
|
|
117
117
|
? { url: action.url, evidenceRef: action.evidenceRef }
|
|
118
118
|
: { url: action.accountURL, evidenceRef: action.accountEvidenceRef };
|
|
119
119
|
let value = await this.#open(receiptTarget, { view: "posts", limit: 5 });
|
|
120
|
-
if (
|
|
120
|
+
if (context.phase !== "baseline" &&
|
|
121
|
+
!(action.kind === "original" && observedReceipt) &&
|
|
121
122
|
!value.needsStateCheck &&
|
|
122
123
|
!(this.#browser.capabilities?.("refreshPosts") ?? ["refreshPosts"]).length) {
|
|
123
124
|
await this.#browser.act({
|
package/dist/platform.d.ts
CHANGED
|
@@ -11,7 +11,9 @@ export declare class XPlatform {
|
|
|
11
11
|
readFeedback: ({ targets, replyLimit }?: any) => Promise<any>;
|
|
12
12
|
readPerson: (target: any) => Promise<any>;
|
|
13
13
|
readPost: (target: any) => Promise<any>;
|
|
14
|
-
findSubmission: (action: any
|
|
14
|
+
findSubmission: (action: any, context?: {
|
|
15
|
+
phase?: "baseline";
|
|
16
|
+
}) => Promise<any>;
|
|
15
17
|
submitFollow: (action: any) => Promise<any>;
|
|
16
18
|
submitPost: (action: any) => Promise<any>;
|
|
17
19
|
submitReply: (action: any) => Promise<any>;
|
|
@@ -23,7 +23,7 @@ export function readNavigation({ targetLabel, bookmarkNavigation = false, } = {}
|
|
|
23
23
|
...new Set([
|
|
24
24
|
...Array.from(document.querySelectorAll("nav a[href],nav button")).filter((el) => !el.closest("main")),
|
|
25
25
|
...Array.from(primary?.querySelectorAll('a[href],button,[role="button"],[role="tab"]') ?? []),
|
|
26
|
-
...Array.from(document.querySelectorAll('[role="menu"] a[href], [role="menu"] button, [data-testid="Dropdown"] a[href], [data-testid="Dropdown"] button')),
|
|
26
|
+
...Array.from(document.querySelectorAll('[role="menu"] a[href], [role="menu"] button, [data-testid="Dropdown"] a[href], [data-testid="Dropdown"] button, [data-testid="Dropdown"] [role="menuitemradio"]')),
|
|
27
27
|
]),
|
|
28
28
|
].filter((el) => visible(el) &&
|
|
29
29
|
!el.closest("article") &&
|
package/dist/readers-posts.js
CHANGED
|
@@ -108,6 +108,7 @@ export function readPosts({ offset = 0, limit = 5, targetId = "", } = {}) {
|
|
|
108
108
|
?.getAttribute("href")
|
|
109
109
|
?.match(/\/status\/(\d+)/)?.[1])
|
|
110
110
|
.filter(Boolean),
|
|
111
|
+
viewportScrollTop: document.scrollingElement?.scrollTop,
|
|
111
112
|
atRenderedTail: Boolean(document.scrollingElement) &&
|
|
112
113
|
document.scrollingElement.scrollTop +
|
|
113
114
|
document.scrollingElement.clientHeight >=
|
|
@@ -109,6 +109,7 @@ export const readXObservation = (query) => {
|
|
|
109
109
|
?.getAttribute("href")
|
|
110
110
|
?.match(/\/status\/(\d+)/)?.[1])
|
|
111
111
|
.filter(Boolean),
|
|
112
|
+
viewportScrollTop: document.scrollingElement?.scrollTop,
|
|
112
113
|
atRenderedTail: Boolean(document.scrollingElement) &&
|
|
113
114
|
document.scrollingElement.scrollTop +
|
|
114
115
|
document.scrollingElement.clientHeight >=
|
|
@@ -445,7 +446,7 @@ export const readXObservation = (query) => {
|
|
|
445
446
|
...new Set([
|
|
446
447
|
...Array.from(document.querySelectorAll("nav a[href],nav button")).filter((el) => !el.closest("main")),
|
|
447
448
|
...Array.from(primary?.querySelectorAll('a[href],button,[role="button"],[role="tab"]') ?? []),
|
|
448
|
-
...Array.from(document.querySelectorAll('[role="menu"] a[href], [role="menu"] button, [data-testid="Dropdown"] a[href], [data-testid="Dropdown"] button')),
|
|
449
|
+
...Array.from(document.querySelectorAll('[role="menu"] a[href], [role="menu"] button, [data-testid="Dropdown"] a[href], [data-testid="Dropdown"] button, [data-testid="Dropdown"] [role="menuitemradio"]')),
|
|
449
450
|
]),
|
|
450
451
|
].filter((el) => visible(el) &&
|
|
451
452
|
!el.closest("article") &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/x-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.56",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed X task harness with compact business results and explicit diagnostics.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "84766d97c35a9cecbee5b8c6461aaa2e28d02c81"
|
|
46
46
|
}
|