@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.
Files changed (79) hide show
  1. package/LICENSE +231 -6
  2. package/README.md +15 -1
  3. package/SOURCE.md +9 -0
  4. package/cli/main.js +232 -200
  5. package/dist/api.d.ts +56 -2
  6. package/dist/api.js +130 -79
  7. package/dist/boundary.js +1 -0
  8. package/dist/browser-common.d.ts +11 -0
  9. package/dist/browser-common.js +34 -18
  10. package/dist/browser-dom-capabilities.d.ts +7 -2
  11. package/dist/browser-dom-capabilities.js +123 -102
  12. package/dist/browser-dom-dependencies.js +1 -0
  13. package/dist/browser-dom-editing.d.ts +20 -2
  14. package/dist/browser-dom-editing.js +148 -116
  15. package/dist/browser-dom-navigation.d.ts +11 -2
  16. package/dist/browser-dom-navigation.js +66 -50
  17. package/dist/browser-dom-submission.d.ts +6 -1
  18. package/dist/browser-dom-submission.js +37 -18
  19. package/dist/browser-dom.d.ts +13 -1
  20. package/dist/browser-dom.js +113 -66
  21. package/dist/browser-native.d.ts +14 -1
  22. package/dist/browser-native.js +161 -128
  23. package/dist/creator-native.d.ts +3 -0
  24. package/dist/creator-native.js +113 -0
  25. package/dist/diagnostics.d.ts +11 -0
  26. package/dist/diagnostics.js +10 -0
  27. package/dist/growth-batch.d.ts +6 -3
  28. package/dist/growth-batch.js +157 -135
  29. package/dist/growth-dependencies.js +1 -0
  30. package/dist/growth-execution.d.ts +8 -2
  31. package/dist/growth-execution.js +68 -37
  32. package/dist/growth-parallel.d.ts +6 -2
  33. package/dist/growth-parallel.js +78 -47
  34. package/dist/growth-plan.d.ts +8 -3
  35. package/dist/growth-plan.js +45 -23
  36. package/dist/growth-policy.d.ts +11 -2
  37. package/dist/growth-policy.js +80 -53
  38. package/dist/growth-publishing.d.ts +7 -2
  39. package/dist/growth-publishing.js +72 -46
  40. package/dist/growth-reading.d.ts +6 -2
  41. package/dist/growth-reading.js +47 -25
  42. package/dist/growth-recovery.d.ts +7 -4
  43. package/dist/growth-recovery.js +177 -148
  44. package/dist/growth-state.d.ts +10 -2
  45. package/dist/growth-state.js +67 -30
  46. package/dist/growth-tasks.d.ts +19 -16
  47. package/dist/growth-tasks.js +153 -90
  48. package/dist/growth.d.ts +25 -1
  49. package/dist/growth.js +207 -124
  50. package/dist/platform-capabilities.d.ts +6 -2
  51. package/dist/platform-capabilities.js +18 -8
  52. package/dist/platform-collections.d.ts +10 -3
  53. package/dist/platform-collections.js +62 -35
  54. package/dist/platform-dependencies.js +1 -0
  55. package/dist/platform-diagnostics.d.ts +7 -2
  56. package/dist/platform-diagnostics.js +31 -19
  57. package/dist/platform-observation.d.ts +10 -2
  58. package/dist/platform-observation.js +44 -23
  59. package/dist/platform-targets.d.ts +10 -3
  60. package/dist/platform-targets.js +39 -14
  61. package/dist/platform-writing.d.ts +10 -2
  62. package/dist/platform-writing.js +63 -32
  63. package/dist/platform.d.ts +22 -1
  64. package/dist/platform.js +120 -52
  65. package/dist/readers-ax-host.d.ts +11 -1
  66. package/dist/readers-ax-host.js +108 -99
  67. package/dist/readers-ax-program.js +5 -0
  68. package/dist/readers-ax-tree.js +1 -0
  69. package/dist/readers-cli.js +79 -0
  70. package/dist/readers.js +9 -0
  71. package/dist/round-state.js +18 -1
  72. package/dist/store.d.ts +22 -4
  73. package/dist/store.js +173 -151
  74. package/dist/types.js +1 -0
  75. package/generated/observation.mjs +1 -1
  76. package/package.json +23 -9
  77. package/skill/SKILL.md +2 -2
  78. package/skill/references/reads.md +1 -1
  79. package/skill/references/setup.md +1 -1
