feat(terminal): newline chord as registry data, plus a Key tester in Settings

capabilities.newline replaces choosing the Shift+Enter bytes in the send-key
route. Key tester shows the keydown/keypress/keyup a browser reports.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Devvyn
2026-10-02 21:53:31 +08:00
co-authored by Claude Sonnet 5.5
parent 9240493c43
commit f39c66e4e8
12 changed files with 208 additions and 7 deletions
+14 -7
View File
@@ -107,6 +107,7 @@ import { buildAgentCaseMarker, writeAgentCaseMarker } from '../../agent-case-mar
import { canUsernameRunPrivilegedCommands, resolveClaudeModeForUsername } from '../../user-store.js';
import { clampEnvOverridesForOwner } from '../../session-env-clamp.js';
import { enabledClis, getCli } from '../../config/cli-registry/registry.js';
import type { NewlineSequence } from '../../config/cli-registry/types.js';
import { resolveCliLaunchError } from '../../utils/cli-launcher.js';
import { legacyConfigForMode } from '../../session-cli-registry-bridge.js';
import { isMultiUserMode } from '../../config/multiuser.js';
@@ -2100,13 +2101,7 @@ export function registerSessionRoutes(
const body = req.body as Record<string, unknown>;
const key = typeof body?.key === 'string' ? body.key : '';
// Map key names to hex byte sequences
const KEY_HEX_MAP: Record<string, string[]> = {
'S-Enter': ['0a'], // \n (line feed)
'C-Enter': ['0a'], // \n (line feed)
};
const hex = KEY_HEX_MAP[key];
if (!hex) {
if (key !== 'S-Enter' && key !== 'C-Enter') {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, `Key not allowed: ${key}`);
}
@@ -2116,6 +2111,18 @@ export function registerSessionRoutes(
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'No tmux session');
}
// Key names map to hex byte sequences. Ctrl+Enter is always a line feed; Shift+Enter is the
// CLI's own newline chord (`capabilities.newline`, default line feed), so a CLI that wants
// Esc+Enter declares it in the registry instead of being special-cased here.
const NEWLINE_HEX: Record<NewlineSequence, string[]> = {
'line-feed': ['0a'], // \n
'esc-enter': ['1b', '0d'], // ESC CR, the Alt/Option+Enter chord
};
const hex =
key === 'C-Enter'
? NEWLINE_HEX['line-feed']
: NEWLINE_HEX[getCli(session.mode)?.capabilities.newline ?? 'line-feed'];
try {
// Route through the dedicated Codeman socket — bare `tmux` would target the
// user's default server and never find this session (same #80 regression class).