打通 Boss 直聘落档挂载与真值→投影映射,并修正带 tools 时的推理强度。
落档自动把产物挂进 context_order;创作期用 declare_variable/declare_map 钉目录与分档;真值变更持续重投影。顺带强制 tools 请求 reasoning_effort=none,并支持消息 restart。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+91
-14
@@ -12,11 +12,28 @@ import {
|
||||
type DictateChatTurn,
|
||||
type DictateProduct,
|
||||
} from "./types.js";
|
||||
import {
|
||||
VARIABLE_CATALOG_TAG,
|
||||
parseVariableCatalog,
|
||||
serializeVariableCatalog,
|
||||
upsertVariableField,
|
||||
type UpsertVariableInput,
|
||||
} from "../skills/variable-catalog.js";
|
||||
import {
|
||||
VALUE_MAP_TAG,
|
||||
parseValueMapDoc,
|
||||
serializeValueMapDoc,
|
||||
upsertValueMapEntry,
|
||||
type UpsertMapInput,
|
||||
} from "../skills/value-map.js";
|
||||
|
||||
export type DictateTurnHandlers = {
|
||||
listProducts: () => DictateProduct[];
|
||||
/** order 省略时由实现沿用原序或默认 */
|
||||
writeProduct: (tag: string, content: string, order?: number) => void;
|
||||
/** 读/写变量目录与映射(程序校验后的 JSON) */
|
||||
readTag: (tag: string) => string | undefined;
|
||||
writeTag: (tag: string, content: string) => void;
|
||||
clearDialogue: () => void;
|
||||
onToolCall?: (name: string, detail: string) => void;
|
||||
onThinkingDelta?: (delta: string) => void;
|
||||
@@ -31,8 +48,7 @@ export type DictateTurnResult = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 跑一轮转述整理:上下文 = 预设提示 + 按相对序的产物 + 全量对话。
|
||||
* 产物只许 insert(position, content, order?);最终文本即对用户回复。
|
||||
* 跑一轮 Boss 直聘整理:上下文 = 预设提示 + 按相对序的产物 + 全量对话。
|
||||
*/
|
||||
export async function runDictateTurn(params: {
|
||||
llm: LlmProvider;
|
||||
@@ -59,7 +75,6 @@ export async function runDictateTurn(params: {
|
||||
products: params.handlers.listProducts(),
|
||||
dialogue,
|
||||
});
|
||||
// driver 会再前置 system;此处只传 user 拼装块,避免双重 system
|
||||
const userBlock = assembled.filter((m) => m.role !== "system");
|
||||
|
||||
const run = await driver.run({
|
||||
@@ -67,17 +82,20 @@ export async function runDictateTurn(params: {
|
||||
messages: userBlock,
|
||||
tools: DICTATE_TOOL_DEFINITIONS,
|
||||
caller: "dictate_agent",
|
||||
label: "转述整理",
|
||||
label: "Boss直聘",
|
||||
onThinkingDelta: params.handlers.onThinkingDelta,
|
||||
onThinkingDone: params.handlers.onThinkingDone,
|
||||
handleStep: (calls) => {
|
||||
const results = calls.map((call) => {
|
||||
const args = safeParseArgs(call.arguments);
|
||||
if (call.name === "insert" || call.name === "write_product") {
|
||||
const tag = String(
|
||||
args.position ?? args.tag ?? "",
|
||||
).trim();
|
||||
const tag = String(args.position ?? args.tag ?? "").trim();
|
||||
const content = String(args.content ?? "");
|
||||
if (tag === VARIABLE_CATALOG_TAG || tag === VALUE_MAP_TAG) {
|
||||
const err = `请用 declare_variable / declare_map,不要 insert ${tag}`;
|
||||
params.handlers.onToolCall?.(call.name, err);
|
||||
return { callId: call.id, content: JSON.stringify({ error: err }) };
|
||||
}
|
||||
if (!isAllowedProductTag(tag)) {
|
||||
const err = `拒绝写入:position 必须以「用户.」或「设计.」开头(收到:${tag || "空"})`;
|
||||
params.handlers.onToolCall?.(call.name, err);
|
||||
@@ -103,6 +121,67 @@ export async function runDictateTurn(params: {
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (call.name === "declare_variable") {
|
||||
const input: UpsertVariableInput = {
|
||||
key: String(args.key ?? "").trim(),
|
||||
type: args.type != null ? String(args.type) : undefined,
|
||||
initial: args.initial,
|
||||
user_visible:
|
||||
args.user_visible === undefined
|
||||
? undefined
|
||||
: Boolean(args.user_visible),
|
||||
note: args.note != null ? String(args.note) : undefined,
|
||||
};
|
||||
const current = parseVariableCatalog(
|
||||
params.handlers.readTag(VARIABLE_CATALOG_TAG),
|
||||
);
|
||||
const { doc, error } = upsertVariableField(current, input);
|
||||
if (error) {
|
||||
params.handlers.onToolCall?.(call.name, error);
|
||||
return { callId: call.id, content: JSON.stringify({ error }) };
|
||||
}
|
||||
params.handlers.writeTag(
|
||||
VARIABLE_CATALOG_TAG,
|
||||
serializeVariableCatalog(doc),
|
||||
);
|
||||
wroteTags.push(VARIABLE_CATALOG_TAG);
|
||||
const detail = `${input.key} visible=${doc.fields.find((f) => f.key === input.key)?.user_visible !== false}`;
|
||||
params.handlers.onToolCall?.(call.name, detail);
|
||||
return {
|
||||
callId: call.id,
|
||||
content: JSON.stringify({ ok: true, key: input.key, fields: doc.fields.length }),
|
||||
};
|
||||
}
|
||||
if (call.name === "declare_map") {
|
||||
const input: UpsertMapInput = {
|
||||
id: String(args.id ?? "").trim(),
|
||||
field: String(args.field ?? "").trim(),
|
||||
target_tag: String(args.target_tag ?? "").trim(),
|
||||
bands: args.bands,
|
||||
note: args.note != null ? String(args.note) : undefined,
|
||||
};
|
||||
const current = parseValueMapDoc(
|
||||
params.handlers.readTag(VALUE_MAP_TAG),
|
||||
);
|
||||
const { doc, error } = upsertValueMapEntry(current, input);
|
||||
if (error) {
|
||||
params.handlers.onToolCall?.(call.name, error);
|
||||
return { callId: call.id, content: JSON.stringify({ error }) };
|
||||
}
|
||||
params.handlers.writeTag(VALUE_MAP_TAG, serializeValueMapDoc(doc));
|
||||
wroteTags.push(VALUE_MAP_TAG);
|
||||
const detail = `${input.id} ${input.field}→${input.target_tag}`;
|
||||
params.handlers.onToolCall?.(call.name, detail);
|
||||
return {
|
||||
callId: call.id,
|
||||
content: JSON.stringify({
|
||||
ok: true,
|
||||
id: input.id,
|
||||
target_tag: input.target_tag,
|
||||
maps: doc.maps.length,
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (call.name === "clear_dialogue") {
|
||||
const reason = String(args.reason ?? "").trim() || "上下文过长";
|
||||
params.handlers.clearDialogue();
|
||||
@@ -114,7 +193,7 @@ export async function runDictateTurn(params: {
|
||||
content: JSON.stringify({
|
||||
ok: true,
|
||||
cleared: true,
|
||||
note: "对话已清空;产物仍在。后续上下文以产物为准。",
|
||||
note: "对话已清空;产物与变量/映射仍在。",
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -126,8 +205,6 @@ export async function runDictateTurn(params: {
|
||||
},
|
||||
});
|
||||
|
||||
// driver 的 messages 在首轮已固定;clear 后若还要续写依赖模型已知工具结果。
|
||||
// 若模型只调工具不说话,给一个兜底回复。
|
||||
let reply = "";
|
||||
if (run.stop.kind === "text") {
|
||||
reply = run.stop.content.trim();
|
||||
@@ -153,11 +230,11 @@ export async function runDictateTurn(params: {
|
||||
function safeParseArgs(raw: string): Record<string, unknown> {
|
||||
try {
|
||||
const parsed = JSON.parse(raw || "{}");
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, unknown>;
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
return {};
|
||||
}
|
||||
return parsed as Record<string, unknown>;
|
||||
} catch {
|
||||
/* ignore */
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user