Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/social-publication-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ X and Bluesky publication use the agent's selected provider for language underst

Prior user context includes at most four recent whole turns, within the remaining 120,000-character serialized source budget. Current instructions and clarification pairs take priority. Selection stops at a turn that cannot fit; it never truncates a correction or skips back to an older instruction. Refreshes also bound previously cached history. Oversized current instructions still block compilation rather than being silently truncated.

Assistant draft references include ordinary assistant text and exact `done(summary)` arguments paired with a completed tool result. The tool result proves completion only; its summary and page-derived verification fields cannot replace the authored draft. This keeps references such as “publish the second option” intact across Ask-to-Act turns. An unresolved contract reports the missing detail and asks for complete text when needed, rather than another permission-only confirmation.

The contract records destinations, accounts, ordered post bodies, media constraints, reply/quote targets, prohibited destinations, and `all`, `any`, or conditional `fallback` requirements. Exact text uses source anchors so the runtime copies the original text instead of accepting a model-reconstructed excerpt. Repeated anchors accept optional `startOccurrence` and `endOccurrence` positive, one-based ordinals, each counted from the beginning of the named source (including overlapping matches). For a short literal with identical start/end anchors, `startOccurrence` alone selects that copy. Omitted ordinals still require unambiguous anchors; invalid or reversed selections are rejected. Unsupported or ambiguous requirements produce `clarify`; read-only or narrative requests produce `none`. Invalid contracts cannot fall back to lexical intent rules.

A ready contract is not sufficient permission to dispatch. Before a separate publish click, the same selected provider independently checks the authentic request against the concrete composer snapshot. Approval is cached only for that contract, action, payload, account, and page. The runtime rereads the composer after the audit and immediately before execution. A changed snapshot needs a new audit. Rejections retain the checker's reason (or identify invalid/mismatched output) in the tool result so recovery can resolve the actual issue instead of rewriting a matching draft. Arbitrary JavaScript, bundled editing/submission, keyboard submission, and opaque callbacks cannot substitute for the observed publish control.

## Deterministic enforcement

- Preserve exact body and alt text, accepting only NFC equivalence and CRLF normalization. Compose requests bind completion to the audited draft.
- App-observed non-submit controls such as X's “Add post” remain repeatable while composing a thread. Label-based duplicate-submit protection still applies to unresolved and actual publish controls.
- Chrome text replacement sends the browser's explicit Select All editing command before Delete, including on macOS, and still requires a proven empty field before inserting the replacement.
- Read ProseMirror paragraph boundaries and hard breaks as document text, excluding its trailing caret placeholder. Field verification, field digests, Chrome CDP verification, and publication snapshots preserve the same spaces and line breaks.
- Require complete composer/account/media/context observations and a pre-dispatch permalink baseline. Unassigned or shared thread media makes the observation incomplete.
- Failed preflight reports `publicationValidation.issues`, distinguishing missing account/composer/baseline evidence from body, media, or context mismatches. Body mismatches include lengths and the first differing character position/code points without copying the observed draft into diagnostics.
Expand All @@ -34,6 +38,8 @@ Malformed output or failed model calls block publication. Schema validation esta

## Validation

LinkedIn public-post controls with their own composer and audience selector are distinguished from private-message sends. The public Post button still uses the normal submission checks; adjacent messaging panels still require recipient verification. Local navigation fixtures cover both browser engines and reject ambiguous or conflicting composer evidence.

`npm test` includes the deterministic contract/runtime tests, using mocked responses to verify selected-provider routing, validation, caching, conditional progress, and dispatch/completion enforcement. `npm run test:social-contract:dom` runs both browser implementations against local Playwright fixtures, including their actual injected completion probes. It requires installed Playwright Chromium; every fixture request is fulfilled locally.

The historical language cases from PR #340 are retained in `test/llm/fixtures/social-publication-intent.json`. They are evaluation inputs, not a claim of live-model accuracy. Evaluate the compiler and independent audit against the selected provider before drawing conclusions about multilingual understanding, false authorization rates, or latency. Some historical inputs omit the payload or parent URL and should legitimately require clarification.
Expand All @@ -52,3 +58,7 @@ node test/llm/run-social-publication.mjs --config /private/provider.json --suite
The config accepts the existing provider fields (`providerName`, `baseUrl`, `model`, `apiKey`, and optional provider settings). Alternatively supply `--base`, `--model`, `--provider`, and an API-key environment variable named by `--api-key-env`. Use the same provider/model/settings as the agent; the runner does not silently select a replacement. Anthropic native and OpenAI-compatible transports are supported.

