@zhushanwen/pi-system-prompt 1.3.1 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-system-prompt",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "System prompt injection extension for Pi — reads config and appends to system prompt",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -8,7 +8,7 @@
8
8
  * 损坏形态("enabled": "false" 字符串 / capability 非对象)→ 注入(与 readSection 解析方向相反)
9
9
  * - before_agent_start 注入顺序:base → global instructions → capability → append(indexOf 链锁定,
10
10
  * swap 注入顺序两行的 mutant 会被顺序用例 kill)
11
- * - -nc / --no-context-files 守卫:global 注入跳过、append 不受影响
11
+ * - -nc / --no-context-files 检查:global 注入跳过、append 不受影响
12
12
  * - global 候选选择:候选序优先、空白内容跳过继续找、目录缺失降级
13
13
  * - fail-safe:handler 全程 throw → return undefined + logger.error 可观测;logger 自身抛错的
14
14
  * 终极兜底 process.stderr.write;systemPrompt 非法类型的旧 quirk 锚定
@@ -38,6 +38,7 @@ import createExtension, {
38
38
  CAPABILITY_INLINE_TAG_FAMILIES,
39
39
  CAPABILITY_PRESENTATION_ATTRS,
40
40
  SESSION_ARTIFACTS_DIR_SEGMENT,
41
+ SESSION_STORE_DIR_SEGMENT,
41
42
  resolveSessionArtifactsDir,
42
43
  } from '../index'
43
44
  import type { ExtensionAPI, BeforeAgentStartEvent } from '@earendil-works/pi-coding-agent'
@@ -136,7 +137,7 @@ const savedEnv: Record<string, string | undefined> = {}
136
137
  const savedArgv = process.argv
137
138
 