@@ -1,50 +1,71 @@
1
1
  import { xURL } from "./platform-rules.js";
2
- export function createDOMNavigation(dependencies) {
3
- const { session, tab, observe } = dependencies;
4
- async function searchSources(command) {
2
+ export class DOMNavigation {
3
+ #session;
4
+ #tab;
5
+ #observe;
6
+ searchSources;
7
+ waitForFolder;
8
+ waitForPosts;
9
+ openObserved;
10
+ refreshPerson;
11
+ refreshPosts;
12
+ constructor(dependencies) {
13
+ const { session, tab, observe } = dependencies;
14
+ this.#session = session;
15
+ this.#tab = tab;
16
+ this.#observe = observe;
17
+ this.searchSources = this.handleSearchSources.bind(this);
18
+ this.waitForFolder = this.handleWaitForFolder.bind(this);
19
+ this.waitForPosts = this.handleWaitForPosts.bind(this);
20
+ this.openObserved = this.handleOpenObserved.bind(this);
21
+ this.refreshPerson = this.handleRefreshPerson.bind(this);
22
+ this.refreshPosts = this.handleRefreshPosts.bind(this);
23
+ }
24
+ async handleSearchSources(command) {
5
25
  if (typeof command.query !== "string" ||
6
26
  !command.query.trim() ||
7
27
  command.query.length > 300)
8
28
  throw new Error("需一条有效搜索词");
9
- session.current = null;
29
+ this.#session.current = null;
10
30
  const destination = `https://x.com/search?q=${encodeURIComponent(command.query)}&src=typed_query&f=live`;
11
- if (typeof tab.url !== "function" || (await tab.url()) !== destination)
12
- await tab.goto(destination);
31
+ if (typeof this.#tab.url !== "function" ||
32
+ (await this.#tab.url()) !== destination)
33
+ await this.#tab.goto(destination);
13
34
  return;
14
35
  }
15
- async function waitForFolder(command) {
16
- await tab.playwright
36
+ async handleWaitForFolder(command) {
37
+ await this.#tab.playwright
17
38
  .getByText(command.folder, { exact: true })
18
39
  .waitFor({ state: "visible", timeoutMs: 2000 });
19
40
  return;
20
41
  }
21
- async function waitForPosts(command) {
22
- await tab.playwright
42
+ async handleWaitForPosts(command) {
43
+ await this.#tab.playwright
23
44
  .getByRole("article")
24
45
  .first()
25
46
  .waitFor({ state: "visible", timeoutMs: 5000 });
26
47
  return;
27
48
  }
28
- async function openObserved(command) {
49
+ async handleOpenObserved(command) {
29
50
  xURL(command.url);
30
51
  if (!command.evidenceRef)
31
52
  throw new Error("导航缺已观察证据");
32
53
  // A direct adapter caller must also preserve a page already at the target.
33
54
  // Explicit recovery uses forceNavigation; durable verification uses refreshPerson.
34
55
  if (!command.forceNavigation &&
35
- typeof tab.url === "function" &&
36
- (await tab.url()) === xURL(command.url).href)
56
+ typeof this.#tab.url === "function" &&
57
+ (await this.#tab.url()) === xURL(command.url).href)
37
58
  return;
38
- const originalPage = session.current?.result?.page;
39
- session.replyBinding = null;
40
- session.current = null;
59
+ const originalPage = this.#session.current?.result?.page;
60
+ this.#session.replyBinding = null;
61
+ this.#session.current = null;
41
62
  // Keep the loaded X client when the exact observed permalink is already visible.
42
63
  // An ambiguous/missing link retains the existing navigation path, never an arbitrary click.
43
64
  const path = xURL(command.url).pathname + xURL(command.url).search;
44
65
  let link;
45
66
  if (!command.forceNavigation &&
46
- typeof tab.playwright?.locator === "function") {
47
- link = tab.playwright
67
+ typeof this.#tab.playwright?.locator === "function") {
68
+ link = this.#tab.playwright
48
69
  .locator(`a[href=${JSON.stringify(path)}]`)
49
70
  .filter({ visible: true });
50
71
  if (command.view === "person" &&
@@ -63,9 +84,9 @@ export function createDOMNavigation(dependencies) {
63
84
  if (clickedLink)
64
85
  await link.click();
65
86
  else
66
- await tab.goto(command.url);
87
+ await this.#tab.goto(command.url);
67
88
  try {
68
- await tab.playwright.waitForURL(command.url, { timeoutMs: 5000 });
89
+ await this.#tab.playwright.waitForURL(command.url, { timeoutMs: 5000 });
69
90
  }
70
91
  catch (error) {
71
92
  // One inert client-link timeout may use the same observed URL directly.
@@ -74,7 +95,7 @@ export function createDOMNavigation(dependencies) {
74
95
  !originalPage ||
75
96
  !/Timed out waiting for URL/.test(error instanceof Error ? error.message : ""))
76
97
  throw error;
77
- const check = await observe({
98
+ const check = await this.#observe({
78
99
  view: command.view,
79
100
  handle: command.handle,
80
101
  limit: 1,
@@ -83,39 +104,39 @@ export function createDOMNavigation(dependencies) {
83
104
  check.needsStateCheck ||
84
105
  check.alerts?.length)
85
106
  throw error;
86
- await tab.goto(command.url);
87
- await tab.playwright.waitForURL(command.url, { timeoutMs: 5000 });
107
+ await this.#tab.goto(command.url);
108
+ await this.#tab.playwright.waitForURL(command.url, { timeoutMs: 5000 });
88
109
  }
89
110
  if (command.view === "person") {
90
111
  // Profile readiness belongs to profile content; self profiles have no Follow button.
91
- await tab.playwright
112
+ await this.#tab.playwright
92
113
  .locator('[data-testid="UserName"]')
93
114
  .waitFor({ state: "visible", timeoutMs: 5000 });
94
115
  }
95
116
  else if (command.view === "composer") {
96
- await tab.playwright
117
+ await this.#tab.playwright
97
118
  .getByRole("textbox", { name: /^(帖子文本|Post text)$/i })
98
119
  .waitFor({ state: "visible", timeoutMs: 5000 });
99
120
  }
100
121
  else if (command.view === "posts") {
101
- await tab.playwright
122
+ await this.#tab.playwright
102
123
  .getByRole("article")
103
124
  .first()
104
125
  .waitFor({ state: "visible", timeoutMs: 5000 });
105
126
  }
106
127
  return;
107
128
  }
108
- async function refreshPerson(command) {
109
- const at = xURL(await tab.url());
110
- session.replyBinding = null;
129
+ async handleRefreshPerson(command) {
130
+ const at = xURL(await this.#tab.url());
131
+ this.#session.replyBinding = null;
111
132
  if (at.href !== xURL(command.url).href ||
112
133
  at.pathname.toLowerCase() !== `/${command.handle}`.toLowerCase())
113
134
  throw new Error("持久核验页面不匹配");
114
- session.current = null;
135
+ this.#session.current = null;
115
136
  // Let the actual UI request settle before reloading; optimistic Following is not durable proof.
116
137
  let idleTimedOut = false;
117
138
  try {
118
- await tab.playwright.waitForLoadState({
139
+ await this.#tab.playwright.waitForLoadState({
119
140
  state: "networkidle",
120
141
  timeoutMs: 8000,
121
142
  });
@@ -127,39 +148,34 @@ export function createDOMNavigation(dependencies) {
127
148
  // must expose the target relationship before any fresh observation.
128
149
  idleTimedOut = true;
129
150
  }
130
- await tab.reload();
131
- await tab.playwright
151
+ await this.#tab.reload();
152
+ await this.#tab.playwright
132
153
  .getByRole("button", {
133
154
  name: new RegExp(`^(?:正在关注|取消关注|关注|回关) @${command.handle}$`, "i"),
134
155
  })
135
156
  .waitFor({ state: "visible", timeoutMs: 5000 });
136
157
  if (idleTimedOut)
137
- session.refreshNotice =
158
+ this.#session.refreshNotice =
138
159
  "networkidle超时,已沿真实reload与目标关系控件恢复核验";
139
160
  return;
140
161
  }
141
- async function refreshPosts(command) {
142
- session.replyBinding = null;
162
+ async handleRefreshPosts(command) {
163
+ this.#session.replyBinding = null;
143
164
  if (!command.evidenceRef ||
144
- xURL(await tab.url()).href !== xURL(command.url).href)
165
+ xURL(await this.#tab.url()).href !== xURL(command.url).href)
145
166
  throw new Error("公开核验页面不匹配");
146
- session.current = null;
147
- await tab.reload();
167
+ this.#session.current = null;
168
+ await this.#tab.reload();
148
169
  // Native observation settles the real loading transition when the tab exposes it.
149
- if (typeof tab.getAXState === "function")
150
- await tab.getAXState({ emit: false });
151
- await tab.playwright
170
+ if (typeof this.#tab.getAXState === "function")
171
+ await this.#tab.getAXState({ emit: false });
172
+ await this.#tab.playwright
152
173
  .getByRole("article")
153
174
  .first()
154
175
  .waitFor({ state: "visible", timeoutMs: 5000 });
155
176
  return;
156
177
  }
157
- return {
158
- searchSources,
159
- waitForFolder,
160
- waitForPosts,
161
- openObserved,
162
- refreshPerson,
163
- refreshPosts,
164
- };
178
+ }
179
+ export function createDOMNavigation(...args) {
180
+ return new DOMNavigation(...args);
165
181
  }
@@ -1,2 +1,7 @@
1
1
  import type { DOMDependencies } from "./browser-dom-dependencies.js";
2
- export declare function createDOMSubmission(dependencies: Pick<DOMDependencies, "submitAX" | "session" | "tab" | "observe" | "unique" | "locator">): (command: any, prepared: any, query: any, result: any) => Promise<void>;
2
+ export declare class DOMSubmission {
3
+ #private;
4
+ constructor(dependencies: Pick<DOMDependencies, "submitAX" | "session" | "tab" | "observe" | "unique" | "locator">);
5
+ finishDOMAction(command: any, prepared: any, query: any, result: any): Promise<void>;
6
+ }
7
+ export declare function createDOMSubmission(...args: ConstructorParameters<typeof DOMSubmission>): (command: any, prepared: any, query: any, result: any) => Promise<void>;
@@ -1,8 +1,23 @@
1
1
  import { xURL } from "./platform-rules.js";
2
2
  import { valueOf } from "./browser-common.js";
3
- export function createDOMSubmission(dependencies) {
4
- const { submitAX, session, tab, observe, unique, locator } = dependencies;
5
- return async function finishDOMAction(command, prepared, query, result) {
3
+ export class DOMSubmission {
4
+ #submitAX;
5
+ #session;
6
+ #tab;
7
+ #observe;
8
+ #unique;
9
+ #locator;
10
+ constructor(dependencies) {
11
+ this.finishDOMAction = this.finishDOMAction.bind(this);
12
+ const { submitAX, session, tab, observe, unique, locator } = dependencies;
13
+ this.#submitAX = submitAX;
14
+ this.#session = session;
15
+ this.#tab = tab;
16
+ this.#observe = observe;
17
+ this.#unique = unique;
18
+ this.#locator = locator;
19
+ }
20
+ async finishDOMAction(command, prepared, query, result) {
6
21
  const { loc, submittedEditor } = prepared;
7
22
  const binding = command.operation === "openReply"
8
23
  ? {
@@ -13,8 +28,9 @@ export function createDOMSubmission(dependencies) {
13
28
  }
14
29
  : null;
15
30
  let nativeSubmitIndex;
16
- if (submitAX && ["submitPost", "submitReply"].includes(command.operation)) {
17
- const native = await submitAX.observe({ mode: "composer" });
31
+ if (this.#submitAX &&
32
+ ["submitPost", "submitReply"].includes(command.operation)) {
33
+ const native = await this.#submitAX.observe({ mode: "composer" });
18
34
  const editors = native.evidence?.filter((e) => /^(?:文本输入区(?:域)?|文本区域|文本栏) /.test(e.label)) ?? [];
19
35
  const buttons = native.evidence?.filter((e) => new RegExp(command.operation === "submitReply"
20
36
  ? "^按钮 (回复|Reply)(?:,|$)"
@@ -30,9 +46,9 @@ export function createDOMSubmission(dependencies) {
30
46
  }
31
47
  if (["submitPost", "submitReply"].includes(command.operation))
32
48
  await command.beforeSubmit?.();
33
- session.current = null;
49
+ this.#session.current = null;
34
50
  if (nativeSubmitIndex !== undefined)
35
- await submitAX.act({
51
+ await this.#submitAX.act({
36
52
  index: nativeSubmitIndex,
37
53
  operation: "click",
38
54
  read: { mode: "composer" },
@@ -42,15 +58,15 @@ export function createDOMSubmission(dependencies) {
42
58
  else
43
59
  await loc.click();
44
60
  if (binding) {
45
- session.replyBinding = binding;
46
- await tab.playwright
61
+ this.#session.replyBinding = binding;
62
+ await this.#tab.playwright
47
63
  .getByRole("textbox", { name: /^(帖子文本|Post text)$/i })
48
64
  .waitFor({ state: "visible", timeoutMs: 5000 });
49
- if (xURL(await tab.url()).pathname === "/compose/post")
50
- await tab.playwright
65
+ if (xURL(await this.#tab.url()).pathname === "/compose/post")
66
+ await this.#tab.playwright
51
67
  .getByText(`回复 @${binding.author}`, { exact: true })
52
68
  .waitFor({ state: "visible", timeoutMs: 5000 });
53
- const composer = await observe({ view: "composer" });
69
+ const composer = await this.#observe({ view: "composer" });
54
70
  const quote = composer.replyQuotes?.length === 1 ? composer.replyQuotes[0] : null;
55
71
  if (!composer.replyToId &&
56
72
  !composer.needsStateCheck &&
@@ -62,11 +78,11 @@ export function createDOMSubmission(dependencies) {
62
78
  quote?.length >= 30 &&
63
79
  binding.body?.startsWith(quote)) {
64
80
  // Expand the observed quote; the unchanged full-context binding gate must still pass.
65
- const dialog = await unique(locator("dialog").filter({ visible: true }));
66
- const more = await unique(dialog.getByRole("button", { name: /^(显示更多|Show more)$/i }));
67
- session.current = null;
81
+ const dialog = await this.#unique(this.#locator("dialog").filter({ visible: true }));
82
+ const more = await this.#unique(dialog.getByRole("button", { name: /^(显示更多|Show more)$/i }));
83
+ this.#session.current = null;
68
84
  await more.click();
69
- await observe({ view: "composer" });
85
+ await this.#observe({ view: "composer" });
70
86
  }
71
87
  }
72
88
  // Inline replies keep the editor mounted. Wait for this submitted text to clear before
@@ -74,9 +90,12 @@ export function createDOMSubmission(dependencies) {
74
90
  if (submittedEditor)
75
91
  await submittedEditor.waitFor({ state: "hidden", timeoutMs: 10000 });
76
92
  if (["submitPost", "submitReply"].includes(command.operation))
77
- await tab.playwright.waitForLoadState({
93
+ await this.#tab.playwright.waitForLoadState({
78
94
  state: "networkidle",
79
95
  timeoutMs: 8000,
80
96
  });
81
- };
97
+ }
98
+ }
99
+ export function createDOMSubmission(...args) {
100
+ return new DOMSubmission(...args).finishDOMAction;
82
101
  }
@@ -1,2 +1,14 @@
1
1
  export { readXNetworkDiagnostics } from "./browser-dom-network.js";
2
- export declare function createDOMXBrowser(tab: any, { readDOM, nativeSubmit }?: any): any;
2
+ export declare class DOMXBrowser {
3
+ #private;
4
+ capabilities: (operation: any) => any;
5
+ observe: (query: any) => Promise<any>;
6
+ constructor(tab: any, { readDOM, nativeSubmit }?: any);
7
+ private handleObserve;
8
+ private locator;
9
+ private unique;
10
+ collectCreator({ handle, ...options }: import("./types.js").CreatorOptions): Promise<import("./boundary.js").BoundaryRecord>;
11
+ screenshot(): any;
12
+ act(command: any): Promise<any>;
13
+ }
14
+ export declare function createDOMXBrowser(...args: ConstructorParameters<typeof DOMXBrowser>): any;
@@ -7,29 +7,85 @@ import { createDOMNavigation } from "./browser-dom-navigation.js";
7
7
  import { createDOMEditing } from "./browser-dom-editing.js";
8
8
  import { createDOMSubmission } from "./browser-dom-submission.js";
9
9
  export { readXNetworkDiagnostics } from "./browser-dom-network.js";
10
- export function createDOMXBrowser(tab, { readDOM = readXObservation, nativeSubmit = false } = {}) {
11
- const session = {};
12
- const submitAX = nativeSubmit
13
- ? createAXHarness({
14
- getAXState: (options) => tab.getAXState(options),
15
- click: (index) => tab.click(index),
16
- })
17
- : null;
18
- async function observe(query) {
19
- session.current = null;
20
- const evaluate = tab?.playwright?.evaluate?.bind(tab.playwright);
10
+ export class DOMXBrowser {
11
+ #tab;
12
+ #readDOM;
13
+ #nativeSubmit;
14
+ #session;
15
+ #submitAX;
16
+ #navigation;
17
+ #editing;
18
+ #finishDOMAction;
19
+ capabilities;
20
+ observe;
21
+ constructor(tab, { readDOM = readXObservation, nativeSubmit = false } = {}) {
22
+ this.act = this.act.bind(this);
23
+ this.screenshot = this.screenshot.bind(this);
24
+ this.collectCreator = this.collectCreator.bind(this);
25
+ this.#tab = tab;
26
+ this.#readDOM = readDOM;
27
+ this.#nativeSubmit = nativeSubmit;
28
+ this.#session = {};
29
+ this.#submitAX = this.#nativeSubmit
30
+ ? createAXHarness({
31
+ getAXState: (options) => this.#tab.getAXState(options),
32
+ click: (index) => this.#tab.click(index),
33
+ })
34
+ : null;
35
+ this.#navigation = createDOMNavigation({
36
+ session: this.#session,
37
+ tab: this.#tab,
38
+ observe: this.handleObserve.bind(this),
39
+ });
40
+ this.#editing = createDOMEditing({
41
+ session: this.#session,
42
+ unique: this.unique.bind(this),
43
+ locator: this.locator.bind(this),
44
+ tab: this.#tab,
45
+ observe: this.handleObserve.bind(this),
46
+ });
47
+ this.#finishDOMAction = createDOMSubmission({
48
+ submitAX: this.#submitAX,
49
+ session: this.#session,
50
+ tab: this.#tab,
51
+ observe: this.handleObserve.bind(this),
52
+ unique: this.unique.bind(this),
53
+ locator: this.locator.bind(this),
54
+ });
55
+ Object.assign(this, createDOMCapabilities({
56
+ tab: this.#tab,
57
+ nativeSubmit: this.#nativeSubmit,
58
+ }));
59
+ this.observe = this.handleObserve.bind(this);
60
+ Object.assign(this, serialized({
61
+ ...createDOMCapabilities({
62
+ tab: this.#tab,
63
+ nativeSubmit: this.#nativeSubmit,
64
+ }),
65
+ observe: this.handleObserve.bind(this),
66
+ collectCreator: this.collectCreator,
67
+ screenshot: () => {
68
+ this.#session.current = null;
69
+ return this.#tab.screenshot();
70
+ },
71
+ act: this.act,
72
+ }));
73
+ }
74
+ async handleObserve(query) {
75
+ this.#session.current = null;
76
+ const evaluate = this.#tab?.playwright?.evaluate?.bind(this.#tab.playwright);
21
77
  if (!evaluate)
22
78
  throw new Error("缺playwright.evaluate能力");
23
- const result = await evaluate(readDOM, {
79
+ const result = await evaluate(this.#readDOM, {
24
80
  ...query,
25
81
  limit: query.limit ?? 1,
26
82
  });
27
83
  const raw = result;
28
84
  // This observed disclosure confirms AI provenance; preserve it without treating it as a rate/login failure.
29
85
  result.notices = (result.alerts ?? []).filter((text) => text.trim() === "检测到 AI 生成内容");
30
- if (session.refreshNotice) {
31
- result.notices.push(session.refreshNotice);
32
- session.refreshNotice = null;
86
+ if (this.#session.refreshNotice) {
87
+ result.notices.push(this.#session.refreshNotice);
88
+ this.#session.refreshNotice = null;
33
89
  }
34
90
  result.alerts = (result.alerts ?? []).filter((text) => text.trim() !== "检测到 AI 生成内容");
35
91
  if (query.view === "composer") {
@@ -37,67 +93,58 @@ export function createDOMXBrowser(tab, { readDOM = readXObservation, nativeSubmi
37
93
  raw.editors?.length === 1 ? raw.editors[0].text : null;
38
94
  if (result.inlineComposer &&
39
95
  raw.editors?.length === 1 &&
40
- session.replyBinding?.page === result.page &&
96
+ this.#session.replyBinding?.page === result.page &&
41
97
  result.page.match(/\/status\/(\d+)$/)?.[1] ===
42
- session.replyBinding.postId)
43
- result.replyToId = session.replyBinding.postId;
98
+ this.#session.replyBinding.postId)
99
+ result.replyToId = this.#session.replyBinding.postId;
44
100
  if (!result.inlineComposer &&
45
101
  raw.editors?.length === 1 &&
46
- session.replyBinding?.body &&
47
- session.replyBinding?.author &&
102
+ this.#session.replyBinding?.body &&
103
+ this.#session.replyBinding?.author &&
48
104
  result.replyContexts?.length === 1 &&
49
- (result.replyContexts[0].includes(session.replyBinding.body) ||
105
+ (result.replyContexts[0].includes(this.#session.replyBinding.body) ||
50
106
  (result.replyQuotes?.length === 1 &&
51
107
  result.replyQuotes[0].length >= 80 &&
52
- session.replyBinding.body.startsWith(result.replyQuotes[0]))) &&
53
- result.replyContexts[0].includes(`回复 @${session.replyBinding.author}`))
54
- result.replyToId = session.replyBinding.postId;
108
+ this.#session.replyBinding.body.startsWith(result.replyQuotes[0]))) &&
109
+ result.replyContexts[0].includes(`回复 @${this.#session.replyBinding.author}`))
110
+ result.replyToId = this.#session.replyBinding.postId;
55
111
  }
56
- session.current = { query, result };
112
+ this.#session.current = { query, result };
57
113
  return result;
58
114
  }
59
- const locator = (role, label = undefined) => tab.playwright.getByRole(role, label ? { name: label, exact: true } : {});
60
- const unique = async (loc) => {
115
+ locator(role, label = undefined) {
116
+ return this.#tab.playwright.getByRole(role, label ? { name: label, exact: true } : {});
117
+ }
118
+ async unique(loc) {
61
119
  if ((await loc.count()) !== 1)
62
120
  throw new Error("DOM语义控件不唯一");
63
121
  return loc;
64
- };
65
- const navigation = createDOMNavigation({ session, tab, observe });
66
- const editing = createDOMEditing({ session, unique, locator, tab, observe });
67
- const finishDOMAction = createDOMSubmission({
68
- submitAX,
69
- session,
70
- tab,
71
- observe,
72
- unique,
73
- locator,
74
- });
75
- return serialized({
76
- ...createDOMCapabilities({ tab, nativeSubmit }),
77
- observe,
78
- async collectCreator({ handle, ...options }) {
79
- session.current = null;
80
- return collectCreatorDOM(tab, handle, options);
81
- },
82
- screenshot: () => {
83
- session.current = null;
84
- return tab.screenshot();
85
- },
86
- async act(command) {
87
- if (Object.hasOwn(navigation, command.operation))
88
- return navigation[command.operation](command);
89
- if (!session.current)
90
- throw new Error("DOM语义操作必须先observe");
91
- const { query, result } = session.current;
92
- const handler = Object.hasOwn(editing, command.operation)
93
- ? editing[command.operation]
94
- : undefined;
95
- if (!handler)
96
- throw new Error("未封装的DOM动作");
97
- const prepared = await handler(command);
98
- if (prepared.completed)
99
- return prepared.value;
100
- return finishDOMAction(command, prepared, query, result);
101
- },
102
- });
122
+ }
123
+ async collectCreator({ handle, ...options }) {
124
+ this.#session.current = null;
125
+ return collectCreatorDOM(this.#tab, handle, options);
126
+ }
127
+ screenshot() {
128
+ this.#session.current = null;
129
+ return this.#tab.screenshot();
130
+ }
131
+ async act(command) {
132
+ if (Object.hasOwn(this.#navigation, command.operation))
133
+ return this.#navigation[command.operation](command);
134
+ if (!this.#session.current)
135
+ throw new Error("DOM语义操作必须先observe");
136
+ const { query, result } = this.#session.current;
137
+ const handler = Object.hasOwn(this.#editing, command.operation)
138
+ ? this.#editing[command.operation]
139
+ : undefined;
140
+ if (!handler)
141
+ throw new Error("未封装的DOM动作");
142
+ const prepared = await handler(command);
143
+ if (prepared.completed)
144
+ return prepared.value;
145
+ return this.#finishDOMAction(command, prepared, query, result);
146
+ }
147
+ }
148
+ export function createDOMXBrowser(...args) {
149
+ return new DOMXBrowser(...args);
103
150
  }
@@ -1 +1,14 @@
1
- export declare function createNativeXBrowser(app: any): any;
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;