Calls run sequentially. Reports distinguish first-response validity, repairs, semantic contract errors, audit false accepts/rejects, token usage, and median/p95 latency. First-call latency is reported separately without assuming a cold server. Default results are ignored local artifacts under `test/llm/results-social-publication/`, with a Markdown report, per-case JSONL, and prompt/fixture hashes. The runner never invokes a browser action or publishes a post.

For X direct messages, a mounted empty log is treated as loading until the same non-loading log remains empty across repeated observations. That establishes a first-message baseline; completion accepts exactly one resulting matching row and rejects history that appears after dispatch.

X direct messages use their own message workflow, not the public publication contract. On `/i/chat/<conversation>`, the conversation header binds the recipient. An exact display-name or bare-username match resolves to that header's canonical handle during planning or before dispatch; subsequent verification requires the same handle, never a replacement account with the same display name. A send binds the exact body and the identities of all mounted message rows, including hidden and pending ones. An explicitly supplied body is required by the message-field classifier and is recovered from the approved task when a classifier response omits it, including route-local commands such as `Send Alex: Hello there`. Completion requires a new matching outgoing row after the prior conversation tail with `data-send-status="sent"`, an empty composer, and the same conversation. The open X chat composer does not override that delivery evidence; other adapters retain their dialog checks. Workflow field requirements are loaded even when repeated-item tracking is disabled. Incoming, pending, failed, or pre-existing messages cannot satisfy it.
20 changes: 17 additions & 3 deletions src/chrome/src/agent/adapters.js
Original file line number Diff line number Diff line change
Expand Up @@ -16215,12 +16215,22 @@ const ADAPTERS = [
{
name: 'twitter',
category: 'general',
revision: 1,
revision: 2,
regions: ['global'],
jobs: ['publish-post'],
jobs: ['publish-post', 'send-message'],
workflow: {
schema: ADAPTER_WORKFLOW_SCHEMA,
jobs: {
'send-message': {
description: 'Send and verify a direct message in the active X conversation.',
template: 'message',
stateChange: true,
requiresSubmission: true,
requiresLedger: false,
stages: ['access_gate', 'scope', 'fill', 'review', 'commit', 'verify'],
successEvidence: ['A new outgoing message matches the reviewed body in the intended conversation and has the provider sent status.'],
partialEvidence: ['The recipient, composer state, and exact send or verification blocker are reported.'],
},
'publish-post': {
description: 'Prepare, publish, and verify an X post.',
template: 'publish',
Expand All @@ -16234,9 +16244,13 @@ const ADAPTERS = [
},
},
matches: (url) => /^https?:\/\/(www\.)?(twitter\.com|x\.com)\//.test(url),
messaging: {
verifyActiveRecipient: url => /^\/i\/chat\/[^/]+\/?$/.test(new URL(url).pathname),
},
fullPageCapture: { infiniteScroll: isTwitterInfiniteScrollUrl },
notes: `
- The composer is a contenteditable, not a textarea. Character count is enforced client-side at 280 (or higher for Premium).
- On /i/chat/<conversation>, the DM composer stays visible after sending. Verify the exact new outgoing message and its sent status in the same conversation; do not resend because the composer remains open.
- The public post composer is a contenteditable, not a textarea. Character count is enforced client-side at 280 (or higher for Premium).
- On /compose/post, call wait_for_stable before filling the composer. After typing, re-read the visible accessibility tree and require the Post control to be enabled (no disabled=true) before clicking it.
- If the exact text is visible but Post remains disabled, keep the composer open and refill the editor with type_text({selector:"[data-testid=\\\"tweetTextarea_0\\\"]", text:"<exact complete post>", clear:true}); this uses the trusted Chrome typing path. Do not dismiss the composer to recover.
- A click_ax result with verified:false or no observable posting evidence is not proof that the post was published. Keep the composer open and verify a new status URL or matching feed item before reporting success.
Expand Down
Loading
Loading