Ctrl K
ComunidadGuías…

lib/mate-context-service.js

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

Archivo original ↗Preguntar a KiBot
// Restricted contextual access. References originate on authenticated app sockets;
// model arguments and submitted attachments never create or widen a grant.
var crypto = require('crypto');
var refs = require('./workspace-query-service');
var provenance = require('./session-provenance');
var personal = require('./mcp-session-access');
var z = require('zod');
var preparationSchema = z.object({ type: z.literal('mate_context_prepare'), requestId: z.string().max(100).optional(), available: z.boolean(), mateId: z.string().max(128).optional(), sessionId: z.number().int().positive().optional() }).strict();
function attachMateContextService(ctx) {
  var access = require('./workspace-query-access').attachWorkspaceQueryAccess(ctx);
  var views = new WeakMap(), pending = new Map(), active = new WeakMap();
  function owner(ws) { return ctx.isMultiUser() ? ws && ws._clayUser && ws._clayUser.id : null; }
  function privateSession(session, manager, id) {
    if (!session || manager.sessions.get(session.localId) !== session || session.hidden || session.destroying || session.spawn || provenance.isWorker(session) ||
      session.sessionVisibility === 'shared' || session.delegated || session._delegatedBy || session._pairDelegation || session._mcpForeignInput ||
      session.homeDebatePlanning || session.debateSetupMode || session.mateCreationMode || session._mentionInProgress) throw new Error('A live private owner conversation is required.');
    if (ctx.isMultiUser() ? !id || session.ownerId !== id || !ctx.findUser(id) || !personal.canUsePersonalConnections(session, manager.sessions) : !!session.ownerId ||
      (session.history || []).some(function (entry) { return entry.type === 'user_message' && entry.from && entry.from !== 'default'; })) throw new Error('Owner-authored input is required.');
  }
  function source(id, mateId, sessionId) {
    var mate = ctx.resolveMate(id, mateId), project = ctx.getProjects().get('mate-' + mateId);
    var status = project && project.getStatus(), manager = project && project.getSessionManager();
    if (!mate || mate.builtinKey === 'clay' || mate.archived || mate.lifecycleDeletion || !status || !status.isMate || status.mateId !== mateId || (ctx.isMultiUser() && (mate.createdBy !== id || status.projectOwnerId !== id))) throw new Error('This private Mate is unavailable.');
    var session = manager.sessions.get(sessionId);
    privateSession(session, manager, id);
    var user = ctx.isMultiUser() && ctx.findUser(id);
    return { mate: mate, revision: JSON.stringify(mate), project: project, session: session, manager: manager, path: status.path, osIdentity: JSON.stringify(user && [user.id, user.linuxUser || null]) };
  }
  function selected(id, viewer) {
    var project = ctx.getProjects().get(viewer.slug), status = project && project.getStatus();
    var principal = { userId: id, singleUserOwnerId: id };
    if (!status || status.isMate || !access.evaluate(principal, status).accessible) throw new Error('The selected ordinary project is unavailable.');
    var manager = project.getSessionManager(), session = manager.sessions.get(viewer.sessionId);
    privateSession(session, manager, id);
    return { project: project, session: session, path: status.path, revision: JSON.stringify([status.path, status.projectOwnerId, session.sessionOriginId, session.cliSessionId, session.title, session.history]),
      summary: { project: String(status.title || status.project || status.slug).slice(0, 160), conversation: String(session.title || 'New conversation').slice(0, 160) } };
  }
  function validate(entry) {
    if (!entry || Date.now() - entry.created > 300000) throw new Error('Context expired. Send a new message from the current view.');
    var viewer = ctx.getOwnerUi().check(entry.owner, entry.viewer);
    if (views.get(viewer.ws) !== entry.view) throw new Error('The current view changed. Send a new message from that view.');
    var live = source(entry.owner, entry.mateId, entry.source.session.localId), target = selected(entry.owner, viewer);
    if (live.project !== entry.source.project || live.session !== entry.source.session || live.revision !== entry.source.revision || live.path !== entry.source.path || live.osIdentity !== entry.source.osIdentity) throw new Error('The Mate was changed or replaced.');
    if (target.project !== entry.target.project || target.session !== entry.target.session || target.revision !== entry.target.revision) throw new Error('The selected conversation changed or was replaced.');
    return { source: live, target: target };
  }
  function send(ws, value) { if (ws.readyState === 1) ws.send(JSON.stringify(value)); }
  function handleMessage(ws, msg) {
    if (msg.type !== 'mate_context_prepare') return false;
    var id = owner(ws), requestId = typeof msg.requestId === 'string' ? msg.requestId.slice(0, 100) : null;
    try {
      preparationSchema.parse(msg);
      var viewer = ctx.getOwnerUi().list(id).find(function (item) { return item.ws === ws && item.supported; });
      if (!viewer) throw new Error('The originating window is unavailable.');
      var view = {}; views.set(ws, view);
      if (msg.available !== true) throw new Error('This view has no supported conversation selection.');
      var live = source(id, msg.mateId, msg.sessionId), target = selected(id, viewer);
      pending.forEach(function (entry, key) { if (Date.now() - entry.created > 300000 || entry.viewer.ws === ws) pending.delete(key); });
      if (pending.size >= 100) throw new Error('Context capacity is temporarily full.');
      var ref = 'mate-context:' + crypto.randomUUID();
      pending.set(ref, { owner: id, viewer: viewer, view: view, mateId: msg.mateId, source: live, target: target, created: Date.now() });
      send(ws, { type: 'mate_context_prepared', requestId: requestId, contextRef: ref, summary: target.summary, expiresAt: Date.now() + 300000 });
    } catch (error) { send(ws, { type: 'mate_context_prepared', requestId: requestId, contextRef: null, error: error.message }); }
    return true;
  }
  function activate(ws, session, ref, message) {
    active.delete(session);
    var original = pending.get(ref);
    var entry = original && Object.assign({}, original);
    if (!entry) throw new Error('No current context was shared.');
    var live = validate(entry);
    if (owner(ws) !== entry.owner || live.source.session !== session || !live.source.project.clients.has(ws) || ws._clayActiveSession !== session.localId || ws._clayAuthCheck && !ws._clayAuthCheck()) throw new Error('Context belongs to another authenticated conversation.');
    entry.message = message; entry.ref = ref; entry.requests = new Map(); entry.deliveries = new Set(); active.set(session, entry);
    return { contextRef: ref, summary: entry.target.summary, limits: 'Selected conversation metadata; read_selected_context returns at most 20 redacted text turns (24000 characters). No screen capture, Issues, documents, files or hidden settings. Opening this conversation requires exact owner review.' };
  }
  function clear(session) { active.delete(session); }
  function assertActive(session, ref) {
    var entry = active.get(session);
    var latest = (session.history || []).filter(function (item) { return item.type === 'user_message'; }).slice(-1)[0];
    if (!entry || latest !== entry.message || entry.ref !== ref || session.taskStopRequested || session.abortController && session.abortController.signal.aborted) throw new Error('Shared context was interrupted.');
    validate(entry);
  }
  function bind(input) {
    var session = input.session, generation = Number(session._sdkQueryGeneration || 0), entry = active.get(session), original;
    try { original = source(ctx.isMultiUser() ? input.projectOwnerId : null, input.mateId, session.localId); }
    catch (error) { return null; }
    if (original.session !== session || input.projectSlug !== 'mate-' + input.mateId || session.taskStopRequested || session.abortController && session.abortController.signal.aborted) return null;
    function base() {
      var live = source(ctx.isMultiUser() ? input.projectOwnerId : null, input.mateId, session.localId);
      if (live.session !== session || live.project !== original.project || live.revision !== original.revision || live.path !== original.path || live.osIdentity !== original.osIdentity || Number(session._sdkQueryGeneration || 0) !== generation || session.taskStopRequested || session.abortController && session.abortController.signal.aborted) throw new Error('This exact private Mate query is unavailable.');
    }
    function check() {
      base();
      if (!entry || active.get(session) !== entry || Number(session._sdkQueryGeneration || 0) !== generation || session.taskStopRequested || session._pendingMessageDrainPaused || session.abortController && session.abortController.signal.aborted) throw new Error('This context query is unavailable or stopped.');
      var latest = (session.history || []).filter(function (item) { return item.type === 'user_message'; }).slice(-1)[0];
      if (latest !== entry.message || input.mateId !== entry.mateId || input.projectSlug !== 'mate-' + entry.mateId || (ctx.isMultiUser() && input.projectOwnerId !== entry.owner)) throw new Error('Context belongs to another exact owner query.');
      return validate(entry);
    }
    function reference(args) { if (!args || args.contextRef !== (entry && entry.ref)) throw new Error('Use only the context reference returned for this query.'); return check(); }
    return {
      current: function () {
        base();
        if (!entry) return { status: 'unavailable', supported: ['selected_conversation'], unavailable: ['issues', 'documents', 'tabs', 'screen_capture'] };
        check(); return { status: 'shared_snapshot', contextRef: entry.ref, summary: entry.target.summary, expiresAt: entry.created + 300000, supported: ['selected_conversation'], unavailable: ['issues', 'documents', 'tabs', 'screen_capture'], untrusted: true };
      },
      read: async function (args) {
        var live = reference(args), bound = ctx.workspace.bindProjectSession({ projectSlug: entry.viewer.slug, session: live.target.session, viewerId: entry.owner });
        if (!bound) throw new Error('The selected conversation is unavailable.');
        var result = await bound.readProjectSession({ projectSlug: entry.viewer.slug, sessionRef: refs.sessionRef(entry.viewer.slug, live.target.session), limit: 20 });
        reference(args);
        var remaining = 24000;
        var turns = result.turns.map(function (turn) { var text = turn.text.slice(0, remaining); remaining -= text.length; return { role: turn.role, text: text }; }).filter(function (turn) { return !!turn.text; });
        return { summary: entry.target.summary, turns: turns, truncated: result.total > turns.length || remaining === 0, untrusted: true };
      },
      navigate: function (args) {
        reference(args);
        var key = JSON.stringify([args.contextRef, args.why]), old = entry.requests.get(args.requestId);
        if (old) { if (old.key !== key) throw new Error('This requestId already names another intent.'); return old.promise; }
        if (entry.requests.size >= 20) throw new Error('Too many navigation requests in this context.');
        var promise = Promise.resolve().then(async function () {
          var live = check();
          var answer = await live.source.project.requestProductControlReview(session,
            'Open the selected conversation: ' + entry.target.summary.conversation + '\nProject: ' + entry.target.summary.project + '\nWhy: ' + args.why,
            check, { kind: 'product-control', action: 'Open conversation', target: entry.target.summary.conversation, context: 'In ' + entry.target.summary.project + ', in the window that shared this context.', reason: args.why });
          check();
          if (answer.status !== 'submitted' || !answer.answers.control || answer.answers.control.length !== 1 || answer.answers.control[0] !== 'Apply exact change') return { status: 'cancelled' };
          var result = ctx.getOwnerUi().deliver(entry.owner, entry.viewer, { projectSlug: entry.viewer.slug, sessionId: entry.target.session.localId, sessionOriginId: entry.target.session.sessionOriginId, contextualSourceMateId: entry.mateId }, check);
          if (result.deliveryId) entry.deliveries.add(result.deliveryId);
          return result;
        }).catch(function (error) { return { status: 'failed', retrySafe: false, error: error.message }; });
        entry.requests.set(args.requestId, { key: key, promise: promise }); return promise;
      },
      receipt: function (args) { reference(args); if (!entry.deliveries.has(args.deliveryId)) throw new Error('Receipt belongs to another context.'); return ctx.getOwnerUi().read(entry.owner, args.deliveryId); }
    };
  }
  return { handleMessage: handleMessage, activate: activate, clear: clear, assertActive: assertActive, bind: bind };
}
module.exports = { attachMateContextService: attachMateContextService };