100 lines
5.0 KiB
Bash
Executable File
100 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
echo "usage: $0 <openclaw-dist-dir>" >&2
|
|
exit 1
|
|
fi
|
|
|
|
DIST_DIR="$1"
|
|
SEND_JS="${DIST_DIR}/send-sxDwUGaO.js"
|
|
BOT_JS="${DIST_DIR}/bot-Ce301bOE.js"
|
|
|
|
if [[ ! -f "${SEND_JS}" || ! -f "${BOT_JS}" ]]; then
|
|
echo "expected runtime files not found under: ${DIST_DIR}" >&2
|
|
exit 1
|
|
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}"
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
send_js = Path(sys.argv[1])
|
|
bot_js = Path(sys.argv[2])
|
|
|
|
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" }]]);'
|
|
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')
|
|
|
|
bot_text = bot_js.read_text(encoding='utf-8')
|
|
if 'import fsSync from "node:fs";' not in bot_text:
|
|
bot_text = 'import fsSync from "node:fs";\n' + bot_text
|
|
|
|
old_raw = 'const data = (callback.data ?? "").trim();'
|
|
new_raw = '''const data = (callback.data ?? "").trim();
|
|
const replyEndDebugPath = path.join(process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || "", ".openclaw"), "reply-end-debug.log");
|
|
try { fsSync.appendFileSync(replyEndDebugPath, `[raw] ${new Date().toISOString()} data=${data || "<empty>"}\n`); } catch {}'''
|
|
if old_raw in bot_text and 'reply-end-debug.log' not in bot_text:
|
|
bot_text = bot_text.replace(old_raw, new_raw, 1)
|
|
|
|
old_click = '''const chatId = callbackMessage.chat.id;
|
|
const isGroup = callbackMessage.chat.type === "group" || callbackMessage.chat.type === "supergroup";'''
|
|
new_click = '''const chatId = callbackMessage.chat.id;
|
|
const replyEndMatch = /^(rec):(continue|stop)$/.exec(data);
|
|
if (replyEndMatch) {
|
|
const choice = replyEndMatch[2];
|
|
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 {
|
|
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 {
|
|
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 {}
|
|
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 {
|
|
const replyEndPath = path.join(process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || "", ".openclaw"), "reply-end-controls.json");
|
|
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 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_js.write_text(bot_text, encoding='utf-8')
|
|
PY
|
|
|
|
echo "reply-end-controls PoC patch applied to ${DIST_DIR}"
|