fix: add goose execution override for h5 chat
This commit is contained in:
+13
-2
@@ -155,7 +155,7 @@ async function validateToolGatewayResult({ result, validation, cwd }) {
|
|||||||
return { expectedFiles: checks };
|
return { expectedFiles: checks };
|
||||||
}
|
}
|
||||||
|
|
||||||
function withRunMetadata(userMessage, { toolMode = 'chat', taskType = null } = {}) {
|
function withRunMetadata(userMessage, { toolMode = 'chat', taskType = null, forceGoose = false } = {}) {
|
||||||
const message = (userMessage && typeof userMessage === 'object' && !Array.isArray(userMessage))
|
const message = (userMessage && typeof userMessage === 'object' && !Array.isArray(userMessage))
|
||||||
? { ...userMessage }
|
? { ...userMessage }
|
||||||
: { value: userMessage };
|
: { value: userMessage };
|
||||||
@@ -168,6 +168,7 @@ function withRunMetadata(userMessage, { toolMode = 'chat', taskType = null } = {
|
|||||||
: {}),
|
: {}),
|
||||||
toolMode: normalizeAgentRunToolMode(toolMode),
|
toolMode: normalizeAgentRunToolMode(toolMode),
|
||||||
...(taskType ? { taskType } : {}),
|
...(taskType ? { taskType } : {}),
|
||||||
|
...(forceGoose ? { forceGoose: true } : {}),
|
||||||
};
|
};
|
||||||
return { ...message, metadata };
|
return { ...message, metadata };
|
||||||
}
|
}
|
||||||
@@ -184,6 +185,7 @@ function getRunOptionsFromMessage(userMessage) {
|
|||||||
return {
|
return {
|
||||||
toolMode,
|
toolMode,
|
||||||
taskType: normalizeTaskType(runMetadata?.taskType ?? metadata?.taskType),
|
taskType: normalizeTaskType(runMetadata?.taskType ?? metadata?.taskType),
|
||||||
|
forceGoose: runMetadata?.forceGoose === true || metadata?.forceGoose === true,
|
||||||
validation: normalizeToolGatewayValidation(runMetadata?.validation ?? metadata?.toolGatewayValidation),
|
validation: normalizeToolGatewayValidation(runMetadata?.validation ?? metadata?.toolGatewayValidation),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -287,6 +289,7 @@ export function createAgentRunGateway({
|
|||||||
userMessage,
|
userMessage,
|
||||||
toolMode = 'chat',
|
toolMode = 'chat',
|
||||||
taskType = null,
|
taskType = null,
|
||||||
|
forceGoose = false,
|
||||||
}) {
|
}) {
|
||||||
const normalizedRequestId = String(requestId ?? '').trim();
|
const normalizedRequestId = String(requestId ?? '').trim();
|
||||||
if (!normalizedRequestId) {
|
if (!normalizedRequestId) {
|
||||||
@@ -305,6 +308,7 @@ export function createAgentRunGateway({
|
|||||||
const runMessage = withRunMetadata(userMessage, {
|
const runMessage = withRunMetadata(userMessage, {
|
||||||
toolMode: normalizedToolMode,
|
toolMode: normalizedToolMode,
|
||||||
taskType: normalizedTaskType,
|
taskType: normalizedTaskType,
|
||||||
|
forceGoose,
|
||||||
});
|
});
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO h5_agent_runs
|
`INSERT INTO h5_agent_runs
|
||||||
@@ -325,6 +329,7 @@ export function createAgentRunGateway({
|
|||||||
sessionId: sessionId || null,
|
sessionId: sessionId || null,
|
||||||
toolMode: normalizedToolMode,
|
toolMode: normalizedToolMode,
|
||||||
taskType: normalizedTaskType,
|
taskType: normalizedTaskType,
|
||||||
|
forceGoose: Boolean(forceGoose),
|
||||||
});
|
});
|
||||||
if (autoDispatch) dispatchRun(runId);
|
if (autoDispatch) dispatchRun(runId);
|
||||||
return projectRun(await getRunById(runId));
|
return projectRun(await getRunById(runId));
|
||||||
@@ -386,7 +391,7 @@ export function createAgentRunGateway({
|
|||||||
const userMessage = safeJsonParse(row.user_message_json, {});
|
const userMessage = safeJsonParse(row.user_message_json, {});
|
||||||
const runOptions = getRunOptionsFromMessage(userMessage);
|
const runOptions = getRunOptionsFromMessage(userMessage);
|
||||||
const toolGatewayStatus = toolGateway?.getStatus ? toolGateway.getStatus() : null;
|
const toolGatewayStatus = toolGateway?.getStatus ? toolGateway.getStatus() : null;
|
||||||
if (directChatService?.canHandle?.({
|
if (!runOptions.forceGoose && directChatService?.canHandle?.({
|
||||||
sessionId: row.agent_session_id ?? null,
|
sessionId: row.agent_session_id ?? null,
|
||||||
toolMode: runOptions.toolMode,
|
toolMode: runOptions.toolMode,
|
||||||
userMessage,
|
userMessage,
|
||||||
@@ -461,6 +466,12 @@ export function createAgentRunGateway({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sessionId = row.agent_session_id ?? null;
|
let sessionId = row.agent_session_id ?? null;
|
||||||
|
if (isDirectChatSessionId(sessionId)) {
|
||||||
|
await appendEvent(runId, 'direct_session_escalated_to_goose', {
|
||||||
|
previousSessionId: sessionId,
|
||||||
|
});
|
||||||
|
sessionId = null;
|
||||||
|
}
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
const sessionOptions = {};
|
const sessionOptions = {};
|
||||||
if (runOptions.toolMode === 'code' && userAuth?.getCodeAgentSessionPolicy) {
|
if (runOptions.toolMode === 'code' && userAuth?.getCodeAgentSessionPolicy) {
|
||||||
|
|||||||
@@ -301,6 +301,36 @@ test('agent run uses direct chat service for eligible chat messages', async () =
|
|||||||
assert.ok(pool.events.some((event) => event.eventType === 'direct_chat_completed'));
|
assert.ok(pool.events.some((event) => event.eventType === 'direct_chat_completed'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('agent run escalates direct sessions to a new goose session when forced', async () => {
|
||||||
|
const pool = createFakePool();
|
||||||
|
const submitted = [];
|
||||||
|
const gateway = createAgentRunGateway({
|
||||||
|
pool,
|
||||||
|
tkmindProxy: {
|
||||||
|
async startSessionForUser(userId) {
|
||||||
|
assert.equal(userId, 'user-1');
|
||||||
|
return { id: 'goose-session-1' };
|
||||||
|
},
|
||||||
|
async submitSessionReplyForUser(userId, sessionId, requestId, userMessage, options = {}) {
|
||||||
|
submitted.push({ userId, sessionId, requestId, userMessage, options });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
retryDelaysMs: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = await gateway.createRun('user-1', {
|
||||||
|
sessionId: 'h5direct_existing',
|
||||||
|
requestId: 'req-force-goose',
|
||||||
|
userMessage: { role: 'user', content: [{ type: 'text', text: '帮我生成页面 public/a.html' }] },
|
||||||
|
forceGoose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => pool.runs.get(run.id)?.status === 'succeeded');
|
||||||
|
assert.equal(pool.runs.get(run.id).agent_session_id, 'goose-session-1');
|
||||||
|
assert.equal(submitted[0].sessionId, 'goose-session-1');
|
||||||
|
assert.ok(pool.events.some((event) => event.eventType === 'direct_session_escalated_to_goose'));
|
||||||
|
});
|
||||||
|
|
||||||
test('agent run with code tool mode starts and submits with code policy', async () => {
|
test('agent run with code tool mode starts and submits with code policy', async () => {
|
||||||
const pool = createFakePool();
|
const pool = createFakePool();
|
||||||
const submitted = [];
|
const submitted = [];
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export function createPostAgentRunsHandler({
|
|||||||
const userMessage = request.body?.user_message ?? null;
|
const userMessage = request.body?.user_message ?? null;
|
||||||
const rawToolMode = request.body?.tool_mode ?? request.body?.toolMode ?? 'chat';
|
const rawToolMode = request.body?.tool_mode ?? request.body?.toolMode ?? 'chat';
|
||||||
const taskType = String(request.body?.task_type ?? request.body?.taskType ?? '').trim() || null;
|
const taskType = String(request.body?.task_type ?? request.body?.taskType ?? '').trim() || null;
|
||||||
|
const forceGoose = request.body?.force_goose === true || request.body?.forceGoose === true;
|
||||||
if (!requestId) {
|
if (!requestId) {
|
||||||
response.status(400).json({ message: '缺少 request_id' });
|
response.status(400).json({ message: '缺少 request_id' });
|
||||||
return;
|
return;
|
||||||
@@ -110,6 +111,7 @@ export function createPostAgentRunsHandler({
|
|||||||
userMessage,
|
userMessage,
|
||||||
toolMode,
|
toolMode,
|
||||||
taskType,
|
taskType,
|
||||||
|
...(forceGoose ? { forceGoose: true } : {}),
|
||||||
});
|
});
|
||||||
response.status(202).json({ run });
|
response.status(202).json({ run });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -100,6 +100,39 @@ test('POST /agent/runs creates a run and returns 202', async () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('POST /agent/runs forwards force_goose to the run gateway', async () => {
|
||||||
|
const created = [];
|
||||||
|
const handler = createPostAgentRunsHandler({
|
||||||
|
userAuth: {
|
||||||
|
async ownsSession() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
agentRunGateway: {
|
||||||
|
async createRun(userId, payload) {
|
||||||
|
created.push({ userId, payload });
|
||||||
|
return { id: 'run-goose', status: 'queued' };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const res = createResponseRecorder();
|
||||||
|
|
||||||
|
await handler(
|
||||||
|
{
|
||||||
|
currentUser: { id: 'user-1' },
|
||||||
|
body: {
|
||||||
|
request_id: 'req-goose',
|
||||||
|
user_message: { role: 'user', content: [{ type: 'text', text: 'hello' }] },
|
||||||
|
force_goose: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(res.statusCode, 202);
|
||||||
|
assert.equal(created[0].payload.forceGoose, true);
|
||||||
|
});
|
||||||
|
|
||||||
test('POST /agent/runs accepts explicit code tool mode and task type', async () => {
|
test('POST /agent/runs accepts explicit code tool mode and task type', async () => {
|
||||||
const created = [];
|
const created = [];
|
||||||
const handler = createPostAgentRunsHandler({
|
const handler = createPostAgentRunsHandler({
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ export function isDirectChatSessionId(sessionId) {
|
|||||||
return String(sessionId ?? '').startsWith(DIRECT_CHAT_SESSION_PREFIX);
|
return String(sessionId ?? '').startsWith(DIRECT_CHAT_SESSION_PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GOOSE_REQUIRED_PATTERNS = [
|
||||||
|
/\b(public\/[^\s"'<>]+\.html)\b/i,
|
||||||
|
/\b(html|h5|web\s?page|landing\s?page|microsite|docx|word)\b/i,
|
||||||
|
/\b(write|create|generate|publish|download|export|save|edit)\b.{0,40}\b(file|page|html|docx|word|asset)\b/i,
|
||||||
|
/\b(file|page|html|docx|word|asset)\b.{0,40}\b(write|create|generate|publish|download|export|save|edit)\b/i,
|
||||||
|
/(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑).{0,24}(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页)/u,
|
||||||
|
/(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页).{0,24}(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑)/u,
|
||||||
|
/MindSpace\/[^/\s]+\/public\/[^\s"'<>]+\.html/i,
|
||||||
|
];
|
||||||
|
|
||||||
export function sendDirectChatSessionEvents(req, res, snapshot) {
|
export function sendDirectChatSessionEvents(req, res, snapshot) {
|
||||||
let closed = false;
|
let closed = false;
|
||||||
let keepalive = null;
|
let keepalive = null;
|
||||||
@@ -89,6 +99,11 @@ function isTextOnlyUserMessage(message) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requiresGooseExecution(message) {
|
||||||
|
const text = `${messageText(message)}\n${assistantFacingText(message)}`.trim();
|
||||||
|
return GOOSE_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
|
||||||
|
}
|
||||||
|
|
||||||
function renderMemoryLines(memories) {
|
function renderMemoryLines(memories) {
|
||||||
const items = Array.isArray(memories) ? memories : [];
|
const items = Array.isArray(memories) ? memories : [];
|
||||||
if (items.length === 0) return '';
|
if (items.length === 0) return '';
|
||||||
@@ -166,6 +181,7 @@ export function createDirectChatService({
|
|||||||
if (toolMode !== 'chat') return false;
|
if (toolMode !== 'chat') return false;
|
||||||
if (sessionId && !isDirectChatSessionId(sessionId)) return false;
|
if (sessionId && !isDirectChatSessionId(sessionId)) return false;
|
||||||
if (!isTextOnlyUserMessage(userMessage)) return false;
|
if (!isTextOnlyUserMessage(userMessage)) return false;
|
||||||
|
if (requiresGooseExecution(userMessage)) return false;
|
||||||
return Boolean(userAuth && llmProviderService && sessionSnapshotService);
|
return Boolean(userAuth && llmProviderService && sessionSnapshotService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,3 +184,20 @@ test('direct chat rejects non-text messages', () => {
|
|||||||
},
|
},
|
||||||
}), false);
|
}), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('direct chat rejects page generation tasks so goose can execute them', () => {
|
||||||
|
const service = createDirectChatService({
|
||||||
|
enabled: true,
|
||||||
|
userAuth: {},
|
||||||
|
llmProviderService: {},
|
||||||
|
sessionSnapshotService: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(service.canHandle({
|
||||||
|
toolMode: 'chat',
|
||||||
|
userMessage: {
|
||||||
|
role: 'user',
|
||||||
|
content: [{ type: 'text', text: '帮我生成一个秋夜诗页面 public/autumn-night-poem.html' }],
|
||||||
|
},
|
||||||
|
}), false);
|
||||||
|
});
|
||||||
|
|||||||
+20
-1
@@ -114,8 +114,26 @@ function withAgentRunValidationMetadata(
|
|||||||
|
|
||||||
function prepareAgentRunUserMessage(message: Message, options: AgentRunCreateOptions): Message {
|
function prepareAgentRunUserMessage(message: Message, options: AgentRunCreateOptions): Message {
|
||||||
const normalized = normalizeUserMessageForApi(message);
|
const normalized = normalizeUserMessageForApi(message);
|
||||||
|
const withValidation = withAgentRunValidationMetadata(normalized, options.validation);
|
||||||
|
const withRunMetadata = options.forceGoose
|
||||||
|
? {
|
||||||
|
...withValidation,
|
||||||
|
metadata: {
|
||||||
|
...withValidation.metadata,
|
||||||
|
memindRun: {
|
||||||
|
...(
|
||||||
|
withValidation.metadata?.memindRun &&
|
||||||
|
typeof withValidation.metadata.memindRun === 'object'
|
||||||
|
? withValidation.metadata.memindRun
|
||||||
|
: {}
|
||||||
|
),
|
||||||
|
forceGoose: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: withValidation;
|
||||||
return appendAgentRunValidationInstruction(
|
return appendAgentRunValidationInstruction(
|
||||||
withAgentRunValidationMetadata(normalized, options.validation),
|
withRunMetadata,
|
||||||
options.toolMode === 'code' ? options.validationInstruction : null,
|
options.toolMode === 'code' ? options.validationInstruction : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2440,6 +2458,7 @@ export async function createAgentRun(
|
|||||||
user_message: userMessagePayload,
|
user_message: userMessagePayload,
|
||||||
...(options.toolMode ? { tool_mode: options.toolMode } : {}),
|
...(options.toolMode ? { tool_mode: options.toolMode } : {}),
|
||||||
...(options.taskType ? { task_type: options.taskType } : {}),
|
...(options.taskType ? { task_type: options.taskType } : {}),
|
||||||
|
...(options.forceGoose ? { force_goose: true } : {}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{ timeoutMs: AGENT_CONNECT_TIMEOUT_MS },
|
{ timeoutMs: AGENT_CONNECT_TIMEOUT_MS },
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export function ChatPanel({
|
|||||||
text: string,
|
text: string,
|
||||||
imageUrls?: string[],
|
imageUrls?: string[],
|
||||||
previewImageUrls?: string[],
|
previewImageUrls?: string[],
|
||||||
options?: { messageId?: string },
|
options?: { messageId?: string; forceGoose?: boolean },
|
||||||
) => void | Promise<void>;
|
) => void | Promise<void>;
|
||||||
onUploadImage?: (
|
onUploadImage?: (
|
||||||
file: File,
|
file: File,
|
||||||
@@ -167,6 +167,7 @@ export function ChatPanel({
|
|||||||
const [pageSource, setPageSource] = useState<Message | null>(null);
|
const [pageSource, setPageSource] = useState<Message | null>(null);
|
||||||
const [sharePreviewSource, setSharePreviewSource] = useState<Message | null>(null);
|
const [sharePreviewSource, setSharePreviewSource] = useState<Message | null>(null);
|
||||||
const [voiceNotice, setVoiceNotice] = useState<string | null>(null);
|
const [voiceNotice, setVoiceNotice] = useState<string | null>(null);
|
||||||
|
const [forceGoose, setForceGoose] = useState(false);
|
||||||
const [pendingImages, setPendingImages] = useState<PendingChatImage[]>([]);
|
const [pendingImages, setPendingImages] = useState<PendingChatImage[]>([]);
|
||||||
const [uploadingImage, setUploadingImage] = useState(false);
|
const [uploadingImage, setUploadingImage] = useState(false);
|
||||||
const [imageError, setImageError] = useState<string | null>(null);
|
const [imageError, setImageError] = useState<string | null>(null);
|
||||||
@@ -514,7 +515,10 @@ export function ChatPanel({
|
|||||||
}));
|
}));
|
||||||
const imagesToSend = uploadedUrls.filter(Boolean);
|
const imagesToSend = uploadedUrls.filter(Boolean);
|
||||||
const previewImagesToSend = imagesToSend;
|
const previewImagesToSend = imagesToSend;
|
||||||
await onSubmit(trimmed, imagesToSend, previewImagesToSend, { messageId: outgoingMessageId });
|
await onSubmit(trimmed, imagesToSend, previewImagesToSend, {
|
||||||
|
messageId: outgoingMessageId,
|
||||||
|
forceGoose,
|
||||||
|
});
|
||||||
uploadedImages.forEach(revokePendingImage);
|
uploadedImages.forEach(revokePendingImage);
|
||||||
setPendingImages([]);
|
setPendingImages([]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -530,7 +534,7 @@ export function ChatPanel({
|
|||||||
}
|
}
|
||||||
setUploadingImage(false);
|
setUploadingImage(false);
|
||||||
suppressVoiceUpdateRef.current = false;
|
suppressVoiceUpdateRef.current = false;
|
||||||
}, [input, onSubmit, onUploadImage, pendingImages, revokePendingImage, voiceDisabled, voiceRecording]);
|
}, [forceGoose, input, onSubmit, onUploadImage, pendingImages, revokePendingImage, voiceDisabled, voiceRecording]);
|
||||||
|
|
||||||
const handleSubmit = useCallback(async () => {
|
const handleSubmit = useCallback(async () => {
|
||||||
await submitText();
|
await submitText();
|
||||||
@@ -822,6 +826,20 @@ export function ChatPanel({
|
|||||||
onPrefill={setInput}
|
onPrefill={setInput}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{!showHomeWelcome && (
|
||||||
|
<label
|
||||||
|
className={`chat-force-goose-toggle${forceGoose ? ' is-active' : ''}`}
|
||||||
|
title="勾选后,本轮消息强制交给 Goose 执行"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={forceGoose}
|
||||||
|
disabled={voiceDisabled}
|
||||||
|
onChange={(event) => setForceGoose(event.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>强制走 Goose</span>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
{chatState === 'streaming' ? (
|
{chatState === 'streaming' ? (
|
||||||
<button type="button" className="danger-btn chat-input-action" onClick={() => void onStop()}>
|
<button type="button" className="danger-btn chat-input-action" onClick={() => void onStop()}>
|
||||||
停止
|
停止
|
||||||
|
|||||||
@@ -541,7 +541,12 @@ export function ChatView({
|
|||||||
capabilities={capabilities}
|
capabilities={capabilities}
|
||||||
grantedSkills={grantedSkills}
|
grantedSkills={grantedSkills}
|
||||||
onSubmit={(text, imageUrls, previewImageUrls, options) =>
|
onSubmit={(text, imageUrls, previewImageUrls, options) =>
|
||||||
void submit(text, { messageId: options?.messageId }, imageUrls, previewImageUrls)
|
void submit(
|
||||||
|
text,
|
||||||
|
{ messageId: options?.messageId, forceGoose: options?.forceGoose },
|
||||||
|
imageUrls,
|
||||||
|
previewImageUrls,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onUploadImage={(file, onProgress, options) =>
|
onUploadImage={(file, onProgress, options) =>
|
||||||
uploadChatImage(file, onProgress, { messageId: options?.messageId })
|
uploadChatImage(file, onProgress, { messageId: options?.messageId })
|
||||||
|
|||||||
@@ -140,8 +140,22 @@ export function SpaceChatPanel({
|
|||||||
grantedSkills={grantedSkills}
|
grantedSkills={grantedSkills}
|
||||||
onSubmit={(text, imageUrls, previewImageUrls, options) =>
|
onSubmit={(text, imageUrls, previewImageUrls, options) =>
|
||||||
chatBridge
|
chatBridge
|
||||||
? void submit(text, { ...context, messageId: options?.messageId }, imageUrls, previewImageUrls)
|
? void submit(
|
||||||
: void submit(text, { mindspaceContext: context, messageId: options?.messageId }, imageUrls, previewImageUrls)
|
text,
|
||||||
|
{ ...context, messageId: options?.messageId, forceGoose: options?.forceGoose },
|
||||||
|
imageUrls,
|
||||||
|
previewImageUrls,
|
||||||
|
)
|
||||||
|
: void submit(
|
||||||
|
text,
|
||||||
|
{
|
||||||
|
mindspaceContext: context,
|
||||||
|
messageId: options?.messageId,
|
||||||
|
forceGoose: options?.forceGoose,
|
||||||
|
},
|
||||||
|
imageUrls,
|
||||||
|
previewImageUrls,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onUploadImage={(file, onProgress, options) =>
|
onUploadImage={(file, onProgress, options) =>
|
||||||
uploadChatImage(file, onProgress, { messageId: options?.messageId })
|
uploadChatImage(file, onProgress, { messageId: options?.messageId })
|
||||||
|
|||||||
@@ -1136,7 +1136,7 @@ export function useTKMindChat(
|
|||||||
const submit = useCallback(
|
const submit = useCallback(
|
||||||
async (
|
async (
|
||||||
text: string,
|
text: string,
|
||||||
options?: { mindspaceContext?: MindSpaceChatContext; messageId?: string },
|
options?: { mindspaceContext?: MindSpaceChatContext; messageId?: string; forceGoose?: boolean },
|
||||||
imageUrls?: string[],
|
imageUrls?: string[],
|
||||||
previewImageUrls?: string[],
|
previewImageUrls?: string[],
|
||||||
) => {
|
) => {
|
||||||
@@ -1182,16 +1182,20 @@ export function useTKMindChat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const runOptions = resolveAgentRunOptions(trimmed, {
|
||||||
|
taskType: 'h5_chat_code_task',
|
||||||
|
userId: userRef.current?.id ?? null,
|
||||||
|
requestId,
|
||||||
|
mindspaceContext: options?.mindspaceContext ?? null,
|
||||||
|
});
|
||||||
const createdRun = await createAgentRun(
|
const createdRun = await createAgentRun(
|
||||||
activeSessionId,
|
activeSessionId,
|
||||||
requestId,
|
requestId,
|
||||||
userMessage,
|
userMessage,
|
||||||
resolveAgentRunOptions(trimmed, {
|
{
|
||||||
taskType: 'h5_chat_code_task',
|
...runOptions,
|
||||||
userId: userRef.current?.id ?? null,
|
...(options?.forceGoose ? { forceGoose: true } : {}),
|
||||||
requestId,
|
},
|
||||||
mindspaceContext: options?.mindspaceContext ?? null,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
const finishedRun =
|
const finishedRun =
|
||||||
createdRun.status === 'succeeded' ? createdRun : await waitForAgentRun(createdRun.id);
|
createdRun.status === 'succeeded' ? createdRun : await waitForAgentRun(createdRun.id);
|
||||||
|
|||||||
@@ -2702,6 +2702,50 @@ body,
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-force-goose-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
border: 1px solid var(--color-border-input);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
background: var(--color-bg-elevated);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.1;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-force-goose-toggle:hover {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border-color: var(--color-border-strong);
|
||||||
|
background: var(--color-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-force-goose-toggle input {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin: 0;
|
||||||
|
accent-color: #79b7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-force-goose-toggle.is-active {
|
||||||
|
color: #eff6ff;
|
||||||
|
border-color: rgba(121, 183, 255, 0.6);
|
||||||
|
background: rgba(121, 183, 255, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-force-goose-toggle:has(input:disabled) {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-skill-trigger {
|
.chat-skill-trigger {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -9249,6 +9293,16 @@ body,
|
|||||||
padding-inline: 12px;
|
padding-inline: 12px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-force-goose-toggle {
|
||||||
|
min-height: 36px;
|
||||||
|
padding-inline: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-force-goose-toggle span {
|
||||||
|
max-width: 56px;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { MindSpaceChatContext } from '../types';
|
|||||||
export type AgentRunCreateOptions = {
|
export type AgentRunCreateOptions = {
|
||||||
toolMode?: 'chat' | 'code';
|
toolMode?: 'chat' | 'code';
|
||||||
taskType?: string | null;
|
taskType?: string | null;
|
||||||
|
forceGoose?: boolean;
|
||||||
validation?: AgentRunValidation | null;
|
validation?: AgentRunValidation | null;
|
||||||
validationInstruction?: string | null;
|
validationInstruction?: string | null;
|
||||||
};
|
};
|
||||||
@@ -52,6 +53,16 @@ const CODE_TASK_PATTERNS = [
|
|||||||
/(代码|仓库|项目|文件|组件|接口).{0,12}(修改|修复|重构|调试|实现|新增|编写|更新)/,
|
/(代码|仓库|项目|文件|组件|接口).{0,12}(修改|修复|重构|调试|实现|新增|编写|更新)/,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const GOOSE_TASK_PATTERNS = [
|
||||||
|
/\b(public\/[^\s"'<>]+\.html)\b/i,
|
||||||
|
/\b(html|h5|web\s?page|landing\s?page|microsite|docx|word)\b/i,
|
||||||
|
/\b(write|create|generate|publish|download|export|save|edit)\b.{0,40}\b(file|page|html|docx|word|asset)\b/i,
|
||||||
|
/\b(file|page|html|docx|word|asset)\b.{0,40}\b(write|create|generate|publish|download|export|save|edit)\b/i,
|
||||||
|
/(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑).{0,24}(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页)/u,
|
||||||
|
/(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页).{0,24}(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑)/u,
|
||||||
|
/MindSpace\/[^/\s]+\/public\/[^\s"'<>]+\.html/i,
|
||||||
|
];
|
||||||
|
|
||||||
function sanitizeRequestIdForPath(requestId: string): string {
|
function sanitizeRequestIdForPath(requestId: string): string {
|
||||||
const normalized = String(requestId ?? '').trim().replace(/[^a-zA-Z0-9._-]/g, '-');
|
const normalized = String(requestId ?? '').trim().replace(/[^a-zA-Z0-9._-]/g, '-');
|
||||||
return normalized || 'unknown-request';
|
return normalized || 'unknown-request';
|
||||||
@@ -224,10 +235,18 @@ export function resolveAgentRunOptions(
|
|||||||
pageEdit?: { pageId?: string | null; pageTitle?: string | null } | null;
|
pageEdit?: { pageId?: string | null; pageTitle?: string | null } | null;
|
||||||
} = {},
|
} = {},
|
||||||
): AgentRunCreateOptions {
|
): AgentRunCreateOptions {
|
||||||
if (!agentCodeRunsEnabledForUser(userId)) return {};
|
|
||||||
const normalizedText = String(text ?? '').trim();
|
const normalizedText = String(text ?? '').trim();
|
||||||
|
const shouldForceGoose =
|
||||||
|
forceCode || GOOSE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText));
|
||||||
|
|
||||||
|
if (!agentCodeRunsEnabledForUser(userId)) {
|
||||||
|
return shouldForceGoose ? { forceGoose: true, taskType } : {};
|
||||||
|
}
|
||||||
|
|
||||||
const shouldUseCode = forceCode || (allowAutodetect && CODE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText)));
|
const shouldUseCode = forceCode || (allowAutodetect && CODE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText)));
|
||||||
if (!shouldUseCode) return {};
|
if (!shouldUseCode) {
|
||||||
|
return shouldForceGoose ? { forceGoose: true, taskType } : {};
|
||||||
|
}
|
||||||
const normalizedRequestId = requestId ?? crypto.randomUUID();
|
const normalizedRequestId = requestId ?? crypto.randomUUID();
|
||||||
const receipt = buildAgentRunValidationReceipt(normalizedRequestId);
|
const receipt = buildAgentRunValidationReceipt(normalizedRequestId);
|
||||||
const taskValidation = buildAgentRunTaskValidation({
|
const taskValidation = buildAgentRunTaskValidation({
|
||||||
@@ -240,6 +259,7 @@ export function resolveAgentRunOptions(
|
|||||||
return {
|
return {
|
||||||
toolMode: 'code',
|
toolMode: 'code',
|
||||||
taskType,
|
taskType,
|
||||||
|
forceGoose: true,
|
||||||
validation: mergeAgentRunValidationFiles(receipt.validation, taskValidation.validation),
|
validation: mergeAgentRunValidationFiles(receipt.validation, taskValidation.validation),
|
||||||
validationInstruction: `${receipt.instruction}${taskValidation.instruction}`,
|
validationInstruction: `${receipt.instruction}${taskValidation.instruction}`,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user