Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

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;
}