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,
};
@@ -44,12 +44,17 @@ test('Goosed adapter uses the current session API, streams bounded metadata and
const adapter = createGoosedExecutorAdapter({
baseUrl: 'http://127.0.0.1:18006',
secret: 'secret',
provider: 'custom_deepseek',
model: 'deepseek-v4-pro',
workspaceResolver: async () => '/tmp/canary',
fetchImpl: async (url, init) => {
calls.push({ url, init });
if (url.endsWith('/agent/start')) {
return Response.json({ id: 'session-1' });
}
if (url.endsWith('/agent/update_provider')) {
return Response.json({ ok: true });
}
if (url.endsWith('/events')) {
return sseResponse([
{ type: 'message', data: { type: 'message', secret: 'not-projected' } },
@@ -70,15 +75,27 @@ test('Goosed adapter uses the current session API, streams bounded metadata and
assert.equal(calls[0].url, 'http://127.0.0.1:18006/agent/start');
assert.equal(calls[0].init.headers['X-Secret-Key'], 'secret');
assert.equal(JSON.parse(calls[0].init.body).working_dir, '/tmp/canary');
assert.equal(
JSON.parse(calls.find((call) => call.url.endsWith('/reply')).init.body)
.user_message.content[0].text,
'Fix the validation',
const providerBody = JSON.parse(
calls.find((call) => call.url.endsWith('/agent/update_provider')).init.body,
);
assert.deepEqual(providerBody, {
session_id: 'session-1',
provider: 'custom_deepseek',
model: 'deepseek-v4-pro',
});
const replyBody = JSON.parse(
calls.find((call) => call.url.endsWith('/reply')).init.body,
);
assert.equal(replyBody.user_message.content[0].text, 'Fix the validation');
assert.equal(replyBody.user_message.role, 'user');
assert.equal(typeof replyBody.user_message.created, 'number');
assert.equal(replyBody.user_message.metadata.userVisible, true);
assert.equal(replyBody.user_message.metadata.agentVisible, true);
assert.deepEqual(
emitted.map((event) => event.type),
[
'executor_job_session_created',
'executor_job_provider_set',
'executor_job_adapter_event',
'executor_job_adapter_event',
],
@@ -87,6 +104,51 @@ test('Goosed adapter uses the current session API, streams bounded metadata and
assert.deepEqual(await adapter.health(), { ok: true, status: 200 });
});
test('Goosed adapter treats data.type Finish as terminal when SSE event is message', async () => {
// Real Goosed emits: event: message + data: {"type":"Finish",...}
const emitted = [];
const adapter = createGoosedExecutorAdapter({
baseUrl: 'http://127.0.0.1:18006',
secret: 'secret',
workspaceResolver: async () => '/tmp/canary',
fetchImpl: async (url) => {
if (url.endsWith('/agent/start')) {
return Response.json({ id: 'session-finish' });
}
if (url.endsWith('/events')) {
return sseResponse([
{
type: 'message',
data: {
type: 'Message',
message: { role: 'assistant', content: [{ type: 'text', text: 'ok' }] },
},
},
{
type: 'message',
data: { type: 'Finish', reason: 'stop', request_id: 'req-1' },
},
]);
}
if (url.endsWith('/reply')) return Response.json({ ok: true });
if (url.endsWith('/status')) return Response.json({ ok: true });
throw new Error(`Unexpected URL: ${url}`);
},
});
const result = await adapter.submit(request(), {
jobId: 'job-finish',
emit: async (type, data) => emitted.push({ type, data }),
});
assert.equal(result.outcome, 'completed');
assert.equal(result.metrics.eventCount, 2);
assert.deepEqual(
emitted
.filter((event) => event.type === 'executor_job_adapter_event')
.map((event) => event.data.eventType),
['message', 'finish'],
);
});
test('Goosed adapter rejects remote targets unless explicitly allowed', () => {
assert.throws(
() => createGoosedExecutorAdapter({ baseUrl: 'https://executor.example.com' }),
+2
View File
@@ -44,6 +44,8 @@ export function createConfiguredWorker({ env = process.env, fetchImpl = null } =
...(fetchImpl ? { fetchImpl } : {}),
allowRemote: envFlag(env.MEMIND_EXECUTOR_ALLOW_REMOTE_GOOSED, false),
tlsInsecure: envFlag(env.MEMIND_EXECUTOR_GOOSED_TLS_INSECURE, false),
provider: env.MEMIND_EXECUTOR_GOOSED_PROVIDER,
model: env.MEMIND_EXECUTOR_GOOSED_MODEL,
workspaceResolver: async (reference) => workspaceAliases[reference?.id] ?? null,
}));
}