@zhushanwen/pi-provider-live-sync 0.2.1 → 0.3.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 CHANGED
@@ -16,7 +16,16 @@ pi 的可用模型集合在**进程启动时**冻结:`set_model` 从内存快
16
16
  pi 的 `ModelRuntime.refresh()` 首行重读磁盘配置并重组 provider,`allowNetwork:false` 保证
17
17
  **零网络请求**(本地恢复相位,凭据解析/入网前返回);
18
18
  - 刷新后读 `ctx.modelRegistry.getError()`:非空即原样落 ERROR 日志(模型配置被引擎拒绝的
19
- 唯一有效判据——该退化形态下 `refresh().errors` 为空)。
19
+ 唯一有效判据——该退化形态下 `refresh().errors` 为空);
20
+ - taiji 托管环境另有**目录投喂(catalog overlay)**职责:模型目录缓存
21
+ `<数据目录>/provider-catalog-overlay.json` 作为**第三个监视条目**按 **mtime** 比较(区别于
22
+ 两条目的内容比较;基线在扩展工厂期播种、首拍放行),工厂期经 `pi.registerProvider` 注入
23
+ 「内置基线 ⊕ 缓存条目」并集,运行中变化先对本拍 fresh 且尚未注册的 provider 做一次**注册对账**
24
+ (补 `registerProvider`——工厂期因过期 / 缺失 / 损坏未注入的 provider 没有刷新钩子,单靠刷新
25
+ 重建不了注入),再触发同一次零网络刷新——上游目录新模型对运行中的会话立即可见。条目三态:
26
+ **fresh** 注入 / **expired·never-seen** 不注入(已注入的撤销、回落 pi 内置基线)/
27
+ **损坏·不可读** 不注入新 provider、已注入的保留,文件修复后下一拍对账重建注入。原生 pi 用户
28
+ (无 `TAIJI_AGENT_DATA_DIR`)整条不挂(pass-through)。
20
29
 
21
30
  ## 语义要点(易误解处)
22
31
 
@@ -26,6 +35,10 @@ pi 的可用模型集合在**进程启动时**冻结:`set_model` 从内存快
26
35
  | 文件**曾存在、现在缺失**(被隔离/外部删除) | 更新基线但**抑制刷新**(避免用空配置重建可用集合),只记一行日志 |
27
36
  | 读失败(权限等非 ENOENT) | 该文件本拍不比较、基线不动;另一文件照常 |
28
37
  | 刷新抛错 | 基线照常推进(同一内容不重复刷新),下一拍继续 |
38
+ | 目录缓存条目 **fresh** | 注入「内置基线 ⊕ 缓存条目」并集;运行中变化经同一 tick 刷新生效(工厂期未注册的 provider 在该拍先补注册) |
39
+ | 目录缓存条目 **expired / never-seen**(过期、从未见过、缓存缺失) | 不注入;已注入的经 `refreshModels` 钩子撤销、回落 pi 内置基线 |
40
+ | 目录缓存**损坏 / 不可读** | 不注入新 provider,已注入的保留当前注入;修复或删除文件(下次目录刷新重写)后下一拍自动对账重建注入,无需重开会话 |
41
+ | `models.json` 抑制窗口内目录缓存变化 | 不穿透抑制(窗口内一律不刷新),解除后补一次刷新 |
29
42
 
30
43
  ## 安装
31
44
 
@@ -34,7 +47,15 @@ pi 的可用模型集合在**进程启动时**冻结:`set_model` 从内存快
34
47
 
35
48
  ## 排障
36
49
 
