@zhushanwen/pi-smart-context 0.3.0 → 0.3.1

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
@@ -13,10 +13,20 @@
13
13
  - **健壮性**:摘要收缩校验、max-tokens 截断 fail-closed、接管失败 3 次熔断、transcript 回查指针、压缩后最近文件内容重注入(≤5 文件/50K)、多轮压缩降智提示
14
14
  - **subagent 进程**自动静默(`PI_SUBAGENT_ROOT_SESSION_ID` 标记)
15
15
 
16
- ## 配置
16
+ ## 行为门控
17
17
 
18
- `<agentDir>/config/smart-context-ext-config.json`(读时热加载)。schema 与示例见 `skills/smart-context-ext-config/SKILL.md`。xyz-agent 桌面端在设置页(系统 → 智能上下文压缩)可视化配置。
18
+ | 状态 | 工具 execute | 阈值提醒 | 压缩生成接管 |
19
+ |---|---|---|---|
20
+ | enabled,模型未排除 | 放行(低于最低档阈值时拒绝并返回用量建议) | 生效 | 生效(按双模式判定) |
21
+ | enabled,模型命中排除列表 | 拒绝(返回原因) | 跳过 | 跳过(回落 pi 原生生成) |
22
+ | enabled=false | 拒绝("可在设置页开启") | 跳过 | 跳过(回落 pi 原生) |
23
+ | compactModel 未配置/无效 | 放行(same-model 不依赖该配置) | 生效 | same-model 生效;cross-model 回退当前模型,压缩不失败 |
24
+ | 模型切换跨界(model_select) | 常驻不变 | 注入一条可用性变化通知(仅跨界时) | 按新模型即时重判 |
25
+
26
+ 行为正确性由 execute / handler 现场校验兜底,每次事件回调重新读配置(热加载,改完下一 turn 生效,无需重启)。
19
27
 
20
- ## 设计文档
28
+ > 设计层 why 与业界调研记录见原设计文档(已随 2026-09 docs 清理退役,git 历史可查 `docs/extensions/smart-context/design.md`)。
21
29
 
22
- `docs/extensions/smart-context/design.md`(xyz-agent 仓库)——决策依据、探针记录、验收场景。
30
+ ## 配置
31
+
32
+ `<agentDir>/config/smart-context-ext-config.json`(读时热加载)。schema 与示例见 `skills/smart-context-ext-config/SKILL.md`。xyz-agent 桌面端在设置页(系统 → 智能上下文压缩)可视化配置。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-smart-context",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "xyz-agent": {
@@ -36,9 +36,9 @@
36
36
  "pi-package"
37
37
  ],
38
38
  "dependencies": {
39
- "@zhushanwen/pi-ext-guards": "0.3.0",
39
+ "@zhushanwen/pi-ext-guards": "0.4.0",
40
40
  "@zhushanwen/pi-extension-logger": "0.6.0",
41
- "@zhushanwen/pi-llm-shared": "0.7.0"
41
+ "@zhushanwen/pi-llm-shared": "0.8.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@vitest/coverage-v8": "^4.1.9",
@@ -12,7 +12,6 @@ import {
12
12
  formatK,
13
13
  getCurrentModelId,
14
14
  isGatingActive,
15
- isSubagentProcess,
16
15
  isSummaryInflated,
17
16
  normalizeSmartContextConfig,
18
17
  pickMode,
@@ -173,9 +172,6 @@ describe("estimateShadowedTokens 口径契约(D2:与 pi estimateTokens 同
173
172
  });
174
173
  });
175
174
 
176
- describe("subagent 识别(R6)", () => {
177
- it("PI_SUBAGENT_ROOT_SESSION_ID 存在即 subagent", () => {
178
- expect(isSubagentProcess({ PI_SUBAGENT_ROOT_SESSION_ID: "s1" })).toBe(true);
179
- expect(isSubagentProcess({})).toBe(false);
180
- });
181
- });
175
+ // [HISTORICAL] subagent 识别(R6)用例已随本地实现删除移除——现行谓词收敛于
176
+ // ext-guards isSubagentProcess(ext-simplify-17 D4),三态与「宁缺勿污」钉值见
177
+ // extensions/shared/ext-guards/src/__tests__/predicates.test.ts。
@@ -14,6 +14,7 @@
14
14
  import { buildSessionContext, compact as nativeCompact, convertToLlm } from "@earendil-works/pi-coding-agent";
