Add Experience V1 schema migration and agent-run completion extractor.
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>
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user