吸收数字员工长期记忆闭环

This commit is contained in:
baiyanyun
2026-06-09 13:49:11 +08:00
parent ef8252e2dd
commit ddd39396f7
12 changed files with 817 additions and 34 deletions

View File

@@ -22,6 +22,7 @@ import type {
FlowResponse,
GovernanceCommandRequest,
GovernanceCommandResponse,
MemoryEntry,
PlanDispatchResponse,
PlanStepSummary,
SkillSpec,
@@ -37,6 +38,9 @@ declare global {
getUiState?: () => Promise<AdapterState>;
getSkillCapabilities?: () => Promise<QimingclawSkillCapabilities>;
getPlanTemplates?: () => Promise<QimingclawPlanTemplateList>;
listMemories?: (options?: { query?: string; category?: string; limit?: number }) => Promise<QimingclawMemoryRecord[]>;
addMemory?: (input: QimingclawMemoryUpsertInput) => Promise<QimingclawMemoryRecord | null>;
deleteMemory?: (keyOrId: string) => Promise<QimingclawMemoryRecord | null>;
saveUiState?: (update: { state: AdapterState; action?: string; date?: string; metadata?: Record<string, unknown> }) => Promise<AdapterState>;
getSyncStatus?: () => Promise<QimingclawSyncStatus>;
flushSync?: () => Promise<QimingclawSyncStatus>;
@@ -196,6 +200,40 @@ interface QimingclawRuntimeRecords {
events: QimingclawEventRecord[];
artifacts?: QimingclawArtifactRecord[];
approvals?: QimingclawApprovalRecord[];
memories?: QimingclawMemoryRecord[];
}
interface QimingclawMemoryRecord {
id: string;
remoteId?: string | null;
key: string;
content: string;
category: string;
source: string;
sourcePath?: string | null;
sessionId?: string | null;
score?: number | null;
status: string;
payload: unknown;
syncStatus: string;
lastSyncedAt?: string | null;
syncError?: string | null;
createdAt: string;
updatedAt: string;
deletedAt?: string | null;
}
interface QimingclawMemoryUpsertInput {
id?: string;
key?: string;
content: string;
category?: string;
source?: string;
sourcePath?: string | null;
sessionId?: string | null;
score?: number | null;
status?: string;
payload?: Record<string, unknown>;
}
interface QimingclawCronSettings {
@@ -567,6 +605,17 @@ export async function handleQimingclawDigitalApi<T>(
if (method === 'GET' && pathname === '/api/artifact') {
return { artifacts: buildArtifacts(await readQimingclawSnapshot()) } as T;
}
if (method === 'GET' && pathname === '/api/memory') {
return { entries: await readMemoryEntries(url.searchParams.get('query'), url.searchParams.get('category')) } as T;
}
if (method === 'POST' && pathname === '/api/memory') {
return await handleMemoryCreate(parseJsonBody<Record<string, unknown>>(options.body)) as T;
}
const memoryDeleteMatch = pathname.match(/^\/api\/memory\/([^/]+)$/);
if (method === 'DELETE' && memoryDeleteMatch) {
await handleMemoryDelete(decodeURIComponent(memoryDeleteMatch[1] || ''));
return undefined as T;
}
if (method === 'GET' && pathname.startsWith('/api/debug/plan/') && pathname.endsWith('/flow')) {
return buildPlanFlow(decodeURIComponent(pathname.split('/')[4] || ''), await readQimingclawSnapshot()) as T;
}
@@ -743,6 +792,57 @@ async function saveCronSettings(patch: Partial<QimingclawCronSettings>): Promise
}
}
async function readMemoryEntries(query?: string | null, category?: string | null): Promise<MemoryEntry[]> {
try {
const records = await window.QimingClawBridge?.digital?.listMemories?.({
query: query || undefined,
category: category || undefined,
limit: 300,
}) ?? [];
return records.map(memoryEntryFromRecord);
} catch {
return [];
}
}
async function handleMemoryCreate(body: Record<string, unknown>): Promise<{ ok: true; entry: MemoryEntry | null }> {
const key = stringFromUnknown(body.key);
const content = stringFromUnknown(body.content ?? body.text);
const category = stringFromUnknown(body.category) || 'fact';
if (!content) return { ok: true, entry: null };
const record = await window.QimingClawBridge?.digital?.addMemory?.({
key: key || undefined,
content,
category,
source: 'qimingclaw-digital-page',
payload: {
source: 'embedded-memory-api',
key: key || null,
},
}) ?? null;
return { ok: true, entry: record ? memoryEntryFromRecord(record) : null };
}
async function handleMemoryDelete(keyOrId: string): Promise<void> {
try {
await window.QimingClawBridge?.digital?.deleteMemory?.(keyOrId);
} catch {
// Keep the DELETE endpoint best-effort so the UI can refresh into the true state.
}
}
function memoryEntryFromRecord(record: QimingclawMemoryRecord): MemoryEntry {
return {
id: record.id,
key: record.key || record.id,
content: record.content,
category: record.category || 'fact',
timestamp: record.updatedAt || record.createdAt || new Date().toISOString(),
session_id: record.sessionId ?? null,
score: typeof record.score === 'number' ? record.score : null,
};
}
function defaultCronSettings(): QimingclawCronSettings {
return {
enabled: true,