aps-agent/server/app/service.ts

570 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 应用服务:会话与任务编排、能力查询、事件推送(架构设计第 2 节)。
*
* 它持有会话与业务记录,但不做业务规则、不替引擎判断。
* A1 阶段用内存实现;A3.7 换成持久层后接口保持不变。
*/
import { randomBytes } from 'node:crypto';
import { PROTOCOL_VERSION, type CapabilitiesResponse, type CancelResponse, type CreateSessionRequest, type CreateSessionResponse, type HealthResponse, type MeResponse, type PlatformEvent, type ProjectListResponse, type SendMessageRequest, type SendMessageResponse, type SessionDetailResponse, type SessionListResponse, type ApprovalDecisionRequest, type ApprovalDecisionResponse, type ExecutionTraceResponse, type BindingCompleteRequest, type BindingCompleteResponse, type BindingDeleteResponse, type BindingListResponse, type BindingStartResponse, type BindingSummary } from '@aps/contracts';
import { CapabilityRegistry } from '../core/registry/registry.js';
import { createCore, type Core, type InvokeRequest } from '../core/execution/invoke.js';
import { createControlPlane, type ControlPlane } from '../core/control-plane/control-plane.js';
import { IdentityContext } from '../core/identity/context.js';
import { CredentialBroker } from '../core/credentials/broker.js';
import { PendingActionStore } from '../core/approval/store.js';
import { AuditLog } from '../core/audit/log.js';
import { createFakeRuntime } from '../agent/fake-runtime.js';
import type { AgentRuntime, RuntimeSession } from '../agent/runtime.js';
import { createRulePlanner, plannerToFakeTurn } from '../agent/rule-planner.js';
import { toToolDescriptors } from '../agent/tools.js';
import { RuntimeSupervisor, type SupervisorOptions } from '../agent/process-manager.js';
import type { SessionRepository } from '../core/storage/sessions.js';
import {
capabilityFingerprint,
isCapabilityVisible,
visibleCapabilities,
type CapabilityVisibility,
} from '../core/registry/visibility.js';
import { runTurn } from './turn-runner.js';
import { buildContext, type ContextBudget } from './context-builder.js';
import type { ContextRepository } from '../core/storage/context.js';
import { PLATFORM_SYSTEM_PROMPT } from './system-prompt.js';
import { ProviderError, type Provider, type ProviderAuthSpec, type ProviderManifest } from '@aps/provider-sdk';
import { AppError, badRequest, conflict } from './errors.js';
import { ProjectStore } from './projects.js';
import { newId, SessionStore, type SessionRecord } from './sessions.js';
import type { Subject } from './subject.js';
export interface ApplicationServiceOptions {
version?: string;
/** 引擎实现:默认是假引擎(A3.3 起可换成 Pi 子进程) */
runtime?: AgentRuntime;
/** 会话持久化(A3.7):不传则纯内存,测试与临时运行用 */
sessionRepository?: SessionRepository;
/** 能力可见性规则(A3.5):不传则开发期默认全部可见 */
capabilityVisibility?: CapabilityVisibility;
/** 上下文检查点(A3.8):不传则不落检查点 */
contextRepository?: ContextRepository;
/** 上下文预算覆盖(测试用) */
contextBudget?: Partial<ContextBudget>;
/** 引擎进程管理(A3.10):不传则用默认值自建 */
supervisor?: RuntimeSupervisor;
/** 进程管理参数覆盖(测试用) */
supervisorOptions?: Partial<SupervisorOptions>;
/** A4 控制面:五块(身份、策略、凭据、确认、审计)绑在一起的入口;不传则用内存实现自建 */
controlPlane?: ControlPlane;
/** 单独覆盖控制面的某一块(落库版由 server/index.ts 注入) */
identity?: IdentityContext;
credentials?: CredentialBroker;
approvals?: PendingActionStore;
audit?: AuditLog;
/** 确认卡有效期(测试用) */
approvalTtlMs?: number;
}
export class ApplicationService {
private readonly projects = new ProjectStore();
private readonly sessions: SessionStore;
private readonly version: string;
/** 能力目录与执行入口:能力清单来自这里,执行只有一个出口 */
readonly registry = new CapabilityRegistry();
readonly core: Core;
private readonly runtime: AgentRuntime;
private readonly visibility: CapabilityVisibility;
private readonly contextRepository: ContextRepository | undefined;
private readonly contextBudget: Partial<ContextBudget> | undefined;
/** 已经注入过上下文的运行时会话:同一运行时会话只注入一次(它自己有原生上下文) */
private readonly injectedRuntimeSessions = new Set<string>();
/** 每个平台会话一个运行时会话(ADR-0006:首版一进程一会话) */
/** 引擎进程管理:空闲回收、并发上限、崩溃退避重启(A3.10) */
readonly supervisor: RuntimeSupervisor;
/** A4 控制面:策略、身份、凭据、确认、审计的入口 */
readonly controlPlane: ControlPlane;
constructor(options: ApplicationServiceOptions = {}) {
this.version = options.version ?? '0.1.0';
this.sessions = new SessionStore(options.sessionRepository);
this.visibility = options.capabilityVisibility ?? { defaultAllow: true, denyWrite: false, grants: [] };
this.contextRepository = options.contextRepository;
this.contextBudget = options.contextBudget;
this.supervisor = options.supervisor ?? new RuntimeSupervisor(options.supervisorOptions ?? {});
this.controlPlane =
options.controlPlane ??
createControlPlane({
identity: options.identity ?? new IdentityContext(),
credentials: options.credentials ?? new CredentialBroker({ masterKey: randomBytes(32) }),
approvals: options.approvals ?? new PendingActionStore(),
audit: options.audit ?? new AuditLog(),
isVisible: (capability, subject) => isCapabilityVisible(capability, this.visibility, subject),
...(options.approvalTtlMs === undefined ? {} : { approvalTtlMs: options.approvalTtlMs }),
});
this.core = createCore({ registry: this.registry, controlPlane: this.controlPlane });
this.runtime = options.runtime ?? createFakeRuntime({ planner: plannerToFakeTurn(createRulePlanner()) });
}
/** 注册 Provider(A5 接入 APS、A8 接入第二个系统时调用);返回 manifest 便于启动日志与断言 */
async registerProvider(provider: Provider): Promise<ProviderManifest> {
return this.registry.register(provider);
}
/** 上下文检查点(A3.8):平台保存的摘要与来源 */
checkpoint(sessionId: string) {
return this.contextRepository?.get(sessionId);
}
/** 执行记录查询:GET /v1/executions/{id};待确认中的执行也能查到(回执 status = pending-confirmation) */
execution(subject: Subject, executionId: string) {
const record = this.core.executions.get(executionId);
if (record) {
if (record.subjectUserId !== subject.userId) throw new AppError('forbidden', '这是别人的执行记录', { status: 403 });
return record.receipt;
}
const action = this.core.approvals?.byExecution(executionId);
if (!action) return undefined;
if (action.ownerUserId !== subject.userId) throw new AppError('forbidden', '这是别人的执行记录', { status: 403 });
return action.receipt;
}
/** 我绑定了哪些业务系统账号(含未绑的:界面据此提示去绑) */
bindings(subject: Subject): BindingListResponse {
return { bindings: this.registry.providerIds().map((providerId) => this.bindingSummary(subject, providerId)) };
}
/** 发起绑定:按 Provider 声明的登录方式返回下一步(oauth 给跳转地址,密码类直接进 complete) */
bindingStart(providerId: string): BindingStartResponse {
const { auth } = this.requireAuthProvider(providerId);
return {
providerId,
authKind: auth.kind,
...(auth.authorizationUrl === undefined ? {} : { authorizationUrl: auth.authorizationUrl }),
};
}
/**
* 完成绑定:去业务系统换凭据 → 体检 → 加密存进凭据代理 → 记下账号映射。
* 密码只在这一条请求的内存里存在,落库的只有换回来的 token 密文。
*/
async bindingComplete(subject: Subject, providerId: string, body: BindingCompleteRequest): Promise<BindingCompleteResponse> {
const manifest = this.requireAuthProvider(providerId);
const provider = this.registry.provider(providerId);
if (typeof body.accountId !== 'string' || body.accountId.trim() === '') throw badRequest('accountId 必填');
if (provider?.exchange === undefined) {
throw new AppError('not_implemented', `${providerId} 声明了 ${manifest.auth.kind} 登录方式但没实现 exchange()`, { status: 501 });
}
let result;
try {
result = await provider.exchange({
accountId: body.accountId.trim(),
...(body.password === undefined ? {} : { password: body.password }),
...(body.code === undefined ? {} : { code: body.code }),
...(body.state === undefined ? {} : { state: body.state }),
});
} catch (error) {
// 绑定失败要给人看的原因:账号密码不对是 403,连不上是 503,其余按 400
const kind = error instanceof ProviderError ? error.kind : 'unknown_result';
const status = kind === 'forbidden' ? 403 : kind === 'unavailable' ? 503 : 400;
throw new AppError(kind === 'forbidden' ? 'forbidden' : 'invalid_request', error instanceof Error ? error.message : '绑定失败', { status });
}
if (provider.checkCredential !== undefined) {
const check = await provider.checkCredential(result.secret);
if (!check.ok) throw new AppError('forbidden', check.reason ?? '登录态没通过校验', { status: 403 });
}
this.controlPlane.identity.bind({
userId: subject.userId,
tenantId: subject.tenantId,
providerId,
accountId: result.accountId,
...(result.displayName === undefined ? {} : { displayName: result.displayName }),
status: 'active',
});
this.controlPlane.credentials.put(subject, providerId, result.secret);
this.controlPlane.audit.append({
actorUserId: subject.userId,
tenantId: subject.tenantId,
event: 'binding.created',
providerId,
decision: 'active',
detail: { accountId: result.accountId, authKind: manifest.auth.kind },
});
return { binding: this.bindingSummary(subject, providerId) };
}
/** 解绑:删掉凭据与账号映射;执行历史与审计保留(ADR-0008) */
unbind(subject: Subject, providerId: string): BindingDeleteResponse {
const provider = this.registry.provider(providerId);
if (provider === undefined) throw new AppError('not_found', `没有注册 ${providerId}`, { status: 404 });
this.controlPlane.credentials.remove(subject, providerId);
const removed = this.controlPlane.identity.unbind(subject, providerId);
this.controlPlane.audit.append({
actorUserId: subject.userId,
tenantId: subject.tenantId,
event: 'binding.removed',
providerId,
decision: removed ? 'removed' : 'not-bound',
});
return { providerId, removed };
}
private bindingSummary(subject: Subject, providerId: string): BindingSummary {
const authKind = this.registry.manifest(providerId)?.auth?.kind;
const binding = this.controlPlane.identity.resolve(subject, providerId);
return {
providerId,
accountId: binding?.accountId ?? null,
displayName: binding?.displayName ?? null,
status: binding?.status ?? 'unbound',
...(authKind === undefined ? {} : { authKind }),
...(binding === undefined ? {} : { updatedAt: binding.updatedAt }),
};
}
private requireAuthProvider(providerId: string): ProviderManifest & { auth: ProviderAuthSpec } {
const manifest = this.registry.manifest(providerId);
if (manifest === undefined) throw new AppError('not_found', `没有注册 ${providerId}`, { status: 404 });
if (manifest.auth === undefined) throw badRequest(`${providerId} 没有账号概念(未声明 auth)`);
return manifest as ProviderManifest & { auth: ProviderAuthSpec };
}
/** 执行追溯(A4.5):按 executionId 追到人、参数摘要、审批与回执;只有本人能看 */
executionTrace(subject: Subject, executionId: string): ExecutionTraceResponse {
const record = this.core.executions.get(executionId);
const action = this.core.approvals?.byExecution(executionId);
if (!record && !action) throw new AppError('not_found', '没有这条执行记录', { status: 404 });
const ownerUserId = record?.subjectUserId ?? action?.ownerUserId ?? '';
if (ownerUserId !== subject.userId) throw new AppError('forbidden', '这是别人的执行记录', { status: 403 });
const entries = this.controlPlane.audit.trace(executionId).map((entry) => ({
seq: entry.seq,
ts: entry.ts,
event: entry.event,
actorUserId: entry.actorUserId,
...(entry.capabilityId === undefined ? {} : { capabilityId: entry.capabilityId }),
...(entry.providerId === undefined ? {} : { providerId: entry.providerId }),
...(entry.paramsDigest === undefined ? {} : { paramsDigest: entry.paramsDigest }),
...(entry.decision === undefined ? {} : { decision: entry.decision }),
...(entry.detail === undefined ? {} : { detail: entry.detail }),
}));
const receipt = record?.receipt ?? action?.receipt;
return {
executionId,
approval:
action === undefined
? null
: {
approvalId: action.approvalId,
status: action.status,
...(action.decision === undefined ? {} : { decision: action.decision }),
...(action.decidedBy === undefined ? {} : { decidedBy: action.decidedBy }),
...(action.decidedAt === undefined ? {} : { decidedAt: action.decidedAt }),
revision: action.revision,
expiresAt: action.expiresAt,
},
entries,
chainOk: this.controlPlane.audit.verify().ok,
...(receipt === undefined ? {} : { receipt }),
};
}
/** 确认卡响应(协议第 7 节):校验归属与版本 → 签发一次性授权 → 执行 → 推事件 */
async decideApproval(subject: Subject, approvalId: string, body: ApprovalDecisionRequest): Promise<ApprovalDecisionResponse> {
if (body.decision !== 'approve' && body.decision !== 'reject') throw badRequest('decision 只能是 approve 或 reject');
if (typeof body.idempotencyKey !== 'string' || body.idempotencyKey.length < 8) throw badRequest('idempotencyKey 至少 8 个字符');
if (!Number.isInteger(body.revision) || body.revision < 0) throw badRequest('revision 必须是非负整数');
const pending = this.core.approvals?.get(approvalId);
if (pending) this.assertSessionAccess(subject, pending.sessionId); // 别人的会话连看都不给看
const result = await this.core.approve(subject, approvalId, {
decision: body.decision,
revision: body.revision,
idempotencyKey: body.idempotencyKey,
...(body.note === undefined ? {} : { note: body.note }),
});
if (result.kind === 'not-found') throw new AppError('not_found', '确认卡不存在', { status: 404 });
if (result.kind === 'forbidden') throw new AppError('forbidden', '只有会话发起人可以确认这次动作', { status: 403 });
const action = this.core.approvals?.get(approvalId);
const record = action ? this.sessions.get(action.sessionId) : undefined;
if (record) {
record.bus.publish({ sessionId: record.summary.sessionId, type: 'approval.resolved', data: { approvalId, decision: result.decision } });
if (result.receipt && result.receipt.status !== 'pending-confirmation') {
record.bus.publish({
sessionId: record.summary.sessionId,
type: 'execution.updated',
data: {
executionId: result.receipt.executionId,
status: result.receipt.status,
...(result.receipt.errorKind === undefined ? {} : { errorKind: result.receipt.errorKind }),
},
});
}
}
return { approvalId, decision: result.decision, ...(result.receipt === undefined ? {} : { receipt: result.receipt }) };
}
/** Core.invoke:工具调用的唯一出口(引擎侧经工具桥调用,见 A3.4) */
async invoke(request: InvokeRequest) {
return this.core.invoke(request);
}
health(): HealthResponse {
return { status: 'ok', version: this.version, protocolVersion: PROTOCOL_VERSION };
}
me(subject: Subject): MeResponse {
return {
userId: subject.userId,
tenantId: subject.tenantId,
displayName: subject.displayName,
clientKind: subject.clientKind,
};
}
listProjects(): ProjectListResponse {
return { projects: this.projects.list() };
}
listSessions(projectId?: string): SessionListResponse {
return { sessions: this.sessions.list(projectId) };
}
createSession(subject: Subject, request: CreateSessionRequest): CreateSessionResponse {
const scope = request.scope;
if (scope !== 'personal' && scope !== 'project') throw badRequest('scope 只能是 personal 或 project');
if (scope === 'project') {
const projectId = request.projectId;
if (!projectId) throw badRequest('项目会话必须带 projectId(ADR-0003)');
if (!this.projects.get(projectId)) throw badRequest(`项目 ${projectId} 不存在`);
}
const record = this.sessions.create({ scope, projectId: request.projectId, title: request.title }, subject);
return { session: record.summary };
}
getSession(subject: Subject, sessionId: string): SessionDetailResponse {
const record = this.assertSessionAccess(subject, sessionId);
return { session: record.summary, messages: record.messages };
}
/** 这个身份能看到的能力:能力清单与引擎工具列表都走这里,保证两者一致 */
private visibleCapabilitiesFor(subject: Subject) {
return visibleCapabilities(this.registry.list(), this.visibility, { userId: subject.userId, tenantId: subject.tenantId });
}
/** 会话归属校验:不是自己的会话一律拒绝(协作归业务系统,见 D9) */
assertSessionAccess(subject: Subject, sessionId: string): SessionRecord {
const record = this.sessions.require(sessionId);
if (record.owner.userId !== subject.userId) {
throw new AppError('forbidden', '这是别人的会话', { status: 403 });
}
return record;
}
capabilities(subject: Subject, sessionId?: string): CapabilitiesResponse {
if (sessionId) this.assertSessionAccess(subject, sessionId);
return {
capabilities: this.visibleCapabilitiesFor(subject).map((capability) => {
const providerId = this.registry.resolve(capability.id)?.providerId ?? '';
return {
id: capability.id,
providerId,
title: capability.title ?? capability.description,
purpose: capability.description,
effect: capability.effect,
inputSchema: capability.inputSchema,
...(capability.synonyms === undefined ? {} : { synonyms: capability.synonyms }),
};
}),
};
}
sendMessage(subject: Subject, sessionId: string, request: SendMessageRequest): SendMessageResponse {
const record = this.assertSessionAccess(subject, sessionId);
if (typeof request.text !== 'string' || request.text.trim() === '') throw badRequest('text 不能为空');
if (typeof request.idempotencyKey !== 'string' || request.idempotencyKey.length < 8) {
throw badRequest('idempotencyKey 至少 8 个字符');
}
// 幂等重试优先于「会话忙」判定:同一 key 的重试必须返回原结果,不能报冲突
const cached = this.sessions.findIdempotencyKey(sessionId, request.idempotencyKey);
if (cached) return { accepted: true, messageId: cached.messageId, runId: cached.runId };
if (record.state !== 'idle') throw conflict('上一个运行还没结束,先取消或等它完成');
const messageId = newId('m');
const runId = newId('r');
this.sessions.rememberIdempotencyKey(sessionId, request.idempotencyKey, messageId, runId);
this.sessions.appendMessage(sessionId, { messageId, role: 'user', ts: Date.now(), text: request.text });
record.bus.publish({ sessionId, runId, type: 'message.user', data: { messageId, text: request.text } });
const abort = new AbortController();
record.runs.set(runId, { runId, abort });
record.state = 'running';
void this.runTurn(record, runId, messageId, request.text, abort.signal);
return { accepted: true, messageId, runId };
}
/** 引擎回调入口(A3.11 工具桥的平台侧):按会话归属者发起调用,身份不由调用方自带 */
async invokeFromSession(
sessionId: string,
input: { capabilityId: string; params: unknown; idempotencyKey?: string; timeoutMs?: number },
) {
const record = this.sessions.require(sessionId);
const receipt = await this.core.invoke({
subject: { userId: record.owner.userId, tenantId: record.owner.tenantId },
sessionId,
projectId: record.summary.projectId,
capabilityId: input.capabilityId,
params: input.params,
...(input.idempotencyKey === undefined ? {} : { idempotencyKey: input.idempotencyKey }),
...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
});
// 写操作被策略挡在确认卡上:把确认卡推给界面(协议第 7 节第 2 步)
if (receipt.status === 'pending-confirmation' && receipt.approvalId !== undefined) {
const action = this.core.approvals?.get(receipt.approvalId);
if (action) {
record.bus.publish({
sessionId,
type: 'approval.requested',
data: {
approvalId: action.approvalId,
capabilityId: action.capabilityId,
paramsDigest: action.paramsDigest,
impactSummary: action.impactSummary,
revision: action.revision,
expiresAt: action.expiresAt,
},
});
}
}
return receipt;
}
cancel(subject: Subject, sessionId: string): CancelResponse {
const record = this.assertSessionAccess(subject, sessionId);
if (record.state !== 'running') return { state: 'idle' };
record.state = 'cancelling';
// 先让界面看到「正在取消」,再中止引擎与执行
const activeRun = [...record.runs.values()][0];
record.bus.publish({
sessionId,
...(activeRun === undefined ? {} : { runId: activeRun.runId }),
type: 'run.state',
data: { state: 'cancelling' },
});
// 取消:让引擎停在这一轮,回合自然结束(资源等空闲回收或失败回收)
this.supervisor.get(sessionId)?.abort();
for (const run of record.runs.values()) run.abort.abort();
return { state: 'cancelling' };
}
/** 事件流保活:无事件时由入口层定时调用(协议文档第 5 节) */
heartbeat(sessionId: string): void {
this.sessions.require(sessionId).bus.publish({ sessionId, type: 'heartbeat', data: {} });
}
subscribe(subject: Subject, sessionId: string, since: number, listener: (event: PlatformEvent) => void): () => void {
return this.assertSessionAccess(subject, sessionId).bus.subscribe(since, listener);
}
lastSeq(sessionId: string): number {
return this.sessions.require(sessionId).bus.lastSeq;
}
private async runTurn(record: SessionRecord, runId: string, messageId: string, text: string, signal: AbortSignal): Promise<void> {
const sessionId = record.summary.sessionId;
const assistantMessageId = `${messageId}-reply`;
try {
const runtimeSession = await this.runtimeSessionFor(record);
// 上下文由平台构建:新起的运行时会话要带上「摘要 + 最近若干轮」,已有原生上下文的会话不重复注入
const context = this.buildTurnContext(record, messageId);
const shouldInject = !this.injectedRuntimeSessions.has(runtimeSession.id);
this.injectedRuntimeSessions.add(runtimeSession.id);
const promptText = shouldInject && context.prefix !== '' ? `${context.prefix}${text}` : text;
const result = await runTurn({
runtimeSession,
sessionId,
runId,
assistantMessageId,
text: promptText,
signal,
publish: (event) => {
record.bus.publish(event);
},
invoke: (capabilityId, args, idempotencyKey) =>
this.invokeFromSession(sessionId, { capabilityId, params: args, idempotencyKey }),
});
if (result.text !== '') {
this.sessions.appendMessage(sessionId, { messageId: assistantMessageId, role: 'assistant', ts: Date.now(), text: result.text });
}
// 正常回合:清零连续失败计数;错误回合:回收后按退避重建(A3.10)
if (result.finishReason === 'error') {
await this.supervisor.evict(sessionId, 'failed', '回合以错误收尾');
} else {
this.supervisor.markHealthy(sessionId);
this.supervisor.touch(sessionId);
}
// 检查点:把这次构建出的摘要与来源落库,摘要与原始消息都保留(实施设计 1.3)
this.contextRepository?.save({
sessionId,
summary: context.summary,
summarySourceIds: context.summarySourceIds,
includedMessageIds: context.includedMessageIds,
lastMessageId: messageId,
pinned: [],
estimatedChars: context.estimatedChars,
});
} catch (error) {
await this.supervisor.evict(sessionId, 'failed', '引擎调用抛错').catch(() => undefined);
record.bus.publish({
sessionId,
runId,
type: 'error',
data: { code: 'engine.failed', message: error instanceof Error ? error.message : '引擎执行失败', retryable: true },
});
} finally {
record.runs.delete(runId);
if (record.state !== 'idle') record.state = 'idle';
}
}
/** 构建这一轮要注入的上下文(排除刚写入的当前用户消息) */
private buildTurnContext(record: SessionRecord, currentMessageId: string) {
const history = record.messages.filter((message) => message.messageId !== currentMessageId);
const executions = this.core.executions.listBySession(record.summary.sessionId, 6).map((item) => ({
capabilityId: item.capabilityId,
status: item.status,
executionId: item.executionId,
paramsDigest: item.paramsDigest,
createdAt: item.createdAt,
}));
return buildContext({
messages: history,
executions,
...(this.contextBudget === undefined ? {} : { budget: this.contextBudget }),
});
}
/** 取(或创建)平台会话对应的运行时会话:工具来自能力目录,历史来自平台记录 */
private async runtimeSessionFor(record: SessionRecord): Promise<RuntimeSession> {
const sessionId = record.summary.sessionId;
const capabilities = this.visibleCapabilitiesFor(record.owner);
// 身份或工具集变了就重建:工具集不能跨身份复用(架构设计 5.1「按身份给工具」)
const fingerprint = `${record.owner.userId}|${capabilityFingerprint(capabilities)}`;
return this.supervisor.acquire(sessionId, fingerprint, () =>
this.runtime.createSession({
sessionId,
subject: { userId: record.owner.userId, tenantId: record.owner.tenantId },
projectId: record.summary.projectId,
system: PLATFORM_SYSTEM_PROMPT,
tools: toToolDescriptors(capabilities),
history: record.messages,
}),
);
}
/** 进程管理统计(观测与用例用) */
processStats() {
return this.supervisor.stats();
}
}