From 92eedd3b72b2a9d2fd85e6e55259e4a904f68b37 Mon Sep 17 00:00:00 2001 From: "Alice (OpenClaw)" Date: Wed, 13 May 2026 14:31:45 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20drive=20apply=20script=20from=20sha?= =?UTF-8?q?red=20config=20/=20=E8=AE=93=20apply=20script=20=E8=AE=80?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E8=A8=AD=E5=AE=9A=E4=BE=86=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/apply-openclaw-poc-patch.sh | 66 +++++++++++++++++------------ src/config.ts | 22 +++------- 2 files changed, 46 insertions(+), 42 deletions(-) mode change 100755 => 100644 scripts/apply-openclaw-poc-patch.sh diff --git a/scripts/apply-openclaw-poc-patch.sh b/scripts/apply-openclaw-poc-patch.sh old mode 100755 new mode 100644 index f01e6a0..0e1dfd9 --- a/scripts/apply-openclaw-poc-patch.sh +++ b/scripts/apply-openclaw-poc-patch.sh @@ -9,6 +9,7 @@ fi DIST_DIR="$1" SEND_JS="${DIST_DIR}/send-sxDwUGaO.js" BOT_JS="${DIST_DIR}/bot-Ce301bOE.js" +CONFIG_PATH="$(dirname "$0")/../config/reply-end-controls.json" if [[ ! -f "${SEND_JS}" || ! -f "${BOT_JS}" ]]; then echo "expected runtime files not found under: ${DIST_DIR}" >&2 @@ -18,16 +19,29 @@ fi cp "${SEND_JS}" "${SEND_JS}.reply-end-controls.bak" cp "${BOT_JS}" "${BOT_JS}.reply-end-controls.bak" -python3 - <<'PY' "${SEND_JS}" "${BOT_JS}" +python3 - <<'PY' "${SEND_JS}" "${BOT_JS}" "${CONFIG_PATH}" from pathlib import Path +import json import sys send_js = Path(sys.argv[1]) bot_js = Path(sys.argv[2]) +config_path = Path(sys.argv[3]) +cfg = json.loads(config_path.read_text(encoding='utf-8')) + +cb_continue = cfg['callbacks']['continue'] +cb_stop = cfg['callbacks']['stop'] +label_continue = cfg['labels']['continue'] +label_stop = cfg['labels']['stop'] +resolved_continue = cfg['resolvedLabels']['continue'] +resolved_stop = cfg['resolvedLabels']['stop'] +ack_continue = cfg['ack']['continue'] +ack_stop = cfg['ack']['stop'] +stop_policy = cfg['stopPolicyText'] send_text = send_js.read_text(encoding='utf-8') send_old = 'const replyMarkup = buildInlineKeyboard(opts.buttons);' -send_new = 'const replyMarkup = buildInlineKeyboard(opts.buttons ?? [[{ text: "A. 繼續", callback_data: "rec:continue" }, { text: "B. 就這樣吧,不需要額外處理", callback_data: "rec:stop" }]]);' +send_new = f'const replyMarkup = buildInlineKeyboard(opts.buttons ?? [[{{ text: {json.dumps(label_continue, ensure_ascii=False)}, callback_data: {json.dumps(cb_continue)} }}, {{ text: {json.dumps(label_stop, ensure_ascii=False)}, callback_data: {json.dumps(cb_stop)} }}]]);' if send_old in send_text and send_new not in send_text: send_text = send_text.replace(send_old, send_new, 1) send_js.write_text(send_text, encoding='utf-8') @@ -45,51 +59,51 @@ if old_raw in bot_text and 'reply-end-debug.log' not in bot_text: old_click = '''const chatId = callbackMessage.chat.id; const isGroup = callbackMessage.chat.type === "group" || callbackMessage.chat.type === "supergroup";''' -new_click = '''const chatId = callbackMessage.chat.id; +new_click = f'''const chatId = callbackMessage.chat.id; const replyEndMatch = /^(rec):(continue|stop)$/.exec(data); - if (replyEndMatch) { + if (replyEndMatch) {{ const choice = replyEndMatch[2]; - const state = { + const state = {{ lastChoice: choice, lastChoiceAt: new Date().toISOString(), sourceMessageId: String(callbackMessage.message_id), sourceCallbackId: String(callback.id), active: true - }; + }}; globalThis.__replyEndControlsState ??= new Map(); globalThis.__replyEndControlsState.set(String(chatId), state); - try { + try {{ const replyEndPath = path.join(process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || "", ".openclaw"), "reply-end-controls.json"); fsSync.writeFileSync(replyEndPath, JSON.stringify(Object.fromEntries(globalThis.__replyEndControlsState), null, 2)); - fsSync.appendFileSync(replyEndDebugPath, `[state] ${new Date().toISOString()} choice=${choice} stateWriteOk=true\n`); - } catch {} - const resolvedButtons = choice === "continue" ? [[{ text: "✅ A. 繼續", callback_data: "rec:continue" }, { text: "B. 就這樣吧,不需要額外處理", callback_data: "rec:stop" }]] : [[{ text: "A. 繼續", callback_data: "rec:continue" }, { text: "⏹ B. 就這樣吧,不需要額外處理", callback_data: "rec:stop" }]]; - try { + fsSync.appendFileSync(replyEndDebugPath, `[state] ${{new Date().toISOString()}} choice=${{choice}} stateWriteOk=true\n`); + }} catch {{}} + const resolvedButtons = choice === "continue" ? [[{{ text: {json.dumps(resolved_continue, ensure_ascii=False)}, callback_data: {json.dumps(cb_continue)} }} , {{ text: {json.dumps(label_stop, ensure_ascii=False)}, callback_data: {json.dumps(cb_stop)} }}]] : [[{{ text: {json.dumps(label_continue, ensure_ascii=False)}, callback_data: {json.dumps(cb_continue)} }}, {{ text: {json.dumps(resolved_stop, ensure_ascii=False)}, callback_data: {json.dumps(cb_stop)} }}]]; + try {{ await editCallbackButtons(resolvedButtons); - fsSync.appendFileSync(replyEndDebugPath, `[edit] ${new Date().toISOString()} choice=${choice} editOk=true\n`); - } catch {} - try { - await replyToCallbackChat(choice === "continue" ? "reply-end-controls: continue received" : "reply-end-controls: stop received"); - fsSync.appendFileSync(replyEndDebugPath, `[ack] ${new Date().toISOString()} choice=${choice} ackOk=true\n`); - } catch {} + fsSync.appendFileSync(replyEndDebugPath, `[edit] ${{new Date().toISOString()}} choice=${{choice}} editOk=true\n`); + }} catch {{}} + try {{ + await replyToCallbackChat(choice === "continue" ? {json.dumps(ack_continue, ensure_ascii=False)} : {json.dumps(ack_stop, ensure_ascii=False)}); + fsSync.appendFileSync(replyEndDebugPath, `[ack] ${{new Date().toISOString()}} choice=${{choice}} ackOk=true\n`); + }} catch {{}} return; - } + }} const isGroup = callbackMessage.chat.type === "group" || callbackMessage.chat.type === "supergroup";''' if old_click in bot_text and 'reply-end-controls: continue received' not in bot_text: bot_text = bot_text.replace(old_click, new_click, 1) old_stop = 'else bodyText = savedMediaPlaceholder ?? "";' -new_stop = '''else bodyText = savedMediaPlaceholder ?? ""; - try { +new_stop = f'''else bodyText = savedMediaPlaceholder ?? ""; + try {{ const replyEndPath = path.join(process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || "", ".openclaw"), "reply-end-controls.json"); - if (fsSync.existsSync(replyEndPath)) { + if (fsSync.existsSync(replyEndPath)) {{ const rawState = JSON.parse(fsSync.readFileSync(replyEndPath, "utf-8")); const replyEndState = rawState?.[String(chatId)]; - if (replyEndState?.lastChoice === "stop" && replyEndState?.active === true) { - bodyText = `${bodyText}\n\n[System: The user previously pressed reply-end-controls Stop for this conversation. Treat the previous thread as closed. Unless the user's new message contains a clear new instruction or explicit request for more work, you must NOT proactively continue, must NOT add extra checks, must NOT propose next steps, must NOT extend the task, and should answer briefly and close cleanly.]`.trim(); - } - } - } catch {}''' + if (replyEndState?.lastChoice === "stop" && replyEndState?.active === true) {{ + bodyText = `${{bodyText}}\n\n{stop_policy}`.trim(); + }} + }} + }} catch {{}}''' if old_stop in bot_text and 'Treat the previous thread as closed' not in bot_text: bot_text = bot_text.replace(old_stop, new_stop, 1) diff --git a/src/config.ts b/src/config.ts index 783e038..6404dd5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,24 +1,14 @@ import type { ReplyEndChoice } from "./types.js" +import config from "../config/reply-end-controls.json" -export const REPLY_END_CALLBACKS = { - continue: "rec:continue", - stop: "rec:stop", -} as const +export const REPLY_END_CALLBACKS = config.callbacks as Record -export const REPLY_END_LABELS = { - continue: "A. 繼續", - stop: "B. 就這樣吧,不需要額外處理", -} as const +export const REPLY_END_LABELS = config.labels as Record -export const REPLY_END_RESOLVED_LABELS = { - continue: "✅ A. 繼續", - stop: "⏹ B. 就這樣吧,不需要額外處理", -} as const +export const REPLY_END_RESOLVED_LABELS = config.resolvedLabels as Record export function buildReplyEndAckText(choice: ReplyEndChoice): string { - return choice === "continue" - ? "reply-end-controls: continue received" - : "reply-end-controls: stop received" + return config.ack[choice] } -export const STOP_POLICY_TEXT = "[System: The user previously pressed reply-end-controls Stop for this conversation. Treat the previous thread as closed. Unless the user's new message contains a clear new instruction or explicit request for more work, you must NOT proactively continue, must NOT add extra checks, must NOT propose next steps, must NOT extend the task, and should answer briefly and close cleanly.]" +export const STOP_POLICY_TEXT = config.stopPolicyText