@nextclaw/x-harness 0.1.0 → 0.1.1

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 (94) hide show
  1. package/README.md +2 -2
  2. package/cli/main.js +23 -11
  3. package/dist/api.js +53 -1
  4. package/dist/browser-dom-capabilities.d.ts +4 -0
  5. package/dist/browser-dom-capabilities.js +110 -0
  6. package/dist/browser-dom-dependencies.d.ts +16 -0
  7. package/dist/browser-dom-editing.d.ts +148 -0
  8. package/dist/browser-dom-editing.js +386 -0
  9. package/dist/browser-dom-navigation.d.ts +9 -0
  10. package/dist/browser-dom-navigation.js +165 -0
  11. package/dist/browser-dom-network.d.ts +1 -0
  12. package/dist/browser-dom-network.js +128 -0
  13. package/dist/browser-dom-submission.d.ts +2 -0
  14. package/dist/browser-dom-submission.js +82 -0
  15. package/dist/browser-dom.d.ts +1 -1
  16. package/dist/browser-dom.js +51 -802
  17. package/dist/browser-native.js +32 -28
  18. package/dist/browser.js +6 -2
  19. package/dist/creator.js +1 -1
  20. package/dist/growth-batch.d.ts +5 -0
  21. package/dist/growth-batch.js +168 -0
  22. package/dist/growth-constants.d.ts +5 -0
  23. package/dist/growth-constants.js +13 -0
  24. package/dist/growth-dependencies.d.ts +33 -0
  25. package/dist/growth-execution.d.ts +11 -0
  26. package/dist/growth-execution.js +315 -0
  27. package/dist/growth-parallel.d.ts +4 -0
  28. package/dist/growth-parallel.js +301 -0
  29. package/dist/growth-plan.d.ts +4 -0
  30. package/dist/growth-plan.js +207 -0
  31. package/dist/growth-policy.d.ts +9 -0
  32. package/dist/growth-policy.js +181 -0
  33. package/dist/growth-publishing.d.ts +5 -0
  34. package/dist/growth-publishing.js +240 -0
  35. package/dist/growth-reading.d.ts +4 -0
  36. package/dist/growth-reading.js +135 -0
  37. package/dist/growth-recovery.d.ts +6 -0
  38. package/dist/growth-recovery.js +158 -0
  39. package/dist/growth-state.d.ts +8 -0
  40. package/dist/growth-state.js +183 -0
  41. package/dist/growth-tasks.d.ts +17 -0
  42. package/dist/growth-tasks.js +101 -0
  43. package/dist/growth.d.ts +1 -0
  44. package/dist/growth.js +122 -1902
  45. package/dist/platform-capabilities.d.ts +4 -0
  46. package/dist/platform-capabilities.js +19 -0
  47. package/dist/platform-collections.d.ts +7 -0
  48. package/dist/platform-collections.js +223 -0
  49. package/dist/platform-dependencies.d.ts +43 -0
  50. package/dist/platform-diagnostics.d.ts +9 -0
  51. package/dist/platform-diagnostics.js +51 -0
  52. package/dist/platform-observation.d.ts +8 -0
  53. package/dist/platform-observation.js +75 -0
  54. package/dist/platform-rules.d.ts +17 -0
  55. package/dist/platform-rules.js +41 -0
  56. package/dist/platform-targets.d.ts +7 -0
  57. package/dist/platform-targets.js +129 -0
  58. package/dist/platform-writing.d.ts +8 -0
  59. package/dist/platform-writing.js +171 -0
  60. package/dist/platform.d.ts +1 -1
  61. package/dist/platform.js +56 -677
  62. package/dist/readers-actions.d.ts +2 -0
  63. package/dist/readers-actions.js +66 -0
  64. package/dist/readers-ax-host.d.ts +2 -0
  65. package/dist/readers-ax-host.js +125 -0
  66. package/dist/readers-ax-parser.d.ts +1 -0
  67. package/dist/readers-ax-parser.js +69 -0
  68. package/dist/readers-ax-program.d.ts +1 -0
  69. package/dist/readers-ax-tree.d.ts +19 -0
  70. package/dist/readers-ax-tree.js +110 -0
  71. package/dist/readers-ax-views.d.ts +87 -0
  72. package/dist/readers-ax-views.js +247 -0
  73. package/dist/readers-cli.d.ts +6 -0
  74. package/dist/readers-composer.d.ts +2 -0
  75. package/dist/readers-composer.js +131 -0
  76. package/dist/readers-navigation.d.ts +2 -0
  77. package/dist/readers-navigation.js +66 -0
  78. package/dist/readers-posts.d.ts +2 -0
  79. package/dist/readers-posts.js +141 -0
  80. package/dist/readers-profile.d.ts +3 -0
  81. package/dist/readers-profile.js +157 -0
  82. package/dist/readers.d.ts +9 -20
  83. package/dist/results.js +1 -1
  84. package/dist/round-state.js +2 -2
  85. package/dist/types.d.ts +10 -0
  86. package/generated/observation.mjs +2 -1
  87. package/package.json +1 -1
  88. package/skill/SKILL.md +17 -36
  89. package/skill/references/api.md +7 -34
  90. package/skill/references/reads.md +23 -0
  91. package/skill/references/recovery.md +25 -0
  92. package/skill/references/setup.md +23 -0
  93. package/skill/references/writes.md +51 -0
  94. package/dist/readers.js +0 -1045
@@ -0,0 +1,128 @@
1
+ // Read-only transport diagnostics through the same real Chrome tab capability.
2
+ // Never return credential headers, cookies or bodies; only numeric rate metadata is allowed.
3
+ export async function readXNetworkDiagnostics(tab, { afterSequence = 0, startObserving = false } = {}) {
4
+ if (typeof tab?.capabilities?.get !== "function")
5
+ throw new Error("缺真实Chrome诊断能力");
6
+ const cdp = await tab.capabilities.get("cdp");
7
+ if (typeof cdp?.readEvents !== "function")
8
+ throw new Error("缺只读网络事件能力");
9
+ if (startObserving)
10
+ await cdp.send("Network.enable");
11
+ const result = await cdp.readEvents({
12
+ afterSequence,
13
+ limit: 1000,
14
+ methods: [
15
+ "Network.requestWillBeSent",
16
+ "Network.responseReceived",
17
+ "Network.loadingFailed",
18
+ ],
19
+ });
20
+ const traffic = new Map();
21
+ const times = [];
22
+ for (const event of result.events) {
23
+ const p = event.params ?? {};
24
+ let u;
25
+ try {
26
+ u = new URL(p.response?.url ?? p.request?.url);
27
+ }
28
+ catch {
29
+ continue;
30
+ }
31
+ if (!((u.hostname === "x.com" && u.pathname.startsWith("/i/api/")) ||
32
+ u.hostname === "api.x.com"))
33
+ continue;
34
+ const location = u.origin + u.pathname;
35
+ const row = traffic.get(location) ?? {
36
+ location,
37
+ requests: 0,
38
+ responses: 0,
39
+ statuses: {},
40
+ };
41
+ if (event.method === "Network.requestWillBeSent")
42
+ row.requests++;
43
+ if (event.method === "Network.responseReceived") {
44
+ row.responses++;
45
+ const status = p.response?.status;
46
+ if (Number.isInteger(status))
47
+ row.statuses[status] = (row.statuses[status] ?? 0) + 1;
48
+ }
49
+ if (Number.isFinite(p.timestamp))
50
+ times.push(p.timestamp);
51
+ traffic.set(location, row);
52
+ }
53
+ const rates = (headers = {}) => {
54
+ const allowed = {};
55
+ for (const [key, value] of Object.entries(headers)) {
56
+ const name = key.toLowerCase();
57
+ if (![
58
+ "x-rate-limit-limit",
59
+ "x-rate-limit-remaining",
60
+ "x-rate-limit-reset",
61
+ "retry-after",
62
+ ].includes(name))
63
+ continue;
64
+ if (!/^\d{1,12}$/.test(String(value).trim()))
65
+ continue;
66
+ allowed[name] = Number(value);
67
+ }
68
+ const out = {};
69
+ if (allowed["x-rate-limit-limit"] !== undefined)
70
+ out.limit = allowed["x-rate-limit-limit"];
71
+ if (allowed["x-rate-limit-remaining"] !== undefined)
72
+ out.remaining = allowed["x-rate-limit-remaining"];
73
+ if (allowed["x-rate-limit-reset"] !== undefined) {
74
+ const date = new Date(allowed["x-rate-limit-reset"] * 1000);
75
+ if (Number.isFinite(date.getTime()))
76
+ out.resetAt = date.toISOString();
77
+ }
78
+ if (allowed["retry-after"] !== undefined)
79
+ out.retryAfterSeconds = allowed["retry-after"];
80
+ return Object.keys(out).length ? out : undefined;
81
+ };
82
+ return {
83
+ cursor: result.cursor,
84
+ hasMore: result.hasMore,
85
+ truncated: result.truncated,
86
+ traffic: {
87
+ complete: !result.truncated && !result.hasMore,
88
+ requests: [...traffic.values()].reduce((n, r) => n + r.requests, 0),
89
+ responses: [...traffic.values()].reduce((n, r) => n + r.responses, 0),
90
+ durationSeconds: times.length > 1 ? Math.max(...times) - Math.min(...times) : null,
91
+ endpointCount: traffic.size,
92
+ endpoints: [...traffic.values()]
93
+ .sort((a, b) => b.requests - a.requests)
94
+ .slice(0, 10),
95
+ },
96
+ events: result.events
97
+ .map((e) => {
98
+ const p = e.params ?? {};
99
+ const response = p.response ?? {};
100
+ let location;
101
+ try {
102
+ const u = new URL(response.url ?? p.request?.url);
103
+ location = ["http:", "https:"].includes(u.protocol)
104
+ ? u.origin + u.pathname
105
+ : null;
106
+ }
107
+ catch {
108
+ location = null;
109
+ }
110
+ return {
111
+ method: e.method,
112
+ requestId: p.requestId,
113
+ location,
114
+ requestMethod: p.request?.method,
115
+ status: response.status,
116
+ errorText: p.errorText,
117
+ canceled: p.canceled,
118
+ timestamp: Number.isFinite(p.timestamp) ? p.timestamp : undefined,
119
+ rateLimit: rates(response.headers),
120
+ };
121
+ })
122
+ .filter((e) => e.method === "Network.loadingFailed" ||
123
+ (e.location &&
124
+ (e.status >= 400 ||
125
+ e.requestMethod === "POST" ||
126
+ /\/(EditTweet|CreateTweet)$/.test(e.location)))),
127
+ };
128
+ }
@@ -0,0 +1,2 @@
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>;
@@ -0,0 +1,82 @@
1
+ import { xURL } from "./platform-rules.js";
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) {
6
+ const { loc, submittedEditor } = prepared;
7
+ const binding = command.operation === "openReply"
8
+ ? {
9
+ page: result.page,
10
+ postId: command.postId,
11
+ body: result.posts[0].body,
12
+ author: result.posts[0].author,
13
+ }
14
+ : null;
15
+ let nativeSubmitIndex;
16
+ if (submitAX && ["submitPost", "submitReply"].includes(command.operation)) {
17
+ const native = await submitAX.observe({ mode: "composer" });
18
+ const editors = native.evidence?.filter((e) => /^(?:文本输入区(?:域)?|文本区域|文本栏) /.test(e.label)) ?? [];
19
+ const buttons = native.evidence?.filter((e) => new RegExp(command.operation === "submitReply"
20
+ ? "^按钮 (回复|Reply)(?:,|$)"
21
+ : "^按钮 (发帖|发布|Post)(?:,|$)", "i").test(e.label)) ?? [];
22
+ if (native.needsStateCheck ||
23
+ native.alerts?.length ||
24
+ xURL(native.page).href !== xURL(result.page).href ||
25
+ editors.length !== 1 ||
26
+ valueOf(editors[0].label)?.trim() !== result.editorText.trim() ||
27
+ buttons.length !== 1)
28
+ throw new Error("原生提交缺同页、全文及唯一按钮证据");
29
+ nativeSubmitIndex = buttons[0].index;
30
+ }
31
+ if (["submitPost", "submitReply"].includes(command.operation))
32
+ await command.beforeSubmit?.();
33
+ session.current = null;
34
+ if (nativeSubmitIndex !== undefined)
35
+ await submitAX.act({
36
+ index: nativeSubmitIndex,
37
+ operation: "click",
38
+ read: { mode: "composer" },
39
+ });
40
+ else if (command.activation === "keyboard")
41
+ await loc.press("Enter");
42
+ else
43
+ await loc.click();
44
+ if (binding) {
45
+ session.replyBinding = binding;
46
+ await tab.playwright
47
+ .getByRole("textbox", { name: /^(帖子文本|Post text)$/i })
48
+ .waitFor({ state: "visible", timeoutMs: 5000 });
49
+ if (xURL(await tab.url()).pathname === "/compose/post")
50
+ await tab.playwright
51
+ .getByText(`回复 @${binding.author}`, { exact: true })
52
+ .waitFor({ state: "visible", timeoutMs: 5000 });
53
+ const composer = await observe({ view: "composer" });
54
+ const quote = composer.replyQuotes?.length === 1 ? composer.replyQuotes[0] : null;
55
+ if (!composer.replyToId &&
56
+ !composer.needsStateCheck &&
57
+ !composer.alerts?.length &&
58
+ composer.editorText?.trim() === "" &&
59
+ composer.replyContexts?.length === 1 &&
60
+ composer.replyContexts[0].includes(`回复 @${binding.author}`) &&
61
+ /显示更多|Show more/.test(composer.replyContexts[0]) &&
62
+ quote?.length >= 30 &&
63
+ binding.body?.startsWith(quote)) {
64
+ // 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;
68
+ await more.click();
69
+ await observe({ view: "composer" });
70
+ }
71
+ }
72
+ // Inline replies keep the editor mounted. Wait for this submitted text to clear before
73
+ // receipt navigation/reload can interrupt the actual submission request.
74
+ if (submittedEditor)
75
+ await submittedEditor.waitFor({ state: "hidden", timeoutMs: 10000 });
76
+ if (["submitPost", "submitReply"].includes(command.operation))
77
+ await tab.playwright.waitForLoadState({
78
+ state: "networkidle",
79
+ timeoutMs: 8000,
80
+ });
81
+ };
82
+ }
@@ -1,2 +1,2 @@
1
+ export { readXNetworkDiagnostics } from "./browser-dom-network.js";
1
2
  export declare function createDOMXBrowser(tab: any, { readDOM, nativeSubmit }?: any): any;
2
- export declare function readXNetworkDiagnostics(tab: any, { afterSequence, startObserving }?: any): Promise<any>;