Skip to content

coop: fix request-changes feedback submission in terminals without key disambiguation - #1841

Open
tomelm wants to merge 25 commits into
stripe:coopfrom
tomelm:tomer/coop-feedback-submit-fix
Open

coop: fix request-changes feedback submission in terminals without key disambiguation#1841
tomelm wants to merge 25 commits into
stripe:coopfrom
tomelm:tomer/coop-feedback-submit-fix

Conversation

@tomelm

@tomelm tomelm commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Request changes feedback could not be submitted at all in Terminal.app, stock iTerm2, or plain tmux.

#1825 bound submit exclusively to ctrl+enter and super+enter. Only terminals implementing the Kitty keyboard protocol can report those chords — everywhere else Ctrl+Enter arrives as a bare carriage return, indistinguishable from Enter. The binding never matched, the key fell through to the textarea, and every submit attempt just inserted a newline. Cmd+Enter never reached the program at all, since iTerm2 consumes it for fullscreen.

Bubble Tea does request Kitty flag 1 (keyboardEnhancementsFlags always sets it), so the original behavior works in Ghostty/kitty/WezTerm — likely why it looked correct when written.

Changes

  • Enter submits. Newline moves to ctrl+j (the LF control byte, delivered by every terminal), with alt+enter, ctrl+enter, and shift+enter also bound.
  • The advertised newline chord follows the terminal's reported capability. Bubble Tea sends a KeyboardEnhancementsMsg only when the terminal supports key disambiguation. Terminals that report it see ctrl+enter newline; everything else sees ctrl+j newline. This is capability detection rather than name matching — TERM_PROGRAM would misreport both iTerm2 with CSI u enabled and any terminal reached through tmux or SSH. All chords stay bound in both cases, so a wrong detection changes only the hint, never the behavior.
  • Cancel stays second in ShortHelp so it survives footer truncation at tiny widths.

Why the existing tests missed it

They synthesize tea.KeyPressMsg{Code: tea.KeyEnter, Mod: tea.ModCtrl} directly — a message no real terminal produces. The model layer was always correct; the delivery layer was the problem. The new tests exercise the keys a terminal can actually send, and were confirmed to fail against the old bindings on exactly ctrl+j and alt+enter.

Tests

  • go test ./pkg/coop/... -count=1
  • go test -race ./pkg/coop/tui -count=1
  • go vet ./pkg/coop/...
  • golangci-lint run pkg/coop/... — 0 issues
  • Verified manually in iTerm2 by the reporter

🤖 Generated with Claude Code

tomer-stripe and others added 25 commits June 16, 2026 15:58
## Summary

This PR establishes the `coop` integration branch as the base for the
co-op stacked PR series.

Earlier, stripe#1674 was merged into `tomer/coop-stack-base`, but the active
stack is now moving to the protected `coop` branch. Because `coop`
already has newer release commits and cannot be pushed to directly, this
PR reapplies the already-reviewed co-op domain model onto `coop` through
the normal protected-branch PR flow.

Once this merges, the rest of the stack can target `coop` cleanly. In
particular, stripe#1675 should show only the session store files instead of
re-showing the domain model files from stripe#1674.

## What this adds

- Core co-op session domain types: sessions, steps, nodes, review state,
and command responses.
- SDK snippet request/response helpers used by later workflow and agent
command PRs.
- Focused domain tests for session transitions, review behavior, and
snippets.

## Review notes

This should be reviewed as the same domain-model foundation from stripe#1674,
now applied to `coop`. There are no TUI, command registration, workflow
service, or deployment changes in this PR; those remain in later stacked
PRs.

## Verification

- `go test ./pkg/coop -count=1`
- `go test ./pkg/coop/... ./pkg/cmd/coop ./pkg/cmd -count=1`
- `golangci-lint run --max-issues-per-linter=0 --max-same-issues=0
./pkg/coop/... ./pkg/cmd/coop`

Co-authored-by: codex <noreply@openai.com>
## What this adds
Adds JSON session persistence, optimistic version checks, lock files,
heartbeat files, session listing, latest-session lookup, deletion, and
Windows-safe session replacement.

## Review focus
Review this PR as one slice of the co-op stack. It should be
understandable on its own against `tomer/coop-split-domain-core`.

## Stack
Base: `tomer/coop-split-domain-core`  
Head: `tomer/coop-split-domain-store`

## Validation
Ran `go test ./pkg/coop -count=1` and `golangci-lint run
--max-issues-per-linter=0 --max-same-issues=0 ./pkg/coop/...`.

Co-authored-by: codex <noreply@openai.com>
## What this adds
Adds embedded blueprint loading, prefix matching, metadata listing,
session creation from blueprints, and the minimal fixture catalog needed
to validate loader behavior.

## Review focus
Review this PR as one slice of the co-op stack. It should be
understandable on its own against `tomer/coop-split-domain-store`.

## Stack
Base: `tomer/coop-split-domain-store`  
Head: `tomer/coop-split-blueprint-core`

## Validation
Ran `go test ./pkg/coop -count=1` and `golangci-lint run
--max-issues-per-linter=0 --max-same-issues=0 ./pkg/coop/...`.

---------

Co-authored-by: codex <noreply@openai.com>
## What this adds
Adds the remaining bundled co-op integration blueprints as catalog data,
keeping the loader/review mechanics separate from the larger JSON
review.

## Review focus
Review this PR as one slice of the co-op stack. It should be
understandable on its own against `tomer/coop-split-blueprint-core`.

## Stack
Base: `tomer/coop-split-blueprint-core`  
Head: `tomer/coop-split-blueprint-catalog`

## Validation
Ran `go test ./pkg/coop -count=1` and `golangci-lint run
--max-issues-per-linter=0 --max-same-issues=0 ./pkg/coop/...`.

Co-authored-by: codex <noreply@openai.com>
## What this adds
Adds the workflow service that applies agent lifecycle transitions,
review/next-action helper logic, command responses, snippet lookup
integration, and workflow tests.

## Review focus
Review this PR as one slice of the co-op stack. It should be
understandable on its own against `tomer/coop-split-blueprint-catalog`.

## Stack
Base: `tomer/coop-split-blueprint-catalog`  
Head: `tomer/coop-split-workflow`

## Validation
Ran `go test ./pkg/coop/... -count=1` and `golangci-lint run
--max-issues-per-linter=0 --max-same-issues=0 ./pkg/coop/...`.

---------

Co-authored-by: codex <noreply@openai.com>
Part 6 of the clean co-op stack targeting `coop`.

This PR adds the Cobra command surface for co-op mode without
introducing the TUI yet.

Changes:
- Adds `stripe coop` command wiring.
- Adds agent-facing commands for reporting work, checks, review state,
status, stop, run, and recommendations.
- Adds command tests for the new co-op command package.

Review notes:
- This should be reviewed first in the remaining stack.
- The next PR layers in Charm dependencies and prompt helpers.

---------

Co-authored-by: codex <noreply@openai.com>
Part 7 of the clean co-op stack targeting `coop`.

This PR adds the prompt helper layer and the initial Charm dependency
changes needed by later UI work.

Changes:
- Adds co-op prompt helper code and tests.
- Updates Go module dependencies for the prompt layer.

Review notes:
- Builds on the co-op command surface from the previous PR.
- The next PR adds the production TUI implementation.

---------

Co-authored-by: codex <noreply@openai.com>
Part 8 of the clean co-op stack targeting `coop`.

This PR adds the production Bubble Tea terminal UI for co-op sessions.

Changes:
- Adds the TUI app/model/view structure.
- Adds layout, outline, detail, markdown, review, selection, mouse, and
keymap handling.
- Adds the remaining Charm ecosystem dependencies used by the UI.

Review notes:
- This PR is intentionally the largest remaining production UI layer.
- Tests for this UI are isolated in the next PR.

---------

Co-authored-by: codex <noreply@openai.com>
Part 9 of the clean co-op stack targeting `coop`.

This PR adds focused test coverage for the co-op TUI introduced in the
previous PR.

Changes:
- Adds model, layout, interaction, stress, and view tests.
- Adds small test-support hooks in the TUI command/review code.

Review notes:
- This PR is primarily test coverage for the previous production TUI
layer.

---------

Co-authored-by: codex <noreply@openai.com>
Part 10 of the clean co-op stack targeting `coop`.

This PR adds the co-op start/join launcher path and the hidden
debug-agent harness used for local/manual validation.

Changes:
- Adds start, join, launcher, and debug-agent commands.
- Adds launcher/debug-agent tests.
- Adds debug-agent documentation and the tmux validation script.

Review notes:
- The debug agent is intentionally retained as an internal/testing aid.
- This branch was adjusted to use the current step-scoped review helpers
and session fixture shape.

---------

Co-authored-by: codex <noreply@openai.com>
Part 11 of the clean co-op stack targeting `coop`.

This PR adds deploy-oriented follow-up support for completed co-op
sessions without introducing deployment as a normal integration
blueprint.

Changes:
- Adds deploy and deploy-update next-action suggestions after a session
completes.
- Adds internal compiled guided actions for deploy follow-ups. These
create normal parented co-op sessions, so the TUI can show step-by-step
deploy progress in the left rail, but they are not embedded blueprint
JSON, are not returned by `ListBlueprints`, and are not started through
`stripe coop run`.
- Keeps the generic guided-action-to-session machinery in `pkg/coop`,
while deploy-specific guided action definitions live in
`pkg/coop/followups`.
- Adds `stripe coop agent start-followup` for agents to create those
guided follow-up sessions from a completed parent session.
- Separates deploy-update from deploy: existing deployment
infrastructure gets a guided flow that pushes the integration changes to
the detected target.
- Updates the TUI so deploy choices wait for the guided follow-up
session; add-integration still waits for a new integration session.
- Adds/updates tests for guided action definitions, command creation,
next-action responses, TUI waiting behavior, and parent-session routing.

Review notes:
- The earlier `deploy-stripe-projects` blueprint was removed in response
to review feedback. Deploy is modeled as an internal follow-up action
with session steps, not as blueprint source of truth.
- The deploy guided action tells the agent to use Stripe Projects plugin
detection and commands as the deployment source of truth.
- The deploy-update guided action tells the agent to use the existing
detected provider path.
- The deploy guidance tells the agent not to supplement or modify co-op
blueprints as deployment source of truth and not to print secret key
material.

---------

Co-authored-by: codex <noreply@openai.com>
Committed-By-Agent: codex
Co-authored-by: codex <noreply@openai.com>

# Conflicts:
#	go.mod
#	go.sum
Part 12 of the clean co-op stack targeting `coop`.

This PR registers the co-op command in the root command surface and adds
supporting documentation/export tooling.

Changes:
- Registers the co-op command with the root CLI.
- Adds co-op README documentation.
- Adds blueprint export script documentation and package metadata.
- Updates root/log tests for the new command surface.

Review notes:
- This is the final PR in the clean stack.

---------

Co-authored-by: codex <noreply@openai.com>
## Summary
- Validate `${node.<step>.<node>...}` references when a blueprint is
loaded (`LoadBlueprint`), so malformed or unknown node references fail
fast instead of surfacing as confusing errors mid-session.
- Collect valid `step.node` (and `step.node.request`) keys, then check
every `${node...}` reference in the marshaled blueprint against them.
- Allow integer-indexed reference segments (e.g. an indexed
test-request) as valid.

## Tests
- `TestValidateBlueprintReferences` — covers valid references, malformed
references, and unknown referenced nodes.
- `go test ./pkg/coop/... ./pkg/cmd/coop/...` — all green.

## Base
Based directly on `coop`. First of the co-op product-quality follow-ups;
`agent-quality` and `blueprint-schema` build on top and will be opened
as `coop` advances.
…ping (stripe#1773)

**Stack 1/3** (base: `coop`). The two release-blocking findings from the
co-op mode review.

### 1. Shell-quoting in the tmux launcher (security)
The launcher built a `bash -c` script by interpolating paths with
`strconv.Quote`, which produces a **Go** string literal, not shell
quoting. Inside bash double quotes, `$(...)` and backticks still execute
— so an env-controlled path (`TMPDIR`, `XDG_CONFIG_HOME`) or a hostile
`--agent` value could inject commands. Replaced with a POSIX
`shellQuote` helper (single-quote + `'\''` escaping) at all
interpolation sites; kept `strconv.Itoa` for numeric args.

### 2. Wide-terminal layout wrapping (UX)
In the split workspace (width ≥ 100) the outline's divider rules and
titles were sized to the full terminal width (up to 80) and wrapped into
a broken multi-line mess inside the ~43-col left column — visible on any
normal wide terminal. Added an `outlineWidthOverride` so the outline
renders to the left-column width; the right detail panel keeps full
width.

Dropped a third reported item (completion-header "tab leak") after
verifying it's a `tmux capture-pane` artifact of bubbletea's
cursor-movement optimization, not a defect — the real `View()` output is
tab-free at every stage.

**Tests:** unit test for `shellQuote` metacharacter neutralization;
regression test that the split outline fits the left column; updated an
existing debug-agent test that had encoded the old unsafe
double-quoting. Full race suite + golangci-lint clean; debug-agent tmux
smoke test 16/16.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
**Stack 2/3** (base: `coop-fixes-high`). Eight medium-severity findings
from the co-op mode review.

- **session:** `IsComplete()` returns false for a session with no nodes,
so a fresh/malformed empty session isn't shown as "Integration
complete."
- **tui:** gate the completion view to suggestion nav / select / quit /
claim-open; work-view keys (expand, collapse, confirm, follow, reject,
copy) no longer leak in to mutate hidden state or fire stray commands
against the completion cursor.
- **tui:** `g`/`G`/`Home`/`End` now move the outline selection to the
first/last item (and scroll to it) instead of being no-ops when the
outline fits on screen (they only scrolled the viewport).
- **cmd/coop:** agent commands emit a structured `ok:false` JSON
response with a hint on infra errors (e.g. heartbeat/store write
failures mid-await) instead of plain text, so an agent parsing stdout
always gets a recoverable response.
- **cmd/coop:** surface the specific blueprint error (e.g. an ambiguous
prefix + its candidate list) from `coop run`/`coop start` instead of a
generic "not found."
- **store:** `LatestSession()` skips unreadable/corrupt files so one bad
file doesn't mask valid sessions from `status`/`join` (matches
`LatestActiveSession`).
- **cmd/coop:** create the store *before* launching the tmux agent pane,
so a store failure can't orphan the pane and leave a dangling active
session.
- **workflow:** remove the unreachable single-node await path
(`awaitNodeReview` + now-dead `rejectedResponse`); review always waits
at step granularity.

**Tests:** regression tests for empty-session completion,
completion-view key gating, top/bottom selection movement, and
corrupt-file resilience; verified ambiguous-prefix output via the built
CLI. Full race suite + golangci-lint clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary

While doing post-merge validation of `coop`, the outside-tmux `coop
start --debug-agent` path failed when `XDG_CONFIG_HOME` was set. The
debug-agent pane inherited the custom config dir, but the TUI `coop
join` command in the newly-created tmux session did not, so it looked in
the default config dir and could not find the session.

This updates tmux command construction so both the TUI pane and
agent/debug-agent pane preserve the co-op config environment.

## Validation

- `env -u CODEX_CI -u CODEX_THREAD_ID -u CODEX_SANDBOX -u
CODEX_SANDBOX_NETWORK_DISABLED go test ./... -count=1`
- `golangci-lint run ./...`
- `go test -race ./pkg/coop/... ./pkg/cmd/coop -count=1`
- `scripts/test-coop-debug-agent-tmux.sh`
- Manual outside-tmux smoke with custom `XDG_CONFIG_HOME`, wide terminal
size, debug agent review/confirm flow, and completion view

---------

Co-authored-by: codex <noreply@openai.com>
Committed-By-Agent: codex
Co-authored-by: codex <noreply@openai.com>
**Stack 3/3** (base: `coop-fixes-medium`). Low-severity polish and
hardening from the co-op mode review.

- **store:** fsync the temp file before rename and fsync the parent
directory after, so a crash can't leave a renamed-but-truncated session
file (atomic rename gives name-swap atomicity, not data durability).
- **store:** reclaim a stale lock left by a crashed writer. Lock files
now record pid+time; a lock untouched longer than 30s (healthy writers
hold it for milliseconds) is removed and retried, instead of wedging the
session until manual deletion. The 30s threshold stays well above the 5s
lock timeout, so active-writer contention still times out as before.
- **tui:** truncate the header claim URL with `ansi.Truncate`
(width-aware) instead of byte-slicing, which could split a multibyte
rune.
- **helpers:** `filterCompletedSuggestions` allocates a fresh slice
instead of reusing the caller's backing array via `suggestions[:0]`.
- **workflow:** remove the dead `SelectNextAction` (no callers) and its
now-orphaned `contains` helper; they encoded a next-steps write policy
that diverged from the live lock-held `Update` path in the TUI.
- **test:** the debug-agent tmux smoke test now asserts the wide split
workspace has no wrapped divider rules (previously it only checked text
presence, so it passed even with the wide-layout bug).

Intentionally did **not** touch the bundled blueprint JSON (two
blueprints use a single coarse review step) — per the README those are
exported from the upstream Workbench source and shouldn't be hand-edited
here.

**Tests:** full race suite + golangci-lint clean; debug-agent tmux smoke
test 16/16 including the new divider assertion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude

# Conflicts:
#	pkg/cmd/root.go
## Summary
- frame blueprint nodes as app implementation work rather than CLI-only
tasks
- add concrete guidance for SDK calls, signed webhooks, UI wiring,
secure configuration, and safe test payment methods
- require honest app-level verification evidence while accounting for
unsupported event triggers and blocked environments

This changes agent instructions only. It preserves the existing `nodes`
response and does not change co-op commands, blueprint schemas, or
session schemas.

## Testing
- `go test ./pkg/cmd/coop ./pkg/coop/...`
- `go test ./... -count=1`

Co-authored-by: tomer-stripe <42354557+tomer-stripe@users.noreply.github.com>
## Summary
- make Enter insert a newline and Ctrl/Cmd+Enter submit feedback
- preserve bracketed paste and burst input without truncating long
messages
- keep feedback editable after store errors and update visible key hints

## Tests
- `go test ./pkg/coop/... -count=1`
- `go test -race ./pkg/coop/tui -count=1`
- `go vet ./pkg/coop/tui`

---------

Co-authored-by: tomer-stripe <42354557+tomer-stripe@users.noreply.github.com>
The request-changes editor bound submit exclusively to ctrl+enter and
super+enter. Only terminals implementing the Kitty keyboard protocol can
report those chords; Terminal.app, stock iTerm2 and plain tmux send a
bare carriage return instead, indistinguishable from enter. The binding
never matched there, the key fell through to the textarea, and every
submit attempt inserted a newline — feedback could not be sent at all.

Swap the roles so enter submits, and bind newline to ctrl+j in addition
to the chords. ctrl+j is the LF control byte, which every terminal
delivers, so multi-line feedback stays possible where ctrl+enter cannot
be reported.

The existing tests passed because they synthesize KeyPressMsg values
with ModCtrl directly, which no terminal produces. Add coverage that
exercises the keys a terminal can actually send.
ctrl+j always works but ctrl+enter is the more discoverable chord, and
which one is available depends on the terminal rather than on anything
the CLI can assume. Bubble Tea already queries the Kitty keyboard
protocol at startup and replies with a KeyboardEnhancementsMsg only when
the terminal supports key disambiguation, so use that signal instead of
matching on terminal names: TERM_PROGRAM would misreport both iTerm2
with CSI u enabled and any terminal reached through tmux or SSH.

Terminals reporting disambiguation see "ctrl+enter newline"; everything
else keeps "ctrl+j newline". Every chord stays bound in both cases, so
an incorrect detection only changes the hint, never the behavior.

Fold the keyboard, focus and blur cases into one handler for terminal
state. They are the same kind of message and it keeps Update under the
gocyclo limit.
@tomelm
tomelm requested review from a team as code owners July 24, 2026 20:14
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.

3 participants