@nextclaw/x-harness 0.2.43 → 0.2.45
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.d.ts +1 -0
- package/dist/browser-dom-capabilities.js +22 -0
- package/dist/browser-dom-dependencies.d.ts +1 -0
- package/dist/browser-dom-editing.d.ts +2 -0
- package/dist/browser-dom-editing.js +40 -0
- package/dist/browser-dom.js +6 -0
- package/dist/growth-policy.js +5 -0
- package/dist/growth-recovery.js +6 -2
- package/dist/platform-capabilities.js +4 -0
- package/dist/platform-writing.d.ts +3 -0
- package/dist/platform-writing.js +49 -11
- package/dist/readers-composer.js +9 -0
- package/dist/types.d.ts +1 -0
- package/generated/observation.mjs +9 -0
- package/package.json +2 -2
- package/skill/references/quotes.md +5 -0
- package/skill/references/writes.md +42 -12
package/SOURCE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Corresponding source
|
|
2
2
|
|
|
3
|
-
Version 0.2.
|
|
3
|
+
Version 0.2.45, commit 52258a7e88c8ebdab05e3981d37ec78becabffe6.
|
|
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/52258a7e88c8ebdab05e3981d37ec78becabffe6
|
|
6
6
|
|
|
7
7
|
Clone this revision, run npm ci, npm run bootstrap and npm run release:build.
|
|
8
8
|
|
|
@@ -89,6 +89,8 @@ export class DOMCapabilities {
|
|
|
89
89
|
"press",
|
|
90
90
|
]));
|
|
91
91
|
}
|
|
92
|
+
if (operation === "openQuoteComposer")
|
|
93
|
+
return this.quoteCapabilities(base);
|
|
92
94
|
if (operation === "attachAssets")
|
|
93
95
|
base.push(...missingMethods(this.#tab?.playwright, [
|
|
94
96
|
"waitForEvent",
|
|
@@ -99,6 +101,26 @@ export class DOMCapabilities {
|
|
|
99
101
|
base.push(...missingMethods(this.#tab, ["getAXState", "click"]));
|
|
100
102
|
return base;
|
|
101
103
|
}
|
|
104
|
+
quoteCapabilities(base) {
|
|
105
|
+
base.push(...missingMethods(this.#tab?.playwright, ["getByTestId"]));
|
|
106
|
+
if (typeof this.#tab?.playwright?.getByTestId === "function")
|
|
107
|
+
base.push(...missingMethods(this.#tab.playwright.getByTestId("attachments"), [
|
|
108
|
+
"getByTestId",
|
|
109
|
+
"waitFor",
|
|
110
|
+
]));
|
|
111
|
+
base.push(...missingMethods(this.#tab?.playwright, ["locator", "getByRole"]));
|
|
112
|
+
if (typeof this.#tab?.playwright?.getByRole === "function") {
|
|
113
|
+
base.push(...missingMethods(this.#tab.playwright.getByRole("article"), [
|
|
114
|
+
"filter",
|
|
115
|
+
"getByRole",
|
|
116
|
+
]));
|
|
117
|
+
base.push(...missingMethods(this.#tab.playwright.getByRole("button"), [
|
|
118
|
+
"press",
|
|
119
|
+
"waitFor",
|
|
120
|
+
]));
|
|
121
|
+
}
|
|
122
|
+
return base;
|
|
123
|
+
}
|
|
102
124
|
creatorCapabilities(base, operation) {
|
|
103
125
|
base.push(...missingMethods(this.#tab, [
|
|
104
126
|
"url",
|
|
@@ -126,6 +126,7 @@ export declare class DOMEditing {
|
|
|
126
126
|
submittedEditor: any;
|
|
127
127
|
}>;
|
|
128
128
|
likePost: (command: any) => Promise<any>;
|
|
129
|
+
openQuoteComposer: (command: any) => Promise<any>;
|
|
129
130
|
openReply: (command: any) => Promise<{
|
|
130
131
|
completed: boolean;
|
|
131
132
|
loc: any;
|
|
@@ -165,6 +166,7 @@ export declare class DOMEditing {
|
|
|
165
166
|
private handleAttachAssets;
|
|
166
167
|
private handleFollow;
|
|
167
168
|
private handleLikePost;
|
|
169
|
+
private handleOpenQuoteComposer;
|
|
168
170
|
private handleOpenReply;
|
|
169
171
|
private handleFillComposer;
|
|
170
172
|
private handleSubmitPost;
|
|
@@ -19,6 +19,7 @@ export class DOMEditing {
|
|
|
19
19
|
attachAssets;
|
|
20
20
|
follow;
|
|
21
21
|
likePost;
|
|
22
|
+
openQuoteComposer;
|
|
22
23
|
openReply;
|
|
23
24
|
fillComposer;
|
|
24
25
|
submitPost;
|
|
@@ -44,6 +45,7 @@ export class DOMEditing {
|
|
|
44
45
|
this.attachAssets = this.handleAttachAssets.bind(this);
|
|
45
46
|
this.follow = this.handleFollow.bind(this);
|
|
46
47
|
this.likePost = this.handleLikePost.bind(this);
|
|
48
|
+
this.openQuoteComposer = this.handleOpenQuoteComposer.bind(this);
|
|
47
49
|
this.openReply = this.handleOpenReply.bind(this);
|
|
48
50
|
this.fillComposer = this.handleFillComposer.bind(this);
|
|
49
51
|
this.submitPost = this.handleSubmitPost.bind(this);
|
|
@@ -398,6 +400,44 @@ export class DOMEditing {
|
|
|
398
400
|
},
|
|
399
401
|
};
|
|
400
402
|
}
|
|
403
|
+
async handleOpenQuoteComposer(command) {
|
|
404
|
+
const { query, result } = this.#session.current;
|
|
405
|
+
this.#session.quoteBinding = null;
|
|
406
|
+
if (query.targetId !== command.postId ||
|
|
407
|
+
result.needsStateCheck ||
|
|
408
|
+
result.alerts?.length ||
|
|
409
|
+
result.posts?.length !== 1 ||
|
|
410
|
+
result.posts[0].id !== command.postId)
|
|
411
|
+
throw Error("unique-quote-source-required");
|
|
412
|
+
const post = result.posts[0];
|
|
413
|
+
const buttons = post.buttons.filter((b) => /转帖|Repost|Retweet/i.test(b.label));
|
|
414
|
+
if (buttons.length !== 1 ||
|
|
415
|
+
!post.body?.trim() ||
|
|
416
|
+
!post.author ||
|
|
417
|
+
post.primary?.match(/\/status\/(\d+)$/)?.[1] !== command.postId)
|
|
418
|
+
throw Error("quote-source-controls-unconfirmed");
|
|
419
|
+
const article = await this.#unique(this.#locator("article").filter({
|
|
420
|
+
has: this.#tab.playwright.locator(`a[href=${JSON.stringify(post.primary)}]`),
|
|
421
|
+
visible: true,
|
|
422
|
+
}));
|
|
423
|
+
await (await this.#unique(article.getByRole("button", { name: buttons[0].label, exact: true }))).press("Enter");
|
|
424
|
+
const menu = this.#tab.playwright.getByRole("menuitem", {
|
|
425
|
+
name: /^(引用|Quote)$/,
|
|
426
|
+
});
|
|
427
|
+
await menu.waitFor({ state: "visible", timeoutMs: 2000 });
|
|
428
|
+
await (await this.#unique(menu)).press("Enter");
|
|
429
|
+
await this.#tab.playwright
|
|
430
|
+
.getByTestId("attachments")
|
|
431
|
+
.getByTestId("tweetText")
|
|
432
|
+
.waitFor({ state: "visible", timeoutMs: 2000 });
|
|
433
|
+
this.#session.quoteBinding = {
|
|
434
|
+
postId: post.id,
|
|
435
|
+
author: post.author,
|
|
436
|
+
body: post.body,
|
|
437
|
+
};
|
|
438
|
+
this.#session.current = null;
|
|
439
|
+
return { completed: true };
|
|
440
|
+
}
|
|
401
441
|
async handleOpenReply(command) {
|
|
402
442
|
const { query, result } = this.#session.current;
|
|
403
443
|
let loc, submittedEditor;
|
package/dist/browser-dom.js
CHANGED
|
@@ -89,6 +89,12 @@ export class DOMXBrowser {
|
|
|
89
89
|
}
|
|
90
90
|
result.alerts = (result.alerts ?? []).filter((text) => text.trim() !== "检测到 AI 生成内容");
|
|
91
91
|
if (query.view === "composer") {
|
|
92
|
+
if (this.#session.quoteBinding && raw.quoteContexts?.length === 1) {
|
|
93
|
+
const q = raw.quoteContexts[0], b = this.#session.quoteBinding;
|
|
94
|
+
if (q.author?.toLowerCase() === b.author?.toLowerCase() &&
|
|
95
|
+
q.body?.startsWith(b.body))
|
|
96
|
+
result.quoteSourceId = b.postId;
|
|
97
|
+
}
|
|
92
98
|
result.editorText =
|
|
93
99
|
raw.editors?.length === 1 ? raw.editors[0].text : null;
|
|
94
100
|
if (result.inlineComposer &&
|
package/dist/growth-policy.js
CHANGED
|
@@ -173,6 +173,11 @@ export class GrowthPolicy {
|
|
|
173
173
|
action.quality.draftFingerprint !==
|
|
174
174
|
this.#store.fingerprint(action.draftPath)))
|
|
175
175
|
return "draft-changed-since-quality-review";
|
|
176
|
+
if (action.quoteTarget &&
|
|
177
|
+
(!/^\d+$/.test(action.quoteTarget.postId ?? "") ||
|
|
178
|
+
!this.#evidenceExists(action.quoteTarget.evidenceRef) ||
|
|
179
|
+
xURL(action.quoteTarget.url).pathname.match(/\/status\/(\d+)$/)?.[1] !== action.quoteTarget.postId))
|
|
180
|
+
return "quote-target-proof-missing";
|
|
176
181
|
const scheduleReason = new DraftSchedule().reason(action, this.#clock());
|
|
177
182
|
if (scheduleReason)
|
|
178
183
|
return scheduleReason;
|
package/dist/growth-recovery.js
CHANGED
|
@@ -49,7 +49,8 @@ export class GrowthRecovery {
|
|
|
49
49
|
url.pathname.toLowerCase() !==
|
|
50
50
|
`/${action.accountHandle}/status/${postId}`.toLowerCase())
|
|
51
51
|
throw new Error("public-account-mismatch");
|
|
52
|
-
const resolveQuote =
|
|
52
|
+
const resolveQuote = Boolean(action.quoteTarget) ||
|
|
53
|
+
/\nhttps:\/\/x\.com\/\w{1,15}\/status\/\d+$/.test(action.text);
|
|
53
54
|
const quoteCaps = this.#capability("read", { resolveQuote });
|
|
54
55
|
if (!quoteCaps.covered)
|
|
55
56
|
return { status: "needsCapability", missing: quoteCaps.missing };
|
|
@@ -57,7 +58,7 @@ export class GrowthRecovery {
|
|
|
57
58
|
postId,
|
|
58
59
|
url: publicURL,
|
|
59
60
|
evidenceRef,
|
|
60
|
-
resolveQuote
|
|
61
|
+
resolveQuote,
|
|
61
62
|
});
|
|
62
63
|
const trailing = action.text.match(/(?:\n+)?((?:https:\/\/|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\/)\S+)$/);
|
|
63
64
|
let sourceURL = null;
|
|
@@ -114,6 +115,9 @@ export class GrowthRecovery {
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
return (p.id === postId &&
|
|
118
|
+
(!action.quoteTarget ||
|
|
119
|
+
(p.quoteSource?.parentId === p.id &&
|
|
120
|
+
p.quoteSource?.id === action.quoteTarget.postId)) &&
|
|
117
121
|
same(p.author, action.accountHandle) &&
|
|
118
122
|
(exact ||
|
|
119
123
|
quote ||
|
|
@@ -13,6 +13,10 @@ export class PlatformCapabilities {
|
|
|
13
13
|
if (operation === "readCreator" &&
|
|
14
14
|
typeof this.#browser.collectCreator !== "function")
|
|
15
15
|
absent.push("browser.collectCreator");
|
|
16
|
+
if (input.quoteTarget)
|
|
17
|
+
absent.push(...(this.#browser.capabilities?.("openQuoteComposer") ?? [
|
|
18
|
+
"openQuoteComposer",
|
|
19
|
+
]), ...(this.#browser.capabilities?.("openQuote") ?? ["openQuote"]));
|
|
16
20
|
if (input.resolveQuote)
|
|
17
21
|
absent.push(...(this.#browser.capabilities?.("openQuote") ?? ["openQuote"]));
|
|
18
22
|
if (input.kind === "follow" || operation === "submitFollow")
|
|
@@ -9,6 +9,9 @@ export declare class PlatformWriting {
|
|
|
9
9
|
constructor(dependencies: Pick<PlatformDependencies, "observed" | "browser" | "followStage" | "readPerson" | "open" | "clock" | "readPost">);
|
|
10
10
|
private handleCompose;
|
|
11
11
|
private handleFindSubmission;
|
|
12
|
+
private matchesQuote;
|
|
13
|
+
private matchesContent;
|
|
14
|
+
private readQuotedReceipt;
|
|
12
15
|
private readLoadedReplyReceipt;
|
|
13
16
|
private handleSubmitFollow;
|
|
14
17
|
private handleSubmitPost;
|
package/dist/platform-writing.js
CHANGED
|
@@ -33,7 +33,8 @@ export class PlatformWriting {
|
|
|
33
33
|
value.alerts?.length ||
|
|
34
34
|
typeof value.editorText !== "string" ||
|
|
35
35
|
value.editorText.trim() !== "" ||
|
|
36
|
-
(action.kind === "reply" && value.replyToId !== action.postId)
|
|
36
|
+
(action.kind === "reply" && value.replyToId !== action.postId) ||
|
|
37
|
+
(action.quoteTarget && value.quoteSourceId !== action.quoteTarget.postId))
|
|
37
38
|
throw new Error("编辑器不为空或回复上下文未绑定");
|
|
38
39
|
if (action.assets?.length)
|
|
39
40
|
await this.#browser.act({
|
|
@@ -46,6 +47,8 @@ export class PlatformWriting {
|
|
|
46
47
|
value.needsStateCheck ||
|
|
47
48
|
value.alerts?.length ||
|
|
48
49
|
(action.kind === "reply" && value.replyToId !== action.postId) ||
|
|
50
|
+
(action.quoteTarget &&
|
|
51
|
+
value.quoteSourceId !== action.quoteTarget.postId) ||
|
|
49
52
|
(action.accountHandle &&
|
|
50
53
|
!sameHandle(value.accountHandle, action.accountHandle)))
|
|
51
54
|
throw new Error(`提交前账号/正文/目标未核实 ${JSON.stringify({ textMatches: sameText(value.editorText, action.text), needsStateCheck: value.needsStateCheck, alerts: value.alerts, accountHandle: value.accountHandle, replyToId: value.replyToId })}`);
|
|
@@ -124,15 +127,7 @@ export class PlatformWriting {
|
|
|
124
127
|
});
|
|
125
128
|
value = await this.#observed({ view: "posts", limit: 5 });
|
|
126
129
|
}
|
|
127
|
-
const matchesContent = (post) =>
|
|
128
|
-
samePublishedText(post.body, action.text) &&
|
|
129
|
-
/^\d+$/.test(post.id ?? "") &&
|
|
130
|
-
matchesReviewedAssets(post, action.assets) &&
|
|
131
|
-
!(action.beforeIds ?? []).includes(post.id) &&
|
|
132
|
-
Number.isFinite(Date.parse(post.datetime)) &&
|
|
133
|
-
Date.parse(post.datetime) >= Date.parse(action.intentAt) &&
|
|
134
|
-
Date.parse(post.datetime) <= this.#clock() + 5000 &&
|
|
135
|
-
!post.needsExpansion;
|
|
130
|
+
const matchesContent = (post) => this.matchesContent(post, action);
|
|
136
131
|
let matches = (value.posts ?? []).filter(matchesContent);
|
|
137
132
|
const receiptCandidate = action.kind === "reply" &&
|
|
138
133
|
matches.length === 1 &&
|
|
@@ -170,6 +165,12 @@ export class PlatformWriting {
|
|
|
170
165
|
matches = (value.posts ?? []).filter(matchesContent);
|
|
171
166
|
}
|
|
172
167
|
}
|
|
168
|
+
if (action.quoteTarget && matches.length === 1) {
|
|
169
|
+
value = await this.readQuotedReceipt(value, action, matches[0]);
|
|
170
|
+
matches = (value.posts ?? [])
|
|
171
|
+
.filter(matchesContent)
|
|
172
|
+
.filter((p) => this.matchesQuote(p, action));
|
|
173
|
+
}
|
|
173
174
|
if (action.kind === "reply")
|
|
174
175
|
matches = matches.filter((post) => post.replyToId === action.postId);
|
|
175
176
|
return {
|
|
@@ -180,6 +181,29 @@ export class PlatformWriting {
|
|
|
180
181
|
missing: matches.length === 1 ? [] : ["unique-public-submission-unconfirmed"],
|
|
181
182
|
};
|
|
182
183
|
}
|
|
184
|
+
matchesQuote(post, action) {
|
|
185
|
+
return (post.quoteSource?.parentId === post.id &&
|
|
186
|
+
post.quoteSource?.id === action.quoteTarget.postId);
|
|
187
|
+
}
|
|
188
|
+
matchesContent(post, action) {
|
|
189
|
+
return (sameHandle(post.author, action.accountHandle) &&
|
|
190
|
+
samePublishedText(post.body, action.text) &&
|
|
191
|
+
/^\d+$/.test(post.id ?? "") &&
|
|
192
|
+
matchesReviewedAssets(post, action.assets) &&
|
|
193
|
+
!(action.beforeIds ?? []).includes(post.id) &&
|
|
194
|
+
Number.isFinite(Date.parse(post.datetime)) &&
|
|
195
|
+
Date.parse(post.datetime) >= Date.parse(action.intentAt) &&
|
|
196
|
+
Date.parse(post.datetime) <= this.#clock() + 5000 &&
|
|
197
|
+
!post.needsExpansion);
|
|
198
|
+
}
|
|
199
|
+
async readQuotedReceipt(value, action, candidate) {
|
|
200
|
+
return this.#readPost({
|
|
201
|
+
postId: candidate.id,
|
|
202
|
+
url: new URL(candidate.primary, value.page).href,
|
|
203
|
+
evidenceRef: { page: value.page, observedAt: value.observedAt },
|
|
204
|
+
resolveQuote: true,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
183
207
|
async readLoadedReplyReceipt(value, targetId, matchesContent) {
|
|
184
208
|
const candidates = (value.posts ?? []).filter(matchesContent);
|
|
185
209
|
if (value.needsStateCheck ||
|
|
@@ -222,7 +246,21 @@ export class PlatformWriting {
|
|
|
222
246
|
return clicked;
|
|
223
247
|
}
|
|
224
248
|
async handleSubmitPost(action) {
|
|
225
|
-
|
|
249
|
+
if (action.quoteTarget) {
|
|
250
|
+
const source = await this.#readPost(action.quoteTarget);
|
|
251
|
+
if (source.needsStateCheck ||
|
|
252
|
+
source.alerts?.length ||
|
|
253
|
+
source.posts?.length !== 1 ||
|
|
254
|
+
source.posts[0].id !== action.quoteTarget.postId)
|
|
255
|
+
throw Error("quote-source-unconfirmed");
|
|
256
|
+
await this.#browser.act({
|
|
257
|
+
operation: "openQuoteComposer",
|
|
258
|
+
postId: action.quoteTarget.postId,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
await this.#open({ url: action.composeURL, evidenceRef: action.composeEvidenceRef }, { view: "composer" });
|
|
263
|
+
}
|
|
226
264
|
return this.handleCompose(action);
|
|
227
265
|
}
|
|
228
266
|
async handleSubmitReply(action) {
|
package/dist/readers-composer.js
CHANGED
|
@@ -101,6 +101,15 @@ export function readComposer() {
|
|
|
101
101
|
kind: "video",
|
|
102
102
|
label: el.getAttribute("aria-label"),
|
|
103
103
|
})),
|
|
104
|
+
quoteContexts: Array.from(scope.querySelectorAll('[data-testid="attachments"]'))
|
|
105
|
+
.filter(visible)
|
|
106
|
+
.filter((el) => el.innerText.startsWith("引用") || el.innerText.startsWith("Quote"))
|
|
107
|
+
.map((el) => ({
|
|
108
|
+
author: el
|
|
109
|
+
.querySelector('[data-testid="User-Name"]')
|
|
110
|
+
?.innerText.match(/@([A-Za-z0-9_]{1,15})/)?.[1],
|
|
111
|
+
body: el.querySelector('[data-testid="tweetText"]')?.innerText,
|
|
112
|
+
})),
|
|
104
113
|
needsStateCheck: false,
|
|
105
114
|
};
|
|
106
115
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -383,6 +383,15 @@ export const readXObservation = (query) => {
|
|
|
383
383
|
kind: "video",
|
|
384
384
|
label: el.getAttribute("aria-label"),
|
|
385
385
|
})),
|
|
386
|
+
quoteContexts: Array.from(scope.querySelectorAll('[data-testid="attachments"]'))
|
|
387
|
+
.filter(visible)
|
|
388
|
+
.filter((el) => el.innerText.startsWith("引用") || el.innerText.startsWith("Quote"))
|
|
389
|
+
.map((el) => ({
|
|
390
|
+
author: el
|
|
391
|
+
.querySelector('[data-testid="User-Name"]')
|
|
392
|
+
?.innerText.match(/@([A-Za-z0-9_]{1,15})/)?.[1],
|
|
393
|
+
body: el.querySelector('[data-testid="tweetText"]')?.innerText,
|
|
394
|
+
})),
|
|
386
395
|
needsStateCheck: false,
|
|
387
396
|
};
|
|
388
397
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/x-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.45",
|
|
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": "52258a7e88c8ebdab05e3981d37ec78becabffe6"
|
|
46
46
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Reviewed quote posts
|
|
2
|
+
|
|
3
|
+
Use publishPost with original preparedAction.quoteTarget:{postId,url,evidenceRef} from a complete observed source, and the reviewed commentary as text. The publisher opens the source’s observed repost→Quote editor. Empty composer, source preview, account and exact commentary are bound before submission. No plain-post fallback.
|
|
4
|
+
|
|
5
|
+
Public verification resolves the quote card and requires its exact source ID and parent binding. Wrong or absent card remains unknown. Same-ID recovery may run after notAfter; never resubmit unknown. Original draft, calendar deadline, quality fingerprint and shared intent guards remain.
|
|
@@ -16,15 +16,33 @@ Original action optional notBefore is an ISO timestamp with timezone. Explicit d
|
|
|
16
16
|
|
|
17
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
18
|
|
|
19
|
+
Real quotes: pass `quoteTarget:{postId,url,evidenceRef}`; composer and public card must bind that ID. See [quotes](quotes.md).
|
|
20
|
+
|
|
19
21
|
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.
|
|
20
22
|
|
|
21
23
|
```js
|
|
22
|
-
var result = await x.publishPost({
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
var result = await x.publishPost({
|
|
25
|
+
taskId,
|
|
26
|
+
draftPath,
|
|
27
|
+
preparedAction: {
|
|
28
|
+
id: actionId,
|
|
29
|
+
status: "prepared",
|
|
30
|
+
kind: "original",
|
|
31
|
+
accountHandle,
|
|
32
|
+
accountURL,
|
|
33
|
+
accountEvidenceRef,
|
|
34
|
+
quality: {
|
|
35
|
+
approved: true,
|
|
36
|
+
approvedText: text,
|
|
37
|
+
evidenceRef: reviewProof,
|
|
38
|
+
draftFingerprint,
|
|
39
|
+
},
|
|
40
|
+
draftPath,
|
|
41
|
+
text,
|
|
42
|
+
composeURL,
|
|
43
|
+
composeEvidenceRef,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
28
46
|
```
|
|
29
47
|
|
|
30
48
|
Optional independentExecution:{tabId,evidenceRef,scopeEvidenceRef} requires real independent task-tab/scope proof; binding executor scope is covered in setup only if needed. Existing original action can omit preparedAction; draftPath still required.
|
|
@@ -34,12 +52,24 @@ Optional independentExecution:{tabId,evidenceRef,scopeEvidenceRef} requires real
|
|
|
34
52
|
Additional fields: kind:'reply', postId, url, evidenceRef (observed parent), text; optional assets/activation as above. Common account/quality fields still required. The exact parent is verified by the API. `replyBatch({taskId,actions})` accepts 1–5 reviewed replies, submits serially and recovers an observed receipt once without resending; waiting returns unfinished work. Incoming clicks have a ≥30s minimum gap, not a safety guarantee. Incoming conversations require reviewed conversation:{recipientHandle,rootId,round,automatedLoop?}; at most 3 own rounds per recipient/root, with intent/unknown counted. Mark observed automatic loops to stop earlier.
|
|
35
53
|
|
|
36
54
|
```js
|
|
37
|
-
var result = await x.replyToPost({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
var result = await x.replyToPost({
|
|
56
|
+
taskId,
|
|
57
|
+
postId,
|
|
58
|
+
text,
|
|
59
|
+
preparedAction: {
|
|
60
|
+
id: actionId,
|
|
61
|
+
status: "prepared",
|
|
62
|
+
kind: "reply",
|
|
63
|
+
accountHandle,
|
|
64
|
+
accountURL,
|
|
65
|
+
accountEvidenceRef,
|
|
66
|
+
quality: { approved: true, approvedText: text, evidenceRef: reviewProof },
|
|
67
|
+
postId,
|
|
68
|
+
url: parentURL,
|
|
69
|
+
evidenceRef: parentProof,
|
|
70
|
+
text,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
43
73
|
```
|
|
44
74
|
|
|
45
75
|
## Follow
|