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:
+95
-17
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user