| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- #!/usr/bin/env node
- /**
- * 清空会话成果链接(一次性运维脚本,2026-11-10)
- *
- * ## 背景
- *
- * 旧版本把「用户自己上传的文件」也写进了会话记录的 `deliverableUrls`,
- * 结果右栏「成果」里出现了用户自己的图片。用户裁决:**全部清空重来**。
- *
- * 根因已在写入端修复(只认 `sender === 'agent'` + 拒收 /user/…/chat/… 链接),
- * 本脚本只负责把存量脏数据抹掉,之后容器产出的成果会重新积累。
- *
- * ## 做法
- *
- * 不新增云函数 op,直接复用既有 `upsert`:
- * `opUpsert` 只在**显式传入** `deliverableUrls` 数组时才写入,
- * 传 `[]` 即清空;其余字段不传就是不改(标题/归属/消息数都原样保留)。
- *
- * ## 安全
- *
- * · 默认 **dry-run**:只列出将被清空的会话,**不发任何写请求**
- * · 必须显式传 `--apply` 才真正清空
- * · 只清 `deliverableUrls`,**不删会话、不碰 Matrix 消息**
- * · 凭据从 `.claude/dev-login.json` 读取,绝不打印
- *
- * ## 用法
- *
- * ```bash
- * node scripts/clear-deliverable-urls.mjs # 只看清单
- * node scripts/clear-deliverable-urls.mjs --apply # 真的清空
- * ```
- */
- import { readFileSync } from 'node:fs';
- import { dirname, resolve } from 'node:path';
- import { fileURLToPath } from 'node:url';
- const here = dirname(fileURLToPath(import.meta.url));
- const root = resolve(here, '..');
- const FUNCTIONS_URL = 'https://server.fmode.cn/api/functions';
- const APP_ID = 'ncloudmaster';
- /** 会话目录云函数(与 `parse-api.ts` 的 `FN.chatThreads` 同源) */
- const FN_CHAT_THREADS = 'oR5O67i1Cm';
- /** 容器状态云函数:用来枚举账号下的容器,逐个拉会话目录 */
- const FN_AGENT_STATUS = 'xlosi3m46q';
- const APPLY = process.argv.includes('--apply');
- function loadToken() {
- const p = resolve(root, '.claude/dev-login.json');
- let raw;
- try {
- raw = JSON.parse(readFileSync(p, 'utf8'));
- } catch {
- console.error(`✗ 读不到登录凭据:${p}`);
- process.exit(2);
- }
- const token = (raw.token || '').trim();
- if (!token) {
- console.error('✗ 凭据里没有 token');
- process.exit(2);
- }
- return token;
- }
- async function callFn(token, id, params) {
- const res = await fetch(FUNCTIONS_URL, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-Parse-Application-Id': APP_ID,
- 'X-Parse-Session-Token': token,
- },
- body: JSON.stringify({ id, params }),
- });
- const json = await res.json().catch(() => ({}));
- if (json.code !== 200) throw new Error(`${id}: ${json.mess || JSON.stringify(json)}`);
- return json;
- }
- async function main() {
- const token = loadToken();
- // ① 账号下有哪些容器
- const agents = (await callFn(token, FN_AGENT_STATUS, { op: 'query' })).agents || [];
- const agentIds = [...new Set(agents.map((a) => a.agentId).filter(Boolean))];
- console.log(`容器 ${agentIds.length} 个:${agentIds.join(', ')}\n`);
- // ② 逐容器拉会话目录,收集「有成果链接」的会话
- const targets = [];
- for (const agentId of agentIds) {
- let threads = [];
- try {
- threads = (await callFn(token, FN_CHAT_THREADS, { op: 'list', agentId })).threads || [];
- } catch (e) {
- console.warn(`⚠ ${agentId} 拉取失败:${e.message}`);
- continue;
- }
- for (const t of threads) {
- const urls = t.deliverableUrls || [];
- if (urls.length === 0) continue;
- targets.push({ agentId, thread: t, urls });
- }
- }
- if (targets.length === 0) {
- console.log('✓ 没有需要清空的会话');
- return;
- }
- // ③ 清单(用户要先过目)
- console.log(`将清空 ${targets.length} 条会话的成果链接:\n`);
- for (const { agentId, thread, urls } of targets) {
- console.log(`· 「${thread.title || '(无标题)'}」 ${thread.threadRoot}`);
- console.log(` agentId=${agentId} ${urls.length} 条:`);
- for (const u of urls) console.log(` ${u}`);
- }
- console.log('');
- if (!APPLY) {
- console.log('(dry-run)未做任何修改。确认无误后加 --apply 执行。');
- return;
- }
- // ④ 逐条清空:只传 roomId + threadRoot + deliverableUrls:[]
- let ok = 0;
- let failed = 0;
- for (const { thread } of targets) {
- try {
- await callFn(token, FN_CHAT_THREADS, {
- op: 'upsert',
- items: [
- {
- roomId: thread.roomId,
- threadRoot: thread.threadRoot,
- deliverableUrls: [],
- },
- ],
- });
- ok++;
- console.log(`✓ 已清空 ${thread.threadRoot}`);
- } catch (e) {
- failed++;
- console.warn(`✗ ${thread.threadRoot}:${e.message}`);
- }
- }
- console.log(`\n完成:成功 ${ok},失败 ${failed}`);
- if (failed > 0) process.exit(1);
- }
- main().catch((e) => {
- console.error('✗', e.message);
- process.exit(1);
- });
|