Skip to content

feat(gateway): attribute prompts to authenticated users#111207

Merged
steipete merged 1 commit into
mainfrom
claude/identity-prompt-attribution
Jul 19, 2026
Merged

feat(gateway): attribute prompts to authenticated users#111207
steipete merged 1 commit into
mainfrom
claude/identity-prompt-attribution

Conversation

@steipete

Copy link
Copy Markdown
Contributor

What Problem This Solves

On a shared gateway, prompts from different teammates are indistinguishable in session transcripts — everything reads as one anonymous operator. The persisted user-turn store already supports durable, opt-in sender attribution (built for channel senders like Telegram), but gateway clients never populate it.

Part of the multiplayer-identity program: #111133 (PR "attribution" of the train; builds on #111179).

Why This Change Was Made

When an identified gateway client (trusted-proxy/Tailscale-authenticated) submits a prompt, the gateway now stamps the existing UserTurnInput.sender field with the connection's identity (sender: { id: authenticatedUserId }) on both the chat and agent execution paths. The Control UI renders a small attribution label on prompt bubbles via a new formatSenderLabel helper (display name → username → email local part → opaque id), so labels upgrade automatically once durable profile ids replace emails.

Scope lines held deliberately:

  • Prompts only — tool calls, steering, and agent output remain the agent's, unattributed.
  • senderIsOwner semantics untouched (admin-scope-derived, verified before changing nothing).
  • Identity optional: unidentified connections persist exactly today's un-attributed turns (conditional spread; no field written).

User Impact

Shared-gateway teams see who asked what in session transcripts. Single-user and token/password gateways see zero change.

Evidence

  • node scripts/run-vitest.mjs src/gateway/server.chat.gateway-server-chat-b.test.ts — 87 tests, incl. two identified senders + one unidentified in a single session.
  • node scripts/run-vitest.mjs src/gateway/server-methods/agent.test.ts — 246 tests (agent-path attribution).
  • node scripts/run-vitest.mjs src/sessions/user-turn-transcript.test.ts — 36 tests (durable persistence unchanged semantics).
  • node scripts/run-vitest.mjs ui/src/lib/chat/message-normalizer.test.ts ui/src/pages/chat/components/chat-message.test.ts — 138 tests (label precedence + rendering).
  • Core/UI TypeScript lanes green on Blacksmith Testbox; targeted oxlint + format clean.
  • Structured autoreview (codex/gpt-5.6-sol, xhigh): clean, no actionable findings ("patch is correct").

Production diff is 42 insertions / 1 deletion; the rest is tests.

@steipete
steipete marked this pull request as ready for review July 19, 2026 04:28
@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime size: M maintainer Maintainer-authored PR labels Jul 19, 2026

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

Copy link
Copy Markdown
Contributor

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: d1e9af6588

ℹ️ 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".

Comment on lines +190 to +192
...(params.client?.authenticatedUserId
? { sender: { id: params.client.authenticatedUserId } }
: {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Persist the attributed recorder in agent runtimes

When an authenticated client invokes the agent RPC, this attributed recorder is only forwarded through ingressOpts.userTurnTranscriptRecorder; the normal embedded runtime unconditionally creates a different recorder in src/agents/command/run-embedded-attempt.ts:151, while the ACP path constructs its own userInput in src/agents/command/acp-execution.ts:198, so neither persists this senderId. Consequently agent-RPC prompts remain unattributed in actual transcripts—the added test only verifies the mocked ingress argument. Please make both runtime persistence paths consume the prepared recorder or otherwise carry its sender metadata through.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 721aee1 into main Jul 19, 2026
200 of 205 checks passed
@steipete
steipete deleted the claude/identity-prompt-attribution branch July 19, 2026 04:38
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

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

Labels

app: web-ui App: web-ui gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant