dsh-compact-relay 0.5.1 → 0.5.3
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/CHANGELOG.md +128 -1
- package/README.en.md +3 -0
- package/README.md +29 -3
- package/TUTORIAL.md +2 -0
- package/lib/actions/better-compact.js +38 -4
- package/lib/actions/compact-run.js +54 -5
- package/lib/actions/favorite.js +435 -0
- package/lib/actions/force-archive.js +161 -0
- package/lib/actions/handoff.js +29 -23
- package/lib/actions/recover.js +17 -4
- package/lib/actions/state.js +20 -3
- package/lib/adapters/commands.js +36 -0
- package/lib/adapters/http.js +118 -1
- package/lib/client.js +1038 -106
- package/lib/config.js +8 -0
- package/lib/core/config-schema.js +25 -2
- package/lib/core/errors.js +9 -3
- package/lib/core/favorite-score.js +220 -0
- package/lib/core/favorites.js +167 -0
- package/lib/core/relay-record.js +131 -0
- package/lib/dsh/favorite-scorer.js +91 -0
- package/lib/dsh/favorite-watch.js +201 -0
- package/lib/dsh/favorites-file.js +349 -0
- package/lib/dsh/handoff-writer.js +1 -1
- package/lib/dsh/model-task.js +14 -10
- package/lib/dsh/relay.js +3 -0
- package/lib/dsh/route.js +19 -0
- package/lib/dsh/select-range.js +2 -2
- package/lib/dsh/sessions.js +19 -12
- package/lib/index.js +13 -2
- package/package.json +2 -2
package/lib/dsh/model-task.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* 两个任务不是同一种产物(一个压成六段摘要、一个写九段交接单,校验/重试规则
|
|
6
6
|
* 也不同),所以**不合并重试循环**;但下面四件事完全一样、以前各写一遍:
|
|
7
7
|
* 1. 前置:会话存在性 → 解析 provider/model 路由 → 窗口预算预检 → 推理档位;
|
|
8
|
-
* 2. cache 对齐参数:复用上次请求的 temperature/stop
|
|
9
|
-
*
|
|
8
|
+
* 2. cache 对齐参数:复用上次请求的 temperature/stop;reasoningEffort 由
|
|
9
|
+
* prepareModelCall 显式决定(2026-10-27 起优先沿用主请求的档位,见下);
|
|
10
10
|
* 3. 单次流式调用 + finishProblem 归一(超限/工具调用/中断);
|
|
11
11
|
* 4. 成功结果的公共元信息(provider/model/maxTokens/attempts/usage)。
|
|
12
12
|
*
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { actionError } from '../core/errors.js';
|
|
16
16
|
import { assertBudget, callStream, reasoningEffortFor } from './llm-call.js';
|
|
17
|
-
import { cacheControls, routeOf } from './route.js';
|
|
17
|
+
import { cacheControls, cacheEffortOf, routeOf } from './route.js';
|
|
18
18
|
import { finishProblem } from './stream.js';
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -33,7 +33,7 @@ import { finishProblem } from './stream.js';
|
|
|
33
33
|
* @param {string} input.code 动作错误码
|
|
34
34
|
* @param {string} input.noSessionMessage 会话不存在时的用户可见错误文案
|
|
35
35
|
* @param {string} input.noTargetMessage 拿不到 provider/model 时的用户可见错误文案
|
|
36
|
-
* @returns {Promise<{target:{provider:string, model:string}, reasoningEffort:
|
|
36
|
+
* @returns {Promise<{target:{provider:string, model:string}, reasoningEffort:string|undefined}>}
|
|
37
37
|
*/
|
|
38
38
|
export async function prepareModelCall({
|
|
39
39
|
ctx,
|
|
@@ -53,18 +53,22 @@ export async function prepareModelCall({
|
|
|
53
53
|
|
|
54
54
|
// 架构 A2:调模型前先做窗口预算预检;超窗直接报 budget,不白等一次失败请求。
|
|
55
55
|
await assertBudget({ ctx, target, inputTokens, maxTokens, label });
|
|
56
|
-
//
|
|
57
|
-
|
|
56
|
+
// 2026-10-27 P1 取证:reasoningEffort 会切换 provider 的 thinking 开关,是缓存前缀的
|
|
57
|
+
// 断点。摘要/交接单回放的是主请求的前缀,所以默认**沿用主请求的档位**(cache 对齐);
|
|
58
|
+
// 只有拿不到 header(无请求头 / 模型已切走)时,才退回旧策略:
|
|
59
|
+
// P2-5 的「模型目录支持 off 就 off」(省思考 token;这是从 P1 之前的行为,保留作兜底)。
|
|
60
|
+
const headerEffort = cacheEffortOf(session, target);
|
|
61
|
+
const reasoningEffort = headerEffort !== undefined ? headerEffort : await reasoningEffortFor(ctx, target);
|
|
58
62
|
|
|
59
63
|
return { target, reasoningEffort };
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
/**
|
|
63
|
-
* cache 对齐参数:复用上次请求的 temperature/stop
|
|
67
|
+
* cache 对齐参数:复用上次请求的 temperature/stop;reasoningEffort 不从这里拿。
|
|
64
68
|
*
|
|
65
|
-
* 2026-10-
|
|
66
|
-
*
|
|
67
|
-
*
|
|
69
|
+
* reasoningEffort 由 prepareModelCall 决定(2026-10-27 起优先与主请求一致,保证缓存命中)。
|
|
70
|
+
* 2026-10-19 的旧坑(max 思考把 maxTokens 吃光)已由「summary/handoff.maxTokens 提到
|
|
71
|
+
* 16K + 保留截断重试」兜住,不再是「一律 off」的理由。
|
|
68
72
|
*
|
|
69
73
|
* @param {object} session
|
|
70
74
|
* @param {{provider:string, model:string}} target
|
package/lib/dsh/relay.js
CHANGED
|
@@ -56,6 +56,9 @@ export function normalizeRelayRecord(data) {
|
|
|
56
56
|
scheme: RECORD_SCHEMES.has(data.scheme) ? data.scheme : null,
|
|
57
57
|
titleSeq: Number.isInteger(data.titleSeq) && data.titleSeq > 0 ? data.titleSeq : null,
|
|
58
58
|
historyTitle: typeof data.historyTitle === 'string' && data.historyTitle.length > 0 ? data.historyTitle : null,
|
|
59
|
+
// 0.6:链根会话 id(收藏文件按链根定位)。老记录**不写这个键**,读侧回退 parentId——
|
|
60
|
+
// 条件展开保持旧记录读取形状不变(0.3 记录字节契约)。
|
|
61
|
+
...(typeof data.rootId === 'string' && data.rootId.length > 0 ? { rootId: data.rootId } : {}),
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
|
package/lib/dsh/route.js
CHANGED
|
@@ -51,6 +51,25 @@ export function routeOf(ctx, session, agent) {
|
|
|
51
51
|
return asRoute(agent?.options);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 复用「上一条真实请求」的推理档位(cache 对齐用)。
|
|
57
|
+
*
|
|
58
|
+
* 2026-10-27 P1 取证:摘要/交接单回放的是上一条主请求的前缀,但 reasoningEffort
|
|
59
|
+
* 会切换 provider 的 thinking 开关;一旦与主请求不同,缓存前缀会在第一条
|
|
60
|
+
* assistant 消息的渲染处断开(生产实测命中 2–5%,隔离 A/B:off 74.0% vs max 96.9%)。
|
|
61
|
+
* 所以只要 header 里的 provider/model 与本次目标一致,就原样沿用它的 reasoningEffort。
|
|
62
|
+
*
|
|
63
|
+
* @param {object} session
|
|
64
|
+
* @param {{provider: string, model: string}} target
|
|
65
|
+
* @returns {string|undefined}
|
|
66
|
+
*/
|
|
67
|
+
export function cacheEffortOf(session, target) {
|
|
68
|
+
const config = session?.requestHeader?.()?.config;
|
|
69
|
+
if (config === undefined || config.provider !== target.provider || config.model !== target.model) return undefined;
|
|
70
|
+
return typeof config.reasoningEffort === 'string' && config.reasoningEffort.length > 0 ? config.reasoningEffort : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
/**
|
|
55
74
|
* 复用上次真实请求的采样控制参数,让附带调用尽量贴近它的前缀缓存。
|
|
56
75
|
* @param {object} session
|
package/lib/dsh/select-range.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* 工具调用必须配对(provider 的 toolPairingBalanced*)。
|
|
7
7
|
*/
|
|
8
8
|
import { compactionPolicy } from '../core/policy.js';
|
|
9
|
-
import { clipRangeByPins, latestPinnedSeqs } from '../core/pins.js';
|
|
10
9
|
import { selectFullRange } from '../core/ranges.js';
|
|
11
10
|
import { countSummaries, firstCompactableIdx, lastCheckpointSurfaceSeq, sessionLog } from './session-view.js';
|
|
12
11
|
|
|
@@ -33,7 +32,8 @@ export function selectCompactableRange({ session, nodes, engine, cfg, tokenOf })
|
|
|
33
32
|
|
|
34
33
|
// M6:锁定的消息必须保留——范围里碰到就截到最早那条锁定消息之前;
|
|
35
34
|
// 截空(锁定挡在最前面)就本次不压,由调用方提示 /unpin。
|
|
36
|
-
|
|
35
|
+
// 0.6:pin 已退役(改「收藏」),不再按锁定消息截断选段;收藏不改变压缩范围。
|
|
36
|
+
let range = candidate;
|
|
37
37
|
if (range === null) return null;
|
|
38
38
|
|
|
39
39
|
// clip 缩短后终点可能落在 tool-call 中间(原来平衡的 candidate 被截断):
|
package/lib/dsh/sessions.js
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
truncateTitleUtf8,
|
|
29
29
|
} from '../core/relay.js';
|
|
30
30
|
import { HANDOFF_PREAMBLE } from '../core/prompts.js';
|
|
31
|
+
import { childRelayRecordOf } from '../core/relay-record.js';
|
|
31
32
|
import { localIso } from './diag-counters.js';
|
|
32
33
|
import { cacheControls, routeOf } from './route.js';
|
|
33
34
|
import { relayRecordOf, writeRelayRecord } from './relay.js';
|
|
@@ -444,20 +445,26 @@ export async function createHandoffSession({ ctx, engine, session, agent, doc, t
|
|
|
444
445
|
// 0.5.0 补:交接单已由调用方落盘,path 一并写进子记录 —— 接力条在子会话里直接
|
|
445
446
|
// 拿到「查看交接单」的路径,父会话未加载/已归档时不再退化为「暂无交接单」。
|
|
446
447
|
// path 拿不到(旧调用方/失败)就不写这个键,不猜、不写空串。
|
|
448
|
+
// 0.5.2 S2:record 形状(含键顺序、不写父会话侧字段、条件 path)由
|
|
449
|
+
// core/relay-record 的唯一构造器负责,这里只传语义参数。
|
|
447
450
|
const titlePlan = plan ?? handoffTitlePlanOf(ctx, session);
|
|
448
451
|
const relayAt = Date.now();
|
|
449
|
-
const relayWrote = writeRelayRecord(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
452
|
+
const relayWrote = writeRelayRecord(
|
|
453
|
+
engine,
|
|
454
|
+
childSession,
|
|
455
|
+
childRelayRecordOf({
|
|
456
|
+
parentId: session.id,
|
|
457
|
+
childId,
|
|
458
|
+
ordinal: titlePlan.index,
|
|
459
|
+
baseTitle: titlePlan.baseTitle,
|
|
460
|
+
title,
|
|
461
|
+
scheme: titlePlan.scheme,
|
|
462
|
+
titleSeq,
|
|
463
|
+
path,
|
|
464
|
+
at: relayAt,
|
|
465
|
+
atIso: localIso(new Date(relayAt)),
|
|
466
|
+
}),
|
|
467
|
+
);
|
|
461
468
|
if (!relayWrote) {
|
|
462
469
|
log?.warn?.(`compact-relay: 新会话 ${childId} 接力链记录写入失败(下次从该会话交接将回退到标题解析)`);
|
|
463
470
|
}
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { createDiagCounters } from './dsh/diag-counters.js';
|
|
|
16
16
|
import engine from './dsh/engine.js';
|
|
17
17
|
import { createLedgerStore } from './dsh/ledger.js';
|
|
18
18
|
import { createStateHub } from './dsh/state-hub.js';
|
|
19
|
+
import { createFavoriteWatcher } from './dsh/favorite-watch.js';
|
|
19
20
|
|
|
20
21
|
export const name = 'compact-relay';
|
|
21
22
|
|
|
@@ -74,8 +75,18 @@ export function apply(ctx, config) {
|
|
|
74
75
|
// A9:readState 由这里注入,dsh 层不反向依赖 actions 层。
|
|
75
76
|
const hub = createStateHub({ readState: (sessionId) => readState(deps, { sessionId }) });
|
|
76
77
|
deps.hub = hub;
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
// 0.5.3 P1-6:推荐收藏——助手消息落地后异步打分,结果只进 readState 投影(绝不自动收藏)。
|
|
79
|
+
const favoriteWatch = createFavoriteWatcher(deps);
|
|
80
|
+
favoriteWatch.setHub(hub);
|
|
81
|
+
deps.favoriteWatch = favoriteWatch;
|
|
82
|
+
ctx.on('session/event', (session, event) => {
|
|
83
|
+
hub.onSessionEvent(session, event);
|
|
84
|
+
favoriteWatch.onEvent(session, event);
|
|
85
|
+
});
|
|
86
|
+
ctx.effect(() => () => {
|
|
87
|
+
favoriteWatch.close();
|
|
88
|
+
hub.close();
|
|
89
|
+
}, 'compact-relay: state hub + favorite watcher');
|
|
79
90
|
|
|
80
91
|
// 0.3.4(BUG-010):给模型一条「当前在可压缩区间」的动态 system-prompt 提示,
|
|
81
92
|
// 让「这里适合压缩」从不可判断变成可判断。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-compact-relay",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "DeepSeek Harness 上下文指示器插件:提供容量监控、压缩预览与确认、九段式交接单生成及消息锁定功能。 · Context indicator for DSH: preview-then-confirm compaction, 9-section session handoff, and pinned messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"node": ">=22"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
|
-
"test": "node scripts/build-client.mjs --check && node --check lib/client.js && node test/smoke.mjs && node test/handoff.mjs && node test/plugin-load.mjs && node test/contract.mjs && node test/compact-log.mjs && node test/compress-cue.mjs && node test/state-passive.mjs && node test/native-compaction.mjs && node test/markdown-lite.mjs && node test/clip.mjs && node test/diagnostics.mjs && node test/pin-contract.mjs && node test/ledger.mjs && node test/handoff-p2.mjs && node test/relay-title-scheme.mjs && node test/relay-chain.mjs && node test/relay-chain-endpoint.mjs && node test/handoff-naming.mjs && node test/native-line.mjs && node test/handoff-guard.mjs && node test/settings-ux.mjs && node test/pin-health.mjs && node test/archive.mjs && node test/relay-bar.mjs",
|
|
56
|
+
"test": "node scripts/build-client.mjs --check && node --check lib/client.js && node test/smoke.mjs && node test/handoff.mjs && node test/plugin-load.mjs && node test/contract.mjs && node test/compact-log.mjs && node test/compress-cue.mjs && node test/state-passive.mjs && node test/native-compaction.mjs && node test/markdown-lite.mjs && node test/clip.mjs && node test/diagnostics.mjs && node test/pin-contract.mjs && node test/favorite.mjs && node test/favorites-unit.mjs && node test/favorite-suggest.mjs && node test/favorite-preview.mjs && node test/ledger.mjs && node test/handoff-p2.mjs && node test/relay-title-scheme.mjs && node test/relay-chain.mjs && node test/relay-chain-endpoint.mjs && node test/handoff-naming.mjs && node test/native-line.mjs && node test/handoff-guard.mjs && node test/settings-ux.mjs && node test/pin-health.mjs && node test/archive.mjs && node test/force-archive.mjs && node test/relay-bar.mjs && node test/relay-record.mjs && node test/contract-golden.mjs",
|
|
57
57
|
"test:e2e": "node test/e2e/smoke.mjs",
|
|
58
58
|
"test:e2e:settings": "node test/e2e/settings-ui.mjs",
|
|
59
59
|
"test:e2e:settings-ux": "node test/e2e/settings-ux.mjs",
|