@nextclaw/x-harness 0.2.40 → 0.2.42
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/browser-dom-capabilities.js +8 -0
- package/dist/browser-dom-navigation.js +16 -0
- package/dist/draft-schedule.d.ts +2 -0
- package/dist/draft-schedule.js +21 -5
- package/dist/platform-collections.js +11 -4
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
- package/skill/references/writes.md +2 -0
package/SOURCE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Corresponding source
|
|
2
2
|
|
|
3
|
-
Version 0.2.
|
|
3
|
+
Version 0.2.42, commit 56272553b4ce936e466e3139f56f1adbde4a353c.
|
|
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/56272553b4ce936e466e3139f56f1adbde4a353c
|
|
6
6
|
|
|
7
7
|
Clone this revision, run npm ci, npm run bootstrap and npm run release:build.
|
|
8
8
|
|
|
@@ -126,6 +126,14 @@ export class DOMCapabilities {
|
|
|
126
126
|
"first",
|
|
127
127
|
"waitFor",
|
|
128
128
|
]));
|
|
129
|
+
if (operation === "readBookmarks" &&
|
|
130
|
+
typeof this.#tab?.playwright?.locator === "function")
|
|
131
|
+
base.push(...missingMethods(this.#tab.playwright.locator("div"), [
|
|
132
|
+
"locator",
|
|
133
|
+
"filter",
|
|
134
|
+
"first",
|
|
135
|
+
"waitFor",
|
|
136
|
+
]));
|
|
129
137
|
if (operation !== "readFeedback") {
|
|
130
138
|
base.push(...missingMethods(this.#tab?.playwright, [
|
|
131
139
|
"getByRole",
|
|
@@ -64,6 +64,22 @@ export class DOMNavigation {
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
async handleWaitForFolder(command) {
|
|
67
|
+
if (command.folderDiscovery) {
|
|
68
|
+
// "All bookmarks" renders before the asynchronously loaded named cards.
|
|
69
|
+
// Wait for any named card inside that specific menu, never an unrelated
|
|
70
|
+
// dialog or arbitrary first clickable control.
|
|
71
|
+
const menu = this.#tab.playwright
|
|
72
|
+
.locator('[role="dialog"],[data-testid="Dropdown"]')
|
|
73
|
+
.filter({ hasText: command.folder });
|
|
74
|
+
await menu
|
|
75
|
+
.locator("span:not(:has(span))", {})
|
|
76
|
+
.filter({
|
|
77
|
+
hasText: /^(?!书签$|Bookmarks$|所有书签$|All bookmarks$).+$/i,
|
|
78
|
+
})
|
|
79
|
+
.first()
|
|
80
|
+
.waitFor({ state: "visible", timeoutMs: 2000 });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
67
83
|
await this.#tab.playwright
|
|
68
84
|
.getByText(command.folder, { exact: true })
|
|
69
85
|
.waitFor({ state: "visible", timeoutMs: 2000 });
|
package/dist/draft-schedule.d.ts
CHANGED
package/dist/draft-schedule.js
CHANGED
|
@@ -12,20 +12,36 @@ export class DraftSchedule {
|
|
|
12
12
|
action.notBefore,
|
|
13
13
|
...Array.from(metadata.matchAll(/^(?:notBefore\s*:\s*|最早发布窗口[::]\s*)([^\n;;]+)/gm), (m) => m[1].trim().replace(/^['"]|['"]$/g, "")),
|
|
14
14
|
].filter((v) => v !== undefined && v !== null);
|
|
15
|
+
const endings = [
|
|
16
|
+
action.notAfter,
|
|
17
|
+
...Array.from(metadata.matchAll(/^(?:notAfter\s*:\s*|最晚发布窗口[::]\s*)([^\n;;]+)/gm), (m) => m[1].trim().replace(/^['"]|['"]$/g, "")),
|
|
18
|
+
].filter((v) => v !== undefined && v !== null);
|
|
19
|
+
if (endings.some((v) => typeof v !== "string" ||
|
|
20
|
+
!/(?:Z|[+-]\d{2}:\d{2})$/.test(v) ||
|
|
21
|
+
!Number.isFinite(Date.parse(v))))
|
|
22
|
+
return { invalid: true, invalidAfter: true };
|
|
23
|
+
const until = endings.length
|
|
24
|
+
? new Date(Math.min(...endings.map((v) => Date.parse(v)))).toISOString()
|
|
25
|
+
: undefined;
|
|
15
26
|
if (!declared.length)
|
|
16
|
-
return {};
|
|
27
|
+
return until ? { until } : {};
|
|
17
28
|
if (declared.some((v) => typeof v !== "string" ||
|
|
18
29
|
!/(?:Z|[+-]\d{2}:\d{2})$/.test(v) ||
|
|
19
30
|
!Number.isFinite(Date.parse(v))))
|
|
20
31
|
return { invalid: true };
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
const at = new Date(Math.max(...declared.map((v) => Date.parse(v)))).toISOString();
|
|
33
|
+
if (until && Date.parse(until) <= Date.parse(at))
|
|
34
|
+
return { invalid: true, invalidAfter: true };
|
|
35
|
+
return { at, ...(until ? { until } : {}) };
|
|
24
36
|
}
|
|
25
37
|
reason(action, now) {
|
|
26
38
|
const schedule = this.resolve(action);
|
|
27
39
|
if (schedule.invalid)
|
|
28
|
-
return
|
|
40
|
+
return schedule.invalidAfter
|
|
41
|
+
? "draft-not-after-invalid"
|
|
42
|
+
: "draft-not-before-invalid";
|
|
43
|
+
if (schedule.until && now >= Date.parse(schedule.until))
|
|
44
|
+
return "draft-window-expired";
|
|
29
45
|
return schedule.at && now < Date.parse(schedule.at)
|
|
30
46
|
? "draft-not-before-not-reached"
|
|
31
47
|
: null;
|
|
@@ -240,7 +240,7 @@ export class PlatformCollections {
|
|
|
240
240
|
}
|
|
241
241
|
if (discoverOnly) {
|
|
242
242
|
if (!nav.bookmarkFolders?.length) {
|
|
243
|
-
await this.waitForBookmarkLabel(nav, "所有书签", "All bookmarks");
|
|
243
|
+
await this.waitForBookmarkLabel(nav, "所有书签", "All bookmarks", true);
|
|
244
244
|
nav = await this.#ready({
|
|
245
245
|
view: "navigation",
|
|
246
246
|
bookmarkNavigation: true,
|
|
@@ -250,7 +250,10 @@ export class PlatformCollections {
|
|
|
250
250
|
}
|
|
251
251
|
if (!(nav.controls ?? []).some((c) => c.label.split("\n")[0] === folder)) {
|
|
252
252
|
try {
|
|
253
|
-
await this.#browser.act({
|
|
253
|
+
await this.#browser.act({
|
|
254
|
+
operation: "waitForFolder",
|
|
255
|
+
folder,
|
|
256
|
+
});
|
|
254
257
|
}
|
|
255
258
|
catch (error) {
|
|
256
259
|
if (!/timed out|timeout/i.test(error.message))
|
|
@@ -304,12 +307,16 @@ export class PlatformCollections {
|
|
|
304
307
|
missing: folders.length ? [] : ["bookmark-folder-menu-unconfirmed"],
|
|
305
308
|
};
|
|
306
309
|
}
|
|
307
|
-
async waitForBookmarkLabel(nav, chinese, english) {
|
|
310
|
+
async waitForBookmarkLabel(nav, chinese, english, folderDiscovery = false) {
|
|
308
311
|
const folder = (nav.controls ?? []).some((c) => /^(History|Bookmarks)$/i.test(c.label))
|
|
309
312
|
? english
|
|
310
313
|
: chinese;
|
|
311
314
|
try {
|
|
312
|
-
await this.#browser.act({
|
|
315
|
+
await this.#browser.act({
|
|
316
|
+
operation: "waitForFolder",
|
|
317
|
+
folder,
|
|
318
|
+
folderDiscovery,
|
|
319
|
+
});
|
|
313
320
|
}
|
|
314
321
|
catch (error) {
|
|
315
322
|
if (!/timed out|timeout/i.test(error.message))
|
package/dist/types.d.ts
CHANGED
|
@@ -250,6 +250,8 @@ export type PreparedAction = PreparedActionBase & ({
|
|
|
250
250
|
kind: "original";
|
|
251
251
|
draftPath: string;
|
|
252
252
|
notBefore?: string;
|
|
253
|
+
/** Exclusive publication deadline; does not block unknown recovery. */
|
|
254
|
+
notAfter?: string;
|
|
253
255
|
text: string;
|
|
254
256
|
composeURL: string;
|
|
255
257
|
composeEvidenceRef: EvidenceRef;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/x-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.42",
|
|
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": "56272553b4ce936e466e3139f56f1adbde4a353c"
|
|
46
46
|
}
|
|
@@ -14,6 +14,8 @@ Action APIs return status/actionId/publicURL/evidenceRef and reason/missing/resu
|
|
|
14
14
|
|
|
15
15
|
Original action optional notBefore is an ISO timestamp with timezone. Explicit draft header notBefore or 最早发布窗口 is also enforced; the later constraint wins. Invalid metadata blocks, and before-window independent publishing returns waiting; scoped rounds report draft-not-before-not-reached. Both preserve no intent or submission. Recovery of an already submitted post is unaffected.
|
|
16
16
|
|
|
17
|
+
Original actions may pass `notAfter` as the calendar window’s exclusive ISO end. It is rechecked at intent and immediately before activation; expiration submits nothing. Unknown verification remains allowed after expiry.
|
|
18
|
+
|
|
17
19
|
Additional fields: kind:'original', draftPath (existing .post.md), text (complete approved text), composeURL, composeEvidenceRef; optional assets:[{path,alt}], activation:'click'|'keyboard'. Every media path must exist and be reviewed. The DOM uploader supports one absolute PNG/JPEG/WebP image with required ALT, or one absolute MP4 video. Video uploads wait for a visible preview and public settlement requires a video on the exact fresh post; image ALT controls are not applied to videos.
|
|
18
20
|
|
|
19
21
|
```js
|