37
- - 日志前缀 `[provider-live-sync]`:`config change detected (...)` 表示检测到变更并已刷新;
38
- `model config rejected by the engine: ...` 表示 pi 拒绝了配置文件(原文含字段路径);
39
- `config file disappeared: ...` 表示文件被隔离/删除(此拍不刷新,引擎保留上一份快照)。
50
+ - 日志前缀 `[provider-live-sync]`:`config change detected (...)` 表示检测到变更并已刷新
51
+ (列表含 `models.json` / `auth.json` / `provider-catalog-overlay.json`);`model config rejected
52
+ by the engine: ...` 表示 pi 拒绝了配置(原文含字段路径,来源不限——两配置文件与目录投喂
53
+ 注入集都可能被拒);`config file disappeared: ...` 表示文件被隔离/删除(此拍不刷新,引擎保留
54
+ 上一份快照)。
55
+ - 目录投喂侧:`catalog cache corrupt/unreadable ..., overlay injection skipped` = 工厂期或对账拍
56
+ 缓存损坏/不可读、本拍不注入新 provider(修复或删除该文件后下一拍自动对账重建注入,无需重开
57
+ 会话);`catalog cache unreadable during refresh ... keeping the current injection` = 运行期
58
+ 保留注入、文件恢复可读后下一拍自愈;`catalog overlay model skipped` / `registerProvider failed`
59
+ = 目录数据不完整或 pi 结构校验失败,等下次设置页目录刷新或按日志里的缓存路径修数据源
60
+ (缓存重写后自动重试注册)。
40
61
  - 配置无轮询开关:周期为设计标定值(2s)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-provider-live-sync",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Keep the running pi process's model snapshot in sync with on-disk provider/model/credential config changes.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -29,9 +29,11 @@
29
29
  "skills/"
30
30
  ],
31
31
  "dependencies": {
32
- "@zhushanwen/pi-extension-logger": "0.6.2"
32
+ "@zhushanwen/pi-extension-logger": "0.6.2",
33
+ "@zhushanwen/pi-llm-shared": "0.12.0"
33
34
  },
34
35
  "devDependencies": {
36
+ "@earendil-works/pi-ai": "1.0.0",
35
37
  "@vitest/coverage-v8": "^4.1.9",
36
38
  "vitest": "^4.1.8"
37
39
  },
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: provider-live-sync-ext-config
3
- description: 使用或排查 @zhushanwen/pi-provider-live-sync(运行中 pi 的模型快照实时同步)时加载。说明它为何存在(pi 快照冻结在 spawn 时刻)、轮询语义(2s / 内容比较 / 按文件独立基线)、四态规则(缺失常态 / 曾存在→缺失抑制 / 读失败跳过 / 变化触发)、零网络保证、错误可见性(getError 全文)、日志与排障、无配置项的原因。触发词:provider 不刷新、新加模型切不过去、Model not found、模型快照、provider-live-sync、配置变更不生效、refresh 模型、扩展刷新。
3
+ description: "使用或排查 @zhushanwen/pi-provider-live-sync(运行中 pi 的模型快照实时同步 + 目录投喂)时加载。说明它为何存在(pi 快照冻结在 spawn 时刻)、轮询语义(2s:models.json/auth.json 两条目内容比较 + taiji 目录缓存一条目 mtime 比较,后者工厂期播种基线、首拍放行)、四态规则(缺失常态 / 曾存在→缺失抑制 / 读失败跳过 / 变化触发)、目录投喂三态(fresh 注入 / expired·never-seen 不注入 / 损坏·不可读保留注入)、零网络保证、错误可见性(getError 全文)、日志与排障、无配置项的原因。触发词:provider 不刷新、新加模型切不过去、Model not found、模型快照、provider-live-sync、配置变更不生效、refresh 模型、扩展刷新、目录投喂、catalog overlay、catalog cache、registerProvider。"
4
4
  ---
5
5
 
6
6
  # provider-live-sync 扩展说明
@@ -16,11 +16,22 @@ pi 自身没有文件 watcher、也没有 refresh RPC。因此「会话开着
16
16
  `ctx.modelRegistry.refresh({ allowNetwork: false })`。pi 的 `ModelRuntime.refresh()` 首行会重读磁盘
17
17
  配置并重组 provider,所以新增 / 删除 provider、模型、凭据都会立即对运行中的会话生效。
18
18
 
