@zhushanwen/pi-smart-context 0.3.0 → 0.3.2
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 +13 -5
- package/package.json +6 -6
- package/skills/smart-context-ext-config/SKILL.md +5 -5
- package/src/__tests__/pure.test.ts +5 -9
- package/src/__tests__/startup-config-declaration.test.ts +4 -4
- package/src/__tests__/tool.test.ts +1 -1
- package/src/compact-handler.ts +3 -2
- package/src/index.ts +3 -4
- package/src/llm.ts +2 -1
- package/src/pure.ts +6 -26
- package/src/tool.ts +4 -9
- package/vitest.config.ts +2 -4
package/README.md
CHANGED
|
@@ -8,15 +8,23 @@
|
|
|
8
8
|
- **双模式生成接管**(`session_before_compact`,覆盖工具 / `/compact` / 内建 auto 三条路径):
|
|
9
9
|
- same-model:完整原始上下文 + 会话原 system prompt + tools + 末尾追加压缩指令——前缀缓存全命中,成本≈增量指令+输出,且模型看全量上下文(质量上限最高)
|
|
10
10
|
- cross-model:调用 pi 原生 `compact()` 仅换模型与凭证——split-turn / fileOps / previousSummary 原生组装
|
|
11
|
-
- **3 档阈值提醒**(默认
|
|
11
|
+
- **3 档阈值提醒**(默认 400K/500K/600K):`agent_settled` 越档检查,每档一次、多档合并、压缩后重置,followUp 投递;措辞是数据投递不是指令
|
|
12
12
|
- **排除模型**(精准 `provider/modelId` 匹配):工具拒绝 + 不提醒 + 回落 pi 原生生成;切换跨界时注入一条可用性通知,downshift(切小窗模型将触线)时建议先压缩
|
|
13
13
|
- **健壮性**:摘要收缩校验、max-tokens 截断 fail-closed、接管失败 3 次熔断、transcript 回查指针、压缩后最近文件内容重注入(≤5 文件/50K)、多轮压缩降智提示
|
|
14
14
|
- **subagent 进程**自动静默(`PI_SUBAGENT_ROOT_SESSION_ID` 标记)
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## 行为门控
|
|
17
|
+
|
|
18
|
+
| 状态 | 工具 execute | 阈值提醒 | 压缩生成接管 |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| enabled,模型未排除 | 放行(低于最低档阈值时拒绝并返回用量建议) | 生效 | 生效(按双模式判定) |
|
|
21
|
+
| enabled,模型命中排除列表 | 拒绝(返回原因) | 跳过 | 跳过(回落 pi 原生生成) |
|
|
22
|
+
| enabled=false | 拒绝("可在设置页开启") | 跳过 | 跳过(回落 pi 原生) |
|
|
23
|
+
| compactModel 未配置/无效 | 放行(same-model 不依赖该配置) | 生效 | same-model 生效;cross-model 回退当前模型,压缩不失败 |
|
|
24
|
+
| 模型切换跨界(model_select) | 常驻不变 | 注入一条可用性变化通知(仅跨界时) | 按新模型即时重判 |
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
行为正确性由 execute / handler 现场校验兜底,每次事件回调重新读配置(热加载,改完下一 turn 生效,无需重启)。
|
|
19
27
|
|
|
20
|
-
##
|
|
28
|
+
## 配置
|
|
21
29
|
|
|
22
|
-
`
|
|
30
|
+
`<agentDir>/config/smart-context-ext-config.json`(读时热加载)。schema 与示例见 `skills/smart-context-ext-config/SKILL.md`。taiji 桌面端在设置页(系统 → 智能上下文压缩)可视化配置。
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-smart-context",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
|
-
"
|
|
6
|
+
"taiji": {
|
|
7
7
|
"role": "universal",
|
|
8
8
|
"startupConfig": [
|
|
9
9
|
{
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"ref": ""
|
|
16
16
|
},
|
|
17
17
|
"reminderThresholds": [
|
|
18
|
-
200000,
|
|
19
18
|
400000,
|
|
19
|
+
500000,
|
|
20
20
|
600000
|
|
21
21
|
],
|
|
22
22
|
"excludedModels": []
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"pi-package"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@zhushanwen/pi-ext-guards": "0.
|
|
40
|
-
"@zhushanwen/pi-extension-logger": "0.6.
|
|
41
|
-
"@zhushanwen/pi-llm-shared": "0.
|
|
39
|
+
"@zhushanwen/pi-ext-guards": "0.4.1",
|
|
40
|
+
"@zhushanwen/pi-extension-logger": "0.6.1",
|
|
41
|
+
"@zhushanwen/pi-llm-shared": "0.8.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@vitest/coverage-v8": "^4.1.9",
|
|
@@ -15,7 +15,7 @@ description: "配置 @zhushanwen/pi-smart-context(智能上下文压缩:agen
|
|
|
15
15
|
|
|
16
16
|
## 配置文件
|
|
17
17
|
|
|
18
|
-
路径:`<agentDir>/config/smart-context-ext-config.json`(`<agentDir>` = `getAgentDir()`,默认 `~/.pi/agent`,
|
|
18
|
+
路径:`<agentDir>/config/smart-context-ext-config.json`(`<agentDir>` = `getAgentDir()`,默认 `~/.pi/agent`,taiji 环境 `~/.taiji/agent`;`PI_CODING_AGENT_DIR` 可覆盖)。
|
|
19
19
|
|
|
20
20
|
### Schema
|
|
21
21
|
|
|
@@ -23,7 +23,7 @@ description: "配置 @zhushanwen/pi-smart-context(智能上下文压缩:agen
|
|
|
23
23
|
|---|---|---|---|
|
|
24
24
|
| `enabled` | boolean | `true` | 总开关 |
|
|
25
25
|
| `compactModel` | `{type:"ref", ref:string}` | `{type:"ref", ref:""}` | 压缩模型(完整 `provider/modelId`)。`ref` 为空 = 跟随当前会话模型(same-model 模式);等于当前模型同样进 same-model 模式 |
|
|
26
|
-
| `reminderThresholds` | `number[]` | `[
|
|
26
|
+
| `reminderThresholds` | `number[]` | `[400000, 500000, 600000]` | 3 档提醒阈值(token 绝对数,升序) |
|
|
27
27
|
| `excludedModels` | `string[]` | `[]` | 排除模型列表,完整 `provider/modelId` 精准等值匹配(如 `"deepseek/deepseek-chat"`),不做前缀匹配 |
|
|
28
28
|
|
|
29
29
|
### 配置示例
|
|
@@ -32,7 +32,7 @@ description: "配置 @zhushanwen/pi-smart-context(智能上下文压缩:agen
|
|
|
32
32
|
{
|
|
33
33
|
"enabled": true,
|
|
34
34
|
"compactModel": { "type": "ref", "ref": "xiaomi-token-plan-cn/mimo-v2.5" },
|
|
35
|
-
"reminderThresholds": [
|
|
35
|
+
"reminderThresholds": [400000, 500000, 600000],
|
|
36
36
|
"excludedModels": ["deepseek/deepseek-chat"]
|
|
37
37
|
}
|
|
38
38
|
```
|
|
@@ -52,5 +52,5 @@ description: "配置 @zhushanwen/pi-smart-context(智能上下文压缩:agen
|
|
|
52
52
|
- 压缩回退当前模型:`compactModel.ref` 指向的模型不可用(无凭证/已删)——换可用模型或留空
|
|
53
53
|
- 工具调用被拒「已禁用」/「已排除」:检查 `enabled` 与 `excludedModels`
|
|
54
54
|
- 提醒未出现:`getContextUsage().tokens` 在压缩后首个响应前为 null,属正常(下轮恢复)
|
|
55
|
-
- 连续接管失败 3 次后不再接管:本会话熔断保护,查看 `
|
|
56
|
-
- 调试:`
|
|
55
|
+
- 连续接管失败 3 次后不再接管:本会话熔断保护,查看 `TAIJI_AGENT_DEBUG=1` 日志定位失败原因
|
|
56
|
+
- 调试:`TAIJI_AGENT_DEBUG=1` 后看 `[smart-context]` 前缀日志(`~/.pi/agent/logs/`)
|
|
@@ -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,
|
|
@@ -23,7 +22,7 @@ describe("normalizeSmartContextConfig", () => {
|
|
|
23
22
|
it("非对象输入回退默认值", () => {
|
|
24
23
|
const c = normalizeSmartContextConfig(null);
|
|
25
24
|
expect(c).toEqual(DEFAULT_SMART_CONTEXT_CONFIG);
|
|
26
|
-
expect(c.reminderThresholds).toEqual([
|
|
25
|
+
expect(c.reminderThresholds).toEqual([400_000, 500_000, 600_000]);
|
|
27
26
|
});
|
|
28
27
|
|
|
29
28
|
it("阈值过滤非正数并升序、截 3 档;空数组回退默认", () => {
|
|
@@ -31,7 +30,7 @@ describe("normalizeSmartContextConfig", () => {
|
|
|
31
30
|
expect(c.reminderThresholds).toEqual([100_000, 200_000, 300_000]);
|
|
32
31
|
|
|
33
32
|
const c2 = normalizeSmartContextConfig({ reminderThresholds: [] });
|
|
34
|
-
expect(c2.reminderThresholds).toEqual([
|
|
33
|
+
expect(c2.reminderThresholds).toEqual([400_000, 500_000, 600_000]);
|
|
35
34
|
});
|
|
36
35
|
|
|
37
36
|
it("excludedModels 只留含 / 的字符串条目并去重(精准匹配要求完整 provider/modelId)", () => {
|
|
@@ -173,9 +172,6 @@ describe("estimateShadowedTokens 口径契约(D2:与 pi estimateTokens 同
|
|
|
173
172
|
});
|
|
174
173
|
});
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* startupConfig 声明守护测试:断言 package.json `
|
|
2
|
+
* startupConfig 声明守护测试:断言 package.json `taiji.startupConfig` 声明的
|
|
3
3
|
* content 与代码 DEFAULT_SMART_CONTEXT_CONFIG 常量深相等,防止两侧任一改动未同步(漂移)。
|
|
4
4
|
* 机制:runtime 启动序列统一 ensure(extension-startup-config.ts),声明即首建内容。
|
|
5
5
|
*
|
|
@@ -13,7 +13,7 @@ import { describe, expect, it } from "vitest";
|
|
|
13
13
|
|
|
14
14
|
import { DEFAULT_SMART_CONTEXT_CONFIG } from "../pure.js";
|
|
15
15
|
|
|
16
|
-
/** package.json `
|
|
16
|
+
/** package.json `taiji.startupConfig` 声明条目的最小形状(守护断言用)。 */
|
|
17
17
|
interface StartupConfigEntry {
|
|
18
18
|
path: string;
|
|
19
19
|
content: unknown;
|
|
@@ -21,11 +21,11 @@ interface StartupConfigEntry {
|
|
|
21
21
|
|
|
22
22
|
const pkg = JSON.parse(
|
|
23
23
|
readFileSync(join(fileURLToPath(import.meta.url), "../../../package.json"), "utf-8"),
|
|
24
|
-
) as { "
|
|
24
|
+
) as { "taiji"?: { startupConfig?: StartupConfigEntry[] } };
|
|
25
25
|
|
|
26
26
|
describe("startupConfig 声明守护", () => {
|
|
27
27
|
it("声明 content 与代码 DEFAULT_SMART_CONTEXT_CONFIG 深相等", () => {
|
|
28
|
-
const entry = pkg["
|
|
28
|
+
const entry = pkg["taiji"]?.startupConfig?.find(
|
|
29
29
|
(e: StartupConfigEntry) => e.path === "config/smart-context-ext-config.json",
|
|
30
30
|
);
|
|
31
31
|
expect(entry).toBeDefined();
|
|
@@ -38,7 +38,7 @@ function makePi(): { pi: ExtensionAPI & { sendUserMessage: ReturnType<typeof vi.
|
|
|
38
38
|
function makeCtx(compactImpl?: (options: CompactOpts) => void): ExtensionContext {
|
|
39
39
|
return {
|
|
40
40
|
model: { provider: "zai", id: "glm" },
|
|
41
|
-
getContextUsage: () => ({ tokens:
|
|
41
|
+
getContextUsage: () => ({ tokens: 450_000, contextWindow: 1_000_000 }),
|
|
42
42
|
sessionManager: { getEntries: () => [] },
|
|
43
43
|
compact: compactImpl ?? ((options: CompactOpts) => {
|
|
44
44
|
options.onComplete({
|
package/src/compact-handler.ts
CHANGED
|
@@ -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";
|
|
@@ -57,7 +58,7 @@ export function createTakeoverState(): TakeoverState {
|
|
|
57
58
|
/** 统一 logger(@zhushanwen/pi-extension-logger:debug 文件日志 / warn 走 appendEntry 持久化)。 */
|
|
58
59
|
const logger = createLogger("smart-context");
|
|
59
60
|
|
|
60
|
-
/** 开发调试日志(
|
|
61
|
+
/** 开发调试日志(TAIJI_AGENT_DEBUG=1 时写文件,默认 no-op;logging-conventions 统一通道)。 */
|
|
61
62
|
export function debugLog(message: string): void {
|
|
62
63
|
logger.debug(message);
|
|
63
64
|
}
|
|
@@ -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:
|
|
323
|
+
warnLog("takeover error, falling back to native", { error: toErrorMessage(error) });
|
|
323
324
|
return {}; // D7 回退
|
|
324
325
|
}
|
|
325
326
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zhushanwen/pi-smart-context 入口:事件接线 + 门控。
|
|
3
3
|
*
|
|
4
|
-
* 设计文档:docs/extensions/smart-context/design.md
|
|
5
4
|
* - session_start:session 级闭包状态重建(规范 Session 隔离:fired 档位/熔断计数不跨 session)
|
|
6
5
|
* - subagent 进程(R6):不注册工具、不提醒(宁缺勿污)
|
|
7
6
|
* - session_before_compact:双模式接管(compact-handler)
|
|
@@ -11,7 +10,7 @@
|
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
12
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import { guardStaleCtx, toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
13
|
+
import { guardStaleCtx, isSubagentProcess, toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
15
14
|
import { setPiHandle } from "@zhushanwen/pi-extension-logger";
|
|
16
15
|
|
|
17
16
|
import {
|
|
@@ -27,7 +26,6 @@ import {
|
|
|
27
26
|
findCrossedThresholds,
|
|
28
27
|
getCurrentModelId,
|
|
29
28
|
isGatingActive,
|
|
30
|
-
isSubagentProcess,
|
|
31
29
|
loadSmartContextConfig,
|
|
32
30
|
type EntryLike,
|
|
33
31
|
} from "./pure.js";
|
|
@@ -63,7 +61,8 @@ export default function smartContextExtension(pi: ExtensionAPI): void {
|
|
|
63
61
|
// 日志通道注入(extension-logger 两阶段初始化:工厂拿 pi → setPiHandle)
|
|
64
62
|
setPiHandle(pi);
|
|
65
63
|
|
|
66
|
-
// R6:subagent
|
|
64
|
+
// R6:subagent 子进程不注册工具、不提醒(宁缺勿污;TAIJI_AGENT_SUBAGENT 标记——
|
|
65
|
+
// ext-guards isSubagentProcess,ext-simplify-17 D4 重锚)
|
|
67
66
|
if (isSubagentProcess()) {
|
|
68
67
|
debugLog("subagent process detected, staying inert");
|
|
69
68
|
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:
|
|
131
|
+
return { ok: false, error: toErrorMessage(error) };
|
|
131
132
|
}
|
|
132
133
|
}
|
package/src/pure.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* 纯函数层:配置 schema / 加载 / 门控判定 / 阈值检查 / 摘要后处理。
|
|
3
3
|
*
|
|
4
4
|
* 无副作用(fs 读取经 llm-shared loadConfig 的缓存封装),全部可单测。
|
|
5
|
-
* 设计文档:docs/extensions/smart-context/design.md(D5 门控矩阵 / D6 阈值保护 / D8 配置 schema)。
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import { estimateTokens } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { isRecord } from "@zhushanwen/pi-ext-guards";
|
|
9
9
|
import type { ModelSelector } from "@zhushanwen/pi-llm-shared";
|
|
10
|
-
import { loadConfig } from "@zhushanwen/pi-llm-shared";
|
|
10
|
+
import { loadConfig, normalizeModelSelector } from "@zhushanwen/pi-llm-shared";
|
|
11
11
|
|
|
12
12
|
// ──────────────────────── 配置 schema(D8) ────────────────────────
|
|
13
13
|
|
|
@@ -18,9 +18,9 @@ export const CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
|
18
18
|
/** 提醒阈值最大档数(3 档)。 */
|
|
19
19
|
const MAX_THRESHOLD_TIERS = 3;
|
|
20
20
|
|
|
21
|
-
/** 3 档提醒阈值默认值(token 绝对数):
|
|
22
|
-
const DEFAULT_REMINDER_THRESHOLD_TIER_1_TOKENS =
|
|
23
|
-
const DEFAULT_REMINDER_THRESHOLD_TIER_2_TOKENS =
|
|
21
|
+
/** 3 档提醒阈值默认值(token 绝对数):400K / 500K / 600K。 */
|
|
22
|
+
const DEFAULT_REMINDER_THRESHOLD_TIER_1_TOKENS = 400_000;
|
|
23
|
+
const DEFAULT_REMINDER_THRESHOLD_TIER_2_TOKENS = 500_000;
|
|
24
24
|
const DEFAULT_REMINDER_THRESHOLD_TIER_3_TOKENS = 600_000;
|
|
25
25
|
const DEFAULT_REMINDER_THRESHOLDS: readonly number[] = [
|
|
26
26
|
DEFAULT_REMINDER_THRESHOLD_TIER_1_TOKENS,
|
|
@@ -59,13 +59,8 @@ export function normalizeSmartContextConfig(raw: unknown): SmartContextConfig {
|
|
|
59
59
|
|
|
60
60
|
const enabled = typeof r.enabled === "boolean" ? r.enabled : base.enabled;
|
|
61
61
|
|
|
62
|
-
const rawModel = typeof r.compactModel === "object" && r.compactModel !== null
|
|
63
|
-
? (r.compactModel as Record<string, unknown>)
|
|
64
|
-
: null;
|
|
65
62
|
const compactModel: ModelSelector =
|
|
66
|
-
|
|
67
|
-
? { type: "ref", ref: rawModel.ref }
|
|
68
|
-
: { type: "ref", ref: "" };
|
|
63
|
+
normalizeModelSelector(r.compactModel) ?? { type: "ref", ref: "" };
|
|
69
64
|
|
|
70
65
|
const rawThresholds = Array.isArray(r.reminderThresholds)
|
|
71
66
|
? r.reminderThresholds
|
|
@@ -249,16 +244,6 @@ export function buildReinjectSection(contents: ReadonlyArray<{ path: string; con
|
|
|
249
244
|
return `\n\n<recently-read-files>\n${parts.join("\n\n")}\n</recently-read-files>`;
|
|
250
245
|
}
|
|
251
246
|
|
|
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
247
|
// ──────────────────────── session entries 统计(D13 纯函数) ────────────────────────
|
|
263
248
|
|
|
264
249
|
/** sessionManager entries 的宽松形状(降智计数)。 */
|
|
@@ -271,11 +256,6 @@ export function countCompactions(entries: ReadonlyArray<EntryLike>): number {
|
|
|
271
256
|
return entries.filter((e) => e.type === "compaction").length;
|
|
272
257
|
}
|
|
273
258
|
|
|
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
259
|
/**
|
|
280
260
|
* 保留段已 Read 的文件集合(D13-11 去重:重注入跳过保留段已有的 Read 结果)。
|
|
281
261
|
* 保留段 = 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 : {};
|
|
@@ -150,8 +145,8 @@ export function registerCompactContextTool(
|
|
|
150
145
|
const config = loadSmartContextConfig();
|
|
151
146
|
if (!gating.active) {
|
|
152
147
|
const reason = config.enabled
|
|
153
|
-
? `当前模型 ${gating.modelId} 已配置为排除(smart-context excludedModels),压缩工具不可用。可在
|
|
154
|
-
: `smart-context 已禁用。可在
|
|
148
|
+
? `当前模型 ${gating.modelId} 已配置为排除(smart-context excludedModels),压缩工具不可用。可在 taiji 设置页或 smart-context-ext-config skill 中调整。`
|
|
149
|
+
: `smart-context 已禁用。可在 taiji 设置页开启,或经 smart-context-ext-config skill 修改配置。`;
|
|
155
150
|
throw new Error(reason);
|
|
156
151
|
}
|
|
157
152
|
|
|
@@ -190,7 +185,7 @@ export function registerCompactContextTool(
|
|
|
190
185
|
: "未知";
|
|
191
186
|
const showHint = compactionCount + 1 >= DEGRADATION_HINT_MIN_COMPACTIONS;
|
|
192
187
|
const lines = [
|
|
193
|
-
`[smart-context] 压缩完成。模式:${resultMode}${fellBack ? "(压缩模型不可用,已回退当前模型——请检查配置:
|
|
188
|
+
`[smart-context] 压缩完成。模式:${resultMode}${fellBack ? "(压缩模型不可用,已回退当前模型——请检查配置:taiji 设置页或 smart-context-ext-config skill)" : ""}。`,
|
|
194
189
|
`压缩前 ${formatK(result.tokensBefore ?? 0)} tokens → 压缩后约 ${formatK(result.estimatedTokensAfter ?? 0)} tokens;摘要生成成本:${cost}。`,
|
|
195
190
|
showHint ? buildDegradationHintLine() : "",
|
|
196
191
|
].filter((l) => l !== "");
|
package/vitest.config.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { taijiTestConfig } from "../../../test-guard/factory.ts"
|
|
2
2
|
|
|
3
|
-
export default
|
|
3
|
+
export default taijiTestConfig({
|
|
4
4
|
test: {
|
|
5
|
-
reporters: ["default", "junit"],
|
|
6
|
-
outputFile: { junit: "./test-results/vitest-junit.xml" },
|
|
7
5
|
include: ["src/__tests__/**/*.test.ts"],
|
|
8
6
|
},
|
|
9
7
|
});
|