Ctrl K
ComunidadGuías…

lib/clay-scheduling.js

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

Archivo original ↗Preguntar a KiBot
var crypto = require('crypto');
var path = require('path');
var authority = require('./canonical-clay-scheduling-authority');
var input = require('./clay-scheduling-input');
var mateInput = require('./mate-schedule-input');
var refs = require('./workspace-query-service');
function hash(value) { return crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex'); }
function text(value, max) { return typeof value === 'string' && value.trim() && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value); }
function attachClayScheduling(ctx) {
  var state = ctx.state;
  var receipts = require('./clay-scheduling-receipts').attachReceipts(ctx);
  function resolve(selector) {
    var principal = ctx.authorize(), slug, mate;
    if (selector.kind === 'mate') {
      var owner = principal.userId || principal.singleUserOwnerId || 'default';
      mate = ctx.resolveMate(owner, selector.mateId);
      if (!mate || mate.lifecycleDeletion || ctx.multiUser() && mate.createdBy !== owner) throw new Error('Scheduled Mate is unavailable for this owner.');
      slug = ctx.mateSlug(selector.mateId);
    } else slug = selector.projectSlug;
    var project = ctx.getProjects().get(slug), status = project && project.getStatus();
    if (!status || !!status.isMate !== (selector.kind === 'mate')) throw new Error('Scheduled target runtime is unavailable.');
    if (mate) {
      if (status.mateId !== mate.id || status.projectOwnerId !== owner || path.resolve(status.path) !== path.resolve(ctx.getMateDir(owner, mate.id))) throw new Error('Scheduled Mate ownership or directory changed.');
    } else ctx.authorizeProject(principal, status);
    var session;
    project.getSessionManager().sessions.forEach(function (candidate) { if (refs.sessionRef(slug, candidate) === selector.sessionRef) session = candidate; });
    if (!session || session.destroying || session.hidden || session.spawn || require('./session-provenance').isWorker(session) || session.delegated || session._delegatedBy || session._pairDelegation || session.mateCreationMode || session.homeDebatePlanning || session.debateSetupMode || session.taskStopRequested || session.abortController && session.abortController.signal.aborted || session._mcpForeignInput || session.sessionVisibility === 'shared' || (session.ownerId || null) !== (principal.userId || null)) throw new Error('Scheduling requires the exact live private owner session; stopped sessions need explicit owner resumption.');
    if (ctx.multiUser() && !require('./mcp-session-access').canUsePersonalConnections(session, project.getSessionManager().sessions)) throw new Error('Foreign input cannot authorize schedules.');
    if (!ctx.multiUser() && (session.history || []).some(function (entry) { return entry.type === 'user_message' && entry.from && entry.from !== 'default'; })) throw new Error('Foreign input cannot authorize schedules.');
    var service = selector.kind === 'mate' ? project.getMateScheduleControl() : project.getScheduledTaskControl();
    if (!service) throw new Error('Scheduling service is unavailable.');
    return { slug: slug, project: project, status: status, session: session, service: service, registry: mate || null };
  }
  function identity(live) { return hash([live.status.path, live.status.projectKnowledgeId, live.status.parentSlug, live.status.changeSetId, live.registry, live.session.sessionOriginId, live.session.ownerId, live.session.vendor, live.session.model, live.session.mode, live.session.runtimeMode, live.session._sdkQueryGeneration]); }
  function serviceTarget(selector) {
    var initial = resolve(selector), fingerprint = identity(initial);
    var token = authority.issue(function (slug, session) {
      var live = resolve(selector);
      if (live.project !== initial.project || live.session !== initial.session || live.service !== initial.service || slug !== initial.slug || session !== initial.session || identity(live) !== fingerprint) throw new Error('The exact scheduling runtime changed. Inspect and review again.');
      return ctx.actor();
    });
    if (selector.kind === 'mate') initial.service.list(initial.session, token); else initial.service.authorize(initial.session, token);
    return { selector: selector, initial: initial, token: token, fingerprint: fingerprint };
  }
  function check(target, exact) {
    var live = resolve(target.selector);
    authority.validate(target.token, live.slug, live.session);
    if (exact) {
      if (Date.now() - target.created > 300000) throw new Error('Scheduling inspection expired.');
      if (hash(snapshot(target)) !== target.snapshot) throw new Error('The exact schedule or draft revision changed. Inspect and review again.');
    }
    return live;
  }
  function snapshot(target) {
    var live = target.initial, op = target.op;
    return target.selector.kind === 'mate' ? op.id ? live.service.read(live.session, op, target.token) : null : live.service.snapshot(live.session, op, target.token);
  }
  function read(args) {
    ctx.authorize(); var parsed = input.readSchema.parse(args), target = serviceTarget(parsed.target), live = target.initial;
    if (parsed.operation.action === 'receipt') { var receipt = receipts.read(parsed.operation.requestId); if (!receipt || receipt.targetKey !== hash(parsed.target)) throw new Error('Scheduling receipt is unavailable for this target.'); return { receipt: true, status: receipt.status, result: receipt.result || null, guidance: receipt.status === 'applying' ? 'Persistence is unknown; inspect the target before any new approved intent.' : 'Historical scheduling acknowledgement, not task completion.' }; }
    return parsed.target.kind === 'mate' ? { schedules: parsed.operation.action === 'list' ? live.service.list(live.session, target.token) : [live.service.read(live.session, parsed.operation, target.token)], timezone: mateInput.timezone() } : live.service.read(live.session, parsed.operation, target.token);
  }
  function begin(args) {
    ctx.authorize(); var selector = input.targetSchema.parse(args.target); serviceTarget(selector);
    state.interview = true;
    return { status: 'interview_started', timezone: mateInput.timezone(), guidance: 'Ask exactly one structured question at a time. Clarify message/task, local date/time, explicitly requested repetition, and project task Driver/Worker runtimes. Inspect the complete intent, then request owner review. A project task still requires final Create task in its existing workbench.' };
  }
  function inspect(args) {
    ctx.authorize(); var parsed = JSON.parse(JSON.stringify(input.inspectSchema.parse(args))), target = serviceTarget(parsed.target), op = parsed.operation;
    var mate = parsed.target.kind === 'mate';
    if (mate !== /_mate$/.test(op.action)) throw new Error('This operation does not match the selected scheduling domain.');
    target.op = op; target.created = Date.now();
    var entry = snapshot(target);
    if (op.id && (mate ? entry.revision : entry.revision) !== op.revision) throw new Error('Schedule revision conflict.');
    if (mate && op.data) mateInput.normalize(op.data, entry);
    if (JSON.stringify(parsed).length > 1400) throw new Error('The exact scheduling intent must fit the existing owner review.');
    target.snapshot = hash(entry);
    if (state.targets.size >= 100) state.targets.delete(state.targets.keys().next().value);
    var token = 'scheduling:' + crypto.randomUUID(); target.presentationValid = function () { check(target, true); }; state.targets.set(token, target);
    return { targetToken: token, target: parsed.target, operation: op, timezone: mateInput.timezone(), expiresInSeconds: 300 };
  }
  function act(args) {
    ctx.authorize();
    if (!args || Object.keys(args).some(function (key) { return ['targetToken', 'requestId', 'why'].indexOf(key) < 0; }) || !text(args.targetToken, 100) || !text(args.requestId, 128) || !text(args.why, 300)) throw new Error('Use the inspected scheduling target, dated requestId and reason.');
    args = Object.assign({}, args);
    var target = state.targets.get(args.targetToken);
    if (!target) throw new Error('Inspect the exact scheduling intent first.');
    var key = hash([target.selector, target.op, args.why]), previous = state.requests.get(args.requestId), durable = receipts.read(args.requestId, key);
    if (previous) { if (previous.key !== key) throw new Error('requestId names another scheduling intent.'); return previous.promise.then(function (result) { check(target, false); return result; }); }
    if (durable) { check(target, false); return durable.status === 'applying' ? { status: 'failed', persisted: 'unknown', retrySafe: false, error: 'This saved scheduling attempt needs inspection; it will not execute again automatically.' } : durable.result; }
    if (state.requests.size >= 100) throw new Error('Too many scheduling requests in this query.');
    var persisted = false;
    var promise = Promise.resolve().then(async function () {
      var live = check(target, true);
      var question = 'Apply this exact scheduling intent (' + mateInput.timezone() + '):\n' + JSON.stringify({ target: target.selector, operation: target.op }) + '\nWhy: ' + args.why;
      if (question.length > 2000) throw new Error('This exact change is too large for the owner review.');
      var response = await ctx.review(question, function () { check(target, true); });
      if (response.status !== 'submitted' || !response.answers.control || response.answers.control.length !== 1 || response.answers.control[0] !== 'Apply exact change') return { status: 'cancelled', persisted: false };
      live = check(target, true);
      receipts.save({ id: args.requestId, key: key, targetKey: hash(target.selector), status: 'applying' });
      live = check(target, true); persisted = 'unknown';
      var result;
      if (target.selector.kind === 'mate') {
        var op = target.op, record;
        if (op.action === 'create_mate') record = live.service.create(live.session, mateInput.normalize(op.data), 'clay_' + hash([ctx.sourceKey, args.requestId]).slice(0, 32), target.token);
        else if (op.action === 'update_mate') record = live.service.update(live.session, op, mateInput.normalize(op.data, live.service.read(live.session, op, target.token)), target.token);
        else record = live.service.cancel(live.session, op, target.token);
        result = { status: 'completed', persisted: true, schedule: record, delivery: record.state === 'active' ? 'queued' : record.state };
      } else result = live.service.apply(live.session, target.op, target.token, args.requestId);
      persisted = result.persisted; check(target, false);
      receipts.save({ id: args.requestId, key: key, targetKey: hash(target.selector), status: 'finished', result: result });
      return result;
    }).catch(function (error) { return { status: 'failed', persisted: persisted, retrySafe: false, error: error.message }; });
    state.requests.set(args.requestId, { key: key, promise: promise }); return promise;
  }
  return { beginSchedulingInterview: begin, readSchedules: read, inspectScheduleChange: inspect, actSchedules: act,
    schedulingQuestionLimit: function () { try { ctx.authorize(); return state.interview ? 1 : null; } catch (error) { return null; } } };
}
module.exports = { attachClayScheduling: attachClayScheduling };