Files
叫我小杨同学 e1d8e59087 fix: 0.2.0 system prompt修复 + patch89 + override状态机 + 提示词优化
- core.js: isKeepableUserOverride重排序, markOverrideCustomized不写hash(explicitSave), commitOverrideOnApply读盘, patch89 SYSTEM_FIELD_EXTRACT
- index.js: system_field_extracted状态字段(status/apply/override端点)
- default-prompt-inject.en.md: 优化英文版提示词
- default-prompt-inject.zh.md: 优化中文版提示词
- docs/修改文档.md: 完整修改文档+还原说明
- pi-ai anthropic-messages.js: system从顶层字段改为messages里role:system消息
2026-10-13 16:36:15 +08:00

645 lines
20 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const DRAFT_TTL_MS = 10 * 60 * 1000;
const pendingDrafts = new Map();
let lastPending = null;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function ignoreSettled(value) {
Promise.resolve(value).catch(() => {});
}
function errorText(err) {
if (!err) return "回退失败";
if (typeof err === "string") return err;
return err.message || err.code || String(err);
}
function asEventList(events) {
if (Array.isArray(events)) return events;
if (events && typeof events[Symbol.iterator] === "function") {
try {
return [...events];
} catch {
return [];
}
}
return [];
}
function service(ctx, name) {
if (!ctx || typeof ctx.get !== "function") return undefined;
try {
return ctx.get(name);
} catch {
return undefined;
}
}
function userTextFromEvent(event) {
const data = event?.data;
const content = data?.content ?? data?.message?.content;
if (typeof content === "string") return content.trim();
if (!Array.isArray(content)) return "";
const parts = [];
for (const block of content) {
if (!block) continue;
if (typeof block === "string") {
parts.push(block);
continue;
}
if (block.type === "text" || typeof block.text === "string") {
parts.push(String(block.text ?? ""));
}
}
return parts.join("").trim();
}
function isPluginDraft(text) {
const value = String(text || "").trim();
if (!value) return false;
return /^\[MNEMON\]/i.test(value)
|| /MNEMON RUNTIME MEMORY SNAPSHOT/i.test(value)
|| /^MNEMON VIEW TOOLS/im.test(value);
}
function isRewindMarkerEvent(event) {
if (!event || event.type !== "user/message") return false;
const id = event.data?.id;
return typeof id === "string" && id.startsWith("rewind-");
}
function isHumanUserEvent(event) {
if (event?.type !== "user/message") return false;
const source = event.data?.source;
if (source?.kind === "plugin") return false;
if (source?.kind && source.kind !== "user") return false;
const text = userTextFromEvent(event);
if (!text || isPluginDraft(text)) return false;
return true;
}
/** 这一轮真正发出去的那句。手打是 user;goal 各轮也算(含 round 0 的首轮 goal)。 */
function isRoundPromptEvent(event) {
if (!event || event.type !== "user/message" || isRewindMarkerEvent(event)) return false;
const source = event.data?.source;
const kind = source?.kind;
if (kind === "plugin") return false;
const text = userTextFromEvent(event);
if (!text || isPluginDraft(text)) return false;
if (!kind || kind === "user") return true;
if (kind === "goal") return Number.isFinite(Number(source.round)) && Number(source.round) >= 0;
return false;
}
/** 最近一轮已经 turn/end 的边界。用 turn 窗口找提示,避免第二轮完成却退回第一句。 */
function eventsInOrder(events) {
return asEventList(events)
.map((event, index) => ({ event, seq: Number.isFinite(Number(event?.seq)) ? Number(event.seq) : index }))
.sort((left, right) => left.seq - right.seq);
}
function lastOpenTurn(events) {
const list = asEventList(events);
for (let i = list.length - 1; i >= 0; i -= 1) {
const type = list[i]?.type;
if (type === "turn/start") return list[i];
if (type === "turn/end") return null;
}
return null;
}
function isSubagentHeader(header) {
if (!header) return false;
return header.origin === "subagent" || (header.delegationDepth ?? 0) > 0;
}
export function pendingInboxInserts(events) {
const list = Array.isArray(events) ? events : [];
let lastEnd = -1;
let lastStart = -1;
for (const event of list) {
if (event?.type === "turn/end") lastEnd = event.seq;
if (event?.type === "turn/start") lastStart = event.seq;
}
const after = lastEnd >= 0 ? lastEnd : -1;
const ids = [];
const seen = new Set();
for (const event of list) {
if (!event || event.seq <= after) continue;
if (lastStart > after && event.seq >= lastStart) continue;
if (event.type !== "agent/inbox/spliced") continue;
if (event.data?.target && event.data.target !== "next-turn") continue;
for (const message of event.data?.inserted || []) {
const id = message?.id;
if (!id || seen.has(id)) continue;
seen.add(id);
ids.push(id);
}
}
return ids;
}
export function lastTurnSubagentIds(events, fromSeq = -1) {
const list = Array.isArray(events) ? events : [];
let floor = Number(fromSeq);
if (!Number.isFinite(floor) || floor < 0) {
floor = -1;
for (let i = list.length - 1; i >= 0; i -= 1) {
if (list[i]?.type === "turn/start") {
floor = list[i].seq;
break;
}
}
}
if (floor < 0) return [];
const ids = [];
const seen = new Set();
for (const event of list) {
if (!event || event.seq < floor) continue;
if (event.type !== "subagent/catalog" && event.type !== "subagent/descriptor") continue;
const id = event.data?.childId || event.data?.sessionId || event.data?.id;
if (!id || seen.has(id)) continue;
seen.add(id);
ids.push(id);
}
return ids;
}
function seqBefore(events, cutSeq) {
const floor = Number(cutSeq);
let prev = null;
for (const row of eventsInOrder(events)) {
if (!Number.isFinite(row.seq)) continue;
if (Number.isFinite(floor) && row.seq >= floor) break;
prev = row;
}
return prev && Number.isFinite(prev.seq) ? prev.seq : null;
}
function lastEventOfType(events, type) {
const list = asEventList(events);
for (let i = list.length - 1; i >= 0; i -= 1) {
if (list[i]?.type === type) return list[i];
}
return null;
}
function userTextFromRange(events, fromSeq) {
const list = asEventList(events);
const floor = Number(fromSeq);
for (let i = list.length - 1; i >= 0; i -= 1) {
const event = list[i];
const seq = Number(event?.seq);
if (Number.isFinite(floor) && Number.isFinite(seq) && seq < floor) break;
if (isRoundPromptEvent(event)) return userTextFromEvent(event);
}
return "";
}
function isFirstStepOfTurn(events, start) {
if (!start) return true;
const turn = start.data?.turn;
const startSeq = Number(start.seq);
return !asEventList(events).some((event) => (
event?.type === "step/start"
&& event !== start
&& Number(event.seq) < startSeq
&& (turn == null || event.data?.turn === turn)
));
}
function turnStartFor(events, turn, beforeSeq) {
const limit = Number(beforeSeq);
let found = null;
for (const event of asEventList(events)) {
const seq = Number(event?.seq);
if (Number.isFinite(limit) && Number.isFinite(seq) && seq > limit) break;
if (event?.type === "turn/start" && (turn == null || event.data?.turn === turn)) found = event;
}
return found;
}
/** once = 撤最后一步;round = 撤最后一整轮。fork 的 atSeq 含这一条,之后的消息都不要。 */
export function planRewind(events, mode = "once") {
return mode === "round" ? planRewindTurn(events) : planRewindStep(events);
}
function planRewindStep(events) {
const start = lastEventOfType(events, "step/start");
if (!start) return planRewindTurn(events);
const turn = start.data?.turn;
const step = start.data?.step;
let cutSeq = Number(start.seq);
if (isFirstStepOfTurn(events, start)) {
const turnStart = turnStartFor(events, turn, start.seq);
if (turnStart && Number.isFinite(Number(turnStart.seq))) cutSeq = Number(turnStart.seq);
}
const text = userTextFromRange(events, cutSeq);
const atSeq = seqBefore(events, cutSeq);
if (atSeq == null) {
if (!text) return { ok: false, error: "没有可回退的上一句" };
return { ok: true, mode: "blank", text, unit: "step", turn, step, fromSeq: cutSeq };
}
return { ok: true, mode: "fork", atSeq, text, unit: "step", turn, step, fromSeq: cutSeq };
}
function planRewindTurn(events) {
const list = asEventList(events);
const open = lastOpenTurn(list);
let turnStart = open;
if (!turnStart) {
const ended = lastEventOfType(list, "turn/end");
if (ended) turnStart = turnStartFor(list, ended.data?.turn, ended.seq) || ended;
}
if (!turnStart) {
const lastUser = [...list].reverse().find((event) => isRoundPromptEvent(event));
if (!lastUser) return { ok: false, error: "没有可回退的上一句" };
const cutSeq = Number(lastUser.seq);
const text = userTextFromEvent(lastUser);
const atSeq = seqBefore(list, cutSeq);
if (atSeq == null) return { ok: true, mode: "blank", text, unit: "turn", fromSeq: cutSeq };
return { ok: true, mode: "fork", atSeq, text, unit: "turn", fromSeq: cutSeq };
}
const cutSeq = Number(turnStart.seq);
const text = userTextFromRange(list, cutSeq);
const atSeq = seqBefore(list, cutSeq);
if (!text && atSeq == null) return { ok: false, error: "没有可回退的上一句" };
if (atSeq == null) return { ok: true, mode: "blank", text, unit: "turn", fromSeq: cutSeq };
return { ok: true, mode: "fork", atSeq, text, unit: "turn", fromSeq: cutSeq };
}
function rememberDraft(sessionId, text, parentId) {
const rec = {
sessionId,
parentId,
text: text || "",
at: Date.now(),
};
pendingDrafts.set(sessionId, rec);
lastPending = rec;
const now = Date.now();
for (const [id, row] of pendingDrafts) {
if (now - row.at > DRAFT_TTL_MS) pendingDrafts.delete(id);
}
return rec;
}
export function getDraft(sessionId) {
if (!sessionId) return null;
const rec = pendingDrafts.get(sessionId);
if (!rec) return null;
if (Date.now() - rec.at > DRAFT_TTL_MS) {
pendingDrafts.delete(sessionId);
return null;
}
return rec;
}
export function takeDraft(sessionId) {
const rec = getDraft(sessionId);
if (rec) pendingDrafts.delete(sessionId);
return rec;
}
export function getPending() {
if (!lastPending) return null;
if (Date.now() - lastPending.at > DRAFT_TTL_MS) {
lastPending = null;
return null;
}
return lastPending;
}
export function lastHumanUserText(events) {
const list = Array.isArray(events) ? events : [];
for (let i = list.length - 1; i >= 0; i -= 1) {
if (isHumanUserEvent(list[i])) return userTextFromEvent(list[i]);
}
return "";
}
export async function readLastUserText(ctx, sessionId) {
if (!sessionId) return { ok: false, text: "", error: "没有当前会话" };
try {
const log = await readSessionLog(ctx, sessionId);
return { ok: true, text: lastHumanUserText(log.events) };
} catch (err) {
return { ok: false, text: "", error: errorText(err) };
}
}
function disposeObservation(observed) {
try {
observed?.[Symbol.dispose]?.();
} catch {
/* ignore */
}
}
async function readSessionLog(ctx, sessionId) {
const store = service(ctx, "sessions");
const live = store?.get?.(sessionId);
if (live?.snapshotEvents) {
return {
events: asEventList(live.snapshotEvents()),
header: live.header,
cwd: live.header?.cwd,
};
}
const controller = service(ctx, "sessionController");
if (typeof controller?.inspect === "function") {
const inspected = await controller.inspect(sessionId);
return {
events: asEventList(inspected?.events),
header: inspected?.meta || inspected?.header,
cwd: inspected?.meta?.cwd || inspected?.header?.cwd,
};
}
const query = service(ctx, "sessionQuery");
if (typeof query?.observeSession === "function") {
const observed = await query.observeSession(sessionId, { projectionMode: "none" });
try {
return {
events: asEventList(observed?.events),
header: observed?.header,
cwd: observed?.header?.cwd,
};
} finally {
disposeObservation(observed);
}
}
throw new Error("找不到当前会话");
}
async function waitTurnClosed(ctx, sessionId, timeoutMs = 8000) {
const started = Date.now();
let last = await readSessionLog(ctx, sessionId);
while (lastOpenTurn(last.events)) {
if (Date.now() - started >= timeoutMs) {
throw new Error("回合还在进行,请先点停止再回退");
}
await sleep(80);
last = await readSessionLog(ctx, sessionId);
}
return last;
}
function getAgent(ctx, sessionId) {
const agents = service(ctx, "agents");
try {
return agents?.get?.(sessionId) || agents?.agent?.(sessionId) || undefined;
} catch {
return undefined;
}
}
/** 回退后下一句按新请求开,不要接着上一轮的 request/header series。 */
function resetRequestSeries(ctx, sessionId) {
const agent = getAgent(ctx, sessionId);
if (!agent) return;
try { agent.requestHeaderLogged = false; } catch { /* ignore */ }
try { agent.requestSurfaceGeneration = -1; } catch { /* ignore */ }
try { agent.frozenMessages?.clear?.(); } catch { /* ignore */ }
}
async function disarmRewoundSession(ctx, sessionId, options = {}) {
if (!sessionId) return;
const controller = service(ctx, "sessionController");
const agent = getAgent(ctx, sessionId);
// 只清 inbox。fork 出的新会话不要 cancel:abort 后再发会走官方 wakingAfterAbort,等于误唤醒。
if (options.cancel !== false) {
const runningAbort = agent?.phase?.kind && agent.phase.kind !== "idle" ? agent.phase.abort : null;
if (runningAbort) {
try { ignoreSettled(agent.cancel({ kind: "user" }, { keepInbox: true })); } catch { /* ignore */ }
}
}
const ids = [];
try {
const log = await readSessionLog(ctx, sessionId);
ids.push(...pendingInboxInserts(log.events));
} catch { /* ignore */ }
try {
for (const message of [...(agent?.inbox?.nextTurn || []), ...(agent?.inbox?.nextStep || [])]) {
if (message?.id) ids.push(message.id);
}
} catch { /* ignore */ }
const seen = new Set();
for (const id of ids) {
if (!id || seen.has(id)) continue;
seen.add(id);
try {
await controller?.updateQueue?.({
sessionId,
itemId: id,
action: { kind: "remove" },
});
} catch { /* ignore */ }
}
}
export function invocationSessionId(invocation) {
return invocation?.agent?.session?.id
|| invocation?.agent?.sessionId
|| invocation?.agent?.session?.header?.id;
}
async function resolveMainSession(ctx, sessionId) {
const headers = new Map();
let currentId = sessionId;
const seen = new Set();
while (currentId && !seen.has(currentId)) {
seen.add(currentId);
const log = await readSessionLog(ctx, currentId);
const header = log.header || {};
headers.set(currentId, header);
if (!isSubagentHeader(header) || !header.parentSession) {
return { sessionId: currentId, log };
}
currentId = header.parentSession;
}
return { sessionId, log: await readSessionLog(ctx, sessionId) };
}
function liveSession(ctx, sessionId) {
const store = service(ctx, "sessions");
try {
return store?.get?.(sessionId) || null;
} catch {
return null;
}
}
async function createBlank(ctx, controller, sourceId, cwd) {
const workspace = findWorkspace(ctx, sourceId, cwd);
if (workspace?.id) return controller.create({ workspaceId: workspace.id });
if (cwd) return controller.create({ cwd });
return controller.create({});
}
function findWorkspace(ctx, sessionId, cwd) {
const registry = service(ctx, "workspaceRegistry");
const list = registry?.list?.() || [];
return list.find((workspace) => (workspace?.sessionIds || []).includes(sessionId))
|| (cwd ? list.find((workspace) => workspace?.path === cwd) : undefined);
}
async function hideRewoundSource(ctx, sessionId) {
if (!sessionId) return false;
const registry = service(ctx, "workspaceRegistry");
const workspaces = service(ctx, "workspaces");
const target = typeof registry?.archiveSession === "function"
? registry
: (typeof workspaces?.archiveSession === "function" ? workspaces : null);
let hidden = false;
if (target) {
await target.archiveSession(sessionId, { stopActivity: true });
hidden = true;
}
const listed = findWorkspace(ctx, sessionId);
const entity = listed?.id && typeof registry?.get === "function" ? registry.get(listed.id) : null;
if (entity && typeof entity.detachSession === "function") {
await entity.detachSession(sessionId);
hidden = true;
}
return hidden;
}
const rewindLocks = new Map();
/** 官方 fork 要求 events[atSeq].seq === atSeq(从 0 起连续)。 */
function officialForkBoundary(events, atSeq) {
const list = asEventList(events);
let n = Number(atSeq);
if (!Number.isSafeInteger(n) || n < 0) return null;
while (n >= 0) {
if (list[n]?.seq === n) return n;
n -= 1;
}
return null;
}
function prepareForkedChild(ctx, childId) {
resetRequestSeries(ctx, childId);
return liveSession(ctx, childId);
}
async function doRewindOneSession(ctx, controller, sessionId, options = {}) {
if (!controller || typeof controller.fork !== "function" || typeof controller.create !== "function") {
return { ok: false, error: "当前 dsh 没有会话回退接口" };
}
let log = await readSessionLog(ctx, sessionId);
if (lastOpenTurn(log.events)) {
try { ignoreSettled(controller.cancel?.({ sessionId })); } catch { /* ignore */ }
log = await waitTurnClosed(ctx, sessionId);
}
const plan = planRewind(log.events, options.mode === "round" ? "round" : "once");
if (!plan.ok) return plan;
let created;
if (plan.mode === "fork") {
const atSeq = officialForkBoundary(log.events, plan.atSeq);
if (atSeq == null) return { ok: false, error: "没有可回退的上一句" };
created = await controller.fork({ sessionId, atSeq });
} else {
created = await createBlank(ctx, controller, sessionId, log.cwd);
}
const childId = created?.sessionId || (typeof created === "string" ? created : "");
if (!childId) return { ok: false, error: "回退会话创建失败" };
prepareForkedChild(ctx, childId);
await disarmRewoundSession(ctx, childId, { cancel: false });
let archived = false;
try {
archived = await hideRewoundSource(ctx, sessionId);
} catch { /* 旧会话没藏住也不挡新会话重发 */ }
const rec = rememberDraft(childId, plan.text, sessionId);
return {
ok: true,
sessionId: childId,
parentId: sessionId,
archived,
cleared: true,
text: rec.text,
cut: plan.unit || plan.mode,
at: rec.at,
};
}
async function rewindOneSession(ctx, controller, sessionId, options = {}) {
const key = String(sessionId || "");
if (!key) return doRewindOneSession(ctx, controller, sessionId, options);
const prev = rewindLocks.get(key) || Promise.resolve();
const next = prev.then(async () => {
try {
return await doRewindOneSession(ctx, controller, sessionId, options);
} finally {
if (rewindLocks.get(key) === next) rewindLocks.delete(key);
}
}, async () => {
try {
return await doRewindOneSession(ctx, controller, sessionId, options);
} finally {
if (rewindLocks.get(key) === next) rewindLocks.delete(key);
}
});
rewindLocks.set(key, next);
return next;
}
export async function analyzeRewind(ctx, sessionId) {
if (!sessionId) return { ok: false, error: "没有当前会话" };
try {
const log = await readSessionLog(ctx, sessionId);
const kind = isSubagentHeader(log.header) ? "subagent" : "main";
const subagentIds = kind === "main" ? lastTurnSubagentIds(log.events) : [];
return {
ok: true,
kind,
sessionId,
hasSubagents: subagentIds.length > 0,
subagentIds,
};
} catch (err) {
return { ok: false, error: errorText(err) };
}
}
export async function applyRewind(ctx, sessionId, mode) {
if (!sessionId) return { ok: false, error: "没有当前会话" };
const controller = service(ctx, "sessionController");
const action = mode === "round" ? "round" : "once";
try {
if (action === "round") {
const target = await resolveMainSession(ctx, sessionId);
if (sessionId !== target.sessionId) {
try { ignoreSettled(controller.cancel?.({ sessionId })); } catch { /* ignore */ }
}
const preview = planRewind(target.log?.events || [], "round");
const children = preview.ok ? lastTurnSubagentIds(target.log.events, preview.fromSeq) : [];
const result = await rewindOneSession(ctx, controller, target.sessionId, { mode: "round" });
if (!result.ok) return result;
for (const childId of children) {
if (!childId || childId === target.sessionId) continue;
try { ignoreSettled(controller.cancel?.({ sessionId: childId })); } catch { /* ignore */ }
try { await hideRewoundSource(ctx, childId); } catch { /* ignore */ }
}
return { ...result, sourceId: sessionId, mode: "round" };
}
const result = await rewindOneSession(ctx, controller, sessionId, { mode: "once" });
if (!result.ok) return result;
return { ...result, sourceId: sessionId, mode: "once" };
} catch (err) {
return { ok: false, error: errorText(err) };
}
}