15
15
  import type { CompactionResult, SessionBeforeCompactEvent } from "@earendil-works/pi-coding-agent";
16
16
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
17
+ import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
17
18
  import { createLogger } from "@zhushanwen/pi-extension-logger";
18
19
  import { resolveModel } from "@zhushanwen/pi-llm-shared";
19
20
  import { readFileSync } from "node:fs";
@@ -319,7 +320,7 @@ export function createBeforeCompactHandler(
319
320
  return { compaction: result };
320
321
  } catch (error) {
321
322
  state.failStreak += 1;
322
- warnLog("takeover error, falling back to native", { error: error instanceof Error ? error.message : String(error) });
323
+ warnLog("takeover error, falling back to native", { error: toErrorMessage(error) });
323
324
  return {}; // D7 回退
324
325
  }
325
326
  };
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
14
- import { guardStaleCtx, toErrorMessage } from "@zhushanwen/pi-ext-guards";
14
+ import { guardStaleCtx, isSubagentProcess, toErrorMessage } from "@zhushanwen/pi-ext-guards";
15
15
  import { setPiHandle } from "@zhushanwen/pi-extension-logger";
16
16
 
17
17
  import {
@@ -27,7 +27,6 @@ import {
27
27
  findCrossedThresholds,
28
28
  getCurrentModelId,
29
29
  isGatingActive,
30
- isSubagentProcess,
31
30
  loadSmartContextConfig,
32
31
  type EntryLike,
33
32
  } from "./pure.js";
@@ -63,7 +62,8 @@ export default function smartContextExtension(pi: ExtensionAPI): void {
63
62
  // 日志通道注入(extension-logger 两阶段初始化:工厂拿 pi → setPiHandle)
64
63
  setPiHandle(pi);
65
64
 
66
- // R6:subagent 子进程不注册工具、不提醒(PI_SUBAGENT_ROOT_SESSION_ID 标记)
65
+ // R6:subagent 子进程不注册工具、不提醒(宁缺勿污;XYZ_AGENT_SUBAGENT 标记——
66
+ // ext-guards isSubagentProcess,ext-simplify-17 D4 重锚)
67
67
  if (isSubagentProcess()) {
68
68
  debugLog("subagent process detected, staying inert");
69
69
  return;
package/src/llm.ts CHANGED
@@ -14,6 +14,7 @@ import { completeSimple } from "@earendil-works/pi-ai/compat";
14
14
  import type { Context as LlmContext, SimpleStreamOptions } from "@earendil-works/pi-ai/compat";
15
15
  import type { Tool as LlmTool, Message, Model } from "@earendil-works/pi-ai";
16
16
  import type { ExtensionContext, ToolInfo } from "@earendil-works/pi-coding-agent";
17
+ import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
17
18
 
18
19
  /**
19
20
  * ToolInfo → pi-ai Tool 投影:三字段直取。parameters 是 typebox schema(主会话同源对象,
@@ -127,6 +128,6 @@ export async function callSameModelCompaction(
127
128
  const text = resp.content.map(blockText).join("\n").trim();
128
129
  return { ok: true, text, usage: resp.usage, stopReason: resp.stopReason };
129
130
  } catch (error) {
130
- return { ok: false, error: error instanceof Error ? error.message : String(error) };
131
+ return { ok: false, error: toErrorMessage(error) };
131
132
  }
132
133
  }
package/src/pure.ts CHANGED
@@ -6,8 +6,9 @@
6
6
  */
7
7
 
8
8
  import { estimateTokens } from "@earendil-works/pi-coding-agent";
9
+ import { isRecord } from "@zhushanwen/pi-ext-guards";
9
10
  import type { ModelSelector } from "@zhushanwen/pi-llm-shared";
10
- import { loadConfig } from "@zhushanwen/pi-llm-shared";
11
+ import { loadConfig, normalizeModelSelector } from "@zhushanwen/pi-llm-shared";
11
12
 
12
13
  // ──────────────────────── 配置 schema(D8) ────────────────────────
13
14
 
@@ -59,13 +60,8 @@ export function normalizeSmartContextConfig(raw: unknown): SmartContextConfig {
59
60
 
60
61
  const enabled = typeof r.enabled === "boolean" ? r.enabled : base.enabled;
61
62
 
62
- const rawModel = typeof r.compactModel === "object" && r.compactModel !== null
63
- ? (r.compactModel as Record<string, unknown>)
64
- : null;
65
63
  const compactModel: ModelSelector =
66
- rawModel?.type === "ref" && typeof rawModel.ref === "string"
67
- ? { type: "ref", ref: rawModel.ref }
68
- : { type: "ref", ref: "" };
64
+ normalizeModelSelector(r.compactModel) ?? { type: "ref", ref: "" };
69
65
 
70
66
  const rawThresholds = Array.isArray(r.reminderThresholds)
71
67
  ? r.reminderThresholds
@@ -249,16 +245,6 @@ export function buildReinjectSection(contents: ReadonlyArray<{ path: string; con
249
245
  return `\n\n<recently-read-files>\n${parts.join("\n\n")}\n</recently-read-files>`;
250
246
  }
251
247
 
252
- // ──────────────────────── subagent 识别(R6) ────────────────────────
253
-
254
- /**
255
- * subagent 子进程检测(D9/R6):subagent-core session-runner 无条件注入 PI_SUBAGENT_ROOT_SESSION_ID。
256
- * 命中 → 本进程不注册工具、不提醒(宁缺勿污)。
257
- */
258
- export function isSubagentProcess(env: NodeJS.ProcessEnv = process.env): boolean {
259
- return env.PI_SUBAGENT_ROOT_SESSION_ID !== undefined;
260
- }
261
-
262
248
  // ──────────────────────── session entries 统计(D13 纯函数) ────────────────────────
263
249
 
264
250
  /** sessionManager entries 的宽松形状(降智计数)。 */
@@ -271,11 +257,6 @@ export function countCompactions(entries: ReadonlyArray<EntryLike>): number {
271
257
  return entries.filter((e) => e.type === "compaction").length;
272
258
  }
273
259
 
274
- /** unknown 的对象收窄(Record 视图;字段消费再经 typeof / Array.isArray 收窄)。 */
275
- function isRecord(value: unknown): value is Record<string, unknown> {
276
- return typeof value === "object" && value !== null;
277
- }
278
-
279
260
  /**
280
261
  * 保留段已 Read 的文件集合(D13-11 去重:重注入跳过保留段已有的 Read 结果)。
281
262
  * 保留段 = branchEntries 中 firstKeptEntryId 之后的 message entries;从其 toolCall 参数提取 path。
package/src/tool.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
13
- import { guardStaleCtx, toErrorMessage } from "@zhushanwen/pi-ext-guards";
13
+ import { guardStaleCtx, isRecord, toErrorMessage } from "@zhushanwen/pi-ext-guards";
14
14
  import { Type } from "typebox";
15
15
 
16
16
  import { debugLog } from "./compact-handler.js";
@@ -66,11 +66,6 @@ const TOOL_DESCRIPTION =
66
66
  "2) 后续工作不再依赖将被压缩的早期细节;3) 上下文已超过提醒阈值(你会收到 [smart-context 提示])。" +
67
67
  "若任一条件不满足,不要调用。";
68
68
 
69
- /** unknown 的对象收窄(Record 视图;字段消费再经 typeof 收窄)。 */
70
- function isRecord(value: unknown): value is Record<string, unknown> {
71
- return typeof value === "object" && value !== null;
72
- }
73
-
74
69
  /** CompactionResult → 宽松形状的 guard 转换(onComplete 回调参数消费,避免 cast)。 */
75
70
  function toCompactionResultLike(result: unknown): CompactionResultLike {
76
71
  const r: Record<string, unknown> = isRecord(result) ? result : {};