Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
import type { AgentActivityInteraction } from "@tutti-os/agent-activity-core";
|
|
import type { AgentInteractionResponseInput } from "../../../shared/agentConversation/contracts/agentConversationVM";
|
|
import { normalizeAskUserQuestions } from "../../../shared/agentConversation/askUserQuestions";
|
|
import {
|
|
buildAskUserAnswerPayload,
|
|
writeOwnAnswer,
|
|
type InteractiveAnswerPayload
|
|
} from "../../../shared/agentConversation/interactiveAnswerPayload";
|
|
import type { AgentPromptContentBlock } from "../../../shared/contracts/dto";
|
|
import type { AgentComposerSubmitOptions } from "../composer/AgentComposer.types";
|
|
import { agentPromptContentDisplayText } from "../model/agentComposerDraft";
|
|
|
|
export interface PendingQuestionComposerTarget {
|
|
agentSessionId: string;
|
|
questionIds: readonly string[];
|
|
requestId: string;
|
|
turnId: string;
|
|
}
|
|
|
|
export function resolvePendingQuestionComposerTarget(input: {
|
|
activeTurnId: string;
|
|
agentSessionId: string;
|
|
pendingInteractions: readonly AgentActivityInteraction[];
|
|
}): PendingQuestionComposerTarget | null {
|
|
const activeTurnId = input.activeTurnId.trim();
|
|
const agentSessionId = input.agentSessionId.trim();
|
|
if (!activeTurnId || !agentSessionId) return null;
|
|
|
|
const matching = input.pendingInteractions.filter(
|
|
(interaction) =>
|
|
interaction.status === "pending" &&
|
|
interaction.kind === "question" &&
|
|
interaction.agentSessionId.trim() === agentSessionId &&
|
|
interaction.turnId.trim() === activeTurnId
|
|
);
|
|
if (matching.length !== 1) return null;
|
|
|
|
const interaction = matching[0]!;
|
|
const questions = normalizeAskUserQuestions(interaction.input?.questions);
|
|
if (
|
|
questions.length === 0 ||
|
|
questions.some((question) => question.allowFreeText === false)
|
|
) {
|
|
return null;
|
|
}
|
|
return {
|
|
agentSessionId,
|
|
questionIds: questions.map((question) => question.id),
|
|
requestId: interaction.requestId,
|
|
turnId: activeTurnId
|
|
};
|
|
}
|
|
|
|
export function resolvePendingQuestionComposerResponse(input: {
|
|
activeTurnId: string;
|
|
agentSessionId: string;
|
|
content: readonly AgentPromptContentBlock[];
|
|
pendingInteractions: readonly AgentActivityInteraction[];
|
|
submitOptions?: Pick<
|
|
AgentComposerSubmitOptions,
|
|
"capabilityRefs" | "requiredSettingsPatch" | "tuttiMode"
|
|
>;
|
|
}): AgentInteractionResponseInput | null {
|
|
if (
|
|
input.submitOptions?.requiredSettingsPatch !== undefined ||
|
|
Boolean(input.submitOptions?.capabilityRefs?.length) ||
|
|
input.submitOptions?.tuttiMode?.active === true ||
|
|
input.content.length === 0 ||
|
|
input.content.some((block) => block.type !== "text")
|
|
) {
|
|
return null;
|
|
}
|
|
const target = resolvePendingQuestionComposerTarget(input);
|
|
if (!target) return null;
|
|
const payload = buildAskUserComposerAnswerPayload(
|
|
target.questionIds,
|
|
agentPromptContentDisplayText(input.content)
|
|
);
|
|
if (!payload) return null;
|
|
return {
|
|
action: "submit",
|
|
agentSessionId: target.agentSessionId,
|
|
payload: { ...payload },
|
|
requestId: target.requestId,
|
|
turnId: target.turnId
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The ordinary Composer is a task-level escape hatch, not a second per-question
|
|
* form. Attach its one instruction to each normalized question while keeping
|
|
* this AgentGUI-only policy out of the public interactive-answer wire module.
|
|
*/
|
|
function buildAskUserComposerAnswerPayload(
|
|
rawQuestionIds: readonly string[],
|
|
rawAnswer: string
|
|
): InteractiveAnswerPayload | null {
|
|
const answer = rawAnswer.trim();
|
|
if (!answer) return null;
|
|
|
|
const answersByQuestionId: Record<string, string> = {};
|
|
for (const rawQuestionId of rawQuestionIds) {
|
|
const questionId = rawQuestionId.trim();
|
|
if (!questionId || Object.hasOwn(answersByQuestionId, questionId)) {
|
|
continue;
|
|
}
|
|
writeOwnAnswer(answersByQuestionId, questionId, answer);
|
|
}
|
|
return Object.keys(answersByQuestionId).length > 0
|
|
? buildAskUserAnswerPayload(answersByQuestionId)
|
|
: null;
|
|
}
|