b923e54eff
Extend h5_experience with structured fields, wire mindspace-agent-runner and agent-run-gateway to persist task_outcome records with provenance, and add local migration and verification scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
3.4 KiB
JavaScript
111 lines
3.4 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
buildMindspaceJobExperiencePayload,
|
|
formatExperienceInjectionBlock,
|
|
formatExperienceInjectionLine,
|
|
mapExperienceRow,
|
|
normalizeExperienceRecordInput,
|
|
serializeExperienceJson,
|
|
} from './experience-schema.mjs';
|
|
|
|
test('normalizeExperienceRecordInput dedupes tags and defaults kind/status', () => {
|
|
const normalized = normalizeExperienceRecordInput({
|
|
title: ' 标题 ',
|
|
body: '正文',
|
|
tags: ['a', 'a', ' b ', ''],
|
|
kind: 'TASK_OUTCOME',
|
|
status: 'ACTIVE',
|
|
});
|
|
assert.equal(normalized.kind, 'task_outcome');
|
|
assert.equal(normalized.status, 'active');
|
|
assert.deepEqual(normalized.tags, ['a', 'b']);
|
|
});
|
|
|
|
test('normalizeExperienceRecordInput clamps confidence and validates result', () => {
|
|
const normalized = normalizeExperienceRecordInput({
|
|
title: 't',
|
|
body: 'b',
|
|
confidence: 1.5,
|
|
result: 'SUCCESS',
|
|
});
|
|
assert.equal(normalized.confidence, 1);
|
|
assert.equal(normalized.result, 'success');
|
|
});
|
|
|
|
test('serializeExperienceJson accepts objects and valid JSON strings', () => {
|
|
assert.equal(serializeExperienceJson({ a: 1 }), '{"a":1}');
|
|
assert.equal(serializeExperienceJson('{"a":1}'), '{"a":1}');
|
|
assert.equal(serializeExperienceJson(''), null);
|
|
});
|
|
|
|
test('mapExperienceRow parses JSON columns', () => {
|
|
const mapped = mapExperienceRow({
|
|
id: 'id-1',
|
|
scope: 'global',
|
|
kind: 'lesson',
|
|
title: 't',
|
|
body: 'b',
|
|
tags_json: '["x"]',
|
|
use_count: 2,
|
|
environment_json: '{"runtime":"portal"}',
|
|
evidence_json: '{"sources":[]}',
|
|
status: 'active',
|
|
created_at: 1,
|
|
updated_at: 2,
|
|
});
|
|
assert.deepEqual(mapped.tags, ['x']);
|
|
assert.deepEqual(mapped.environment, { runtime: 'portal' });
|
|
assert.equal(mapped.useCount, 2);
|
|
});
|
|
|
|
test('formatExperienceInjectionLine includes structured hints', () => {
|
|
const line = formatExperienceInjectionLine({
|
|
title: '部署',
|
|
body: '摘要',
|
|
problem: '公网访问失败',
|
|
result: 'success',
|
|
confidence: 0.9,
|
|
environment: { runtime: '103', components: ['caddy', 'portal'] },
|
|
});
|
|
assert.match(line, /部署: 摘要/);
|
|
assert.match(line, /问题: 公网访问失败/);
|
|
assert.match(line, /结果: success, conf=0.9/);
|
|
assert.match(line, /环境: 103 · caddy \+ portal/);
|
|
});
|
|
|
|
test('formatExperienceInjectionBlock wraps lines with header', () => {
|
|
const block = formatExperienceInjectionBlock([
|
|
{ title: 'A', body: 'one' },
|
|
{ title: 'B', body: 'two' },
|
|
]);
|
|
assert.match(block, /^# 相关经验/);
|
|
assert.match(block, /- A: one/);
|
|
assert.match(block, /- B: two/);
|
|
});
|
|
|
|
test('buildMindspaceJobExperiencePayload maps failed jobs to failure result', () => {
|
|
const payload = buildMindspaceJobExperiencePayload({
|
|
claim: { instruction: '生成页面', userId: 'u1' },
|
|
sessionId: 's1',
|
|
parsed: { title: '页面标题', summary: '完成摘要', status: 'failed' },
|
|
jobId: 'job-42',
|
|
});
|
|
assert.equal(payload.kind, 'task_outcome');
|
|
assert.equal(payload.result, 'failure');
|
|
assert.equal(payload.problem, '生成页面');
|
|
assert.equal(payload.sourceSessionId, 's1');
|
|
assert.equal(payload.evidence.provenance.job_id, 'job-42');
|
|
});
|
|
|
|
test('buildMindspaceJobExperiencePayload returns null when title or summary missing', () => {
|
|
assert.equal(
|
|
buildMindspaceJobExperiencePayload({
|
|
claim: { instruction: 'x' },
|
|
sessionId: 's',
|
|
parsed: { title: '', summary: 'ok' },
|
|
}),
|
|
null,
|
|
);
|
|
});
|