lib/project-capsule-turn.js
Rama main · réplica de lectura del repositorio original.
// Delivers a Capsule turn prompt into this Mate project.
//
// A Capsule game against a Mate needs the Mate to actually take its turns:
// when Logic passes the turn to the mate seat, this module wakes the Mate
// with a prompt telling it to snapshot the game and act. One session hosts
// the whole game (found again by session.capsuleGame.toolId), so the user
// watches a single running conversation of the Mate playing rather than a
// new session per turn. The prompt is recorded as an internal user message,
// the same shape the debate engine uses, so the transcript shows the nudge
// without pretending the human typed it.
//
// This module only carries words to the Mate. Every actual game mutation the
// Mate then makes goes through the same clay_tool_act pipeline as always.
function attachCapsuleTurn(ctx) {
var sm = ctx.sm;
function ownsSession(session, principal) {
if (ctx.isMultiUser()) return !!principal.userId && session.ownerId === principal.userId;
return !session.ownerId;
}
function findGameSession(principal, toolId) {
var found = null;
sm.sessions.forEach(function (session) {
if (found || !session || session.hidden || session.destroying) return;
if (!session.capsuleGame || session.capsuleGame.toolId !== toolId) return;
if (!ownsSession(session, principal)) return;
found = session;
});
return found;
}
async function createGameSession(principal, options) {
var selection = await ctx.resolveModel(principal);
if (!selection || selection.status !== "ready" || !selection.vendor || !selection.model) {
throw new Error(selection && selection.error ? selection.error : "No configured model is available for the Mate.");
}
var create = typeof sm.createSessionRaw === "function" ? sm.createSessionRaw : sm.createSession;
var session = create.call(sm, {
ownerId: principal.userId || null,
vendor: selection.vendor,
model: selection.model,
effort: selection.effort || null,
});
session.title = (options.toolName || options.toolId) + " game";
session.capsuleGame = { toolId: options.toolId };
return session;
}
async function deliverCapsuleTurn(principal, options) {
if (!principal || !options || !options.toolId || !options.text) {
throw new Error("A bound owner, toolId, and turn text are required.");
}
var sdk = ctx.getSdk();
if (!sdk || typeof sdk.startQuery !== "function") throw new Error("Mate project runtime is unavailable.");
var session = findGameSession(principal, options.toolId);
var created = false;
if (!session) {
session = await createGameSession(principal, options);
created = true;
}
// Recorded as a dedicated event, not a user message: the transcript shows
// a short system note (see historyToHomeChat) instead of pretending the
// human typed the delivery prompt. The full prompt still reaches the
// model through the query below.
sm.sendAndRecord(session, {
type: "capsule_turn",
text: options.text,
toolId: options.toolId,
toolName: options.toolName || options.toolId,
kind: options.kind || "turn",
});
session.isProcessing = true;
session.lastActivity = Date.now();
sm.broadcastSessionList();
try {
var delivered = typeof sdk.pushMessage === "function" ? sdk.pushMessage(session, options.text) : false;
if (!delivered) await Promise.resolve(sdk.startQuery(session, options.text, undefined, ctx.getLinuxUserForSession(session)));
} catch (error) {
session.isProcessing = false;
sm.sendAndRecord(session, { type: "error", text: "Capsule turn could not start: " + (error && error.message ? error.message : String(error)) });
sm.broadcastSessionList();
throw error;
}
// The reference shape the home chat surface opens sessions by.
return {
session: session,
created: created,
reference: (typeof session.cliSessionId === "string" && session.cliSessionId) || "local:" + session.localId,
};
}
return { deliverCapsuleTurn: deliverCapsuleTurn };
}
module.exports = { attachCapsuleTurn: attachCapsuleTurn };