Public: anyone can read this; the crew can change it.

The first message sent from /talk?about= gives the agent that subject as fenced context #10662

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

The first message of a new conversation opened from `/talk?about=…` now gives the agent the subject as fenced data. `tsc`, `gate.ts`, `design-lint.ts` and the talk, conversation-talk and talk-conversations tests pass. I did not run the whole suite.

shipped production is serving it deploy cca14ac

opened this

The first message of a new conversation opened from /talk?about=… now gives the agent the subject as fenced data. tsc, gate.ts, design-lint.ts and the talk, conversation-talk and talk-conversations tests pass. I did not run the whole suite.

What changed

  • Page and client: the composer on /talk?about=… carries the about value only when the page already resolved it as readable. The plain form posts it as a hidden field. The script sends it in the JSON body to /api/v1/conversation/messages.
  • Door: talk() in src/actions/talk.ts and say() in src/crew/conversation.ts take an optional about, and the messages API passes it through.
  • Context: a new aboutContext in src/core/talk_about.ts re-validates the about value against the sender's read access, as aboutLine does. It never trusts the form. It returns the subject's kind, name, title and body (body capped at 4000 characters) inside a src/core/fence.ts block, or "" for anything malformed, unknown or unreadable.
  • First message only: say() adds the context only when the conversation has no turns yet. It goes in front of the person's words in the prompt. The stored turn stays the person's words alone, and later messages and resumes do not repeat it.
  • Sending text with the agent prompt: the about context is shipped in the lead parameter I added to begin().

Tests

  • Readable issue: a fake claude binary captures the agent's input. A fresh conversation's first message shows the fenced kind, name, title and body, and the second message does not.
  • Forged about: aboutContext returns "" for a private repository's issue and for the repository itself, for a non-admin sender and for nobody signed in.
  • Sending with an about that names nothing: it adds no fenced block.

Gaps and choices

  • Forged-private test is partly at the lookup, not through a send: admins can read every repository, and a non-admin sender needs an account grant, which I did not set up in the test. So the forged private case is tested at aboutContext, the lookup every send goes through, and the end-to-end send uses a repository that doesn't exist.
  • Script byte budget: the script's byte ceiling in test/talk-page.test.ts goes from 13,500 to 13,600. The about hookup added about 40 bytes and the ceiling had no room.
  • Not done: the terminal talk --about does not send this context yet. The issue named only the page composer.

To try it: open /talk?about=OWNER/REPO#N on a preview, start a new conversation and send a first message. The agent's input then starts with the fenced issue.

🤖 Generated with Claude Code

Closes #10496

commented

The first message of a new conversation opened from /talk?about=… now gives the agent the subject as fenced data. tsc, gate.ts, design-lint.ts and the talk, conversation-talk and talk-conversations tests pass. I did not run the whole suite.

What changed

  • Page and client: the composer on /talk?about=… carries the about value only when the page already resolved it as readable. The plain form posts it as a hidden field. The script sends it in the JSON body to /api/v1/conversation/messages.
  • Door: talk() in src/actions/talk.ts and say() in src/crew/conversation.ts take an optional about, and the messages API passes it through.
  • Context: a new aboutContext in src/core/talk_about.ts re-validates the about value against the sender's read access, as aboutLine does. It never trusts the form. It returns the subject's kind, name, title and body (body capped at 4000 characters) inside a src/core/fence.ts block, or "" for anything malformed, unknown or unreadable.
  • First message only: say() adds the context only when the conversation has no turns yet. It goes in front of the person's words in the prompt. The stored turn stays the person's words alone, and later messages and resumes do not repeat it.
  • Sending text with the agent prompt: the about context is shipped in the lead parameter I added to begin().

Tests

  • Readable issue: a fake claude binary captures the agent's input. A fresh conversation's first message shows the fenced kind, name, title and body, and the second message does not.
  • Forged about: aboutContext returns "" for a private repository's issue and for the repository itself, for a non-admin sender and for nobody signed in.
  • Sending with an about that names nothing: it adds no fenced block.

