Add MindSpace page live edit, chat skills, and H5 deploy tooling.
Introduce page edit sessions with draft preview and patch API, chat skill picker, user memory profile, h5ApiBase resolution, voice WAV transport, and scripts for 105/g2 deployment and Plaza local dev. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+118
-1
@@ -2,7 +2,13 @@ import crypto from 'node:crypto';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const WORKSPACE_TEMP_SKIP = new Set(['.tkmindhints', '.goosehints', '.agents', '.goose']);
|
||||
const WORKSPACE_TEMP_SKIP = new Set([
|
||||
'.tkmindhints',
|
||||
'.goosehints',
|
||||
'.tkmind-profile.json',
|
||||
'.agents',
|
||||
'.goose',
|
||||
]);
|
||||
|
||||
function candidateId(kind, key) {
|
||||
return `${kind}:${crypto.createHash('sha256').update(key).digest('hex').slice(0, 24)}`;
|
||||
@@ -120,6 +126,39 @@ export function createCleanupService(pool, options = {}) {
|
||||
});
|
||||
});
|
||||
|
||||
const [staleVersions] = await pool.query(
|
||||
`SELECT pv.id AS version_id, pv.page_id, pv.version_no, pv.content_asset_id, pv.bundle_asset_id,
|
||||
p.title, a.size_bytes, a.display_name, pv.created_at,
|
||||
COALESCE(bundle.size_bytes, 0) AS bundle_size_bytes
|
||||
FROM h5_page_versions pv
|
||||
JOIN h5_page_records p ON p.id = pv.page_id AND p.user_id = ? AND p.status <> 'deleted'
|
||||
JOIN h5_assets a ON a.id = pv.content_asset_id AND a.user_id = ? AND a.status <> 'deleted'
|
||||
LEFT JOIN h5_assets bundle
|
||||
ON bundle.id = pv.bundle_asset_id AND bundle.user_id = ? AND bundle.status <> 'deleted'
|
||||
WHERE pv.id <> p.current_version_id
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM h5_publish_records pr
|
||||
WHERE pr.page_version_id = pv.id AND pr.user_id = ?
|
||||
)
|
||||
ORDER BY pv.created_at ASC
|
||||
LIMIT 500`,
|
||||
[userId, userId, userId, userId],
|
||||
);
|
||||
for (const row of staleVersions) {
|
||||
const contentBytes = Number(row.size_bytes ?? 0);
|
||||
const bundleBytes = Number(row.bundle_size_bytes ?? 0);
|
||||
candidates.push({
|
||||
id: candidateId('page_version', row.version_id),
|
||||
kind: 'stale_page_version',
|
||||
label: `${row.title} · v${row.version_no}`,
|
||||
path: `page/${row.page_id}/version/${row.version_no}`,
|
||||
sizeBytes: contentBytes + bundleBytes,
|
||||
createdAt: Number(row.created_at),
|
||||
detail: '页面历史版本(非当前版本)',
|
||||
refId: row.version_id,
|
||||
});
|
||||
}
|
||||
|
||||
return candidates;
|
||||
};
|
||||
|
||||
@@ -191,6 +230,84 @@ export function createCleanupService(pool, options = {}) {
|
||||
await fs.rm(resolved, { force: true });
|
||||
freedBytes += sizeBytes;
|
||||
removedCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.kind === 'stale_page_version') {
|
||||
const conn = await pool.getConnection();
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
const [rows] = await conn.query(
|
||||
`SELECT pv.id, pv.page_id, pv.content_asset_id, pv.bundle_asset_id,
|
||||
p.current_version_id, p.space_id
|
||||
FROM h5_page_versions pv
|
||||
JOIN h5_page_records p ON p.id = pv.page_id
|
||||
WHERE pv.id = ? AND p.user_id = ?
|
||||
LIMIT 1 FOR UPDATE`,
|
||||
[item.refId, userId],
|
||||
);
|
||||
const version = rows[0];
|
||||
if (!version || version.id === version.current_version_id) {
|
||||
await conn.rollback();
|
||||
continue;
|
||||
}
|
||||
const [pubRows] = await conn.query(
|
||||
`SELECT id FROM h5_publish_records WHERE page_version_id = ? AND user_id = ? LIMIT 1`,
|
||||
[version.id, userId],
|
||||
);
|
||||
if (pubRows.length) {
|
||||
await conn.rollback();
|
||||
continue;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
let freed = 0;
|
||||
const assetsToDelete = [version.content_asset_id].filter(Boolean);
|
||||
if (version.bundle_asset_id) {
|
||||
const [bundleUse] = await conn.query(
|
||||
`SELECT COUNT(*) AS cnt FROM h5_page_versions
|
||||
WHERE bundle_asset_id = ? AND id <> ?`,
|
||||
[version.bundle_asset_id, version.id],
|
||||
);
|
||||
if (Number(bundleUse[0]?.cnt ?? 0) === 0) {
|
||||
assetsToDelete.push(version.bundle_asset_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const assetId of assetsToDelete) {
|
||||
const [assetRows] = await conn.query(
|
||||
`SELECT id, size_bytes FROM h5_assets
|
||||
WHERE id = ? AND user_id = ? AND status <> 'deleted'
|
||||
LIMIT 1 FOR UPDATE`,
|
||||
[assetId, userId],
|
||||
);
|
||||
const asset = assetRows[0];
|
||||
if (!asset) continue;
|
||||
await conn.query(
|
||||
`UPDATE h5_assets SET status = 'deleted', deleted_at = ?, updated_at = ?
|
||||
WHERE id = ? AND user_id = ?`,
|
||||
[now, now, assetId, userId],
|
||||
);
|
||||
freed += Number(asset.size_bytes ?? 0);
|
||||
}
|
||||
|
||||
await conn.query(`DELETE FROM h5_page_versions WHERE id = ?`, [version.id]);
|
||||
if (freed > 0) {
|
||||
await conn.query(
|
||||
`UPDATE h5_user_spaces
|
||||
SET used_bytes = GREATEST(0, used_bytes - ?), updated_at = ?
|
||||
WHERE id = ? AND user_id = ?`,
|
||||
[freed, now, version.space_id, userId],
|
||||
);
|
||||
}
|
||||
await conn.commit();
|
||||
freedBytes += freed;
|
||||
removedCount += 1;
|
||||
} catch {
|
||||
await conn.rollback();
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user