lib/project-issue-launch.js
Rama main · réplica de lectura del repositorio original.
// Creates one visible Driver session for an exact Issue revision.
var yoke = require("./yoke");
var provenance = require("./session-provenance");
var sessionVisibility = require("./session-visibility");
function attachIssueLaunch(ctx) {
var requests = new Map();
var operations = new Map();
function osUsersEnabled() {
return typeof ctx.osUsers === "function" ? ctx.osUsers() === true : ctx.osUsers === true;
}
function requestFingerprint(args) {
return JSON.stringify({ ref: args.ref, expectedRevision: args.expectedRevision });
}
function trimRequests() {
if (requests.size <= 200) return;
for (var pair of requests) {
if (pair[1].settled) { requests.delete(pair[0]); return; }
}
}
function record(session, event) {
if (ctx.sm && typeof ctx.sm.sendAndRecord === "function") ctx.sm.sendAndRecord(session, event);
else {
session.history = session.history || [];
session.history.push(event);
if (ctx.sm && typeof ctx.sm.appendToSessionFile === "function") ctx.sm.appendToSessionFile(session, event);
}
}
function sendStatus(session, status) {
if (ctx.sm && typeof ctx.sm.sendToSession === "function") ctx.sm.sendToSession(session, { type: "status", status: status });
}
function lastLaunchError(session, fromIndex) {
var history = session.history || [];
for (var i = history.length - 1; i >= fromIndex; i--) {
if (history[i] && history[i].type === "error" && history[i].text) return String(history[i].text);
}
return "The Driver session could not start its agent runtime.";
}
function failLaunch(session, fromIndex, error) {
var message = error && error.message ? error.message : lastLaunchError(session, fromIndex);
if (lastLaunchError(session, fromIndex) === "The Driver session could not start its agent runtime.") {
record(session, { type: "error", text: "Could not start issue work: " + message, issueRef: session.issueOrigin && session.issueOrigin.ref });
}
session.isProcessing = false;
if (typeof ctx.onProcessingChanged === "function") ctx.onProcessingChanged();
sendStatus(session, "idle");
var launchError = new Error(message || "The Driver session could not start its agent runtime.");
launchError.sessionId = session.localId;
return launchError;
}
async function run(ws, actor, actorId, args, bound) {
var entry = bound.readIssue({ ref: args.ref });
if (entry.revision !== args.expectedRevision) throw new Error("Issue revision conflict. Reload before starting work.");
if (entry.status === "resolved" || entry.status === "closed") throw new Error("Reopen this issue before starting work.");
if ((entry.linkedWorkSessions || []).length >= 40) throw new Error("This issue has reached its work-session limit.");
var expectedLinuxUser = typeof ctx.getLinuxUserForSession === "function" ? ctx.getLinuxUserForSession({ ownerId: actorId }) : null;
if (osUsersEnabled() && !expectedLinuxUser) throw new Error("An OS identity is required to start work.");
var runtime = await ctx.resolveDefaultAi(ws);
if (ws.readyState !== 1 || ws._clayUser !== actor || actorId !== (ws._clayUser && ws._clayUser.id || null)) {
throw new Error("The requesting connection changed.");
}
if (typeof ctx.canStartWork === "function" && ctx.canStartWork(ws, actor) !== true) throw new Error("Issue work is no longer allowed from this connection.");
entry = bound.readIssue({ ref: args.ref });
if (entry.revision !== args.expectedRevision) throw new Error("Issue revision conflict. Reload before starting work.");
if (entry.status === "resolved" || entry.status === "closed") throw new Error("Reopen this issue before starting work.");
if (!runtime || !runtime.ready || !ctx.sm || (ctx.sm.installedVendors || []).indexOf(runtime.vendor) < 0) {
throw new Error(runtime && runtime.error || "Default AI is unavailable.");
}
if (yoke.sessionTools.capability(runtime.vendor).kind === "none") throw new Error("The selected provider does not support Issues tools.");
var liveLinuxUser = typeof ctx.getLinuxUserForSession === "function" ? ctx.getLinuxUserForSession({ ownerId: actorId }) : null;
if (osUsersEnabled() && liveLinuxUser !== expectedLinuxUser) throw new Error("The OS identity changed while issue work was being prepared.");
var projectAccess = typeof ctx.getProjectAccess === "function" ? ctx.getProjectAccess() : null;
var session = ctx.sm.createSessionRaw({ vendor: runtime.vendor, model: runtime.model, effort: runtime.effort || null,
mode: "gui", ownerId: actor && actor.id || null, sessionVisibility: sessionVisibility.defaultForProject(projectAccess) });
provenance.ensureOrigin(session);
if (session.hidden === true || session.delegated === true || provenance.isWorker(session)) {
ctx.sm.deleteSession(session.localId);
throw new Error("A visible Driver session is required to start issue work.");
}
var linuxUser = typeof ctx.ensureProjectAccessForSession === "function"
? ctx.ensureProjectAccessForSession(session) : expectedLinuxUser;
if (osUsersEnabled() && (!linuxUser || linuxUser !== expectedLinuxUser)) {
ctx.sm.deleteSession(session.localId);
throw new Error("The OS identity changed before issue work could start.");
}
session.issueOrigin = { ref: entry.ref, revision: args.expectedRevision, requestId: args.requestId, sessionOriginId: session.sessionOriginId };
if (entry.github && entry.github.url) session.githubLinks = [Object.assign({}, entry.github)];
try { bound.linkWorkSession(args, session); }
catch (error) { ctx.sm.deleteSession(session.localId); throw error; }
session.title = "Issue · " + entry.title;
session.titleManuallySet = true;
ctx.sm.saveSessionFile(session);
var prompt = "Work on project issue " + entry.ref + ". Read it with read_issue, inspect the repository, implement the requested outcome and verify it. Update this same issue as work progresses. Do not commit, publish, or create a PR unless explicitly authorized. If implementation is verified but no commit is authorized, keep it in_progress and record that state. Resolve only with verified commit evidence. Session completion never resolves the issue automatically.\n\nRequested issue:\n" + JSON.stringify({ title: entry.title, summary: entry.summary, body: entry.body });
ctx.sm.switchSession(session.localId, ws);
ws._clayActiveSession = session.localId;
record(session, { type: "user_message", text: prompt, issueRef: entry.ref, issueRevision: args.expectedRevision });
session.isProcessing = true;
sendStatus(session, "processing");
if (typeof ctx.onProcessingChanged === "function") ctx.onProcessingChanged();
ctx.sm.broadcastSessionList();
var historyStart = (session.history || []).length;
var sdk = ctx.getSdk && ctx.getSdk();
if (!sdk || typeof sdk.startQuery !== "function") throw failLaunch(session, historyStart, new Error("The SDK bridge is not ready."));
try { await sdk.startQuery(session, prompt, undefined, linuxUser); }
catch (error) { throw failLaunch(session, historyStart, error); }
if (ctx.sm.sessions.get(session.localId) !== session || !session.queryInstance) {
throw failLaunch(session, historyStart, null);
}
return { sessionId: session.localId, sessionOriginId: session.sessionOriginId, ref: entry.ref };
}
return function startWork(ws, msg, bound) {
var args = Object.assign({}, msg && msg.args || {}, { requestId: msg && msg.requestId });
var actor = ws && ws._clayUser || null;
var actorId = actor && actor.id || null;
if (!msg || typeof msg.requestId !== "string" || !msg.requestId || msg.requestId.length > 200) {
return Promise.reject(new Error("A work request identifier is required."));
}
if (!bound || typeof args.ref !== "string" || !Number.isInteger(args.expectedRevision)) {
return Promise.reject(new Error("An exact issue reference and expectedRevision are required."));
}
var fingerprint = requestFingerprint(args);
var requestKey = JSON.stringify([bound.scopeId, actorId, msg.requestId]);
var prior = requests.get(requestKey);
if (prior) {
if (prior.ws !== ws || prior.fingerprint !== fingerprint) return Promise.reject(new Error("Work request identifier was already used with different arguments."));
return prior.task;
}
var operationKey = JSON.stringify([bound.scopeId, actorId, args.ref, args.expectedRevision]);
var operation = operations.get(operationKey);
var task = operation ? operation.task : run(ws, actor, actorId, args, bound);
if (!operation) {
operations.set(operationKey, { task: task });
task.then(function () { if (operations.get(operationKey).task === task) operations.delete(operationKey); },
function () { if (operations.get(operationKey).task === task) operations.delete(operationKey); });
}
var request = { ws: ws, fingerprint: fingerprint, task: task, settled: false };
requests.set(requestKey, request);
task.then(function () { request.settled = true; trimRequests(); }, function () { request.settled = true; trimRequests(); });
trimRequests();
return task;
};
}
module.exports = { attachIssueLaunch: attachIssueLaunch };