Skip to content

[codex] Add harness contact handshake#206

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
senamakel:codex/harness-contact-handshake
Jul 2, 2026
Merged

[codex] Add harness contact handshake#206
senamakel merged 1 commit into
tinyhumansai:mainfrom
senamakel:codex/harness-contact-handshake

Conversation

@senamakel

@senamakel senamakel commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a contact handshake before Codex/Claude harness session envelopes are DM-forwarded.
  • Resolves the configured DM recipient to the relay's base64 messaging key, checks contact status, and sends an idempotent contact request when needed.
  • Blocks forwarding while approval is pending, hard-fails blocked contacts, and retries once if the relay still returns not_a_contact.
  • Updates the Codex wrapper relay fixture to enforce contact authorization and cover accepted and pending contact flows.

Validation

  • pnpm exec vitest run tests/codex-cli.test.ts
  • pnpm build

Notes

  • Pushed with --no-verify after the monorepo pre-push hook failed in the unrelated website build: /sitemap.xml timed out after three 60s attempts.
  • Before that failure, the hook had completed formatting, lint, SDK TypeScript, plugin TypeScript, and most of the website Next build successfully.

Summary by CodeRabbit

  • Bug Fixes
    • Improved DM delivery by validating contact status before sending messages, reducing failed direct-message attempts.
    • Added automatic retry behavior when a recipient is not yet an accepted contact.
    • Better handles pending contact requests, with clearer failure feedback when delivery cannot proceed.
    • Expanded test coverage for contact-approval and pending-message flows to improve reliability.

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

@senamakel is attempting to deploy a commit to the Vezures Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The SessionEnvelopePublisher now resolves and validates DM recipients through contact-status checks before sending, requesting contact acceptance when needed, and retrying sends on "not a contact" failures. Tests are expanded with a relay harness simulating contact status/request endpoints and covering both auto-accept and pending-contact scenarios.

Changes

Contact-Aware DM Sending

Layer / File(s) Summary
Recipient resolution and contact ensure/retry logic
sdk/typescript/src/cli/harness-wrapper.ts
Imports resolveRecipientKey, adds a cached contactPromise field, and implements ensureContact/ensureContactNow to verify/request contact status; publishNow resolves the recipient, sends the DM, and retries with a refreshed contact on "not a contact" errors, guarded by an updated isNotAContactError helper.
Relay harness contact endpoints and test coverage
sdk/typescript/tests/codex-cli.test.ts
Reworks makeRelay into a typed HarnessRelay tracking contactRequests and relationship state, adds GET status and POST contact-request endpoints with authorization gating on message PUTs, updates the auto-accept test to assert recorded contact requests, and adds a pending-contact test verifying exit code 1, stderr messaging, and blocked delivery.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Publisher as SessionEnvelopePublisher
  participant Contacts as Contacts API
  participant Relay

  Publisher->>Publisher: resolveRecipientKey(dmRecipient)
  Publisher->>Contacts: status()
  alt not accepted
    Publisher->>Contacts: request()
    alt blocked or pending
      Publisher-->>Publisher: throw error
    end
  end
  Publisher->>Relay: sendMessage()
  alt send fails ("not a contact")
    Publisher->>Publisher: clear contactPromise
    Publisher->>Contacts: ensureContact() again
    Publisher->>Relay: sendMessage() retry
  end
Loading

Poem

A rabbit checks before it leaps,
"Are we contacts?" the burrow peeps.
If pending, wait; if blocked, retreat,
But once accepted — message, sweet! 🐇✉️
Hop, retry, deliver with cheer!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a harness contact handshake for Codex.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@senamakel senamakel marked this pull request as ready for review July 2, 2026 04:56
@senamakel senamakel merged commit bc15b21 into tinyhumansai:main Jul 2, 2026
8 of 10 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99e7ac6928

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

