























@@ -3,7 +3,12 @@
33 * turns replies into app-server answer payloads.
44 */
55import {
6+buildAgentHarnessUserInputAnswers,
7+deliverAgentHarnessUserInputPrompt,
68embeddedAgentLog,
9+emptyAgentHarnessUserInputAnswers,
10+type AgentHarnessUserInputOption,
11+type AgentHarnessUserInputQuestion,
712type EmbeddedRunAttemptParams,
813} from "openclaw/plugin-sdk/agent-harness-runtime";
914import { formatCodexDisplayText } from "../command-formatters.js";
@@ -19,25 +24,11 @@ type PendingUserInput = {
1924threadId: string;
2025turnId: string;
2126itemId: string;
22-questions: UserInputQuestion[];
27+questions: AgentHarnessUserInputQuestion[];
2328resolve: (value: JsonValue) => void;
2429cleanup: () => void;
2530};
263127-type UserInputQuestion = {
28-id: string;
29-header: string;
30-question: string;
31-isOther: boolean;
32-isSecret: boolean;
33-options: UserInputOption[] | null;
34-};
35-36-type UserInputOption = {
37-label: string;
38-description: string;
39-};
40-4132type CodexUserInputBridge = {
4233handleRequest: (request: {
4334id: number | string;
@@ -142,7 +133,7 @@ function readUserInputParams(value: JsonValue | undefined):
142133threadId: string;
143134turnId: string;
144135itemId: string;
145-questions: UserInputQuestion[];
136+questions: AgentHarnessUserInputQuestion[];
146137}
147138| undefined {
148139if (!isJsonObject(value)) {
@@ -157,11 +148,11 @@ function readUserInputParams(value: JsonValue | undefined):
157148}
158149const questions = questionsRaw
159150.map(readQuestion)
160-.filter((question): question is UserInputQuestion => Boolean(question));
151+.filter((question): question is AgentHarnessUserInputQuestion => Boolean(question));
161152return { threadId, turnId, itemId, questions };
162153}
163154164-function readQuestion(value: JsonValue): UserInputQuestion | undefined {
155+function readQuestion(value: JsonValue): AgentHarnessUserInputQuestion | undefined {
165156if (!isJsonObject(value)) {
166157return undefined;
167158}
@@ -181,17 +172,17 @@ function readQuestion(value: JsonValue): UserInputQuestion | undefined {
181172};
182173}
183174184-function readOptions(value: JsonValue | undefined): UserInputOption[] | null {
175+function readOptions(value: JsonValue | undefined): AgentHarnessUserInputOption[] | null {
185176if (!Array.isArray(value)) {
186177return null;
187178}
188179const options = value
189180.map(readOption)
190-.filter((option): option is UserInputOption => Boolean(option));
181+.filter((option): option is AgentHarnessUserInputOption => Boolean(option));
191182return options.length > 0 ? options : null;
192183}
193184194-function readOption(value: JsonValue): UserInputOption | undefined {
185+function readOption(value: JsonValue): AgentHarnessUserInputOption | undefined {
195186if (!isJsonObject(value)) {
196187return undefined;
197188}
@@ -202,116 +193,25 @@ function readOption(value: JsonValue): UserInputOption | undefined {
202193203194async function deliverUserInputPrompt(
204195params: EmbeddedRunAttemptParams,
205-questions: UserInputQuestion[],
196+questions: AgentHarnessUserInputQuestion[],
206197): Promise<void> {
207-const text = formatUserInputPrompt(questions);
208-if (params.onBlockReply) {
209-await params.onBlockReply({ text });
210-return;
211-}
212-await params.onPartialReply?.({ text });
213-}
214-215-function formatUserInputPrompt(questions: UserInputQuestion[]): string {
216-const lines = ["Codex needs input:"];
217-questions.forEach((question, index) => {
218-if (questions.length > 1) {
219-lines.push(
220-"",
221-`${index + 1}. ${formatCodexDisplayText(question.header)}`,
222-formatCodexDisplayText(question.question),
223-);
224-} else {
225-lines.push(
226-"",
227-formatCodexDisplayText(question.header),
228-formatCodexDisplayText(question.question),
229-);
230-}
231-if (question.isSecret) {
232-lines.push("This channel may show your reply to other participants.");
233-}
234-question.options?.forEach((option, optionIndex) => {
235-lines.push(
236-`${optionIndex + 1}. ${formatCodexDisplayText(option.label)}${
237- option.description ? ` - ${formatCodexDisplayText(option.description)}` : ""
238- }`,
239-);
240-});
241-if (question.isOther) {
242-lines.push("Other: reply with your own answer.");
243-}
198+await deliverAgentHarnessUserInputPrompt(params, questions, {
199+formatText: formatCodexDisplayText,
200+intro: "Codex needs input:",
244201});
245-return lines.join("\n");
246202}
247203248-function buildUserInputResponse(questions: UserInputQuestion[], inputText: string): JsonObject {
204+function buildUserInputResponse(
205+questions: AgentHarnessUserInputQuestion[],
206+inputText: string,
207+): JsonObject {
249208// Multi-question replies may use "header: answer" or numbered lines. Keep the
250209// parser permissive so chat-channel replies remain ergonomic.
251-const answers: JsonObject = {};
252-if (questions.length === 1) {
253-const question = questions[0];
254-if (question) {
255-const answer = normalizeAnswer(inputText, question);
256-answers[question.id] = { answers: answer ? [answer] : [] };
257-}
258-return { answers };
259-}
260-261-const keyed = parseKeyedAnswers(inputText);
262-const fallbackLines = inputText
263-.split(/\r?\n/)
264-.map((line) => line.trim())
265-.filter(Boolean);
266-questions.forEach((question, index) => {
267-const key =
268-keyed.get(question.id.toLowerCase()) ??
269-keyed.get(question.header.toLowerCase()) ??
270-keyed.get(question.question.toLowerCase()) ??
271-keyed.get(String(index + 1));
272-const answer = key ?? fallbackLines[index] ?? "";
273-const normalized = answer ? normalizeAnswer(answer, question) : undefined;
274-answers[question.id] = { answers: normalized ? [normalized] : [] };
275-});
276-return { answers };
277-}
278-279-function normalizeAnswer(answer: string, question: UserInputQuestion): string | undefined {
280-const trimmed = answer.trim();
281-const options = question.options ?? [];
282-const optionIndex = /^\d+$/.test(trimmed) ? Number(trimmed) - 1 : -1;
283-const indexed = optionIndex >= 0 ? options[optionIndex] : undefined;
284-if (indexed) {
285-return indexed.label;
286-}
287-const exact = options.find((option) => option.label.toLowerCase() === trimmed.toLowerCase());
288-if (exact) {
289-return exact.label;
290-}
291-if (options.length > 0 && !question.isOther) {
292-return undefined;
293-}
294-return trimmed || undefined;
295-}
296-297-function parseKeyedAnswers(inputText: string): Map<string, string> {
298-const answers = new Map<string, string>();
299-for (const line of inputText.split(/\r?\n/)) {
300-const match = line.match(/^\s*([^:=-]+?)\s*[:=-]\s*(.+?)\s*$/);
301-if (!match) {
302-continue;
303-}
304-const key = match[1]?.trim().toLowerCase();
305-const value = match[2]?.trim();
306-if (key && value) {
307-answers.set(key, value);
308-}
309-}
310-return answers;
210+return buildAgentHarnessUserInputAnswers(questions, inputText) as unknown as JsonObject;
311211}
312212313213function emptyUserInputResponse(): JsonObject {
314-return { answers: {} };
214+return emptyAgentHarnessUserInputAnswers() as unknown as JsonObject;
315215}
316216317217function readString(record: JsonObject, key: string): string | undefined {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。