把正文组成收成数据契约,程序投影与旁观维护不再靠模型临场猜。

有变量真值时固定开旁观填表,开场与游玩只填模型生成区。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 23:47:58 +08:00
co-authored by Cursor
parent bb4f934b3d
commit d0a48f9431
21 changed files with 3094 additions and 224 deletions
+9 -3
View File
@@ -75,18 +75,24 @@ export function protagonistFragmentFromPersona(
);
}
/** 黑板上尚无该 tag 的主角设定时写入(可指定开场同槽 tag)。 */
/**
* 黑板上尚无该 tag 的主角设定时写入(可指定开场同槽 tag)。
* overwrite:开场自带的同槽名片随开场改稿同步。
*/
export function ensureProtagonistFromPersona(
board: Blackboard,
persona: OpeningUserPersona | null | undefined,
tag: string = PROTAGONIST_TAG,
opts: { overwrite?: boolean } = {},
): boolean {
if (!persona?.name?.trim()) return false;
const writeTag = tag.trim() || PROTAGONIST_TAG;
if (board.getContentByTag(writeTag)?.trim()) return false;
const existing = board.getContentByTag(writeTag)?.trim();
const content = protagonistFragmentFromPersona(persona);
if (existing && (!opts.overwrite || existing === content.trim())) return false;
board.write({
tag: writeTag,
content: protagonistFragmentFromPersona(persona),
content,
source: "runtime",
});
return true;
+110 -21
View File
@@ -140,6 +140,10 @@ import {
isDictateModeValue,
} from "../dictate/types.js";
import { applyDictatePlayBind } from "../dictate/play-bind.js";
import {
applyMaintenanceStrategyToPlaySlots,
resolveMaintenanceStrategyFromBoard,
} from "../skills/maintenance-strategy.js";
import {
dictateProductFamily,
mergeGenerationRulesArtifacts,
@@ -148,6 +152,11 @@ import {
VARIABLE_CATALOG_TAG,
seedVariablesFromCatalog,
} from "../skills/variable-catalog.js";
import {
VARIABLE_DESIGN_TAG,
syncVariableDesignToRuntime,
writeVariableDesignSync,
} from "../skills/variable-design-compile.js";
import {
VALUE_MAP_TAG,
reprojectValueMaps,
@@ -164,6 +173,11 @@ import {
parseShellAdaptationFromReplyFormat,
presentOutputFromFallback,
} from "../skills/present-packet.js";
import { parseReplyDataContract } from "../skills/reply-contract.js";
import {
mergePresentWithProjections,
projectPresentBlocks,
} from "../skills/present-project.js";
import { debugLog, labelAction, logStateChange, sessionSnap } from "../log.js";
import {
executeChance,
@@ -2049,21 +2063,45 @@ export class PhaseRuntime {
// 偏好/文风改由节点产物(设计.用户需求 / 设计.叙事指南*)挂载,不再全量自动摊库
if (this.isDictateIntakeMode()) {
const bound = applyDictatePlayBind(this.blackboard);
if (bound.boundTags.length) {
if (!bound.skipped && bound.boundTags.length) {
this.onMessage(
`[对话落盘] 已自动挂载 ${bound.boundTags.length} 个产物到游玩上下文:${bound.boundTags.join("、")}`,
);
} else {
} else if (!bound.skipped) {
this.onMessage("[对话落盘] 已生成默认游玩规格(尚无产物可挂载)");
}
const strategyRaw = this.blackboard.getContentByTag("设计.维护策略")?.trim();
if (strategyRaw) {
try {
const s = JSON.parse(strategyRaw) as {
advice?: string;
auditor?: boolean;
inline?: boolean;
};
if (s.auditor) {
this.onMessage(
`[维护策略] 旁观副 LLM 填表(play_slots.auditor=true)`,
);
} else if (s.advice && s.advice !== "none") {
this.onMessage(`[维护策略] ${s.advice} → 旁观未开`);
}
} catch {
/* ignore */
}
}
this.seedVariablesAndReproject("opening-setup");
} else {
const finalTag = this.blackboard.getContentByTag(WORKER_SET_FINAL_TAG)?.trim();
const draft = this.blackboard.getContentByTag(WORKER_SET_DRAFT_TAG)?.trim();
if (!finalTag) {
const strategy = resolveMaintenanceStrategyFromBoard(this.blackboard);
const play_slots = applyMaintenanceStrategyToPlaySlots(
defaultPlaySlots(),
strategy,
);
const spec = draft?.trim()
? draft
: JSON.stringify({ play_slots: defaultPlaySlots() });
: JSON.stringify({ play_slots }, null, 2);
this.writeWorkerTagContent(WORKER_SET_FINAL_TAG, spec, "opening-setup");
}
}
@@ -2077,36 +2115,84 @@ export class PhaseRuntime {
};
}
/** 目录初值 → 变量.当前;再按 设计.变量映射 重写投影 tag */
/** 变量设计 → 目录 → 变量.当前;再按 设计.变量映射 重写投影 tag */
private seedVariablesAndReproject(actor: string): void {
const seeded = seedVariablesFromCatalog({
const designSync = syncVariableDesignToRuntime({
designRaw: this.blackboard.getContentByTag(VARIABLE_DESIGN_TAG),
catalogRaw: this.blackboard.getContentByTag(VARIABLE_CATALOG_TAG),
currentRaw: this.blackboard.getContentByTag(OPENING_CURRENT_VARS_TAG),
initialRaw: this.blackboard.getContentByTag(OPENING_INITIAL_VARS_TAG),
actor: `system:${actor}`,
});
if (seeded.initial) {
this.blackboard.write({
tag: OPENING_INITIAL_VARS_TAG,
content: seeded.initial,
source: actor,
if (designSync.catalog || designSync.current || designSync.initial) {
writeVariableDesignSync(designSync, (tag, content) => {
this.blackboard.write({ tag, content, source: actor });
});
}
if (seeded.current) {
this.blackboard.write({
tag: OPENING_CURRENT_VARS_TAG,
content: seeded.current,
source: actor,
});
if (seeded.seededKeys.length) {
if (designSync.added.length) {
this.onMessage(
`[变量] 已种初值:${seeded.seededKeys.join("、")}`,
`[变量] 已从变量设计同步目录:${designSync.added.join("、")}`,
);
}
if (designSync.current && designSync.seededKeys.length) {
this.onMessage(
`[变量] 已种初值:${designSync.seededKeys.join("、")}`,
);
}
} else {
const seeded = seedVariablesFromCatalog({
catalogRaw: this.blackboard.getContentByTag(VARIABLE_CATALOG_TAG),
currentRaw: this.blackboard.getContentByTag(OPENING_CURRENT_VARS_TAG),
initialRaw: this.blackboard.getContentByTag(OPENING_INITIAL_VARS_TAG),
actor: `system:${actor}`,
});
if (seeded.initial) {
this.blackboard.write({
tag: OPENING_INITIAL_VARS_TAG,
content: seeded.initial,
source: actor,
});
}
if (seeded.current) {
this.blackboard.write({
tag: OPENING_CURRENT_VARS_TAG,
content: seeded.current,
source: actor,
});
if (seeded.seededKeys.length) {
this.onMessage(
`[变量] 已种初值:${seeded.seededKeys.join("、")}`,
);
}
}
}
this.applyValueMapProjections(actor);
}
/** 把 program_projection 块从黑板灌进 present 正文 */
private enrichPresentWithProjections(content: string): string {
const trimmed = content.trim();
// 纯 Markdown 终稿保持原文;投影由视图 presentationProjection 灌进壳
if (!trimmed.startsWith("{")) return content;
const replyFormatRaw =
this.blackboard.getContentByTag("设计.正文组成") ??
this.blackboard.getContentByTag("设计.0层正文组成") ??
this.blackboard.getContentByTag("设计.回复格式");
const contract = parseReplyDataContract(replyFormatRaw);
if (!contract?.programBlocks.length) return content;
const projections = projectPresentBlocks({
contract,
statusBarRaw:
this.blackboard.getContentByTag("设计.监控栏") ??
this.blackboard.getContentByTag("设计.回复呈现"),
currentVarsRaw: this.blackboard.getContentByTag(OPENING_CURRENT_VARS_TAG),
topologyRaw: this.blackboard.getContentByTag("运行.拓扑"),
readTag: (tag) => this.blackboard.getContentByTag(tag),
});
if (!Object.keys(projections).length) return content;
const shell =
parseShellAdaptationFromReplyFormat(replyFormatRaw)?.shell_id ?? "prose";
return mergePresentWithProjections(content, projections, shell);
}
/** 按当前真值持续投影映射 tag(非 once) */
private applyValueMapProjections(workerId: string): void {
if (!this.blackboard.getContentByTag(VALUE_MAP_TAG)?.trim()) return;
@@ -2589,10 +2675,13 @@ export class PhaseRuntime {
): string {
const tableTags = new Set(["运行.初始变量", "变量.当前"]);
let toWrite = content;
if (tag === "输出.用户展示" || tag === "输出.开场白") {
toWrite = this.enrichPresentWithProjections(toWrite);
}
let prevDoc: TableDoc | null = null;
let nextDoc: TableDoc | null = null;
if (tableTags.has(tag)) {
const patch = parseTableDoc(content);
const patch = parseTableDoc(toWrite);
if (patch) {
prevDoc = parseTableDoc(this.blackboard.getContentByTag(tag));
const { doc, skipped } = mergeTableCells({
+199
View File
@@ -0,0 +1,199 @@
/**
* 有变量真值时固定旁观副 LLM 填表(auditor_bundle)。
* 旁观上下文刻意保持小:上一份表 + 创作期维护准则 + 本轮主世界正文;
* 不再按维护复杂度.建议在内联 / 旁观间切换,避免主世界 tool / state_ops 反复探测。
* 变量设计里的维护复杂度可作旁观备注,不驱动开槽。
*/
import type { Blackboard } from "../blackboard/blackboard.js";
import { extractJsonObjectText } from "./worker-set-parse.js";
import type { PlaySlotsConfig } from "./play-slots.js";
import { VARIABLE_DESIGN_TAG } from "./variable-design-compile.js";
export const MAINTENANCE_STRATEGY_SCHEMA = "maintenance-strategy.v1" as const;
export const MAINTENANCE_STRATEGY_TAG = "设计.维护策略";
export type MaintenanceAdvice =
| "none"
| "inline_tool"
| "inline_structured"
| "auditor_bundle";
export type MaintenanceStrategyDoc = {
schema: typeof MAINTENANCE_STRATEGY_SCHEMA;
advice: MaintenanceAdvice;
/** 是否启用旁观维护槽 */
auditor: boolean;
/** 是否走主世界内联维护(已弃用开槽;固定旁观后恒为 false) */
inline: boolean;
source: string;
依据?: string[];
};
function asString(v: unknown): string | undefined {
if (typeof v !== "string") return undefined;
const t = v.trim();
return t || undefined;
}
function asObject(v: unknown): Record<string, unknown> | undefined {
if (!v || typeof v !== "object" || Array.isArray(v)) return undefined;
return v as Record<string, unknown>;
}
export function normalizeMaintenanceAdvice(raw: unknown): MaintenanceAdvice {
const s = asString(raw)?.toLowerCase() ?? "";
if (
s === "auditor_bundle" ||
s === "auditor" ||
s === "旁观" ||
s === "旁观维护" ||
s === "bundle"
) {
return "auditor_bundle";
}
if (
s === "inline_structured" ||
s === "structured" ||
s === "内联结构化" ||
s === "state_ops"
) {
return "inline_structured";
}
if (
s === "inline_tool" ||
s === "inline" ||
s === "tool" ||
s === "内联" ||
s === "内联工具"
) {
return "inline_tool";
}
if (s === "none" || s === "无" || s === "不维护") return "none";
return "none";
}
function designHasTruths(raw: string | null | undefined): boolean {
if (!raw?.trim()) return false;
const text = extractJsonObjectText(raw) ?? raw.trim();
let doc: unknown;
try {
doc = JSON.parse(text);
} catch {
return false;
}
const root = asObject(doc);
if (!root) return false;
const body = asObject(root.正文) ?? root;
const truths = body.真值 ?? body.fields ?? body.变量;
return Array.isArray(truths) && truths.length > 0;
}
/** 解析变量设计产物中的维护复杂度(仅作备注;开槽不读此建议) */
export function parseMaintenanceAdviceFromVariableDesign(
raw: string | null | undefined,
): { advice: MaintenanceAdvice; 依据: string[] } | null {
if (!raw?.trim()) return null;
const text = extractJsonObjectText(raw) ?? raw.trim();
let doc: unknown;
try {
doc = JSON.parse(text);
} catch {
return null;
}
const root = asObject(doc);
if (!root) return null;
const body = asObject(root.正文) ?? root;
const box = asObject(body.维护复杂度) ?? asObject(body.maintenance);
if (!box) {
if (designHasTruths(raw)) {
return {
advice: "auditor_bundle",
依据: ["变量设计有真值;程序固定旁观副 LLM 填表"],
};
}
return { advice: "none", 依据: [] };
}
const advice = normalizeMaintenanceAdvice(box.建议 ?? box.advice ?? box.strategy);
const reasonsRaw = box.依据 ?? box.reasons ?? box.why;
const 依据 = Array.isArray(reasonsRaw)
? reasonsRaw.map((r) => asString(r)).filter((r): r is string => Boolean(r))
: asString(reasonsRaw)
? [asString(reasonsRaw)!]
: [];
return { advice, 依据 };
}
export function strategyFromAdvice(
advice: MaintenanceAdvice,
依据: string[] = [],
source = VARIABLE_DESIGN_TAG,
): MaintenanceStrategyDoc {
// 开槽层:仅 auditor_bundle 开旁观;inline_* 在 resolve 阶段会被抬成旁观
const auditor = advice === "auditor_bundle";
const inline = advice === "inline_tool" || advice === "inline_structured";
return {
schema: MAINTENANCE_STRATEGY_SCHEMA,
advice,
auditor,
inline,
source,
...(依据.length ? { 依据 } : {}),
};
}
/**
* 有真值 → 固定旁观填表;无真值 → 不启旁观。
* 设计里的 inline_* 建议不关闭旁观,只记入依据。
*/
export function resolveMaintenanceStrategyFromBoard(
board: Blackboard,
): MaintenanceStrategyDoc {
const designRaw = board.getContentByTag(VARIABLE_DESIGN_TAG);
if (!designHasTruths(designRaw)) {
return strategyFromAdvice(
"none",
["无变量真值,不启旁观填表"],
VARIABLE_DESIGN_TAG,
);
}
const parsed = parseMaintenanceAdviceFromVariableDesign(designRaw);
const 依据 = [
"程序固定旁观副 LLM 填表(上一份表 + 维护准则 + 本轮主世界正文)",
...(parsed?.依据 ?? []),
];
if (
parsed &&
parsed.advice !== "auditor_bundle" &&
parsed.advice !== "none"
) {
依据.push(`设计原建议 ${parsed.advice},已忽略内联开槽`);
}
return strategyFromAdvice("auditor_bundle", 依据, VARIABLE_DESIGN_TAG);
}
/** 按维护策略覆盖 play_slots.auditor(不改 gm/narrator 等) */
export function applyMaintenanceStrategyToPlaySlots(
slots: PlaySlotsConfig,
strategy: MaintenanceStrategyDoc,
): PlaySlotsConfig {
return {
...slots,
auditor: strategy.auditor,
};
}
export function serializeMaintenanceStrategy(doc: MaintenanceStrategyDoc): string {
return JSON.stringify(doc, null, 2);
}
export function writeMaintenanceStrategy(
board: Blackboard,
strategy: MaintenanceStrategyDoc,
source = "system:maintenance-strategy",
): void {
board.write({
tag: MAINTENANCE_STRATEGY_TAG,
content: serializeMaintenanceStrategy(strategy),
source,
});
}
+10 -5
View File
@@ -347,13 +347,18 @@ export function parseOpeningUserPersona(
if (!text) return null;
try {
const doc = JSON.parse(text) as unknown;
const row =
doc && typeof doc === "object" && !Array.isArray(doc)
? (doc as Record<string, unknown>)
: undefined;
const meta =
row?.meta && typeof row.meta === "object" && !Array.isArray(row.meta)
? (row.meta as Record<string, unknown>)
: undefined;
const fromJson =
parseUserPersonaField(doc) ||
parseUserPersonaField(
doc && typeof doc === "object" && !Array.isArray(doc)
? (doc as Record<string, unknown>).用户角色
: undefined,
);
parseUserPersonaField(row?.用户角色) ||
parseUserPersonaField(meta?.用户角色);
if (fromJson) return fromJson;
} catch {
/* not json */
+63 -31
View File
@@ -45,7 +45,10 @@ export type PlayAuxOpening = {
/** 产物 tag:设计.开场白 或 设计.开场白#槽位;opening-setup 候选为假想 tag */
tag: string;
slot: string;
/** 侧栏预览用:present.v1 已压成可读正文 */
text: string;
/** 选用时落盘 / 灌呈现壳:保留完整 present.v1 或散文原文 */
document: string;
persona: OpeningUserPersona | null;
};
@@ -67,34 +70,15 @@ function selectedOpeningIndex(slots: Record<string, unknown> | undefined): numbe
return 0;
}
/** 黑板上全部可选用开场(工序候选优先;否则对话落盘 设计.开场白[#槽]) */
export function collectOpenings(board: Blackboard): PlayAuxOpening[] {
const payload = parseOpeningSealPayload(
board.getContentByTag(OPENING_SETUP_ARTIFACT_TAG),
0,
);
if (payload?.candidates.length) {
return payload.candidates.map((text, index) => {
const one = parseOpeningSealPayload(
board.getContentByTag(OPENING_SETUP_ARTIFACT_TAG),
index,
);
const tag = `${OPENING_SETUP_ARTIFACT_TAG}#${index + 1}`;
return {
index,
tag,
slot: String(index + 1),
text: presentReadablePlain(text) || text,
persona: one?.persona ?? payload.persona,
};
});
}
type OpeningProductRow = {
tag: string;
content: string;
order: number;
slot: string;
};
const rows: Array<{
tag: string;
content: string;
order: number;
}> = [];
function listOpeningProductRows(board: Blackboard): OpeningProductRow[] {
const rows: OpeningProductRow[] = [];
for (const e of board.listTagIndex()) {
const family = dictateProductFamily(e.tag);
if (
@@ -111,26 +95,74 @@ export function collectOpenings(board: Blackboard): PlayAuxOpening[] {
typeof rawOrder === "number" && Number.isFinite(rawOrder)
? rawOrder
: effectiveDictateOrder({ tag: e.tag });
rows.push({ tag: e.tag, content, order });
rows.push({
tag: e.tag,
content,
order,
slot: dictateProductSlot(e.tag),
});
}
rows.sort((a, b) => {
if (a.order !== b.order) return a.order - b.order;
return a.tag.localeCompare(b.tag, "zh");
});
return rows;
}
function openingsFromRows(
board: Blackboard,
rows: OpeningProductRow[],
): PlayAuxOpening[] {
return rows.map((row, index) => {
const split = splitOpeningDocument(row.content);
const text = split.text.trim() || row.content;
const document = split.text.trim() || row.content;
return {
index,
tag: row.tag,
slot: dictateProductSlot(row.tag),
text: presentReadablePlain(text) || text,
slot: row.slot,
document,
text: presentReadablePlain(document) || document,
persona: personaBoundToOpening(board, row.tag, row.content),
};
});
}
/**
* 可切换开场。已有 `设计.开场白#场景短码` 时只认这些带槽位的卡,
* 裸 `设计.开场白` 与整包 `设计.开场白与开场变量` 不进切换列表。
* 还没有槽位卡时,才回退旧整包候选或裸基名。
*/
export function collectOpenings(board: Blackboard): PlayAuxOpening[] {
const rows = listOpeningProductRows(board);
const slotted = rows.filter((row) => row.slot);
if (slotted.length) return openingsFromRows(board, slotted);
const payload = parseOpeningSealPayload(
board.getContentByTag(OPENING_SETUP_ARTIFACT_TAG),
0,
);
if (payload?.candidates.length) {
return payload.candidates.map((text, index) => {
const one = parseOpeningSealPayload(
board.getContentByTag(OPENING_SETUP_ARTIFACT_TAG),
index,
);
const tag = `${OPENING_SETUP_ARTIFACT_TAG}#${index + 1}`;
const document = text.trim();
return {
index,
tag,
slot: String(index + 1),
document,
text: presentReadablePlain(document) || document,
persona: one?.persona ?? payload.persona,
};
});
}
return openingsFromRows(board, rows);
}
export function buildPlayAuxView(
board: Blackboard,
slots?: Record<string, unknown>,
+22 -16
View File
@@ -40,9 +40,11 @@ export type ShellAdaptation = {
tweaks: PresentShellTweaks;
};
export type PresentBlocks = Partial<
Record<PresentRegionId, string | Record<string, unknown> | unknown[]>
>;
export type PresentBlockValue = string | Record<string, unknown> | unknown[];
/** 已知壳区域以外的键来自本局正文组成数据契约,必须原样保留。 */
export type PresentBlocks = Partial<Record<PresentRegionId, PresentBlockValue>> &
Record<string, PresentBlockValue | undefined>;
export type PresentPacket = {
schema: typeof PRESENT_SCHEMA;
@@ -112,25 +114,29 @@ export function normalizePresentShellId(
return fallback;
}
function asPresentBlockValue(value: unknown): PresentBlockValue | undefined {
if (typeof value === "string") return value;
if (Array.isArray(value)) return value;
if (value && typeof value === "object") return value as Record<string, unknown>;
return undefined;
}
function normalizeBlocks(raw: unknown): PresentBlocks {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
const row = raw as Record<string, unknown>;
const out: PresentBlocks = {};
const keys: PresentRegionId[] = [
"monitor",
"header",
"body",
"footer",
"aside",
"hidden",
];
for (const k of keys) {
if (row[k] !== undefined) out[k] = row[k] as PresentBlocks[typeof k];
for (const [key, value] of Object.entries(row)) {
if (key === "正文" || key === "监控") continue;
const block = asPresentBlockValue(value);
if (block !== undefined) out[key] = block;
}
if (out.body === undefined && row.正文 !== undefined) {
const body = asPresentBlockValue(row.正文);
if (body !== undefined) out.body = body;
}
// 兼容中文键
if (out.body === undefined && row.正文 !== undefined) out.body = row.正文 as string;
if (out.monitor === undefined && row.监控 !== undefined) {
out.monitor = row.监控 as PresentBlocks["monitor"];
const monitor = asPresentBlockValue(row.监控);
if (monitor !== undefined) out.monitor = monitor;
}
if (typeof out.body === "string") {
out.body = stripPresentSourceFences(out.body);
+351
View File
@@ -0,0 +1,351 @@
/**
* 程序投影:把 变量.当前 / 拓扑等权威数据灌进 present.v1 的 program_projection 区。
* 模型只填 model_generated;本模块在写盘与视图层补齐投影块。
*/
import { parseTableDoc } from "../blackboard/table-cells.js";
import { tableValueMap } from "../blackboard/table-side-effects.js";
import { extractJsonObjectText } from "./worker-set-parse.js";
import type { ReplyContractBlock, ReplyDataContract } from "./reply-contract.js";
import {
parsePresentPacket,
stringifyPresentPacket,
type PresentBlockValue,
type PresentShellId,
} from "./present-packet.js";
export type ProjectionBinding = {
id: string;
type: string;
ref?: string;
fields?: string[];
};
function asString(v: unknown): string | undefined {
if (typeof v !== "string") return undefined;
const t = v.trim();
return t || undefined;
}
function asObject(v: unknown): Record<string, unknown> | undefined {
if (!v || typeof v !== "object" || Array.isArray(v)) return undefined;
return v as Record<string, unknown>;
}
/** 从回复呈现(设计.监控栏)取出程序投影绑定 */
export function parseStatusBarProjectionBindings(
raw: string | null | undefined,
): ProjectionBinding[] {
if (!raw?.trim()) return [];
const text = extractJsonObjectText(raw) ?? raw.trim();
let doc: unknown;
try {
doc = JSON.parse(text);
} catch {
return [];
}
const root = asObject(doc);
if (!root) return [];
const body = asObject(root.正文) ?? root;
const regions = body.语义区域 ?? body.regions ?? body.areas;
if (!Array.isArray(regions)) return [];
const out: ProjectionBinding[] = [];
for (const item of regions) {
const row = asObject(item);
if (!row) continue;
const id = asString(row.id);
if (!id) continue;
const source = asObject(row.来源) ?? asObject(row.source);
const mode =
asString(source?.方式) ??
asString(source?.mode) ??
asString(source?.source) ??
asString(row.来源);
if (
mode &&
mode !== "program_projection" &&
mode !== "程序投影" &&
mode !== "projection"
) {
continue;
}
// 无方式时:若标了 ref 也当作可投影
const ref =
asString(source?.ref) ??
asString(source?.tag) ??
asString(row.ref) ??
asString(row.权威ref);
const fieldsRaw = source?.字段 ?? source?.fields ?? row.字段 ?? row.fields;
const fields = Array.isArray(fieldsRaw)
? fieldsRaw
.map((f) => asString(f))
.filter((f): f is string => Boolean(f))
: undefined;
out.push({
id,
type:
asString(row.内容类型) ??
asString(row.type) ??
asString(row.类型) ??
"kv",
...(ref ? { ref } : {}),
...(fields?.length ? { fields } : {}),
});
}
return out;
}
function pickValues(
all: Map<string, unknown>,
fields?: string[],
): Record<string, unknown> {
const out: Record<string, unknown> = {};
if (fields?.length) {
for (const key of fields) {
if (all.has(key)) out[key] = all.get(key);
}
return out;
}
for (const [key, value] of all) out[key] = value;
return out;
}
/** 表单元格常把对象/数组存成 JSON 字符串;投影前解开,避免壳只能看到一坨字。 */
export function coerceJsonValue(value: unknown): unknown {
if (typeof value === "string") {
const t = value.trim();
if (
(t.startsWith("{") && t.endsWith("}")) ||
(t.startsWith("[") && t.endsWith("]"))
) {
try {
return coerceJsonValue(JSON.parse(t));
} catch {
return value;
}
}
return value;
}
if (Array.isArray(value)) return value.map(coerceJsonValue);
if (value && typeof value === "object") {
const out: Record<string, unknown> = {};
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
out[key] = coerceJsonValue(child);
}
return out;
}
return value;
}
function coerceValueMap(
values: Record<string, unknown>,
): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const [key, value] of Object.entries(values)) {
out[key] = coerceJsonValue(value);
}
return out;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
/** 名字 → 属性对象 的矩阵,压成带 name 列的行数组,方便 table 渲染。 */
function matrixMapToRows(
map: Record<string, unknown>,
nameKey = "name",
): Record<string, unknown>[] {
return Object.entries(map).map(([name, stats]) => {
if (isPlainObject(stats)) return { [nameKey]: name, ...stats };
return { [nameKey]: name, value: stats };
});
}
function shapeTable(values: Record<string, unknown>): PresentBlockValue {
const coerced = coerceValueMap(values);
const keys = Object.keys(coerced);
if (keys.length === 1) {
const source = keys[0]!;
const only = coerced[source];
if (Array.isArray(only)) {
return { source, rows: only };
}
if (isPlainObject(only)) {
const entries = Object.entries(only);
if (
entries.length &&
entries.every(([, child]) => isPlainObject(child))
) {
return { source, rows: matrixMapToRows(only) };
}
}
}
return coerced;
}
function shapeBlock(
type: string,
values: Record<string, unknown>,
): PresentBlockValue | undefined {
const keys = Object.keys(values);
if (!keys.length) return undefined;
if (type === "list") {
const coerced = coerceValueMap(values);
return Object.keys(coerced).map(
(key) => `${key}:${typeof coerced[key] === "string" ? coerced[key] : JSON.stringify(coerced[key])}`,
);
}
if (type === "text" || type === "markdown" || type === "safe_markdown") {
const coerced = coerceValueMap(values);
return Object.keys(coerced)
.map((key) => {
const cell = coerced[key];
const text =
typeof cell === "string" ? cell : JSON.stringify(cell, null, 2);
return `【${key}】${text}`;
})
.join("\n");
}
if (type === "table") {
return shapeTable(values);
}
// kv / topology / 默认:解开 JSON 字符串后的对象
return coerceValueMap(values);
}
function readTagContent(
readTag: ((tag: string) => string | null | undefined) | undefined,
tag: string,
fallbacks: Record<string, string | null | undefined>,
): string | null | undefined {
if (Object.prototype.hasOwnProperty.call(fallbacks, tag)) {
return fallbacks[tag];
}
return readTag?.(tag);
}
function valuesFromRef(params: {
ref: string;
fields?: string[];
readTag?: (tag: string) => string | null | undefined;
currentVarsRaw?: string | null;
topologyRaw?: string | null;
}): Record<string, unknown> {
const ref = params.ref.trim();
const fallbacks: Record<string, string | null | undefined> = {
"变量.当前": params.currentVarsRaw,
"运行.初始变量": params.currentVarsRaw,
"运行.拓扑": params.topologyRaw,
};
const raw = readTagContent(params.readTag, ref, fallbacks);
if (ref === "变量.当前" || ref === "运行.初始变量") {
const map = tableValueMap(parseTableDoc(raw));
return pickValues(map, params.fields);
}
if (!raw?.trim()) return {};
// 表文档
const table = parseTableDoc(raw);
if (table?.rows.length) {
return pickValues(tableValueMap(table), params.fields);
}
// JSON 对象(拓扑等)
try {
const text = extractJsonObjectText(raw) ?? raw.trim();
const parsed = JSON.parse(text) as unknown;
const obj = asObject(parsed);
if (obj) {
if (params.fields?.length) {
const out: Record<string, unknown> = {};
for (const key of params.fields) {
if (key in obj) out[key] = obj[key];
}
return out;
}
return obj;
}
} catch {
/* plain */
}
if (params.fields?.length === 1) {
return { [params.fields[0]!]: raw.trim() };
}
return { value: raw.trim() };
}
/**
* 按正文组成契约 + 回复呈现绑定,从黑板投影 program_projection 块。
*/
export function projectPresentBlocks(params: {
contract: ReplyDataContract;
statusBarRaw?: string | null;
currentVarsRaw?: string | null;
topologyRaw?: string | null;
readTag?: (tag: string) => string | null | undefined;
}): Record<string, PresentBlockValue> {
const bindings = parseStatusBarProjectionBindings(params.statusBarRaw);
const byId = new Map(bindings.map((b) => [b.id, b]));
const currentMap = tableValueMap(parseTableDoc(params.currentVarsRaw));
const out: Record<string, PresentBlockValue> = {};
for (const block of params.contract.programBlocks) {
const bound = byId.get(block.id);
const type = bound?.type || block.type || "kv";
let values: Record<string, unknown>;
if (bound?.ref) {
values = valuesFromRef({
ref: bound.ref,
fields: bound.fields,
readTag: params.readTag,
currentVarsRaw: params.currentVarsRaw,
topologyRaw: params.topologyRaw,
});
} else if (type === "topology") {
values = valuesFromRef({
ref: "运行.拓扑",
fields: bound?.fields,
readTag: params.readTag,
currentVarsRaw: params.currentVarsRaw,
topologyRaw: params.topologyRaw,
});
} else {
values = pickValues(currentMap, bound?.fields);
}
const shaped = shapeBlock(type, values);
if (shaped !== undefined) out[block.id] = shaped;
}
return out;
}
/** 仅用契约(无监控栏)时的便捷入口 */
export function projectPresentBlocksFromContract(
contract: ReplyDataContract,
currentVarsRaw: string | null | undefined,
): Record<string, PresentBlockValue> {
return projectPresentBlocks({ contract, currentVarsRaw });
}
export function mergePresentWithProjections(
content: string,
projections: Record<string, PresentBlockValue>,
fallbackShell: PresentShellId = "prose",
): string {
if (!Object.keys(projections).length) return content;
const view = parsePresentPacket(content, fallbackShell);
const blocks = { ...view.packet.blocks, ...projections };
return stringifyPresentPacket({
...view.packet,
blocks,
});
}
export function projectionForBlocks(
blocks: ReplyContractBlock[],
currentVarsRaw: string | null | undefined,
): Record<string, PresentBlockValue> {
if (!blocks.length) return {};
const contract: ReplyDataContract = {
modelBlocks: [],
programBlocks: blocks,
};
return projectPresentBlocks({ contract, currentVarsRaw });
}
+249
View File
@@ -0,0 +1,249 @@
/**
* 从「设计.正文组成」取出数据契约。
* 开场与之后每一楼只填模型生成区;程序把这份 present.v1 灌进已固定的 frontend。
*/
import { extractJsonObjectText } from "./worker-set-parse.js";
export type ReplyContractBlock = {
id: string;
type: string;
source: "model_generated" | "program_projection";
role?: string;
};
export type ReplyFrontend = {
html: string;
css: string;
js: string;
};
export type ReplyDataContract = {
modelBlocks: ReplyContractBlock[];
programBlocks: ReplyContractBlock[];
frontend?: ReplyFrontend;
};
export function parseReplyDataContract(
raw: string | undefined | null,
): ReplyDataContract | undefined {
const doc = parseObject(raw);
if (!doc) return undefined;
const body = asObject(doc.正文) ?? doc;
const contract = asObject(body.数据契约) ?? asObject(body.data_contract);
const blocksRaw = asObject(contract?.blocks);
if (!blocksRaw) return undefined;
const modelBlocks: ReplyContractBlock[] = [];
const programBlocks: ReplyContractBlock[] = [];
for (const [id, value] of Object.entries(blocksRaw)) {
const row = asObject(value);
if (!id.trim() || !row) continue;
const block: ReplyContractBlock = {
id: id.trim(),
type: asString(row.类型 ?? row.type) || "text",
source:
asString(row.来源 ?? row.source) === "program_projection"
? "program_projection"
: "model_generated",
role: asString(row.职责 ?? row.role),
};
if (block.source === "program_projection") programBlocks.push(block);
else modelBlocks.push(block);
}
if (!modelBlocks.length && !programBlocks.length) return undefined;
const frontend = parseFrontend(asObject(body.frontend));
return {
modelBlocks,
programBlocks,
...(frontend ? { frontend } : {}),
};
}
export function replyContractSummary(contract: ReplyDataContract): {
schema: "present.v1";
model_blocks: Array<{ id: string; type: string; role?: string }>;
program_blocks: string[];
rule: string;
} {
return {
schema: "present.v1",
model_blocks: contract.modelBlocks.map((block) => ({
id: block.id,
type: block.type,
...(block.role ? { role: block.role } : {}),
})),
program_blocks: contract.programBlocks.map((block) => block.id),
rule: "开场和之后每一轮只填写 model_blocks。程序把这份 present.v1 灌进已固定的 frontend,替换示例内容。不要重写 frontend,不要填写 program_blocks。",
};
}
/** 开场 JSON 里所有 present.v1 的 blocks。缺模型区时返回错误,供 insert 拒绝。 */
export function openingContractError(
content: string,
contract: ReplyDataContract,
): string | undefined {
if (!contract.modelBlocks.length) return undefined;
const maps = collectPresentBlockMaps(content);
const need = formatModelBlocks(contract);
if (!maps.length) {
return `有正文组成时,开场必须给出 present.v1,并填写模型生成区:${need}。程序投影区不要填写。`;
}
const missing = new Set<string>();
for (const blocks of maps) {
for (const block of contract.modelBlocks) {
if (!Object.prototype.hasOwnProperty.call(blocks, block.id)) {
missing.add(block.id);
}
}
}
if (!missing.size) return undefined;
return `开场的 present.v1 未按正文组成填写模型生成区,缺少:${[...missing].join("、")}。需要:${need}。程序投影区不要填写。`;
}
export function presentSchemaFromContract(contract: ReplyDataContract): {
schema: Record<string, unknown>;
loose: boolean;
} {
const properties: Record<string, unknown> = {};
const required: string[] = [];
let loose = false;
for (const block of contract.modelBlocks) {
required.push(block.id);
const built = schemaForBlock(block);
properties[block.id] = built.schema;
if (built.loose) loose = true;
}
return {
loose,
schema: {
type: "object",
additionalProperties: false,
required: ["schema", "blocks"],
properties: {
schema: { type: "string", enum: ["present.v1"] },
blocks: {
type: "object",
additionalProperties: false,
required,
properties,
},
},
},
};
}
export function contractOutputInstruction(contract: ReplyDataContract): string {
const lines = contract.modelBlocks.map((block) => {
const role = block.role ? `:${block.role}` : "";
return `- ${block.id}(${typeHint(block.type)})${role}`;
});
const projected = contract.programBlocks.map((block) => block.id);
return `
---
## 运行时输出协议(按正文组成灌数)
本局前端已经固定。只输出一个 present.v1 对象,程序把它灌进正文组成,替换示例内容。
不要输出 HTML、CSS、JS,不要重写模板,不要再套 shell。
只填模型生成区:
${lines.join("\n")}
${projected.length ? `不要填写程序投影区:${projected.join("、")}。\n` : ""}list 用字符串数组,没有内容时给 []。markdown 与 text 用字符串。
不要写追问、askUser、自评或「可验收产物」。`;
}
function formatModelBlocks(contract: ReplyDataContract): string {
return contract.modelBlocks
.map((block) => `${block.id}(${typeHint(block.type)})`)
.join("、");
}
function typeHint(type: string): string {
if (type === "list") return "字符串数组";
if (type === "markdown" || type === "text") return "字符串";
if (type === "kv" || type === "table" || type === "topology") return "对象";
return type || "字符串";
}
function schemaForBlock(block: ReplyContractBlock): {
schema: Record<string, unknown>;
loose: boolean;
} {
if (block.type === "list") {
return {
loose: false,
schema: { type: "array", items: { type: "string" } },
};
}
if (block.type === "markdown" || block.type === "text") {
return { loose: false, schema: { type: "string" } };
}
return {
loose: true,
schema: { type: "object", additionalProperties: true },
};
}
function collectPresentBlockMaps(content: string): Array<Record<string, unknown>> {
const doc = parseObject(content);
if (!doc) return [];
const maps: Array<Record<string, unknown>> = [];
walk(doc, maps, 0);
return maps;
}
function walk(node: unknown, maps: Array<Record<string, unknown>>, depth: number): void {
if (depth > 8 || node == null) return;
if (typeof node === "string") {
if (!node.includes("present.v1")) return;
const parsed = parseObject(node);
if (parsed) walk(parsed, maps, depth + 1);
return;
}
if (Array.isArray(node)) {
for (const item of node) walk(item, maps, depth + 1);
return;
}
if (typeof node !== "object") return;
const row = node as Record<string, unknown>;
const blocks = asObject(row.blocks);
if ((row.schema === "present.v1" || row.shell != null) && blocks) {
maps.push(blocks);
}
for (const value of Object.values(row)) walk(value, maps, depth + 1);
}
function parseFrontend(raw: Record<string, unknown> | undefined): ReplyFrontend | undefined {
if (!raw) return undefined;
const html = asString(raw.html);
if (!html || !/<\w+/.test(html)) return undefined;
return {
html,
css: asString(raw.css) ?? "",
js: asString(raw.js) ?? "",
};
}
function parseObject(raw: string | undefined | null): Record<string, unknown> | undefined {
const text = raw?.trim() ?? "";
if (!text) return undefined;
const jsonText = extractJsonObjectText(text) ?? (text.startsWith("{") ? text : null);
if (!jsonText) return undefined;
try {
const doc = JSON.parse(jsonText) as unknown;
return asObject(doc);
} catch {
return undefined;
}
}
function asObject(value: unknown): Record<string, unknown> | undefined {
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
return value as Record<string, unknown>;
}
function asString(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
const text = value.trim();
return text || undefined;
}
+216
View File
@@ -0,0 +1,216 @@
/**
* 从「设计.变量设计与更新规则」编译运行目录,并种进 变量.当前。
* 创作产物是合同;设计.变量目录 / 变量.当前 是运行表。
*/
import { extractJsonObjectText } from "./worker-set-parse.js";
import {
parseVariableCatalog,
seedVariablesFromCatalog,
serializeVariableCatalog,
upsertVariableField,
VARIABLE_CATALOG_TAG,
type VariableCatalogDoc,
type VariableCatalogField,
type VariableFieldType,
} from "./variable-catalog.js";
export const VARIABLE_DESIGN_TAG = "设计.变量设计与更新规则";
function asString(v: unknown): string | undefined {
if (typeof v !== "string") return undefined;
const t = v.trim();
return t || undefined;
}
function asObject(v: unknown): Record<string, unknown> | undefined {
if (!v || typeof v !== "object" || Array.isArray(v)) return undefined;
return v as Record<string, unknown>;
}
function parseType(raw: unknown): VariableFieldType {
const s = asString(raw)?.toLowerCase();
if (s === "number" || s === "数字" || s === "integer" || s === "int") {
return "number";
}
if (s === "boolean" || s === "布尔") return "boolean";
if (s === "enum" || s === "枚举") return "enum";
return "string";
}
/** 解析变量设计产物里的真值列表 → 目录字段(未写入) */
export function catalogFieldsFromVariableDesign(
raw: string | null | undefined,
): VariableCatalogField[] {
if (!raw?.trim()) return [];
const text = extractJsonObjectText(raw) ?? raw.trim();
let doc: unknown;
try {
doc = JSON.parse(text);
} catch {
return [];
}
const root = asObject(doc);
if (!root) return [];
const body = asObject(root.正文) ?? root;
const truths = body.真值 ?? body.fields ?? body.变量;
if (!Array.isArray(truths)) return [];
const fields: VariableCatalogField[] = [];
const seen = new Set<string>();
for (const item of truths) {
const row = asObject(item);
if (!row) continue;
const key =
asString(row.id) ??
asString(row.key) ??
asString(row.名) ??
asString(row.name) ??
asString(row.名称);
if (!key || seen.has(key)) continue;
seen.add(key);
const type = parseType(row.类型 ?? row.type);
const initBox = asObject(row.初始化) ?? asObject(row.initial);
const initial =
initBox?.默认值 ??
initBox?.default ??
initBox?.value ??
row.initial ??
row.初值 ??
row.默认值;
const publicity = asString(row.公开性) ?? asString(row.visibility);
const userVisible =
publicity === "hidden" ||
publicity === "隐藏" ||
row.user_visible === false ||
row.对用户可见 === false
? false
: true;
const note =
asString(row.名称) ??
asString(row.用途) ??
asString(row.note) ??
asString(row.备注);
const { doc: one } = upsertVariableField(null, {
key,
type,
initial,
user_visible: userVisible,
note,
});
const field = one.fields[0];
if (field) fields.push(field);
}
return fields;
}
/** 把设计稿真值并进已有目录(不删目录里多出来的 declare 字段) */
export function mergeCatalogFromVariableDesign(
existing: VariableCatalogDoc | null,
designRaw: string | null | undefined,
): {
doc: VariableCatalogDoc;
added: string[];
updated: string[];
} | null {
const fromDesign = catalogFieldsFromVariableDesign(designRaw);
if (!fromDesign.length) return null;
let doc: VariableCatalogDoc = existing ?? {
schema: "variable-catalog.v1",
fields: [],
};
const added: string[] = [];
const updated: string[] = [];
for (const field of fromDesign) {
const before = doc.fields.find((f) => f.key === field.key);
const { doc: next } = upsertVariableField(doc, {
key: field.key,
type: field.type,
initial: field.initial,
user_visible: field.user_visible,
user_editable: field.user_editable,
note: field.note,
});
doc = next;
if (!before) added.push(field.key);
else updated.push(field.key);
}
return { doc, added, updated };
}
export type SyncVariableDesignResult = {
/** 有变更时返回新目录 JSON */
catalog: string | null;
current: string | null;
initial: string | null;
seededKeys: string[];
catalogKeys: string[];
added: string[];
};
/**
* 设计稿 → 目录 → 种/补 变量.当前 与 运行.初始变量。
* 已有格不覆盖;目录缺键才追加。
*/
export function syncVariableDesignToRuntime(params: {
designRaw: string | null | undefined;
catalogRaw: string | null | undefined;
currentRaw: string | null | undefined;
initialRaw: string | null | undefined;
}): SyncVariableDesignResult {
const empty: SyncVariableDesignResult = {
catalog: null,
current: null,
initial: null,
seededKeys: [],
catalogKeys: [],
added: [],
};
const existing = parseVariableCatalog(params.catalogRaw);
const merged = mergeCatalogFromVariableDesign(existing, params.designRaw);
if (!merged) return empty;
const catalogChanged =
!existing ||
serializeVariableCatalog(existing) !== serializeVariableCatalog(merged.doc);
const catalogRaw = catalogChanged
? serializeVariableCatalog(merged.doc)
: (params.catalogRaw?.trim() ?? serializeVariableCatalog(merged.doc));
const seeded = seedVariablesFromCatalog({
catalogRaw,
currentRaw: params.currentRaw,
initialRaw: params.initialRaw,
actor: "system:variable-design",
});
return {
catalog: catalogChanged ? catalogRaw : null,
current: seeded.current,
initial: seeded.initial,
seededKeys: seeded.seededKeys,
catalogKeys: merged.doc.fields.map((f) => f.key),
added: merged.added,
};
}
/** 把 sync 结果写回黑板;返回实际写入的 tag */
export function writeVariableDesignSync(
sync: SyncVariableDesignResult,
writeTag: (tag: string, content: string) => void,
): string[] {
const wrote: string[] = [];
if (sync.catalog) {
writeTag(VARIABLE_CATALOG_TAG, sync.catalog);
wrote.push(VARIABLE_CATALOG_TAG);
}
if (sync.initial) {
writeTag("运行.初始变量", sync.initial);
wrote.push("运行.初始变量");
}
if (sync.current) {
writeTag("变量.当前", sync.current);
wrote.push("变量.当前");
}
return wrote;
}
+1 -1
View File
@@ -107,7 +107,7 @@ export const DESIGN_STAGE_SKILL_META: Record<
"opening-generator": {
label: "开局 · 开场白",
purpose:
"创作末尾:优先落库设计.开场白与开场变量;否则现写开场;初值与开场同真相。",
"创作末尾:每条开场落库设计.开场白#场景短码;没有变量的卡不写初值。",
},
};
+41 -16
View File
@@ -21,6 +21,12 @@ import {
} from "../parse/json-doc.js";
import { assembleWorkerContext } from "../skills/context-segments.js";
import { isPlayLayerActive } from "../skills/play-turn.js";
import { isNarratorEnabled } from "../skills/play-slots.js";
import {
contractOutputInstruction,
parseReplyDataContract,
presentSchemaFromContract,
} from "../skills/reply-contract.js";
import {
PRESENT_TAG,
PRESENT_JSON_SCHEMA,
@@ -829,21 +835,33 @@ export async function runWorkerSkill(params: WorkerRunParams): Promise<WorkerRun
isPlayLayerActive(params.slots) && isPlayGmBodyWorker(worker.id);
const playPresent =
isPlayLayerActive(params.slots) && isPlayPresentWorker(worker.id);
const replyFormatRaw =
params.blackboard.getContentByTag("设计.正文组成") ??
params.blackboard.getContentByTag("设计.0层正文组成") ??
params.blackboard.getContentByTag("设计.回复格式");
const replyContract = parseReplyDataContract(replyFormatRaw);
const narratorOn = isNarratorEnabled(playSlots);
const fillByContract =
Boolean(replyContract?.frontend && replyContract.modelBlocks.length) &&
(playPresent || (playGmBody && !narratorOn));
const contractSchema =
fillByContract && replyContract
? presentSchemaFromContract(replyContract)
: undefined;
const playShell: PresentShellId | undefined =
playPresent || playGmBody
? parseShellAdaptationFromReplyFormat(
params.blackboard.getContentByTag("设计.正文组成") ??
params.blackboard.getContentByTag("设计.回复格式"),
)?.shell_id ?? "prose"
? parseShellAdaptationFromReplyFormat(replyFormatRaw)?.shell_id ?? "prose"
: undefined;
const systemContent =
promptBody +
(playGmBody
? playGmBodyOutputInstruction()
: playPresent && playShell
? playPresentOutputInstruction(playShell)
: WORKER_OUTPUT_INSTRUCTION) +
(contractSchema && replyContract && !useGmHarness
? contractOutputInstruction(replyContract)
: playGmBody
? playGmBodyOutputInstruction()
: playPresent && playShell
? playPresentOutputInstruction(playShell)
: WORKER_OUTPUT_INSTRUCTION) +
(useGmHarness ? GM_CHANCE_HARNESS_INSTRUCTION : "");
// 任务 messages 即可;preset 夹心由 wrapLlmForSession / PresetLlmProvider 统一装配
@@ -888,7 +906,7 @@ export async function runWorkerSkill(params: WorkerRunParams): Promise<WorkerRun
}
: undefined;
if (playGmBody) {
if (playGmBody && !contractSchema) {
const completeOpts = {
caller: `worker:${worker.id}`,
label: worker.name?.trim() || worker.id,
@@ -905,12 +923,19 @@ export async function runWorkerSkill(params: WorkerRunParams): Promise<WorkerRun
}
const result = await completeStructured(params.llm, messages, {
schema: playPresent ? PRESENT_JSON_SCHEMA : {
type: "object",
additionalProperties: true,
},
name: playPresent ? "submit_present_packet" : "submit_worker_result",
schemaIsLoose: !playPresent,
schema: contractSchema
? contractSchema.schema
: playPresent
? PRESENT_JSON_SCHEMA
: {
type: "object",
additionalProperties: true,
},
name:
contractSchema || playPresent
? "submit_present_packet"
: "submit_worker_result",
schemaIsLoose: contractSchema ? contractSchema.loose : !playPresent,
capabilities: resolveActiveProfile()?.capabilities,
caller: `worker:${worker.id}`,
label: worker.name?.trim() || worker.id,