接通主角绑定、开场封印与游玩辅助,让世界模拟器从创作连续落到本局。

直聘插入按模块回馈,并记录请求骨架便于排查。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 07:40:08 +08:00
co-authored by Cursor
parent 9306c8e659
commit dd0a9e87d8
88 changed files with 6869 additions and 786 deletions
+3 -3
View File
@@ -14,7 +14,6 @@ import type {
ToolDefinition,
} from "../llm/client.js";
import { supportsToolStream } from "../llm/stream-complete.js";
import { debugLog } from "../log.js";
export type DriverToolCall = {
id: string;
@@ -87,9 +86,10 @@ async function completeToolsPreferStream(
messages: ChatMessage[],
tools: ToolDefinition[],
caller: string | undefined,
label: string | undefined,
callbacks: StreamCallbacks,
): Promise<CompleteWithToolsResult> {
const options = { tools, caller };
const options = { tools, caller, label };
if (supportsToolStream(llm)) {
return llm.completeWithToolsStream!(messages, options, callbacks);
}
@@ -115,12 +115,12 @@ export function createLocalLlmDriver(llm: LlmProvider): AgentDriver {
const label = input.label ?? "driver";
for (let iteration = 1; iteration <= maxIterations; iteration++) {
debugLog("llm", `${label} 第${iteration}轮`);
const result = await completeToolsPreferStream(
llm,
messages,
input.tools,
input.caller,
`${label} 第${iteration}轮`,
streamCallbacks,
);
emitThinkingDone(input, result.reasoning);
+67 -6
View File
@@ -36,7 +36,7 @@ import { toActiveSkillSnapshot } from "../skills/snapshot.js";
import { runWorkerSkill } from "../worker/executor.js";
import { resolveWorkerId } from "../worker/resolve-id.js";
import { resolveWorkerLlmProvider } from "../skills/worker-llm.js";
import { personaForLifecycle } from "../persona/store.js";
import { overlayPlayPersona } from "../persona/protagonist-bind.js";
import {
DIALOGUE_HISTORY_TAG,
appendDialogueHistoryTurn,
@@ -127,9 +127,11 @@ import {
OPENING_CURRENT_VARS_TAG,
OPENING_INITIAL_VARS_TAG,
OPENING_OUTPUT_TAG,
OPENING_PERSONA_TAG,
OPENING_SETUP_ARTIFACT_TAG,
parseOpeningSealPayload,
reopenCreationFlowRaw,
serializeOpeningPersona,
SLOT_CREATION_SEALED_BY_OPENING,
SLOT_OPENING_SELECTED_INDEX,
} from "../skills/opening-seal.js";
@@ -138,6 +140,10 @@ import {
isDictateModeValue,
} from "../dictate/types.js";
import { applyDictatePlayBind } from "../dictate/play-bind.js";
import {
dictateProductFamily,
mergeGenerationRulesArtifacts,
} from "../dictate/repeatable-tags.js";
import {
VARIABLE_CATALOG_TAG,
seedVariablesFromCatalog,
@@ -172,7 +178,9 @@ import {
import {
mergeTableCells,
parseTableDoc,
patchUserTableCells,
stringifyTableDoc,
type TableCellPatch,
type TableDoc,
} from "../blackboard/table-cells.js";
import {
@@ -332,6 +340,16 @@ export class PhaseRuntime {
return this.session;
}
setOpeningSelectedIndex(index: number): void {
this.session = {
...this.session,
slots: {
...this.session.slots,
[SLOT_OPENING_SELECTED_INDEX]: Math.trunc(index),
},
};
}
getLastWorkerRunSnapshot(): {
workerId: string;
runtimeSession: RuntimeSession;
@@ -741,6 +759,29 @@ export class PhaseRuntime {
}
}
/**
* 用户按格手改表:须带读到的 rev。版本冲突或只读格跳过,不整表覆盖。
*/
patchUserTable(
tag: string,
patches: TableCellPatch[],
): ReturnType<typeof patchUserTableCells> {
const allowed = tag === "变量.当前" || tag === "运行.初始变量";
if (!allowed) throw new Error("该表不可手改");
const prev = parseTableDoc(this.blackboard.getContentByTag(tag));
const result = patchUserTableCells(prev, patches);
if (result.applied.length) {
this.blackboard.write({
tag,
content: stringifyTableDoc(result.doc),
source: "user",
});
this.applyTableSideEffects(prev, result.doc, "user");
if (tag === "变量.当前") this.applyValueMapProjections("user");
}
return result;
}
/**
* 用户文本输入的统一入口。
* legacy skill_selection 阶段会先解析为 selectSkill,否则走 user_submitted_input。
@@ -1944,6 +1985,14 @@ export class PhaseRuntime {
this.writeWorkerTagContent(OPENING_OUTPUT_TAG, openingText, "opening-setup");
}
if (payload?.persona) {
this.writeWorkerTagContent(
OPENING_PERSONA_TAG,
serializeOpeningPersona(payload.persona),
"opening-setup",
);
}
if (payload?.variables.length) {
const source = "worker:opening-setup";
const initialPatch = mergeOpeningTablePatch(
@@ -1995,15 +2044,15 @@ export class PhaseRuntime {
/** 收口后若还没有运行规格,用草稿或默认槽位补一份,才能进游玩 */
private ensurePlaySpecReady(): void {
// Boss 直聘:落档/开玩时自动挂载产物;种变量初值;按映射重投影
// 对话落盘:落档/开玩时自动挂载产物;种变量初值;按映射重投影
if (this.isDictateIntakeMode()) {
const bound = applyDictatePlayBind(this.blackboard);
if (bound.boundTags.length) {
this.onMessage(
`[Boss直聘] 已自动挂载 ${bound.boundTags.length} 个产物到游玩上下文:${bound.boundTags.join("、")}`,
`[对话落盘] 已自动挂载 ${bound.boundTags.length} 个产物到游玩上下文:${bound.boundTags.join("、")}`,
);
} else {
this.onMessage("[Boss直聘] 已生成默认游玩规格(尚无产物可挂载)");
this.onMessage("[对话落盘] 已生成默认游玩规格(尚无产物可挂载)");
}
this.seedVariablesAndReproject("opening-setup");
} else {
@@ -2274,7 +2323,7 @@ export class PhaseRuntime {
slots,
fallbackLlm: this.llm,
getPersona: () =>
personaForLifecycle(inferLifecycleStage(this.session)),
overlayPlayPersona(inferLifecycleStage(this.session), this.blackboard),
});
this.onMessage(
@@ -2670,7 +2719,19 @@ export class PhaseRuntime {
need: { rule_id: string; reason: string },
workerId: string,
): void {
const rulesRaw = this.blackboard.getContentByTag("设计.生成规则");
const board = this.blackboard;
const rawPieces: string[] = [];
for (const e of board.listTagIndex()) {
if (dictateProductFamily(e.tag) !== "设计.生成规则") continue;
const c = board.getContentByTag(e.tag)?.trim();
if (c) rawPieces.push(c);
}
const rulesRaw =
rawPieces.length === 0
? board.getContentByTag("设计.生成规则")
: rawPieces.length === 1
? rawPieces[0]
: mergeGenerationRulesArtifacts(rawPieces);
const result = runMaintainNeedGenerateSampling({
need,
generationRulesRaw: rulesRaw,