Ctrl K
ComunidadGuías…

lib/project-driver-continuation.js

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

Archivo original ↗Preguntar a KiBot
var crypto = require("crypto");
var buildShape = require("./session-spawn-mcp-server").buildShape;
var sessionProvenance = require("./session-provenance");
var workspaceSessionRef = require("./workspace-query-service").sessionRef;
var workerProposalControl = require("./worker-proposal-control");
var transaction = require("./driver-continuation-transaction");
var continuationAccess = require("./driver-continuation-access");
var continuationLease = require("./driver-continuation-lease");
var continuationStartup = require("./driver-continuation-startup");
var continuationTrigger = require("./driver-continuation-trigger");
var continuationPair = require("./driver-continuation-pair");
var attachContinuationPairTransfer = require("./driver-continuation-pair-transfer").attachContinuationPairTransfer;
var continuationRecord = require("./driver-continuation-record");
var contextKey = continuationRecord.contextKey, findProposal = continuationRecord.findProposal,
  hasEntries = continuationRecord.hasEntries, text = continuationRecord.text, toolResult = continuationRecord.toolResult;
var MAX_FIELD_CHARS = 2400;
var MAX_TOTAL_CHARS = 14000;
var ACTIVE_RUN_STATES = ["armed", "running", "reviewing", "waiting-worker", "waiting-user", "paused"];
var HANDOFF_FIELDS = ["goal", "constraints", "decisions", "rejectedApproaches", "unresolved", "nextAction", "verification", "repositoryState"];
function attachDriverContinuation(ctx) {
  var sm = ctx.sm;
  function isLiveDriver(session) {
    return continuationAccess.isLiveDriver(session, ctx.isMate, sm.sessions, ctx.isDriverOperatedSession);
  }

  function skipPermissions(session) {
    return !!(ctx.dangerouslySkipPermissions || session && (
      session.dangerouslySkipPermissions || session.permissionMode === "bypassPermissions" ||
      session.effectivePermissionMode === "bypassPermissions"
    ));
  }
  function authorize(session, ownerId) {
    if (!isLiveDriver(session) || (session.ownerId || null) !== (ownerId || null)) return false;
    try { return ctx.authorizeSession(session, ownerId) === true; }
    catch (e) { return false; }
  }
  function update(session, proposal, patch) {
    var applied = transaction.persist([{ target: proposal, patch: Object.assign({}, patch, { updatedAt: Date.now() }) }], function () {
      return sm.saveSessionFile(session);
    });
    if (!applied.ok) return false;
    sm.sendToSession(session, Object.assign({
      type: "driver_continuation_update",
      proposalId: proposal.proposalId,
      sourceSessionId: session.localId,
      sourceOriginId: proposal.sourceOriginId,
      projectSlug: ctx.projectSlug,
    }, patch));
    return true;
  }

  function updateTarget(target, patch) {
    return transaction.persist([{ target: target.driverContinuation, patch: patch }], function () {
      return sm.saveSessionFile(target);
    }).ok;
  }

  function normalizedHandoff(args) {
    var handoff = {};
    var total = 0;
    for (var i = 0; i < HANDOFF_FIELDS.length; i++) {
      var field = HANDOFF_FIELDS[i];
      handoff[field] = text(args[field], MAX_FIELD_CHARS);
      total += handoff[field].length;
    }
    if (!handoff.goal || !handoff.nextAction) return { error: "goal and nextAction are required." };
    if (total > MAX_TOTAL_CHARS) return { error: "The continuation handoff is too long." };
    return { value: handoff };
  }

  function proposalReason(args) {
    return text(args && args.reason, 800);
  }

  function propose(args, session, generation) {
    if (!authorize(session, session && session.ownerId || null)) return toolResult({ error: "Driver continuation requires an authorized live Project Driver session." });
    if (skipPermissions(session)) return toolResult({ error: "Driver continuation proposals are disabled while Skip permissions is selected or forced." });
    if (Number.isInteger(generation) && Number(session._sdkQueryGeneration || 0) !== generation) {
      return toolResult({ error: "This continuation tool belongs to an older query." });
    }
    var reason = proposalReason(args);
    var milestone = text(args && args.milestone, 1200);
    var benefit = text(args && args.benefit, 1200);
    var normalized = normalizedHandoff(args || {});
    if (normalized.error) return toolResult({ error: normalized.error });
    var eligibility = continuationTrigger.evaluate(session, ctx, {
      reason: reason, milestone: milestone, benefit: benefit, nextAction: normalized.value.nextAction,
    });
    if (eligibility.error) return toolResult({ error: eligibility.error,
      proposalId: eligibility.previous && eligibility.previous.proposalId || undefined });
    var key = contextKey(session);
    sessionProvenance.ensureOrigin(session);
    var proposal = {
      type: "driver_continuation_proposal",
      proposalId: "continuation_" + crypto.randomUUID(),
      status: "pending",
      reason: reason,
      milestone: milestone,
      benefit: benefit,
      handoff: normalized.value,
      contextKey: key,
      sourceSessionId: session.localId,
      sourceOriginId: session.sessionOriginId,
      sourceSessionRef: workspaceSessionRef(ctx.projectSlug, session),
      ownerId: session.ownerId || null,
      projectSlug: ctx.projectSlug,
      contextStatus: eligibility.status,
      triggerEvidence: eligibility.evidence,
      createdAt: Date.now(),
      updatedAt: Date.now(),
    };
    var posted = transaction.appendPersisted(session, proposal, function () {
      return sm.saveSessionFile(session);
    });
    if (!posted.ok) {
      return toolResult({ error: "The continuation proposal could not be persisted. No review card was posted." });
    }
    sm.sendToSession(session, proposal);
    return toolResult({
      status: "posted",
      proposalId: proposal.proposalId,
      instruction: "The continuation card is waiting for the user's explicit choice. End this turn without accepting it yourself.",
    });
  }

  function unsafeReason(session) {
    if (session.isProcessing || session._queryStarting || session._awaitingTurnResult) return "Wait for the current Driver turn to finish, then retry.";
    if (ctx.pendingMessageQueue && ctx.pendingMessageQueue.hasActive(session)) return "Pending human messages must be handled here before continuing in a new session.";
    if (session.scheduledMessage || session.rateLimitAutoContinuePending) return "A scheduled callback is still attached to this session.";
    if (session.autonomousRun && ACTIVE_RUN_STATES.indexOf(session.autonomousRun.state) !== -1) return "Until complete is still active in this session.";
    if (hasEntries(session.pendingPermissions) || hasEntries(session.pendingAskUser) ||
        hasEntries(session.pendingElicitations) || hasEntries(session.pendingUserDialogs)) {
      return "This session is waiting for a permission or user-input response.";
    }
    if (workerProposalControl.pendingProposal(session)) return "A Split Worker proposal is still waiting for a decision.";
    var pair = continuationPair.inspect(session, ctx);
    if (!pair.ok) return pair.error;
    if (!pair.group && (session._pairDelegation || continuationPair.activeFollowups(session))) {
      return "Split Worker work or follow-ups are still pending.";
    }
    if (session.pendingPush && session.pendingPush.length) return "Messages are still waiting for delivery in this session.";
    if (session.taskStopRequested || session.destroying || session._runtimeRefreshRequested) return "This session is changing state; retry when it is idle.";
    return "";
  }

  function actorForResponse(ws, source) {
    var userId = ws && ws._clayUser ? ws._clayUser.id : null;
    if (ws._clayActiveSession !== source.localId) throw new Error("The continuation decision belongs to another source session.");
    if (ctx.isMultiUser()) {
      if (!userId || source.ownerId !== userId) throw new Error("Only the source Driver owner can decide this continuation.");
    } else if (source.ownerId) {
      throw new Error("The source Driver owner is unavailable.");
    }
    if (!authorize(source, userId)) throw new Error("Driver continuation access changed.");
    return userId;
  }

  function successorFor(proposal) {
    var found = null;
    sm.sessions.forEach(function (candidate) {
      if (found || !candidate || !candidate.driverContinuation) return;
      if (candidate.driverContinuation.proposalId === proposal.proposalId &&
          candidate.driverContinuation.sourceOriginId === proposal.sourceOriginId &&
          candidate.driverContinuation.projectSlug === ctx.projectSlug &&
          (candidate.ownerId || null) === proposal.ownerId) found = candidate;
    });
    return found;
  }

  function startupProven(candidate) {
    var relation = candidate && candidate.driverContinuation;
    return !!(candidate && candidate.cliSessionId && relation &&
      relation.startupProof === "initialized" && Number.isInteger(relation.acceptedQueryGeneration));
  }

  function acceptanceError(ws, source, proposal) {
    actorForResponse(ws, source);
    if (skipPermissions(source)) return "Driver continuation is disabled while Skip permissions is selected or forced.";
    if (proposal.contextKey !== contextKey(source)) return "The source work changed after this proposal. Ask the Driver to prepare a fresh continuation.";
    return unsafeReason(source);
  }

  function releaseLease(source, lease) {
    if (!continuationLease.end(source, lease)) return false;
    if (typeof ctx.consumePendingMessage === "function") {
      setImmediate(function () { ctx.consumePendingMessage(source); });
    }
    return true;
  }

  var pairTransferControl = attachContinuationPairTransfer({ ctx: ctx, update: update,
    beginLease: continuationLease.begin, releaseLease: releaseLease });

  function commitRelation(source, proposal, target) {
    var relation = target.driverContinuation;
    var transactionId = relation.transactionId || proposal.transactionId || "transition_" + crypto.randomUUID();
    if (!startupProven(target)) {
      return { ok: false, error: "The successor has no verified startup proof." };
    }
    if (relation.status === "starting") {
      if (!updateTarget(target, { status: "prepared", transactionId: transactionId, preparedAt: Date.now() })) {
        return { ok: false, error: "The prepared successor relationship could not be persisted." };
      }
    }
    if (proposal.status !== "accepted" && proposal.transactionId !== transactionId) {
      if (!update(source, proposal, { status: "starting", transactionId: transactionId, targetSessionId: target.localId,
        targetOriginId: target.sessionOriginId, error: null })) {
        return { ok: false, error: "The source continuation relationship could not be persisted." };
      }
    }
    if (relation.status !== "accepted") {
      if (!updateTarget(target, { status: "accepted", transactionId: transactionId, acceptedAt: Date.now() })) {
        return { ok: false, error: "The accepted successor relationship could not be persisted." };
      }
    }
    if (proposal.status !== "accepted") {
      if (!update(source, proposal, { status: "accepted", transactionId: transactionId, targetSessionId: target.localId,
        targetOriginId: target.sessionOriginId, acceptedAt: Date.now(), error: null })) {
        return { ok: false, error: "The accepted source relationship could not be persisted." };
      }
    }
    if (!transaction.accepted(proposal, relation)) return { ok: false, error: "The continuation relationship is not durably complete." };
    return { ok: true };
  }

  async function accept(ws, source, proposal) {
    var ownerId = actorForResponse(ws, source);
    var reason = acceptanceError(ws, source, proposal);
    if (reason) {
      if (proposal.contextKey !== contextKey(source)) {
        update(source, proposal, { status: "superseded", error: reason });
      }
      throw new Error(reason);
    }
    var existing = successorFor(proposal);
    if (existing) {
      if (!authorize(existing, ownerId)) throw new Error("Successor continuation access changed.");
      if (!startupProven(existing)) {
        if (!existing.queryInstance && !existing.isProcessing && !existing._queryStarting) {
          sm.deleteSessionQuiet(existing.localId);
          update(source, proposal, { status: "superseded", targetSessionId: null, targetOriginId: null,
            error: "The prior successor did not survive startup. Prepare a fresh continuation proposal." });
          throw new Error("The prior successor did not survive startup. Prepare a fresh continuation proposal.");
        }
        throw new Error("The successor is still starting. Wait for initialization confirmation before retrying.");
      }
      var retainedLease = source._driverContinuationLease;
      if (!retainedLease) {
        var recoveredPair = pairTransferControl.recoverIfNeeded(source, existing, proposal);
        if (!recoveredPair.ok) throw new Error(recoveredPair.error);
        retainedLease = recoveredPair.lease;
      }
      if (retainedLease && retainedLease.proposalId === proposal.proposalId && retainedLease.target === existing) {
        var retryReason = pairTransferControl.retryError(source, existing, retainedLease, function () {
          return acceptanceError(ws, source, proposal);
        });
        if (retryReason) throw new Error(retryReason);
        var existingPairCommit = pairTransferControl.commitOrCancel(retainedLease);
        if (!existingPairCommit.ok) throw new Error(existingPairCommit.error);
      }
      var existingCommit = commitRelation(source, proposal, existing);
      if (!existingCommit.ok) throw new Error(existingCommit.error);
      if (retainedLease && retainedLease.proposalId === proposal.proposalId && retainedLease.target === existing) {
        releaseLease(source, retainedLease);
      }
      sm.switchSession(existing.localId, ws);
      return { ok: true, status: "accepted", targetSessionId: existing.localId };
    }
    if (proposal.status === "starting") {
      if (!update(source, proposal, { status: "pending", error: "The prior startup did not leave a durable successor. Retry when ready." })) {
        throw new Error("The stale startup state could not be persisted.");
      }
    }
    if (proposal.status !== "pending") throw new Error("This continuation proposal has already been resolved.");
    var target = sm.createSessionRaw({
      ownerId: source.ownerId || null,
      sessionVisibility: source.sessionVisibility,
      vendor: source.vendor,
      model: source.model,
      effort: source.effort,
      permissionMode: source.permissionMode || null,
    });
    target.mcpPermissionModeOverrides = Object.assign({}, source.mcpPermissionModeOverrides || {});
    target.effectivePermissionMode = null;
    target.title = (source.title || "Continued work") + " · Continued";
    target.driverContinuation = {
      proposalId: proposal.proposalId,
      sourceOriginId: proposal.sourceOriginId,
      sourceSessionRef: proposal.sourceSessionRef,
      projectSlug: ctx.projectSlug,
      status: "starting",
      createdAt: Date.now(),
    };
    sm.sendAndRecord(target, {
      type: "driver_continuation_context",
      proposalId: proposal.proposalId,
      sourceSessionId: source.localId,
      sourceOriginId: proposal.sourceOriginId,
      sourceSessionRef: proposal.sourceSessionRef,
      projectSlug: ctx.projectSlug,
      reason: proposal.reason,
      goal: proposal.handoff.goal,
      nextAction: proposal.handoff.nextAction,
      repositoryState: proposal.handoff.repositoryState,
      _ts: Date.now(),
    });
    if (!update(source, proposal, { status: "starting", targetSessionId: target.localId, targetOriginId: target.sessionOriginId, error: null })) {
      sm.deleteSessionQuiet(target.localId);
      var persistenceError = "The source continuation proposal could not be persisted.";
      update(source, proposal, { status: "pending", targetSessionId: null, targetOriginId: null, error: persistenceError });
      throw new Error(persistenceError);
    }
    target.isProcessing = true;
    target.sentToolResults = {};
    ctx.onProcessingChanged();
    var lease = continuationLease.begin(source, target, proposal.proposalId);
    var pairTransfer = pairTransferControl.begin(source, target, proposal, lease);
    if (!pairTransfer.ok) {
      releaseLease(source, lease);
      sm.deleteSessionQuiet(target.localId);
      update(source, proposal, { status: "pending", targetSessionId: null, targetOriginId: null, error: pairTransfer.error });
      throw new Error(pairTransfer.error);
    }
    function startupAcceptanceError() {
      return pairTransferControl.validationError(source, target, lease, function () {
        return acceptanceError(ws, source, proposal);
      });
    }
    var started = false;
    var retainTarget = false;
    var timedOut = false;
    function lateStartup(lifecycle) {
      continuationStartup.handleLate({ sm: sm, source: source, target: target, proposal: proposal, lease: lease,
        validate: startupAcceptanceError,
        commitPair: function () { return pairTransferControl.commit(lease); },
        release: function () { releaseLease(source, lease); },
        update: function (patch) { return update(source, proposal, patch); } }, lifecycle);
    }
    try {
      var sdk = ctx.getSdk();
      if (!sdk || typeof sdk.startQueryWithAcceptance !== "function") throw new Error("SDK bridge cannot confirm successor startup.");
      var lifecycle = await sdk.startQueryWithAcceptance(target, continuationStartup.compactPrompt(proposal), undefined,
        ctx.getLinuxUserForSession(target), function () {
          return continuationLease.guard(lease, startupAcceptanceError);
        }, lateStartup);
      started = !!(lifecycle && lifecycle.accepted === true);
      retainTarget = !!(lifecycle && lifecycle.initialAccepted === true && lifecycle.queryAlive === true);
      timedOut = !!(lifecycle && lifecycle.timedOut === true);
      if (!started) throw new Error(lease.error || lifecycle && lifecycle.reason || "The successor agent did not accept the compact handoff.");
      if (!Number.isInteger(lifecycle.queryGeneration)) throw new Error("The successor has no verified startup proof.");
      target.driverContinuation.acceptedQueryGeneration = lifecycle.queryGeneration;
      target.driverContinuation.acceptedInitialAt = Date.now();
      target.driverContinuation.startupProof = "initialized";
      if (!await continuationStartup.waitForDurableIdentity(sm, target, ctx.identityWaitMs)) throw new Error("The successor accepted its handoff but did not receive a durable session identity in time.");
      if (!transaction.saveObserved(function () { return sm.saveSessionFile(target); })) {
        lease.waitingCommit = true;
        throw new Error("The verified successor startup could not be persisted. Retry without starting another successor.");
      }
      try { reason = startupAcceptanceError(); }
      catch (revalidationError) { reason = revalidationError.message || String(revalidationError); }
      if (reason) {
        lease.cancel(reason);
        throw new Error(reason);
      }
      lease.waitingCommit = true;
      var pairCommitted = pairTransferControl.commit(lease);
      if (!pairCommitted.ok) {
        lease.cancel(pairCommitted.error);
        throw new Error(pairCommitted.error);
      }
      var committed = commitRelation(source, proposal, target);
      if (!committed.ok) throw new Error(committed.error);
      releaseLease(source, lease);
      sm.broadcastSessionList();
      sm.switchSession(target.localId, ws);
      return { ok: true, status: "accepted", targetSessionId: target.localId, ownerId: ownerId };
    } catch (error) {
      if (timedOut && retainTarget && !lease.cancelled) lease.waitingLateStartup = true;
      else if (!lease.waitingCommit && !lease.cancelled) {
        pairTransferControl.rollback(lease);
        releaseLease(source, lease);
      }
      if (lease.cancelled && !lease.cleanupComplete) {
        error = new Error(lease.cleanupError || error.message || String(error));
      } else if (!lease.cleanupComplete && started !== true && retainTarget !== true) {
        pairTransferControl.rollback(lease);
        sm.deleteSessionQuiet(target.localId);
      }
      if (proposal.status !== "accepted") update(source, proposal, {
        status: lease.cancelled ? "superseded" : "pending", targetSessionId: sm.sessions.has(target.localId) ? target.localId : null,
        targetOriginId: sm.sessions.has(target.localId) ? target.sessionOriginId : null,
        error: error.message || String(error),
      });
      throw error;
    }
  }
  async function respond(ws, msg) {
    var ownerId = ws && ws._clayUser ? ws._clayUser.id : null;
    var source = continuationAccess.findOwnedSessionByOrigin(sm.sessions, msg.sourceOriginId, ownerId);
    if (!source) throw new Error("The source Driver session no longer exists.");
    actorForResponse(ws, source);
    var proposal = findProposal(source, msg.proposalId);
    if (!proposal || proposal.ownerId !== (source.ownerId || null) || proposal.projectSlug !== ctx.projectSlug ||
        proposal.sourceOriginId !== source.sessionOriginId || proposal.sourceOriginId !== msg.sourceOriginId) {
      throw new Error("The exact continuation proposal was not found.");
    }
    if (msg.accepted !== true) {
      if (proposal.status !== "pending") throw new Error("This continuation proposal has already been resolved.");
      var declinedAt = Date.now();
      var declined = transaction.persist([
        { target: source, patch: { driverContinuationDecline: { proposalId: proposal.proposalId, declinedAt: declinedAt, permanent: true } } },
        { target: proposal, patch: { status: "declined", declinedAt: declinedAt, error: null, updatedAt: declinedAt } },
      ], function () { return sm.saveSessionFile(source); });
      if (!declined.ok) throw new Error("The decision to stay could not be persisted.");
      sm.sendToSession(source, { type: "driver_continuation_update", proposalId: proposal.proposalId,
        sourceSessionId: source.localId, sourceOriginId: proposal.sourceOriginId, projectSlug: ctx.projectSlug,
        status: "declined", declinedAt: declinedAt, error: null });
      return { ok: true, status: "declined", sourceSessionId: source.localId };
    }
    var result = await accept(ws, source, proposal);
    result.sourceSessionId = source.localId;
    return result;
  }

  function openSource(ws, msg) {
    var target = sm.sessions.get(ws._clayActiveSession);
    var relation = target && target.driverContinuation;
    var ownerId = ws && ws._clayUser ? ws._clayUser.id : null;
    if (!target || !relation || relation.proposalId !== msg.proposalId || relation.sourceOriginId !== msg.sourceOriginId) {
      throw new Error("The exact continuation source link is stale.");
    }
    if (!authorize(target, ownerId)) throw new Error("Continuation source access changed.");
    var source = null;
    sm.sessions.forEach(function (candidate) {
      if (source || !candidate) return;
      if (candidate.sessionOriginId === relation.sourceOriginId && (candidate.ownerId || null) === (target.ownerId || null)) source = candidate;
    });
    if (!source || !authorize(source, ownerId)) throw new Error("The original Driver session is no longer available.");
    sm.switchSession(source.localId, ws);
    return { ok: true, status: "source_opened", targetSessionId: source.localId };
  }

  function handleMessage(ws, msg) {
    if (msg.type !== "driver_continuation_response" && msg.type !== "driver_continuation_open_source") return false;
    var requestId = text(msg.requestId, 128);
    var exactProject = text(msg.projectSlug, 200);
    var sourceOriginId = text(msg.sourceOriginId, 200);
    var operationName = msg.type === "driver_continuation_open_source" ? "open_source" : "decision";
    if (!requestId || exactProject !== ctx.projectSlug || !sourceOriginId) {
      ctx.sendTo(ws, { type: "driver_continuation_result", requestId: requestId, operation: operationName,
        projectSlug: ctx.projectSlug, sourceOriginId: sourceOriginId, proposalId: msg.proposalId,
        sourceSessionId: null, ok: false, error: "The continuation request correlation is incomplete or stale." });
      return true;
    }
    var operation = msg.type === "driver_continuation_open_source"
      ? Promise.resolve().then(function () { return openSource(ws, msg); })
      : respond(ws, msg);
    operation.then(function (result) {
      ctx.sendTo(ws, Object.assign({ type: "driver_continuation_result", requestId: requestId, operation: operationName,
        projectSlug: ctx.projectSlug, sourceOriginId: sourceOriginId, proposalId: msg.proposalId,
        sourceSessionId: null }, result));
    }).catch(function (error) {
      ctx.sendTo(ws, { type: "driver_continuation_result", requestId: requestId, operation: operationName,
        projectSlug: ctx.projectSlug, sourceOriginId: sourceOriginId, proposalId: msg.proposalId,
        sourceSessionId: null, ok: false, error: error.message || String(error) });
    });
    return true;
  }

  function getToolDefs(session, generation) {
    if (!authorize(session, session && session.ownerId || null) || skipPermissions(session)) return [];
    if (continuationTrigger.historicalProposal(session) || session.driverContinuationDecline) return [];
    return [{
      name: "propose_driver_continuation",
      description: "Propose, but never accept, the source session's one user-reviewed proactive continuation. Clay requires measured current-context pressure or a completed recorded compaction, a completed milestone, a specific reason and benefit, and a prepared next action. Never claim guaranteed savings.",
      inputSchema: buildShape({
        reason: { type: "string", description: "Why moving at this specific boundary is useful now." },
        milestone: { type: "string", description: "The concrete milestone completed before this safe transition boundary." },
        benefit: { type: "string", description: "The concrete continuity benefit, without unsupported savings claims." },
        goal: { type: "string" }, constraints: { type: "string" },
        decisions: { type: "string", description: "Decisions and why they were made." },
        rejectedApproaches: { type: "string" }, unresolved: { type: "string" },
        nextAction: { type: "string" }, verification: { type: "string" }, repositoryState: { type: "string" },
      }, ["reason", "milestone", "benefit", "goal", "nextAction"]),
      handler: function (args) { return propose(args || {}, session, generation); },
    }];
  }

  function getSystemPrompt(session) {
    if (!authorize(session, session && session.ownerId || null) || skipPermissions(session)) return "";
    if (continuationTrigger.historicalProposal(session) || session.driverContinuationDecline) return "";
    return continuationTrigger.prompt(session) + " Never propose while a Split Worker is attached or work or user input is pending. " +
      "After posting, end the turn. Same-project bounded history can be retrieved later; do not request global source reading.";
  }

  return { getSystemPrompt: getSystemPrompt, getToolDefs: getToolDefs, handleMessage: handleMessage, respond: respond, openSource: openSource };
}

module.exports = { attachDriverContinuation: attachDriverContinuation, contextKey: contextKey, findProposal: findProposal };