@nextclaw/x-harness 0.1.1 → 0.2.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/LICENSE +231 -6
- package/README.md +15 -1
- package/SOURCE.md +9 -0
- package/cli/main.js +232 -200
- package/dist/api.d.ts +56 -2
- package/dist/api.js +130 -79
- package/dist/boundary.js +1 -0
- package/dist/browser-common.d.ts +11 -0
- package/dist/browser-common.js +34 -18
- package/dist/browser-dom-capabilities.d.ts +7 -2
- package/dist/browser-dom-capabilities.js +123 -102
- package/dist/browser-dom-dependencies.js +1 -0
- package/dist/browser-dom-editing.d.ts +20 -2
- package/dist/browser-dom-editing.js +148 -116
- package/dist/browser-dom-navigation.d.ts +11 -2
- package/dist/browser-dom-navigation.js +66 -50
- package/dist/browser-dom-submission.d.ts +6 -1
- package/dist/browser-dom-submission.js +37 -18
- package/dist/browser-dom.d.ts +13 -1
- package/dist/browser-dom.js +113 -66
- package/dist/browser-native.d.ts +14 -1
- package/dist/browser-native.js +161 -128
- package/dist/creator-native.d.ts +3 -0
- package/dist/creator-native.js +113 -0
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.js +10 -0
- package/dist/growth-batch.d.ts +6 -3
- package/dist/growth-batch.js +157 -135
- package/dist/growth-dependencies.js +1 -0
- package/dist/growth-execution.d.ts +8 -2
- package/dist/growth-execution.js +68 -37
- package/dist/growth-parallel.d.ts +6 -2
- package/dist/growth-parallel.js +78 -47
- package/dist/growth-plan.d.ts +8 -3
- package/dist/growth-plan.js +45 -23
- package/dist/growth-policy.d.ts +11 -2
- package/dist/growth-policy.js +80 -53
- package/dist/growth-publishing.d.ts +7 -2
- package/dist/growth-publishing.js +72 -46
- package/dist/growth-reading.d.ts +6 -2
- package/dist/growth-reading.js +47 -25
- package/dist/growth-recovery.d.ts +7 -4
- package/dist/growth-recovery.js +177 -148
- package/dist/growth-state.d.ts +10 -2
- package/dist/growth-state.js +67 -30
- package/dist/growth-tasks.d.ts +19 -16
- package/dist/growth-tasks.js +153 -90
- package/dist/growth.d.ts +25 -1
- package/dist/growth.js +207 -124
- package/dist/platform-capabilities.d.ts +6 -2
- package/dist/platform-capabilities.js +18 -8
- package/dist/platform-collections.d.ts +10 -3
- package/dist/platform-collections.js +62 -35
- package/dist/platform-dependencies.js +1 -0
- package/dist/platform-diagnostics.d.ts +7 -2
- package/dist/platform-diagnostics.js +31 -19
- package/dist/platform-observation.d.ts +10 -2
- package/dist/platform-observation.js +44 -23
- package/dist/platform-targets.d.ts +10 -3
- package/dist/platform-targets.js +39 -14
- package/dist/platform-writing.d.ts +10 -2
- package/dist/platform-writing.js +63 -32
- package/dist/platform.d.ts +22 -1
- package/dist/platform.js +120 -52
- package/dist/readers-ax-host.d.ts +11 -1
- package/dist/readers-ax-host.js +108 -99
- package/dist/readers-ax-program.js +5 -0
- package/dist/readers-ax-tree.js +1 -0
- package/dist/readers-cli.js +79 -0
- package/dist/readers.js +9 -0
- package/dist/round-state.js +18 -1
- package/dist/store.d.ts +22 -4
- package/dist/store.js +173 -151
- package/dist/types.js +1 -0
- package/generated/observation.mjs +1 -1
- package/package.json +23 -9
- package/skill/SKILL.md +2 -2
- package/skill/references/reads.md +1 -1
- package/skill/references/recovery.md +4 -4
- package/skill/references/setup.md +1 -1
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export class PlatformCapabilities {
|
|
2
|
+
#missing;
|
|
3
|
+
#browser;
|
|
4
|
+
preflight;
|
|
5
|
+
constructor(dependencies) {
|
|
6
|
+
const { missing, browser } = dependencies;
|
|
7
|
+
this.#missing = missing;
|
|
8
|
+
this.#browser = browser;
|
|
9
|
+
this.preflight = this.handlePreflight.bind(this);
|
|
10
|
+
}
|
|
11
|
+
handlePreflight({ operation, input = {} }) {
|
|
12
|
+
const absent = this.#missing(operation);
|
|
5
13
|
if (operation === "readCreator" &&
|
|
6
|
-
typeof browser.collectCreator !== "function")
|
|
14
|
+
typeof this.#browser.collectCreator !== "function")
|
|
7
15
|
absent.push("browser.collectCreator");
|
|
8
16
|
if (input.kind === "follow" || operation === "submitFollow")
|
|
9
|
-
absent.push(...(browser.capabilities?.("refreshPerson") ?? ["refreshPerson"]));
|
|
17
|
+
absent.push(...(this.#browser.capabilities?.("refreshPerson") ?? ["refreshPerson"]));
|
|
10
18
|
if (operation !== "read" && input.assets?.length)
|
|
11
|
-
absent.push(...(browser.capabilities?.("attachAssets") ?? ["attachAssets"]));
|
|
19
|
+
absent.push(...(this.#browser.capabilities?.("attachAssets") ?? ["attachAssets"]));
|
|
12
20
|
return {
|
|
13
21
|
covered: absent.length === 0,
|
|
14
22
|
missing: [...new Set(absent)],
|
|
15
23
|
owner: "scripts/x-harness-browser.mjs",
|
|
16
24
|
};
|
|
17
25
|
}
|
|
18
|
-
|
|
26
|
+
}
|
|
27
|
+
export function createPlatformCapabilities(...args) {
|
|
28
|
+
return new PlatformCapabilities(...args);
|
|
19
29
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { PlatformDependencies } from "./platform-dependencies.js";
|
|
2
|
-
export declare
|
|
3
|
-
|
|
2
|
+
export declare class PlatformCollections {
|
|
3
|
+
#private;
|
|
4
|
+
collectSources: ({ processedIds, maxPages, limit }?: any) => Promise<any>;
|
|
4
5
|
readCreator: (args: import("./types.js").CreatorOptions) => Promise<any>;
|
|
5
6
|
readBookmarks: ({ folder, ...window }?: any) => Promise<any>;
|
|
6
7
|
searchSources: ({ query, limit, maxPages, processedIds, }?: any) => Promise<any>;
|
|
7
|
-
|
|
8
|
+
constructor(dependencies: Pick<PlatformDependencies, "ready" | "observed" | "browser" | "choose">);
|
|
9
|
+
private handleCollectSources;
|
|
10
|
+
private handleReadCreator;
|
|
11
|
+
private handleReadBookmarks;
|
|
12
|
+
private handleSearchSources;
|
|
13
|
+
}
|
|
14
|
+
export declare function createPlatformCollections(...args: ConstructorParameters<typeof PlatformCollections>): PlatformCollections;
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { xURL, source } from "./platform-rules.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export class PlatformCollections {
|
|
3
|
+
#ready;
|
|
4
|
+
#observed;
|
|
5
|
+
#browser;
|
|
6
|
+
#choose;
|
|
7
|
+
collectSources;
|
|
8
|
+
readCreator;
|
|
9
|
+
readBookmarks;
|
|
10
|
+
searchSources;
|
|
11
|
+
constructor(dependencies) {
|
|
12
|
+
const { ready, observed, browser, choose } = dependencies;
|
|
13
|
+
this.#ready = ready;
|
|
14
|
+
this.#observed = observed;
|
|
15
|
+
this.#browser = browser;
|
|
16
|
+
this.#choose = choose;
|
|
17
|
+
this.collectSources = this.handleCollectSources.bind(this);
|
|
18
|
+
this.readCreator = this.handleReadCreator.bind(this);
|
|
19
|
+
this.readBookmarks = this.handleReadBookmarks.bind(this);
|
|
20
|
+
this.searchSources = this.handleSearchSources.bind(this);
|
|
21
|
+
}
|
|
22
|
+
async handleCollectSources({ processedIds = [], maxPages = 3, limit = 1, } = {}) {
|
|
5
23
|
if (!Array.isArray(processedIds) ||
|
|
6
24
|
!Number.isInteger(maxPages) ||
|
|
7
25
|
maxPages < 1 ||
|
|
@@ -13,7 +31,7 @@ export function createPlatformCollections(dependencies) {
|
|
|
13
31
|
const seen = new Set(processedIds), sources = [], missing = [];
|
|
14
32
|
let last, previousIds = "", offset = 0, firstDOMId;
|
|
15
33
|
for (let page = 0; page < maxPages && sources.length < limit; page++) {
|
|
16
|
-
last = await ready({ view: "posts", offset, limit: 5 });
|
|
34
|
+
last = await this.#ready({ view: "posts", offset, limit: 5 });
|
|
17
35
|
if (page === 0)
|
|
18
36
|
firstDOMId = last.posts?.[0]?.id;
|
|
19
37
|
if (last.needsStateCheck || last.alerts?.length)
|
|
@@ -39,7 +57,7 @@ export function createPlatformCollections(dependencies) {
|
|
|
39
57
|
seen.add(candidate.id);
|
|
40
58
|
let post = candidate;
|
|
41
59
|
if (candidate.needsExpansion) {
|
|
42
|
-
const target = await observed({
|
|
60
|
+
const target = await this.#observed({
|
|
43
61
|
view: "posts",
|
|
44
62
|
targetId: candidate.id,
|
|
45
63
|
limit: 1,
|
|
@@ -48,8 +66,11 @@ export function createPlatformCollections(dependencies) {
|
|
|
48
66
|
missing.push("expansion-target-unconfirmed");
|
|
49
67
|
continue;
|
|
50
68
|
}
|
|
51
|
-
await browser.act({
|
|
52
|
-
|
|
69
|
+
await this.#browser.act({
|
|
70
|
+
operation: "expandPost",
|
|
71
|
+
postId: candidate.id,
|
|
72
|
+
});
|
|
73
|
+
const expanded = await this.#ready({
|
|
53
74
|
view: "posts",
|
|
54
75
|
targetId: candidate.id,
|
|
55
76
|
limit: 1,
|
|
@@ -77,12 +98,12 @@ export function createPlatformCollections(dependencies) {
|
|
|
77
98
|
}
|
|
78
99
|
if (sources.length < limit && page + 1 < maxPages) {
|
|
79
100
|
// Expansion narrowed the semantic window; restore the list before scrolling.
|
|
80
|
-
last = await observed({ view: "posts", offset, limit: 5 });
|
|
101
|
+
last = await this.#observed({ view: "posts", offset, limit: 5 });
|
|
81
102
|
if (last.moreInDOM)
|
|
82
103
|
offset += 5;
|
|
83
104
|
else {
|
|
84
|
-
await browser.act({ operation: "nextPosts" });
|
|
85
|
-
const advanced = await ready({ view: "posts", limit: 5 });
|
|
105
|
+
await this.#browser.act({ operation: "nextPosts" });
|
|
106
|
+
const advanced = await this.#ready({ view: "posts", limit: 5 });
|
|
86
107
|
// Retained DOM prefixes include a partial final window too; skip
|
|
87
108
|
// exactly the consumed cards, while virtualized replacements restart.
|
|
88
109
|
offset =
|
|
@@ -106,14 +127,14 @@ export function createPlatformCollections(dependencies) {
|
|
|
106
127
|
: [...new Set([...missing, "bounded-source-window-exhausted"])],
|
|
107
128
|
};
|
|
108
129
|
}
|
|
109
|
-
async
|
|
110
|
-
if (typeof browser.collectCreator !== "function")
|
|
130
|
+
async handleReadCreator(args) {
|
|
131
|
+
if (typeof this.#browser.collectCreator !== "function")
|
|
111
132
|
return {
|
|
112
133
|
status: "needsCapability",
|
|
113
134
|
complete: false,
|
|
114
135
|
missing: ["browser.collectCreator"],
|
|
115
136
|
};
|
|
116
|
-
const value = await browser.collectCreator(args);
|
|
137
|
+
const value = await this.#browser.collectCreator(args);
|
|
117
138
|
const absent = [
|
|
118
139
|
...new Set([
|
|
119
140
|
...(value.missing ?? []),
|
|
@@ -130,19 +151,19 @@ export function createPlatformCollections(dependencies) {
|
|
|
130
151
|
missing: absent,
|
|
131
152
|
};
|
|
132
153
|
}
|
|
133
|
-
async
|
|
154
|
+
async handleReadBookmarks({ folder, ...window } = {}) {
|
|
134
155
|
if (typeof folder !== "string" || !folder.trim())
|
|
135
156
|
throw new Error("需指定收藏文件夹;不以全部书签冒充命名文件夹");
|
|
136
|
-
let nav = await ready({ view: "navigation", targetLabel: folder });
|
|
157
|
+
let nav = await this.#ready({ view: "navigation", targetLabel: folder });
|
|
137
158
|
const bookmark = (c) => /^(书签|收藏|Bookmarks)$/i.test(c.label);
|
|
138
|
-
if (!(await choose(nav, bookmark, { activateSelected: true }))) {
|
|
139
|
-
if (await choose(nav, (c) => /^(更多菜单项|More menu items)$/i.test(c.label))) {
|
|
140
|
-
nav = await ready({ view: "menu" });
|
|
141
|
-
if (!(await choose(nav, bookmark))) {
|
|
159
|
+
if (!(await this.#choose(nav, bookmark, { activateSelected: true }))) {
|
|
160
|
+
if (await this.#choose(nav, (c) => /^(更多菜单项|More menu items)$/i.test(c.label))) {
|
|
161
|
+
nav = await this.#ready({ view: "menu" });
|
|
162
|
+
if (!(await this.#choose(nav, bookmark))) {
|
|
142
163
|
if (nav.items?.length)
|
|
143
|
-
await browser.act({ operation: "dismissMenu" });
|
|
144
|
-
nav = await ready({ view: "navigation", targetLabel: folder });
|
|
145
|
-
if (!(await choose(nav, (c) => /^(历史|History)$/i.test(c.label))))
|
|
164
|
+
await this.#browser.act({ operation: "dismissMenu" });
|
|
165
|
+
nav = await this.#ready({ view: "navigation", targetLabel: folder });
|
|
166
|
+
if (!(await this.#choose(nav, (c) => /^(历史|History)$/i.test(c.label))))
|
|
146
167
|
return {
|
|
147
168
|
status: "needsCapability",
|
|
148
169
|
owner: "scripts/x-harness-browser.mjs",
|
|
@@ -150,34 +171,34 @@ export function createPlatformCollections(dependencies) {
|
|
|
150
171
|
};
|
|
151
172
|
}
|
|
152
173
|
}
|
|
153
|
-
else if (!(await choose(nav, (c) => /^(历史|History)$/i.test(c.label))))
|
|
174
|
+
else if (!(await this.#choose(nav, (c) => /^(历史|History)$/i.test(c.label))))
|
|
154
175
|
return {
|
|
155
176
|
status: "needsCapability",
|
|
156
177
|
owner: "scripts/x-harness-browser.mjs",
|
|
157
178
|
missing: ["bookmarks-entry-not-observed"],
|
|
158
179
|
};
|
|
159
180
|
}
|
|
160
|
-
nav = await ready({ view: "navigation", targetLabel: folder });
|
|
181
|
+
nav = await this.#ready({ view: "navigation", targetLabel: folder });
|
|
161
182
|
const tabs = (nav.controls ?? []).filter((c) => c.role === "tab" && bookmark(c));
|
|
162
183
|
if (tabs.length === 1 &&
|
|
163
184
|
(!tabs[0].selected ||
|
|
164
185
|
!(nav.controls ?? []).some((c) => c.label.split("\n")[0] === folder))) {
|
|
165
|
-
await choose(nav, (c) => c === tabs[0], {
|
|
186
|
+
await this.#choose(nav, (c) => c === tabs[0], {
|
|
166
187
|
activateSelected: true,
|
|
167
188
|
});
|
|
168
|
-
nav = await ready({ view: "navigation", targetLabel: folder });
|
|
189
|
+
nav = await this.#ready({ view: "navigation", targetLabel: folder });
|
|
169
190
|
}
|
|
170
191
|
if (!(nav.controls ?? []).some((c) => c.label.split("\n")[0] === folder)) {
|
|
171
192
|
try {
|
|
172
|
-
await browser.act({ operation: "waitForFolder", folder });
|
|
193
|
+
await this.#browser.act({ operation: "waitForFolder", folder });
|
|
173
194
|
}
|
|
174
195
|
catch (error) {
|
|
175
196
|
if (!/timed out|timeout/i.test(error.message))
|
|
176
197
|
throw error;
|
|
177
198
|
}
|
|
178
|
-
nav = await ready({ view: "navigation", targetLabel: folder });
|
|
199
|
+
nav = await this.#ready({ view: "navigation", targetLabel: folder });
|
|
179
200
|
}
|
|
180
|
-
if (!(await choose(nav, (c) => c.label.split("\n")[0] === folder)))
|
|
201
|
+
if (!(await this.#choose(nav, (c) => c.label.split("\n")[0] === folder)))
|
|
181
202
|
return {
|
|
182
203
|
status: "needsCapability",
|
|
183
204
|
owner: "scripts/x-harness-browser.mjs",
|
|
@@ -186,7 +207,7 @@ export function createPlatformCollections(dependencies) {
|
|
|
186
207
|
folder,
|
|
187
208
|
controls: nav.controls,
|
|
188
209
|
};
|
|
189
|
-
const selected = await ready({ view: "navigation" });
|
|
210
|
+
const selected = await this.#ready({ view: "navigation" });
|
|
190
211
|
if (selected.page === nav.page && !selected.headings?.includes(folder))
|
|
191
212
|
return {
|
|
192
213
|
status: "needsDecision",
|
|
@@ -195,7 +216,7 @@ export function createPlatformCollections(dependencies) {
|
|
|
195
216
|
folder,
|
|
196
217
|
page: selected.page,
|
|
197
218
|
};
|
|
198
|
-
const value = await
|
|
219
|
+
const value = await this.handleCollectSources(window);
|
|
199
220
|
return {
|
|
200
221
|
...value,
|
|
201
222
|
folder,
|
|
@@ -206,9 +227,13 @@ export function createPlatformCollections(dependencies) {
|
|
|
206
227
|
},
|
|
207
228
|
};
|
|
208
229
|
}
|
|
209
|
-
async
|
|
210
|
-
await browser.act({ operation: "searchSources", query });
|
|
211
|
-
const value = await
|
|
230
|
+
async handleSearchSources({ query, limit = 3, maxPages = 1, processedIds = [], } = {}) {
|
|
231
|
+
await this.#browser.act({ operation: "searchSources", query });
|
|
232
|
+
const value = await this.handleCollectSources({
|
|
233
|
+
limit,
|
|
234
|
+
maxPages,
|
|
235
|
+
processedIds,
|
|
236
|
+
});
|
|
212
237
|
if (!value.page ||
|
|
213
238
|
xURL(value.page).pathname !== "/search" ||
|
|
214
239
|
xURL(value.page).searchParams.get("q") !== query)
|
|
@@ -219,5 +244,7 @@ export function createPlatformCollections(dependencies) {
|
|
|
219
244
|
};
|
|
220
245
|
return { ...value, query };
|
|
221
246
|
}
|
|
222
|
-
|
|
247
|
+
}
|
|
248
|
+
export function createPlatformCollections(...args) {
|
|
249
|
+
return new PlatformCollections(...args);
|
|
223
250
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { PlatformDependencies } from "./platform-dependencies.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class PlatformDiagnostics {
|
|
3
|
+
#private;
|
|
3
4
|
followStage: (action: any, stage: any, run: any) => Promise<any>;
|
|
4
5
|
captureProof: ({ observation }: any) => {
|
|
5
6
|
followDiagnostics?: any[] | undefined;
|
|
6
7
|
capturedAt: string;
|
|
7
8
|
observation: any;
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
constructor(dependencies: Pick<PlatformDependencies, "clock">);
|
|
11
|
+
private handleFollowStage;
|
|
12
|
+
private handleCaptureProof;
|
|
13
|
+
}
|
|
14
|
+
export declare function createPlatformDiagnostics(...args: ConstructorParameters<typeof PlatformDiagnostics>): PlatformDiagnostics;
|
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
export class PlatformDiagnostics {
|
|
2
|
+
#clock;
|
|
3
|
+
#followDiagnostics;
|
|
4
|
+
followStage;
|
|
5
|
+
captureProof;
|
|
6
|
+
constructor(dependencies) {
|
|
7
|
+
const { clock } = dependencies;
|
|
8
|
+
this.#clock = clock;
|
|
9
|
+
this.#followDiagnostics = [];
|
|
10
|
+
this.followStage = this.handleFollowStage.bind(this);
|
|
11
|
+
this.captureProof = this.handleCaptureProof.bind(this);
|
|
12
|
+
}
|
|
13
|
+
async handleFollowStage(action, stage, run) {
|
|
14
|
+
const started = this.#clock();
|
|
7
15
|
const event = {
|
|
8
16
|
actionId: action.id ?? null,
|
|
9
17
|
handle: action.handle,
|
|
10
18
|
stage,
|
|
11
19
|
startedAt: new Date(started).toISOString(),
|
|
12
20
|
};
|
|
13
|
-
followDiagnostics.push(event);
|
|
14
|
-
if (followDiagnostics.length > 40)
|
|
15
|
-
followDiagnostics.shift();
|
|
21
|
+
this.#followDiagnostics.push(event);
|
|
22
|
+
if (this.#followDiagnostics.length > 40)
|
|
23
|
+
this.#followDiagnostics.shift();
|
|
16
24
|
try {
|
|
17
25
|
const value = await run();
|
|
18
26
|
Object.assign(event, {
|
|
19
27
|
status: "returned",
|
|
20
|
-
durationMs: clock() - started,
|
|
28
|
+
durationMs: this.#clock() - started,
|
|
21
29
|
});
|
|
22
30
|
if (value && typeof value === "object")
|
|
23
31
|
Object.assign(event, {
|
|
@@ -34,18 +42,22 @@ export function createPlatformDiagnostics(dependencies) {
|
|
|
34
42
|
catch (error) {
|
|
35
43
|
Object.assign(event, {
|
|
36
44
|
status: "error",
|
|
37
|
-
durationMs: clock() - started,
|
|
45
|
+
durationMs: this.#clock() - started,
|
|
38
46
|
error: String(error.message ?? error).slice(0, 500),
|
|
39
47
|
});
|
|
40
48
|
throw error;
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
handleCaptureProof({ observation }) {
|
|
52
|
+
return {
|
|
53
|
+
capturedAt: new Date(this.#clock()).toISOString(),
|
|
54
|
+
observation,
|
|
55
|
+
...(this.#followDiagnostics.length
|
|
56
|
+
? { followDiagnostics: structuredClone(this.#followDiagnostics) }
|
|
57
|
+
: {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function createPlatformDiagnostics(...args) {
|
|
62
|
+
return new PlatformDiagnostics(...args);
|
|
51
63
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { PlatformDependencies } from "./platform-dependencies.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class PlatformObservation {
|
|
3
|
+
#private;
|
|
3
4
|
missing: (operation: any) => any[];
|
|
4
5
|
observed: (query: any) => Promise<any>;
|
|
5
6
|
open: (target: any, query: any) => Promise<any>;
|
|
6
7
|
ready: (query: any) => Promise<any>;
|
|
7
8
|
choose: (value: any, pattern: any, { activateSelected }?: any) => Promise<any>;
|
|
8
|
-
|
|
9
|
+
constructor(dependencies: Pick<PlatformDependencies, "browser" | "clock">);
|
|
10
|
+
private handleObserved;
|
|
11
|
+
private handleOpen;
|
|
12
|
+
private handleReady;
|
|
13
|
+
private handleChoose;
|
|
14
|
+
private handleMissing;
|
|
15
|
+
}
|
|
16
|
+
export declare function createPlatformObservation(...args: ConstructorParameters<typeof PlatformObservation>): PlatformObservation;
|
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
import { xURL } from "./platform-rules.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
2
|
+
export class PlatformObservation {
|
|
3
|
+
#browser;
|
|
4
|
+
#clock;
|
|
5
|
+
#methods;
|
|
6
|
+
missing;
|
|
7
|
+
observed;
|
|
8
|
+
open;
|
|
9
|
+
ready;
|
|
10
|
+
choose;
|
|
11
|
+
constructor(dependencies) {
|
|
12
|
+
const { browser, clock } = dependencies;
|
|
13
|
+
this.#browser = browser;
|
|
14
|
+
this.#clock = clock;
|
|
15
|
+
this.#methods = ["observe", "act"];
|
|
16
|
+
this.missing = this.handleMissing.bind(this);
|
|
17
|
+
this.observed = this.handleObserved.bind(this);
|
|
18
|
+
this.open = this.handleOpen.bind(this);
|
|
19
|
+
this.ready = this.handleReady.bind(this);
|
|
20
|
+
this.choose = this.handleChoose.bind(this);
|
|
21
|
+
}
|
|
22
|
+
async handleObserved(query) {
|
|
23
|
+
const value = await this.#browser.observe(query);
|
|
13
24
|
return {
|
|
14
25
|
...value,
|
|
15
|
-
observedAt: new Date(clock()).toISOString(),
|
|
26
|
+
observedAt: new Date(this.#clock()).toISOString(),
|
|
16
27
|
fresh: true,
|
|
17
28
|
};
|
|
18
29
|
}
|
|
19
|
-
async
|
|
30
|
+
async handleOpen(target, query) {
|
|
20
31
|
const url = xURL(target.url);
|
|
21
32
|
if (!target.evidenceRef)
|
|
22
33
|
throw new Error("目标URL缺已观察证据");
|
|
23
|
-
let value = await
|
|
34
|
+
let value = await this.handleObserved(query);
|
|
24
35
|
let atTarget = false;
|
|
25
36
|
try {
|
|
26
37
|
atTarget = xURL(value.page).href === url.href;
|
|
@@ -29,14 +40,14 @@ export function createPlatformObservation(dependencies) {
|
|
|
29
40
|
/* Blank/non-X pages require observed navigation. */
|
|
30
41
|
}
|
|
31
42
|
if (!atTarget) {
|
|
32
|
-
await browser.act({
|
|
43
|
+
await this.#browser.act({
|
|
33
44
|
operation: "openObserved",
|
|
34
45
|
url: url.href,
|
|
35
46
|
evidenceRef: target.evidenceRef,
|
|
36
47
|
view: query.view,
|
|
37
48
|
handle: query.handle,
|
|
38
49
|
});
|
|
39
|
-
value = await
|
|
50
|
+
value = await this.handleReady(query);
|
|
40
51
|
}
|
|
41
52
|
if (!value.page || xURL(value.page).href !== url.href)
|
|
42
53
|
return {
|
|
@@ -46,30 +57,40 @@ export function createPlatformObservation(dependencies) {
|
|
|
46
57
|
};
|
|
47
58
|
return value;
|
|
48
59
|
}
|
|
49
|
-
async
|
|
50
|
-
let value = await
|
|
60
|
+
async handleReady(query) {
|
|
61
|
+
let value = await this.handleObserved(query);
|
|
51
62
|
if (value.needsStateCheck &&
|
|
52
63
|
!value.alerts?.length &&
|
|
53
64
|
query.view === "posts") {
|
|
54
65
|
try {
|
|
55
|
-
await browser.act({ operation: "waitForPosts" });
|
|
66
|
+
await this.#browser.act({ operation: "waitForPosts" });
|
|
56
67
|
}
|
|
57
68
|
catch (error) {
|
|
58
69
|
if (!/timed out|timeout/i.test(error.message))
|
|
59
70
|
throw error;
|
|
60
71
|
}
|
|
61
|
-
value = await
|
|
72
|
+
value = await this.handleObserved(query);
|
|
62
73
|
}
|
|
63
74
|
return value;
|
|
64
75
|
}
|
|
65
|
-
async
|
|
76
|
+
async handleChoose(value, pattern, { activateSelected = false } = {}) {
|
|
66
77
|
const controls = (value.controls ?? value.items ?? []).filter((c) => !c.disabled && pattern(c));
|
|
67
78
|
if (controls.length !== 1)
|
|
68
79
|
return false;
|
|
69
80
|
if (controls[0].selected && !activateSelected)
|
|
70
81
|
return true;
|
|
71
|
-
await browser.act({ operation: "openControl", ...controls[0] });
|
|
82
|
+
await this.#browser.act({ operation: "openControl", ...controls[0] });
|
|
72
83
|
return true;
|
|
73
84
|
}
|
|
74
|
-
|
|
85
|
+
handleMissing(operation) {
|
|
86
|
+
return [
|
|
87
|
+
...this.#methods.filter((name) => typeof this.#browser?.[name] !== "function"),
|
|
88
|
+
...(typeof this.#browser?.capabilities === "function"
|
|
89
|
+
? this.#browser.capabilities(operation)
|
|
90
|
+
: ["capabilities"]),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function createPlatformObservation(...args) {
|
|
95
|
+
return new PlatformObservation(...args);
|
|
75
96
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { PlatformDependencies } from "./platform-dependencies.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class PlatformTargets {
|
|
3
|
+
#private;
|
|
3
4
|
readAccount: () => Promise<any>;
|
|
4
|
-
readFeedback: ({ targets, replyLimit
|
|
5
|
+
readFeedback: ({ targets, replyLimit }?: any) => Promise<any>;
|
|
5
6
|
readPerson: (target: any) => Promise<any>;
|
|
6
7
|
readPost: (target: any) => Promise<any>;
|
|
7
|
-
|
|
8
|
+
constructor(dependencies: Pick<PlatformDependencies, "observed" | "open" | "browser" | "ready">);
|
|
9
|
+
private handleReadFeedback;
|
|
10
|
+
private handleReadPerson;
|
|
11
|
+
private handleReadPost;
|
|
12
|
+
private handleReadAccount;
|
|
13
|
+
}
|
|
14
|
+
export declare function createPlatformTargets(...args: ConstructorParameters<typeof PlatformTargets>): PlatformTargets;
|
package/dist/platform-targets.js
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import { xURL, sameHandle, handlePattern, source } from "./platform-rules.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
export class PlatformTargets {
|
|
3
|
+
#observed;
|
|
4
|
+
#open;
|
|
5
|
+
#browser;
|
|
6
|
+
#ready;
|
|
7
|
+
readAccount;
|
|
8
|
+
readFeedback;
|
|
9
|
+
readPerson;
|
|
10
|
+
readPost;
|
|
11
|
+
constructor(dependencies) {
|
|
12
|
+
const { observed, open, browser, ready } = dependencies;
|
|
13
|
+
this.#observed = observed;
|
|
14
|
+
this.#open = open;
|
|
15
|
+
this.#browser = browser;
|
|
16
|
+
this.#ready = ready;
|
|
17
|
+
this.readAccount = this.handleReadAccount.bind(this);
|
|
18
|
+
this.readFeedback = this.handleReadFeedback.bind(this);
|
|
19
|
+
this.readPerson = this.handleReadPerson.bind(this);
|
|
20
|
+
this.readPost = this.handleReadPost.bind(this);
|
|
21
|
+
}
|
|
22
|
+
async handleReadFeedback({ targets, replyLimit = 3, } = {}) {
|
|
6
23
|
if (!Array.isArray(targets) || targets.length < 1 || targets.length > 5)
|
|
7
24
|
throw new Error("反馈窗口须为1–5个已发布ID");
|
|
8
25
|
if (!Number.isInteger(replyLimit) || replyLimit < 0 || replyLimit > 3)
|
|
9
26
|
throw new Error("回复核验窗口须为0–3");
|
|
10
27
|
const feedback = [];
|
|
11
28
|
for (const target of targets) {
|
|
12
|
-
const value = await
|
|
29
|
+
const value = await this.handleReadPost(target), post = value.posts?.find((p) => p.id === target.postId);
|
|
13
30
|
if (value.needsStateCheck || value.alerts?.length || !post) {
|
|
14
31
|
feedback.push({
|
|
15
32
|
id: target.postId,
|
|
@@ -17,7 +34,7 @@ export function createPlatformTargets(dependencies) {
|
|
|
17
34
|
});
|
|
18
35
|
continue;
|
|
19
36
|
}
|
|
20
|
-
const thread = await observed({ view: "posts", limit: 5 });
|
|
37
|
+
const thread = await this.#observed({ view: "posts", limit: 5 });
|
|
21
38
|
const replies = [], unconfirmedReplyIds = [];
|
|
22
39
|
const targetIndex = (thread.posts ?? []).findIndex((p) => p.id === post.id);
|
|
23
40
|
const candidates = targetIndex < 0
|
|
@@ -27,7 +44,7 @@ export function createPlatformTargets(dependencies) {
|
|
|
27
44
|
if (!candidate.primary || !candidate.id)
|
|
28
45
|
continue;
|
|
29
46
|
const url = new URL(candidate.primary, thread.page).href;
|
|
30
|
-
const detail = await open({
|
|
47
|
+
const detail = await this.#open({
|
|
31
48
|
url,
|
|
32
49
|
evidenceRef: {
|
|
33
50
|
page: thread.page,
|
|
@@ -68,13 +85,13 @@ export function createPlatformTargets(dependencies) {
|
|
|
68
85
|
pageComplete: false,
|
|
69
86
|
};
|
|
70
87
|
}
|
|
71
|
-
async
|
|
88
|
+
async handleReadPerson(target) {
|
|
72
89
|
if (!handlePattern.test(target.handle ?? ""))
|
|
73
90
|
throw new Error("无效已观察handle");
|
|
74
91
|
if (xURL(target.url).pathname.toLowerCase() !==
|
|
75
92
|
`/${target.handle}`.toLowerCase())
|
|
76
93
|
throw new Error("人物URL与handle不一致");
|
|
77
|
-
const value = await open(target, {
|
|
94
|
+
const value = await this.#open(target, {
|
|
78
95
|
view: "person",
|
|
79
96
|
handle: target.handle,
|
|
80
97
|
});
|
|
@@ -86,11 +103,11 @@ export function createPlatformTargets(dependencies) {
|
|
|
86
103
|
missing: ["person-identity-mismatch"],
|
|
87
104
|
};
|
|
88
105
|
}
|
|
89
|
-
async
|
|
106
|
+
async handleReadPost(target) {
|
|
90
107
|
if (!/^\d+$/.test(target.postId ?? "") ||
|
|
91
108
|
xURL(target.url).pathname.match(/\/status\/(\d+)$/)?.[1] !== target.postId)
|
|
92
109
|
throw new Error("帖子URL与公开ID不一致");
|
|
93
|
-
let value = await open(target, {
|
|
110
|
+
let value = await this.#open(target, {
|
|
94
111
|
view: "posts",
|
|
95
112
|
targetId: target.postId,
|
|
96
113
|
});
|
|
@@ -101,8 +118,11 @@ export function createPlatformTargets(dependencies) {
|
|
|
101
118
|
!value.alerts?.length &&
|
|
102
119
|
posts.length === 1 &&
|
|
103
120
|
posts[0].needsExpansion) {
|
|
104
|
-
await browser.act({
|
|
105
|
-
|
|
121
|
+
await this.#browser.act({
|
|
122
|
+
operation: "expandPost",
|
|
123
|
+
postId: target.postId,
|
|
124
|
+
});
|
|
125
|
+
value = await this.#ready({ view: "posts", targetId: target.postId });
|
|
106
126
|
posts = (value.posts ?? []).filter((p) => p.id === target.postId);
|
|
107
127
|
}
|
|
108
128
|
const missing = [...(value.missing ?? [])];
|
|
@@ -125,5 +145,10 @@ export function createPlatformTargets(dependencies) {
|
|
|
125
145
|
missing,
|
|
126
146
|
};
|
|
127
147
|
}
|
|
128
|
-
|
|
148
|
+
handleReadAccount() {
|
|
149
|
+
return this.#observed({ view: "account" });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function createPlatformTargets(...args) {
|
|
153
|
+
return new PlatformTargets(...args);
|
|
129
154
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { PlatformDependencies } from "./platform-dependencies.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class PlatformWriting {
|
|
3
|
+
#private;
|
|
3
4
|
compose: (action: any) => Promise<any>;
|
|
4
5
|
findSubmission: (action: any) => Promise<any>;
|
|
5
6
|
submitFollow: (action: any) => Promise<any>;
|
|
6
7
|
submitPost: (action: any) => Promise<any>;
|
|
7
8
|
submitReply: (action: any) => Promise<any>;
|
|
8
|
-
|
|
9
|
+
constructor(dependencies: Pick<PlatformDependencies, "observed" | "browser" | "followStage" | "readPerson" | "open" | "clock" | "readPost">);
|
|
10
|
+
private handleCompose;
|
|
11
|
+
private handleFindSubmission;
|
|
12
|
+
private handleSubmitFollow;
|
|
13
|
+
private handleSubmitPost;
|
|
14
|
+
private handleSubmitReply;
|
|
15
|
+
}
|
|
16
|
+
export declare function createPlatformWriting(...args: ConstructorParameters<typeof PlatformWriting>): PlatformWriting;
|