Ctrl K
ComunidadGuías…

lib/clay-mate-lifecycle.js

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

Archivo original ↗Preguntar a KiBot
var crypto = require('crypto');
var catalog = require('./mate-avatar-catalog');
var colors = require('./mate-workspace-color');
function string(value, max) { return typeof value === 'string' && value.trim() && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value); }
function revision(mate) { var copy = Object.assign({}, mate); delete copy.lifecycleDeletion; return JSON.stringify(copy); }
function runtime(project, sessionsOnly) {
  if (!project) return null;
  var status = project.getStatus();
  return JSON.stringify([status.mateId, status.projectOwnerId, !sessionsOnly && project.inspectMateLifecycleRuntime && project.inspectMateLifecycleRuntime(), Array.from(project.getSessionManager().sessions.values()).map(function (s) { return [s.localId, s.ownerId, s.sessionVisibility, s.title, s.isProcessing, s._queryStarting, s._sdkQueryGeneration, s.history && s.history.length, s.terminalId, s.runtimeTerminalId]; })]);
}
function profileChanges(mate, patch) {
  if (!patch || typeof patch !== 'object' || Array.isArray(patch) || !Object.keys(patch).length || Object.keys(patch).some(function (key) { return ['name', 'bio', 'workspaceColor', 'avatarCatalogId'].indexOf(key) < 0; })) throw new Error('Use only name, bio, workspaceColor or avatarCatalogId.');
  if (mate.lifecycleDeletion) throw new Error('Mate deletion is pending.');
  var changes = {}, profile = Object.assign({}, mate.profile);
  if (patch.name !== undefined) {
    if (mate.primary || !string(patch.name, 100)) throw new Error('This Mate name cannot be changed to that value.');
    changes.name = patch.name.trim(); profile.displayName = changes.name;
  }
  if (patch.bio !== undefined) {
    if (mate.primary || typeof patch.bio !== 'string' || patch.bio.length > 600 || /[\u0000-\u001f\u007f]/.test(patch.bio)) throw new Error('Use an introduction of at most 600 characters for an editable Mate.');
    changes.bio = patch.bio.trim();
  }
  if (patch.workspaceColor !== undefined) {
    if (mate.primary || mate.builtinKey === 'clay' || !colors.valid(patch.workspaceColor)) throw new Error('This workspace color is unavailable.');
    changes.workspaceColor = patch.workspaceColor;
  }
  if (patch.avatarCatalogId !== undefined) {
    if (profile.avatarLocked || !catalog.valid(patch.avatarCatalogId)) throw new Error('Choose an editable catalog avatar.');
    profile.avatarCatalogId = patch.avatarCatalogId; profile.avatarCustom = '';
  }
  if (patch.name !== undefined || patch.avatarCatalogId !== undefined) changes.profile = profile;
  return changes;
}
function attachClayMateLifecycle(ctx) {
  var state = ctx.state;
  function service() { var value = ctx.getService && ctx.getService(); if (!value) throw new Error('Mate lifecycle controls are unavailable.'); return value; }
  function owner() { var p = ctx.authorize(); return p.userId || p.singleUserOwnerId || 'default'; }
  function projection(m) { return { mateId: m.id, name: (m.profile || {}).displayName || m.name || 'New Mate', bio: m.bio || '', profile: m.profile || {}, workspaceColor: m.workspaceColor || null, primary: !!m.primary, canonicalClay: m.builtinKey === 'clay', deletionPending: !!m.lifecycleDeletion }; }
  function list(args) {
    if (args && Object.keys(args).length) throw new Error('Mate listing accepts no arguments.');
    return { mates: service().list(owner()).map(projection) };
  }
  function inspect(args) {
    if (!args || !string(args.mateId, 100) || Object.keys(args).some(function (k) { return k !== 'mateId'; })) throw new Error('An exact mateId is required.');
    var mate = service().read(owner(), args.mateId), project = ctx.getProjects().get('mate-' + mate.id);
    if (project) {
      var status = project.getStatus();
      if (!status.isMate || status.mateId !== mate.id || status.projectOwnerId !== owner()) throw new Error('Mate runtime ownership changed.');
      project.getSessionManager().sessions.forEach(function (s) { if (s.sessionVisibility === 'shared' || s.ownerId && s.ownerId !== owner()) throw new Error('Mate runtime is not private to this owner.'); });
    }
    if (state.targets.size >= 100) state.targets.delete(state.targets.keys().next().value);
    var token = 'mate-control:' + crypto.randomUUID();
    state.targets.set(token, { mate: mate, revision: revision(mate), deletion: JSON.stringify(mate.lifecycleDeletion), project: project, runtime: runtime(project), cleanupRuntime: runtime(project, true), created: Date.now() });
    return Object.assign(projection(mate), { targetToken: token, expiresInSeconds: 300, actions: mate.primary || mate.builtinKey === 'clay' ? ['profile'] : ['profile', 'delete'], profileFields: ['name', 'bio', 'workspaceColor', 'avatarCatalogId'] });
  }
  function check(target, cleanup) {
    var mate = service().read(owner(), target.mate.id), project = ctx.getProjects().get('mate-' + mate.id);
    if (Date.now() - target.created > 300000 || revision(mate) !== target.revision || !cleanup && JSON.stringify(mate.lifecycleDeletion) !== target.deletion) throw new Error('The exact Mate revision changed. Inspect again.');
    if (!cleanup && (project !== target.project || runtime(project) !== target.runtime)) throw new Error('Mate runtime changed. Inspect again.');
    if (cleanup && runtime(target.project, true) !== target.cleanupRuntime) throw new Error('Mate sessions changed during cleanup. Inspect again.');
    if (cleanup && project && project !== target.project) throw new Error('Mate runtime was replaced during cleanup.');
    return mate;
  }
  function act(args) {
    owner();
    if (!args || Object.keys(args).some(function (key) { return ['targetToken', 'action', 'patch', 'why', 'requestId'].indexOf(key) < 0; }) || ['profile', 'delete'].indexOf(args.action) < 0 || !string(args.why, 600) || !string(args.requestId, 128) || !string(args.targetToken, 100) || args.action === 'delete' && args.patch !== undefined) throw new Error('Use an exact Mate target, profile or delete action, why and requestId.');
    args = JSON.parse(JSON.stringify(args));
    var target = state.targets.get(args.targetToken), key = JSON.stringify(args), previous = state.requests.get(args.requestId);
    if (!target) throw new Error('Inspect the exact Mate first.');
    if (previous) {
      if (previous.key !== key) throw new Error('requestId already names a different exact Mate action.');
      return previous.promise.then(function (result) {
        service().validateReceipt(owner(), target.mate.id, result.status === 'completed' && args.action === 'delete');
        return result;
      });
    }
    if (state.requests.size >= 100) throw new Error('Too many Mate requests in this query.');
    var promise = Promise.resolve().then(async function () {
      var mate = check(target), changes;
      if (args.action === 'profile') changes = profileChanges(mate, args.patch);
      if (args.action === 'delete' && (mate.primary || mate.builtinKey === 'clay')) throw new Error('Canonical Clay and primary Mates cannot be deleted.');
      var question = (args.action === 'delete' ? 'Permanently delete this Mate and its private directory after runtime cleanup. Stored transcripts and external provider history are retained.' : 'Edit this Mate profile: ' + JSON.stringify(args.patch)) + '\nMate: ' + ((mate.profile || {}).displayName || mate.name || 'New Mate') + ' (' + mate.id + ')\nWhy: ' + args.why;
      if (question.length > 2000) throw new Error('The exact change must fit the owner review.');
      var answer = await ctx.review(question, function () { check(target); });
      if (answer.status !== 'submitted' || !answer.answers.control || answer.answers.control.length !== 1 || answer.answers.control[0] !== 'Apply exact change') return { status: 'cancelled' };
      check(target);
      if (args.action === 'profile') { service().update(owner(), mate.id, changes); return { status: 'completed', persisted: true, mateId: mate.id }; }
      return Object.assign({ status: 'completed', mateId: mate.id }, await service().remove(owner(), mate.id, function (cleanup) { check(target, cleanup); }));
    }).catch(function (error) { return Object.assign({ status: 'failed', error: error.message }, error.productControlResult || {}); });
    state.requests.set(args.requestId, { key: key, promise: promise });
    return promise;
  }
  return { listMates: list, inspectMate: inspect, actMate: act };
}
module.exports = { attachClayMateLifecycle: attachClayMateLifecycle };