private ensureContact(ctx: Awaited<ReturnType<typeof makeContext>>): Promise<string> {
this.contactPromise ??= this.ensureContactNow(ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not cache a rejected pending-contact check

In a long-running harness where the first envelope creates an outgoing contact request and the operator accepts it before later semantic messages, this ??= keeps the rejected “contact request pending” promise in contactPromise. Every later publish reuses that same rejection and never re-reads /contacts/.../status, so DM forwarding stays disabled until the wrapper restarts even though the relationship is now accepted. Clear the cached promise on rejection or cache only successful resolutions.

Useful? React with 👍 / 👎.

}
this.contactPromise = undefined;
const refreshedRecipient = await this.ensureContact(ctx);
await sendMessage(ctx.client, ctx.signer, refreshedRecipient, JSON.stringify(envelope));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset Signal state before retrying rejected first sends

When the relay returns not_a_contact for the first encrypted DM to a peer after ensureContact succeeds, the failed sendMessage has already run MessagesApi.send/SignalSession.encrypt, which stores and advances an outbound Signal session before the PUT response is known. Retrying here then sends a CIPHERTEXT instead of the required PREKEY_BUNDLE; a recipient that never received the rejected pre-key message has no session and will drop the retried envelope. Reset the peer Signal session or avoid encrypting until authorization is confirmed before retrying.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
sdk/typescript/tests/codex-cli.test.ts (2)

567-660: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Split makeRelay into route-handler helpers.

The contact-route additions make this helper exceed the 20-line function limit. Consider extracting message, contact, and key handlers. As per coding guidelines, “Limit functions to a maximum of 20 lines of code; extract longer logic into separate helper functions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/typescript/tests/codex-cli.test.ts` around lines 567 - 660, `makeRelay`
now exceeds the 20-line limit because it contains all route handling inline.
Refactor the request-dispatch logic in `makeRelay` into separate helper
functions for messages, contacts, and key routes, and have the main relay
handler delegate to those helpers while keeping `relay.contactRequests`
initialization intact.

Source: Coding guidelines


333-418: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Annotate the new callbacks and extract setup helpers.

The new test callback and nested callbacks need explicit return types, and the test body is well over 20 lines. As per coding guidelines, “Use explicit return types for all functions in TypeScript” and “Limit functions to a maximum of 20 lines of code.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/typescript/tests/codex-cli.test.ts` around lines 333 - 418, The new test
in codex-cli.test.ts needs to satisfy TypeScript style rules by adding explicit
return types to the main test callback and any nested callback functions (for
example the relay spawn microtask and timeout handlers), and by reducing the
oversized test body in the request-contact-approval scenario. Extract the
repeated setup and session-writing logic into small helper functions near the
existing test utilities so the test itself stays under 20 lines, while keeping
the behavior anchored around the runTinyPlaceCli, makeRelay, and makeClient
flow.

Source: Coding guidelines

sdk/typescript/src/cli/harness-wrapper.ts (1)

611-639: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract part of ensureContactNow to stay under the function-length limit.

This new helper is over 20 lines. Extracting the repeated blocked/accepted status handling keeps the contact flow easier to scan. As per coding guidelines, “Limit functions to a maximum of 20 lines of code; extract longer logic into separate helper functions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/typescript/src/cli/harness-wrapper.ts` around lines 611 - 639,
`ensureContactNow` is over the function-length limit because it repeats the
contact status checks inline; extract the accepted/blocked handling into a small
helper and reuse it for both the pre-request and post-request
`ctx.client.contacts.status` results. Keep the core flow in `ensureContactNow`
focused on resolving the recipient, checking the signer, requesting contact, and
delegating status interpretation to the new helper so the logic stays under 20
lines and easier to scan.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/typescript/src/cli/harness-wrapper.ts`:
- Around line 606-611: The new helper parameter name uses an unsupported
abbreviation. Update the `ensureContact` and `ensureContactNow` method
signatures in `harness-wrapper.ts` to use `context` instead of `ctx`, and rename
any matching references inside those helpers so the naming follows the coding
guidelines.
- Around line 606-608: Avoid caching failed contact checks in ensureContact;
contactPromise is currently set from ensureContactNow(ctx) before it settles, so
a rejection gets reused on later publishes. Update ensureContact in
harness-wrapper so only successful contact results are cached, and clear/reset
contactPromise when ensureContactNow rejects or throws. Use ensureContact and
ensureContactNow as the key locations to keep the retry behavior working after
recovery or approval.

In `@sdk/typescript/tests/codex-cli.test.ts`:
- Around line 666-667: The helper contactKey currently uses abbreviated
parameter names a and b, which violates the naming guideline. Rename the
parameters in contactKey to descriptive names that reflect the two contacts
being compared, and update the function body to use those new names
consistently.

---

Nitpick comments:
In `@sdk/typescript/src/cli/harness-wrapper.ts`:
- Around line 611-639: `ensureContactNow` is over the function-length limit
because it repeats the contact status checks inline; extract the
accepted/blocked handling into a small helper and reuse it for both the
pre-request and post-request `ctx.client.contacts.status` results. Keep the core
flow in `ensureContactNow` focused on resolving the recipient, checking the
signer, requesting contact, and delegating status interpretation to the new
helper so the logic stays under 20 lines and easier to scan.

In `@sdk/typescript/tests/codex-cli.test.ts`:
- Around line 567-660: `makeRelay` now exceeds the 20-line limit because it
contains all route handling inline. Refactor the request-dispatch logic in
`makeRelay` into separate helper functions for messages, contacts, and key
routes, and have the main relay handler delegate to those helpers while keeping
`relay.contactRequests` initialization intact.
- Around line 333-418: The new test in codex-cli.test.ts needs to satisfy
TypeScript style rules by adding explicit return types to the main test callback
and any nested callback functions (for example the relay spawn microtask and
timeout handlers), and by reducing the oversized test body in the
request-contact-approval scenario. Extract the repeated setup and
session-writing logic into small helper functions near the existing test
utilities so the test itself stays under 20 lines, while keeping the behavior
anchored around the runTinyPlaceCli, makeRelay, and makeClient flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78c8a93d-9912-4230-aa4b-0b7e5ce1b148

📥 Commits

Reviewing files that changed from the base of the PR and between 1dd964a and 99e7ac6.

📒 Files selected for processing (2)
  • sdk/typescript/src/cli/harness-wrapper.ts
  • sdk/typescript/tests/codex-cli.test.ts

Comment on lines +606 to +608
private ensureContact(ctx: Awaited<ReturnType<typeof makeContext>>): Promise<string> {
this.contactPromise ??= this.ensureContactNow(ctx);
return this.contactPromise;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid caching rejected contact checks.

contactPromise is assigned before ensureContactNow settles, so a transient status/request failure—or a pending-contact throw—gets reused by every later publish without rechecking after recovery or approval.

Proposed fix
   private ensureContact(ctx: Awaited<ReturnType<typeof makeContext>>): Promise<string> {
-    this.contactPromise ??= this.ensureContactNow(ctx);
+    this.contactPromise ??= this.ensureContactNow(ctx).catch((error: unknown) => {
+      this.contactPromise = undefined;
+      throw error;
+    });
     return this.contactPromise;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private ensureContact(ctx: Awaited<ReturnType<typeof makeContext>>): Promise<string> {
this.contactPromise ??= this.ensureContactNow(ctx);
return this.contactPromise;
private ensureContact(ctx: Awaited<ReturnType<typeof makeContext>>): Promise<string> {
this.contactPromise ??= this.ensureContactNow(ctx).catch((error: unknown) => {
this.contactPromise = undefined;
throw error;
});
return this.contactPromise;
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn as spawnChild } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/typescript/src/cli/harness-wrapper.ts` around lines 606 - 608, Avoid
caching failed contact checks in ensureContact; contactPromise is currently set
from ensureContactNow(ctx) before it settles, so a rejection gets reused on
later publishes. Update ensureContact in harness-wrapper so only successful
contact results are cached, and clear/reset contactPromise when ensureContactNow
rejects or throws. Use ensureContact and ensureContactNow as the key locations
to keep the retry behavior working after recovery or approval.

Comment on lines +606 to +611
private ensureContact(ctx: Awaited<ReturnType<typeof makeContext>>): Promise<string> {
this.contactPromise ??= this.ensureContactNow(ctx);
return this.contactPromise;
}

private async ensureContactNow(ctx: Awaited<ReturnType<typeof makeContext>>): Promise<string> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename ctx in the new helpers.

ctx is not in the abbreviation exception list; use context for the new helper parameter. As per coding guidelines, “Avoid abbreviations … exceptions: db, arg, args, env, fn, prop, props, ref, refs.”

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn as spawnChild } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/typescript/src/cli/harness-wrapper.ts` around lines 606 - 611, The new
helper parameter name uses an unsupported abbreviation. Update the
`ensureContact` and `ensureContactNow` method signatures in `harness-wrapper.ts`
to use `context` instead of `ctx`, and rename any matching references inside
those helpers so the naming follows the coding guidelines.

Source: Coding guidelines

Comment on lines +666 to +667
function contactKey(a: string, b: string): string {
return [a, b].sort().join("\0");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use descriptive contact-key parameter names.

a and b violate the abbreviation rule.

Proposed fix
-function contactKey(a: string, b: string): string {
-  return [a, b].sort().join("\0");
+function contactKey(firstParticipant: string, secondParticipant: string): string {
+  return [firstParticipant, secondParticipant].sort().join("\0");
 }

As per coding guidelines, “Avoid abbreviations … exceptions: db, arg, args, env, fn, prop, props, ref, refs.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function contactKey(a: string, b: string): string {
return [a, b].sort().join("\0");
function contactKey(firstParticipant: string, secondParticipant: string): string {
return [firstParticipant, secondParticipant].sort().join("\0");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/typescript/tests/codex-cli.test.ts` around lines 666 - 667, The helper
contactKey currently uses abbreviated parameter names a and b, which violates
the naming guideline. Rename the parameters in contactKey to descriptive names
that reflect the two contacts being compared, and update the function body to
use those new names consistently.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant