@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,171 @@
1
+ import { xURL, sameHandle, sameText, samePublishedText, } from "./platform-rules.js";
2
+ export function createPlatformWriting(dependencies) {
3
+ const { observed, browser, followStage, readPerson, open, clock, readPost } = dependencies;
4
+ async function compose(action) {
5
+ let value = await observed({ view: "composer" });
6
+ if (value.needsStateCheck ||
7
+ value.alerts?.length ||
8
+ typeof value.editorText !== "string" ||
9
+ value.editorText.trim() !== "" ||
10
+ (action.kind === "reply" && value.replyToId !== action.postId))
11
+ throw new Error("编辑器不为空或回复上下文未绑定");
12
+ if (action.assets?.length)
13
+ await browser.act({ operation: "attachAssets", assets: action.assets });
14
+ await browser.act({ operation: "fillComposer", text: action.text });
15
+ value = await observed({ view: "composer" });
16
+ if (!sameText(value.editorText, action.text) ||
17
+ value.needsStateCheck ||
18
+ value.alerts?.length ||
19
+ (action.kind === "reply" && value.replyToId !== action.postId) ||
20
+ (action.accountHandle &&
21
+ !sameHandle(value.accountHandle, action.accountHandle)))
22
+ throw new Error(`提交前账号/正文/目标未核实 ${JSON.stringify({ textMatches: sameText(value.editorText, action.text), needsStateCheck: value.needsStateCheck, alerts: value.alerts, accountHandle: value.accountHandle, replyToId: value.replyToId })}`);
23
+ return browser.act({
24
+ operation: action.kind === "reply" ? "submitReply" : "submitPost",
25
+ beforeSubmit: action.beforeSubmit,
26
+ ...(action.activation ? { activation: action.activation } : {}),
27
+ });
28
+ }
29
+ async function findSubmission(action) {
30
+ if (action.kind === "follow") {
31
+ let value = await followStage(action, "verify-read", () => readPerson(action));
32
+ if (!value.needsStateCheck) {
33
+ const absent = browser.capabilities?.("refreshPerson") ?? [
34
+ "refreshPerson",
35
+ ];
36
+ if (absent.length)
37
+ return {
38
+ ...value,
39
+ verified: false,
40
+ missing: absent,
41
+ needsStateCheck: true,
42
+ };
43
+ await followStage(action, "verify-refresh", () => browser.act({
44
+ operation: "refreshPerson",
45
+ handle: action.handle,
46
+ url: action.url,
47
+ evidenceRef: action.evidenceRef,
48
+ }));
49
+ value = await followStage(action, "verify-after-refresh", () => readPerson(action));
50
+ }
51
+ return {
52
+ ...value,
53
+ verified: !value.needsStateCheck &&
54
+ !value.alerts?.length &&
55
+ value.relationship === "following" &&
56
+ (!action.accountHandle ||
57
+ sameHandle(value.accountHandle, action.accountHandle)),
58
+ publicId: null,
59
+ };
60
+ }
61
+ // A bounded window cannot prove absence. Reply receipts use the observed target conversation.
62
+ const observedReceipt = ["reply", "original"].includes(action.kind) &&
63
+ action.observedPublicURL &&
64
+ action.observedPublicEvidenceRef;
65
+ const receiptPath = observedReceipt
66
+ ? xURL(action.observedPublicURL).pathname.split("/")
67
+ : [];
68
+ if (observedReceipt &&
69
+ (receiptPath.length !== 4 ||
70
+ !sameHandle(receiptPath[1], action.accountHandle) ||
71
+ receiptPath[2] !== "status" ||
72
+ !/^[0-9]+$/.test(receiptPath[3])))
73
+ throw new Error("已观察回复回执须属于原账号");
74
+ const receiptTarget = observedReceipt
75
+ ? {
76
+ url: action.observedPublicURL,
77
+ evidenceRef: action.observedPublicEvidenceRef,
78
+ }
79
+ : action.kind === "reply" && action.url && action.evidenceRef
80
+ ? { url: action.url, evidenceRef: action.evidenceRef }
81
+ : { url: action.accountURL, evidenceRef: action.accountEvidenceRef };
82
+ let value = await open(receiptTarget, { view: "posts", limit: 5 });
83
+ if (!(action.kind === "original" && observedReceipt) &&
84
+ !value.needsStateCheck &&
85
+ !(browser.capabilities?.("refreshPosts") ?? ["refreshPosts"]).length) {
86
+ await browser.act({
87
+ operation: "refreshPosts",
88
+ url: receiptTarget.url,
89
+ evidenceRef: receiptTarget.evidenceRef,
90
+ });
91
+ value = await observed({ view: "posts", limit: 5 });
92
+ }
93
+ const matchesContent = (post) => sameHandle(post.author, action.accountHandle) &&
94
+ samePublishedText(post.body, action.text) &&
95
+ /^\d+$/.test(post.id ?? "") &&
96
+ (!action.assets?.length ||
97
+ action.assets.every((asset) => post.images?.some((image) => image.alt === asset.alt))) &&
98
+ !(action.beforeIds ?? []).includes(post.id) &&
99
+ Number.isFinite(Date.parse(post.datetime)) &&
100
+ Date.parse(post.datetime) >= Date.parse(action.intentAt) &&
101
+ Date.parse(post.datetime) <= clock() + 5000 &&
102
+ !post.needsExpansion;
103
+ let matches = (value.posts ?? []).filter(matchesContent);
104
+ if (action.kind === "reply" &&
105
+ matches.length === 1 &&
106
+ matches[0].replyToId == null &&
107
+ matches[0].primary) {
108
+ // The candidate URL comes from this fresh public observation, not from a guessed ID path.
109
+ const candidate = matches[0];
110
+ const url = xURL(new URL(candidate.primary, xURL(value.page).origin).href);
111
+ if (url.pathname.toLowerCase() ===
112
+ `/${action.accountHandle}/status/${candidate.id}`.toLowerCase()) {
113
+ value = await open({
114
+ url: url.href,
115
+ evidenceRef: {
116
+ page: value.page,
117
+ observedAt: value.observedAt,
118
+ publicId: candidate.id,
119
+ primary: candidate.primary,
120
+ },
121
+ }, {
122
+ view: "posts",
123
+ targetId: candidate.id,
124
+ replyTargetId: action.postId,
125
+ });
126
+ matches = (value.posts ?? []).filter(matchesContent);
127
+ }
128
+ }
129
+ if (action.kind === "reply")
130
+ matches = matches.filter((post) => post.replyToId === action.postId);
131
+ return {
132
+ ...value,
133
+ verified: !value.needsStateCheck && !value.alerts?.length && matches.length === 1,
134
+ publicId: matches.length === 1 ? matches[0].id : null,
135
+ missing: matches.length === 1 ? [] : ["unique-public-submission-unconfirmed"],
136
+ };
137
+ }
138
+ async function submitFollow(action) {
139
+ const value = await followStage(action, "submit-preflight-read", () => readPerson(action));
140
+ if (value.needsStateCheck ||
141
+ value.alerts?.length ||
142
+ !["not-following", "follows-you"].includes(value.relationship) ||
143
+ (action.accountHandle &&
144
+ !sameHandle(value.accountHandle, action.accountHandle)))
145
+ throw new Error("fresh账号/人物关系不允许关注");
146
+ await followStage(action, "submit-guard", () => action.beforeSubmit?.());
147
+ // A returned click is not a receipt from X; durable success still requires fresh verification.
148
+ const clicked = await followStage(action, "click", () => browser.act({ operation: "follow", handle: action.handle }));
149
+ try {
150
+ await followStage(action, "click-ui-observation", () => observed({ view: "person", handle: action.handle }));
151
+ }
152
+ catch {
153
+ /* The click may have committed; retain diagnostic failure and use the original fresh receipt path. */
154
+ }
155
+ return clicked;
156
+ }
157
+ async function submitPost(action) {
158
+ await open({ url: action.composeURL, evidenceRef: action.composeEvidenceRef }, { view: "composer" });
159
+ return compose(action);
160
+ }
161
+ async function submitReply(action) {
162
+ const value = await readPost(action);
163
+ if (value.needsStateCheck ||
164
+ value.alerts?.length ||
165
+ !(value.posts ?? []).some((p) => p.id === action.postId))
166
+ throw new Error("回复主帖身份未核实");
167
+ await browser.act({ operation: "openReply", postId: action.postId });
168
+ return compose(action);
169
+ }
170
+ return { compose, findSubmission, submitFollow, submitPost, submitReply };
171
+ }
@@ -1,2 +1,2 @@
1
- export declare function xURL(value: any): any;
1
+ export { xURL } from "./platform-rules.js";
2
2
  export declare function createXPlatform(browser: any, { clock }?: any): any;