@nextclaw/x-harness 0.2.66 → 0.2.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SOURCE.md +2 -2
- package/dist/api.d.ts +1 -0
- package/dist/api.js +5 -0
- package/dist/browser-dom-capabilities.js +6 -1
- package/dist/browser-dom-navigation.d.ts +2 -0
- package/dist/browser-dom-navigation.js +7 -0
- package/dist/browser-native.js +7 -1
- package/dist/growth-execution.d.ts +1 -0
- package/dist/growth-execution.js +19 -2
- package/dist/growth-tasks.d.ts +1 -0
- package/dist/growth-tasks.js +12 -1
- package/dist/growth.d.ts +1 -0
- package/dist/growth.js +1 -0
- package/dist/platform-collections.d.ts +2 -0
- package/dist/platform-collections.js +142 -0
- package/dist/platform-dependencies.d.ts +2 -0
- package/dist/platform-rules.js +7 -1
- package/dist/platform.d.ts +2 -0
- package/dist/platform.js +5 -1
- package/dist/results.js +33 -0
- package/dist/types.d.ts +9 -0
- package/package.json +2 -2
- package/skill/references/reads.md +8 -7
- package/skill/references/receipt-recovery.md +7 -0
- package/skill/references/recovery.md +1 -1
package/SOURCE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Corresponding source
|
|
2
2
|
|
|
3
|
-
Version 0.2.
|
|
3
|
+
Version 0.2.67, commit 03a90495bd2c781577399aca850742082abe3556.
|
|
4
4
|
|
|
5
|
-
Complete source, build scripts and dependencies: https://github.com/Peiiii/x-harness/tree/
|
|
5
|
+
Complete source, build scripts and dependencies: https://github.com/Peiiii/x-harness/tree/03a90495bd2c781577399aca850742082abe3556
|
|
6
6
|
|
|
7
7
|
Clone this revision, run npm ci, npm run bootstrap and npm run release:build.
|
|
8
8
|
|
package/dist/api.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ export declare class BusinessHarness implements XGrowthHarness {
|
|
|
82
82
|
diagnostic?: undefined;
|
|
83
83
|
}>;
|
|
84
84
|
searchSources(args: import("./types.js").SearchOptions): Promise<ReadResult>;
|
|
85
|
+
readExploreRecommendations(args?: import("./types.js").ExploreRecommendationOptions): Promise<ReadResult>;
|
|
85
86
|
readPost({ target, mode, includeContext, includeThread, resolveQuote, }: {
|
|
86
87
|
includeContext?: boolean;
|
|
87
88
|
includeThread?: boolean;
|
package/dist/api.js
CHANGED
|
@@ -33,6 +33,8 @@ export class BusinessHarness {
|
|
|
33
33
|
this.readIncomingConversations = this.readIncomingConversations.bind(this);
|
|
34
34
|
this.readIncomingReplies = this.readIncomingReplies.bind(this);
|
|
35
35
|
this.searchSources = this.searchSources.bind(this);
|
|
36
|
+
this.readExploreRecommendations =
|
|
37
|
+
this.readExploreRecommendations.bind(this);
|
|
36
38
|
this.readBookmarks = this.readBookmarks.bind(this);
|
|
37
39
|
this.readCreator = this.readCreator.bind(this);
|
|
38
40
|
const { store, platform } = dependencies;
|
|
@@ -599,6 +601,9 @@ export class BusinessHarness {
|
|
|
599
601
|
searchSources(args) {
|
|
600
602
|
return this.read("searchSources", args, args.mode);
|
|
601
603
|
}
|
|
604
|
+
readExploreRecommendations(args = {}) {
|
|
605
|
+
return this.read("readExploreRecommendations", args, args.mode);
|
|
606
|
+
}
|
|
602
607
|
readPost({ target, mode, includeContext, includeThread, resolveQuote, }) {
|
|
603
608
|
return this.read("readPost", { ...target, includeContext, includeThread, resolveQuote }, mode);
|
|
604
609
|
}
|
|
@@ -45,7 +45,12 @@ export class DOMCapabilities {
|
|
|
45
45
|
if (operation === "readCreator") {
|
|
46
46
|
return this.creatorCapabilities(base, operation);
|
|
47
47
|
}
|
|
48
|
-
if ([
|
|
48
|
+
if ([
|
|
49
|
+
"readBookmarks",
|
|
50
|
+
"searchSources",
|
|
51
|
+
"readExploreRecommendations",
|
|
52
|
+
"readFeedback",
|
|
53
|
+
].includes(operation)) {
|
|
49
54
|
return this.collectionCapabilities(base, operation);
|
|
50
55
|
}
|
|
51
56
|
if (operation !== "read")
|
|
@@ -3,6 +3,7 @@ export declare class DOMNavigation {
|
|
|
3
3
|
#private;
|
|
4
4
|
openQuote: (command: any) => Promise<any>;
|
|
5
5
|
searchSources: (command: any) => Promise<void>;
|
|
6
|
+
openExploreRecommendations: (command: any) => Promise<void>;
|
|
6
7
|
waitForFolder: (command: any) => Promise<void>;
|
|
7
8
|
waitForPosts: (command: any) => Promise<void>;
|
|
8
9
|
openObserved: (command: any) => Promise<void>;
|
|
@@ -11,6 +12,7 @@ export declare class DOMNavigation {
|
|
|
11
12
|
constructor(dependencies: Pick<DOMDependencies, "session" | "tab" | "observe">);
|
|
12
13
|
private handleOpenQuote;
|
|
13
14
|
private handleSearchSources;
|
|
15
|
+
private handleOpenExploreRecommendations;
|
|
14
16
|
private handleWaitForFolder;
|
|
15
17
|
private handleWaitForPosts;
|
|
16
18
|
private handleOpenObserved;
|
|
@@ -5,6 +5,7 @@ export class DOMNavigation {
|
|
|
5
5
|
#observe;
|
|
6
6
|
openQuote;
|
|
7
7
|
searchSources;
|
|
8
|
+
openExploreRecommendations;
|
|
8
9
|
waitForFolder;
|
|
9
10
|
waitForPosts;
|
|
10
11
|
openObserved;
|
|
@@ -17,6 +18,8 @@ export class DOMNavigation {
|
|
|
17
18
|
this.#observe = observe;
|
|
18
19
|
this.openQuote = this.handleOpenQuote.bind(this);
|
|
19
20
|
this.searchSources = this.handleSearchSources.bind(this);
|
|
21
|
+
this.openExploreRecommendations =
|
|
22
|
+
this.handleOpenExploreRecommendations.bind(this);
|
|
20
23
|
this.waitForFolder = this.handleWaitForFolder.bind(this);
|
|
21
24
|
this.waitForPosts = this.handleWaitForPosts.bind(this);
|
|
22
25
|
this.openObserved = this.handleOpenObserved.bind(this);
|
|
@@ -70,6 +73,10 @@ export class DOMNavigation {
|
|
|
70
73
|
await this.#tab.goto(destination);
|
|
71
74
|
return;
|
|
72
75
|
}
|
|
76
|
+
async handleOpenExploreRecommendations() {
|
|
77
|
+
this.#session.current = null;
|
|
78
|
+
await this.#tab.goto("https://x.com/explore");
|
|
79
|
+
}
|
|
73
80
|
async handleWaitForFolder(command) {
|
|
74
81
|
if (command.selected) {
|
|
75
82
|
await this.#tab.playwright
|
package/dist/browser-native.js
CHANGED
|
@@ -170,7 +170,13 @@ export class NativeXBrowser {
|
|
|
170
170
|
capabilities(operation) {
|
|
171
171
|
if (operation === "readPeople")
|
|
172
172
|
return ["native-people-blue-evidence"];
|
|
173
|
-
if ([
|
|
173
|
+
if ([
|
|
174
|
+
"readBookmarks",
|
|
175
|
+
"searchSources",
|
|
176
|
+
"readExploreRecommendations",
|
|
177
|
+
"readFeedback",
|
|
178
|
+
"likePost",
|
|
179
|
+
].includes(operation))
|
|
174
180
|
return [`native.${operation}`];
|
|
175
181
|
const names = operation === "read"
|
|
176
182
|
? ["getAXState"]
|
|
@@ -12,6 +12,7 @@ export declare class GrowthExecution {
|
|
|
12
12
|
private checkExecutionIdentity;
|
|
13
13
|
private actionIsActionable;
|
|
14
14
|
private admitAction;
|
|
15
|
+
private readSubmissionWithURLContinuation;
|
|
15
16
|
private handleExecute;
|
|
16
17
|
}
|
|
17
18
|
export declare function createGrowthExecution(...args: ConstructorParameters<typeof GrowthExecution>): GrowthExecution;
|
package/dist/growth-execution.js
CHANGED
|
@@ -176,7 +176,7 @@ export class GrowthExecution {
|
|
|
176
176
|
if (action.kind !== "follow") {
|
|
177
177
|
let baseline;
|
|
178
178
|
try {
|
|
179
|
-
baseline = await this
|
|
179
|
+
baseline = await this.readSubmissionWithURLContinuation({ ...action, intentAt: this.#stamp() }, { phase: "baseline" });
|
|
180
180
|
}
|
|
181
181
|
catch (error) {
|
|
182
182
|
return {
|
|
@@ -202,6 +202,23 @@ export class GrowthExecution {
|
|
|
202
202
|
}
|
|
203
203
|
return { beforeIds };
|
|
204
204
|
}
|
|
205
|
+
async readSubmissionWithURLContinuation(action, context) {
|
|
206
|
+
try {
|
|
207
|
+
return await this.#platform.findSubmission(action, context);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const preparedBaseline = context?.phase === "baseline";
|
|
211
|
+
const observedReceipt = !context &&
|
|
212
|
+
action.observedPublicURL &&
|
|
213
|
+
this.#evidenceExists(action.observedPublicEvidenceRef);
|
|
214
|
+
if (action.kind !== "reply" ||
|
|
215
|
+
!(preparedBaseline || observedReceipt) ||
|
|
216
|
+
!/Timed out waiting for URL/.test(String(error?.message ?? "")))
|
|
217
|
+
throw error;
|
|
218
|
+
// Continue only this read; the existing admission and receipt gates stay intact.
|
|
219
|
+
return this.#platform.findSubmission(action, context);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
205
222
|
async handleExecute(snapshot, action) {
|
|
206
223
|
const executor = this.#executor;
|
|
207
224
|
const recovering = ["intent", "unknown"].includes(action.status);
|
|
@@ -215,7 +232,7 @@ export class GrowthExecution {
|
|
|
215
232
|
if (recovering) {
|
|
216
233
|
let observation;
|
|
217
234
|
try {
|
|
218
|
-
observation = await this
|
|
235
|
+
observation = await this.readSubmissionWithURLContinuation(action);
|
|
219
236
|
}
|
|
220
237
|
catch (error) {
|
|
221
238
|
observation = {
|
package/dist/growth-tasks.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class GrowthTasks {
|
|
|
10
10
|
readPerson(args: any): Promise<any>;
|
|
11
11
|
readBookmarks(args: any): Promise<any>;
|
|
12
12
|
searchSources(args: any): Promise<any>;
|
|
13
|
+
readExploreRecommendations(args?: any): Promise<any>;
|
|
13
14
|
readFeedback(args: any): Promise<any>;
|
|
14
15
|
runGrowthRound(args: any): Promise<any>;
|
|
15
16
|
resumeGrowthRound(args: any): Promise<any>;
|
package/dist/growth-tasks.js
CHANGED
|
@@ -19,6 +19,8 @@ export class GrowthTasks {
|
|
|
19
19
|
this.runGrowthRound = this.runGrowthRound.bind(this);
|
|
20
20
|
this.readFeedback = this.readFeedback.bind(this);
|
|
21
21
|
this.searchSources = this.searchSources.bind(this);
|
|
22
|
+
this.readExploreRecommendations =
|
|
23
|
+
this.readExploreRecommendations.bind(this);
|
|
22
24
|
this.readBookmarks = this.readBookmarks.bind(this);
|
|
23
25
|
this.readPerson = this.readPerson.bind(this);
|
|
24
26
|
this.readPeople = this.readPeople.bind(this);
|
|
@@ -40,7 +42,13 @@ export class GrowthTasks {
|
|
|
40
42
|
}
|
|
41
43
|
preflight({ operation, input = {} }) {
|
|
42
44
|
const executor = this.#executor;
|
|
43
|
-
if ([
|
|
45
|
+
if ([
|
|
46
|
+
"readPeople",
|
|
47
|
+
"readBookmarks",
|
|
48
|
+
"searchSources",
|
|
49
|
+
"readExploreRecommendations",
|
|
50
|
+
"readFeedback",
|
|
51
|
+
].includes(operation))
|
|
44
52
|
return this.#platform.preflight({ operation, input });
|
|
45
53
|
if (["inspectRound", "verifySubmission"].includes(operation))
|
|
46
54
|
return this.#capability("read", input);
|
|
@@ -110,6 +118,9 @@ export class GrowthTasks {
|
|
|
110
118
|
searchSources(args) {
|
|
111
119
|
return this.#readTask("searchSources", args);
|
|
112
120
|
}
|
|
121
|
+
readExploreRecommendations(args = {}) {
|
|
122
|
+
return this.#readTask("readExploreRecommendations", args);
|
|
123
|
+
}
|
|
113
124
|
readFeedback(args) {
|
|
114
125
|
return this.#readTask("readFeedback", args);
|
|
115
126
|
}
|
package/dist/growth.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare class GrowthHarness {
|
|
|
13
13
|
readPerson: (args: any) => Promise<any>;
|
|
14
14
|
readBookmarks: (args: any) => Promise<any>;
|
|
15
15
|
searchSources: (args: any) => Promise<any>;
|
|
16
|
+
readExploreRecommendations: (args?: any) => Promise<any>;
|
|
16
17
|
readFeedback: (args: any) => Promise<any>;
|
|
17
18
|
runGrowthRound: (args: any) => Promise<any>;
|
|
18
19
|
resumeGrowthRound: (args: any) => Promise<any>;
|
package/dist/growth.js
CHANGED
|
@@ -4,6 +4,7 @@ export declare class PlatformCollections {
|
|
|
4
4
|
collectSources: ({ processedIds, maxPages, limit }?: any) => Promise<any>;
|
|
5
5
|
readCreator: (args: import("./types.js").CreatorOptions) => Promise<any>;
|
|
6
6
|
readBookmarks: ({ folder, ...window }?: any) => Promise<any>;
|
|
7
|
+
readExploreRecommendations: (args?: import("./types.js").ExploreRecommendationOptions) => Promise<any>;
|
|
7
8
|
searchSources: ({ query, limit, maxPages, processedIds, }?: any) => Promise<any>;
|
|
8
9
|
constructor(dependencies: Pick<PlatformDependencies, "ready" | "observed" | "browser" | "choose">);
|
|
9
10
|
private handleCollectSources;
|
|
@@ -15,5 +16,6 @@ export declare class PlatformCollections {
|
|
|
15
16
|
private bookmarkDiscovery;
|
|
16
17
|
private waitForBookmarkLabel;
|
|
17
18
|
private handleSearchSources;
|
|
19
|
+
private handleReadExploreRecommendations;
|
|
18
20
|
}
|
|
19
21
|
export declare function createPlatformCollections(...args: ConstructorParameters<typeof PlatformCollections>): PlatformCollections;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { xURL, source } from "./platform-rules.js";
|
|
2
|
+
const exploreNavigationEvidence = (nav) => ({
|
|
3
|
+
page: nav.page,
|
|
4
|
+
headings: (nav.headings ?? []).slice(0, 5),
|
|
5
|
+
tabs: (nav.controls ?? [])
|
|
6
|
+
.filter((control) => control.role === "tab")
|
|
7
|
+
.slice(0, 8)
|
|
8
|
+
.map((control) => ({
|
|
9
|
+
label: control.label,
|
|
10
|
+
selected: control.selected === true,
|
|
11
|
+
})),
|
|
12
|
+
});
|
|
2
13
|
export class PlatformCollections {
|
|
3
14
|
#ready;
|
|
4
15
|
#observed;
|
|
@@ -7,6 +18,7 @@ export class PlatformCollections {
|
|
|
7
18
|
collectSources;
|
|
8
19
|
readCreator;
|
|
9
20
|
readBookmarks;
|
|
21
|
+
readExploreRecommendations;
|
|
10
22
|
searchSources;
|
|
11
23
|
constructor(dependencies) {
|
|
12
24
|
const { ready, observed, browser, choose } = dependencies;
|
|
@@ -18,6 +30,8 @@ export class PlatformCollections {
|
|
|
18
30
|
this.readCreator = this.handleReadCreator.bind(this);
|
|
19
31
|
this.readBookmarks = this.handleReadBookmarks.bind(this);
|
|
20
32
|
this.searchSources = this.handleSearchSources.bind(this);
|
|
33
|
+
this.readExploreRecommendations =
|
|
34
|
+
this.handleReadExploreRecommendations.bind(this);
|
|
21
35
|
}
|
|
22
36
|
async handleCollectSources({ processedIds = [], maxPages = 3, limit = 1, } = {}) {
|
|
23
37
|
if (!Array.isArray(processedIds) ||
|
|
@@ -439,6 +453,134 @@ export class PlatformCollections {
|
|
|
439
453
|
};
|
|
440
454
|
return { ...value, query };
|
|
441
455
|
}
|
|
456
|
+
async handleReadExploreRecommendations({ limit = 3, maxPages = 1, processedIds = [], } = {}) {
|
|
457
|
+
await this.#browser.act({ operation: "openExploreRecommendations" });
|
|
458
|
+
let nav = await this.#ready({ view: "navigation" });
|
|
459
|
+
const page = (value) => {
|
|
460
|
+
try {
|
|
461
|
+
const url = xURL(value);
|
|
462
|
+
return url.pathname.replace(/\/+$/, "") === "/explore" ? url : null;
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const entryPage = page(nav.page);
|
|
469
|
+
if (!entryPage)
|
|
470
|
+
return {
|
|
471
|
+
status: "needsDecision",
|
|
472
|
+
fresh: true,
|
|
473
|
+
complete: false,
|
|
474
|
+
page: nav.page,
|
|
475
|
+
missing: ["explore-page-unconfirmed"],
|
|
476
|
+
sources: [],
|
|
477
|
+
navigationEvidence: exploreNavigationEvidence(nav),
|
|
478
|
+
};
|
|
479
|
+
if (!nav.alerts?.length && nav.needsStateCheck) {
|
|
480
|
+
try {
|
|
481
|
+
await this.#browser.act({ operation: "waitForPosts" });
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
if (!/timed out|timeout/i.test(error.message))
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
nav = await this.#ready({ view: "navigation" });
|
|
488
|
+
}
|
|
489
|
+
if (nav.alerts?.length)
|
|
490
|
+
return {
|
|
491
|
+
status: "needsDecision",
|
|
492
|
+
fresh: true,
|
|
493
|
+
complete: false,
|
|
494
|
+
page: nav.page,
|
|
495
|
+
missing: ["platform-alert"],
|
|
496
|
+
sources: [],
|
|
497
|
+
navigationEvidence: exploreNavigationEvidence(nav),
|
|
498
|
+
};
|
|
499
|
+
if (nav.needsStateCheck) {
|
|
500
|
+
const diagnosticRead = await this.handleCollectSources({
|
|
501
|
+
limit,
|
|
502
|
+
maxPages,
|
|
503
|
+
processedIds,
|
|
504
|
+
});
|
|
505
|
+
const sameExplorePage = page(diagnosticRead.page);
|
|
506
|
+
return {
|
|
507
|
+
...diagnosticRead,
|
|
508
|
+
status: "needsDecision",
|
|
509
|
+
complete: false,
|
|
510
|
+
missing: [
|
|
511
|
+
...new Set([
|
|
512
|
+
"explore-navigation-unconfirmed",
|
|
513
|
+
...(!sameExplorePage ? ["explore-page-changed-during-read"] : []),
|
|
514
|
+
...(diagnosticRead.missing ?? []),
|
|
515
|
+
]),
|
|
516
|
+
],
|
|
517
|
+
sources: sameExplorePage ? (diagnosticRead.sources ?? []) : [],
|
|
518
|
+
navigationEvidence: exploreNavigationEvidence(nav),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
const recommended = (nav.controls ?? []).filter((control) => control.role === "tab" &&
|
|
522
|
+
["为你推荐", "For you"].includes(control.label.split("\n")[0]));
|
|
523
|
+
if (recommended.length !== 1)
|
|
524
|
+
return {
|
|
525
|
+
status: "needsDecision",
|
|
526
|
+
fresh: true,
|
|
527
|
+
complete: false,
|
|
528
|
+
page: entryPage.href,
|
|
529
|
+
missing: ["explore-recommended-tab-unconfirmed"],
|
|
530
|
+
sources: [],
|
|
531
|
+
navigationEvidence: exploreNavigationEvidence(nav),
|
|
532
|
+
};
|
|
533
|
+
const expectedLabel = recommended[0].label.split("\n")[0];
|
|
534
|
+
if (!recommended[0].selected &&
|
|
535
|
+
!(await this.#choose(nav, (control) => control.role === "tab" &&
|
|
536
|
+
control.label.split("\n")[0] === expectedLabel)))
|
|
537
|
+
return {
|
|
538
|
+
status: "needsDecision",
|
|
539
|
+
fresh: true,
|
|
540
|
+
complete: false,
|
|
541
|
+
page: entryPage.href,
|
|
542
|
+
missing: ["explore-recommended-tab-selection-unavailable"],
|
|
543
|
+
sources: [],
|
|
544
|
+
navigationEvidence: exploreNavigationEvidence(nav),
|
|
545
|
+
};
|
|
546
|
+
nav = await this.#ready({ view: "navigation" });
|
|
547
|
+
const selected = (nav.controls ?? []).filter((control) => control.role === "tab" &&
|
|
548
|
+
control.label.split("\n")[0] === expectedLabel &&
|
|
549
|
+
control.selected);
|
|
550
|
+
if (!page(nav.page) ||
|
|
551
|
+
nav.needsStateCheck ||
|
|
552
|
+
nav.alerts?.length ||
|
|
553
|
+
selected.length !== 1)
|
|
554
|
+
return {
|
|
555
|
+
status: "needsDecision",
|
|
556
|
+
fresh: true,
|
|
557
|
+
complete: false,
|
|
558
|
+
page: nav.page,
|
|
559
|
+
missing: ["explore-recommended-tab-selection-unconfirmed"],
|
|
560
|
+
sources: [],
|
|
561
|
+
navigationEvidence: exploreNavigationEvidence(nav),
|
|
562
|
+
};
|
|
563
|
+
const value = await this.handleCollectSources({
|
|
564
|
+
limit,
|
|
565
|
+
maxPages,
|
|
566
|
+
processedIds,
|
|
567
|
+
});
|
|
568
|
+
if (!page(value.page))
|
|
569
|
+
return {
|
|
570
|
+
...value,
|
|
571
|
+
status: "needsDecision",
|
|
572
|
+
complete: false,
|
|
573
|
+
missing: ["explore-page-changed-during-read"],
|
|
574
|
+
};
|
|
575
|
+
return {
|
|
576
|
+
...value,
|
|
577
|
+
sourceOrigin: {
|
|
578
|
+
kind: "explore-recommendations",
|
|
579
|
+
page: entryPage.href,
|
|
580
|
+
selectedTab: expectedLabel,
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
}
|
|
442
584
|
}
|
|
443
585
|
export function createPlatformCollections(...args) {
|
|
444
586
|
return new PlatformCollections(...args);
|
|
@@ -19,6 +19,7 @@ export interface PlatformDependencies {
|
|
|
19
19
|
media: any;
|
|
20
20
|
links: any;
|
|
21
21
|
needsExpansion: any;
|
|
22
|
+
metrics: string[];
|
|
22
23
|
missing: any[];
|
|
23
24
|
};
|
|
24
25
|
collectSources: ({ processedIds, maxPages, limit }?: any) => Promise<any>;
|
|
@@ -28,6 +29,7 @@ export interface PlatformDependencies {
|
|
|
28
29
|
readAccount: () => Promise<any>;
|
|
29
30
|
readBookmarks: ({ folder, ...window }?: any) => Promise<any>;
|
|
30
31
|
searchSources: ({ query, limit, maxPages, processedIds, }?: any) => Promise<any>;
|
|
32
|
+
readExploreRecommendations: (args?: import("./types.js").ExploreRecommendationOptions) => Promise<any>;
|
|
31
33
|
readFeedback: ({ targets, replyLimit }?: any) => Promise<any>;
|
|
32
34
|
readPerson: (target: any) => Promise<any>;
|
|
33
35
|
readPost: (target: any) => Promise<any>;
|
package/dist/platform-rules.js
CHANGED
|
@@ -34,7 +34,13 @@ export const source = (post, page) => ({
|
|
|
34
34
|
images: post.images ?? [],
|
|
35
35
|
media: post.media ?? [],
|
|
36
36
|
links: post.links ?? [],
|
|
37
|
-
metrics: [
|
|
37
|
+
metrics: [
|
|
38
|
+
...new Set([...(post.buttons ?? []), ...(post.metrics ?? [])]
|
|
39
|
+
.map((metric) => typeof metric === "string" ? metric : metric?.label)
|
|
40
|
+
.filter((label) => typeof label === "string" &&
|
|
41
|
+
/(?:回复|转帖|喜欢|书签|观看|查看|引用|replies|reply|reposts?|retweets?|likes?|bookmarks?|views?|quotes?)/i.test(label) &&
|
|
42
|
+
/\d/.test(label))),
|
|
43
|
+
],
|
|
38
44
|
needsExpansion: post.needsExpansion,
|
|
39
45
|
missing: [
|
|
40
46
|
...(post.missing ?? []),
|
package/dist/platform.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createPlatformCollections } from "./platform-collections.js";
|
|
1
2
|
export { xURL } from "./platform-rules.js";
|
|
2
3
|
export declare class XPlatform {
|
|
3
4
|
#private;
|
|
@@ -8,6 +9,7 @@ export declare class XPlatform {
|
|
|
8
9
|
readAccount: () => Promise<any>;
|
|
9
10
|
readBookmarks: ({ folder, ...window }?: any) => Promise<any>;
|
|
10
11
|
searchSources: ({ query, limit, maxPages, processedIds, }?: any) => Promise<any>;
|
|
12
|
+
readExploreRecommendations: ReturnType<typeof createPlatformCollections>["readExploreRecommendations"];
|
|
11
13
|
readFeedback: ({ targets, replyLimit }?: any) => Promise<any>;
|
|
12
14
|
readPerson: (target: any) => Promise<any>;
|
|
13
15
|
readPost: (target: any) => Promise<any>;
|
package/dist/platform.js
CHANGED
|
@@ -23,6 +23,7 @@ export class XPlatform {
|
|
|
23
23
|
#readCreator;
|
|
24
24
|
#readBookmarks;
|
|
25
25
|
#searchSources;
|
|
26
|
+
#readExploreRecommendations;
|
|
26
27
|
#createPlatformDiagnosticsResult;
|
|
27
28
|
#followStage;
|
|
28
29
|
#captureProof;
|
|
@@ -41,6 +42,7 @@ export class XPlatform {
|
|
|
41
42
|
readAccount;
|
|
42
43
|
readBookmarks;
|
|
43
44
|
searchSources;
|
|
45
|
+
readExploreRecommendations;
|
|
44
46
|
readFeedback;
|
|
45
47
|
readPerson;
|
|
46
48
|
readPost;
|
|
@@ -79,11 +81,12 @@ export class XPlatform {
|
|
|
79
81
|
browser: browser,
|
|
80
82
|
choose: this.#choose,
|
|
81
83
|
});
|
|
82
|
-
const { collectSources, readCreator, readBookmarks, searchSources } = this.#createPlatformCollectionsResult;
|
|
84
|
+
const { collectSources, readCreator, readBookmarks, searchSources, readExploreRecommendations, } = this.#createPlatformCollectionsResult;
|
|
83
85
|
this.#collectSources = collectSources;
|
|
84
86
|
this.#readCreator = readCreator;
|
|
85
87
|
this.#readBookmarks = readBookmarks;
|
|
86
88
|
this.#searchSources = searchSources;
|
|
89
|
+
this.#readExploreRecommendations = readExploreRecommendations;
|
|
87
90
|
this.#createPlatformDiagnosticsResult = createPlatformDiagnostics({
|
|
88
91
|
clock: this.#clock,
|
|
89
92
|
});
|
|
@@ -116,6 +119,7 @@ export class XPlatform {
|
|
|
116
119
|
this.readAccount = this.#readAccount;
|
|
117
120
|
this.readBookmarks = this.#readBookmarks;
|
|
118
121
|
this.searchSources = this.#searchSources;
|
|
122
|
+
this.readExploreRecommendations = this.#readExploreRecommendations;
|
|
119
123
|
this.readFeedback = this.#readFeedback;
|
|
120
124
|
this.readPerson = this.#readPerson;
|
|
121
125
|
this.readPeople = this.#createPlatformTargetsResult.readPeople;
|
package/dist/results.js
CHANGED
|
@@ -90,6 +90,17 @@ export function source(value, page) {
|
|
|
90
90
|
: []);
|
|
91
91
|
if (out.links)
|
|
92
92
|
out.links = out.links.filter((link) => isContentLink(link, out));
|
|
93
|
+
if (raw.metrics !== undefined) {
|
|
94
|
+
const metrics = list(raw.metrics).flatMap((value) => {
|
|
95
|
+
if (typeof value === "string")
|
|
96
|
+
return [value];
|
|
97
|
+
const label = object(value).label;
|
|
98
|
+
return typeof label === "string" ? [label] : [];
|
|
99
|
+
});
|
|
100
|
+
out.metrics = [
|
|
101
|
+
...new Set(metrics.filter((label) => /(?:回复|转帖|喜欢|书签|观看|查看|引用|replies|reply|reposts?|retweets?|likes?|bookmarks?|views?|quotes?)/i.test(label) && /\d/.test(label))),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
93
104
|
if (strings(raw.missing).length)
|
|
94
105
|
out.missing = strings(raw.missing);
|
|
95
106
|
return out;
|
|
@@ -200,6 +211,28 @@ export function readResult(operation, value, mode) {
|
|
|
200
211
|
out.complete = false;
|
|
201
212
|
if (raw.ids !== undefined)
|
|
202
213
|
out.ids = strings(raw.ids);
|
|
214
|
+
if (operation === "readExploreRecommendations") {
|
|
215
|
+
const origin = object(raw.sourceOrigin);
|
|
216
|
+
try {
|
|
217
|
+
const page = new URL(String(origin.page));
|
|
218
|
+
if (origin.kind === "explore-recommendations" &&
|
|
219
|
+
page.protocol === "https:" &&
|
|
220
|
+
page.hostname === "x.com" &&
|
|
221
|
+
!page.username &&
|
|
222
|
+
!page.password &&
|
|
223
|
+
!page.port &&
|
|
224
|
+
page.pathname.replace(/\/+$/, "") === "/explore" &&
|
|
225
|
+
["为你推荐", "For you"].includes(String(origin.selectedTab)))
|
|
226
|
+
out.sourceOrigin = {
|
|
227
|
+
kind: "explore-recommendations",
|
|
228
|
+
page: page.href,
|
|
229
|
+
selectedTab: String(origin.selectedTab),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
/* Unverified provenance is omitted at the public business boundary. */
|
|
234
|
+
}
|
|
235
|
+
}
|
|
203
236
|
const missing = strings(raw.missing);
|
|
204
237
|
if (list(raw.alerts).length && !missing.includes("platform-alert"))
|
|
205
238
|
missing.push("platform-alert");
|
package/dist/types.d.ts
CHANGED
|
@@ -93,6 +93,11 @@ export interface Diagnostic {
|
|
|
93
93
|
next?: "inspect-local-proof" | "restore-proof-store";
|
|
94
94
|
evidenceRef?: EvidenceRef;
|
|
95
95
|
}
|
|
96
|
+
export interface SourceOriginEvidence {
|
|
97
|
+
kind: "explore-recommendations";
|
|
98
|
+
page: string;
|
|
99
|
+
selectedTab: "为你推荐" | "For you";
|
|
100
|
+
}
|
|
96
101
|
export interface ReadResult {
|
|
97
102
|
status: "observed" | "needsDecision" | "needsCapability";
|
|
98
103
|
fresh?: boolean;
|
|
@@ -124,6 +129,7 @@ export interface ReadResult {
|
|
|
124
129
|
scopeComplete?: boolean;
|
|
125
130
|
};
|
|
126
131
|
sources?: PostEvidence[];
|
|
132
|
+
sourceOrigin?: SourceOriginEvidence;
|
|
127
133
|
post?: PostEvidence;
|
|
128
134
|
context?: PostEvidence[];
|
|
129
135
|
threadPosts?: PostEvidence[];
|
|
@@ -253,6 +259,8 @@ export interface BookmarkOptions extends WindowOptions {
|
|
|
253
259
|
export interface SearchOptions extends WindowOptions {
|
|
254
260
|
query: string;
|
|
255
261
|
}
|
|
262
|
+
export interface ExploreRecommendationOptions extends WindowOptions {
|
|
263
|
+
}
|
|
256
264
|
export interface CreatorOptions {
|
|
257
265
|
handle: string;
|
|
258
266
|
target?: number;
|
|
@@ -563,6 +571,7 @@ export interface XGrowthHarness {
|
|
|
563
571
|
readFollowbackAudit(args: FollowbackAuditOptions): Promise<FollowbackAuditResult>;
|
|
564
572
|
readBookmarks(args: BookmarkOptions): Promise<ReadResult>;
|
|
565
573
|
searchSources(args: SearchOptions): Promise<ReadResult>;
|
|
574
|
+
readExploreRecommendations(args?: ExploreRecommendationOptions): Promise<ReadResult>;
|
|
566
575
|
readIncomingReplies(args?: IncomingReplyOptions): Promise<ReadResult>;
|
|
567
576
|
readIncomingConversations(args?: IncomingConversationOptions): Promise<IncomingConversationResult>;
|
|
568
577
|
readPost(args: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/x-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.67",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed X task harness with compact business results and explicit diagnostics.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "03a90495bd2c781577399aca850742082abe3556"
|
|
46
46
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
| readCreator | handle | target (10;1–50), maxScrolls, navigate, expand, mode |
|
|
7
7
|
| readBookmarks | folder (observed name), or discoverOnly:true | processedIds, limit (1–5), maxPages (1–10), resume, mode |
|
|
8
8
|
| searchSources | query | processedIds, limit (1–5), maxPages (1–10), mode |
|
|
9
|
+
| readExploreRecommendations | none | processedIds, limit (1–5), maxPages (1–10), mode |
|
|
9
10
|
| readPost | target: {postId,url,evidenceRef} | includeContext, resolveQuote, mode |
|
|
10
11
|
| readPerson | target: {handle,url,evidenceRef} | mode |
|
|
11
12
|
| readPeople | target: {url,evidenceRef} | offset, limit (1–5), advance, mode |
|
|
@@ -13,20 +14,20 @@
|
|
|
13
14
|
| inspectAccount | none | mode |
|
|
14
15
|
| inspectRound | taskId (original batch ID) | none |
|
|
15
16
|
|
|
16
|
-
Targets
|
|
17
|
+
Targets need proof. Native `readCreator` requires a selected profile (`navigate:false`); relative time is unknown. Stop on route changes.
|
|
17
18
|
|
|
18
|
-
Bookmarks resume:true continues the bound folder; rendered tail ≠ full proof.
|
|
19
|
+
Bookmarks `resume:true` continues the bound folder; rendered tail ≠ full proof.
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
Observed ≠ complete; `pageComplete:false` means finite scope. Preserve full text; view media before describing; keep raw reads local.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
Incoming replies: ledger only; carry processedIds; skip settled/intent/unknown. Read parents with `readPost(includeContext:true)`; deduplicate.
|
|
23
24
|
|
|
24
|
-
Incoming: `readIncomingConversations({targets?,...window})`
|
|
25
|
+
Incoming conversations: `readIncomingConversations({targets?,...window})` discovers/reads ≤5 chains in one call. Targets need proof; returns nodes, ancestors, confirmed edges, unknown parents and attempts. `rootCandidateId` may not be root. Missing context blocks review; don't infer edges or clearance.
|
|
25
26
|
|
|
26
27
|
`resolveQuote:true` follows one observed card and returns `post.quoteSource` only after exact destination/body/author/time binding.
|
|
27
28
|
|
|
28
|
-
`readPeople`:
|
|
29
|
+
`readPeople`: selected followers/following; `offset` reuses cells, `advance` scrolls. Returns bio, blue, relationship; dedup. Unknowns need profiles; Native unsupported.
|
|
29
30
|
|
|
30
31
|
Discovery: names only.
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
Count `sourceOrigin` only with confirmed `/explore` “For you” tab; exclude trends/search. Preserve visible counts; absent stays unknown.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Observed reply receipt recovery
|
|
2
|
+
|
|
3
|
+
If the root reports an original unknown but a fresh proof or public search exposes its actual own-account permalink, keep that original action. Never derive a receipt ID from a timestamp or guess URLs. Read the observed candidate with root readPost and its original evidenceRef; confirm the actual author, reviewed text, new ID and time at/after intent. A visible parent in context alone is not the final exact-parent verification.
|
|
4
|
+
|
|
5
|
+
The original ledger already supports observedPublicURL and observedPublicEvidenceRef. Use the existing diagnostics createFileRoundStore transaction to attach only these two fields to that same unknown/intent reply, preserving other actions, executors and counts. Both fields must come from actual observed proof, not a claimed success. Then call root verifySubmission({taskId,actionId}); its normal fresh author/text/time/assets/exact-parent gates settle the original action. A rejected/ambiguous hint remains unknown. Do not manually mark verified, create a replacement action, resend or bypass alerts.
|
|
6
|
+
|
|
7
|
+
Prepared reply baseline URL timeouts get one internal read-only continuation. A second timeout, unrelated failure, alerts or unconfirmed content stops before intent. Reusing this root behavior is preferable to model-level per-parent rewarming. This does not grant new platform access or override project budgets/windows.
|
|
@@ -4,7 +4,7 @@ Load on failure/unknown only. Keep operation/status/missing/code/proof/error; do
|
|
|
4
4
|
|
|
5
5
|
| Signal | Next action and exit condition |
|
|
6
6
|
| --- | --- |
|
|
7
|
-
| unknown submission | `await x.verifySubmission({taskId,actionId})`, same IDs; no
|
|
7
|
+
| unknown submission | `await x.verifySubmission({taskId,actionId})`, same IDs; no resend. [Observed link](receipt-recovery.md). |
|
|
8
8
|
| Existing public post needs recovery | `await x.verifyPublishedPost({taskId,draftPath,publicURL,evidenceRef})`; observed URL/proof. sourceLink/sourceLinks: [link proof](source-link-recovery.md). |
|
|
9
9
|
| Follow needs independent recovery | `await x.verifyFollow({handle,url,evidenceRef,mode:'decision'})`; honor returned verified. Normal followBatch already verifies. |
|
|
10
10
|
| needsDecision | Resolve returned missing review/proof in original action, resume same ID. Current AI performs internal review under project contract. |
|