Archived
app 装配收尾:18 个模块接上,MVP 判据 1-2 端到端跑通
装配(app 是唯一组装层): - wiring/engines.ts:skill/flow/loop/task/process/trigger 六个模块经端口接起来; 两个范式引擎按统一 ParadigmEngine 契约暴露给 task —— 对上层不可区分 - agent-runner.ts:loop 的 Agent 回路经 ModelGateway(两缝之一,回放审看得见); 出参过 schema 框住三型行动,不合规退化为「等人」而不是乱动世界 - container:interaction / human-tool / activity-registry / release 全部接上; event 的 TriggerMatcher 与 org 的 InFlightWorkProbe 以延迟绑定解环,端口不变 - workers/consumers.ts:派发器的两个消费者(任务监听推进旧单 / 触发器开新单,独立判定) - tools/first-party.ts:查历史价(录制回放替身)+ 三分类打标(纯工具,DMN 确定性求值) - seed-quote.ts:治理台四件事一次做完 —— 绑 LLM / 注册工具两审 / 经安装通道装活动包 / 发布流程包(闭合校验通过)+ 配触发器与邮件通道 已验证(真机): 1. 治理台配置全过;活动包静态审 0 条提示、流程包闭合校验通过 2. 一封 RFQ 邮件入站 → 归属解析 → 触发器命中 1 → 起一条工作流 → A1 任务按岗位唯一人解析落到 BD 冯兵,runtime=running 修复:zod 判别式重复值(周期触发两型展开为 periodic-calendar / periodic-data, triggerKindOf 对回三类分类学);活动构建脚本移出 activities(它要用 fs, 而活动包零环境能力 —— depcruise 拦下后按边界搬家) 358 个测试通过,门禁五道全绿 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
co-authored by
Claude Opus 5
parent
c6cdb4c310
commit
78dc03bb58
+289
-12
@@ -42,6 +42,19 @@ import {
|
||||
} from '@modules/toolbox';
|
||||
import type { MessageService } from '@modules/message';
|
||||
import { createMessageRepository, createMessageService } from '@modules/message';
|
||||
import type { HumanToolService } from '@modules/human-tool';
|
||||
import { createCardStore, createHumanToolService } from '@modules/human-tool';
|
||||
import type { InteractionService } from '@modules/interaction';
|
||||
import { createInteractionService, createInteractionStore, templateIsSafe } from '@modules/interaction';
|
||||
import type { ActivityRegistryService } from '@modules/activity-registry';
|
||||
import { createRegistryService, createRegistryStore } from '@modules/activity-registry';
|
||||
import type { ReleaseService } from '@modules/release';
|
||||
import { createIntentStore, createReleaseService } from '@modules/release';
|
||||
import type { TaskService } from '@modules/task';
|
||||
import type { ProcessService } from '@modules/process';
|
||||
import type { TriggerService } from '@modules/trigger';
|
||||
import { FIRST_PARTY_TOOLS } from './tools/first-party.ts';
|
||||
import { wireEngines } from './wiring/engines.ts';
|
||||
import type { ChannelService } from '@modules/channel';
|
||||
import {
|
||||
createChannelRepository,
|
||||
@@ -68,21 +81,45 @@ export type Container = {
|
||||
readonly toolInvoker: ToolInvoker;
|
||||
readonly message: MessageService;
|
||||
readonly channel: ChannelService;
|
||||
readonly humanTool: HumanToolService;
|
||||
readonly interaction: InteractionService;
|
||||
readonly registry: ActivityRegistryService;
|
||||
readonly release: ReleaseService;
|
||||
readonly task: TaskService;
|
||||
readonly process: ProcessService;
|
||||
readonly trigger: TriggerService;
|
||||
close(): Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 在途工作探针 —— 停用前置检查要问的是 task(L6)与 human-tool(L3),二者随 M3/M4 落地。
|
||||
* 占位返回空集;M4 起接真实用例(docs/debt.md · M1-1)。
|
||||
* 在途工作探针 —— 停用前置检查要问的是 task(L6)与 human-tool(L3),二者层级在 org 之上,
|
||||
* 故以**延迟绑定**的端口接入:org 先构造,真身就位后回填(§5.2 跨层协作经端口)。
|
||||
*/
|
||||
const emptyInFlightProbe: InFlightWorkProbe = {
|
||||
openTasksOf: async () => [],
|
||||
openCardsOf: async () => [],
|
||||
openApprovalsOf: async () => [],
|
||||
};
|
||||
function createDeferredInFlightProbe(): InFlightWorkProbe & { bind(real: InFlightWorkProbe): void } {
|
||||
let real: InFlightWorkProbe | undefined;
|
||||
return {
|
||||
openTasksOf: async (id) => (real === undefined ? [] : real.openTasksOf(id)),
|
||||
openCardsOf: async (id) => (real === undefined ? [] : real.openCardsOf(id)),
|
||||
openApprovalsOf: async (id) => (real === undefined ? [] : real.openApprovalsOf(id)),
|
||||
bind(next) {
|
||||
real = next;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 触发器判定 —— trigger(L7)随 M5 落地,当下恒不命中(docs/debt.md · M1-1) */
|
||||
const noTriggerMatcher: TriggerMatcher = { match: async () => [] };
|
||||
/**
|
||||
* 触发器判定的**延迟绑定**:`event`(L1)在 `trigger`(L7)之前构造,
|
||||
* 用一层可后填的间接层把环解开 —— 端口不变,装配顺序解耦(§5.2)。
|
||||
*/
|
||||
function createDeferredTriggerMatcher(): TriggerMatcher & { bind(real: TriggerMatcher): void } {
|
||||
let real: TriggerMatcher | undefined;
|
||||
return {
|
||||
match: async (envelope) => (real === undefined ? [] : real.match(envelope)),
|
||||
bind(next) {
|
||||
real = next;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听内容条件的求值 —— @sfeel 的求值语义**前后端同源**(§8.3)。
|
||||
@@ -100,7 +137,7 @@ const sfeelEvaluator = (expression: string, scope: Readonly<Record<string, unkno
|
||||
};
|
||||
|
||||
/** 第一方工具实现 —— 由 app 注册进 ToolRuntime;活动只见调用契约 */
|
||||
export const firstPartyToolHandlers = new Map<string, ToolHandler>();
|
||||
export const firstPartyToolHandlers = new Map<string, ToolHandler>(FIRST_PARTY_TOOLS);
|
||||
|
||||
export async function createContainer(config: AppConfig): Promise<Container> {
|
||||
const pool = new Pool({ connectionString: config.databaseUrl, max: 10 });
|
||||
@@ -111,11 +148,13 @@ export async function createContainer(config: AppConfig): Promise<Container> {
|
||||
// 草稿层的建表:ledger schema 的唯一写者是 ledger adapter,DDL 在其出口给出
|
||||
await pool.query(DRAFT_DDL);
|
||||
|
||||
const inFlightProbe = createDeferredInFlightProbe();
|
||||
|
||||
const org = createOrgService({
|
||||
positions: createPositionRepository(db),
|
||||
staff: createStaffRepository(db),
|
||||
grants: createGrantRepository(db),
|
||||
inFlight: emptyInFlightProbe,
|
||||
inFlight: inFlightProbe,
|
||||
ids,
|
||||
});
|
||||
|
||||
@@ -142,12 +181,14 @@ export async function createContainer(config: AppConfig): Promise<Container> {
|
||||
ids,
|
||||
});
|
||||
|
||||
const triggerMatcher = createDeferredTriggerMatcher();
|
||||
|
||||
const event = createEventService({
|
||||
store: createEventStore(db, ids),
|
||||
clock: systemClock,
|
||||
ids,
|
||||
evaluate: sfeelEvaluator,
|
||||
triggers: noTriggerMatcher,
|
||||
triggers: triggerMatcher,
|
||||
messages: message,
|
||||
});
|
||||
|
||||
@@ -224,6 +265,235 @@ export async function createContainer(config: AppConfig): Promise<Container> {
|
||||
ids,
|
||||
});
|
||||
|
||||
/* ── L3/L4:交互、人类工具、活动库、放行门 ─────────────────────── */
|
||||
|
||||
const interaction = createInteractionService({
|
||||
store: createInteractionStore(db),
|
||||
events: {
|
||||
publishHumanEvent: async (input) => {
|
||||
await event.publish({
|
||||
type: 'human',
|
||||
correlation: {
|
||||
target: { kind: 'task', id: input.taskId },
|
||||
cause: { kind: 'card-reply', ref: input.cardId },
|
||||
keys: { cardId: input.cardId },
|
||||
},
|
||||
payload: { data: input.payload },
|
||||
occurredAt: systemClock.now().toISOString(),
|
||||
source: { kind: 'desk' },
|
||||
});
|
||||
},
|
||||
},
|
||||
memory: {
|
||||
append: async (input) =>
|
||||
void (await memory.append({
|
||||
...input,
|
||||
stamp: { contractVersion: '1', businessModelVersion: '1', skillVersions: [] },
|
||||
})),
|
||||
},
|
||||
audience: { canSee: async () => true },
|
||||
clock: systemClock,
|
||||
ids,
|
||||
});
|
||||
|
||||
const humanTool = createHumanToolService({
|
||||
cards: createCardStore(db),
|
||||
outbox: { sendElicitation: (input) => message.sendElicitation(input) },
|
||||
memory: {
|
||||
append: async (input) =>
|
||||
void (await memory.append({
|
||||
...input,
|
||||
stamp: { contractVersion: '1', businessModelVersion: '1', skillVersions: [] },
|
||||
})),
|
||||
},
|
||||
events: {
|
||||
publishHumanEvent: async (input) => {
|
||||
await event.publish({
|
||||
type: 'human',
|
||||
correlation: {
|
||||
target: { kind: 'task', id: input.taskId },
|
||||
cause: { kind: 'card-reply', ref: input.cardId },
|
||||
keys: { cardId: input.cardId },
|
||||
},
|
||||
payload: { data: input.payload },
|
||||
occurredAt: systemClock.now().toISOString(),
|
||||
source: { kind: 'desk' },
|
||||
});
|
||||
},
|
||||
},
|
||||
clock: systemClock,
|
||||
ids,
|
||||
});
|
||||
|
||||
const registry = createRegistryService({
|
||||
store: createRegistryStore(db),
|
||||
tools: { assertContractsAvailable: (names) => toolbox.assertContractsAvailable(names) },
|
||||
models: { assertNamesBound: (names) => llm.assertNamesBound(names) },
|
||||
ledger: {
|
||||
applyDeclarations: async (declarations) => {
|
||||
// 安装时依声明**自生长**:声明合并 → DDL → 只增不破坏
|
||||
const results = await ledger.applyDeclarations(declarations);
|
||||
return results.map((r) => ({
|
||||
applied: r.applied,
|
||||
conflicts: r.conflicts.map((c) => ({ field: c.field, message: c.message })),
|
||||
}));
|
||||
},
|
||||
},
|
||||
templates: { isSafe: templateIsSafe },
|
||||
clock: systemClock,
|
||||
ids,
|
||||
// 首次安装无历史 → 免回放审(OS 11.2)
|
||||
replayCases: async () => [],
|
||||
sdkVersion: '0.1.0-m5',
|
||||
});
|
||||
|
||||
const release = createReleaseService({
|
||||
intents: createIntentStore(db),
|
||||
ledger: {
|
||||
checkConflicts: async (taskId) =>
|
||||
(await ledger.checkConflicts(taskId)).map((c) => ({ field: c.field, key: c.key })),
|
||||
releaseWrite: (taskId) => ledger.releaseWrite(taskId),
|
||||
},
|
||||
tools: {
|
||||
isInvocable: async (tool, activityId) => {
|
||||
const contract = await toolbox.get(tool);
|
||||
if (contract === undefined) return { ok: false, reason: `工具未注册:${tool}` };
|
||||
const allowlist = await toolbox.allowlistOf(activityId);
|
||||
if (contract.pure !== true && !allowlist.includes(tool)) {
|
||||
return { ok: false, reason: `工具不在本活动白名单:${tool}` };
|
||||
}
|
||||
return { ok: true };
|
||||
},
|
||||
validateInput: async (tool, input) => {
|
||||
const contract = await toolbox.get(tool);
|
||||
if (contract === undefined) return { ok: false, errors: [`工具未注册:${tool}`] };
|
||||
return schemas.validate(contract.input, input);
|
||||
},
|
||||
validateTarget: async (input) => {
|
||||
const result = await toolInvoker.validate({ tool: input.tool, input: input.payload, activityId: input.activityId, taskId: input.taskId });
|
||||
if (result === undefined) return undefined;
|
||||
return { ok: result.ok, ...(result.error === undefined ? {} : { error: result.error.message }) };
|
||||
},
|
||||
execute: async (input) => {
|
||||
const result = await toolInvoker.invoke({
|
||||
tool: input.tool,
|
||||
input: input.payload,
|
||||
activityId: input.activityId,
|
||||
taskId: input.taskId,
|
||||
});
|
||||
return {
|
||||
id: input.idempotencyKey,
|
||||
ok: result.ok,
|
||||
...(result.error === undefined ? {} : { error: result.error.message }),
|
||||
retryable: result.error?.retryable ?? false,
|
||||
};
|
||||
},
|
||||
},
|
||||
cards: {
|
||||
issueAcceptance: async (input) => {
|
||||
const issued = await humanTool.issueAcceptance(
|
||||
{ taskId: input.taskId, ownerId: input.ownerId, humanCallCap: input.humanCallCap },
|
||||
{
|
||||
title: input.title,
|
||||
basis: [...input.basis],
|
||||
deliverable: input.deliverable as never,
|
||||
criteria: [...input.criteria],
|
||||
actions: input.actions.map((a) => ({ ...a })),
|
||||
fields: [],
|
||||
},
|
||||
);
|
||||
return { id: issued.order.id };
|
||||
},
|
||||
issueLandingFailure: async (input) => {
|
||||
const issued = await humanTool.issueLandingFailure(
|
||||
{ taskId: input.taskId, ownerId: input.ownerId, humanCallCap: input.humanCallCap },
|
||||
{ failures: input.failures },
|
||||
);
|
||||
return { id: issued.order.id };
|
||||
},
|
||||
},
|
||||
tasks: {
|
||||
activityIdOf: async (taskId) => (await engines.task.get(taskId)).activityId,
|
||||
outputPortsOf: async (taskId) => {
|
||||
const task = await engines.task.get(taskId);
|
||||
const manifest = await registry.manifestOf(task.activityId);
|
||||
return {
|
||||
mandatory: manifest.contract.output.mandatory.map((p) => p.name),
|
||||
selective: manifest.contract.output.selective.map((g) => ({
|
||||
group: g.group,
|
||||
ports: g.ports.map((p) => p.name),
|
||||
})),
|
||||
};
|
||||
},
|
||||
},
|
||||
ports: {
|
||||
emit: async (input) => {
|
||||
// 落地收敛才发射 —— 下游只见落地完成的任务
|
||||
await event.publish({
|
||||
type: 'data',
|
||||
correlation: {
|
||||
target: { kind: 'task', id: input.taskId },
|
||||
cause: { kind: 'engine', ref: 'output-port' },
|
||||
keys: { port: input.port },
|
||||
},
|
||||
payload: { data: input.payload },
|
||||
occurredAt: systemClock.now().toISOString(),
|
||||
source: { kind: 'engine', detail: 'release' },
|
||||
});
|
||||
},
|
||||
},
|
||||
memory: {
|
||||
append: async (input) =>
|
||||
void (await memory.append({
|
||||
...input,
|
||||
stamp: { contractVersion: '1', businessModelVersion: '1', skillVersions: [] },
|
||||
})),
|
||||
},
|
||||
clock: systemClock,
|
||||
ids,
|
||||
});
|
||||
|
||||
/* ── L5–L7:范式引擎、任务、流程层 ─────────────────────────────── */
|
||||
|
||||
const engines = wireEngines({
|
||||
db,
|
||||
clock: systemClock,
|
||||
ids,
|
||||
event,
|
||||
ledger,
|
||||
memory,
|
||||
toolbox,
|
||||
toolInvoker,
|
||||
humanTool,
|
||||
interaction,
|
||||
message,
|
||||
org,
|
||||
llm,
|
||||
modelGateway,
|
||||
registry,
|
||||
release,
|
||||
schemas,
|
||||
});
|
||||
|
||||
// 停用前置检查的真身就位:在途任务问 task,待答卡问 human-tool
|
||||
inFlightProbe.bind({
|
||||
openTasksOf: (staffId) => engines.task.openTasksOf(staffId),
|
||||
openCardsOf: (staffId) => humanTool.openCardsOf(staffId),
|
||||
openApprovalsOf: async () => [],
|
||||
});
|
||||
|
||||
// 触发器就位后回填 —— event 的三级消费自此真判定
|
||||
triggerMatcher.bind({
|
||||
match: (envelope) =>
|
||||
engines.trigger.match({
|
||||
type: envelope.type,
|
||||
keys: envelope.correlation.keys,
|
||||
payload: envelope.payload.data,
|
||||
source: envelope.source.kind,
|
||||
occurredAt: envelope.occurredAt,
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
config,
|
||||
pool,
|
||||
@@ -238,6 +508,13 @@ export async function createContainer(config: AppConfig): Promise<Container> {
|
||||
toolInvoker,
|
||||
message,
|
||||
channel,
|
||||
humanTool,
|
||||
interaction,
|
||||
registry,
|
||||
release,
|
||||
task: engines.task,
|
||||
process: engines.process,
|
||||
trigger: engines.trigger,
|
||||
async close() {
|
||||
await pool.end();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user