Add smart ACK provider for WeChat MP replies

Replace fixed ackText with a rule-based AckProvider that picks
response templates by message type and intent (translate, summary,
rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync,
zero I/O, auto-falls back to config.ackText on any error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
john
2026-06-26 15:19:03 +08:00
parent 9ed4fd48d7
commit 9b4a25799f
162 changed files with 17276 additions and 2054 deletions
+166
View File
@@ -175,6 +175,7 @@ export async function migrateSchema(pool) {
['goosed_provider_id', 'VARCHAR(64) NULL AFTER models_json'],
['engine', "VARCHAR(32) NOT NULL DEFAULT 'openai' AFTER goosed_provider_id"],
['relay_provider', 'VARCHAR(64) NULL AFTER engine'],
['is_vision_selected', 'TINYINT(1) NOT NULL DEFAULT 0 AFTER is_selected'],
];
for (const [column, definition] of llmColumns) {
if (!(await columnExists(pool, 'h5_llm_provider_keys', column))) {
@@ -182,6 +183,22 @@ export async function migrateSchema(pool) {
}
}
await pool.query(`
CREATE TABLE IF NOT EXISTS h5_llm_executor_bindings (
id CHAR(36) PRIMARY KEY,
executor ENUM('goose', 'aider', 'openhands') NOT NULL,
purpose VARCHAR(32) NOT NULL DEFAULT 'default',
provider_key_id CHAR(36) NULL,
model VARCHAR(128) NOT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
UNIQUE KEY uq_h5_llm_executor_binding (executor, purpose),
KEY idx_h5_llm_executor_provider (provider_key_id),
CONSTRAINT fk_h5_llm_executor_provider FOREIGN KEY (provider_key_id) REFERENCES h5_llm_provider_keys(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
const publishColumns = [
['plaza_view_count', 'BIGINT NOT NULL DEFAULT 0'],
['plaza_like_count', 'BIGINT NOT NULL DEFAULT 0'],
@@ -272,6 +289,35 @@ export async function migrateSchema(pool) {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await pool.query(`
CREATE TABLE IF NOT EXISTS h5_wechat_mp_message_details (
id CHAR(36) PRIMARY KEY,
app_id VARCHAR(32) NOT NULL,
openid VARCHAR(64) NOT NULL,
user_id CHAR(36) NULL,
msg_id VARCHAR(128) NULL,
msg_type VARCHAR(32) NOT NULL,
display_text TEXT NULL,
agent_text TEXT NULL,
media_id VARCHAR(256) NULL,
media_url TEXT NULL,
media_public_url TEXT NULL,
media_format VARCHAR(64) NULL,
location_lat DECIMAL(10,7) NULL,
location_lng DECIMAL(10,7) NULL,
location_label VARCHAR(255) NULL,
link_url TEXT NULL,
link_title VARCHAR(255) NULL,
raw_xml_hash CHAR(40) NULL,
raw_json JSON NULL,
created_at BIGINT NOT NULL,
KEY idx_wechat_mp_message_details_user (app_id, openid, created_at),
KEY idx_wechat_mp_message_details_msg (msg_id),
KEY idx_wechat_mp_message_details_type (msg_type, created_at),
CONSTRAINT fk_wechat_mp_message_details_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await pool.query(`
CREATE TABLE IF NOT EXISTS h5_schedule_items (
id CHAR(36) PRIMARY KEY,
@@ -350,6 +396,32 @@ export async function migrateSchema(pool) {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await pool.query(`
CREATE TABLE IF NOT EXISTS h5_balance_alert_subscriptions (
id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
threshold_cents BIGINT NOT NULL,
channel ENUM('wechat', 'in_app') NOT NULL DEFAULT 'wechat',
status ENUM('active', 'locked', 'failed', 'cancelled') NOT NULL DEFAULT 'active',
next_run_at BIGINT NOT NULL,
last_run_at BIGINT NULL,
last_notified_balance_cents BIGINT NULL,
attempts INT NOT NULL DEFAULT 0,
locked_until BIGINT NULL,
last_error VARCHAR(500) NULL,
source_channel ENUM('h5', 'wechat', 'agent', 'api') NOT NULL DEFAULT 'agent',
source_session_id VARCHAR(128) NULL,
source_message_id VARCHAR(128) NULL,
source_text TEXT NULL,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
UNIQUE KEY uq_balance_alert_user_channel (user_id, channel),
KEY idx_balance_alert_due (status, next_run_at),
KEY idx_balance_alert_user (user_id, status, next_run_at),
CONSTRAINT fk_balance_alert_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await pool.query(`
CREATE TABLE IF NOT EXISTS h5_schedule_delivery_logs (
id CHAR(36) PRIMARY KEY,
@@ -371,10 +443,104 @@ export async function migrateSchema(pool) {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await pool.query(`
CREATE TABLE IF NOT EXISTS h5_user_notifications (
id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
channel ENUM('web', 'wechat') NOT NULL DEFAULT 'web',
notification_type VARCHAR(64) NOT NULL,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
data_json JSON NULL,
status ENUM('unread', 'read') NOT NULL DEFAULT 'unread',
read_at BIGINT NULL,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
KEY idx_user_notifications_user_status_created (user_id, status, created_at),
KEY idx_user_notifications_user_created (user_id, created_at),
CONSTRAINT fk_user_notifications_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await pool.query(
`ALTER TABLE h5_payment_orders
MODIFY pay_mode ENUM('native', 'h5', 'jsapi') NOT NULL DEFAULT 'native'`,
);
// Session snapshot cache table (additive, no existing-data impact).
// Rollback: DROP TABLE h5_session_snapshots — safe, no dependants.
await pool.query(`
CREATE TABLE IF NOT EXISTS h5_session_snapshots (
agent_session_id VARCHAR(128) NOT NULL PRIMARY KEY,
user_id CHAR(36) NOT NULL,
name VARCHAR(512) NOT NULL DEFAULT '',
working_dir VARCHAR(1024) NOT NULL DEFAULT '',
created_at_str VARCHAR(64) NOT NULL DEFAULT '',
updated_at_str VARCHAR(64) NOT NULL DEFAULT '',
user_set_name TINYINT(1) NOT NULL DEFAULT 0,
recipe_json TEXT NULL,
synced_msg_count INT NOT NULL DEFAULT 0,
source_updated_at VARCHAR(64) NOT NULL DEFAULT '',
messages_json LONGTEXT NOT NULL,
synced_at BIGINT NOT NULL,
KEY idx_h5_snapshots_user (user_id),
KEY idx_h5_snapshots_synced (synced_at),
CONSTRAINT fk_h5_snapshot_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
// Subscription plan table for tiered monthly billing.
// Rollback: DROP TABLE h5_subscriptions — safe, no dependants.
await pool.query(`
CREATE TABLE IF NOT EXISTS h5_subscriptions (
id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
plan_type VARCHAR(32) NOT NULL DEFAULT 'free',
status ENUM('active', 'expired', 'cancelled') NOT NULL DEFAULT 'active',
period_tokens_limit BIGINT NOT NULL DEFAULT 0,
period_tokens_used BIGINT NOT NULL DEFAULT 0,
period_start BIGINT NOT NULL,
period_end BIGINT NOT NULL,
expires_at BIGINT NOT NULL,
overage_rate DECIMAL(4,2) NOT NULL DEFAULT 1.00,
operator_id CHAR(36) NULL,
note VARCHAR(512) NULL,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
KEY idx_h5_sub_user_status (user_id, status),
KEY idx_h5_sub_expires (expires_at, status),
CONSTRAINT fk_h5_sub_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
// Back-fill free subscriptions for existing users who pre-date the subscription system.
// Idempotent: only inserts where no active subscription exists.
await pool.query(`
INSERT INTO h5_subscriptions
(id, user_id, plan_type, status,
period_tokens_limit, period_tokens_used,
period_start, period_end, expires_at, overage_rate,
operator_id, note, created_at, updated_at)
SELECT
UUID(),
u.id,
'free',
'active',
150000, 0,
UNIX_TIMESTAMP() * 1000,
(UNIX_TIMESTAMP() + 30 * 86400) * 1000,
(UNIX_TIMESTAMP() + 30 * 86400) * 1000,
1.00,
NULL,
'系统迁移补建免费套餐',
UNIX_TIMESTAMP() * 1000,
UNIX_TIMESTAMP() * 1000
FROM h5_users u
WHERE NOT EXISTS (
SELECT 1 FROM h5_subscriptions s
WHERE s.user_id = u.id AND s.status = 'active'
)
`);
}
export async function initSchema(pool) {