fix: remove native question limits
This commit is contained in:
@@ -120,14 +120,13 @@ const continueHostStreamEventSchema = z.discriminatedUnion('type', [
|
|||||||
|
|
||||||
const continueHostQuestionSchema = z
|
const continueHostQuestionSchema = z
|
||||||
.object({
|
.object({
|
||||||
requestId: z.string().min(1).max(128),
|
requestId: z.string().min(1),
|
||||||
timestamp: z.number().finite().optional(),
|
timestamp: z.number().finite().optional(),
|
||||||
question: z.string().trim().min(1).max(2_000),
|
question: z.string().trim().min(1),
|
||||||
options: z
|
options: z
|
||||||
.array(z.string().trim().min(1).max(200))
|
.array(z.string().trim().min(1))
|
||||||
.max(20)
|
|
||||||
.optional(),
|
.optional(),
|
||||||
defaultAnswer: z.string().trim().max(2_000).optional()
|
defaultAnswer: z.string().trim().optional()
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
@@ -925,7 +924,7 @@ export class ContinueHostAdapter {
|
|||||||
patched = replaceExactly(
|
patched = replaceExactly(
|
||||||
patched,
|
patched,
|
||||||
serverMarker,
|
serverMarker,
|
||||||
'let j=(0,atn.default)();if(!process.env.GOODBUDDY_CONTINUE_HOST_TOKEN)throw new Error("Missing GoodBuddy host token");j.use((we,Te,ue)=>{we.headers.authorization===`Bearer ${process.env.GOODBUDDY_CONTINUE_HOST_TOKEN}`?ue():Te.status(401).json({error:"Unauthorized"})}),j.use(atn.default.json({limit:"20mb"})),j.post("/goodbuddy/question-answer",(we,Te)=>{let{requestId:ue,answer:ce,isCustomAnswer:de}=we.body??{};typeof ue==="string"&&ue.length>0&&ue.length<=128&&typeof ce==="string"&&ce.length>0&&ce.length<=2e3?Lbe.answerQuestion(ue,ce,de===!0)?Te.json({success:!0}):Te.status(404).json({error:"Question not pending"}):Te.status(400).json({error:"Invalid question answer"})}),j.get("/state"'
|
'let j=(0,atn.default)();if(!process.env.GOODBUDDY_CONTINUE_HOST_TOKEN)throw new Error("Missing GoodBuddy host token");j.use((we,Te,ue)=>{we.headers.authorization===`Bearer ${process.env.GOODBUDDY_CONTINUE_HOST_TOKEN}`?ue():Te.status(401).json({error:"Unauthorized"})}),j.use(atn.default.json({limit:"20mb"})),j.post("/goodbuddy/question-answer",(we,Te)=>{let{requestId:ue,answer:ce,isCustomAnswer:de}=we.body??{};typeof ue==="string"&&ue.length>0&&typeof ce==="string"&&ce.length>0?Lbe.answerQuestion(ue,ce,de===!0)?Te.json({success:!0}):Te.status(404).json({error:"Question not pending"}):Te.status(400).json({error:"Invalid question answer"})}),j.get("/state"'
|
||||||
)
|
)
|
||||||
patched = replaceExactly(
|
patched = replaceExactly(
|
||||||
patched,
|
patched,
|
||||||
|
|||||||
@@ -2075,7 +2075,26 @@ describe("OpenCodeRuntime embedded permission mediation", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it.each(["parent", "child"] as const)("parses %s OpenCode questions and sends selected answers back", async (owner) => {
|
it.each(["parent", "child"] as const)("parses %s OpenCode questions and sends selected answers back", async (owner) => {
|
||||||
|
const longQuestion = `请选择实现方式\n${"发布说明内容。".repeat(6_000)}`;
|
||||||
const setup = runClient([
|
const setup = runClient([
|
||||||
|
{
|
||||||
|
id: "question-tool-running",
|
||||||
|
type: "message.part.updated",
|
||||||
|
properties: {
|
||||||
|
sessionID: "session-1",
|
||||||
|
part: {
|
||||||
|
id: "part-question-1",
|
||||||
|
callID: "call-question-1",
|
||||||
|
type: "tool",
|
||||||
|
tool: "question",
|
||||||
|
state: {
|
||||||
|
status: "running",
|
||||||
|
input: { questions: [] },
|
||||||
|
time: { start: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "question-event",
|
id: "question-event",
|
||||||
type: "question.asked",
|
type: "question.asked",
|
||||||
@@ -2085,7 +2104,7 @@ describe("OpenCodeRuntime embedded permission mediation", () => {
|
|||||||
questions: [
|
questions: [
|
||||||
{
|
{
|
||||||
header: "实现方式",
|
header: "实现方式",
|
||||||
question: "请选择实现方式",
|
question: longQuestion,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: "直接修改",
|
label: "直接修改",
|
||||||
@@ -2106,6 +2125,25 @@ describe("OpenCodeRuntime embedded permission mediation", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "question-tool-completed",
|
||||||
|
type: "message.part.updated",
|
||||||
|
properties: {
|
||||||
|
sessionID: "session-1",
|
||||||
|
part: {
|
||||||
|
id: "part-question-1",
|
||||||
|
callID: "call-question-1",
|
||||||
|
type: "tool",
|
||||||
|
tool: "question",
|
||||||
|
state: {
|
||||||
|
status: "completed",
|
||||||
|
input: { questions: [] },
|
||||||
|
output: "Answered",
|
||||||
|
time: { start: 1, end: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "idle",
|
id: "idle",
|
||||||
type: "session.idle",
|
type: "session.idle",
|
||||||
@@ -2136,7 +2174,7 @@ describe("OpenCodeRuntime embedded permission mediation", () => {
|
|||||||
questions: [
|
questions: [
|
||||||
{
|
{
|
||||||
header: "实现方式",
|
header: "实现方式",
|
||||||
question: "请选择实现方式",
|
question: longQuestion,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
custom: true,
|
custom: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -81,9 +81,6 @@ const MAX_PERMISSION_PATTERN_LENGTH = 1_024;
|
|||||||
const MAX_PERMISSION_PATTERNS_BYTES = 8 * 1_024;
|
const MAX_PERMISSION_PATTERNS_BYTES = 8 * 1_024;
|
||||||
const MAX_PERMISSION_METADATA_BYTES = 8 * 1_024;
|
const MAX_PERMISSION_METADATA_BYTES = 8 * 1_024;
|
||||||
const MAX_EXECUTION_OUTPUT_BYTES = 1024 * 1024;
|
const MAX_EXECUTION_OUTPUT_BYTES = 1024 * 1024;
|
||||||
const MAX_QUESTION_REQUEST_BYTES = 32 * 1_024;
|
|
||||||
const MAX_QUESTIONS_PER_REQUEST = 4;
|
|
||||||
const MAX_QUESTION_OPTIONS = 20;
|
|
||||||
const MAX_NATIVE_AGENTS = runtimeNativeInventoryLimits.agents;
|
const MAX_NATIVE_AGENTS = runtimeNativeInventoryLimits.agents;
|
||||||
const MAX_NATIVE_TOOLS = runtimeNativeInventoryLimits.tools;
|
const MAX_NATIVE_TOOLS = runtimeNativeInventoryLimits.tools;
|
||||||
const MAX_NATIVE_COMMANDS = runtimeNativeInventoryLimits.commands;
|
const MAX_NATIVE_COMMANDS = runtimeNativeInventoryLimits.commands;
|
||||||
@@ -384,29 +381,22 @@ function parseQuestionRequest(
|
|||||||
if (
|
if (
|
||||||
typeof id !== "string" ||
|
typeof id !== "string" ||
|
||||||
id.length === 0 ||
|
id.length === 0 ||
|
||||||
id.length > MAX_PERMISSION_NAME_LENGTH ||
|
|
||||||
!Array.isArray(questions) ||
|
!Array.isArray(questions) ||
|
||||||
questions.length === 0 ||
|
questions.length === 0 ||
|
||||||
questions.length > MAX_QUESTIONS_PER_REQUEST ||
|
|
||||||
!questions.every(
|
!questions.every(
|
||||||
(question) =>
|
(question) =>
|
||||||
isRecord(question) &&
|
isRecord(question) &&
|
||||||
typeof question.question === "string" &&
|
typeof question.question === "string" &&
|
||||||
question.question.trim().length > 0 &&
|
question.question.trim().length > 0 &&
|
||||||
question.question.length <= 2_000 &&
|
|
||||||
typeof question.header === "string" &&
|
typeof question.header === "string" &&
|
||||||
question.header.trim().length > 0 &&
|
question.header.trim().length > 0 &&
|
||||||
question.header.length <= 120 &&
|
|
||||||
Array.isArray(question.options) &&
|
Array.isArray(question.options) &&
|
||||||
question.options.length <= MAX_QUESTION_OPTIONS &&
|
|
||||||
question.options.every(
|
question.options.every(
|
||||||
(option) =>
|
(option) =>
|
||||||
isRecord(option) &&
|
isRecord(option) &&
|
||||||
typeof option.label === "string" &&
|
typeof option.label === "string" &&
|
||||||
option.label.trim().length > 0 &&
|
option.label.trim().length > 0 &&
|
||||||
option.label.length <= 200 &&
|
typeof option.description === "string",
|
||||||
typeof option.description === "string" &&
|
|
||||||
option.description.length <= 1_000,
|
|
||||||
) &&
|
) &&
|
||||||
(question.multiple === undefined ||
|
(question.multiple === undefined ||
|
||||||
typeof question.multiple === "boolean") &&
|
typeof question.multiple === "boolean") &&
|
||||||
@@ -416,22 +406,11 @@ function parseQuestionRequest(
|
|||||||
(!isRecord(tool) ||
|
(!isRecord(tool) ||
|
||||||
typeof tool.messageID !== "string" ||
|
typeof tool.messageID !== "string" ||
|
||||||
tool.messageID.length === 0 ||
|
tool.messageID.length === 0 ||
|
||||||
tool.messageID.length > 256 ||
|
|
||||||
typeof tool.callID !== "string" ||
|
typeof tool.callID !== "string" ||
|
||||||
tool.callID.length === 0 ||
|
tool.callID.length === 0))
|
||||||
tool.callID.length > 256))
|
|
||||||
) {
|
) {
|
||||||
throw new Error("OpenCode 提问请求格式无效");
|
throw new Error("OpenCode 提问请求格式无效");
|
||||||
}
|
}
|
||||||
let serialized: string;
|
|
||||||
try {
|
|
||||||
serialized = JSON.stringify(properties);
|
|
||||||
} catch {
|
|
||||||
throw new Error("OpenCode 提问请求无法序列化");
|
|
||||||
}
|
|
||||||
if (!byteLengthWithin(serialized, MAX_QUESTION_REQUEST_BYTES)) {
|
|
||||||
throw new Error("OpenCode 提问请求超过安全限制");
|
|
||||||
}
|
|
||||||
return properties as QuestionRequest;
|
return properties as QuestionRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2569,6 +2548,7 @@ export class OpenCodeRuntime implements AgentRuntime {
|
|||||||
throw new Error("OpenCode 工具调用 ID 格式无效");
|
throw new Error("OpenCode 工具调用 ID 格式无效");
|
||||||
}
|
}
|
||||||
const toolName = part.tool.slice(0, 200);
|
const toolName = part.tool.slice(0, 200);
|
||||||
|
const nativeControlTool = toolName.trim().toLowerCase() === "question";
|
||||||
const state =
|
const state =
|
||||||
part.state.status === "error" ? "failed" : part.state.status;
|
part.state.status === "error" ? "failed" : part.state.status;
|
||||||
if (state === "failed") {
|
if (state === "failed") {
|
||||||
@@ -2609,7 +2589,7 @@ export class OpenCodeRuntime implements AgentRuntime {
|
|||||||
const retained = subagentProgress.retain(subagent);
|
const retained = subagentProgress.retain(subagent);
|
||||||
toolStates.get(callId)!.subagent = retained;
|
toolStates.get(callId)!.subagent = retained;
|
||||||
yield retained;
|
yield retained;
|
||||||
} else {
|
} else if (!nativeControlTool) {
|
||||||
yield {
|
yield {
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
type: "tool",
|
type: "tool",
|
||||||
|
|||||||
@@ -122,7 +122,6 @@ export function AgentQuestionCard({
|
|||||||
<span>{t('question.otherAnswer')}</span>
|
<span>{t('question.otherAnswer')}</span>
|
||||||
<input
|
<input
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
maxLength={2_000}
|
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
const answer = event.target.value
|
const answer = event.target.value
|
||||||
setCustom((current) =>
|
setCustom((current) =>
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import {
|
|||||||
projectExecutionSpaceSchema,
|
projectExecutionSpaceSchema,
|
||||||
projectUpdateSchema
|
projectUpdateSchema
|
||||||
} from './assistant-contracts'
|
} from './assistant-contracts'
|
||||||
import { subagentEventSchema } from './contracts'
|
import {
|
||||||
|
agentQuestionResponseSchema,
|
||||||
|
subagentEventSchema
|
||||||
|
} from './contracts'
|
||||||
|
|
||||||
const untouchedProject: AssistantProject = {
|
const untouchedProject: AssistantProject = {
|
||||||
id: '00000000-0000-4000-8000-000000000101',
|
id: '00000000-0000-4000-8000-000000000101',
|
||||||
@@ -34,6 +37,25 @@ const untouchedProject: AssistantProject = {
|
|||||||
updatedAt: '2026-08-01T00:00:00.000Z'
|
updatedAt: '2026-08-01T00:00:00.000Z'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('agent question contracts', () => {
|
||||||
|
it('does not impose GoodBuddy-specific question answer limits', () => {
|
||||||
|
const answers = Array.from({ length: 5 }, (_, questionIndex) =>
|
||||||
|
Array.from(
|
||||||
|
{ length: 21 },
|
||||||
|
(_, optionIndex) =>
|
||||||
|
`${questionIndex}:${optionIndex}:${'回答内容'.repeat(600)}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
agentQuestionResponseSchema.parse({
|
||||||
|
questionId: `question-${'id'.repeat(100)}`,
|
||||||
|
answers
|
||||||
|
}).answers
|
||||||
|
).toEqual(answers)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('activity history contracts', () => {
|
describe('activity history contracts', () => {
|
||||||
it('accepts more than 500 records and details longer than 4,000 characters', () => {
|
it('accepts more than 500 records and details longer than 4,000 characters', () => {
|
||||||
const records = Array.from({ length: 501 }, (_, index) => ({
|
const records = Array.from({ length: 501 }, (_, index) => ({
|
||||||
|
|||||||
@@ -305,13 +305,12 @@ export const workspaceOpenPathRequestSchema = z
|
|||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
export const agentQuestionAnswerSchema = z
|
export const agentQuestionAnswerSchema = z
|
||||||
.array(z.string().trim().min(1).max(2_000))
|
.array(z.string().trim().min(1))
|
||||||
.max(20)
|
|
||||||
|
|
||||||
export const agentQuestionResponseSchema = z
|
export const agentQuestionResponseSchema = z
|
||||||
.object({
|
.object({
|
||||||
questionId: z.string().trim().min(1).max(128),
|
questionId: z.string().trim().min(1),
|
||||||
answers: z.array(agentQuestionAnswerSchema).max(4)
|
answers: z.array(agentQuestionAnswerSchema)
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user