@nextclaw/x-harness 0.1.0 → 0.2.0
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 +17 -3
- package/SOURCE.md +9 -0
- package/cli/main.js +232 -188
- package/dist/api.d.ts +56 -2
- package/dist/api.js +178 -75
- 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 +9 -0
- package/dist/browser-dom-capabilities.js +131 -0
- package/dist/browser-dom-dependencies.d.ts +16 -0
- package/dist/browser-dom-dependencies.js +1 -0
- package/dist/browser-dom-editing.d.ts +166 -0
- package/dist/browser-dom-editing.js +418 -0
- package/dist/browser-dom-navigation.d.ts +18 -0
- package/dist/browser-dom-navigation.js +181 -0
- package/dist/browser-dom-network.d.ts +1 -0
- package/dist/browser-dom-network.js +128 -0
- package/dist/browser-dom-submission.d.ts +7 -0
- package/dist/browser-dom-submission.js +101 -0
- package/dist/browser-dom.d.ts +14 -2
- package/dist/browser-dom.js +120 -824
- package/dist/browser-native.d.ts +14 -1
- package/dist/browser-native.js +180 -143
- package/dist/browser.js +6 -2
- package/dist/creator-native.d.ts +3 -0
- package/dist/creator-native.js +113 -0
- package/dist/creator.js +1 -1
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.js +10 -0
- package/dist/growth-batch.d.ts +8 -0
- package/dist/growth-batch.js +190 -0
- package/dist/growth-constants.d.ts +5 -0
- package/dist/growth-constants.js +13 -0
- package/dist/growth-dependencies.d.ts +33 -0
- package/dist/growth-dependencies.js +1 -0
- package/dist/growth-execution.d.ts +17 -0
- package/dist/growth-execution.js +346 -0
- package/dist/growth-parallel.d.ts +8 -0
- package/dist/growth-parallel.js +332 -0
- package/dist/growth-plan.d.ts +9 -0
- package/dist/growth-plan.js +229 -0
- package/dist/growth-policy.d.ts +18 -0
- package/dist/growth-policy.js +208 -0
- package/dist/growth-publishing.d.ts +10 -0
- package/dist/growth-publishing.js +266 -0
- package/dist/growth-reading.d.ts +8 -0
- package/dist/growth-reading.js +157 -0
- package/dist/growth-recovery.d.ts +9 -0
- package/dist/growth-recovery.js +187 -0
- package/dist/growth-state.d.ts +16 -0
- package/dist/growth-state.js +220 -0
- package/dist/growth-tasks.d.ts +20 -0
- package/dist/growth-tasks.js +164 -0
- package/dist/growth.d.ts +26 -1
- package/dist/growth.js +211 -1908
- package/dist/platform-capabilities.d.ts +8 -0
- package/dist/platform-capabilities.js +29 -0
- package/dist/platform-collections.d.ts +14 -0
- package/dist/platform-collections.js +250 -0
- package/dist/platform-dependencies.d.ts +43 -0
- package/dist/platform-dependencies.js +1 -0
- package/dist/platform-diagnostics.d.ts +14 -0
- package/dist/platform-diagnostics.js +63 -0
- package/dist/platform-observation.d.ts +16 -0
- package/dist/platform-observation.js +96 -0
- package/dist/platform-rules.d.ts +17 -0
- package/dist/platform-rules.js +41 -0
- package/dist/platform-targets.d.ts +14 -0
- package/dist/platform-targets.js +154 -0
- package/dist/platform-writing.d.ts +16 -0
- package/dist/platform-writing.js +202 -0
- package/dist/platform.d.ts +23 -2
- package/dist/platform.js +125 -678
- package/dist/readers-actions.d.ts +2 -0
- package/dist/readers-actions.js +66 -0
- package/dist/readers-ax-host.d.ts +12 -0
- package/dist/readers-ax-host.js +134 -0
- package/dist/readers-ax-parser.d.ts +1 -0
- package/dist/readers-ax-parser.js +69 -0
- package/dist/readers-ax-program.d.ts +1 -0
- package/dist/readers-ax-program.js +5 -0
- package/dist/readers-ax-tree.d.ts +19 -0
- package/dist/readers-ax-tree.js +111 -0
- package/dist/readers-ax-views.d.ts +87 -0
- package/dist/readers-ax-views.js +247 -0
- package/dist/readers-cli.d.ts +6 -0
- package/dist/readers-cli.js +79 -0
- package/dist/readers-composer.d.ts +2 -0
- package/dist/readers-composer.js +131 -0
- package/dist/readers-navigation.d.ts +2 -0
- package/dist/readers-navigation.js +66 -0
- package/dist/readers-posts.d.ts +2 -0
- package/dist/readers-posts.js +141 -0
- package/dist/readers-profile.d.ts +3 -0
- package/dist/readers-profile.js +157 -0
- package/dist/readers.d.ts +9 -20
- package/dist/readers.js +9 -1045
- package/dist/results.js +1 -1
- package/dist/round-state.js +20 -3
- package/dist/store.d.ts +22 -4
- package/dist/store.js +173 -151
- package/dist/types.d.ts +10 -0
- package/dist/types.js +1 -0
- package/generated/observation.mjs +3 -2
- package/package.json +23 -9
- package/skill/SKILL.md +17 -36
- package/skill/references/api.md +7 -34
- package/skill/references/reads.md +23 -0
- package/skill/references/recovery.md +25 -0
- package/skill/references/setup.md +23 -0
- package/skill/references/writes.md +51 -0
package/dist/browser-native.d.ts
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import type { BoundaryRecord } from "./boundary.js";
|
|
2
|
+
export declare class NativeXBrowser {
|
|
3
|
+
#private;
|
|
4
|
+
observe: (query: any) => Promise<any>;
|
|
5
|
+
constructor(app: any);
|
|
6
|
+
private navigateNativeTarget;
|
|
7
|
+
private handleObserve;
|
|
8
|
+
private one;
|
|
9
|
+
collectCreator(args: import("./types.js").CreatorOptions): Promise<BoundaryRecord>;
|
|
10
|
+
capabilities(operation: any): any;
|
|
11
|
+
act(command: any): Promise<any>;
|
|
12
|
+
screenshot(): any;
|
|
13
|
+
}
|
|
14
|
+
export declare function createNativeXBrowser(...args: ConstructorParameters<typeof NativeXBrowser>): any;
|
package/dist/browser-native.js
CHANGED
|
@@ -1,52 +1,107 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { collectCreatorNative } from "./creator-native.js";
|
|
2
|
+
import { createAXHarness } from "./readers-ax-host.js";
|
|
3
|
+
import { readAX } from "./readers-ax-parser.js";
|
|
4
|
+
import { xURL } from "./platform-rules.js";
|
|
3
5
|
import { serialized, missingMethods, plain, valueOf, } from "./browser-common.js";
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
export class NativeXBrowser {
|
|
7
|
+
#app;
|
|
8
|
+
#latestAX;
|
|
9
|
+
#delegated;
|
|
10
|
+
#low;
|
|
11
|
+
#current;
|
|
12
|
+
#modes;
|
|
13
|
+
observe;
|
|
14
|
+
constructor(app) {
|
|
15
|
+
this.screenshot = this.screenshot.bind(this);
|
|
16
|
+
this.act = this.act.bind(this);
|
|
17
|
+
this.capabilities = this.capabilities.bind(this);
|
|
18
|
+
this.collectCreator = this.collectCreator.bind(this);
|
|
19
|
+
this.#app = app;
|
|
20
|
+
this.#delegated = {};
|
|
21
|
+
for (const name of [
|
|
22
|
+
"getAXState",
|
|
23
|
+
"click",
|
|
24
|
+
"pressKey",
|
|
25
|
+
"paste",
|
|
26
|
+
"scroll",
|
|
27
|
+
"performSecondaryAction",
|
|
28
|
+
"getScreenshot",
|
|
29
|
+
]) {
|
|
30
|
+
Object.defineProperty(this.#delegated, name, {
|
|
31
|
+
get: () => {
|
|
32
|
+
if (typeof this.#app?.[name] !== "function")
|
|
33
|
+
return undefined;
|
|
34
|
+
return name === "getAXState"
|
|
35
|
+
? async (options) => {
|
|
36
|
+
this.#latestAX = await this.#app.getAXState(options);
|
|
37
|
+
return this.#latestAX;
|
|
38
|
+
}
|
|
39
|
+
: (...args) => this.#app[name](...args);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
this.#low = createAXHarness(this.#delegated);
|
|
44
|
+
this.#modes = {
|
|
45
|
+
account: "navigation",
|
|
46
|
+
person: "profile",
|
|
47
|
+
posts: "posts",
|
|
48
|
+
composer: "composer",
|
|
49
|
+
};
|
|
50
|
+
this.observe = this.handleObserve.bind(this);
|
|
51
|
+
Object.assign(this, serialized({
|
|
52
|
+
collectCreator: (args) => collectCreatorNative(this.#low, args),
|
|
53
|
+
capabilities: this.capabilities,
|
|
54
|
+
observe: this.handleObserve.bind(this),
|
|
55
|
+
act: this.act,
|
|
56
|
+
screenshot: () => {
|
|
57
|
+
this.#current = null;
|
|
58
|
+
return this.#low.screenshot();
|
|
26
59
|
},
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
async navigateNativeTarget(command) {
|
|
63
|
+
xURL(command.url);
|
|
64
|
+
if (!command.evidenceRef)
|
|
65
|
+
throw new Error("导航URL缺原观察证据");
|
|
66
|
+
await this.#low.observe({ mode: "navigation" }).then((raw) => {
|
|
67
|
+
this.#current = { raw };
|
|
68
|
+
});
|
|
69
|
+
let index = this.one((e) => /^文本栏 .*地址和搜索栏/.test(e.label));
|
|
70
|
+
await this.#low
|
|
71
|
+
.act({
|
|
72
|
+
index,
|
|
73
|
+
operation: "fill",
|
|
74
|
+
value: command.url,
|
|
75
|
+
read: { mode: "navigation" },
|
|
76
|
+
})
|
|
77
|
+
.then((raw) => {
|
|
78
|
+
this.#current = { raw };
|
|
79
|
+
});
|
|
80
|
+
index = this.one((e) => /^文本栏 .*地址和搜索栏/.test(e.label));
|
|
81
|
+
await this.#low.act({
|
|
82
|
+
index,
|
|
83
|
+
operation: "pressKey",
|
|
84
|
+
value: "Return",
|
|
85
|
+
read: { mode: "navigation" },
|
|
27
86
|
});
|
|
87
|
+
this.#current = null;
|
|
88
|
+
return;
|
|
28
89
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
account: "navigation",
|
|
33
|
-
person: "profile",
|
|
34
|
-
posts: "posts",
|
|
35
|
-
composer: "composer",
|
|
36
|
-
};
|
|
37
|
-
async function observe(query) {
|
|
38
|
-
current = null;
|
|
39
|
-
if (!modes[query.view])
|
|
90
|
+
async handleObserve(query) {
|
|
91
|
+
this.#current = null;
|
|
92
|
+
if (!this.#modes[query.view])
|
|
40
93
|
throw new Error("未封装的业务视图");
|
|
41
|
-
const raw = await low.observe({
|
|
42
|
-
mode: modes[query.view],
|
|
94
|
+
const raw = await this.#low.observe({
|
|
95
|
+
mode: this.#modes[query.view],
|
|
43
96
|
...(query.targetId ? { targetId: query.targetId } : {}),
|
|
44
97
|
limit: query.limit ?? 1,
|
|
45
98
|
});
|
|
46
|
-
current = { query, raw };
|
|
99
|
+
this.#current = { query, raw };
|
|
47
100
|
const entries = raw.evidence ?? [];
|
|
48
101
|
// Bind actor and target to the SAME suppressed observation; no additional tool round trip.
|
|
49
|
-
const ownLinks = readAX(latestAX, {
|
|
102
|
+
const ownLinks = readAX(this.#latestAX, {
|
|
103
|
+
mode: "navigation",
|
|
104
|
+
}).evidence.filter((e) => /^link 个人资料, Value: x\.com\/\w+$/.test(e.label));
|
|
50
105
|
const base = {
|
|
51
106
|
page: raw.page,
|
|
52
107
|
alerts: raw.alerts,
|
|
@@ -100,112 +155,94 @@ export function createNativeXBrowser(app) {
|
|
|
100
155
|
})),
|
|
101
156
|
};
|
|
102
157
|
}
|
|
103
|
-
|
|
104
|
-
const entries = (current?.raw.evidence ?? [])
|
|
105
|
-
.concat(current?.raw.navigation ?? [])
|
|
158
|
+
one(predicate) {
|
|
159
|
+
const entries = (this.#current?.raw.evidence ?? [])
|
|
160
|
+
.concat(this.#current?.raw.navigation ?? [])
|
|
106
161
|
.filter(predicate);
|
|
107
162
|
if (entries.length !== 1)
|
|
108
163
|
throw new Error("语义控件未唯一观察:先补能力/证据");
|
|
109
164
|
return entries[0].index;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
? "^按钮 (回复|Reply)(?:,|$)"
|
|
194
|
-
: "^按钮 (发帖|发布|Post)(?:,|$)", "i").test(e.label));
|
|
195
|
-
}
|
|
196
|
-
else
|
|
197
|
-
throw new Error("未封装的原生动作");
|
|
198
|
-
const read = {
|
|
199
|
-
mode: command.operation === "follow" ? "profile" : "composer",
|
|
200
|
-
};
|
|
201
|
-
if (["submitPost", "submitReply"].includes(command.operation))
|
|
202
|
-
await command.beforeSubmit?.();
|
|
203
|
-
current = null;
|
|
204
|
-
return low.act({ index, operation, value, read });
|
|
205
|
-
},
|
|
206
|
-
screenshot: () => {
|
|
207
|
-
current = null;
|
|
208
|
-
return low.screenshot();
|
|
209
|
-
},
|
|
210
|
-
});
|
|
165
|
+
}
|
|
166
|
+
collectCreator(args) {
|
|
167
|
+
return collectCreatorNative(this.#low, args);
|
|
168
|
+
}
|
|
169
|
+
capabilities(operation) {
|
|
170
|
+
if (["readBookmarks", "searchSources", "readFeedback"].includes(operation))
|
|
171
|
+
return [`native.${operation}`];
|
|
172
|
+
const names = operation === "read"
|
|
173
|
+
? ["getAXState"]
|
|
174
|
+
: ["getAXState", "click", "pressKey", "paste"];
|
|
175
|
+
return operation === "attachAssets"
|
|
176
|
+
? ["native.attachAssets"]
|
|
177
|
+
: operation === "refreshPosts"
|
|
178
|
+
? ["native.refreshPosts"]
|
|
179
|
+
: missingMethods(this.#app, names);
|
|
180
|
+
}
|
|
181
|
+
async act(command) {
|
|
182
|
+
if (command.operation === "waitForPosts") {
|
|
183
|
+
this.#current = null;
|
|
184
|
+
return this.#low.observe({ mode: "posts", limit: 5 });
|
|
185
|
+
}
|
|
186
|
+
if (command.operation === "openObserved") {
|
|
187
|
+
return this.navigateNativeTarget(command);
|
|
188
|
+
}
|
|
189
|
+
if (command.operation === "refreshPerson") {
|
|
190
|
+
xURL(command.url);
|
|
191
|
+
await this.#low.observe({ mode: "navigation" }).then((raw) => {
|
|
192
|
+
this.#current = { raw };
|
|
193
|
+
});
|
|
194
|
+
const index = this.one((e) => /^按钮 (重新加载|刷新)(?:,|$)/.test(e.label));
|
|
195
|
+
this.#current = null;
|
|
196
|
+
return this.#low.act({
|
|
197
|
+
index,
|
|
198
|
+
operation: "click",
|
|
199
|
+
read: { mode: "profile" },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (!this.#current)
|
|
203
|
+
throw new Error("语义操作必须先observe");
|
|
204
|
+
let index, operation = "click", value;
|
|
205
|
+
if (command.operation === "follow")
|
|
206
|
+
index = this.one((e) => [
|
|
207
|
+
`按钮 关注 @${command.handle}`,
|
|
208
|
+
`按钮 回关 @${command.handle}`,
|
|
209
|
+
].includes(e.label));
|
|
210
|
+
else if (command.operation === "openReply") {
|
|
211
|
+
if (this.#current.query?.targetId !== command.postId)
|
|
212
|
+
throw new Error("回复目标不匹配");
|
|
213
|
+
const target = this.#current.raw.posts?.filter((p) => p.id === command.postId);
|
|
214
|
+
if (target?.length !== 1)
|
|
215
|
+
throw new Error("回复主帖不唯一");
|
|
216
|
+
const buttons = target[0].evidence.filter((e) => /^按钮 .*回复/.test(e.label));
|
|
217
|
+
if (buttons.length !== 1)
|
|
218
|
+
throw new Error("回复按钮不唯一");
|
|
219
|
+
index = buttons[0].index;
|
|
220
|
+
}
|
|
221
|
+
else if (command.operation === "fillComposer") {
|
|
222
|
+
index = this.one((e) => /^(?:文本输入区(?:域)?|文本区域|文本栏) /.test(e.label));
|
|
223
|
+
operation = "fill";
|
|
224
|
+
value = command.text;
|
|
225
|
+
}
|
|
226
|
+
else if (["submitPost", "submitReply"].includes(command.operation)) {
|
|
227
|
+
index = this.one((e) => new RegExp(command.operation === "submitReply"
|
|
228
|
+
? "^按钮 (回复|Reply)(?:,|$)"
|
|
229
|
+
: "^按钮 (发帖|发布|Post)(?:,|$)", "i").test(e.label));
|
|
230
|
+
}
|
|
231
|
+
else
|
|
232
|
+
throw new Error("未封装的原生动作");
|
|
233
|
+
const read = {
|
|
234
|
+
mode: command.operation === "follow" ? "profile" : "composer",
|
|
235
|
+
};
|
|
236
|
+
if (["submitPost", "submitReply"].includes(command.operation))
|
|
237
|
+
await command.beforeSubmit?.();
|
|
238
|
+
this.#current = null;
|
|
239
|
+
return this.#low.act({ index, operation, value, read });
|
|
240
|
+
}
|
|
241
|
+
screenshot() {
|
|
242
|
+
this.#current = null;
|
|
243
|
+
return this.#low.screenshot();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
export function createNativeXBrowser(...args) {
|
|
247
|
+
return new NativeXBrowser(...args);
|
|
211
248
|
}
|
package/dist/browser.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { readProfile
|
|
1
|
+
import { readProfile } from "./readers-profile.js";
|
|
2
|
+
import { readPosts } from "./readers-posts.js";
|
|
3
|
+
import { readComposer, readMediaEditor } from "./readers-composer.js";
|
|
4
|
+
import { readNavigation, readMenu } from "./readers-navigation.js";
|
|
2
5
|
export { createDOMXBrowser, readXNetworkDiagnostics } from "./browser-dom.js";
|
|
3
6
|
export { createNativeXBrowser } from "./browser-native.js";
|
|
4
7
|
// Read-only supplement: account identity, composer context and exact tweet bodies.
|
|
@@ -106,7 +109,8 @@ export function readXSurface({ view, posts = [], targetId, replyTargetId, } = {}
|
|
|
106
109
|
const read = (node) => {
|
|
107
110
|
if (node.nodeType === 3)
|
|
108
111
|
return node.textContent ?? "";
|
|
109
|
-
if (node.nodeType === 1 &&
|
|
112
|
+
if (node.nodeType === 1 &&
|
|
113
|
+
node.tagName === "A") {
|
|
110
114
|
const href = node.getAttribute("href") ?? "";
|
|
111
115
|
if (/^\/\w{1,15}\/status\/\d+$/.test(href))
|
|
112
116
|
return `https://x.com${href}`;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Bounded collection on an already selected profile. Relative AX times stay unknown.
|
|
2
|
+
export async function collectCreatorNative(low, { handle, target = 10, maxScrolls = 18, navigate = true }) {
|
|
3
|
+
if (!/^[A-Za-z0-9_]{1,15}$/.test(handle) ||
|
|
4
|
+
!Number.isInteger(target) ||
|
|
5
|
+
target < 1 ||
|
|
6
|
+
target > 50 ||
|
|
7
|
+
!Number.isInteger(maxScrolls) ||
|
|
8
|
+
maxScrolls < 0 ||
|
|
9
|
+
maxScrolls > 50)
|
|
10
|
+
throw new Error("Invalid creator collection input");
|
|
11
|
+
if (navigate)
|
|
12
|
+
return {
|
|
13
|
+
status: "needsCapability",
|
|
14
|
+
complete: false,
|
|
15
|
+
posts: [],
|
|
16
|
+
missing: ["native.creator-navigation"],
|
|
17
|
+
};
|
|
18
|
+
const profile = await low.observe({ mode: "profile" });
|
|
19
|
+
const expected = new RegExp(`^(?:https?://)?x\\.com/${handle}/?$`, "i");
|
|
20
|
+
if (!expected.test(profile.page ?? "") || profile.needsStateCheck)
|
|
21
|
+
throw new Error("creator-url-mismatch");
|
|
22
|
+
const posts = new Map();
|
|
23
|
+
const missing = new Set();
|
|
24
|
+
const labels = (profile.evidence ?? [])
|
|
25
|
+
.filter((e) => /^(?:文本|text|link) /.test(e.label))
|
|
26
|
+
.map((e) => e.label.replace(/^(?:文本|text|link) /, ""));
|
|
27
|
+
const meta = {
|
|
28
|
+
headerText: labels.join("\n"),
|
|
29
|
+
totalPostsText: labels.find((label) => /^[\d,.万亿KMB]+ 帖子$/.test(label)) ??
|
|
30
|
+
null,
|
|
31
|
+
missing: [],
|
|
32
|
+
};
|
|
33
|
+
let scrolls = 0, stagnant = 0, last;
|
|
34
|
+
const result = () => {
|
|
35
|
+
const selected = [...posts.values()].slice(0, target);
|
|
36
|
+
if (selected.length < target)
|
|
37
|
+
missing.add("creator-target-not-reached");
|
|
38
|
+
for (const post of selected) {
|
|
39
|
+
if (!post.datetime)
|
|
40
|
+
missing.add("absolute-time-missing");
|
|
41
|
+
if (post.needsExpansion)
|
|
42
|
+
missing.add("post-needs-expansion");
|
|
43
|
+
if (!post.body)
|
|
44
|
+
missing.add("post-body-missing");
|
|
45
|
+
}
|
|
46
|
+
if (profile.alerts?.length)
|
|
47
|
+
missing.add("platform-alert");
|
|
48
|
+
return {
|
|
49
|
+
profile: { ...profile, handle },
|
|
50
|
+
meta,
|
|
51
|
+
posts: selected,
|
|
52
|
+
complete: missing.size === 0,
|
|
53
|
+
missing: [...missing],
|
|
54
|
+
scrolls,
|
|
55
|
+
observedAt: new Date().toISOString(),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
for (;;) {
|
|
59
|
+
const before = posts.size;
|
|
60
|
+
for (let offset = 0; offset < 250; offset += 5) {
|
|
61
|
+
last = await low.observe({ mode: "posts", offset, limit: 5 });
|
|
62
|
+
if (!expected.test(last.page ?? "") ||
|
|
63
|
+
last.needsStateCheck ||
|
|
64
|
+
last.alerts?.length) {
|
|
65
|
+
missing.add("creator-window-unconfirmed");
|
|
66
|
+
return { ...result(), alerts: last.alerts ?? [] };
|
|
67
|
+
}
|
|
68
|
+
for (const post of last.posts ?? []) {
|
|
69
|
+
if (post.id && post.author?.toLowerCase() === handle.toLowerCase())
|
|
70
|
+
posts.set(post.id, {
|
|
71
|
+
...post,
|
|
72
|
+
body: post.text,
|
|
73
|
+
primary: `https://x.com/${post.author}/status/${post.id}`,
|
|
74
|
+
links: (post.evidence ?? []).flatMap((e) => {
|
|
75
|
+
const href = e.label.match(/Value: ((?:https?:\/\/)?(?:[\w.-]+\.[a-z]+)\/[^\s,]*)/i)?.[1];
|
|
76
|
+
return href
|
|
77
|
+
? [href.startsWith("http") ? href : `https://${href}`]
|
|
78
|
+
: [];
|
|
79
|
+
}),
|
|
80
|
+
metricDescriptions: (post.evidence ?? []).map((e) => e.label),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (!last.moreInDOM || posts.size >= target)
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
stagnant = posts.size === before ? stagnant + 1 : 0;
|
|
87
|
+
if (posts.size >= target || scrolls >= maxScrolls || stagnant >= 3)
|
|
88
|
+
break;
|
|
89
|
+
// Restore a fresh visible window so the scroll index is never from an old read.
|
|
90
|
+
last = await low.observe({ mode: "posts", limit: 5 });
|
|
91
|
+
if (!expected.test(last.page ?? "") ||
|
|
92
|
+
last.needsStateCheck ||
|
|
93
|
+
last.alerts?.length) {
|
|
94
|
+
missing.add("creator-window-unconfirmed");
|
|
95
|
+
return result();
|
|
96
|
+
}
|
|
97
|
+
const control = last.posts
|
|
98
|
+
?.flatMap((post) => post.evidence ?? [])
|
|
99
|
+
.find((entry) => /^link .*Value: x\.com\/\w+\/status\/\d+(?:,|$)/.test(entry.label));
|
|
100
|
+
if (!control) {
|
|
101
|
+
missing.add("native.creator-scroll-target");
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
await low.act({
|
|
105
|
+
index: control.index,
|
|
106
|
+
operation: "scroll",
|
|
107
|
+
value: "down",
|
|
108
|
+
read: { mode: "posts", limit: 5 },
|
|
109
|
+
});
|
|
110
|
+
scrolls++;
|
|
111
|
+
}
|
|
112
|
+
return result();
|
|
113
|
+
}
|
package/dist/creator.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Explicit lower-level diagnostics and legacy consumers. Normal tasks use the root. */
|
|
2
|
+
export { createGrowthHarness } from "./growth.js";
|
|
3
|
+
export { createBusinessHarness } from "./api.js";
|
|
4
|
+
export { createFileRoundStore, HarnessConflict } from "./store.js";
|
|
5
|
+
export { createXPlatform, xURL } from "./platform.js";
|
|
6
|
+
export { createDOMXBrowser, createNativeXBrowser, readXSurface, atomicObservationCode, readXNetworkDiagnostics, } from "./browser.js";
|
|
7
|
+
export { readXObservation } from "./observation.js";
|
|
8
|
+
export * from "./readers.js";
|
|
9
|
+
export { checkRound, readRound, runRoundCLI } from "./round-state.js";
|
|
10
|
+
export { readCreatorMeta, readCreatorPostExtras } from "./creator.js";
|
|
11
|
+
export type { BoundaryRecord } from "./boundary.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Explicit lower-level diagnostics and legacy consumers. Normal tasks use the root. */
|
|
2
|
+
export { createGrowthHarness } from "./growth.js";
|
|
3
|
+
export { createBusinessHarness } from "./api.js";
|
|
4
|
+
export { createFileRoundStore, HarnessConflict } from "./store.js";
|
|
5
|
+
export { createXPlatform, xURL } from "./platform.js";
|
|
6
|
+
export { createDOMXBrowser, createNativeXBrowser, readXSurface, atomicObservationCode, readXNetworkDiagnostics, } from "./browser.js";
|
|
7
|
+
export { readXObservation } from "./observation.js";
|
|
8
|
+
export * from "./readers.js";
|
|
9
|
+
export { checkRound, readRound, runRoundCLI } from "./round-state.js";
|
|
10
|
+
export { readCreatorMeta, readCreatorPostExtras } from "./creator.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BoundaryRecord } from "./boundary.js";
|
|
2
|
+
import type { GrowthDependencies } from "./growth-dependencies.js";
|
|
3
|
+
export declare class FollowBatch {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(dependencies: Pick<GrowthDependencies, "prepareActionsForCall" | "store" | "scope" | "stamp" | "clock" | "evidenceExists" | "executor" | "parallelRound" | "round">);
|
|
6
|
+
followBatch({ taskId, handles, preparedActions }?: any): Promise<BoundaryRecord>;
|
|
7
|
+
}
|
|
8
|
+
export declare function createFollowBatch(...args: ConstructorParameters<typeof FollowBatch>): FollowBatch;
|