feat(page-data): add validation suggestions, repair path, and thinking fixes
Memind CI / Test, build, and release guards (push) Failing after 3m18s

Map Page Data failure codes to Chinese remediation hints, trigger one-shot
goosed repair for remediable cases, and recover poisoned thinking sessions.
Route local DeepSeek through the no-think proxy via host.docker.internal so
tool rounds no longer hit reasoning_content 400; document gate and case-study
scenarios for event registration repair.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-25 23:01:55 +08:00
parent 6b6f9ed01c
commit 8ccf2db05c
28 changed files with 1652 additions and 154 deletions
+70 -20
View File
@@ -1,3 +1,4 @@
import crypto from 'node:crypto';
import { spawn as nodeSpawn } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
@@ -89,14 +90,26 @@ async function* parseSse(body) {
}
}
function terminalGoosedEvent(event) {
const type = String(
event?.event
?? event?.data?.type
function goosedEventType(event) {
// Real Goosed SSE frames use `event: message` (or omit event) and put the
// semantic type on `data.type` (e.g. Message / Finish). Prefer data.type so
// the generic SSE event name does not mask Finish.
const dataType = String(
event?.data?.type
?? event?.data?.event_type
?? '',
).trim().toLowerCase();
return ['finish', 'finished', 'completed', 'error', 'failed'].includes(type);
if (dataType) return dataType;
return String(event?.event ?? '').trim().toLowerCase();
}
function terminalGoosedEvent(event) {
const type = goosedEventType(event);
if (['finish', 'finished', 'completed', 'error', 'failed'].includes(type)) {
return true;
}
// Some Goose builds emit `{ type: "Error", error: "..." }` without event name.
return Boolean(event?.data?.error) && type === '';
}
export function createGoosedExecutorAdapter({
@@ -105,6 +118,8 @@ export function createGoosedExecutorAdapter({
fetchImpl = null,
allowRemote = false,
tlsInsecure = false,
provider = '',
model = '',
workspaceResolver = async (reference) => reference?.id ?? null,
} = {}) {
const target = normalizeBaseUrl(baseUrl, { allowRemote });
@@ -166,6 +181,33 @@ export function createGoosedExecutorAdapter({
sessionRef: { kind: 'goosed-session', id: sessionId },
});
const providerId = String(provider ?? '').trim();
const modelId = String(model ?? '').trim();
if (providerId) {
const providerRes = await transport(`${target}/agent/update_provider`, {
method: 'POST',
headers: headers(),
body: JSON.stringify({
session_id: sessionId,
provider: providerId,
...(modelId ? { model: modelId } : {}),
}),
signal: controller.signal,
...(dispatcher ? { dispatcher } : {}),
});
if (!providerRes.ok) {
throw await responseError(
providerRes,
'GOOSED_PROVIDER_UPDATE_FAILED',
'Goosed provider update failed',
);
}
await emit('executor_job_provider_set', {
provider: providerId,
model: modelId || null,
});
}
const eventsResponse = await transport(
`${target}/sessions/${encodeURIComponent(sessionId)}/events`,
{
@@ -188,9 +230,7 @@ export function createGoosedExecutorAdapter({
let finalEvent = null;
for await (const event of parseSse(eventsResponse.body)) {
eventCount += 1;
const eventType = String(
event?.event ?? event?.data?.type ?? event?.data?.event_type ?? 'message',
).slice(0, 96);
const eventType = (goosedEventType(event) || 'message').slice(0, 96);
await emit('executor_job_adapter_event', {
adapter: 'goosed',
eventType,
@@ -204,17 +244,26 @@ export function createGoosedExecutorAdapter({
return { eventCount, finalEvent };
})();
// Goosed /reply requires created + metadata.userVisible/agentVisible.
const userMessage = {
id: crypto.randomUUID(),
role: 'user',
created: Math.floor(Date.now() / 1000),
metadata: {
userVisible: true,
agentVisible: true,
},
content: [{ type: 'text', text: request.task.instruction }],
};
const reply = await transport(
`${target}/sessions/${encodeURIComponent(sessionId)}/reply`,
{
method: 'POST',
headers: headers(),
body: JSON.stringify({
request_id: jobId,
user_message: {
role: 'user',
content: [{ type: 'text', text: request.task.instruction }],
},
// Goosed requires a UUID request_id; Executor Job ids are not UUIDs.
request_id: crypto.randomUUID(),
user_message: userMessage,
}),
signal: controller.signal,
...(dispatcher ? { dispatcher } : {}),
@@ -224,14 +273,14 @@ export function createGoosedExecutorAdapter({
reply.body?.cancel?.().catch?.(() => {});
const finished = await finishPromise;
const finalType = String(
finished.finalEvent?.event
?? finished.finalEvent?.data?.type
?? finished.finalEvent?.data?.event_type
?? '',
).toLowerCase();
const finalType = goosedEventType(finished.finalEvent);
if (['error', 'failed'].includes(finalType)) {
throw adapterError('GOOSED_RUN_FAILED', 'Goosed reported a failed terminal event');
const detail = String(
finished.finalEvent?.data?.error
?? finished.finalEvent?.data?.message
?? 'Goosed reported a failed terminal event',
).slice(0, 500);
throw adapterError('GOOSED_RUN_FAILED', detail);
}
return {
outcome: 'completed',
@@ -518,5 +567,6 @@ export const executorAdapterInternals = {
normalizeBaseUrl,
parseSse,
resolveWorkspace,
goosedEventType,
terminalGoosedEvent,
};