@nextclaw/x-harness 0.1.1 → 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 +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/setup.md +1 -1
package/dist/browser-native.js
CHANGED
|
@@ -1,17 +1,73 @@
|
|
|
1
|
+
import { collectCreatorNative } from "./creator-native.js";
|
|
1
2
|
import { createAXHarness } from "./readers-ax-host.js";
|
|
2
3
|
import { readAX } from "./readers-ax-parser.js";
|
|
3
4
|
import { xURL } from "./platform-rules.js";
|
|
4
5
|
import { serialized, missingMethods, plain, valueOf, } from "./browser-common.js";
|
|
5
|
-
export
|
|
6
|
-
|
|
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();
|
|
59
|
+
},
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
async navigateNativeTarget(command) {
|
|
7
63
|
xURL(command.url);
|
|
8
64
|
if (!command.evidenceRef)
|
|
9
65
|
throw new Error("导航URL缺原观察证据");
|
|
10
|
-
await low.observe({ mode: "navigation" }).then((raw) => {
|
|
11
|
-
current = { raw };
|
|
66
|
+
await this.#low.observe({ mode: "navigation" }).then((raw) => {
|
|
67
|
+
this.#current = { raw };
|
|
12
68
|
});
|
|
13
|
-
let index = one((e) => /^文本栏 .*地址和搜索栏/.test(e.label));
|
|
14
|
-
await low
|
|
69
|
+
let index = this.one((e) => /^文本栏 .*地址和搜索栏/.test(e.label));
|
|
70
|
+
await this.#low
|
|
15
71
|
.act({
|
|
16
72
|
index,
|
|
17
73
|
operation: "fill",
|
|
@@ -19,63 +75,33 @@ export function createNativeXBrowser(app) {
|
|
|
19
75
|
read: { mode: "navigation" },
|
|
20
76
|
})
|
|
21
77
|
.then((raw) => {
|
|
22
|
-
current = { raw };
|
|
78
|
+
this.#current = { raw };
|
|
23
79
|
});
|
|
24
|
-
index = one((e) => /^文本栏 .*地址和搜索栏/.test(e.label));
|
|
25
|
-
await low.act({
|
|
80
|
+
index = this.one((e) => /^文本栏 .*地址和搜索栏/.test(e.label));
|
|
81
|
+
await this.#low.act({
|
|
26
82
|
index,
|
|
27
83
|
operation: "pressKey",
|
|
28
84
|
value: "Return",
|
|
29
85
|
read: { mode: "navigation" },
|
|
30
86
|
});
|
|
31
|
-
current = null;
|
|
87
|
+
this.#current = null;
|
|
32
88
|
return;
|
|
33
89
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"getAXState",
|
|
38
|
-
"click",
|
|
39
|
-
"pressKey",
|
|
40
|
-
"paste",
|
|
41
|
-
"scroll",
|
|
42
|
-
"performSecondaryAction",
|
|
43
|
-
"getScreenshot",
|
|
44
|
-
]) {
|
|
45
|
-
Object.defineProperty(delegated, name, {
|
|
46
|
-
get() {
|
|
47
|
-
if (typeof app?.[name] !== "function")
|
|
48
|
-
return undefined;
|
|
49
|
-
return name === "getAXState"
|
|
50
|
-
? async (options) => {
|
|
51
|
-
latestAX = await app.getAXState(options);
|
|
52
|
-
return latestAX;
|
|
53
|
-
}
|
|
54
|
-
: (...args) => app[name](...args);
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
const low = createAXHarness(delegated);
|
|
59
|
-
let current;
|
|
60
|
-
const modes = {
|
|
61
|
-
account: "navigation",
|
|
62
|
-
person: "profile",
|
|
63
|
-
posts: "posts",
|
|
64
|
-
composer: "composer",
|
|
65
|
-
};
|
|
66
|
-
async function observe(query) {
|
|
67
|
-
current = null;
|
|
68
|
-
if (!modes[query.view])
|
|
90
|
+
async handleObserve(query) {
|
|
91
|
+
this.#current = null;
|
|
92
|
+
if (!this.#modes[query.view])
|
|
69
93
|
throw new Error("未封装的业务视图");
|
|
70
|
-
const raw = await low.observe({
|
|
71
|
-
mode: modes[query.view],
|
|
94
|
+
const raw = await this.#low.observe({
|
|
95
|
+
mode: this.#modes[query.view],
|
|
72
96
|
...(query.targetId ? { targetId: query.targetId } : {}),
|
|
73
97
|
limit: query.limit ?? 1,
|
|
74
98
|
});
|
|
75
|
-
current = { query, raw };
|
|
99
|
+
this.#current = { query, raw };
|
|
76
100
|
const entries = raw.evidence ?? [];
|
|
77
101
|
// Bind actor and target to the SAME suppressed observation; no additional tool round trip.
|
|
78
|
-
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));
|
|
79
105
|
const base = {
|
|
80
106
|
page: raw.page,
|
|
81
107
|
alerts: raw.alerts,
|
|
@@ -129,87 +155,94 @@ export function createNativeXBrowser(app) {
|
|
|
129
155
|
})),
|
|
130
156
|
};
|
|
131
157
|
}
|
|
132
|
-
|
|
133
|
-
const entries = (current?.raw.evidence ?? [])
|
|
134
|
-
.concat(current?.raw.navigation ?? [])
|
|
158
|
+
one(predicate) {
|
|
159
|
+
const entries = (this.#current?.raw.evidence ?? [])
|
|
160
|
+
.concat(this.#current?.raw.navigation ?? [])
|
|
135
161
|
.filter(predicate);
|
|
136
162
|
if (entries.length !== 1)
|
|
137
163
|
throw new Error("语义控件未唯一观察:先补能力/证据");
|
|
138
164
|
return entries[0].index;
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
}
|
|
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);
|
|
215
248
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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";
|
package/dist/growth-batch.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { BoundaryRecord } from "./boundary.js";
|
|
2
2
|
import type { GrowthDependencies } from "./growth-dependencies.js";
|
|
3
|
-
export declare
|
|
4
|
-
|
|
5
|
-
|
|
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;
|