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:
John
2026-06-15 22:09:38 -07:00
parent 3cd322ccfe
commit 6ee6fd64dd
94 changed files with 7015 additions and 2136 deletions
+118 -1
View File
@@ -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();
}
}
}