{"record":{"id":"a3f7b145e44b4099","repo":"Hmbown/CodeWhale","slug":"unsupported-key-text-on-harmony-backend","errorCode":null,"errorMessage":"unsupported key \"${text}\" on harmony backend","messagePattern":"unsupported key \"(.+?)\" on harmony backend","errorType":"validation","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/harmonyos.mjs","lineNumber":248,"sourceCode":"    left_mouse_up: async () => { throw new ExecError(\"low-level press/release is not exposed by uitest uiInput; use left_click_drag\"); },\n    scroll: ({ target, direction = \"down\", amount = 300 }) => {\n      const dist = Math.max(60, Math.min(1200, amount * 24));\n      const dx = direction === \"left\" ? dist : direction === \"right\" ? -dist : 0;\n      const dy = direction === \"up\" ? dist : direction === \"down\" ? -dist : 0;\n      return uiInput([\"swipe\", String(Math.round(target.x)), String(Math.round(target.y)), String(Math.round(target.x + dx)), String(Math.round(target.y + dy)), \"400\"]);\n    },\n    type: async ({ text }) => {\n      if (!text) return { action_sent: false, note: \"empty text\" };\n      await uiInput([\"inputText\", \"300\", \"300\", escDeviceText(text)]).catch(async (e) => {\n        // Some builds require coordinates of the focused field; retry with a click-first pattern.\n        throw e;\n      });\n      return { action_sent: true, chars: text.length, note: \"inputText at 300,300 — click the field first for focused input\" };\n    },\n    key: ({ text }) => {\n      const KEYMAP = { enter: \"Enter\", return: \"Enter\", escape: \"Esc\", esc: \"Esc\", back: \"Back\", home: \"Home\", backspace: \"Back\", delete: \"Del\", tab: \"Tab\", left: \"DPAD_LEFT\", right: \"DPAD_RIGHT\", up: \"DPAD_UP\", down: \"DPAD_DOWN\", power: \"Power\", menu: \"Menu\" };\n      const k = KEYMAP[String(text).toLowerCase()] ?? String(text);\n      if (!/^[A-Za-z0-9_]+$/.test(k)) throw new ExecError(`unsupported key \"${text}\" on harmony backend`);\n      return uiInput([\"keyEvent\", k]);\n    },\n    hold_key: async ({ text, duration }) => {\n      if (String(text).toLowerCase() !== \"click\") throw new ExecError('hold_key on harmony supports only hold_key({\"text\":\"click\"}) = longClick');\n      const d = Math.max(1, Math.min(5, Number(duration) || 1));\n      return uiInput([\"longClick\", \"300\", \"300\"]);\n    },\n    set_value: async ({ target, value }) => {\n      const b = await centerOf(target);\n      await uiInput([\"click\", String(b.cx), String(b.cy)]);\n      await new Promise((r) => setTimeout(r, 300));\n      await uiInput([\"inputText\", String(b.cx), String(b.cy), escDeviceText(String(value))]);\n      return { action_sent: true, strategy: \"uitest-element\" };\n    },\n    select_text: async () => { throw new ExecError(\"select_text is not exposed by uitest dumpLayout/uiInput on the harmony backend\"); },\n    perform_action: async ({ target, action }) => {\n      const b = await centerOf(target);\n      if (action === \"longClick\") return uiInput([\"longClick\", String(b.cx), String(b.cy)]);","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/harmonyos.mjs#L230-L266","documentation":"The harmonyos backend's `key` action maps a small set of key names (enter, esc, back, dpad directions, power, menu) to uitest keyEvent codes via KEYMAP. If the mapped key still contains characters other than [A-Za-z0-9_], the backend refuses to forward it, because hdc uitest keyEvent only accepts identifier-shaped key codes. This guards against sending arbitrary text through the key channel.","triggerScenarios":"Calling the key action with a text value not in KEYMAP that contains spaces, punctuation or symbols, e.g. key({text:\"ctrl+c\"}), key({text:\"a b\"}), key({text:\"\\\"\"}) — anything where KEYMAP lookup misses and the raw string fails /^[A-Za-z0-9_]+$/.","commonSituations":"Porting Android automation scripts that use modifier combos (ctrl+c, meta+space) to HarmonyOS; typos like \"space bar\"; assuming all keyboard keys are supported when only a fixed KEYMAP subset is.","solutions":["Use one of the mapped key names: enter, return, escape, esc, back, home, backspace, delete, tab, left, right, up, down, power, menu","For character input use the text/input action (inputText) instead of key — the field must be focused first","For modifier combinations, perform separate key calls since the backend does not support chords"],"exampleFix":"// before\nawait backend.key({ text: \"ctrl+c\" });\n// after\nawait backend.key({ text: \"menu\" }); // or use text input for characters","handlingStrategy":"validation","validationCode":"const ALLOWED = new Set([\"enter\",\"return\",\"escape\",\"esc\",\"back\",\"home\",\"backspace\",\"delete\",\"tab\",\"left\",\"right\",\"up\",\"down\",\"power\",\"menu\"]);\nconst k = String(text).toLowerCase();\nif (!ALLOWED.has(k) && !/^[A-Za-z0-9_]+$/.test(k)) throw new Error(`key \"${text}\" not supported on harmony backend`);","typeGuard":"const isHarmonyKey = (t) => typeof t === \"string\" && (/^[A-Za-z0-9_]+$/.test(t) || [\"enter\",\"return\",\"escape\",\"esc\",\"back\",\"home\",\"backspace\",\"delete\",\"tab\",\"left\",\"right\",\"up\",\"down\",\"power\",\"menu\"].includes(t.toLowerCase()));","tryCatchPattern":null,"preventionTips":["Check the backend's KEYMAP before sending key names","Use the text/input action for characters, key only for mapped control keys","Avoid modifier-combo strings (\"ctrl+c\") — they never pass the identifier check"],"tags":["harmonyos","key-input","validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}