Ctrl K
ComunidadGuías…

lib/mate-lifecycle-service.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
var crypto = require('crypto');
function attachMateLifecycleService(ctx) {
  var busy = new Set();
  function owner(userId) {
    if (ctx.users.isMultiUser() && (!userId || !ctx.users.findUserById(userId))) throw new Error('Mate owner is unavailable.');
    return ctx.mates.buildMateCtx(ctx.users.isMultiUser() ? userId : null);
  }
  function read(userId, id) {
    var mate = ctx.mates.getMate(owner(userId), id);
    if (!mate || ctx.users.isMultiUser() && mate.createdBy !== userId) throw new Error('Mate is unavailable or belongs to another owner.');
    return mate;
  }
  function validateReceipt(userId, id, allowMissing) {
    var mate = ctx.mates.getMate(owner(userId), id);
    if (!mate && allowMissing) return;
    if (!mate || ctx.users.isMultiUser() && mate.createdBy !== userId) throw new Error('Mate receipt access changed.');
  }
  function list(userId) {
    return ctx.mates.getAllMates(owner(userId)).filter(function (mate) { return !ctx.users.isMultiUser() || mate.createdBy === userId; });
  }
  function broadcast(userId, message, ws) {
    var seen = new Set();
    function send(socket) {
      if (!socket || seen.has(socket) || socket.readyState !== 1) return;
      if (ctx.users.isMultiUser() && (!socket._clayUser || socket._clayUser.id !== userId)) return;
      seen.add(socket); socket.send(JSON.stringify(message));
    }
    send(ws);
    ctx.projects.forEach(function (project) { project.forEachClient(send); });
  }
  function update(userId, id, updates, ws, requestId) {
    var mate = read(userId, id);
    if (Object.prototype.hasOwnProperty.call(updates, 'lifecycleDeletion')) throw new Error('Mate deletion state is server-managed.');
    if (busy.has(id) || mate.lifecycleDeletion) throw new Error('Mate deletion is pending.');
    var updated = ctx.mates.updateMate(owner(userId), id, updates);
    if (!updated || updated.error) throw new Error(updated && updated.error || 'Mate not found');
    try {
      broadcast(userId, { type: 'mate_updated', mate: updated, requestId: requestId }, ws);
      if (ctx.refresh) ctx.refresh(owner(userId));
    } catch (error) { error.productControlResult = { persisted: true, retrySafe: false }; throw error; }
    return updated;
  }
  function idle(project) {
    if (!project) return;
    if (project.inspectMateLifecycleRuntime && project.inspectMateLifecycleRuntime().some(function (t) { return !t.exited; })) throw new Error('Close the Mate terminals before deletion.');
    project.getSessionManager().sessions.forEach(function (session) {
      if (session.isProcessing || session._queryStarting || session.queryInstance || session.worker || session.terminalId || session.runtimeTerminalId || Object.keys(session._mentionSessions || {}).length) throw new Error('Wait for this Mate runtime to become idle before deletion.');
    });
  }
  async function closeRuntime(slug, validate) {
    var timer;
    try {
      return await Promise.race([Promise.resolve().then(function () { if (validate) validate(true); return ctx.removeProject(slug, true); }), new Promise(function (resolve, reject) {
        timer = setTimeout(function () { reject(new Error('Mate runtime cleanup timed out; deletion remains pending.')); }, 10000);
      })]);
    } finally { clearTimeout(timer); }
  }
  async function remove(userId, id, validate, ws) {
    var mate = read(userId, id), slug = 'mate-' + id, project = ctx.projects.get(slug);
    if (mate.primary || mate.builtinKey === 'clay') throw new Error('Canonical Clay and primary Mates cannot be deleted.');
    if (busy.has(id)) throw new Error('Another Mate lifecycle operation is applying.');
    idle(project);
    if (validate) validate();
    busy.add(id);
    var persisted = false, runtimeClosed = false;
    try {
      // Retain an owner-visible tombstone until every required cleanup is acknowledged.
      if (!mate.lifecycleDeletion) mate = ctx.mates.updateMate(owner(userId), id, { lifecycleDeletion: { id: crypto.randomUUID(), runtimeRequired: !!project, runtimeClosed: !project } });
      if (!mate || mate.error) throw new Error('Could not persist the pending Mate deletion.');
      persisted = true;
      if (mate.builtinKey) ctx.users.addDeletedBuiltinKey(userId, mate.builtinKey);
      if (!project && mate.lifecycleDeletion.runtimeRequired && !mate.lifecycleDeletion.runtimeClosed) throw new Error('Runtime cleanup proof is unavailable; retain the pending deletion for operator recovery.');
      if (project && await closeRuntime(slug, validate) !== true) throw new Error('Mate runtime cleanup was not acknowledged.');
      runtimeClosed = true;
      mate = ctx.mates.updateMate(owner(userId), id, { lifecycleDeletion: Object.assign({}, mate.lifecycleDeletion, { runtimeClosed: true }) });
      if (!mate || mate.error) throw new Error('Runtime closure proof could not be saved.');
      if (validate) validate(true);
      var current = read(userId, id);
      if (!current.lifecycleDeletion || current.lifecycleDeletion.id !== mate.lifecycleDeletion.id) throw new Error('Mate deletion identity changed.');
      var result = ctx.mates.deleteMate(owner(userId), id, { strictCleanup: true });
      if (!result || !result.ok) throw new Error(result && result.error || 'Mate deletion was not persisted.');
      var definitions = require('./builtin-mates');
      var available = ctx.mates.getMissingBuiltinKeys(owner(userId)).map(function (key) { var def = definitions.getBuiltinByKey(key); return def && { key: def.key, displayName: def.displayName, bio: def.bio, avatarCustom: def.avatarCustom || '', avatarStyle: def.avatarStyle || 'imprint', avatarColor: def.avatarColor || '' }; }).filter(Boolean);
      broadcast(userId, { type: 'mate_deleted', mateId: id, availableBuiltins: available }, ws);
      if (ctx.refresh) ctx.refresh(owner(userId));
      return { persisted: true, runtimeClosed: true, directoryRemoved: true };
    } catch (error) {
      error.productControlResult = { persisted: persisted, runtimeClosed: runtimeClosed, deletionPending: !!ctx.mates.getMate(owner(userId), id), retrySafe: false };
      throw error;
    } finally { busy.delete(id); }
  }
  return { read: read, validateReceipt: validateReceipt, list: list, update: update, remove: remove };
}
module.exports = { attachMateLifecycleService: attachMateLifecycleService };