@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
package/dist/platform-writing.js
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
import { xURL, sameHandle, sameText, samePublishedText, } from "./platform-rules.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
export class PlatformWriting {
|
|
3
|
+
#observed;
|
|
4
|
+
#browser;
|
|
5
|
+
#followStage;
|
|
6
|
+
#readPerson;
|
|
7
|
+
#open;
|
|
8
|
+
#clock;
|
|
9
|
+
#readPost;
|
|
10
|
+
compose;
|
|
11
|
+
findSubmission;
|
|
12
|
+
submitFollow;
|
|
13
|
+
submitPost;
|
|
14
|
+
submitReply;
|
|
15
|
+
constructor(dependencies) {
|
|
16
|
+
const { observed, browser, followStage, readPerson, open, clock, readPost, } = dependencies;
|
|
17
|
+
this.#observed = observed;
|
|
18
|
+
this.#browser = browser;
|
|
19
|
+
this.#followStage = followStage;
|
|
20
|
+
this.#readPerson = readPerson;
|
|
21
|
+
this.#open = open;
|
|
22
|
+
this.#clock = clock;
|
|
23
|
+
this.#readPost = readPost;
|
|
24
|
+
this.compose = this.handleCompose.bind(this);
|
|
25
|
+
this.findSubmission = this.handleFindSubmission.bind(this);
|
|
26
|
+
this.submitFollow = this.handleSubmitFollow.bind(this);
|
|
27
|
+
this.submitPost = this.handleSubmitPost.bind(this);
|
|
28
|
+
this.submitReply = this.handleSubmitReply.bind(this);
|
|
29
|
+
}
|
|
30
|
+
async handleCompose(action) {
|
|
31
|
+
let value = await this.#observed({ view: "composer" });
|
|
6
32
|
if (value.needsStateCheck ||
|
|
7
33
|
value.alerts?.length ||
|
|
8
34
|
typeof value.editorText !== "string" ||
|
|
@@ -10,9 +36,12 @@ export function createPlatformWriting(dependencies) {
|
|
|
10
36
|
(action.kind === "reply" && value.replyToId !== action.postId))
|
|
11
37
|
throw new Error("编辑器不为空或回复上下文未绑定");
|
|
12
38
|
if (action.assets?.length)
|
|
13
|
-
await browser.act({
|
|
14
|
-
|
|
15
|
-
|
|
39
|
+
await this.#browser.act({
|
|
40
|
+
operation: "attachAssets",
|
|
41
|
+
assets: action.assets,
|
|
42
|
+
});
|
|
43
|
+
await this.#browser.act({ operation: "fillComposer", text: action.text });
|
|
44
|
+
value = await this.#observed({ view: "composer" });
|
|
16
45
|
if (!sameText(value.editorText, action.text) ||
|
|
17
46
|
value.needsStateCheck ||
|
|
18
47
|
value.alerts?.length ||
|
|
@@ -20,17 +49,17 @@ export function createPlatformWriting(dependencies) {
|
|
|
20
49
|
(action.accountHandle &&
|
|
21
50
|
!sameHandle(value.accountHandle, action.accountHandle)))
|
|
22
51
|
throw new Error(`提交前账号/正文/目标未核实 ${JSON.stringify({ textMatches: sameText(value.editorText, action.text), needsStateCheck: value.needsStateCheck, alerts: value.alerts, accountHandle: value.accountHandle, replyToId: value.replyToId })}`);
|
|
23
|
-
return browser.act({
|
|
52
|
+
return this.#browser.act({
|
|
24
53
|
operation: action.kind === "reply" ? "submitReply" : "submitPost",
|
|
25
54
|
beforeSubmit: action.beforeSubmit,
|
|
26
55
|
...(action.activation ? { activation: action.activation } : {}),
|
|
27
56
|
});
|
|
28
57
|
}
|
|
29
|
-
async
|
|
58
|
+
async handleFindSubmission(action) {
|
|
30
59
|
if (action.kind === "follow") {
|
|
31
|
-
let value = await followStage(action, "verify-read", () => readPerson(action));
|
|
60
|
+
let value = await this.#followStage(action, "verify-read", () => this.#readPerson(action));
|
|
32
61
|
if (!value.needsStateCheck) {
|
|
33
|
-
const absent = browser.capabilities?.("refreshPerson") ?? [
|
|
62
|
+
const absent = this.#browser.capabilities?.("refreshPerson") ?? [
|
|
34
63
|
"refreshPerson",
|
|
35
64
|
];
|
|
36
65
|
if (absent.length)
|
|
@@ -40,13 +69,13 @@ export function createPlatformWriting(dependencies) {
|
|
|
40
69
|
missing: absent,
|
|
41
70
|
needsStateCheck: true,
|
|
42
71
|
};
|
|
43
|
-
await followStage(action, "verify-refresh", () => browser.act({
|
|
72
|
+
await this.#followStage(action, "verify-refresh", () => this.#browser.act({
|
|
44
73
|
operation: "refreshPerson",
|
|
45
74
|
handle: action.handle,
|
|
46
75
|
url: action.url,
|
|
47
76
|
evidenceRef: action.evidenceRef,
|
|
48
77
|
}));
|
|
49
|
-
value = await followStage(action, "verify-after-refresh", () => readPerson(action));
|
|
78
|
+
value = await this.#followStage(action, "verify-after-refresh", () => this.#readPerson(action));
|
|
50
79
|
}
|
|
51
80
|
return {
|
|
52
81
|
...value,
|
|
@@ -79,16 +108,16 @@ export function createPlatformWriting(dependencies) {
|
|
|
79
108
|
: action.kind === "reply" && action.url && action.evidenceRef
|
|
80
109
|
? { url: action.url, evidenceRef: action.evidenceRef }
|
|
81
110
|
: { url: action.accountURL, evidenceRef: action.accountEvidenceRef };
|
|
82
|
-
let value = await open(receiptTarget, { view: "posts", limit: 5 });
|
|
111
|
+
let value = await this.#open(receiptTarget, { view: "posts", limit: 5 });
|
|
83
112
|
if (!(action.kind === "original" && observedReceipt) &&
|
|
84
113
|
!value.needsStateCheck &&
|
|
85
|
-
!(browser.capabilities?.("refreshPosts") ?? ["refreshPosts"]).length) {
|
|
86
|
-
await browser.act({
|
|
114
|
+
!(this.#browser.capabilities?.("refreshPosts") ?? ["refreshPosts"]).length) {
|
|
115
|
+
await this.#browser.act({
|
|
87
116
|
operation: "refreshPosts",
|
|
88
117
|
url: receiptTarget.url,
|
|
89
118
|
evidenceRef: receiptTarget.evidenceRef,
|
|
90
119
|
});
|
|
91
|
-
value = await observed({ view: "posts", limit: 5 });
|
|
120
|
+
value = await this.#observed({ view: "posts", limit: 5 });
|
|
92
121
|
}
|
|
93
122
|
const matchesContent = (post) => sameHandle(post.author, action.accountHandle) &&
|
|
94
123
|
samePublishedText(post.body, action.text) &&
|
|
@@ -98,7 +127,7 @@ export function createPlatformWriting(dependencies) {
|
|
|
98
127
|
!(action.beforeIds ?? []).includes(post.id) &&
|
|
99
128
|
Number.isFinite(Date.parse(post.datetime)) &&
|
|
100
129
|
Date.parse(post.datetime) >= Date.parse(action.intentAt) &&
|
|
101
|
-
Date.parse(post.datetime) <= clock() + 5000 &&
|
|
130
|
+
Date.parse(post.datetime) <= this.#clock() + 5000 &&
|
|
102
131
|
!post.needsExpansion;
|
|
103
132
|
let matches = (value.posts ?? []).filter(matchesContent);
|
|
104
133
|
if (action.kind === "reply" &&
|
|
@@ -110,7 +139,7 @@ export function createPlatformWriting(dependencies) {
|
|
|
110
139
|
const url = xURL(new URL(candidate.primary, xURL(value.page).origin).href);
|
|
111
140
|
if (url.pathname.toLowerCase() ===
|
|
112
141
|
`/${action.accountHandle}/status/${candidate.id}`.toLowerCase()) {
|
|
113
|
-
value = await open({
|
|
142
|
+
value = await this.#open({
|
|
114
143
|
url: url.href,
|
|
115
144
|
evidenceRef: {
|
|
116
145
|
page: value.page,
|
|
@@ -135,37 +164,39 @@ export function createPlatformWriting(dependencies) {
|
|
|
135
164
|
missing: matches.length === 1 ? [] : ["unique-public-submission-unconfirmed"],
|
|
136
165
|
};
|
|
137
166
|
}
|
|
138
|
-
async
|
|
139
|
-
const value = await followStage(action, "submit-preflight-read", () => readPerson(action));
|
|
167
|
+
async handleSubmitFollow(action) {
|
|
168
|
+
const value = await this.#followStage(action, "submit-preflight-read", () => this.#readPerson(action));
|
|
140
169
|
if (value.needsStateCheck ||
|
|
141
170
|
value.alerts?.length ||
|
|
142
171
|
!["not-following", "follows-you"].includes(value.relationship) ||
|
|
143
172
|
(action.accountHandle &&
|
|
144
173
|
!sameHandle(value.accountHandle, action.accountHandle)))
|
|
145
174
|
throw new Error("fresh账号/人物关系不允许关注");
|
|
146
|
-
await followStage(action, "submit-guard", () => action.beforeSubmit?.());
|
|
175
|
+
await this.#followStage(action, "submit-guard", () => action.beforeSubmit?.());
|
|
147
176
|
// A returned click is not a receipt from X; durable success still requires fresh verification.
|
|
148
|
-
const clicked = await followStage(action, "click", () => browser.act({ operation: "follow", handle: action.handle }));
|
|
177
|
+
const clicked = await this.#followStage(action, "click", () => this.#browser.act({ operation: "follow", handle: action.handle }));
|
|
149
178
|
try {
|
|
150
|
-
await followStage(action, "click-ui-observation", () => observed({ view: "person", handle: action.handle }));
|
|
179
|
+
await this.#followStage(action, "click-ui-observation", () => this.#observed({ view: "person", handle: action.handle }));
|
|
151
180
|
}
|
|
152
181
|
catch {
|
|
153
182
|
/* The click may have committed; retain diagnostic failure and use the original fresh receipt path. */
|
|
154
183
|
}
|
|
155
184
|
return clicked;
|
|
156
185
|
}
|
|
157
|
-
async
|
|
158
|
-
await open({ url: action.composeURL, evidenceRef: action.composeEvidenceRef }, { view: "composer" });
|
|
159
|
-
return
|
|
186
|
+
async handleSubmitPost(action) {
|
|
187
|
+
await this.#open({ url: action.composeURL, evidenceRef: action.composeEvidenceRef }, { view: "composer" });
|
|
188
|
+
return this.handleCompose(action);
|
|
160
189
|
}
|
|
161
|
-
async
|
|
162
|
-
const value = await readPost(action);
|
|
190
|
+
async handleSubmitReply(action) {
|
|
191
|
+
const value = await this.#readPost(action);
|
|
163
192
|
if (value.needsStateCheck ||
|
|
164
193
|
value.alerts?.length ||
|
|
165
194
|
!(value.posts ?? []).some((p) => p.id === action.postId))
|
|
166
195
|
throw new Error("回复主帖身份未核实");
|
|
167
|
-
await browser.act({ operation: "openReply", postId: action.postId });
|
|
168
|
-
return
|
|
196
|
+
await this.#browser.act({ operation: "openReply", postId: action.postId });
|
|
197
|
+
return this.handleCompose(action);
|
|
169
198
|
}
|
|
170
|
-
|
|
199
|
+
}
|
|
200
|
+
export function createPlatformWriting(...args) {
|
|
201
|
+
return new PlatformWriting(...args);
|
|
171
202
|
}
|
package/dist/platform.d.ts
CHANGED
|
@@ -1,2 +1,23 @@
|
|
|
1
1
|
export { xURL } from "./platform-rules.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class XPlatform {
|
|
3
|
+
#private;
|
|
4
|
+
preflight: ({ operation, input }: any) => any;
|
|
5
|
+
readCreator: (args: import("./types.js").CreatorOptions) => Promise<any>;
|
|
6
|
+
readAccount: () => Promise<any>;
|
|
7
|
+
readBookmarks: ({ folder, ...window }?: any) => Promise<any>;
|
|
8
|
+
searchSources: ({ query, limit, maxPages, processedIds, }?: any) => Promise<any>;
|
|
9
|
+
readFeedback: ({ targets, replyLimit }?: any) => Promise<any>;
|
|
10
|
+
readPerson: (target: any) => Promise<any>;
|
|
11
|
+
readPost: (target: any) => Promise<any>;
|
|
12
|
+
findSubmission: (action: any) => Promise<any>;
|
|
13
|
+
submitFollow: (action: any) => Promise<any>;
|
|
14
|
+
submitPost: (action: any) => Promise<any>;
|
|
15
|
+
submitReply: (action: any) => Promise<any>;
|
|
16
|
+
captureProof: ({ observation }: any) => {
|
|
17
|
+
followDiagnostics?: any[] | undefined;
|
|
18
|
+
capturedAt: string;
|
|
19
|
+
observation: any;
|
|
20
|
+
};
|
|
21
|
+
constructor(browser: any, { clock }?: any);
|
|
22
|
+
}
|
|
23
|
+
export declare function createXPlatform(...args: ConstructorParameters<typeof XPlatform>): any;
|
package/dist/platform.js
CHANGED
|
@@ -5,56 +5,124 @@ import { createPlatformDiagnostics } from "./platform-diagnostics.js";
|
|
|
5
5
|
import { createPlatformWriting } from "./platform-writing.js";
|
|
6
6
|
import { createPlatformCapabilities } from "./platform-capabilities.js";
|
|
7
7
|
export { xURL } from "./platform-rules.js";
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
8
|
+
export class XPlatform {
|
|
9
|
+
#clock;
|
|
10
|
+
#createPlatformObservationResult;
|
|
11
|
+
#missing;
|
|
12
|
+
#observed;
|
|
13
|
+
#open;
|
|
14
|
+
#ready;
|
|
15
|
+
#choose;
|
|
16
|
+
#createPlatformTargetsResult;
|
|
17
|
+
#readAccount;
|
|
18
|
+
#readFeedback;
|
|
19
|
+
#readPerson;
|
|
20
|
+
#readPost;
|
|
21
|
+
#createPlatformCollectionsResult;
|
|
22
|
+
#collectSources;
|
|
23
|
+
#readCreator;
|
|
24
|
+
#readBookmarks;
|
|
25
|
+
#searchSources;
|
|
26
|
+
#createPlatformDiagnosticsResult;
|
|
27
|
+
#followStage;
|
|
28
|
+
#captureProof;
|
|
29
|
+
#createPlatformWritingResult;
|
|
30
|
+
#compose;
|
|
31
|
+
#findSubmission;
|
|
32
|
+
#submitFollow;
|
|
33
|
+
#submitPost;
|
|
34
|
+
#submitReply;
|
|
35
|
+
#createPlatformCapabilitiesResult;
|
|
36
|
+
#preflight;
|
|
37
|
+
preflight;
|
|
38
|
+
readCreator;
|
|
39
|
+
readAccount;
|
|
40
|
+
readBookmarks;
|
|
41
|
+
searchSources;
|
|
42
|
+
readFeedback;
|
|
43
|
+
readPerson;
|
|
44
|
+
readPost;
|
|
45
|
+
findSubmission;
|
|
46
|
+
submitFollow;
|
|
47
|
+
submitPost;
|
|
48
|
+
submitReply;
|
|
49
|
+
captureProof;
|
|
50
|
+
constructor(browser, { clock = () => Date.now() } = {}) {
|
|
51
|
+
this.#clock = clock;
|
|
52
|
+
this.#createPlatformObservationResult = createPlatformObservation({
|
|
53
|
+
browser: browser,
|
|
54
|
+
clock: this.#clock,
|
|
55
|
+
});
|
|
56
|
+
const { missing, observed, open, ready, choose } = this.#createPlatformObservationResult;
|
|
57
|
+
this.#missing = missing;
|
|
58
|
+
this.#observed = observed;
|
|
59
|
+
this.#open = open;
|
|
60
|
+
this.#ready = ready;
|
|
61
|
+
this.#choose = choose;
|
|
62
|
+
this.#createPlatformTargetsResult = createPlatformTargets({
|
|
63
|
+
observed: this.#observed,
|
|
64
|
+
open: this.#open,
|
|
65
|
+
browser: browser,
|
|
66
|
+
ready: this.#ready,
|
|
67
|
+
});
|
|
68
|
+
const { readAccount, readFeedback, readPerson, readPost } = this.#createPlatformTargetsResult;
|
|
69
|
+
this.#readAccount = readAccount;
|
|
70
|
+
this.#readFeedback = readFeedback;
|
|
71
|
+
this.#readPerson = readPerson;
|
|
72
|
+
this.#readPost = readPost;
|
|
73
|
+
this.#createPlatformCollectionsResult = createPlatformCollections({
|
|
74
|
+
ready: this.#ready,
|
|
75
|
+
observed: this.#observed,
|
|
76
|
+
browser: browser,
|
|
77
|
+
choose: this.#choose,
|
|
78
|
+
});
|
|
79
|
+
const { collectSources, readCreator, readBookmarks, searchSources } = this.#createPlatformCollectionsResult;
|
|
80
|
+
this.#collectSources = collectSources;
|
|
81
|
+
this.#readCreator = readCreator;
|
|
82
|
+
this.#readBookmarks = readBookmarks;
|
|
83
|
+
this.#searchSources = searchSources;
|
|
84
|
+
this.#createPlatformDiagnosticsResult = createPlatformDiagnostics({
|
|
85
|
+
clock: this.#clock,
|
|
86
|
+
});
|
|
87
|
+
const { followStage, captureProof } = this.#createPlatformDiagnosticsResult;
|
|
88
|
+
this.#followStage = followStage;
|
|
89
|
+
this.#captureProof = captureProof;
|
|
90
|
+
this.#createPlatformWritingResult = createPlatformWriting({
|
|
91
|
+
observed: this.#observed,
|
|
92
|
+
browser: browser,
|
|
93
|
+
followStage: this.#followStage,
|
|
94
|
+
readPerson: this.#readPerson,
|
|
95
|
+
open: this.#open,
|
|
96
|
+
clock: this.#clock,
|
|
97
|
+
readPost: this.#readPost,
|
|
98
|
+
});
|
|
99
|
+
const { compose, findSubmission, submitFollow, submitPost, submitReply } = this.#createPlatformWritingResult;
|
|
100
|
+
this.#compose = compose;
|
|
101
|
+
this.#findSubmission = findSubmission;
|
|
102
|
+
this.#submitFollow = submitFollow;
|
|
103
|
+
this.#submitPost = submitPost;
|
|
104
|
+
this.#submitReply = submitReply;
|
|
105
|
+
this.#createPlatformCapabilitiesResult = createPlatformCapabilities({
|
|
106
|
+
missing: this.#missing,
|
|
107
|
+
browser: browser,
|
|
108
|
+
});
|
|
109
|
+
const { preflight } = this.#createPlatformCapabilitiesResult;
|
|
110
|
+
this.#preflight = preflight;
|
|
111
|
+
this.preflight = this.#preflight;
|
|
112
|
+
this.readCreator = this.#readCreator;
|
|
113
|
+
this.readAccount = this.#readAccount;
|
|
114
|
+
this.readBookmarks = this.#readBookmarks;
|
|
115
|
+
this.searchSources = this.#searchSources;
|
|
116
|
+
this.readFeedback = this.#readFeedback;
|
|
117
|
+
this.readPerson = this.#readPerson;
|
|
118
|
+
this.readPost = this.#readPost;
|
|
119
|
+
this.findSubmission = this.#findSubmission;
|
|
120
|
+
this.submitFollow = this.#submitFollow;
|
|
121
|
+
this.submitPost = this.#submitPost;
|
|
122
|
+
this.submitReply = this.#submitReply;
|
|
123
|
+
this.captureProof = this.#captureProof;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export function createXPlatform(...args) {
|
|
127
|
+
return new XPlatform(...args);
|
|
60
128
|
}
|
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
export declare function createAXTabHarness(tab: any): any;
|
|
2
|
-
export declare
|
|
2
|
+
export declare class AXHarness {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(app: any);
|
|
5
|
+
private requireMethods;
|
|
6
|
+
private indexes;
|
|
7
|
+
private handleObserve;
|
|
8
|
+
observe(options?: any): Promise<any>;
|
|
9
|
+
act({ index, operation, value, read, }?: any): Promise<any>;
|
|
10
|
+
screenshot(): Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createAXHarness(...args: ConstructorParameters<typeof AXHarness>): any;
|
package/dist/readers-ax-host.js
CHANGED
|
@@ -11,115 +11,124 @@ export function createAXTabHarness(tab) {
|
|
|
11
11
|
getScreenshot: (options) => tab.getScreenshot(options),
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
export class AXHarness {
|
|
15
|
+
#app;
|
|
16
|
+
#controls;
|
|
17
|
+
#readable;
|
|
18
|
+
#busy;
|
|
19
|
+
constructor(app) {
|
|
20
|
+
this.screenshot = this.screenshot.bind(this);
|
|
21
|
+
this.act = this.act.bind(this);
|
|
22
|
+
this.observe = this.observe.bind(this);
|
|
23
|
+
this.#app = app;
|
|
24
|
+
this.#controls = new Map();
|
|
25
|
+
this.#readable = false;
|
|
26
|
+
this.#busy = false;
|
|
27
|
+
}
|
|
28
|
+
requireMethods(methods) {
|
|
29
|
+
const missing = methods.filter((name) => typeof this.#app?.[name] !== "function");
|
|
18
30
|
if (missing.length) {
|
|
19
|
-
controls.clear();
|
|
20
|
-
readable = false;
|
|
31
|
+
this.#controls.clear();
|
|
32
|
+
this.#readable = false;
|
|
21
33
|
throw new Error(`harness能力不足:${missing.join("/")};先补原入口并验证,未执行操作`);
|
|
22
34
|
}
|
|
23
|
-
}
|
|
24
|
-
|
|
35
|
+
}
|
|
36
|
+
indexes(value) {
|
|
25
37
|
if (!value || typeof value !== "object")
|
|
26
38
|
return;
|
|
27
39
|
if (Number.isInteger(value.index) && typeof value.label === "string")
|
|
28
|
-
controls.set(value.index, value.label);
|
|
40
|
+
this.#controls.set(value.index, value.label);
|
|
29
41
|
for (const child of Object.values(value))
|
|
30
|
-
indexes(child);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
controls.clear();
|
|
34
|
-
readable = false;
|
|
42
|
+
this.indexes(child);
|
|
43
|
+
}
|
|
44
|
+
async handleObserve(options = {}) {
|
|
45
|
+
this.#controls.clear();
|
|
46
|
+
this.#readable = false;
|
|
35
47
|
readAX("", options);
|
|
36
|
-
requireMethods(["getAXState"]);
|
|
37
|
-
const result = readAX(await app.getAXState({ emit: false, disableDiffing: true }), options);
|
|
38
|
-
indexes(result);
|
|
39
|
-
readable = true;
|
|
48
|
+
this.requireMethods(["getAXState"]);
|
|
49
|
+
const result = readAX(await this.#app.getAXState({ emit: false, disableDiffing: true }), options);
|
|
50
|
+
this.indexes(result);
|
|
51
|
+
this.#readable = true;
|
|
40
52
|
return result;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
await app.
|
|
91
|
-
else if (["fill", "paste", "pressKey"].includes(operation)) {
|
|
92
|
-
await app.click(index);
|
|
93
|
-
if (operation === "fill") {
|
|
94
|
-
await app.pressKey("super+a");
|
|
95
|
-
await app.paste(value);
|
|
96
|
-
}
|
|
97
|
-
else
|
|
98
|
-
await app[operation](value);
|
|
53
|
+
}
|
|
54
|
+
async observe(options = {}) {
|
|
55
|
+
if (this.#busy)
|
|
56
|
+
throw new Error("AX操作进行中,禁止并发读取");
|
|
57
|
+
this.#busy = true;
|
|
58
|
+
try {
|
|
59
|
+
return await this.handleObserve(options);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
this.#busy = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async act({ index, operation = "click", value, read = {}, } = {}) {
|
|
66
|
+
if (this.#busy)
|
|
67
|
+
throw new Error("AX操作进行中,禁止并发写入");
|
|
68
|
+
if (!this.#readable || !this.#controls.has(index))
|
|
69
|
+
throw new Error("必须先observe;index须来自最新定向证据");
|
|
70
|
+
if (!["click", "fill", "paste", "pressKey", "scroll", "secondary"].includes(operation))
|
|
71
|
+
throw new Error("未封装的AX操作,先补能力并验证");
|
|
72
|
+
// Validate before any side effect; a malformed follow-up read must never submit.
|
|
73
|
+
readAX("", read);
|
|
74
|
+
if (["fill", "paste", "pressKey", "secondary"].includes(operation) &&
|
|
75
|
+
typeof value !== "string")
|
|
76
|
+
throw new Error("AX操作value须为字符串");
|
|
77
|
+
if (["fill", "paste", "pressKey"].includes(operation) &&
|
|
78
|
+
!/^(?:文本输入区(?:域)?|文本区域|文本栏|组合框) /.test(this.#controls.get(index)))
|
|
79
|
+
throw new Error("键盘操作须定位最新证据中的编辑控件");
|
|
80
|
+
if (operation === "secondary" &&
|
|
81
|
+
!this.#controls.get(index).includes(`Secondary Actions: ${value}`))
|
|
82
|
+
throw new Error("未观察到该辅助动作");
|
|
83
|
+
if (operation === "scroll" &&
|
|
84
|
+
!["up", "down", "left", "right"].includes(value))
|
|
85
|
+
throw new Error("无效滚动方向");
|
|
86
|
+
const methods = operation === "fill"
|
|
87
|
+
? ["click", "pressKey", "paste"]
|
|
88
|
+
: ["paste", "pressKey"].includes(operation)
|
|
89
|
+
? ["click", operation]
|
|
90
|
+
: [operation === "secondary" ? "performSecondaryAction" : operation];
|
|
91
|
+
this.requireMethods([...methods, "getAXState"]);
|
|
92
|
+
this.#busy = true;
|
|
93
|
+
this.#controls.clear();
|
|
94
|
+
this.#readable = false;
|
|
95
|
+
try {
|
|
96
|
+
if (operation === "click")
|
|
97
|
+
await this.#app.click(index);
|
|
98
|
+
else if (["fill", "paste", "pressKey"].includes(operation)) {
|
|
99
|
+
await this.#app.click(index);
|
|
100
|
+
if (operation === "fill") {
|
|
101
|
+
await this.#app.pressKey("super+a");
|
|
102
|
+
await this.#app.paste(value);
|
|
99
103
|
}
|
|
100
|
-
else if (operation === "scroll")
|
|
101
|
-
await app.scroll(index, value, 1);
|
|
102
104
|
else
|
|
103
|
-
await app
|
|
104
|
-
return await observe(read);
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
busy = false;
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
async screenshot() {
|
|
111
|
-
if (busy)
|
|
112
|
-
throw new Error("AX操作进行中,禁止并发截图");
|
|
113
|
-
requireMethods(["getScreenshot"]);
|
|
114
|
-
busy = true;
|
|
115
|
-
controls.clear();
|
|
116
|
-
readable = false;
|
|
117
|
-
try {
|
|
118
|
-
return await app.getScreenshot({ emit: false });
|
|
105
|
+
await this.#app[operation](value);
|
|
119
106
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
107
|
+
else if (operation === "scroll")
|
|
108
|
+
await this.#app.scroll(index, value, 1);
|
|
109
|
+
else
|
|
110
|
+
await this.#app.performSecondaryAction(index, value);
|
|
111
|
+
return await this.handleObserve(read);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
this.#busy = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async screenshot() {
|
|
118
|
+
if (this.#busy)
|
|
119
|
+
throw new Error("AX操作进行中,禁止并发截图");
|
|
120
|
+
this.requireMethods(["getScreenshot"]);
|
|
121
|
+
this.#busy = true;
|
|
122
|
+
this.#controls.clear();
|
|
123
|
+
this.#readable = false;
|
|
124
|
+
try {
|
|
125
|
+
return await this.#app.getScreenshot({ emit: false });
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
this.#busy = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export function createAXHarness(...args) {
|
|
133
|
+
return new AXHarness(...args);
|
|
125
134
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { readAX } from "./readers-ax-parser.js";
|
|
2
|
+
import { parseAXNodes, createAXTree } from "./readers-ax-tree.js";
|
|
3
|
+
import { projectAXFilepicker, projectAXMedia, projectAXNavigation, projectAXPeople, projectAXPosts, projectAXProfile, projectAXComposer, } from "./readers-ax-views.js";
|
|
4
|
+
// Explicit serialization closure; the same parser runs in Node and the REPL.
|
|
5
|
+
export const axParserCode = `(function(){const parseAXNodes=${parseAXNodes.toString()};const createAXTree=${createAXTree.toString()};const projectAXFilepicker=${projectAXFilepicker.toString()};const projectAXMedia=${projectAXMedia.toString()};const projectAXNavigation=${projectAXNavigation.toString()};const projectAXPeople=${projectAXPeople.toString()};const projectAXPosts=${projectAXPosts.toString()};const projectAXProfile=${projectAXProfile.toString()};const projectAXComposer=${projectAXComposer.toString()};return ${readAX.toString()};})()`;
|
package/dist/readers-ax-tree.js
CHANGED
|
@@ -46,6 +46,7 @@ export function createAXTree(nodes) {
|
|
|
46
46
|
/^文本栏 .*地址和搜索栏/.test(n.label) ||
|
|
47
47
|
/^按钮 (?:重新加载|新标签页)$/.test(n.label) ||
|
|
48
48
|
n.label === "窗口" ||
|
|
49
|
+
/, ID: makeKeyAndOrderFront:$/.test(n.label) ||
|
|
49
50
|
/^标签 \((?:selected|settable)/.test(n.label) ||
|
|
50
51
|
/^(?:tab|标签)(?: \([^)]*\))? (?:帖子|回复|转帖|媒体|文章|Posts|Replies|Reposts|Media|Articles)(?:,|$)/.test(n.label) ||
|
|
51
52
|
/^组合框 (?:查询词条|Search)(?:,|$)/.test(n.label) ||
|