refactor: drive apply script from shared config / 讓 apply script 讀共享設定來源
This commit is contained in:
66
scripts/apply-openclaw-poc-patch.sh
Executable file → Normal file
66
scripts/apply-openclaw-poc-patch.sh
Executable file → Normal file
@@ -9,6 +9,7 @@ fi
|
|||||||
DIST_DIR="$1"
|
DIST_DIR="$1"
|
||||||
SEND_JS="${DIST_DIR}/send-sxDwUGaO.js"
|
SEND_JS="${DIST_DIR}/send-sxDwUGaO.js"
|
||||||
BOT_JS="${DIST_DIR}/bot-Ce301bOE.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
|
if [[ ! -f "${SEND_JS}" || ! -f "${BOT_JS}" ]]; then
|
||||||
echo "expected runtime files not found under: ${DIST_DIR}" >&2
|
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 "${SEND_JS}" "${SEND_JS}.reply-end-controls.bak"
|
||||||
cp "${BOT_JS}" "${BOT_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
|
from pathlib import Path
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
send_js = Path(sys.argv[1])
|
send_js = Path(sys.argv[1])
|
||||||
bot_js = Path(sys.argv[2])
|
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_text = send_js.read_text(encoding='utf-8')
|
||||||
send_old = 'const replyMarkup = buildInlineKeyboard(opts.buttons);'
|
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:
|
if send_old in send_text and send_new not in send_text:
|
||||||
send_text = send_text.replace(send_old, send_new, 1)
|
send_text = send_text.replace(send_old, send_new, 1)
|
||||||
send_js.write_text(send_text, encoding='utf-8')
|
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;
|
old_click = '''const chatId = callbackMessage.chat.id;
|
||||||
const isGroup = callbackMessage.chat.type === "group" || callbackMessage.chat.type === "supergroup";'''
|
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);
|
const replyEndMatch = /^(rec):(continue|stop)$/.exec(data);
|
||||||
if (replyEndMatch) {
|
if (replyEndMatch) {{
|
||||||
const choice = replyEndMatch[2];
|
const choice = replyEndMatch[2];
|
||||||
const state = {
|
const state = {{
|
||||||
lastChoice: choice,
|
lastChoice: choice,
|
||||||
lastChoiceAt: new Date().toISOString(),
|
lastChoiceAt: new Date().toISOString(),
|
||||||
sourceMessageId: String(callbackMessage.message_id),
|
sourceMessageId: String(callbackMessage.message_id),
|
||||||
sourceCallbackId: String(callback.id),
|
sourceCallbackId: String(callback.id),
|
||||||
active: true
|
active: true
|
||||||
};
|
}};
|
||||||
globalThis.__replyEndControlsState ??= new Map();
|
globalThis.__replyEndControlsState ??= new Map();
|
||||||
globalThis.__replyEndControlsState.set(String(chatId), state);
|
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");
|
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.writeFileSync(replyEndPath, JSON.stringify(Object.fromEntries(globalThis.__replyEndControlsState), null, 2));
|
||||||
fsSync.appendFileSync(replyEndDebugPath, `[state] ${new Date().toISOString()} choice=${choice} stateWriteOk=true\n`);
|
fsSync.appendFileSync(replyEndDebugPath, `[state] ${{new Date().toISOString()}} choice=${{choice}} stateWriteOk=true\n`);
|
||||||
} catch {}
|
}} 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" }]];
|
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 {
|
try {{
|
||||||
await editCallbackButtons(resolvedButtons);
|
await editCallbackButtons(resolvedButtons);
|
||||||
fsSync.appendFileSync(replyEndDebugPath, `[edit] ${new Date().toISOString()} choice=${choice} editOk=true\n`);
|
fsSync.appendFileSync(replyEndDebugPath, `[edit] ${{new Date().toISOString()}} choice=${{choice}} editOk=true\n`);
|
||||||
} catch {}
|
}} catch {{}}
|
||||||
try {
|
try {{
|
||||||
await replyToCallbackChat(choice === "continue" ? "reply-end-controls: continue received" : "reply-end-controls: stop received");
|
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`);
|
fsSync.appendFileSync(replyEndDebugPath, `[ack] ${{new Date().toISOString()}} choice=${{choice}} ackOk=true\n`);
|
||||||
} catch {}
|
}} catch {{}}
|
||||||
return;
|
return;
|
||||||
}
|
}}
|
||||||
const isGroup = callbackMessage.chat.type === "group" || callbackMessage.chat.type === "supergroup";'''
|
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:
|
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)
|
bot_text = bot_text.replace(old_click, new_click, 1)
|
||||||
|
|
||||||
old_stop = 'else bodyText = savedMediaPlaceholder ?? "<media:document>";'
|
old_stop = 'else bodyText = savedMediaPlaceholder ?? "<media:document>";'
|
||||||
new_stop = '''else bodyText = savedMediaPlaceholder ?? "<media:document>";
|
new_stop = f'''else bodyText = savedMediaPlaceholder ?? "<media:document>";
|
||||||
try {
|
try {{
|
||||||
const replyEndPath = path.join(process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || "", ".openclaw"), "reply-end-controls.json");
|
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 rawState = JSON.parse(fsSync.readFileSync(replyEndPath, "utf-8"));
|
||||||
const replyEndState = rawState?.[String(chatId)];
|
const replyEndState = rawState?.[String(chatId)];
|
||||||
if (replyEndState?.lastChoice === "stop" && replyEndState?.active === true) {
|
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();
|
bodyText = `${{bodyText}}\n\n{stop_policy}`.trim();
|
||||||
}
|
}}
|
||||||
}
|
}}
|
||||||
} catch {}'''
|
}} catch {{}}'''
|
||||||
if old_stop in bot_text and 'Treat the previous thread as closed' not in bot_text:
|
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)
|
bot_text = bot_text.replace(old_stop, new_stop, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
import type { ReplyEndChoice } from "./types.js"
|
import type { ReplyEndChoice } from "./types.js"
|
||||||
|
import config from "../config/reply-end-controls.json"
|
||||||
|
|
||||||
export const REPLY_END_CALLBACKS = {
|
export const REPLY_END_CALLBACKS = config.callbacks as Record<ReplyEndChoice, string>
|
||||||
continue: "rec:continue",
|
|
||||||
stop: "rec:stop",
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export const REPLY_END_LABELS = {
|
export const REPLY_END_LABELS = config.labels as Record<ReplyEndChoice, string>
|
||||||
continue: "A. 繼續",
|
|
||||||
stop: "B. 就這樣吧,不需要額外處理",
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export const REPLY_END_RESOLVED_LABELS = {
|
export const REPLY_END_RESOLVED_LABELS = config.resolvedLabels as Record<ReplyEndChoice, string>
|
||||||
continue: "✅ A. 繼續",
|
|
||||||
stop: "⏹ B. 就這樣吧,不需要額外處理",
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export function buildReplyEndAckText(choice: ReplyEndChoice): string {
|
export function buildReplyEndAckText(choice: ReplyEndChoice): string {
|
||||||
return choice === "continue"
|
return config.ack[choice]
|
||||||
? "reply-end-controls: continue received"
|
|
||||||
: "reply-end-controls: stop received"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user