@nextclaw/x-harness 0.2.1 → 0.2.3

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # Corresponding source
2
2
 
3
- Version 0.2.1, commit 66d1ce5637601897d7d769f74d56eacffc2e865e.
3
+ Version 0.2.3, commit 51c80ceb827a4eb28c4fbbd78b9182ba3332156d.
4
4
 
5
- Complete source, build scripts and dependencies: https://github.com/Peiiii/x-harness/tree/66d1ce5637601897d7d769f74d56eacffc2e865e
5
+ Complete source, build scripts and dependencies: https://github.com/Peiiii/x-harness/tree/51c80ceb827a4eb28c4fbbd78b9182ba3332156d
6
6
 
7
7
  Clone this revision, run npm ci, npm run bootstrap and npm run release:build.
8
8
 
@@ -248,12 +248,26 @@ export class DOMEditing {
248
248
  if (query.view !== "composer" ||
249
249
  result.needsStateCheck ||
250
250
  command.assets?.length !== 1)
251
- throw new Error("仅上传已观察编辑器的一张带ALT图片");
251
+ throw new Error("仅上传已观察编辑器的一张图片或一个MP4视频");
252
252
  const asset = command.assets[0];
253
- if (!asset.alt?.trim())
253
+ const isVideo = /\.mp4$/i.test(asset.path ?? "");
254
+ if (!isVideo && !asset.alt?.trim())
254
255
  throw new Error("图片须有完整ALT");
255
256
  this.#session.current = null;
256
257
  await attachImage(this.#tab, { path: asset.path });
258
+ if (isVideo) {
259
+ await this.#tab.playwright
260
+ .locator('[data-testid="videoPlayer"],video')
261
+ .first()
262
+ .waitFor({ state: "visible", timeoutMs: 60000 });
263
+ const composer = await this.#observe({ view: "composer" });
264
+ if (composer.needsStateCheck || !composer.videos?.length)
265
+ throw new Error("上传视频预览未确认");
266
+ return {
267
+ completed: true,
268
+ value: { assets: [asset], mediaEvidence: composer.videos, composer },
269
+ };
270
+ }
257
271
  await this.#tab.playwright
258
272
  .getByRole("button", {
259
273
  name: /^(?:编辑|Edit)(?:图片|照片|媒体| image| photo| media)?(?: \d+)?$/i,
@@ -83,7 +83,7 @@ export class GrowthPolicy {
83
83
  hour: "2-digit",
84
84
  hourCycle: "h23",
85
85
  }).format(now));
86
- if (hour < 7 || hour >= 22)
86
+ if ((hour < 7 || hour >= 22) && !this.authorizedWindow(state, "follow"))
87
87
  return {
88
88
  status: "needsDecision",
89
89
  reason: "outside-contract-write-window",
@@ -58,6 +58,7 @@ export class PlatformWriting {
58
58
  async handleFindSubmission(action) {
59
59
  if (action.kind === "follow") {
60
60
  let value = await this.#followStage(action, "verify-read", () => this.#readPerson(action));
61
+ const verificationAlerts = [...(value.alerts ?? [])];
61
62
  if (!value.needsStateCheck) {
62
63
  const absent = this.#browser.capabilities?.("refreshPerson") ?? [
63
64
  "refreshPerson",
@@ -77,6 +78,10 @@ export class PlatformWriting {
77
78
  }));
78
79
  value = await this.#followStage(action, "verify-after-refresh", () => this.#readPerson(action));
79
80
  }
81
+ value = {
82
+ ...value,
83
+ alerts: [...new Set([...verificationAlerts, ...(value.alerts ?? [])])],
84
+ };
80
85
  return {
81
86
  ...value,
82
87
  verified: !value.needsStateCheck &&
@@ -123,7 +128,9 @@ export class PlatformWriting {
123
128
  samePublishedText(post.body, action.text) &&
124
129
  /^\d+$/.test(post.id ?? "") &&
125
130
  (!action.assets?.length ||
126
- action.assets.every((asset) => post.images?.some((image) => image.alt === asset.alt))) &&
131
+ action.assets.every((asset) => /\.mp4$/i.test(asset.path ?? "")
132
+ ? post.media?.some((media) => media.kind === "video")
133
+ : post.images?.some((image) => image.alt === asset.alt))) &&
127
134
  !(action.beforeIds ?? []).includes(post.id) &&
128
135
  Number.isFinite(Date.parse(post.datetime)) &&
129
136
  Date.parse(post.datetime) >= Date.parse(action.intentAt) &&
@@ -1,9 +1,9 @@
1
1
  import { readComposer } from "./readers-composer.js";
2
2
  import { readPosts } from "./readers-posts.js";
3
- // Upload only through the observed image button and browser file chooser.
3
+ // Upload only through the observed media button and browser file chooser.
4
4
  export async function attachImage(tab, { path } = {}) {
5
- if (!/^\/.*\.(?:png|jpe?g|webp)$/i.test(path ?? ""))
6
- throw new Error("需要绝对图片路径");
5
+ if (!/^\/.*\.(?:png|jpe?g|webp|mp4)$/i.test(path ?? ""))
6
+ throw new Error("需要绝对图片或MP4路径");
7
7
  if (typeof tab?.playwright?.evaluate !== "function" ||
8
8
  typeof tab?.playwright?.getByRole !== "function" ||
9
9
  typeof tab?.playwright?.waitForEvent !== "function")
@@ -95,6 +95,12 @@ export function readComposer() {
95
95
  images: Array.from(scope.querySelectorAll("img"))
96
96
  .filter(visible)
97
97
  .map((el) => ({ alt: el.alt })),
98
+ videos: Array.from(scope.querySelectorAll('[data-testid="videoPlayer"],video'))
99
+ .filter(visible)
100
+ .map((el) => ({
101
+ kind: "video",
102
+ label: el.getAttribute("aria-label"),
103
+ })),
98
104
  needsStateCheck: false,
99
105
  };
100
106
  }
@@ -285,6 +285,12 @@ export const readXObservation = (query) => {
285
285
  images: Array.from(scope.querySelectorAll("img"))
286
286
  .filter(visible)
287
287
  .map((el) => ({ alt: el.alt })),
288
+ videos: Array.from(scope.querySelectorAll('[data-testid="videoPlayer"],video'))
289
+ .filter(visible)
290
+ .map((el) => ({
291
+ kind: "video",
292
+ label: el.getAttribute("aria-label"),
293
+ })),
288
294
  needsStateCheck: false,
289
295
  };
290
296
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/x-harness",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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": "66d1ce5637601897d7d769f74d56eacffc2e865e"
45
+ "gitHead": "51c80ceb827a4eb28c4fbbd78b9182ba3332156d"
46
46
  }
@@ -12,7 +12,7 @@ Action APIs return status/actionId/publicURL/evidenceRef and reason/missing/resu
12
12
 
13
13
  ## Publish
14
14
 
15
- 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.
15
+ 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.
16
16
 
17
17
  ```js
18
18
  var result = await x.publishPost({ taskId, draftPath, preparedAction: {