/** * 执行记录(架构设计 4.2:执行模块)。 * * 平台侧的权威记录:executionId、能力、参数摘要、状态、回执、幂等键。 * A3.7 落库前的内存实现;幂等索引与参数摘要的语义不随存储变化。 */ import { createHash } from 'node:crypto'; import type { ExecutionReceipt } from '@aps/contracts'; export interface ExecutionRecord { executionId: string; capabilityId: string; providerId: string; sessionId: string; projectId: string | null; subjectUserId: string; /** 参数摘要:只存摘要不存明文(架构设计 4.1「凭据不进日志」同理) */ paramsDigest: string; idempotencyKey?: string; status: ExecutionReceipt['status']; receipt: ExecutionReceipt; createdAt: number; updatedAt: number; } export function digestParams(params: unknown): string { const canonical = JSON.stringify(params ?? null, Object.keys((params ?? {}) as Record).sort()); return `sha256:${createHash('sha256').update(canonical).digest('hex').slice(0, 16)}`; } export class ExecutionStore { private readonly records = new Map(); private readonly byIdempotencyKey = new Map(); create(input: Omit & { receipt: ExecutionReceipt }): ExecutionRecord { const now = Date.now(); const record: ExecutionRecord = { ...input, status: input.receipt.status, createdAt: now, updatedAt: now, }; this.records.set(record.executionId, record); if (record.idempotencyKey) this.byIdempotencyKey.set(record.idempotencyKey, record.executionId); return record; } complete(executionId: string, receipt: ExecutionReceipt): ExecutionRecord | undefined { const record = this.records.get(executionId); if (!record) return undefined; record.receipt = receipt; record.status = receipt.status; record.updatedAt = Date.now(); return record; } get(executionId: string): ExecutionRecord | undefined { return this.records.get(executionId); } findByIdempotencyKey(key: string): ExecutionRecord | undefined { const executionId = this.byIdempotencyKey.get(key); return executionId ? this.records.get(executionId) : undefined; } /** 某个会话最近的执行记录(上下文摘要里引用工具结果用) */ listBySession(sessionId: string, limit = 10): ExecutionRecord[] { return [...this.records.values()] .filter((record) => record.sessionId === sessionId) .sort((a, b) => b.createdAt - a.createdAt) .slice(0, limit); } list(limit = 50): ExecutionRecord[] { return [...this.records.values()].sort((a, b) => b.createdAt - a.createdAt).slice(0, limit); } }