19
+ 第二个职责是**目录投喂(catalog overlay)**:taiji 的模型目录缓存
20
+ (`<数据目录>/provider-catalog-overlay.json`)经 pi 官方注册点 `pi.registerProvider` 注入注册表,
21
+ 修的是「上游目录新模型在 pi 执行侧不可见」那类断裂(`set_model` 报 ENGINE_MODEL_MISSING、
22
+ spawn `--model` 近似回退等)。注册发生在扩展工厂函数体内;该缓存文件作为**第三个监视条目**
23
+ 挂进同一个 tick(mtime 比较,区别于既有两条目的内容比较),检出变化先做一次**注册对账**
24
+ (对本拍 fresh 且尚未注册的 provider 补 `registerProvider`——工厂期因过期 / 缺失 / 损坏未注入的
25
+ provider 没有刷新钩子,单靠刷新重建不了注入),再触发同一次零网络刷新,新目录模型对运行中的
26
+ 会话立即可见。此职责只在 taiji 托管环境(`TAIJI_AGENT_DATA_DIR` 存在)生效,原生 pi 用户整条
27
+ 不挂(pass-through)。
28
+
19
29
  ## 无配置项(有意)
20
30
 
21
- 轮询周期 2s 是设计标定值:它是「内容变更 → 生效」的上界,且小于「保存 → 切回会话 → 打开 popover
22
- → 点选」的人工时延。没有开关、没有阈值、没有排除列表——本扩展**只做一件事**,任何配置面都会扩大
23
- 「配置错了就不生效」的故障面。需要改周期请改包内常量并同步设计文档。
31
+ 轮询周期 2s 是设计标定值:它是「变更 → 生效」的上界,且小于「保存 → 切回会话 → 打开 popover
32
+ → 点选」的人工时延。没有开关、没有阈值、没有排除列表——本扩展**没有任何可配置项**(两项职责
33
+ 共用同一条轮询链),每多一个可配置项都会扩大「配置错了就不生效」的故障面。需要改周期请改包内
34
+ 常量,并同步本 SKILL 与 README。
24
35
 
25
36
  ## 语义要点(排障时先看这张表)
26
37
 
@@ -32,6 +43,12 @@ pi 自身没有文件 watcher、也没有 refresh RPC。因此「会话开着
32
43
  | 读失败(权限等非 ENOENT) | 该文件本拍不比较、基线不动;另一文件照常 | 无法区分「读不到」与「不存在」时,不动基线避免误触发 |
33
44
  | 刷新抛错 | 基线照常推进(同一内容不重复刷新),下一拍继续 | 否则同一坏内容会每拍刷新 + 每拍报错(日志风暴) |
34
45
  | 半写文件(截断的内容) | 视为内容变化 → 刷新一次;完整内容到达后再刷一次 | 唯一非原子写方是 pi 对 auth.json 的原地写;瞬时收缩下一拍自愈 |
46
+ | 第三个监视条目(目录缓存)的比较方式 | **mtime** 比较(写入方是 runtime 的 tmp+rename 原子替换写,mtime 可靠);基线在**工厂期播种**、首拍放行 | 「工厂期注入 → tick 1」约 2s 窗内的缓存变更必须可检出,否则 mtime 稳定后永不重检 |
47
+ | 目录缓存条目 **fresh**(`lastModified` 晚于内置快照时刻) | 注入「内置基线 ⊕ 缓存条目」全量并集;运行中变化经同一 tick 刷新——工厂期未注册的 provider(当时过期 / 缺失 / 损坏)在该拍先补注册再刷新 | 新目录模型立即可见,无需重开会话;没有刷新钩子的 provider 不会被 pi 的刷新碰到,必须先补注册 |
48
+ | 目录缓存条目 **expired / never-seen**(过期、从未见过、缓存缺失) | 不注入;已注入的经 `refreshModels` 钩子回落纯内置基线(撤销注入) | 无权威数据时凭空造模型禁止(过期含远端 404/501 落盘的否定信号) |
49
+ | 目录缓存**损坏 / 不可读——工厂期或对账拍** | 不注入新 provider(已注入的按下一行保留);恢复 = 修复或删除该文件(下次设置页目录刷新会重写)→ 文件 mtime 变化 → 下一拍 tick 对账自动重建注入,无需重开会话 | 尚未注册的 provider 没有已注入的集合可保,损坏即按 never-seen 跳过 |
50
+ | 目录缓存**损坏 / 不可读——运行期刷新拍** | 钩子返回 undefined → pi 保留当前注入;文件恢复可读后下一拍自动恢复 | 宁可保留最后一份好快照,不因瞬时读失败清空可见集合 |
51
+ | `models.json` 抑制窗口内目录缓存变化 | 不穿透抑制(窗口内一律不刷新);抑制解除后补一次刷新 | 配置源缺失窗口内绝不 refresh,代价是保守方向的延迟 |
35
52
 
