dsh-context-compression-improved 0.2.1 → 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/.gitattributes +1 -1
- package/.github/workflows/ci.yml +1 -7
- package/CHANGELOG.ja.md +18 -21
- package/CHANGELOG.ko.md +17 -21
- package/CHANGELOG.md +16 -17
- package/CHANGELOG.zh.md +15 -18
- package/CONTRIBUTING.md +1 -1
- package/README.ja.md +1 -20
- package/README.ko.md +12 -30
- package/README.md +1 -19
- package/README.zh.md +5 -21
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/docs/compat-0.1.5-report.md +77 -0
- package/docs/installation.ja.md +6 -4
- package/docs/installation.ko.md +9 -7
- package/docs/installation.md +3 -1
- package/docs/installation.zh.md +3 -2
- package/docs/repair-log.md +54 -33
- package/eslint.config.js +2 -2
- package/package.json +56 -36
- package/packages/selector/README.md +7 -12
- package/packages/selector/README.zh.md +6 -13
- package/packages/selector/THIRD_PARTY_NOTICES.md +2 -8
- package/packages/selector/cordis.patch.yml +7 -5
- package/packages/selector/dsh.plugin.json +2 -2
- package/packages/selector/lib/client.d.ts +31 -5
- package/packages/selector/lib/client.js +100 -82
- package/packages/selector/lib/index.d.ts +8 -20
- package/packages/selector/lib/index.js +9 -14
- package/packages/selector/lib/invariant.js +4 -4
- package/packages/selector/lib/pruner.d.ts +13 -4
- package/packages/selector/lib/pruner.js +91 -46
- package/packages/selector/lib/tail-trim.js +13 -4
- package/packages/selector/package.json +23 -23
- package/packages/selector/screenshots.json +1 -4
- package/packages/selector/src/client/CompressionProfileControls.tsx +5 -8
- package/packages/selector/src/client/CompressionProfileSelector.tsx +9 -5
- package/packages/selector/src/client/EstimatorControls.tsx +28 -32
- package/packages/selector/src/client/index.ts +101 -63
- package/packages/selector/src/client/locales.ts +0 -2
- package/packages/selector/src/client/preset-options.ts +49 -35
- package/packages/selector/src/client/settings-section.tsx +4 -6
- package/packages/selector/src/deepseek-v4-tokenizer.ts +0 -5
- package/packages/selector/src/index.ts +41 -46
- package/packages/selector/src/invariant.ts +5 -5
- package/packages/selector/src/pruner.ts +67 -36
- package/packages/selector/src/runtime/measurement.ts +6 -2
- package/packages/selector/src/runtime/session-events.ts +14 -1
- package/packages/selector/src/runtime/tail-trim.ts +4 -4
- package/packages/selector/src/runtime/tokenpilot/estimator.ts +34 -3
- package/packages/selector/src/runtime/types.ts +1 -1
- package/packages/selector/tests/auto-compact.client.spec.tsx +0 -13
- package/packages/selector/tests/built/client-artifact.spec.ts +5 -1
- package/packages/selector/tests/code-skeleton.client.spec.ts +2 -1
- package/packages/selector/tests/custom-contract.client.spec.ts +2 -1
- package/packages/selector/tests/estimator-channel.client.spec.tsx +16 -11
- package/packages/selector/tests/estimator-route-registration.host.spec.ts +12 -0
- package/packages/selector/tests/host-preset-overlay.host.spec.ts +6 -4
- package/packages/selector/tests/preset-options-write.client.spec.ts +87 -79
- package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +17 -12
- package/packages/selector/tests/preset-overlay.host.spec.ts +5 -2
- package/packages/selector/tests/profiles.client.spec.tsx +3 -3
- package/packages/selector/tests/public/package-contract.client.spec.ts +27 -3
- package/packages/selector/tests/runtime/public/public-runtime.spec.ts +103 -81
- package/packages/selector/tests/runtime/session-events.spec.ts +1 -1
- package/packages/selector/tests/settings-seat.client.spec.ts +86 -0
- package/packages/selector/tests/standing-generation.host.spec.ts +88 -71
- package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +24 -21
- package/pnpm-workspace.yaml +52 -5
- package/scripts/capture-profile-baseline.mjs +42 -0
- package/scripts/generate-tokenizer-fixtures.py +5 -5
- package/scripts/generate-vision-fixtures.py +2 -2
- package/scripts/{packed-components-smoke.ts → packed-components-smoke.mjs} +116 -119
- package/scripts/{packed-install-e2e.ts → packed-install-e2e.mjs} +188 -160
- package/scripts/{verify-release.ts → verify-release.mjs} +70 -94
- package/tests/TEST_INVENTORY.md +6 -7
- package/scripts/capture-profile-baseline.ts +0 -80
- package/tsconfig.scripts.json +0 -13
- /package/{packages/selector/assets/screenshots → docs/assets}/context-compression-selector-profiles.jpg +0 -0
- /package/{packages/selector/assets/screenshots → docs/assets}/context-compression-selector-settings.png +0 -0
|
@@ -734,6 +734,9 @@ window.__ModuleLoader__.load({
|
|
|
734
734
|
* exactly that — so keep the heading and its anchor id in place and spend them
|
|
735
735
|
* on the reason plus the profile that unlocks the card. The gate itself is
|
|
736
736
|
* unchanged: no estimator control exists outside tokenpilot-inspired.
|
|
737
|
+
*
|
|
738
|
+
* Exported from the estimator card's own module so the gated card and its
|
|
739
|
+
* explanation cannot drift apart.
|
|
737
740
|
*/
|
|
738
741
|
function EstimatorInactiveNotice({ profile, t }) {
|
|
739
742
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -749,17 +752,17 @@ window.__ModuleLoader__.load({
|
|
|
749
752
|
})]
|
|
750
753
|
});
|
|
751
754
|
}
|
|
752
|
-
const
|
|
755
|
+
const ESTIMATOR_CATALOG_ROUTE = "/api/dsh-context-compression-improved/estimator-catalog";
|
|
753
756
|
/**
|
|
754
757
|
* TokenPilot-inspired estimator channel card. Shown only while the
|
|
755
758
|
* tokenpilot-inspired profile is selected, and split by channel:
|
|
756
759
|
*
|
|
757
|
-
* - `host` reuses the providers and credentials already configured in
|
|
758
|
-
* through the Harness `llm` service, so this card names a provider and a
|
|
760
|
+
* - `host` reuses the providers and credentials the user already configured in
|
|
761
|
+
* DSH through the Harness `llm` service, so this card names a provider and a
|
|
759
762
|
* model and accepts NO API key — the key field belongs to the direct channel
|
|
760
763
|
* alone.
|
|
761
|
-
* - `direct` talks to a native OpenAI-compatible endpoint, the only
|
|
762
|
-
*
|
|
764
|
+
* - `direct` talks to a native OpenAI-compatible endpoint, which is the only
|
|
765
|
+
* channel that carries its own base URL and write-only key.
|
|
763
766
|
*
|
|
764
767
|
* The whole card is advisory: an unconfigured or failing endpoint keeps every
|
|
765
768
|
* consumer on its rule-only fallback.
|
|
@@ -775,15 +778,17 @@ window.__ModuleLoader__.load({
|
|
|
775
778
|
if (mode !== "host") return;
|
|
776
779
|
let alive = true;
|
|
777
780
|
let attempts = 0;
|
|
778
|
-
const load = async (
|
|
779
|
-
|
|
780
|
-
const response = await fetch(
|
|
781
|
-
|
|
782
|
-
} catch {
|
|
781
|
+
const load = async () => {
|
|
782
|
+
try {
|
|
783
|
+
const response = await fetch(ESTIMATOR_CATALOG_ROUTE, { headers: { "cache-control": "no-cache" } });
|
|
784
|
+
return response.ok ? await response.json() : void 0;
|
|
785
|
+
} catch {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
783
788
|
};
|
|
784
789
|
const tick = () => {
|
|
785
790
|
attempts += 1;
|
|
786
|
-
load(
|
|
791
|
+
load().then((body) => {
|
|
787
792
|
if (!alive) return;
|
|
788
793
|
if (body !== void 0 && (body.providers?.length ?? 0) > 0) {
|
|
789
794
|
setCatalog(body);
|
|
@@ -935,7 +940,6 @@ window.__ModuleLoader__.load({
|
|
|
935
940
|
type: "text",
|
|
936
941
|
value: model,
|
|
937
942
|
disabled,
|
|
938
|
-
placeholder: t("estimator.model.placeholder"),
|
|
939
943
|
onChange: (event) => {
|
|
940
944
|
setModel(event.currentTarget.value);
|
|
941
945
|
},
|
|
@@ -1004,12 +1008,8 @@ window.__ModuleLoader__.load({
|
|
|
1004
1008
|
function ContextCompressionSettingsSection(props) {
|
|
1005
1009
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsCompressionProfileControls, { ...props });
|
|
1006
1010
|
}
|
|
1007
|
-
function SettingsCompressionProfileControls({ useCompression,
|
|
1011
|
+
function SettingsCompressionProfileControls({ useCompression, select, saveCustom, resetCustom, saveAutoCompact, saveCodeSkeleton, savePresetOptions, t }) {
|
|
1008
1012
|
const state = useCompression((snapshot) => snapshot);
|
|
1009
|
-
const selectorAvailable = useSessions((sessions) => {
|
|
1010
|
-
const current = sessions.current;
|
|
1011
|
-
return current === void 0 ? void 0 : sessions.byId[current]?.agentPreset;
|
|
1012
|
-
}) !== "minimal";
|
|
1013
1013
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
1014
1014
|
const [saveError, setSaveError] = (0, react.useState)(null);
|
|
1015
1015
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
@@ -1021,7 +1021,7 @@ window.__ModuleLoader__.load({
|
|
|
1021
1021
|
if (state.status === "unavailable") return null;
|
|
1022
1022
|
const busy = state.status === "loading" || saving;
|
|
1023
1023
|
const selectProfile = (profile) => {
|
|
1024
|
-
if (!
|
|
1024
|
+
if (!state.writable || profile === current) return;
|
|
1025
1025
|
setSaveError(null);
|
|
1026
1026
|
setSaving(true);
|
|
1027
1027
|
select(profile).then(() => {
|
|
@@ -1052,7 +1052,7 @@ window.__ModuleLoader__.load({
|
|
|
1052
1052
|
className: _dsh_context_compression_css_466eb745356d_CompressionProfileSelector_module_css_default.settingsDescription,
|
|
1053
1053
|
children: t("settings.description")
|
|
1054
1054
|
}),
|
|
1055
|
-
|
|
1055
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1056
1056
|
className: _dsh_context_compression_css_466eb745356d_CompressionProfileSelector_module_css_default.profileGrid,
|
|
1057
1057
|
"aria-label": t("label"),
|
|
1058
1058
|
children: COMPRESSION_PROFILES.map((profile) => {
|
|
@@ -1080,21 +1080,17 @@ window.__ModuleLoader__.load({
|
|
|
1080
1080
|
})]
|
|
1081
1081
|
}, profile);
|
|
1082
1082
|
})
|
|
1083
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1084
|
-
className: _dsh_context_compression_css_466eb745356d_CompressionProfileSelector_module_css_default.unavailable,
|
|
1085
|
-
role: "status",
|
|
1086
|
-
children: t("status.minimalUnavailable")
|
|
1087
1083
|
}),
|
|
1088
1084
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoCompactThresholdControls, {
|
|
1089
1085
|
value: state.value?.autoCompact?.thresholdPercent ?? AUTO_COMPACT_THRESHOLD_LIMITS.default,
|
|
1090
|
-
disabled: busy || !state.writable ||
|
|
1086
|
+
disabled: busy || !state.writable || false,
|
|
1091
1087
|
save: saveAutoCompact,
|
|
1092
1088
|
settle,
|
|
1093
1089
|
t
|
|
1094
1090
|
}),
|
|
1095
1091
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodeSkeletonControls, {
|
|
1096
1092
|
value: state.value?.codeSkeleton?.enabled ?? false,
|
|
1097
|
-
disabled: busy || !state.writable ||
|
|
1093
|
+
disabled: busy || !state.writable || false,
|
|
1098
1094
|
save: saveCodeSkeleton,
|
|
1099
1095
|
settle,
|
|
1100
1096
|
t
|
|
@@ -1104,7 +1100,7 @@ window.__ModuleLoader__.load({
|
|
|
1104
1100
|
t
|
|
1105
1101
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EstimatorControls, {
|
|
1106
1102
|
options: state.value?.presetOptions ?? {},
|
|
1107
|
-
disabled: busy || !state.writable ||
|
|
1103
|
+
disabled: busy || !state.writable || false,
|
|
1108
1104
|
save: savePresetOptions,
|
|
1109
1105
|
settle,
|
|
1110
1106
|
t
|
|
@@ -1113,7 +1109,7 @@ window.__ModuleLoader__.load({
|
|
|
1113
1109
|
className: _dsh_context_compression_css_466eb745356d_CompressionProfileSelector_module_css_default.pricing,
|
|
1114
1110
|
children: t("pricing.disclosure")
|
|
1115
1111
|
}),
|
|
1116
|
-
current !== "custom" || draft === null ||
|
|
1112
|
+
current !== "custom" || draft === null || false ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomPolicyEditor, {
|
|
1117
1113
|
value: draft,
|
|
1118
1114
|
disabled: busy || !state.writable,
|
|
1119
1115
|
setValue: setDraft,
|
|
@@ -1182,7 +1178,6 @@ window.__ModuleLoader__.load({
|
|
|
1182
1178
|
"estimator.description": "辅助小模型零样本判断旧文件读取是否仍有引用价值,仅建议性加速历史清理;未配置或失败时自动退回纯规则通道,不影响主流程。",
|
|
1183
1179
|
"estimator.mode": "通道",
|
|
1184
1180
|
"estimator.mode.off": "关闭(纯规则)",
|
|
1185
|
-
"estimator.followHost": "跟随宿主默认模型",
|
|
1186
1181
|
"estimator.mode.host": "宿主模型(复用已配置供应商)",
|
|
1187
1182
|
"estimator.mode.direct": "直连 OpenAI 兼容端点",
|
|
1188
1183
|
"estimator.inactive": "估计器只随「TokenPilot 启发模式」提供:该模式的预设选项(去重指针、摘要定位块、读取状态语义与估计器通道)不会合并进其他 Profile,因此当前 Profile「{profile}」下没有可配置的估计器通道。选择「TokenPilot 启发模式」后,本区块会出现「通道」选择,可复用已配置供应商(宿主模型)或直连 OpenAI 兼容端点。",
|
|
@@ -1279,7 +1274,6 @@ window.__ModuleLoader__.load({
|
|
|
1279
1274
|
"estimator.description": "A small auxiliary model zero-shots whether old file reads are still likely referenced, advisory-only for history aging; unconfigured or failing endpoints fall back to rule-only behavior.",
|
|
1280
1275
|
"estimator.mode": "Channel",
|
|
1281
1276
|
"estimator.mode.off": "Off (rule-only)",
|
|
1282
|
-
"estimator.followHost": "Follow the host default model",
|
|
1283
1277
|
"estimator.mode.host": "Host model (reuse configured providers)",
|
|
1284
1278
|
"estimator.mode.direct": "Direct OpenAI-compatible endpoint",
|
|
1285
1279
|
"estimator.inactive": "The estimator ships only with the TokenPilot-inspired profile: that profile’s preset options (dedupe pointers, summary locators, read-state semantics, and the estimator channel) are never merged into another profile, so the current profile “{profile}” has no estimator channel to configure. Select TokenPilot-inspired and this section gains a Channel choice — reuse configured providers (host model) or a direct OpenAI-compatible endpoint.",
|
|
@@ -1357,6 +1351,8 @@ window.__ModuleLoader__.load({
|
|
|
1357
1351
|
};
|
|
1358
1352
|
//#endregion
|
|
1359
1353
|
//#region src/client/preset-options.ts
|
|
1354
|
+
/** The namespace key holding every tokenpilot-inspired sub-capability override. */
|
|
1355
|
+
const PRESET_OPTIONS_KEY = "presetOptions";
|
|
1360
1356
|
/** Every field a patch may address, in the schema's own order. */
|
|
1361
1357
|
const PRESET_OPTION_KEYS = [
|
|
1362
1358
|
"dedupeToolResults",
|
|
@@ -1371,38 +1367,55 @@ window.__ModuleLoader__.load({
|
|
|
1371
1367
|
"estimatorTimeoutMs"
|
|
1372
1368
|
];
|
|
1373
1369
|
/**
|
|
1374
|
-
*
|
|
1370
|
+
* Plan the path ops one patch needs against the section currently stored.
|
|
1371
|
+
*
|
|
1372
|
+
* Fields already at their requested value produce no op, so a re-selected
|
|
1373
|
+
* value neither rewrites the document nor reports a save the Host never
|
|
1374
|
+
* committed.
|
|
1375
1375
|
*
|
|
1376
1376
|
* @param current - the decoded `presetOptions` section, when one is stored.
|
|
1377
1377
|
* @param patch - the fields to write or clear.
|
|
1378
|
-
* @returns the
|
|
1378
|
+
* @returns the ordered ops, empty when the patch changes nothing.
|
|
1379
1379
|
*/
|
|
1380
|
-
function
|
|
1381
|
-
const
|
|
1382
|
-
const
|
|
1380
|
+
function planPresetOptionsOps(current, patch) {
|
|
1381
|
+
const stored = current;
|
|
1382
|
+
const ops = [];
|
|
1383
1383
|
for (const key of PRESET_OPTION_KEYS) {
|
|
1384
|
-
|
|
1385
|
-
if (stored !== void 0) merged[key] = stored;
|
|
1386
|
-
}
|
|
1387
|
-
for (const key of Object.keys(patch)) {
|
|
1384
|
+
if (!Object.hasOwn(patch, key)) continue;
|
|
1388
1385
|
const value = patch[key];
|
|
1389
|
-
|
|
1390
|
-
|
|
1386
|
+
const path = [PRESET_OPTIONS_KEY, key];
|
|
1387
|
+
if (value === void 0) {
|
|
1388
|
+
if (stored?.[key] !== void 0) ops.push({
|
|
1389
|
+
op: "unset",
|
|
1390
|
+
path
|
|
1391
|
+
});
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
if (stored?.[key] === value) continue;
|
|
1395
|
+
ops.push({
|
|
1396
|
+
op: "set",
|
|
1397
|
+
path,
|
|
1398
|
+
value
|
|
1399
|
+
});
|
|
1391
1400
|
}
|
|
1392
|
-
return
|
|
1401
|
+
return ops;
|
|
1393
1402
|
}
|
|
1394
1403
|
/**
|
|
1395
|
-
*
|
|
1396
|
-
* the document nor reports a save the Host never committed.
|
|
1404
|
+
* Test whether one resolved settings document already carries every planned op.
|
|
1397
1405
|
*
|
|
1398
|
-
* @param
|
|
1399
|
-
* @param
|
|
1400
|
-
* @returns whether every
|
|
1406
|
+
* @param settings - the resolved namespace section after a write.
|
|
1407
|
+
* @param ops - the ops that write submitted.
|
|
1408
|
+
* @returns whether every named field holds its requested state.
|
|
1401
1409
|
*/
|
|
1402
|
-
function
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1410
|
+
function presetOptionsOpsAccepted(presetOptions, ops) {
|
|
1411
|
+
if (ops.length === 0) return true;
|
|
1412
|
+
if (presetOptions === void 0) return false;
|
|
1413
|
+
const stored = presetOptions;
|
|
1414
|
+
return ops.every((op) => {
|
|
1415
|
+
const key = op.path[op.path.length - 1];
|
|
1416
|
+
if (key === void 0) return false;
|
|
1417
|
+
return op.op === "set" ? stored[key] === op.value : stored[key] === void 0;
|
|
1418
|
+
});
|
|
1406
1419
|
}
|
|
1407
1420
|
//#endregion
|
|
1408
1421
|
//#region src/client/index.ts
|
|
@@ -1417,42 +1430,47 @@ window.__ModuleLoader__.load({
|
|
|
1417
1430
|
return left.version === right.version && left.unit === right.unit && left.prefixPolicy === right.prefixPolicy && left.fresh.enabled === right.fresh.enabled && left.fresh.trigger === right.fresh.trigger && left.fresh.target === right.fresh.target && left.aggregate.enabled === right.aggregate.enabled && left.aggregate.trigger === right.aggregate.trigger && left.aggregate.target === right.aggregate.target && left.history.enabled === right.history.enabled && left.history.trigger === right.history.trigger && left.history.keepRecentToolCalls === right.history.keepRecentToolCalls && left.history.keepRecentTokens === right.history.keepRecentTokens && left.history.minReclaim === right.history.minReclaim && left.tailTrim.enabled === right.tailTrim.enabled && left.tailTrim.trigger === right.tailTrim.trigger;
|
|
1418
1431
|
}
|
|
1419
1432
|
function apply(ctx) {
|
|
1420
|
-
ctx.
|
|
1433
|
+
ctx.locale.register(NS, {
|
|
1421
1434
|
zh,
|
|
1422
1435
|
en
|
|
1423
|
-
}), "ui-context-compression: dictionaries");
|
|
1424
|
-
const scope = ctx.settingsScope.bind({
|
|
1425
|
-
namespace: NS,
|
|
1426
|
-
decode: decodeSettings
|
|
1427
1436
|
});
|
|
1428
|
-
const
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1437
|
+
const injected = () => {
|
|
1438
|
+
const scope = ctx.settingsScope.bind({
|
|
1439
|
+
namespace: NS,
|
|
1440
|
+
decode: decodeSettings
|
|
1441
|
+
});
|
|
1442
|
+
const writeAndConfirm = async (write, accepts) => {
|
|
1443
|
+
const beforeRevision = scope.getSnapshot().revision;
|
|
1444
|
+
await write();
|
|
1445
|
+
const after = scope.getSnapshot();
|
|
1446
|
+
if (after.status !== "ready" || after.value === void 0 || after.revision === beforeRevision || !accepts(after.value)) throw new Error("Context compression settings were not saved.");
|
|
1447
|
+
};
|
|
1448
|
+
return {
|
|
1449
|
+
hooks: { compression: scope },
|
|
1450
|
+
select: (profile) => writeAndConfirm(() => scope.set("profile", profile), (settings) => settings.profile === profile),
|
|
1451
|
+
saveCustom: (custom) => writeAndConfirm(() => scope.set("custom", custom), (settings) => isCustomCompressionPolicy(settings.custom) && sameCustomPolicy(settings.custom, custom)),
|
|
1452
|
+
resetCustom: () => writeAndConfirm(() => scope.set("custom", structuredClone(DEFAULT_CUSTOM_COMPRESSION_POLICY)), (settings) => isCustomCompressionPolicy(settings.custom) && sameCustomPolicy(settings.custom, DEFAULT_CUSTOM_COMPRESSION_POLICY)),
|
|
1453
|
+
saveAutoCompact: (thresholdPercent) => writeAndConfirm(() => scope.set("autoCompact", { thresholdPercent }), (settings) => settings.autoCompact.thresholdPercent === thresholdPercent),
|
|
1454
|
+
saveCodeSkeleton: (enabled) => writeAndConfirm(() => scope.set("codeSkeleton", { enabled }), (settings) => settings.codeSkeleton.enabled === enabled),
|
|
1455
|
+
savePresetOptions: (options) => {
|
|
1456
|
+
const ops = planPresetOptionsOps(scope.getSnapshot().value?.presetOptions, options);
|
|
1457
|
+
if (ops.length === 0) return Promise.resolve();
|
|
1458
|
+
return writeAndConfirm(() => scope.mutate(ops), (settings) => presetOptionsOpsAccepted(settings.presetOptions, ops));
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1433
1461
|
};
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
}
|
|
1447
|
-
});
|
|
1448
|
-
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1449
|
-
name: "settings.section",
|
|
1450
|
-
id: "context-compression",
|
|
1451
|
-
order: 17,
|
|
1452
|
-
label: () => ctx.locale.bind(NS)("nav"),
|
|
1453
|
-
locale: NS,
|
|
1454
|
-
inject: injected
|
|
1455
|
-
}, ContextCompressionSettingsSection));
|
|
1462
|
+
try {
|
|
1463
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1464
|
+
name: "settings.section",
|
|
1465
|
+
id: "context-compression",
|
|
1466
|
+
order: 17,
|
|
1467
|
+
label: () => ctx.locale.bind(NS)("nav"),
|
|
1468
|
+
locale: NS,
|
|
1469
|
+
inject: injected
|
|
1470
|
+
}, ContextCompressionSettingsSection));
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
console.warn("[dsh-context-compression-improved] settings.section 注册失败(新宿主已收编):", error);
|
|
1473
|
+
}
|
|
1456
1474
|
}
|
|
1457
1475
|
//#endregion
|
|
1458
1476
|
exports.apply = apply;
|
|
@@ -6,27 +6,15 @@ interface Config {
|
|
|
6
6
|
/** Add the canonical compression stack to every non-Minimal preset. */
|
|
7
7
|
presetOverlay?: boolean;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* `inject: [webServer]`.
|
|
9
|
+
* Register the estimator-catalog HTTP route on this row.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* fiber is active (`state === 2`), never the caller's `inject` list. The one
|
|
20
|
-
* inject-gated path is the `ctx.webServer` **property** access, which this
|
|
21
|
-
* plugin never uses: `registerEstimatorCatalogRoute` uses `ctx.get` plus its
|
|
22
|
-
* own `ctx.inject(['webServer'], …)`.
|
|
23
|
-
*
|
|
24
|
-
* So the row-level `inject` is **not load-bearing**; it is kept as
|
|
25
|
-
* belt-and-braces so the route row stays inactive until `webServer` exists,
|
|
26
|
-
* and the flag keeps the route off standalone Bundle rows on profiles that
|
|
27
|
-
* have no web server. The internal two-channel registration is what actually
|
|
28
|
-
* covers both arrival orders. Do not cite this comment as a rule to the
|
|
29
|
-
* 0.1.5 replay — cite the host source.
|
|
11
|
+
* The standalone Bundle patch sets this on its own row (which declares
|
|
12
|
+
* `inject: [webServer]`), so profiles without a host web server never mount a
|
|
13
|
+
* row that could only announce a pending route. The row-level `inject` is
|
|
14
|
+
* belt-and-braces: `dsh-host-webserver.register` performs no authorization
|
|
15
|
+
* check and `ctx.get(name)` only asks whether the providing fiber is active,
|
|
16
|
+
* so the two-channel registration inside this function is what actually
|
|
17
|
+
* covers both arrival orders.
|
|
30
18
|
*/
|
|
31
19
|
estimatorCatalogRoute?: boolean;
|
|
32
20
|
}
|
|
@@ -455,6 +455,7 @@ function restoreMethod(presets, snapshot) {
|
|
|
455
455
|
}
|
|
456
456
|
//#endregion
|
|
457
457
|
//#region src/index.ts
|
|
458
|
+
const CONTEXT_COMPRESSION_NAMESPACE = CONTEXT_COMPRESSION_SETTINGS_NAMESPACE;
|
|
458
459
|
const ESTIMATOR_CATALOG_ROUTES = ["/endpoint/dsh-context-compression-improved/estimator-catalog", "/api/dsh-context-compression-improved/estimator-catalog"];
|
|
459
460
|
/**
|
|
460
461
|
* The one service the catalog route actually needs. `llm` and
|
|
@@ -463,21 +464,14 @@ const ESTIMATOR_CATALOG_ROUTES = ["/endpoint/dsh-context-compression-improved/es
|
|
|
463
464
|
*/
|
|
464
465
|
const ESTIMATOR_CATALOG_ROUTE_DEPS = ["webServer"];
|
|
465
466
|
function asWebServer(value) {
|
|
466
|
-
if (typeof value
|
|
467
|
-
return value;
|
|
467
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
468
|
+
return typeof value.register === "function" ? value : void 0;
|
|
468
469
|
}
|
|
469
470
|
/**
|
|
470
471
|
* Serve `GET /api/dsh-context-compression-improved/estimator-catalog` — the
|
|
471
472
|
* settings card's host-route dropdowns (live provider/model groups from the DSH
|
|
472
|
-
* `llm` service plus the effective selection).
|
|
473
|
-
* plugin
|
|
474
|
-
*
|
|
475
|
-
* (The isolation reason this placement was originally justified with — "a route
|
|
476
|
-
* registered there can never reach `webServer` across the isolation boundary" —
|
|
477
|
-
* is **unverified**: no `@deepseek-ai` package calls `.isolate(`, so there is no
|
|
478
|
-
* boundary to cross here. Top-level placement is still the right choice, for a
|
|
479
|
-
* reason that needs no framework rule: the route is host-wide, not
|
|
480
|
-
* per-pruner-instance. Don't promote the isolation wording into a rule.)
|
|
473
|
+
* `llm` service plus the effective selection). Best effort: without the
|
|
474
|
+
* `webServer` service the plugin keeps working, only the HTTP API is missing.
|
|
481
475
|
*
|
|
482
476
|
* The route gates on `webServer` **alone**. `llm` and `agentDefaultModel` only
|
|
483
477
|
* enrich the response and are resolved per request, so listing them here would
|
|
@@ -561,7 +555,6 @@ function registerEstimatorCatalogRoute(ctx) {
|
|
|
561
555
|
});
|
|
562
556
|
log("warn", "context-compression webServer not active yet — estimator catalog route pending: %s", ESTIMATOR_CATALOG_ROUTES.join(", "));
|
|
563
557
|
}
|
|
564
|
-
const CONTEXT_COMPRESSION_NAMESPACE = CONTEXT_COMPRESSION_SETTINGS_NAMESPACE;
|
|
565
558
|
/** Symbol properties reach the shared service target through Cordis proxies. */
|
|
566
559
|
const SHARED_SETTINGS = Symbol.for("dsh-context-compression-improved/settings-registration");
|
|
567
560
|
/** Loader validation for the standalone Bundle opt-in. */
|
|
@@ -586,7 +579,7 @@ function apply(ctx, config = {}) {
|
|
|
586
579
|
presetsCtx.effect(() => () => installation.dispose(), "contextCompressionSelector.agentPresets()");
|
|
587
580
|
});
|
|
588
581
|
} catch (error) {
|
|
589
|
-
console.error("context-compression apply
|
|
582
|
+
console.error("context-compression selector apply failed: %o", error);
|
|
590
583
|
throw error;
|
|
591
584
|
}
|
|
592
585
|
}
|
|
@@ -598,7 +591,9 @@ function apply(ctx, config = {}) {
|
|
|
598
591
|
function resolveAutoCompactThresholdPercent(presetsCtx) {
|
|
599
592
|
const raw = presetsCtx.get("settings")?.get(CONTEXT_COMPRESSION_NAMESPACE);
|
|
600
593
|
try {
|
|
601
|
-
|
|
594
|
+
const threshold = structuredClone(raw)?.autoCompact;
|
|
595
|
+
const value = typeof threshold?.thresholdPercent === "number" ? threshold.thresholdPercent : 80;
|
|
596
|
+
return Number.isFinite(value) && value >= 50 && value <= 90 ? value : 80;
|
|
602
597
|
} catch {
|
|
603
598
|
return 80;
|
|
604
599
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as validatePublishedTailTrim,
|
|
1
|
+
import { a as validatePublishedTailTrim, s as sessionEvents } from "./tail-trim.js";
|
|
2
2
|
//#region src/invariant.ts
|
|
3
3
|
const PACKAGE_NAME = "dsh-context-compression-improved-runtime";
|
|
4
4
|
/** Cordis companion plugin name. */
|
|
@@ -9,14 +9,14 @@ const inject = ["invariants"];
|
|
|
9
9
|
function resemblesCompanion(prune, event) {
|
|
10
10
|
if (event.type !== "tool/result" && event.type !== "user/message" || typeof event.surfaceOp !== "object") return false;
|
|
11
11
|
const sources = new Set(event.sourceEventSeqs ?? []);
|
|
12
|
-
return event.surfaceOp.
|
|
12
|
+
return event.surfaceOp.startSeq === prune.data.shadowedRange.start && event.surfaceOp.endSeq === prune.data.shadowedRange.end || prune.data.shadowedSeqs.some((seq) => sources.has(seq));
|
|
13
13
|
}
|
|
14
14
|
/** Validate one standard prune's immediately adjacent surface replacement. */
|
|
15
15
|
function validateCompanion(session, prune, event, fail) {
|
|
16
16
|
if (event.type !== "tool/result" && event.type !== "user/message" || typeof event.surfaceOp !== "object") fail(`compaction/prune at seq ${prune.seq} must be immediately followed by a replacement surface event`);
|
|
17
17
|
const { shadowedRange, shadowedSeqs } = prune.data;
|
|
18
|
-
if (shadowedSeqs.length === 0 || shadowedSeqs[0] !== shadowedRange.start || shadowedSeqs.at(-1) !== shadowedRange.end) fail(`compaction/prune at seq ${prune.seq} has a shadowed range inconsistent with shadowedSeqs`);
|
|
19
|
-
if (event.surfaceOp.
|
|
18
|
+
if (shadowedSeqs.length === 0 || shadowedSeqs[0] !== Number(shadowedRange.start) || shadowedSeqs.at(-1) !== Number(shadowedRange.end)) fail(`compaction/prune at seq ${prune.seq} has a shadowed range inconsistent with shadowedSeqs`);
|
|
19
|
+
if (event.surfaceOp.startSeq !== shadowedRange.start || event.surfaceOp.endSeq !== shadowedRange.end) fail(`replacement at seq ${event.seq} does not replace compaction/prune range ${shadowedRange.start}-${shadowedRange.end}`);
|
|
20
20
|
const sources = new Set(event.sourceEventSeqs ?? []);
|
|
21
21
|
const missing = shadowedSeqs.filter((seq) => !sources.has(seq));
|
|
22
22
|
if (missing.length > 0) fail(`replacement at seq ${event.seq} omits shadowed source seqs ${missing.join(", ")}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
|
-
import {
|
|
3
|
+
import { ContentBlock, ToolCallId } from "@deepseek-ai/dsh-llm";
|
|
4
4
|
import { Session } from "@deepseek-ai/dsh-session";
|
|
5
5
|
import { TokenMeasurement } from "@deepseek-ai/dsh-token-meter";
|
|
6
6
|
//#region src/runtime/tokenpilot/dedup.d.ts
|
|
@@ -279,7 +279,7 @@ interface PrunedEntry {
|
|
|
279
279
|
/** Newly appended compressed tool-result event. */
|
|
280
280
|
readonly replacementSeq: number;
|
|
281
281
|
/** Tool call shared by the original and replacement. */
|
|
282
|
-
readonly callId:
|
|
282
|
+
readonly callId: ToolCallId;
|
|
283
283
|
/** Reducer or aging strategy that produced the replacement. */
|
|
284
284
|
readonly reducer: string;
|
|
285
285
|
/** Pass stage that landed the replacement. */
|
|
@@ -582,13 +582,14 @@ declare class ToolResultPruner extends Service {
|
|
|
582
582
|
static Config: z<ToolResultPruneConfig>;
|
|
583
583
|
/** Consolidated per-session mutable state. */
|
|
584
584
|
readonly state: PrunerState;
|
|
585
|
+
/** 0.1.5-specific: per-session sets of already-audited native summary seqs. */
|
|
586
|
+
private readonly auditedNativeSummaries;
|
|
585
587
|
constructor(ctx: Context, config?: ToolResultPruneConfig);
|
|
586
588
|
/**
|
|
587
589
|
* Measure text content in Unicode code points; non-text blocks cost zero.
|
|
588
590
|
* @param blocks - tool-result content to measure.
|
|
589
591
|
* @returns total Unicode code points across text blocks.
|
|
590
592
|
*/
|
|
591
|
-
measureContent(blocks: readonly ContentBlock[]): number;
|
|
592
593
|
/**
|
|
593
594
|
* Apply the configured native head/middle/tail transform.
|
|
594
595
|
* @param blocks - original tool-result content.
|
|
@@ -675,10 +676,18 @@ declare class ToolResultPruner extends Service {
|
|
|
675
676
|
private hasRecoveryTool;
|
|
676
677
|
private auditHistoryEvaluation;
|
|
677
678
|
private auditComponent;
|
|
679
|
+
/** Emit the native-auto-compact audit for one summary manifest, once. */
|
|
680
|
+
private emitNativeSummaryAudit;
|
|
681
|
+
/**
|
|
682
|
+
* 0.1.5 commits Native auto-compact by reopening the Session with a seed
|
|
683
|
+
* log; seed events never reach the `session/event` firehose, so scan the
|
|
684
|
+
* snapshot for summary manifests the live listener could not observe.
|
|
685
|
+
*/
|
|
686
|
+
private scanForSeededNativeSummary;
|
|
678
687
|
private auditFailure;
|
|
679
688
|
private auditPublicationFailure;
|
|
680
689
|
private warnExactUnavailable;
|
|
681
690
|
private warnOnce;
|
|
682
691
|
}
|
|
683
692
|
//#endregion
|
|
684
|
-
export { AUTO_COMPACT_THRESHOLD_LIMITS, type AutoCompactSettings, COMPRESSION_PROFILES, CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, type CodeSkeletonSettings, type CompactionTokenView, type CompressionPolicy, type CompressionProfile, type ContextCompressionSettings, ContextCompressionSettingsSchema, type CustomCompressionBudget, type CustomCompressionPolicy, CustomCompressionPolicySchema, type CustomCompressionPolicyV1, type CustomCompressionPolicyV2, type CustomCompressionPolicyV3, type CustomCompressionUnit, type CustomHistoryPolicy, type CustomPolicyResolutionOptions, type CustomPrefixPolicy, type CustomTailTrimPolicy, DEFAULTS, DEFAULT_CUSTOM_COMPRESSION_POLICY, type HistoryMode, type MeasuredTokenSurfaceNode, PRUNE_MARKER, type PruneResult, type PruneSessionOptions, type PruneStage, type PrunedEntry, type
|
|
693
|
+
export { AUTO_COMPACT_THRESHOLD_LIMITS, type AutoCompactSettings, COMPRESSION_PROFILES, CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, type CodeSkeletonSettings, type CompactionTokenView, type CompressionPolicy, type CompressionProfile, type ContextCompressionSettings, ContextCompressionSettingsSchema, type CustomCompressionBudget, type CustomCompressionPolicy, CustomCompressionPolicySchema, type CustomCompressionPolicyV1, type CustomCompressionPolicyV2, type CustomCompressionPolicyV3, type CustomCompressionUnit, type CustomHistoryPolicy, type CustomPolicyResolutionOptions, type CustomPrefixPolicy, type CustomTailTrimPolicy, DEFAULTS, DEFAULT_CUSTOM_COMPRESSION_POLICY, type HistoryMode, type MeasuredTokenSurfaceNode, PRUNE_MARKER, type PruneResult, type PruneSessionOptions, type PruneStage, type PrunedEntry, type ToolResultPruneConfig, ToolResultPruner, ToolResultPruner as default, codePointLength, historicalPlaceholder, isCompressionProfile, isValidAutoCompactThresholdPercent, measureForCompaction, normalizeTerminalText, parseContextCompressionSettings, reduceFreshToolResult, resolveConfig, resolveCustomPolicy, resolvePolicy, verifyReduction };
|