Steer generation rules toward direction pools and readable fills.

Direction-first pools, recipes, and grade manifestation replace enum-tag stuffing; spawned products can be deleted and in-flight runs aborted.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-08 01:02:32 +08:00
co-authored by Cursor
parent 3c751988de
commit e2446b0d97
23 changed files with 2023 additions and 292 deletions
+95 -17
View File
@@ -1224,21 +1224,6 @@ export function creationFlowFromRecipeSeed(
};
}
/**
* 已坐在配方开局步(无 depends_on)且「用户.需求」有首句时:
* 跳过程序再抛模块 opening——首句即该步对话,不是「先开场再进 DAG」。
*/
export function shouldSkipModuleOpening(opts: {
demand: string;
dependsOn?: string[] | null;
phase?: "shown" | "answered" | null;
}): boolean {
if (opts.phase === "shown" || opts.phase === "answered") return false;
if (!String(opts.demand ?? "").trim()) return false;
const deps = opts.dependsOn ?? [];
return deps.length === 0;
}
/** 无 id 时的兜底(目录仍应显式写 id) */
function slugFromName(name: string): string {
const map: Record<string, string> = {
@@ -2623,17 +2608,110 @@ export function removeUnstartedInstance(
if (!step || !isInstanceStep(step) || isStepAccepted(step, acceptedStepIds)) {
return flow;
}
return dropInstanceFromFlow(flow, step.id);
}
function dropInstanceFromFlow(flow: CreationFlow, stepId: string): CreationFlow {
return {
...flow,
steps: flow.steps
.filter((s) => s.id !== step.id)
.filter((s) => s.id !== stepId)
.map((s) => ({
...s,
depends_on: s.depends_on.filter((d) => d !== step.id),
depends_on: s.depends_on.filter((d) => d !== stepId),
})),
};
}
function repeatableInstanceLabel(step: CreationFlowStep): string {
const target = titleFromStepParams(step.params);
if (target && target !== step.name) return `${step.name} · ${target}`;
return step.id !== step.name ? `${step.name}(${step.id})` : step.name;
}
/**
* 图上删除可增殖产物:已验收也可删。
* 另有实例依赖这条时拒绝;收口等一次性节点只解开依赖。
*/
export function removeRepeatableInstance(params: {
flow: CreationFlow;
stepId: string;
catalog?: ModuleCatalog | null;
}): { flow: CreationFlow; removed: CreationFlowStep } | { error: string } {
const step = findStepByRef(params.flow, params.stepId.trim());
if (!step) return { error: "找不到节点" };
if (!isInstanceStep(step)) {
return { error: "只能删除可增殖能力长出来的产物" };
}
const mod = findModuleByName(params.catalog, step.name);
if (mod && mod.repeatable !== true) {
return { error: `「${step.name}」不是可增殖能力,不能从图上删` };
}
if (!mod && !step.from) {
return { error: "只能删除可增殖能力长出来的产物" };
}
const blockers = params.flow.steps.filter(
(s) =>
s.id !== step.id &&
isInstanceStep(s) &&
s.depends_on.includes(step.id),
);
if (blockers.length) {
return {
error: `还有产物依赖这条:${blockers.map(repeatableInstanceLabel).join("、")}。请先删那些产物。`,
};
}
return { flow: dropInstanceFromFlow(params.flow, step.id), removed: step };
}
/** 共享产物 tag 里按 rule_id 抽掉已删实例;同能力已无实例则清空。 */
export function pruneRepeatableArtifactContent(params: {
raw: string;
step: CreationFlowStep;
remainingSameModule: number;
}): string {
if (params.remainingSameModule <= 0) return "";
const stripped = stripFragmentRowsMatchingStep(params.raw, params.step);
return stripped ?? params.raw;
}
function stripFragmentRowsMatchingStep(
raw: string,
step: CreationFlowStep,
): string | null {
const parsed = tryParseJsonDoc(raw);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
return null;
}
const doc = structuredClone(parsed) as Record<string, unknown>;
const bodyRaw = doc.正文;
if (!bodyRaw || typeof bodyRaw !== "object" || Array.isArray(bodyRaw)) {
return null;
}
const body = bodyRaw as Record<string, unknown>;
const ruleId = flowParamText(step.params, "rule_id");
if (!ruleId) return null;
let changed = false;
for (const key of ["rules", "records"] as const) {
const rows = body[key];
if (!Array.isArray(rows)) continue;
const next = rows.filter((item) => {
if (!item || typeof item !== "object" || Array.isArray(item)) return true;
return (
String((item as Record<string, unknown>).rule_id ?? "").trim() !==
ruleId
);
});
if (next.length !== rows.length) {
body[key] = next;
changed = true;
}
}
if (!changed) return null;
doc.正文 = body;
return JSON.stringify(doc, null, 2);
}
/** 实例步 depends_on:具体实例钉最近已验收规则实例;其余跟原型。 */
function resolveInstanceDependsOn(params: {
flow: CreationFlow;
+11
View File
@@ -622,6 +622,17 @@ export function extractUnitContentFromDraft(
return null;
}
/** 从图上删除产物时拿掉该单位的验收切片 */
export function dropAcceptedUnitContent(
existingRaw: unknown,
unitId: string,
): string {
const store = parseAcceptedContentStore(existingRaw);
const id = normalizeCreationUnitId(unitId);
if (id) delete store.units[id];
return JSON.stringify(store, null, 2);
}
/** 写入/覆盖某一单位的最后验收内容 */
export function upsertAcceptedUnitContent(
existingRaw: unknown,
+31
View File
@@ -13,6 +13,36 @@ export const MAINTAIN_GENERATE_TAG = "运行.本轮.旁观.生成抽样";
type PoolEntry = { id?: string; 内容?: string; 权重?: number; weight?: number };
const DIRECTION_KINDS = new Set(["方向", "方向池", "type", "类型", "类型池"]);
const ELEMENT_KINDS = new Set(["元素", "元素池", "enum", "枚举", "枚举池"]);
function poolKindToken(pool: Record<string, unknown>): string | undefined {
return (
asString(pool.池型) ??
asString(pool.kind) ??
asString(pool.类型) ??
asString(pool.pool_type)
);
}
/** 方向池给 LLM 当创作范围,不能程序加权抽。 */
export function isElementPool(pool: Record<string, unknown>): boolean {
const kind = poolKindToken(pool);
if (kind && DIRECTION_KINDS.has(kind)) return false;
if (kind && ELEMENT_KINDS.has(kind)) return true;
const dirs = pool.方向;
const hasDirection =
(dirs != null &&
typeof dirs === "object" &&
!Array.isArray(dirs) &&
Object.keys(dirs as object).length > 0) ||
(Array.isArray(dirs) && dirs.length > 0);
const entries = pool.条目 ?? pool.entries;
const hasEntries = Array.isArray(entries) && entries.length > 0;
if (hasDirection && !hasEntries) return false;
return hasEntries;
}
function asString(v: unknown): string | undefined {
if (typeof v !== "string") return undefined;
const t = v.trim();
@@ -61,6 +91,7 @@ export function buildChanceBatchFromGenerationRule(
for (const poolRaw of pools) {
if (!poolRaw || typeof poolRaw !== "object" || Array.isArray(poolRaw)) continue;
const pool = poolRaw as Record<string, unknown>;
if (!isElementPool(pool)) continue;
const poolId =
asString(pool.pool_id) ?? asString(pool.池id) ?? asString(pool.名称);
const entries = (pool.条目 ?? pool.entries) as PoolEntry[] | undefined;