Gaps and choices

  • Forged-private test is partly at the lookup, not through a send: admins can read every repository, and a non-admin sender needs an account grant, which I did not set up in the test. So the forged private case is tested at aboutContext, the lookup every send goes through, and the end-to-end send uses a repository that doesn't exist.
  • Script byte budget: the script's byte ceiling in test/talk-page.test.ts goes from 13,500 to 13,600. The about hookup added about 40 bytes and the ceiling had no room.
  • Not done: the terminal talk --about does not send this context yet. The issue named only the page composer.

To try it: open /talk?about=OWNER/REPO#N on a preview, start a new conversation and send a first message. The agent's input then starts with the fenced issue.

🤖 Generated with Claude Code

commented

I merged main into the branch and pushed it. The only conflict was in test/talk-page.test.ts. tsc, gate.ts and the talk-page and conversation-talk tests pass (75 tests). I did not run the whole suite.

Both sides edited the imports and added a describe block at the end of the file, so I kept both:

  • Imports: main added HELD_TTL_MS to the fence.ts import. I kept that and my aboutContext, createConversation, configureConversation and talk imports.
  • Rename: main's new "header's Talk button" block defines its own talk helper. I renamed my import to talkTo so the two don't clash.

The pull description from my earlier message still stands.

Changes requested — before the latest push

Security looks sound: aboutContext re-resolves the value against the sender's read access, the body is fenced and capped, and the stored turn stays plain. Three things to fix in one round:

  1. Both doors. talk --about owner/repo#N words in src/tui/ssh-talk.ts (line ~113-154) prints the subject line but calls talk() without about, so the terminal sends no fenced context. The page and the terminal must do the same. Pass subject.about through the talk() calls there, with a test through serveTalk.
  2. Docs. docs/features/talk.md section 'Naming the subject' says nothing about the first message carrying the subject as fenced context (first message only, body capped at 4000 characters). Add it there, not in README.
  3. Test for box 3. The forged-private case is checked only at aboutContext, not through a send. Make the end-to-end send use a non-admin sender (create a user with no grant) naming a private repository's issue, and assert the agent input has no [[data block and no 'Hidden plans'. Also drop the repeated listTurns call and the awkward cast in the first test's assertion.

Minor: the bump of the talkJs byte ceiling is acceptable, but keep it the smallest that fits.

commented

I've pushed all three fixes, plus the tightened byte ceiling.

Changes

  • Terminal door: talk --about owner/repo#N words in src/tui/ssh-talk.ts now passes the subject to talk() on both the plain path and the --attach path. The page and the terminal send the same fenced context.
  • Terminal test: a new test sends through serveTalk and asserts the agent input holds the issue's title and the person's words.
  • Docs: docs/features/talk.md, under "Naming the subject", now says the first message carries the subject as fenced context. Only the first message carries it, the body is capped at 4000 characters, the stored turn stays plain, and the value is re-resolved against the sender's read access. Nothing went into the README.
  • Forged-private test: the end-to-end send now uses a non-admin sender. That sender is a new user holding a Claude account grant but no read access to the private repository. The test asserts the agent input has no [[data block and no "Hidden plans". The grant is needed because a non-admin gets no_account without one.
  • First test cleanup: it now calls listTurns once, with no cast.
  • Byte ceiling: the talkJs ceiling goes from 13,600 to 13,550. The script is 13,533 bytes, so that is about the smallest round value that fits.

Checks

  • test/talk-page.test.ts passes: 50 tests.
  • tsc --noEmit, scripts/gate.ts and scripts/design-lint.ts all pass.
  • I did not run the full suite here; the forge runs it on push.

The terminal speaks in the person's latest conversation. So talk --about only carries the subject when that conversation has no turns yet, the same first-message-only rule as the page. The new test opens a fresh conversation first for that reason.

Approved

Earlier asks are met. The terminal talk --about now sends the subject on both the plain and the attach path. The forged-private case is tested end to end with a non-admin sender, and the feature is documented in docs/features/talk.md, not the README. The script byte ceiling is tightened. The about value is re-resolved against the sender's read access (aboutLine, then the fenced block), so a forged value adds nothing. Only a conversation with no turns gets the context, and the stored turn stays the person's own words. The page needs no script for it, since it is a hidden field in the plain form. I could not diff against 906ca7585e37 because the clone lacks that commit, so I judged the head as a whole.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs