Skip to content

Restore the Translate to English right-click command - #61

Merged
lhr0909 merged 6 commits into
mainfrom
developer/rk-7
Oct 18, 2026
Merged

lhr0909 merged 6 commits into
mainfrom
developer/rk-7

Conversation

@lhr0909

@lhr0909 lhr0909 commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Why

RK-7: Simon needs the Translate to English right-click command back to read customer messages that were not translated. PR #57 removed it along with /translate and Reject draft.

What changed

  • packages/discord: Translate to English (a message command) is registered again and parsed into a translate interaction with targetLanguage: "en" and the target message ID. /translate and Reject draft are still rejected as unsupported.
  • apps/api: handleTranslationInteraction handles translate again. It maps the selected Discord message to the original customer message in the same authorized support thread, requests the cached English translation, publishes it with the existing TranslationWorkflow publish mode as a reply to the original, and privately answers with a link (or says why there is none: failure, already English, still running).
  • TranslationWorkflow.save now fills thread.customer_language only while it is unknown. Before this, Translate to English on a newer customer message replaced a language the operator had named (/reply translate:es, then a Hindi message, gave hi). Review RK-8.
  • Docs: README, docs/architecture/optional-translation.md and the web Discord inbox page describe the command again; the web page no longer lists /translate.

Scope

Only the right-click command. /translate stays removed, because the card names only the right-click command. The untranslated Captioner thread and the support agent outage are separate cards.

Tradeoffs

  • The pre-front-desk: /approve hands the draft back to the agent; drop /translate and Reject draft #57 translation_selection freezing is not restored. It protected target-less /translate from drifting to a newer message on redelivery. A message command always carries target_id, so the lookup is deterministic.
  • Detection never replaces a stored language, so a detected language also stays until an operator names another one. Telling operator-named and detected languages apart would need a new column, which is a D1 migration. I left that out of this PR.
  • storeCustomerTranslation (the older automatic translation path in MessageWorkflow) still has the timestamp-only rule. I didn't touch it because this PR doesn't change that path.
  • Retry hints say "Run Translate to English again" instead of pointing to /translate.

Blast radius

The Discord interaction route gains one branch that runs before the reply and approve paths. No migration and no schema change. The guild command set gains one entry. After merge, pnpm deploy:staging and then pnpm discord:commands:apply are needed before the command shows up in Discord. Registration bulk-overwrites the guild commands with the list in DISCORD_GUILD_COMMANDS.

Verification

  • pnpm check: formatting, lint and types pass (329 files / 196 files).
  • packages/discord: 54/54 tests. apps/api: 160/160 tests, including the operator-language regression (it failed with hi before the fix), including a new test where Translate to English posts the reply to the selected message and links it privately, and restored target-resolution tests (wrong message, other thread).
  • Local walkthrough against wrangler dev --local (the real Worker, D1 migrations, MessageWorkflow and TranslationWorkflow), with signed Discord interactions and loopback Discord/Gemini fixtures:
    • A Hindi customer message is projected. Translate to English on it returns a deferred ephemeral response (type 5, flags 64) and posts **English translation · Hindi → English** as a reply to the original (message_reference = original). The private answer is Translation: https://discord.com/channels/…/600000000000000001.
    • Running it again posts nothing new and returns the same link. There was one Gemini call in total.
    • A non-customer message answers "Select an original customer message posted by RespondKit in this thread."
    • An operator who is not on the allowlist gets "This action is not authorized in the current support thread."
    • D1 read-back: translation_job status succeeded with target en, one translation_post row, and the original message is unchanged (processing_status succeeded).
  • The reviewer's RK-8 harness, re-run at 1e454ba: "operator language preserved" shows es before and after; the failure, retry, disabled-inbox and token-not-persisted cases pass.
  • pnpm discord:commands:apply --dry-run (example topology): the guild command PUT includes Translate to English.
  • Not done: live Discord. That needs a deploy and command registration of this branch, which happen after merge.

🤖 Generated with Claude Code

lhr0909 and others added 3 commits October 18, 2026 20:48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… reply

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@simon-langoustine simon-langoustine left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Request changes. Verdict: verifier-failed at head 5d81ac22b587d50d8f8230ce3da63946419c159a.

One act-on item: the restored command can overwrite an operator-named customer language. The inline thread describes the path and required regression. In the local Worker, /reply translate:es stored Spanish and delivered the Spanish reply; after a newer Hindi customer message, Translate to English changed the thread language to Hindi. RK-7 explicitly requires the operator's named language to remain authoritative.

Checks passed at this head:

  • pnpm exec vp check: 329 files formatted, no lint warnings or type errors in 196 files.
  • pnpm --filter @respondkit/api test: 159 tests passed.
  • pnpm --filter @respondkit/discord test: 54 tests passed.
  • node apps/api/scripts/verify-agent-api.mjs: 50 HTTP calls and 17 captured Discord posts. Includes /approve, Approve draft, legacy approval buttons, hand-back steer notes and outgoing translation.
  • pnpm discord:commands:apply --dry-run --config config/workspaces.example.json: exactly /agent, /approve, Approve draft, /reply, /status, /retry, Translate to English and /activity. No live registration.

The extended signed-interaction walkthrough used the real local Worker, D1 and workflows, with loopback Discord/Gemini fixtures. It verified private type-5/flags-64 acknowledgment, English replying to the original, cache reuse without a second provider call, private wrong-target and unauthorized responses, provider rejection without failing the original, retry recovery, already-English behavior, and a disabled inbox. It also reproduced the language-precedence failure above. This is local backend verification, not a live Discord UI verdict.

Consider: the existing publication workflow stops waiting after about 283 seconds, while translation retries can last longer. A very slow success may need another menu invocation to publish. This timing case was identified by static review, not reproduced, and is not an additional blocker for this restoration.

Noted: no schema migration or inbox-enablement change; interaction credentials stay out of the workflow parameters and the walkthrough's D1 data. The project CONTEXT.md, verify skill/feature map and role-specific rubric were absent. Review used the card, existing walkthrough and interrogate rubric. Two independent GPT reviewers agreed on the language finding; interrogate's Claude/Grok defaults were unavailable. Live Discord rendering, resizing and screenshots require deployment/registration and were not attempted. No deploy, product edits, push or merge.

Evidence: translation walkthrough and reproduction, structured D1 results, reproducible harness, checks, API tests, Discord tests, agent walkthrough, registration dry-run. The API test log includes workerd canceled-request diagnostics; the suite exited zero with all assertions passing.

}
if (interaction.command !== "translate") return;
const messageId = await resolveTranslationMessage(env, scope, interaction);
const job = await requestMessageTranslation(env, scope, messageId, interaction.targetLanguage);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] Preserve an operator-named language when translating a newer customer message

This new command calls requestMessageTranslation, which reaches TranslationWorkflow.save. The existing update at apps/api/src/workflows/translation.ts:126 only compares timestamps. An operator can select Spanish with /reply translate:es, then receive a newer Hindi customer message and invoke this command. The translation changes thread.customer_language from es to hi, so subsequent /reply translate:customer and default draft approvals use Hindi. This violates RK-7's explicit requirement that translation never overwrite a language an operator named.

Keep explicit language choices authoritative and add a regression covering an explicit choice followed by a newer customer message and this menu action. The save SQL predates the PR, but restoring this entry point exposes it through the new user flow.

Reproduced at this head against wrangler dev --local: before {customer_language:"es", customer_language_updated_at:1792328152338}, after {customer_language:"hi", customer_language_updated_at:1792328155344}. Runtime evidence, walkthrough script.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed: 486a613 adds the regression (an operator-named es, then Translate to English on a newer Hindi message; it failed with hi), and 8835fe0 makes TranslationWorkflow.save fill thread.customer_language only while it is NULL, so detection never replaces a named language. I re-ran your walkthrough against 1e454ba: "operator language preserved" shows es before and after, and all its other cases pass. I didn't add a column recording where the language came from, because that is a D1 migration. Tradeoff: a detected language also stays fixed until an operator names another one.

lhr0909 and others added 3 commits October 18, 2026 20:58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@simon-langoustine simon-langoustine left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved at head 1e454babdb70431a967dfafa599c9f8878a3670d. Verification: unit-test-verified, plus a signed local Worker integration walkthrough. This re-review covers the changes since 5d81ac22b587d50d8f8230ce3da63946419c159a and the original blocker.

Act-on: none. The language-precedence finding is fixed. I reran the original sequence with a real signed /reply translate:es, a newer Hindi customer message, and Translate to English. D1 retained customer_language: es and customer_language_updated_at: 1792328593870 before and after. The new regression passes, and existing unknown-language detection still passes.

The legacy storeCustomerTranslation timestamp update cannot overwrite an operator language through any current production route at this head. Its only production call is the incoming arm of storeTranslation in workflows/message.ts:404. That helper's sole call at line 956 is inside the outgoing branch, so it selects publishOperatorReply. Incoming messages, including customer retries, project their original text at line 826 instead. Translate to English and the agent translation tool use the fixed TranslationWorkflow.save. Both independent reviewers reached the same conclusion. This is a source-level conclusion for this head, not an inspection of historical deployed workflow versions.

Checks run at this head:

  • pnpm exec vp check: 329 files formatted, no lint warnings or type errors in 196 files.
  • pnpm --filter @respondkit/api test: 160 tests passed, including the new language regression and adjacent approval/reply coverage.
  • pnpm --filter @respondkit/discord test: 54 tests passed.
  • Extended signed local Worker walkthrough: language preservation, translation reply/private link, cache reuse, wrong target, unauthorized operator, provider failure with original unchanged, retry recovery, already-English response, disabled inbox, and no interaction token in the 38 D1 application tables all passed.

Consider, unchanged from round 1: very slow provider retries can outlast the publication wait and require another menu invocation. No new blocker. The accepted NULL-only update also preserves an earlier detected language until an operator names another; the docs disclose that tradeoff. No migration or inbox-enablement change.

Live Discord rendering remains unverified because deployment and command registration are forbidden here. The unchanged agent API walkthrough and registration dry-run evidence carry forward from round 1. The API suite still emits workerd canceled-request/RPC-disposal diagnostics, but exits zero with all assertions passing. Product code and both review worktrees remain unchanged.

Evidence: runtime reproduction, walkthrough log, reproduction script, legacy reachability, format/lint/types, API tests, Discord tests.

@lhr0909
lhr0909 merged commit 15f0474 into main Oct 18, 2026
1 check passed
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.

2 participants