@zhushanwen/pi-system-prompt-trace 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/package.json +5 -5
- package/src/__tests__/baseline.a12.test.ts +2 -0
- package/src/__tests__/index-wiring.test.ts +56 -1
- package/src/__tests__/trace.a11.test.ts +32 -2
- package/src/baseline.ts +2 -2
- package/src/index.ts +4 -0
- package/src/trace.ts +17 -0
- package/src/types.ts +62 -3
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Session Trace 单元 1(留痕 extension)。system prompt 在 pi 中每次运行时动态重建、默认不落盘——本包在
|
|
4
4
|
system prompt 确立或变化时向 session JSONL 追加留痕 entry,让「resume/reload/换配置前后 prompt 到底是什么」
|
|
5
|
-
|
|
5
|
+
可追溯。
|
|
6
6
|
|
|
7
7
|
## 落盘格式
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
appendEntry("
|
|
10
|
+
appendEntry("taiji:system-prompt", {
|
|
11
11
|
version: number; // session 内单调递增,首条 1
|
|
12
12
|
hash: string; // sha256(fullText) 十六进制
|
|
13
13
|
reason: "initial" | "resume" | "change";
|
|
@@ -31,7 +31,7 @@ appendEntry("xyz:system-prompt", {
|
|
|
31
31
|
|---|---|---|
|
|
32
32
|
| startup / new | initial | 定案 |
|
|
33
33
|
| resume | resume | 定案 |
|
|
34
|
-
| fork / reload | resume | 定案(
|
|
34
|
+
| fork / reload | resume | 定案(fork 档读源文件最后留痕,落盘 reason 维持 resume) |
|
|
35
35
|
|
|
36
36
|
存在 hash 基线(见下)且 prompt 未变时,首个 turn 不写(去重);需写时(hash 已变)恒写 `resume`(无论 session_start reason)。
|
|
37
37
|
|
|
@@ -39,13 +39,12 @@ appendEntry("xyz:system-prompt", {
|
|
|
39
39
|
|
|
40
40
|
1. **进程内 resume(stash)**:`session_before_switch.targetSessionFile` 直读目标 session 文件,取最后一条留痕
|
|
41
41
|
entry 的 hash/version/fullText。switch 会 teardown 并重建 extension runtime,该基线经模块级 stash 跨 runtime 传递。
|
|
42
|
-
2. **fork**:`session_start.previousSessionFile` 直读源 session
|
|
43
|
-
|
|
42
|
+
2. **fork**:`session_start.previousSessionFile` 直读源 session 文件,取源文件最后一条留痕(常态
|
|
43
|
+
/fork 时点 fork 新文件未落盘,不可直读新文件;源文件缺失/未落盘/读取失败 → 视为无基线)。
|
|
44
44
|
3. **直读当前 session 文件**:`ctx.sessionManager.getSessionFile()` 直读 JSONL 最后一条留痕,覆盖 reload /
|
|
45
45
|
重启直 spawn resume / new 兜底等没有 switch 事件的链路(文件不存在或无留痕 → 视为无基线)。
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
session JSONL 自身;存量文件成为无读写方孤儿,可安全手动删除(含 `*.tmp_*` 残留)。
|
|
47
|
+
唯一持久化源 = session JSONL 自身。
|
|
49
48
|
|
|
50
49
|
三档都 miss(无任何基线)时兜底必写:首个 turn 无论 session_start reason 必写一条——
|
|
51
50
|
startup/new → `initial`,resume/fork/reload → `resume`(宁可多写不可漏记)。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-system-prompt-trace",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "System prompt trace extension: appends
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "System prompt trace extension: appends taiji:system-prompt entries to the session JSONL whenever the effective system prompt is established or changes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"keywords": [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"src/**/*.ts",
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
|
-
"
|
|
20
|
+
"taiji": {
|
|
21
21
|
"role": "taiji"
|
|
22
22
|
},
|
|
23
23
|
"pi": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"vitest": "^4.1.8"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@zhushanwen/pi-ext-guards": "0.4.
|
|
37
|
-
"@zhushanwen/pi-extension-logger": "0.6.
|
|
36
|
+
"@zhushanwen/pi-ext-guards": "0.4.1",
|
|
37
|
+
"@zhushanwen/pi-extension-logger": "0.6.1"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"typecheck": "npx tsc --noEmit",
|
|
@@ -72,6 +72,8 @@ function makeHarness(initialPrompt: string, opts?: { noSessionFile?: boolean }):
|
|
|
72
72
|
|
|
73
73
|
const env: TraceEnv = {
|
|
74
74
|
readLastPromptFromFile: (filePath) => readLastPromptFromSessionFile(filePath),
|
|
75
|
+
// F1b:A12 聚焦基线恢复,不涉回落事实(回落专测在 a11 / wiring)
|
|
76
|
+
getPresetFallback: () => undefined,
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
const ctx: TraceContext = {
|
|
@@ -20,7 +20,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
20
20
|
|
|
21
21
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
22
22
|
|
|
23
|
-
import { isSystemPromptTraceEntryData, SYSTEM_PROMPT_CUSTOM_TYPE } from "../types.js";
|
|
23
|
+
import { isSystemPromptTraceEntryData, PRESET_FALLBACK_ENV_KEYS, SYSTEM_PROMPT_CUSTOM_TYPE } from "../types.js";
|
|
24
24
|
import type { SystemPromptTraceEntryData } from "../types.js";
|
|
25
25
|
|
|
26
26
|
// trace.ts 的 computePromptHash 已收敛为包内私有(无外部消费方);测试本地同款实现计算期望值。
|
|
@@ -337,6 +337,61 @@ describe("index.ts wiring SDK 契约", () => {
|
|
|
337
337
|
});
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
+
it("F1b wiring:process.env 两键齐备 → entry 带 presetFallback(from/to);既有字段不削弱", async () => {
|
|
341
|
+
const ext = await loadExtension();
|
|
342
|
+
const h = createWiringHarness();
|
|
343
|
+
process.env[PRESET_FALLBACK_ENV_KEYS.FROM] = "custom:gone-uuid";
|
|
344
|
+
process.env[PRESET_FALLBACK_ENV_KEYS.TO] = "builtin:full";
|
|
345
|
+
try {
|
|
346
|
+
ext(h.pi);
|
|
347
|
+
const ctx = createCtx(() => P1, "sess-w-fb");
|
|
348
|
+
await emit(h, "session_start", { type: "session_start", reason: "startup" }, ctx);
|
|
349
|
+
await emit(h, "turn_start", { type: "turn_start", turnIndex: 0, timestamp: 0 }, ctx);
|
|
350
|
+
} finally {
|
|
351
|
+
delete process.env[PRESET_FALLBACK_ENV_KEYS.FROM];
|
|
352
|
+
delete process.env[PRESET_FALLBACK_ENV_KEYS.TO];
|
|
353
|
+
}
|
|
354
|
+
expect(h.entries).toHaveLength(1);
|
|
355
|
+
expect(entryData(h, 0).presetFallback).toEqual({ from: "custom:gone-uuid", to: "builtin:full" });
|
|
356
|
+
expect(entryData(h, 0)).toMatchObject({
|
|
357
|
+
version: 1,
|
|
358
|
+
reason: "initial",
|
|
359
|
+
fullText: P1,
|
|
360
|
+
charCount: P1.length,
|
|
361
|
+
hash: computePromptHash(P1),
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("F1b wiring:env 未注入 / 两键为空串(runtime 显式清除)→ entry 不含 presetFallback(无假披露)", async () => {
|
|
366
|
+
const ext = await loadExtension();
|
|
367
|
+
// 态 1:未注入
|
|
368
|
+
delete process.env[PRESET_FALLBACK_ENV_KEYS.FROM];
|
|
369
|
+
delete process.env[PRESET_FALLBACK_ENV_KEYS.TO];
|
|
370
|
+
const hAbsent = createWiringHarness();
|
|
371
|
+
ext(hAbsent.pi);
|
|
372
|
+
const ctxAbsent = createCtx(() => P1, "sess-w-fb-absent");
|
|
373
|
+
await emit(hAbsent, "session_start", { type: "session_start", reason: "startup" }, ctxAbsent);
|
|
374
|
+
await emit(hAbsent, "turn_start", { type: "turn_start", turnIndex: 0, timestamp: 0 }, ctxAbsent);
|
|
375
|
+
expect(hAbsent.entries).toHaveLength(1);
|
|
376
|
+
expect(Object.prototype.hasOwnProperty.call(entryData(hAbsent, 0), "presetFallback")).toBe(false);
|
|
377
|
+
|
|
378
|
+
// 态 2:空串(runtime 未回落时写空串显式清除继承值)——不得当回落事实
|
|
379
|
+
process.env[PRESET_FALLBACK_ENV_KEYS.FROM] = "";
|
|
380
|
+
process.env[PRESET_FALLBACK_ENV_KEYS.TO] = "";
|
|
381
|
+
const hEmpty = createWiringHarness();
|
|
382
|
+
try {
|
|
383
|
+
ext(hEmpty.pi);
|
|
384
|
+
const ctxEmpty = createCtx(() => P1, "sess-w-fb-empty");
|
|
385
|
+
await emit(hEmpty, "session_start", { type: "session_start", reason: "startup" }, ctxEmpty);
|
|
386
|
+
await emit(hEmpty, "turn_start", { type: "turn_start", turnIndex: 0, timestamp: 0 }, ctxEmpty);
|
|
387
|
+
} finally {
|
|
388
|
+
delete process.env[PRESET_FALLBACK_ENV_KEYS.FROM];
|
|
389
|
+
delete process.env[PRESET_FALLBACK_ENV_KEYS.TO];
|
|
390
|
+
}
|
|
391
|
+
expect(hEmpty.entries).toHaveLength(1);
|
|
392
|
+
expect(Object.prototype.hasOwnProperty.call(entryData(hEmpty, 0), "presetFallback")).toBe(false);
|
|
393
|
+
});
|
|
394
|
+
|
|
340
395
|
it("getSystemPrompt 抛错 → handler 吞掉不写 entry(留痕是诊断旁路,不影响 agent 主流程)", async () => {
|
|
341
396
|
const ext = await loadExtension();
|
|
342
397
|
const h = createWiringHarness();
|
|
@@ -18,7 +18,7 @@ import { describe, expect, it } from "vitest";
|
|
|
18
18
|
import { createSystemPromptTrace } from "../trace.js";
|
|
19
19
|
import type { SystemPromptTrace, TraceContext, TraceEnv } from "../trace.js";
|
|
20
20
|
import { isSystemPromptTraceEntryData, SYSTEM_PROMPT_CUSTOM_TYPE } from "../types.js";
|
|
21
|
-
import type { SystemPromptTraceEntryData, SwitchStash } from "../types.js";
|
|
21
|
+
import type { PresetFallbackFact, SystemPromptTraceEntryData, SwitchStash } from "../types.js";
|
|
22
22
|
|
|
23
23
|
// trace.ts 的 computePromptHash 已收敛为包内私有(无外部消费方);测试本地同款实现计算期望值。
|
|
24
24
|
const computePromptHash = (text: string): string =>
|
|
@@ -37,13 +37,15 @@ interface Harness {
|
|
|
37
37
|
newLogic(): SystemPromptTrace;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function makeHarness(initialPrompt: string): Harness {
|
|
40
|
+
function makeHarness(initialPrompt: string, presetFallback?: PresetFallbackFact): Harness {
|
|
41
41
|
const entries: SystemPromptTraceEntryData[] = [];
|
|
42
42
|
let prompt = initialPrompt;
|
|
43
43
|
const stash: SwitchStash = { pending: null };
|
|
44
44
|
// A11 不涉文件路径:三档基线全部 miss(直读 / fork prev 文件路径均不可读),隔离验证时机/去重/映射逻辑
|
|
45
45
|
const env: TraceEnv = {
|
|
46
46
|
readLastPromptFromFile: () => null,
|
|
47
|
+
// F1b:默认无回落事实;需要时经参数注入(进程 env 语义的 fake)
|
|
48
|
+
getPresetFallback: () => presetFallback,
|
|
47
49
|
};
|
|
48
50
|
const ctx: TraceContext = {
|
|
49
51
|
getSystemPrompt: () => prompt,
|
|
@@ -134,6 +136,34 @@ describe("A11 留痕时机与去重", () => {
|
|
|
134
136
|
expect(entry.parentVersionDiffSummary).toContain("+ [Available Models]");
|
|
135
137
|
});
|
|
136
138
|
|
|
139
|
+
it("F1b 模式回落事实:env 携带 from/to → 本进程每条 entry 均带 presetFallback(resume 快照与 change 一致),既有字段不削弱", () => {
|
|
140
|
+
const fact: PresetFallbackFact = { from: "custom:gone-uuid", to: "builtin:full" };
|
|
141
|
+
const h = makeHarness(P1, fact);
|
|
142
|
+
h.logic.onSessionStart("resume", undefined, h.ctx);
|
|
143
|
+
h.logic.onTurnStart(h.ctx); // v1 resume
|
|
144
|
+
h.setPrompt(P2);
|
|
145
|
+
h.logic.onTurnStart(h.ctx); // v2 change
|
|
146
|
+
expect(h.entries).toHaveLength(2);
|
|
147
|
+
expect(h.entries[0]?.presetFallback).toEqual(fact);
|
|
148
|
+
expect(h.entries[1]?.presetFallback).toEqual(fact);
|
|
149
|
+
// 既有形状与语义不受影响
|
|
150
|
+
expect(h.entries[0]).toMatchObject({
|
|
151
|
+
version: 1, reason: "resume", fullText: P1, charCount: P1.length, hash: computePromptHash(P1),
|
|
152
|
+
});
|
|
153
|
+
expect(h.entries[1]).toMatchObject({
|
|
154
|
+
version: 2, reason: "change", fullText: P2, charCount: P2.length, hash: computePromptHash(P2),
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("F1b 无回落(getPresetFallback → undefined)→ entry 无 presetFallback 字段(形状向后兼容)", () => {
|
|
159
|
+
const h = makeHarness(P1);
|
|
160
|
+
h.logic.onSessionStart("startup", undefined, h.ctx);
|
|
161
|
+
h.logic.onTurnStart(h.ctx);
|
|
162
|
+
expect(h.entries).toHaveLength(1);
|
|
163
|
+
expect(h.entries[0]?.presetFallback).toBeUndefined();
|
|
164
|
+
expect(Object.prototype.hasOwnProperty.call(h.entries[0], "presetFallback")).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
137
167
|
it("prompt 变回旧值再写 change v3(时间线保留真实历史,只对当前版本去重)", () => {
|
|
138
168
|
const h = makeHarness(P1);
|
|
139
169
|
h.logic.onSessionStart("startup", undefined, h.ctx);
|
package/src/baseline.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* readLastPromptFromSessionFile 是三档统一的读取入口:直读 session JSONL
|
|
6
6
|
* (进程内 resume 的 targetSessionFile、fork 的 previousSessionFile、reload / 直启 resume
|
|
7
|
-
* 的 getSessionFile() 目标文件),倒序找最后一条
|
|
7
|
+
* 的 getSessionFile() 目标文件),倒序找最后一条 taiji:system-prompt 留痕 entry。
|
|
8
8
|
*
|
|
9
9
|
* 所有函数不抛错:读失败返回 null——基线丢失的代价只是
|
|
10
10
|
* 下次 resume 多写一条留痕(设计 D2 已接受),不允许影响 agent 主流程。
|
|
@@ -19,7 +19,7 @@ import type { PromptBaseline } from "./types.js";
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* 解析单行 session JSONL 为留痕 entry data(运行时 guard;任何形状不符 / JSON 损坏返回 null)。
|
|
22
|
-
* pi 落盘形状:{"type":"custom","customType":"
|
|
22
|
+
* pi 落盘形状:{"type":"custom","customType":"taiji:system-prompt","data":{...},...}
|
|
23
23
|
* (session-manager.ts:1122 appendCustomEntry)。
|
|
24
24
|
*/
|
|
25
25
|
function parseTraceEntryData(line: string): { hash: string; version: number; fullText: string } | null {
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { setPiHandle } from "@zhushanwen/pi-extension-logger";
|
|
|
19
19
|
import { readLastPromptFromSessionFile } from "./baseline.js";
|
|
20
20
|
import { createSystemPromptTrace } from "./trace.js";
|
|
21
21
|
import type { TraceContext, TraceEnv } from "./trace.js";
|
|
22
|
+
import { readPresetFallbackFromEnv } from "./types.js";
|
|
22
23
|
import type { SwitchStash } from "./types.js";
|
|
23
24
|
|
|
24
25
|
// 模块级单例:session_before_switch(旧 runtime)→ session_start(新 runtime)之间传递
|
|
@@ -45,6 +46,9 @@ export default function systemPromptTraceExtension(pi: ExtensionAPI): void {
|
|
|
45
46
|
|
|
46
47
|
const env: TraceEnv = {
|
|
47
48
|
readLastPromptFromFile: (filePath) => readLastPromptFromSessionFile(filePath),
|
|
49
|
+
// F1b(E4 trace 披露面):模式回落事实由 runtime 经 spawn 出站 env 携带。
|
|
50
|
+
// 工厂调用时读一次(进程 env 生命期内不变);未回落 → 空串 → undefined。
|
|
51
|
+
getPresetFallback: () => readPresetFallbackFromEnv(),
|
|
48
52
|
};
|
|
49
53
|
|
|
50
54
|
const logic = createSystemPromptTrace(env, switchStash);
|
package/src/trace.ts
CHANGED
|
@@ -20,6 +20,7 @@ const logger = getLogger("pi-system-prompt-trace");
|
|
|
20
20
|
import { summarizePromptDiff } from "./diff.js";
|
|
21
21
|
import { mapReasonForFirstWrite, SYSTEM_PROMPT_CUSTOM_TYPE } from "./types.js";
|
|
22
22
|
import type {
|
|
23
|
+
PresetFallbackFact,
|
|
23
24
|
PromptBaseline,
|
|
24
25
|
SwitchStash,
|
|
25
26
|
SystemPromptTraceEntryData,
|
|
@@ -38,6 +39,13 @@ export interface TraceContext {
|
|
|
38
39
|
/** 文件系统侧依赖(wiring 用真实 fs;测试注入临时目录实现)。 */
|
|
39
40
|
export interface TraceEnv {
|
|
40
41
|
readLastPromptFromFile(filePath: string): PromptBaseline | null;
|
|
42
|
+
/**
|
|
43
|
+
* 本次 pi 进程启动时的模式回落事实(F1b,E4 trace 披露面);未回落 / 未注入 → undefined。
|
|
44
|
+
*
|
|
45
|
+
* 进程 env 在生命期内不变,故工厂构造时读一次即可;为空字符串(runtime 的「显式
|
|
46
|
+
* 清除」形态)时同样返回 undefined——不得把空串当回落事实。
|
|
47
|
+
*/
|
|
48
|
+
getPresetFallback(): PresetFallbackFact | undefined;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
export interface SystemPromptTrace {
|
|
@@ -65,6 +73,10 @@ export function createSystemPromptTrace(env: TraceEnv, stash: SwitchStash): Syst
|
|
|
65
73
|
let sessionStartReason: SessionStartEvent["reason"] | null = null;
|
|
66
74
|
let baseline: PromptBaseline | null = null;
|
|
67
75
|
let current: CurrentPrompt | null = null;
|
|
76
|
+
// F1b(设计 `mode-system-composer-density` §7.5 E4 trace 披露面):
|
|
77
|
+
// 本次 pi 进程的模式回落事实由 spawn 出站 env 携带,进程生命期内恒定,构造时读一次。
|
|
78
|
+
// 未回落 / 未注入 → undefined,entry 的 presetFallback 字段不出现(既有形状向后兼容)。
|
|
79
|
+
const presetFallback = env.getPresetFallback();
|
|
68
80
|
|
|
69
81
|
const write = (
|
|
70
82
|
ctx: TraceContext,
|
|
@@ -84,6 +96,11 @@ export function createSystemPromptTrace(env: TraceEnv, stash: SwitchStash): Syst
|
|
|
84
96
|
if (parentFullText !== undefined) {
|
|
85
97
|
data.parentVersionDiffSummary = summarizePromptDiff(parentFullText, text);
|
|
86
98
|
}
|
|
99
|
+
// 回落事实随本进程写出的每条 entry 携带:本次 pi 运行确实以回落档启动,
|
|
100
|
+
// resume 快照与后续 change 同属该运行,披露一致(事件语义 = 本次运行口径)。
|
|
101
|
+
if (presetFallback !== undefined) {
|
|
102
|
+
data.presetFallback = presetFallback;
|
|
103
|
+
}
|
|
87
104
|
ctx.appendEntry(SYSTEM_PROMPT_CUSTOM_TYPE, data);
|
|
88
105
|
};
|
|
89
106
|
|
package/src/types.ts
CHANGED
|
@@ -8,13 +8,60 @@
|
|
|
8
8
|
import { isRecord } from "@zhushanwen/pi-ext-guards";
|
|
9
9
|
import type { SessionStartEvent } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
|
|
11
|
-
/** 留痕 entry 的 customType(
|
|
12
|
-
export const SYSTEM_PROMPT_CUSTOM_TYPE = "
|
|
11
|
+
/** 留痕 entry 的 customType(taiji: 前缀 = taiji 自定义命名空间)。 */
|
|
12
|
+
export const SYSTEM_PROMPT_CUSTOM_TYPE = "taiji:system-prompt";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 模式回落事实 env 名(字面量镜像 `packages/shared/src/constants.ts` 的
|
|
16
|
+
* `PRESET_FALLBACK_ENV_KEYS`)。
|
|
17
|
+
*
|
|
18
|
+
* extension 独立发布体系不依赖 `@taiji/shared`,只能按字面量镜像——单侧改名即断链
|
|
19
|
+
* (消息见 write 产出的 `presetFallback` 字段消失)。一致性由机器守卫断言:
|
|
20
|
+
* `.githooks/check_env_whitelist_sync.py` 规则 3 A8(漂移即红)。写入方 =
|
|
21
|
+
* `packages/runtime/src/services/session/launch-params.ts` 的 `buildPresetFallbackEnv`。
|
|
22
|
+
*/
|
|
23
|
+
export const PRESET_FALLBACK_ENV_KEYS = {
|
|
24
|
+
FROM: "TAIJI_PRESET_FALLBACK_FROM",
|
|
25
|
+
TO: "TAIJI_PRESET_FALLBACK_TO",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 模式回落事实(F1b,设计 `mode-system-composer-density` §7.5 E4
|
|
30
|
+
* 的 trace 披露面)。
|
|
31
|
+
*
|
|
32
|
+
* 语义 = 本次 pi 进程启动时,sidecar 里的模式定义已不可得,runtime 已回落
|
|
33
|
+
* `builtin:full` 启动(工具面从模式的受限面弹回全工具)——记录在 trace entry 里,
|
|
34
|
+
* 事后审计从 `taiji:system-prompt` 即可得知「提示词为何变了」。
|
|
35
|
+
*/
|
|
36
|
+
export interface PresetFallbackFact {
|
|
37
|
+
/** 原(悬空)模式 id。 */
|
|
38
|
+
from: string;
|
|
39
|
+
/** 回落目标模式 id(现行恒 `builtin:full`)。 */
|
|
40
|
+
to: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 从进程 env 读取模式回落事实(wiring 侧唯一 env 读取点;trace.ts 逻辑经 DI 消费)。
|
|
45
|
+
*
|
|
46
|
+
* - FROM 非空 → 返回 `{ from, to }`(TO 缺失时给空串——形状稳定,不抛错);
|
|
47
|
+
* - FROM 缺失 / 空串 → undefined(未回落,或未注入;空串是 runtime 的「显式清除」
|
|
48
|
+
* 语义,见 `buildPresetFallbackEnv`)。
|
|
49
|
+
*
|
|
50
|
+
* 不抛错:env 是外部输入,任何形态异常都退化为「无回落事实」,不阻断留痕。
|
|
51
|
+
*/
|
|
52
|
+
export function readPresetFallbackFromEnv(
|
|
53
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
54
|
+
): PresetFallbackFact | undefined {
|
|
55
|
+
const from = env[PRESET_FALLBACK_ENV_KEYS.FROM];
|
|
56
|
+
if (typeof from !== "string" || from === "") return undefined;
|
|
57
|
+
const to = env[PRESET_FALLBACK_ENV_KEYS.TO];
|
|
58
|
+
return { from, to: typeof to === "string" ? to : "" };
|
|
59
|
+
}
|
|
13
60
|
|
|
14
61
|
/** 落盘 reason 枚举(initial/resume/change,对齐 DSH request/header 语义,设计 D2)。 */
|
|
15
62
|
export type TraceReason = "initial" | "resume" | "change";
|
|
16
63
|
|
|
17
|
-
/** appendEntry("
|
|
64
|
+
/** appendEntry("taiji:system-prompt", data) 的 data 形状(设计 §5 单元 1)。 */
|
|
18
65
|
export interface SystemPromptTraceEntryData {
|
|
19
66
|
/** session 内单调递增(首条 1;有基线时续接基线版本 +1)。 */
|
|
20
67
|
version: number;
|
|
@@ -27,6 +74,12 @@ export interface SystemPromptTraceEntryData {
|
|
|
27
74
|
charCount: number;
|
|
28
75
|
/** 与上一版的行级 diff 摘要;首条留痕(无 parent)时缺省。 */
|
|
29
76
|
parentVersionDiffSummary?: string;
|
|
77
|
+
/**
|
|
78
|
+
* 模式回落事实(F1b,E4 trace 披露面):本次 pi 进程以「模式已删除 → 回落
|
|
79
|
+
* builtin:full」启动时出现;未回落(正常启动)时**字段不出现**——既有 entry
|
|
80
|
+
* 形状向后兼容。见 `PresetFallbackFact`。
|
|
81
|
+
*/
|
|
82
|
+
presetFallback?: PresetFallbackFact;
|
|
30
83
|
}
|
|
31
84
|
|
|
32
85
|
/** 跨重启恢复的 hash 基线(三档解析统一产自 session JSONL 留痕 entry 直读)。 */
|
|
@@ -55,6 +108,12 @@ export function isSystemPromptTraceEntryData(value: unknown): value is SystemPro
|
|
|
55
108
|
const reason = value["reason"];
|
|
56
109
|
const fullText = value["fullText"];
|
|
57
110
|
const charCount = value["charCount"];
|
|
111
|
+
// 可选字段(F1b):存在时须为 { from: string, to: string };缺失合法(向后兼容)
|
|
112
|
+
const presetFallback = value["presetFallback"];
|
|
113
|
+
if (presetFallback !== undefined) {
|
|
114
|
+
if (!isRecord(presetFallback)) return false;
|
|
115
|
+
if (typeof presetFallback["from"] !== "string" || typeof presetFallback["to"] !== "string") return false;
|
|
116
|
+
}
|
|
58
117
|
return (
|
|
59
118
|
typeof version === "number" &&
|
|
60
119
|
Number.isFinite(version) &&
|