merge: server architecture modularization

This commit is contained in:
john
2026-07-24 19:18:14 +08:00
113 changed files with 29618 additions and 5864 deletions
+184 -2
View File
@@ -13,10 +13,15 @@ import {
} from './tkmind-proxy.mjs';
import { createMemoryV2 } from './memory-v2.mjs';
async function withFakeGoosedSession(handler, { conversation = [] } = {}) {
async function withFakeGoosedSession(
handler,
{ conversation = [], allowConversationUpdate = false } = {},
) {
const workingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memind-memory-v2-'));
const harnessEntries = [];
const replyBodies = [];
const updateBodies = [];
let activeConversation = conversation;
let server;
try {
@@ -42,11 +47,18 @@ async function withFakeGoosedSession(handler, { conversation = [] } = {}) {
id: 'session-1',
working_dir: workingDir,
goose_mode: 'chat',
conversation,
conversation: activeConversation,
}));
return;
}
if (req.method === 'PUT' && req.url === '/sessions/session-1') {
if (allowConversationUpdate) {
updateBodies.push(body);
activeConversation = body.conversation;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
return;
}
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'method not allowed' }));
return;
@@ -86,6 +98,7 @@ async function withFakeGoosedSession(handler, { conversation = [] } = {}) {
workingDir,
harnessEntries,
replyBodies,
updateBodies,
});
} finally {
if (server?.listening) {
@@ -198,6 +211,42 @@ test('sanitizePublicHtmlLinksInText keeps existing own public html links', () =>
}
});
test('sanitizePublicHtmlLinksInText finds pages in the workspace beside H5_USERS_ROOT', () => {
const owner = `test-user-${Date.now()}-shared-workspace`;
const runtimeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'memind-shared-workspace-'));
const previousUsersRoot = process.env.H5_USERS_ROOT;
const htmlPath = path.join(runtimeRoot, 'MindSpace', owner, 'public', 'poem.html');
process.env.H5_USERS_ROOT = path.join(runtimeRoot, 'users');
try {
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, '<!doctype html><title>Poem</title>');
const text =
`[诗词页面](https://m.tkmind.cn/MindSpace/${owner}/public/poem.html)`;
const next = sanitizePublicHtmlLinksInText(text, { id: owner, username: 'john' });
assert.match(next, new RegExp(`/MindSpace/${owner}/public/poem\\.html`));
assert.doesNotMatch(next, /页面生成未完成/);
} finally {
if (previousUsersRoot == null) delete process.env.H5_USERS_ROOT;
else process.env.H5_USERS_ROOT = previousUsersRoot;
fs.rmSync(runtimeRoot, { recursive: true, force: true });
}
});
test('sanitizePublicHtmlLinksInText restores a previously suppressed link after the page appears', () => {
const owner = `test-user-${Date.now()}-restored-link`;
const htmlPath = path.join(process.cwd(), 'MindSpace', owner, 'public', 'poem.html');
try {
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, '<!doctype html><title>Poem</title>');
const text = '页面链接:\n(页面生成未完成,已阻止显示失效链接:poem.html。)';
const next = sanitizePublicHtmlLinksInText(text, { id: owner, username: 'john' });
assert.match(next, new RegExp(`\\[poem\\.html\\]\\([^)]*/MindSpace/${owner}/public/poem\\.html\\)`));
assert.doesNotMatch(next, /页面生成未完成/);
} finally {
fs.rmSync(path.join(process.cwd(), 'MindSpace', owner), { recursive: true, force: true });
}
});
test('sanitizePublicHtmlLinksInText canonicalizes wrong MindSpace public hosts', () => {
const owner = `test-user-${Date.now()}-canonical-host`;
const previousBase = process.env.H5_PUBLIC_BASE_URL;
@@ -845,6 +894,7 @@ test('getRuntimeStatus preserves Memory V2 status contract for release gates', a
'eventLogEnabled',
'failOpen',
'profileEnabled',
'runtimeControl',
'selectedBackend',
'vectorEnabled',
]);
@@ -991,6 +1041,138 @@ test('submitSessionReplyForUser fails open when disclosure policy evaluation thr
});
});
test('submitSessionReplyForUser repairs poisoned tool history before sending the next turn', async () => {
const conversation = [
{ role: 'user', content: [{ type: 'text', text: '生成页面' }] },
{
role: 'assistant',
content: [{
type: 'toolRequest',
id: 'call-a',
toolCall: { status: 'success', value: { name: 'write_file', arguments: {} } },
}],
},
{ role: 'assistant', content: [{ type: 'text', text: '继续' }] },
{
role: 'user',
content: [{
type: 'toolResponse',
id: 'call-a',
toolResult: {
status: 'success',
value: { content: [{ type: 'text', text: 'ok' }], isError: false },
},
}],
},
];
await withFakeGoosedSession(async ({ apiTarget, workingDir, replyBodies, updateBodies }) => {
const proxy = createTkmindProxy({
apiTarget,
apiSecret: 'test-secret',
userAuth: {
...createMemoryTestUserAuth(workingDir),
async ownsSession() {
return true;
},
async canUseChat() {
return { ok: true };
},
async getUserById() {
return { id: 'user-1' };
},
async resolveUserPolicies() {
return { unrestricted: true, policies: {} };
},
},
});
await proxy.submitSessionReplyForUser(
'user-1',
'session-1',
'request-after-tool-repair',
{
role: 'user',
content: [{ type: 'text', text: '继续生成' }],
},
);
assert.equal(updateBodies.length, 1);
assert.deepEqual(
updateBodies[0].conversation.map(
(message) => message.content[0]?.id ?? message.content[0]?.text,
),
['生成页面', 'call-a', 'call-a', '继续'],
);
assert.equal(replyBodies.length, 1);
}, { conversation, allowConversationUpdate: true });
});
test('submitSessionReplyForUser requests a fresh session when Goose cannot update history', async () => {
const conversation = [
{
role: 'assistant',
content: [{
type: 'toolRequest',
id: 'call-a',
toolCall: { status: 'success', value: { name: 'write_file', arguments: {} } },
}],
},
{ role: 'assistant', content: [{ type: 'text', text: '继续' }] },
{
role: 'user',
content: [{
type: 'toolResponse',
id: 'call-a',
toolResult: {
status: 'success',
value: { content: [{ type: 'text', text: 'ok' }], isError: false },
},
}],
},
];
await withFakeGoosedSession(async ({ apiTarget, workingDir, replyBodies }) => {
const proxy = createTkmindProxy({
apiTarget,
apiSecret: 'test-secret',
userAuth: {
...createMemoryTestUserAuth(workingDir),
async ownsSession() {
return true;
},
async canUseChat() {
return { ok: true };
},
async getUserById() {
return { id: 'user-1' };
},
async resolveUserPolicies() {
return { unrestricted: true, policies: {} };
},
},
});
await assert.rejects(
proxy.submitSessionReplyForUser(
'user-1',
'session-1',
'request-needs-fresh-session',
{
role: 'user',
content: [{ type: 'text', text: '继续生成' }],
},
),
(error) => {
assert.equal(error.code, 'SESSION_TOOL_HISTORY_FRESH_SESSION_REQUIRED');
assert.equal(error.repairedConversation.length, 3);
return true;
},
);
assert.equal(replyBodies.length, 0);
}, { conversation });
});
test('submitSessionReplyForUser fails closed when historical image scrub is unsupported', async () => {
await withFakeGoosedSession(async ({ apiTarget, workingDir, replyBodies }) => {
const proxy = createTkmindProxy({