refactor: drive apply script from shared config / 讓 apply script 讀共享設定來源

This commit is contained in:
Alice (OpenClaw)
2026-05-13 14:31:45 +08:00
parent abde0c243f
commit 92eedd3b72
2 changed files with 46 additions and 42 deletions

66
scripts/apply-openclaw-poc-patch.sh Executable file → Normal file
View File

@@ -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 ?? "<media:document>";'
new_stop = '''else bodyText = savedMediaPlaceholder ?? "<media:document>";
try {
new_stop = f'''else bodyText = savedMediaPlaceholder ?? "<media:document>";
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)

View File

@@ -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<ReplyEndChoice, string>
export const REPLY_END_LABELS = {
continue: "A. 繼續",
stop: "B. 就這樣吧,不需要額外處理",
} as const
export const REPLY_END_LABELS = config.labels as Record<ReplyEndChoice, string>
export const REPLY_END_RESOLVED_LABELS = {
continue: "✅ A. 繼續",
stop: "⏹ B. 就這樣吧,不需要額外處理",
} as const
export const REPLY_END_RESOLVED_LABELS = config.resolvedLabels as Record<ReplyEndChoice, string>
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