138
139
  beforeEach(() => {
139
- vi.clearAllMocks() // 清跨用例的 fs mock 调用记录(「守卫不触发」类断言依赖零计数)
140
+ vi.clearAllMocks() // 清跨用例的 fs mock 调用记录(「检查不触发」类断言依赖零计数)
140
141
  for (const k of ENV_KEYS) savedEnv[k] = process.env[k]
141
142
  process.env.TAIJI_AGENT_DATA_DIR = DATA_DIR
142
143
  process.env.TAIJI_GLOBAL_AGENTS_DIR = GLOBAL_DIR
@@ -237,7 +238,7 @@ describe('before_agent_start 注入顺序(base → global → capability → a
237
238
  expect(prompt).toContain(path.join(GLOBAL_DIR, 'AGENTS.md'))
238
239
  })
239
240
 
240
- it('capability 段文案覆盖四点能力面(HTML 白名单 / 相对图片 / 相对链接双通道 / 远程图片不渲染)', () => {
241
+ it('capability 段文案覆盖四点能力侧(HTML 白名单 / 相对图片 / 相对链接双通道 / 远程图片不渲染)', () => {
241
242
  setupFs({ config: '{"append": {"enabled": false, "prompt": ""}}' })
242
243
  const prompt = runHook('BASE-PROMPT')!.systemPrompt as string
243
244
  const capText = prompt.slice(prompt.indexOf(CAP_HEADER))
@@ -314,9 +315,9 @@ describe('capability 段三态锚点(设计 D6:仅显式布尔 false 关闭
314
315
  })
315
316
  })
316
317
 
317
- describe('-nc / --no-context-files 守卫(contextFilesDisabled)', () => {
318
- // capability 与守卫正交(内置段不受 --no-context-files 影响,该旗标只管上下文文件
319
- // 发现),用例显式关 capability 隔离守卫语义。
318
+ describe('-nc / --no-context-files 检查(contextFilesDisabled)', () => {
319
+ // capability 与检查正交(内置段不受 --no-context-files 影响,该旗标只管上下文文件
320
+ // 发现),用例显式关 capability 隔离检查语义。
320
321
  it('argv 含 --no-context-files → global 不注入,append 仍生效(用户显式退出不得溜回来)', () => {
321
322
  setupFs({
322
323
  config: `{"append": {"enabled": true, "prompt": "APPEND-TEXT"}, ${CAP_OFF}}`,
@@ -325,11 +326,11 @@ describe('-nc / --no-context-files 守卫(contextFilesDisabled)', () => {
325
326
  })
326
327
  process.argv = ['node', 'pi', '--no-context-files']
327
328
  expect(runHook('BASE-PROMPT')).toEqual({ systemPrompt: 'BASE-PROMPT\n\nAPPEND-TEXT' })
328
- // 守卫在读 global 文件之前:readdirSync 不应被调用(global 目录完全不被触碰)
329
+ // 检查在读 global 文件之前:readdirSync 不应被调用(global 目录完全不被触碰)
329
330
  expect(readdirSync).not.toHaveBeenCalled()
330
331
  })
331
332
 
332
- it('argv 含 -nc 短形式 → 同样跳过 global 注入(pi CLI 等价短形式,守卫两种形式都命中)', () => {
333
+ it('argv 含 -nc 短形式 → 同样跳过 global 注入(pi CLI 等价短形式,检查两种形式都命中)', () => {
333
334
  setupFs({
334
335
  config: `{"append": {"enabled": true, "prompt": "APPEND-TEXT"}, ${CAP_OFF}}`,
335
336
  globalEntries: ['AGENTS.md'],
@@ -404,7 +405,7 @@ describe('fail-safe(外层 catch return undefined,永不阻断 agent loop)
404
405
  })
405
406
 
406
407
  it('systemPrompt 非法类型(undefined)且无 append → 旧 quirk 锚定:返回仅含 capability 段的结果而非 undefined', () => {
407
- // R3 复核锚定的返回值守卫 quirk(index.ts newPrompt === event.systemPrompt 比较):
408
+ // R3 复核锚定的返回值检查 quirk(index.ts newPrompt === event.systemPrompt 比较):
408
409
  // base 收敛 '',capability 默认开 → newPrompt 非空且 !== undefined → 必然返回对象。
409
410
  // 与重构前行为一致(非回归):quirk 在「有注入时必然返回对象」,schema v2 后 capability
410
411
  // 默认开使该形态成为常态。
@@ -529,17 +530,20 @@ describe('capability 段 ①② 渲染锁定(文案 = 常量渲染产物,手
529
530
  })
530
531
  })
531
532
 
532
- describe('产物目录镜像常量与推导 helper(设计 D1/D7,供 u-artifacts 公式对拍消费)', () => {
533
- it('段名字面量为 artifacts', () => {
533
+ describe('产物目录镜像常量与推导 helper(session 存储树布局,公式对拍机检消费)', () => {
534
+ it('段名字面量:树根段 ext-sessions + 域段 artifacts(与 shared SESSION_STORE_DIR_SEGMENT / getSessionArtifactsDir 对拍)', () => {
535
+ expect(SESSION_STORE_DIR_SEGMENT).toBe('ext-sessions')
534
536
  expect(SESSION_ARTIFACTS_DIR_SEGMENT).toBe('artifacts')
535
537
  })
536
538
 
537
- it('合法 pi sid → <dataDir>/artifacts/<sessionId>', () => {
539
+ it('合法 pi sid → <dataDir>/ext-sessions/<sessionId>/artifacts', () => {
538
540
  expect(resolveSessionArtifactsDir('0198ab12-cdef-7000-8000-1234567890ab')).toBe(
539
- path.join(DATA_DIR, 'artifacts', '0198ab12-cdef-7000-8000-1234567890ab'),
541
+ path.join(DATA_DIR, 'ext-sessions', '0198ab12-cdef-7000-8000-1234567890ab', 'artifacts'),
540
542
  )
541
543
  // 允许 `.` 与 `_`(与 isPiSessionId 同域)
542
- expect(resolveSessionArtifactsDir('a.b-c_d')).toBe(path.join(DATA_DIR, 'artifacts', 'a.b-c_d'))
544
+ expect(resolveSessionArtifactsDir('a.b-c_d')).toBe(
545
+ path.join(DATA_DIR, 'ext-sessions', 'a.b-c_d', 'artifacts'),
546
+ )
543
547
  })
544
548
 
545
549
  it('非法 sessionId(含冒号 / 空串 / 首尾非字母数字)→ throw(防路径穿越)', () => {
@@ -551,7 +555,9 @@ describe('产物目录镜像常量与推导 helper(设计 D1/D7,供 u-artifa
551
555
  it('resolveDataDir 回退(无 TAIJI_AGENT_DATA_DIR 时取 PI_CODING_AGENT_DIR 上级)', () => {
552
556
  delete process.env.TAIJI_AGENT_DATA_DIR
553
557
  process.env.PI_CODING_AGENT_DIR = path.join('/tmp/fallback-data', 'agent')
554
- expect(resolveSessionArtifactsDir('abc')).toBe(path.join('/tmp/fallback-data', 'artifacts', 'abc'))
558
+ expect(resolveSessionArtifactsDir('abc')).toBe(
559
+ path.join('/tmp/fallback-data', 'ext-sessions', 'abc', 'artifacts'),
560
+ )
555
561
  })
556
562
  })
557
563
 
@@ -576,9 +582,9 @@ describe('capability 段 ③④(HTML 交付约定 + 预览约束)与产物
576
582
 
577
583
  it('ctx 会话 id 在 → ③ 段含产物目录绝对路径(与会话 id 匹配)', () => {
578
584
  const text = capabilityTextWithCtx(ctxWithSessionId(() => SID))
579
- // 路径 = <dataDir>/artifacts/<sid>(与 shared getSessionArtifactsDir 同公式)
585
+ // 路径 = <dataDir>/ext-sessions/<sid>/artifacts(与 shared getSessionArtifactsDir 同公式)
580
586
  expect(text).toContain(resolveSessionArtifactsDir(SID))
581
- expect(text).toContain(path.join(DATA_DIR, 'artifacts', SID))
587
+ expect(text).toContain(path.join(DATA_DIR, 'ext-sessions', SID, 'artifacts'))
582
588
  // ③ 段交付约定与 ④ 段预览约束齐备
583
589
  expect(text).toContain('info string is html-preview')
584
590
  expect(text).toContain("that file's absolute path")
@@ -605,7 +611,7 @@ describe('capability 段 ③④(HTML 交付约定 + 预览约束)与产物
605
611
  expect(text).toContain('blocked by the browser CORS policy')
606
612
  expect(text).toContain('CDN links will not load')
607
613
  // 不出现半截/伪造路径
608
- expect(text).not.toContain(path.join(DATA_DIR, 'artifacts'))
614
+ expect(text).not.toContain(path.join(DATA_DIR, 'ext-sessions'))
609
615
  }
610
616
  })
611
617
 
@@ -616,7 +622,7 @@ describe('capability 段 ③④(HTML 交付约定 + 预览约束)与产物
616
622
  const text = capabilityTextWithCtx(ctx)
617
623
  expect(text).toContain('(unavailable this turn)')
618
624
  expect(text).toContain('info string is html-preview')
619
- expect(text).not.toContain(path.join(DATA_DIR, 'artifacts'))
625
+ expect(text).not.toContain(path.join(DATA_DIR, 'ext-sessions'))
620
626
  }
621
627
  })
622
628
 
package/src/index.ts CHANGED
@@ -81,7 +81,7 @@ const GLOBAL_AGENTS_CANDIDATES = ['AGENTS.md', 'AGENTS.MD']
81
81
  * 常量,能力段文案由这些常量渲染——禁止把清单写成散文旁路(成员集合可被
82
82
  * `scripts/check-capability-allowlist-sync.mjs` 与渲染管线白名单对拍,零散文解析)。
83
83
  *
84
- * 成员来源 = `packages/renderer/src/composables/logic/markdown-sanitize.ts` 的
84
+ * 成员来源 = `packages/ui/src/features/chat/markdown-sanitize.ts` 的
85
85
  * `ALLOWED_TAGS` 实际字面量(52 项,GitHub 风格白名单翻译,按族归类,每项恰一次)。
86
86
  * 与渲染白名单的任一方向漂移(清单多出未放行成员 / 白名单新增未入清单)由对拍机检
87
87
  * 红灯拦住,pre-commit 按路径触发。
@@ -214,7 +214,18 @@ function resolveDataDir(): string {
214
214
  }
215
215
 
216
216
  /**
217
- * 会话产物目录镜像常量(设计 D1/D7):`<dataDir>/artifacts/<sessionId>/`。
217
+ * session 存储树根段镜像常量(session-store-layout-inversion D1):
218
+ * `<dataDir>/ext-sessions/<sessionId>/...` 树根段。
219
+ *
220
+ * shared 侧单点声明 = `packages/shared/src/paths.ts` 的 `SESSION_STORE_DIR_SEGMENT` /
221
+ * `getSessionStoreDir`;本包不 import `@taiji/shared`(包边界 + C-proc-26/C-proc-09
222
+ * 门禁组合,见下方产物目录镜像说明),以同名字面量镜像,一致性由
223
+ * `scripts/check-artifact-dir-formula-sync.mjs` 对拍机检守护。
224
+ */
225
+ export const SESSION_STORE_DIR_SEGMENT = 'ext-sessions'
226
+
227
+ /**
228
+ * 会话产物目录镜像常量(session 存储树域 2):`<dataDir>/ext-sessions/<sessionId>/artifacts/`。
218
229
  *
219
230
  * shared 侧单点声明 = `packages/shared/src/paths.ts` 的 `getSessionArtifactsDir`;本包
220
231
  * 不 import `@taiji/shared`(① 包边界——extensions 包全体不依赖 @taiji/shared;② 运行时
@@ -226,22 +237,22 @@ function resolveDataDir(): string {
226
237
  export const SESSION_ARTIFACTS_DIR_SEGMENT = 'artifacts'
227
238
 
228
239
  /**
229
- * 会话产物目录绝对路径(镜像 shared `getSessionArtifactsDir` 公式)。
240
+ * 会话产物目录绝对路径(镜像 shared `getSessionArtifactsDir` 公式:
241
+ * `<dataDir>/ext-sessions/<sessionId>/artifacts`)。
230
242
  *
231
243
  * sessionId 校验与 pi `assertValidSessionId` / runtime `isPiSessionId` 同域:首尾字母
232
- * 数字,中间允许 `[A-Za-z0-9._-]`(允许 `.` / `_`,禁 `:`)——不用 `getImageCacheDir`
233
- * 的窄集 `/^[A-Za-z0-9_-]+$/`(合法含 `.` 的 pi sid 会被窄集误拒)。非法 sessionId
244
+ * 数字,中间允许 `[A-Za-z0-9._-]`(允许 `.` / `_`,禁 `:`)。非法 sessionId
234
245
  * 直接 throw(防路径穿越)。
235
246
  *
236
247
  * @param sessionId 会话 id(子目录分区,须与 `isPiSessionId` 同域)
237
248
  * @throws Error 当 sessionId 含路径分隔符、冒号、空串或首尾非字母数字
238
249
  */
239
250
  export function resolveSessionArtifactsDir(sessionId: string): string {
240
- // 校验与 pi-paths.ts 的 isPiSessionId 同域(字面量与 shared getSessionArtifactsDir 一致)
251
+ // 校验与 pi-paths.ts 的 isPiSessionId 同域(字面量与 shared PI_SESSION_ID_PATTERN 一致)
241
252
  if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(sessionId)) {
242
253
  throw new Error(`invalid sessionId (path traversal blocked): ${sessionId}`)
243
254
  }
244
- return path.join(resolveDataDir(), SESSION_ARTIFACTS_DIR_SEGMENT, sessionId)
255
+ return path.join(resolveDataDir(), SESSION_STORE_DIR_SEGMENT, sessionId, SESSION_ARTIFACTS_DIR_SEGMENT)
245
256
  }
246
257
 
247
258
  /**
@@ -392,8 +403,7 @@ function readSection(raw: unknown): { enabled: boolean; prompt: string } {
392
403
  /**
393
404
  * pi 是否以 --no-context-files / -nc 启动。用户显式退出 AGENTS.md
394
405
  * 自动发现时,全局文件不得从这条通路溜回来。pi CLI 把 -nc 视为 --no-context-files
395
- * 的等价短形式(cli/args.ts),两种形式都必须命中守卫——与镜像侧
396
- * (argv-mirror.ts 同样解析两种形式)保持一致。
406
+ * 的等价短形式(cli/args.ts),本守卫单侧同时覆盖两种 argv 形态。
397
407
  */
398
408
  function contextFilesDisabled(): boolean {
399
409
  return process.argv.includes('--no-context-files') || process.argv.includes('-nc')