36
53
  **零网络**:`allowNetwork: false` 让 pi 只跑本地恢复相位,在任何凭据解析 / 入网前返回。
37
54
 
@@ -39,19 +56,27 @@ pi 自身没有文件 watcher、也没有 refresh RPC。因此「会话开着
39
56
 
40
57
  日志走 `@zhushanwen/pi-extension-logger`(前缀 `[provider-live-sync]`,不写 pi 不捕获的 stderr):
41
58
 
42
- - **error 行**(下表除首行外)→ session JSONL 的 `provider-live-sync:log` custom entry(持久化、
59
+ - **warn / error 行** → session JSONL 的 `provider-live-sync:log` custom entry(持久化、
43
60
  不进 LLM 上下文,重开会话可 grep)+ `TAIJI_AGENT_DEBUG=1` / `TAIJI_AGENT_EXT_LOG=1` 时落
44
61
  `<agentDir>/logs/provider-live-sync-YYYY-MM-DD.log`;
45
- - **debug 行**(`config change detected`)→ 仅上述文件日志(裸 pi 默认 no-op;taiji 托管环境
62
+ - **debug 行**(变更检测与目录投喂的正常观察)→ 仅上述文件日志(裸 pi 默认 no-op;taiji 托管环境
46
63
  runtime 恒注入 `TAIJI_AGENT_EXT_LOG=1`,INFO 级落盘)。
47
64
 
48
- | 日志 | 含义 |
49
- |---|---|
50
- | `config change detected (models.json, auth.json) → refreshing model snapshot` | 检测到变更并已刷新 |
51
- | `model config rejected by the engine (models.json / auth.json): <pi 原文>` | pi 拒绝了配置(含解析失败与 schema 非法的字段路径)。**这是坏配置的唯一机器证据**——该形态下 `refresh()` 返回的 `errors` 为空,只有 `getError()` 有内容 |
52
- | `config file disappeared: models.json — refresh suppressed until it is written again` | 文件被隔离 / 删除,进入持续抑制窗口 |
53
- | `refresh finished with errors (aborted=…, providers=N)` | 通用加固记录(网络 / 凭据 / 取消类;**不是**坏配置判据) |
54
- | `tick failed: …` | 本拍异常(下拍继续,不影响 pi) |
65
+ | 日志 | 级别 | 含义 |
66
+ |---|---|---|
67
+ | `config change detected (<变化条目列表>) → refreshing model snapshot` | debug | 检测到变更并已刷新。列表 = `models.json` / `auth.json` / `provider-catalog-overlay.json`(目录缓存)中本拍有变化的条目 |
68
+ | `model config rejected by the engine: <pi 原文>` | error | pi 拒绝了配置(含解析失败与 schema 非法的字段路径)。来源不限——`models.json`、`auth.json` 与目录投喂注入集都可能被拒,归因看 `getError()` 原文里的字段路径。**这是坏配置的唯一机器证据**——该形态下 `refresh()` 返回的 `errors` 为空,只有 `getError()` 有内容 |
69
+ | `config file disappeared: models.json — refresh suppressed until it is written again` | error | 文件被隔离 / 删除,进入持续抑制窗口 |
70
+ | `refresh finished with errors (aborted=…, providers=N)` | error | 通用加固记录(网络 / 凭据 / 取消类;**不是**坏配置判据) |
71
+ | `tick failed: …` | error | 本拍异常(下拍继续,不影响 pi) |
72
+ | `catalog cache absent (<label>): <路径> — its providers fall to never-seen …` | debug | 目录缓存文件不存在(首装未开过设置页目录刷新的常态):该 provider 按 never-seen 不注入 |
73
+ | `catalog cache corrupt/unreadable (<label>), overlay injection skipped: <路径>` | warn | 目录缓存损坏(JSON / 结构非法)或不可读(权限等),出现在工厂期或 tick 的对账拍:本拍不注入新 provider。恢复 = 修复或删除该文件(下次设置页目录刷新会重写),文件 mtime 变化后下一拍自动对账重建注入,无需重开会话 |
74
+ | `catalog cache unreadable during refresh for <providerId>, keeping the current injection` | warn | **运行期**刷新拍缓存不可读:钩子返回 undefined,pi 保留当前注入;文件恢复可读后下一拍自动恢复 |
75
+ | `catalog overlay model skipped: <providerId>/<modelId> is missing api or baseUrl …` | warn | 目录条目缺 `api` / `baseUrl` 且同 id 无内置模型可继承,被保守档滤掉;等下次目录刷新带来完整数据 |
76
+ | `registerProvider failed for <providerId>: <pi 原文>` | error | pi 对注册入参的结构校验失败:该 provider 保持 pi 内置行为,不阻断其他 provider 注入;按日志里的缓存路径修复目录数据源,下次目录刷新重写缓存后自动重试注册(重开会话同样重试) |
77
+ | `catalog overlay injected for <providerId>: N models …` | debug | 目录投喂注入成功(内置基线 ⊕ 缓存,state=fresh) |
78
+ | `catalog overlay not injected for <providerId>: state=expired/never-seen — falling back to pi's builtin behavior` | debug | 条目过期(含远端 404/501 否定信号)或从未见过:不注入,回落 pi 内置行为 |
79
+ | `catalog overlay revoked for <providerId>: state=… — returning the builtin baseline` | debug | 运行中刷新拍条目转为非 fresh:钩子撤销注入,返回纯内置基线 |
55
80
 
56
81
  常见问题:
57
82
 
@@ -63,10 +88,23 @@ pi 自身没有文件 watcher、也没有 refresh RPC。因此「会话开着
63
88
  (如模型缺 `id`、`baseUrl` 为空串)。按字段路径修正后保存,扩展会自动恢复(≤2s)。
64
89
  - **`config file disappeared` 之后模型列表变少**:这是抑制窗口内的预期形态(引擎保留最后一份
65
90
  好快照);把 `models.json` 写正后下一拍自动恢复,重启会话同样可恢复。
91
+ - **日志出现 `catalog cache corrupt` / `catalog cache unreadable`(目录缓存损坏 / 不可读)**:
92
+ `overlay injection skipped` 形态(工厂期或对账拍)→ 该拍没有注入新 provider,修复或删除该缓存
93
+ 文件(设置页下次目录刷新会重写)后 ≤2s 自动对账重建注入,无需重开会话;`during refresh …
94
+ keeping the current injection` 形态(运行期刷新拍)→ 已注入的保留,文件恢复可读后 ≤2s 自动恢复。
95
+ - **新目录模型切不过去,日志有 `catalog overlay model skipped` 或 `registerProvider failed`**:
96
+ 目录数据不完整(条目缺 `api` / `baseUrl`)或 pi 结构校验失败——`skipped` 等下次设置页目录
97
+ 刷新带来完整数据;`registerProvider failed` 按日志里的缓存路径排查数据源,修复后下次目录刷新
98
+ 重写缓存即自动重试注册(重开会话同样重试)。
99
+ - **目录缓存变了但没看到 `config change detected`**:`models.json` 处于「曾存在 → 缺失」抑制
100
+ 窗口时,目录缓存的变化不穿透抑制(保守方向);文件写回、抑制解除后补一次刷新。
66
101
 
67
102
  ## 相关
68
103
 
69
- - 设计依据:模型切换与运行中 provider 变更的实时可见性(技术设计文档 §3.3 D3/D11/D12)。
104
+ - 设计依据:ADR-0154(docs/adr/decisions.md——通道裁决与边界)。
105
+ - 目录投喂(第二个职责):写入单点约束 C-ext-32(constraints.json);
106
+ 术语见 docs/CONTEXT.md「目录投喂(catalog overlay)」。
70
107
  - pi 语义锚点:`ModelRuntime.refresh()` 重读磁盘(`dist/core/model-runtime.js:501-513`)、
71
- `getError()` 合成(`:305-317`)、pi-ai 本地相位提前返回(`dist/models.js:153-158`);
108
+ `getError()` 合成(`:305-317`)、pi-ai 本地相位提前返回(`dist/models.js:153-158`)、
109
+ 扩展工厂返回后 `registerProvider` 直连注册表立即生效(PS-88,对账注册的依据);
72
110
  登记于 `docs/pi-semantics.json`,受 C-proc-08 版本门禁约束。