dsh-session-conductor 1.3.11 → 1.3.16
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 +23 -1
- package/cordis.patch.yml +0 -0
- package/lib/apply.js +28 -1243
- package/lib/client-parts/apply.js +0 -0
- package/lib/client-parts/components/compaction.js +0 -0
- package/lib/client-parts/components/conductor-settings.js +0 -0
- package/lib/client-parts/components/group.js +0 -0
- package/lib/client-parts/components/panel.js +0 -0
- package/lib/client-parts/components/processing.js +0 -0
- package/lib/client-parts/components/session-icon.js +0 -0
- package/lib/client-parts/components/settings.js +0 -0
- package/lib/client-parts/foundation/bootstrap.js +0 -0
- package/lib/client-parts/foundation/i18n.js +0 -0
- package/lib/client-parts/foundation/list-cache.js +0 -0
- package/lib/client-parts/foundation/styles.js +0 -0
- package/lib/core/config.js +0 -0
- package/lib/core/domain.js +0 -0
- package/lib/core/index-store.js +0 -0
- package/lib/core/lazy.js +0 -0
- package/lib/core/state.js +0 -0
- package/lib/core.js +0 -0
- package/lib/eio-repair.js +0 -0
- package/lib/features/base-routes.js +156 -0
- package/lib/features/context-usage/index.js +0 -0
- package/lib/features/continue/eligibility.js +0 -0
- package/lib/features/continue/scan.js +0 -0
- package/lib/features/continue/session.js +0 -0
- package/lib/features/inject/preset.js +0 -0
- package/lib/features/member-model/batch.js +97 -0
- package/lib/features/member-model/command.js +61 -7
- package/lib/features/member-model/create-teammate.js +0 -0
- package/lib/features/member-model/index.js +15 -1
- package/lib/features/member-model/lead.js +73 -0
- package/lib/features/member-model/locate.js +82 -1
- package/lib/features/member-model/override.js +0 -0
- package/lib/features/member-model/permission-command.js +138 -0
- package/lib/features/member-model/permission.js +213 -0
- package/lib/features/member-model/setup.js +0 -0
- package/lib/features/member-model/switch.js +254 -23
- package/lib/features/member-model/tools.js +324 -0
- package/lib/features/memory-recall/index.js +0 -0
- package/lib/features/rename/analysis.js +0 -0
- package/lib/features/rename/title.js +0 -0
- package/lib/features/rename/value.js +0 -0
- package/lib/features/session-action-routes.js +435 -0
- package/lib/features/session-messaging/index.js +0 -0
- package/lib/features/session-messaging/logic.js +0 -0
- package/lib/features/session-messaging/tools.js +0 -0
- package/lib/features/session-ops-routes.js +0 -0
- package/lib/features/settings-routes.js +400 -0
- package/lib/features/trash.js +0 -0
- package/lib/features/value-index.js +0 -0
- package/lib/features/value-report-routes.js +211 -0
- package/lib/group.js +0 -0
- package/lib/i18n/en.json +0 -0
- package/lib/i18n/zh.json +0 -0
- package/lib/index.js +0 -0
- package/lib/repair.js +0 -0
- package/lib/seq-gap-repair.js +0 -0
- package/lib/session-codec.js +0 -0
- package/lib/session-log.js +0 -0
- package/lib/sessions/detach.js +0 -0
- package/lib/sessions/events.js +0 -0
- package/lib/sessions/interruption.js +0 -0
- package/lib/sessions/list-cache.js +0 -0
- package/lib/sessions/list.js +0 -0
- package/lib/sessions/lookup.js +0 -0
- package/lib/sessions/route.js +0 -0
- package/lib/sessions/session-id.js +0 -0
- package/lib/sessions/tail-io.js +185 -0
- package/lib/sessions/turn.js +0 -0
- package/lib/shared/atomic-file.js +0 -0
- package/lib/shared/constants.js +0 -0
- package/lib/shared/json-store.js +0 -0
- package/lib/shared/log.js +0 -0
- package/lib/shared/timing.js +0 -0
- package/lib/shared/util.js +0 -0
- package/lib/template-inject.js +0 -0
- package/lib/value.js +0 -0
- package/lib/zstd-frames.js +0 -0
- package/package.json +1 -1
- package/scripts/check-member-model.mjs +0 -0
- package/skills/dsh-session-conductor-functions.md +0 -0
- package/skills/dsh-session-conductor.md +0 -0
- package/skills/dsh-session-official-doc.md +0 -0
- package/skills/task-completion-report.md +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
// dsh-session-conductor — 模板与插件设置类 HTTP 路由
|
|
2
|
+
//
|
|
3
|
+
// 【为什么单独成文件】原先内联在 lib/apply.js 的 `ctx.inject(["webServer"])` 回调里(该回调上千行,
|
|
4
|
+
// 审计 readability/max-function-length 拦截)。这里只搬「注册」与 handler 体,逐字保留、行为不变。
|
|
5
|
+
//
|
|
6
|
+
// 路由:/templates · /templates/dir(模板浏览与读写)、/fts-status(全文索引状态)、
|
|
7
|
+
// /compaction-model · /auto-rename-model(两条模型设置)、/auto-continue-gate(自动继续闸门)
|
|
8
|
+
import { readJson, send } from '../http.js';
|
|
9
|
+
import { log } from '../shared/log.js';
|
|
10
|
+
import { readSwitch, patchSwitch } from '../core/config.js';
|
|
11
|
+
import { invalidateSessionListCache } from '../sessions/list-cache.js';
|
|
12
|
+
import { pluginDomain } from '../core/domain.js';
|
|
13
|
+
import { resolveBrowseRoot, resolveDshHome } from '../core/config.js';
|
|
14
|
+
import { setTemplateStateCache } from '../shared/test-hooks.js';
|
|
15
|
+
import { saveTemplate, saveTemplateFromUrl, saveTemplateFromPath, listTemplateDir, removeTemplate, readTemplateSync } from '../template-inject.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 注册模板与设置类路由。
|
|
19
|
+
* @param {object} deps
|
|
20
|
+
* @param {object} deps.webServer 宿主 webServer 服务
|
|
21
|
+
* @param {object} deps.ctx 插件 ctx
|
|
22
|
+
* @returns {void}
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* 处理 /templates 的 GET/POST(列模板槽位与内容 / 写入与导入模板)。
|
|
26
|
+
*
|
|
27
|
+
* 【为什么单独成函数】handler 体 100 余行,内联在 register 回调里会让单函数超长
|
|
28
|
+
* (审计 readability/max-function-length 拦截,阈值 50 行)。抽出只换位置,逻辑一字不改;
|
|
29
|
+
* 缩进保持原样 —— 体内有模板字符串,重排缩进会改变字符串内容。
|
|
30
|
+
*
|
|
31
|
+
* @param {object} req 请求
|
|
32
|
+
* @param {object} res 响应
|
|
33
|
+
* @param {{ctx: object, tplHome: string, browseRoot: string}} deps 显式依赖
|
|
34
|
+
* @returns {Promise<void>}
|
|
35
|
+
*/
|
|
36
|
+
async function handleTemplatesRoute(req, res, { ctx, tplHome, browseRoot }) {
|
|
37
|
+
try {
|
|
38
|
+
// 非 GET 请求会改动会话列表内容,先失效列表缓存,使面板随后的 refresh 立刻看到最新(而非 5s TTL 内的旧列表)。
|
|
39
|
+
if (req.method !== "GET") invalidateSessionListCache();
|
|
40
|
+
const domain = await pluginDomain(ctx);
|
|
41
|
+
const st = domain.global.get();
|
|
42
|
+
const meta = st.sessionTemplates ?? structuredClone(TEMPLATE_DEFAULTS);
|
|
43
|
+
// 缓存写入口必须是归属模块的 setter(import 绑定对导入方只读,直接赋值会 TypeError)
|
|
44
|
+
setTemplateStateCache(meta);
|
|
45
|
+
// 构造带 content 的 slots(GET/POST 统一:导入/编辑后前端编辑框直接读到内容)
|
|
46
|
+
if (req.method === "GET") {
|
|
47
|
+
return send(res, 200, { ok: true, slots: buildTemplateSlotsWithContent(meta), maxBytes: TEMPLATE_MAX_BYTES });
|
|
48
|
+
}
|
|
49
|
+
return await handleTemplatesPost(req, res, { ctx, tplHome, browseRoot, meta });
|
|
50
|
+
return send(res, 405, { ok: false, error: { code: "method", message: MSG_METHOD_ONLY_GET_POST } });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
send(res, 500, { ok: false, error: { code: "internal", message: String(error?.message ?? error) } });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 构造「带内容的模板槽位表」(GET 与 POST 共用:导入/编辑后前端要拿到最新内容)。
|
|
58
|
+
*
|
|
59
|
+
* 【为什么单独成函数】原先是 handleTemplatesRoute 内的局部箭头函数,抽出来只为把主 handler
|
|
60
|
+
* 压到函数长度阈值内;逻辑与缩进一字不改(体内有模板字符串,重排缩进会改内容)。
|
|
61
|
+
*
|
|
62
|
+
* @param {object} meta2 模板元数据
|
|
63
|
+
* @param {string} tplHome 模板落盘目录
|
|
64
|
+
* @returns {object} slot → 槽位元数据 + content
|
|
65
|
+
*/
|
|
66
|
+
function buildTemplateSlotsWithContent(meta2, tplHome) {
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const slot of TEMPLATE_SLOTS) {
|
|
69
|
+
const slotMeta = meta2[slot] ?? { enabled: false, enforce: false, name: "", url: "", bytes: 0, updatedAt: 0 };
|
|
70
|
+
let content = "";
|
|
71
|
+
if (slotMeta.bytes > 0 || slotMeta.name) {
|
|
72
|
+
// readTemplateSync 返回 sanitized 字符串(无文件/不可读返回空串),不是 {ok,text} 对象
|
|
73
|
+
try { content = String(readTemplateSync(tplHome, slot) ?? "").slice(0, TEMPLATE_MAX_BYTES); } catch { content = ""; }
|
|
74
|
+
}
|
|
75
|
+
out[slot] = { ...slotMeta, content };
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 处理 /templates 的 POST(写入 / 导入 / 切换模板槽位)。
|
|
82
|
+
*
|
|
83
|
+
* 【为什么单独成函数】该分支 80 行,与主 handler 放一起会超函数长度阈值;
|
|
84
|
+
* 逻辑与缩进一字不改(体内有模板字符串,重排缩进会改内容)。
|
|
85
|
+
* 原分支内的 return 语义保持不变:返回值直接透传给外层 handler。
|
|
86
|
+
*
|
|
87
|
+
* @param {object} req 请求
|
|
88
|
+
* @param {object} res 响应
|
|
89
|
+
* @param {{ctx: object, tplHome: string, browseRoot: string, meta: object}} deps 显式依赖
|
|
90
|
+
* @returns {Promise<void>}
|
|
91
|
+
*/
|
|
92
|
+
async function handleTemplatesPost(req, res, { ctx, tplHome, browseRoot, meta }) {
|
|
93
|
+
const body = await readJson(req);
|
|
94
|
+
// 目录内选用 md 文件(action="pickPath")——校验用工作区浏览根,落盘用 DSH_HOME
|
|
95
|
+
if (body?.action === "pickPath") return await handleTemplatesPickPath(req, res, { browseRoot, tplHome, meta, body });
|
|
96
|
+
const slot = String(body?.slot ?? "");
|
|
97
|
+
if (!TEMPLATE_SLOTS.includes(slot)) {
|
|
98
|
+
return send(res, 400, { ok: false, error: { code: "bad-slot", message: `模板槽位须为 ${TEMPLATE_SLOTS.join("|")}` } });
|
|
99
|
+
}
|
|
100
|
+
const cur = meta[slot] ?? { enabled: false, enforce: false, name: "", url: "", bytes: 0, updatedAt: 0 };
|
|
101
|
+
if (body?.action === "remove") return await handleTemplatesRemove(req, res, { tplHome, meta, slot, cur });
|
|
102
|
+
let saved = null;
|
|
103
|
+
if (typeof body?.url === "string" && body.url.trim()) {
|
|
104
|
+
saved = await saveTemplateFromUrl(tplHome, slot, body.url);
|
|
105
|
+
} else if (typeof body?.content === "string") {
|
|
106
|
+
// 编辑保存:可只带 content(name 保留现值);也可带 name(本地导入)
|
|
107
|
+
saved = await saveTemplate(tplHome, slot, { name: (typeof body?.name === "string" && body.name.trim()) ? body.name : cur.name, content: body.content });
|
|
108
|
+
}
|
|
109
|
+
if (saved && saved.ok) {
|
|
110
|
+
const next = {
|
|
111
|
+
...meta,
|
|
112
|
+
[slot]: {
|
|
113
|
+
enabled: cur.enabled, // 上传内容不自动开,用户再点开关
|
|
114
|
+
enforce: cur.enforce === true,
|
|
115
|
+
name: saved.name || cur.name,
|
|
116
|
+
url: typeof body?.url === "string" && body.url.trim() ? body.url.trim() : "",
|
|
117
|
+
bytes: saved.bytes,
|
|
118
|
+
updatedAt: Date.now(),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
setTemplateStateCache(next);
|
|
122
|
+
await domain.global.set({ ...st, sessionTemplates: next });
|
|
123
|
+
return send(res, 200, { ok: true, slots: buildTemplateSlotsWithContent(next), message: `模板「${slot}」已保存,记得点「开启注入」` });
|
|
124
|
+
}
|
|
125
|
+
if (saved && !saved.ok) return send(res, 400, saved);
|
|
126
|
+
if (typeof body?.enabled === "boolean") {
|
|
127
|
+
const next = { ...meta, [slot]: { ...cur, enabled: body.enabled } };
|
|
128
|
+
setTemplateStateCache(next);
|
|
129
|
+
await domain.global.set({ ...st, sessionTemplates: next });
|
|
130
|
+
return send(res, 200, { ok: true, slots: buildTemplateSlotsWithContent(next), message: body.enabled ? `模板「${slot}」已开启注入` : `模板「${slot}」已关闭` });
|
|
131
|
+
}
|
|
132
|
+
// 方案模板强制门禁开关({slot:"plan", enforce:bool})——即时生效,无需重启
|
|
133
|
+
if (slot === "plan" && typeof body?.enforce === "boolean") {
|
|
134
|
+
const next = { ...meta, plan: { ...cur, enforce: body.enforce } };
|
|
135
|
+
setTemplateStateCache(next);
|
|
136
|
+
await domain.global.set({ ...st, sessionTemplates: next });
|
|
137
|
+
return send(res, 200, { ok: true, slots: buildTemplateSlotsWithContent(next), message: body.enforce
|
|
138
|
+
? `方案模板强制门禁已开启(未出提案并获确认前,代码修改类工具调用被拒绝)`
|
|
139
|
+
: `方案模板强制门禁已关闭` });
|
|
140
|
+
}
|
|
141
|
+
return send(res, 400, { ok: false, error: { code: "bad-request", message: "需要 {slot, name, content}(本地 md)、{slot, url}(在线 md)、{slot, enabled} 或 {slot:'plan', enforce}" } });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 处理 /templates 的「按路径导入」(action=pickPath):把工作区内某个 md 文件复制成模板。
|
|
146
|
+
*
|
|
147
|
+
* 【为什么单独成函数】主 POST 处理器里这一支 20 余行,抽出来才让上层函数降到长度阈值内;
|
|
148
|
+
* 逻辑与缩进一字不改。返回值直接透传(内部各分支都是 return send(...))。
|
|
149
|
+
*
|
|
150
|
+
* @param {object} req 请求
|
|
151
|
+
* @param {object} res 响应
|
|
152
|
+
* @param {{browseRoot: string, tplHome: string, meta: object, body: object}} deps 显式依赖
|
|
153
|
+
* @returns {Promise<void>}
|
|
154
|
+
*/
|
|
155
|
+
async function handleTemplatesPickPath(req, res, { browseRoot, tplHome, meta, body }) {
|
|
156
|
+
const slot = String(body?.slot ?? "");
|
|
157
|
+
if (!TEMPLATE_SLOTS.includes(slot)) {
|
|
158
|
+
return send(res, 400, { ok: false, error: { code: "bad-slot", message: `模板槽位须为 ${TEMPLATE_SLOTS.join("|")}` } });
|
|
159
|
+
}
|
|
160
|
+
const saved = await saveTemplateFromPath(browseRoot, slot, body.path, tplHome);
|
|
161
|
+
if (!saved.ok) return send(res, 400, saved);
|
|
162
|
+
const cur = meta[slot] ?? { enabled: false, enforce: false, name: "", url: "", bytes: 0, updatedAt: 0 };
|
|
163
|
+
const next = {
|
|
164
|
+
...meta,
|
|
165
|
+
[slot]: {
|
|
166
|
+
enabled: cur.enabled,
|
|
167
|
+
enforce: cur.enforce === true,
|
|
168
|
+
name: saved.name || cur.name,
|
|
169
|
+
url: "",
|
|
170
|
+
bytes: saved.bytes,
|
|
171
|
+
updatedAt: Date.now(),
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
setTemplateStateCache(next);
|
|
175
|
+
await domain.global.set({ ...st, sessionTemplates: next });
|
|
176
|
+
return send(res, 200, { ok: true, slots: buildTemplateSlotsWithContent(next), message: `模板「${slot}」已从目录选用(${saved.name}),记得点「开启注入」` });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 处理 /templates 的「移除」(action=remove):清空该槽位并返回最新槽位表。
|
|
181
|
+
*
|
|
182
|
+
* 【为什么单独成函数】同上:把主 POST 处理器压到长度阈值内,逻辑与缩进一字不改。
|
|
183
|
+
*
|
|
184
|
+
* @param {object} req 请求
|
|
185
|
+
* @param {object} res 响应
|
|
186
|
+
* @param {{tplHome: string, meta: object, slot: string, cur: object}} deps 显式依赖
|
|
187
|
+
* @returns {Promise<void>}
|
|
188
|
+
*/
|
|
189
|
+
async function handleTemplatesRemove(req, res, { tplHome, meta, slot, cur }) {
|
|
190
|
+
const r = await removeTemplate(tplHome, slot);
|
|
191
|
+
if (!r.ok) return send(res, 400, r);
|
|
192
|
+
const next = { ...meta, [slot]: { ...cur, enabled: false, enforce: false, name: "", url: "", bytes: 0, updatedAt: 0 } };
|
|
193
|
+
setTemplateStateCache(next);
|
|
194
|
+
await domain.global.set({ ...st, sessionTemplates: next });
|
|
195
|
+
return send(res, 200, { ok: true, slots: buildTemplateSlotsWithContent(next), message: `模板「${slot}」已清空` });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function registerSettingsRoutes({ webServer, ctx }) {
|
|
199
|
+
// ---------- GET/POST /api/session-conductor/templates ----------
|
|
200
|
+
// 会话模板注入():plan(方案模板)/ closing(收尾模板)两个固定槽位,
|
|
201
|
+
// 支持上传本地 md 文件或在线 md 网址(host 下载转存),内容落盘
|
|
202
|
+
// <DSH_HOME>/template-inject-md/<slot>.md → systemPrompt section「session-templates」注入。
|
|
203
|
+
// GET → {ok, slots: {plan:{enabled,name,url,bytes,updatedAt}, closing:{...}}, maxBytes}
|
|
204
|
+
// POST {slot, enabled?} → 只改开关;
|
|
205
|
+
// POST {slot, name, content} → 上传本地 md;
|
|
206
|
+
// POST {slot, url} → 在线 md 网址下载转存;
|
|
207
|
+
// POST {slot, action:"remove"} → 清空槽位。
|
|
208
|
+
{
|
|
209
|
+
const tplHome = resolveDshHome(ctx, cfg);
|
|
210
|
+
// 浏览根与选用校验根唯一来源 = resolveBrowseRoot(工作区目录)。
|
|
211
|
+
// 同一 browseRoot 变量贯穿本块内两个路由:/templates 的 pickPath 校验、/templates/dir 浏览。
|
|
212
|
+
// 落盘仍 tplHome(DSH_HOME/template-inject-md/),浏览/校验/落盘三者职责清晰。
|
|
213
|
+
const browseRoot = resolveBrowseRoot(ctx, cfg);
|
|
214
|
+
if (tplHome) {
|
|
215
|
+
webServer.register({
|
|
216
|
+
kind: "exact",
|
|
217
|
+
path: "/api/session-conductor/templates",
|
|
218
|
+
handler: (req, res) => handleTemplatesRoute(req, res, { ctx, tplHome, browseRoot }),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// ---------- GET /api/session-conductor/templates/dir ----------
|
|
222
|
+
// 模板目录浏览(「在工作区目录内找」):列 DSH 工作区目录树内一层目录。
|
|
223
|
+
// 浏览根与选用校验根同源(上方 browseRoot);落盘仍在 DSH_HOME/template-inject-md/。
|
|
224
|
+
// GET ?path=... → {ok, path, parent, entries:[{name,path,isDir,isMd}]}(缺省从工作区根开始)
|
|
225
|
+
if (browseRoot) {
|
|
226
|
+
webServer.register({
|
|
227
|
+
kind: "exact",
|
|
228
|
+
path: "/api/session-conductor/templates/dir",
|
|
229
|
+
handler: async (req, res) => {
|
|
230
|
+
try {
|
|
231
|
+
// 非 GET 请求会改动会话列表内容,先失效列表缓存,使面板随后的 refresh 立刻看到最新(而非 5s TTL 内的旧列表)。
|
|
232
|
+
if (req.method !== "GET") invalidateSessionListCache();
|
|
233
|
+
if (req.method !== "GET") return send(res, 405, { ok: false, error: { code: "method", message: "仅 GET" } });
|
|
234
|
+
const url = new URL(req.url, "http://localhost");
|
|
235
|
+
const path = url.searchParams.get("path") ?? "";
|
|
236
|
+
const r = await listTemplateDir(browseRoot, path);
|
|
237
|
+
if (!r.ok) return send(res, 400, r);
|
|
238
|
+
return send(res, 200, r);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
send(res, 500, { ok: false, error: { code: "internal", message: String(error?.message ?? error) } });
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------- GET /api/session-conductor/fts-status ----------
|
|
249
|
+
// 全文搜索诊断( 并入,原独立插件 dsh-session-search 已合并):
|
|
250
|
+
// 报告官方 FTS5(sessionQuery)是否挂载 + 内置 zstd 扫描兜底是否可用。
|
|
251
|
+
webServer.register({
|
|
252
|
+
kind: "exact",
|
|
253
|
+
path: "/api/session-conductor/fts-status",
|
|
254
|
+
handler: async (req, res) => {
|
|
255
|
+
try {
|
|
256
|
+
// 非 GET 请求会改动会话列表内容,先失效列表缓存,使面板随后的 refresh 立刻看到最新(而非 5s TTL 内的旧列表)。
|
|
257
|
+
if (req.method !== "GET") invalidateSessionListCache();
|
|
258
|
+
const sessionQuery = ctx.get("sessionQuery");
|
|
259
|
+
const sessionEvents = ctx.get("sessionEvents");
|
|
260
|
+
send(res, 200, {
|
|
261
|
+
ok: true,
|
|
262
|
+
plugin: "dsh-session-conductor",
|
|
263
|
+
sessionQueryMounted: sessionQuery !== void 0,
|
|
264
|
+
ftsMode: sessionQuery === void 0 ? "unavailable" : "first-search",
|
|
265
|
+
builtinScanAvailable: typeof searchSessions === "function",
|
|
266
|
+
note:
|
|
267
|
+
sessionQuery === void 0
|
|
268
|
+
? "官方 FTS5 服务未挂载(检查 session-query-sqlite openAt 配置),使用内置 zstd 扫描兜底"
|
|
269
|
+
: "官方 FTS5 内容搜索已解锁(openAt: first-search),内置 zstd 扫描为兜底",
|
|
270
|
+
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
send(res, 500, { ok: false, error: { code: "internal", message: String(error?.message ?? error) } });
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// ---------- GET/POST /api/session-conductor/compaction-model ----------
|
|
278
|
+
// 压缩模型选择:会话模型旁单独选压缩用模型。
|
|
279
|
+
// GET → {ok, compactionModel: {provider,model}|null, follow: true(跟随会话模型)}
|
|
280
|
+
// POST {provider, model} → 设置;POST {follow:true} → 重置为跟随会话模型
|
|
281
|
+
webServer.register({
|
|
282
|
+
kind: "exact",
|
|
283
|
+
path: "/api/session-conductor/compaction-model",
|
|
284
|
+
handler: async (req, res) => {
|
|
285
|
+
try {
|
|
286
|
+
// 非 GET 请求会改动会话列表内容,先失效列表缓存,使面板随后的 refresh 立刻看到最新(而非 5s TTL 内的旧列表)。
|
|
287
|
+
if (req.method !== "GET") invalidateSessionListCache();
|
|
288
|
+
const domain = await pluginDomain(ctx);
|
|
289
|
+
const state = domain.global.get();
|
|
290
|
+
const cur = state.compactionModel ?? null;
|
|
291
|
+
if (req.method === "GET") {
|
|
292
|
+
return send(res, 200, { ok: true, compactionModel: cur, follow: cur === null });
|
|
293
|
+
}
|
|
294
|
+
if (req.method === "POST") {
|
|
295
|
+
const body = await readJson(req);
|
|
296
|
+
if (body?.follow === true) {
|
|
297
|
+
await domain.global.set({ ...state, compactionModel: null });
|
|
298
|
+
return send(res, 200, { ok: true, compactionModel: null, follow: true, message: "压缩模型已重置为跟随会话模型" });
|
|
299
|
+
}
|
|
300
|
+
const provider = String(body?.provider ?? "").trim();
|
|
301
|
+
const model = String(body?.model ?? "").trim();
|
|
302
|
+
if (!provider || !model) return send(res, 400, { ok: false, error: { code: "bad-request", message: "需要 provider 和 model" } });
|
|
303
|
+
await domain.global.set({ ...state, compactionModel: { provider, model } });
|
|
304
|
+
return send(res, 200, { ok: true, compactionModel: { provider, model }, follow: false, message: `压缩模型已设为 ${provider}/${model}(重启后生效)` });
|
|
305
|
+
}
|
|
306
|
+
return send(res, 405, { ok: false, error: { code: "method", message: MSG_METHOD_ONLY_GET_POST } });
|
|
307
|
+
} catch (error) {
|
|
308
|
+
send(res, 500, { ok: false, error: { code: "internal", message: String(error?.message ?? error) } });
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// ---------- GET/POST /api/session-conductor/auto-rename-model ----------
|
|
314
|
+
// 自动重命名模型选择:设置页「DSH 同款解析选择器」选定。
|
|
315
|
+
// GET → {ok, selection: {provider,model}|null, follow: true(跟随会话模型), catalog}
|
|
316
|
+
// catalog = 官方 modelCatalog(provider 分组 + 模型 + 默认),供前端渲染选择器。
|
|
317
|
+
// POST {provider, model} → 设置;POST {follow:true} → 重置为跟随会话模型。
|
|
318
|
+
webServer.register({
|
|
319
|
+
kind: "exact",
|
|
320
|
+
path: "/api/session-conductor/auto-rename-model",
|
|
321
|
+
handler: async (req, res) => {
|
|
322
|
+
try {
|
|
323
|
+
const domain = await pluginDomain(ctx);
|
|
324
|
+
const state = domain.global.get();
|
|
325
|
+
const cur = state.autoRenameModel ?? null;
|
|
326
|
+
if (req.method === "GET") {
|
|
327
|
+
// 官方解析选择器的数据源:provider 分组 + 模型列表 + 部署默认
|
|
328
|
+
let catalog = null;
|
|
329
|
+
try {
|
|
330
|
+
const sc = ctx.get("sessionController");
|
|
331
|
+
catalog = sc?.modelCatalog ? await sc.modelCatalog() : null;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
log(ctx, `auto-rename-model: 官方 modelCatalog 获取失败(前端仍可手动输入): ${String(error?.message ?? error)}`);
|
|
334
|
+
catalog = null;
|
|
335
|
+
}
|
|
336
|
+
return send(res, 200, { ok: true, selection: cur, follow: cur === null, catalog });
|
|
337
|
+
}
|
|
338
|
+
if (req.method === "POST") {
|
|
339
|
+
const body = await readJson(req);
|
|
340
|
+
if (body?.follow === true) {
|
|
341
|
+
await domain.global.set({ ...state, autoRenameModel: null });
|
|
342
|
+
return send(res, 200, { ok: true, selection: null, follow: true, message: "自动重命名模型已重置为跟随会话模型" });
|
|
343
|
+
}
|
|
344
|
+
const provider = String(body?.provider ?? "").trim();
|
|
345
|
+
const model = String(body?.model ?? "").trim();
|
|
346
|
+
if (!provider || !model) return send(res, 400, { ok: false, error: { code: "bad-request", message: "需要 provider 和 model" } });
|
|
347
|
+
await domain.global.set({ ...state, autoRenameModel: { provider, model } });
|
|
348
|
+
return send(res, 200, { ok: true, selection: { provider, model }, follow: false, message: `自动重命名模型已设为 ${provider}/${model}` });
|
|
349
|
+
}
|
|
350
|
+
return send(res, 405, { ok: false, error: { code: "method", message: MSG_METHOD_ONLY_GET_POST } });
|
|
351
|
+
} catch (error) {
|
|
352
|
+
send(res, 500, { ok: false, error: { code: "internal", message: String(error?.message ?? error) } });
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// ---------- GET/POST /api/session-conductor/auto-continue-gate ----------
|
|
358
|
+
// 自动续跑全局闸门(与 guardian 联动):
|
|
359
|
+
// GET → {ok, gate: "open"|"closed", note}
|
|
360
|
+
// POST {gate:"open"|"closed"} → 设置;closed 时一切自动续跑跳过(周期扫描/scan 自动续跑部分)
|
|
361
|
+
// guardian 在 DSH 恢复健康时置 closed;用户手动开启或首次手动对话(turn/start user)后自动置 open
|
|
362
|
+
webServer.register({
|
|
363
|
+
kind: "exact",
|
|
364
|
+
path: "/api/session-conductor/auto-continue-gate",
|
|
365
|
+
handler: async (req, res) => {
|
|
366
|
+
try {
|
|
367
|
+
// 非 GET 请求会改动会话列表内容,先失效列表缓存,使面板随后的 refresh 立刻看到最新(而非 5s TTL 内的旧列表)。
|
|
368
|
+
if (req.method !== "GET") invalidateSessionListCache();
|
|
369
|
+
const domain = await pluginDomain(ctx);
|
|
370
|
+
const state = domain.global.get();
|
|
371
|
+
const cur = state.autoContinueGate ?? "open";
|
|
372
|
+
if (req.method === "GET") {
|
|
373
|
+
return send(res, 200, {
|
|
374
|
+
ok: true,
|
|
375
|
+
gate: cur,
|
|
376
|
+
note: cur === "closed"
|
|
377
|
+
? "自动续跑全局闸门关闭:一切自动续跑跳过(DSH 刚启动/崩溃恢复后),用户手动开启或第一次手动对话后自动放行"
|
|
378
|
+
: "自动续跑全局闸门开放:按原有判定(单会话开关 → 全局默认)",
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
if (req.method === "POST") {
|
|
382
|
+
const body = await readJson(req);
|
|
383
|
+
const gate = String(body?.gate ?? "");
|
|
384
|
+
if (gate !== "open" && gate !== "closed") {
|
|
385
|
+
return send(res, 400, { ok: false, error: { code: "bad-request", message: 'gate 必须为 "open" 或 "closed"' } });
|
|
386
|
+
}
|
|
387
|
+
await domain.global.set({ ...state, autoContinueGate: gate });
|
|
388
|
+
return send(res, 200, {
|
|
389
|
+
ok: true,
|
|
390
|
+
gate,
|
|
391
|
+
message: gate === "closed" ? "自动续跑闸门已关闭(所有自动续跑暂停)" : "自动续跑闸门已开放",
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return send(res, 405, { ok: false, error: { code: "method", message: MSG_METHOD_ONLY_GET_POST } });
|
|
395
|
+
} catch (error) {
|
|
396
|
+
send(res, 500, { ok: false, error: { code: "internal", message: String(error?.message ?? error) } });
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
package/lib/features/trash.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// dsh-session-conductor — 会话价值分析与任务完成汇报路由
|
|
2
|
+
//
|
|
3
|
+
// 【为什么单独成文件】原先内联在 lib/apply.js 的 `ctx.inject(["webServer"])` 回调里,
|
|
4
|
+
// 让该回调成为上千行巨型函数(审计 readability/max-function-length 拦截)。
|
|
5
|
+
// 这里只搬「注册」这部分,handler 体逐字保留、行为不变。
|
|
6
|
+
//
|
|
7
|
+
// 路由:
|
|
8
|
+
// POST /value-analysis —— 扫描会话并给出价值评分/清理建议
|
|
9
|
+
// 任务完成汇报管道(task-completion-report)
|
|
10
|
+
import { readJson, send } from '../http.js';
|
|
11
|
+
import { log } from '../shared/log.js';
|
|
12
|
+
import { invalidateSessionListCache } from '../sessions/list-cache.js';
|
|
13
|
+
import { lazyValue } from '../core/lazy.js';
|
|
14
|
+
import { analyzeValueWithLlm } from './rename/value.js';
|
|
15
|
+
import { buildSessionListCached } from '../sessions/list.js';
|
|
16
|
+
import { getValueVerdict, putValueVerdict, saveValueIndexNow } from './value-index.js';
|
|
17
|
+
import { renderCompletionBlock } from '../core.js';
|
|
18
|
+
import { sessionEventsOf } from '../sessions/events.js';
|
|
19
|
+
import { MAX_STALE_DAYS } from '../shared/constants.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 注册价值分析与任务汇报路由。
|
|
23
|
+
* @param {object} deps
|
|
24
|
+
* @param {object} deps.webServer 宿主 webServer 服务
|
|
25
|
+
* @param {object} deps.ctx 插件 ctx
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
export function registerValueReportRoutes({ webServer, ctx }) {
|
|
29
|
+
|
|
30
|
+
// ---------- POST /api/session-conductor/value-analysis ----------
|
|
31
|
+
// 会话价值分析( 规则判定 + 特征/LLM/关键词价值):
|
|
32
|
+
// 规则分类 completed/unfinished/stale/active + 最后回复摘要;
|
|
33
|
+
// 高/低价值 = assessValue 特征评分(活跃/长度/完成/未完成/细节补充);
|
|
34
|
+
// 可选 body.keywords=[...] 记录指定关键词 → 标题/最后用户消息命中任一关键词的会话无条件最高价值;
|
|
35
|
+
// 可选 body.llm=true 额外用 LLM 打分(fail-soft)。
|
|
36
|
+
webServer.register({
|
|
37
|
+
kind: "exact",
|
|
38
|
+
path: "/api/session-conductor/value-analysis",
|
|
39
|
+
handler: async (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
// 非 GET 请求会改动会话列表内容,先失效列表缓存,使面板随后的 refresh 立刻看到最新(而非 5s TTL 内的旧列表)。
|
|
42
|
+
if (req.method !== "GET") invalidateSessionListCache();
|
|
43
|
+
const body = await readJson(req);
|
|
44
|
+
const staleDays = typeof body?.staleDays === "number" && Number.isFinite(body.staleDays)
|
|
45
|
+
? Math.min(MAX_STALE_DAYS, Math.max(1, Math.round(body.staleDays)))
|
|
46
|
+
: 3;
|
|
47
|
+
const useLlm = body?.llm === true;
|
|
48
|
+
const sessions = await buildSessionListCached(ctx); // 价值分析走缓存(容忍 5s 陈旧,避免与面板刷新叠加扫描)
|
|
49
|
+
// 流式(`?stream=1` 或 Accept: application/x-ndjson):**每分析完一个会话就推一行**。
|
|
50
|
+
// 前端拿到 value==="low" 立刻勾上(分析出一个勾一个),不必等全量跑完 ——
|
|
51
|
+
// 全量要逐个读上百份日志,慢的是读取本身,而读取是天然可增量的。
|
|
52
|
+
// 判定规则与批量路径同源(value.js 的 classifySessionLevel),不在这里另写一套。
|
|
53
|
+
const wantValueStream = /application\/x-ndjson/i.test(req.headers?.accept ?? "")
|
|
54
|
+
|| String(req.url ?? "").includes("stream=1");
|
|
55
|
+
if (wantValueStream) {
|
|
56
|
+
// body.ids:只分析这些会话(面板已先用归档/时间/目录在本地筛过一轮,
|
|
57
|
+
// 没必要把全量会话都读一遍事件)。不传 = 全量。
|
|
58
|
+
const wantedIds = Array.isArray(body?.ids)
|
|
59
|
+
? new Set(body.ids.filter((id) => typeof id === "string" && id !== ""))
|
|
60
|
+
: null;
|
|
61
|
+
const streamTargets = wantedIds ? sessions.filter((s) => wantedIds.has(s.id)) : sessions;
|
|
62
|
+
res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
|
|
63
|
+
res.setHeader("Cache-Control", "no-store");
|
|
64
|
+
if (res.socket?.setNoDelay) res.socket.setNoDelay(true);
|
|
65
|
+
res.write(JSON.stringify({ ok: true, stream: true, total: streamTargets.length }) + "\n");
|
|
66
|
+
try {
|
|
67
|
+
if (typeof res.flushHeaders === "function") res.flushHeaders();
|
|
68
|
+
} catch { /* 老宿主不支持 flush,不影响主流程 */ }
|
|
69
|
+
const { classifySessionLevel } = await lazyValue();
|
|
70
|
+
let done = 0;
|
|
71
|
+
for (const s of streamTargets) {
|
|
72
|
+
done += 1;
|
|
73
|
+
// ① 索引命中(内容没变 + 未超 TTL)→ 零 I/O 直接给结论
|
|
74
|
+
const cachedVerdict = getValueVerdict(ctx, s.id, s.updatedAt);
|
|
75
|
+
if (cachedVerdict) {
|
|
76
|
+
try {
|
|
77
|
+
res.write(JSON.stringify({
|
|
78
|
+
id: s.id, value: cachedVerdict.value, score: cachedVerdict.score,
|
|
79
|
+
reason: cachedVerdict.reason, cached: true, done, total: streamTargets.length,
|
|
80
|
+
}) + "\n");
|
|
81
|
+
} catch { break; }
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// ② 没索引 / 会话变过 / 超 TTL → 真读一次日志
|
|
85
|
+
let events = [];
|
|
86
|
+
try {
|
|
87
|
+
const found = await sessionEventsOf(ctx, s.id);
|
|
88
|
+
events = found?.events ?? [];
|
|
89
|
+
} catch { /* 读不到就当空事件:会走 artifact-safe 保守判 high,不误删 */ }
|
|
90
|
+
const verdict = classifySessionLevel(s, events, new Date(), staleDays);
|
|
91
|
+
putValueVerdict(ctx, s.id, s.updatedAt, verdict);
|
|
92
|
+
try {
|
|
93
|
+
res.write(JSON.stringify({ ...verdict, cached: false, done, total: streamTargets.length }) + "\n");
|
|
94
|
+
} catch { break; }
|
|
95
|
+
}
|
|
96
|
+
saveValueIndexNow(ctx); // 本次算出来的结论立刻落盘(下次筛选直接命中索引)
|
|
97
|
+
try { res.end(); } catch { /* 连接已断 */ }
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// 一次性读取各会话事件(复用,避免大会话重复解压导致超时/失败)
|
|
101
|
+
const eventsById = {};
|
|
102
|
+
// 逐个会话读取最后 assistant 文本 + 最后用户消息(损坏/不可读会话跳过,用空串)
|
|
103
|
+
const texts = {};
|
|
104
|
+
const userTexts = {};
|
|
105
|
+
for (const s of sessions) {
|
|
106
|
+
try {
|
|
107
|
+
const found = await sessionEventsOf(ctx, s.id);
|
|
108
|
+
if (found) {
|
|
109
|
+
eventsById[s.id] = found.events || [];
|
|
110
|
+
const { lastAssistantText, lastUserText, filterSessionsByKeywords } = await lazyValue();
|
|
111
|
+
texts[s.id] = lastAssistantText(found.events);
|
|
112
|
+
userTexts[s.id] = lastUserText(found.events);
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
/* 单会话失败跳过 */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// 可选 LLM 高/低价值判断
|
|
119
|
+
let llmById = {};
|
|
120
|
+
if (useLlm) {
|
|
121
|
+
llmById = await analyzeValueWithLlm(ctx, sessions, texts, (m) => log(ctx, m));
|
|
122
|
+
}
|
|
123
|
+
// 特征评分(活跃/长度/完成/未完成/细节补充)+ 关键词命中→无条件最高
|
|
124
|
+
const featuresById = {};
|
|
125
|
+
for (const s of sessions) {
|
|
126
|
+
try {
|
|
127
|
+
const { buildValueFeatures } = await lazyValue();
|
|
128
|
+
featuresById[s.id] = buildValueFeatures(eventsById[s.id] || [], s, new Date());
|
|
129
|
+
} catch {
|
|
130
|
+
const { buildValueFeatures: buildValueFeatures2 } = await lazyValue();
|
|
131
|
+
featuresById[s.id] = buildValueFeatures2([], s, new Date());
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const keywords = Array.isArray(body?.keywords) ? body.keywords : [];
|
|
135
|
+
const { analyzeValuesWithKeywords } = await lazyValue();
|
|
136
|
+
const analysisSummary = analyzeValuesWithKeywords(sessions, texts, userTexts, featuresById, keywords, llmById, new Date(), staleDays);
|
|
137
|
+
const counts = {};
|
|
138
|
+
for (const key of Object.keys(analysisSummary)) counts[key] = analysisSummary[key].length;
|
|
139
|
+
send(res, 200, {
|
|
140
|
+
ok: true,
|
|
141
|
+
staleDays,
|
|
142
|
+
llm: !!useLlm,
|
|
143
|
+
keywords: keywords.length ? keywords : void 0,
|
|
144
|
+
keywordHits: keywords.length ? filterSessionsByKeywords(sessions, texts, userTexts, keywords, new Date()) : [],
|
|
145
|
+
generatedAt: Date.now(),
|
|
146
|
+
counts,
|
|
147
|
+
completed: analysisSummary.completed,
|
|
148
|
+
unfinished: analysisSummary.unfinished,
|
|
149
|
+
stale: analysisSummary.stale,
|
|
150
|
+
active: analysisSummary.active,
|
|
151
|
+
high: analysisSummary.high,
|
|
152
|
+
low: analysisSummary.low,
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
send(res, 400, { ok: false, error: { code: "value-analysis-failed", message: String(error?.message ?? error) } });
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ---------- 任务完成汇报(task-completion-report 管道) ----------
|
|
161
|
+
// 收尾约定不再由代码写死注入 systemPrompt——改由模板注入「收尾模板」槽位承担
|
|
162
|
+
// (设置 → 会话管理 → 模板注入 → closing,可自定义内容)。
|
|
163
|
+
// LLM 侧的 render/check 工具已移除,只剩下面的 HTTP 端点(面板/自检用),故 tools 如实回报空列表。
|
|
164
|
+
webServer.register({
|
|
165
|
+
kind: "exact",
|
|
166
|
+
path: "/api/task-completion/status",
|
|
167
|
+
handler: async (req, res) => {
|
|
168
|
+
send(res, 200, {
|
|
169
|
+
ok: true,
|
|
170
|
+
name: "dsh-session-conductor",
|
|
171
|
+
skill: "task-completion-report",
|
|
172
|
+
conventionSource: "template-inject:closing(不再代码写死,由收尾模板槽位注入)",
|
|
173
|
+
tools: []
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
webServer.register({
|
|
179
|
+
kind: "exact",
|
|
180
|
+
path: "/api/task-completion/render",
|
|
181
|
+
handler: async (req, res) => {
|
|
182
|
+
try {
|
|
183
|
+
// 非 GET 请求会改动会话列表内容,先失效列表缓存,使面板随后的 refresh 立刻看到最新(而非 5s TTL 内的旧列表)。
|
|
184
|
+
if (req.method !== "GET") invalidateSessionListCache();
|
|
185
|
+
const body = await readJson(req);
|
|
186
|
+
const block = renderCompletionBlock(body);
|
|
187
|
+
send(res, 200, { ok: true, block });
|
|
188
|
+
} catch (error) {
|
|
189
|
+
send(res, 400, { ok: false, error: { code: "render-failed", message: String(error?.message ?? error) } });
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
webServer.register({
|
|
195
|
+
kind: "exact",
|
|
196
|
+
path: "/api/task-completion/check",
|
|
197
|
+
handler: async (req, res) => {
|
|
198
|
+
try {
|
|
199
|
+
// 非 GET 请求会改动会话列表内容,先失效列表缓存,使面板随后的 refresh 立刻看到最新(而非 5s TTL 内的旧列表)。
|
|
200
|
+
if (req.method !== "GET") invalidateSessionListCache();
|
|
201
|
+
const body = await readJson(req);
|
|
202
|
+
if (typeof body?.text !== "string") {
|
|
203
|
+
return send(res, 400, { ok: false, error: { code: "bad-request", message: "缺少 text" } });
|
|
204
|
+
}
|
|
205
|
+
send(res, 200, { ok: true, ...checkCompletionText(body.text) });
|
|
206
|
+
} catch (error) {
|
|
207
|
+
send(res, 400, { ok: false, error: { code: "check-failed", message: String(error?.message ?? error) } });
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
package/lib/group.js
CHANGED
|
File without changes
|
package/lib/i18n/en.json
CHANGED
|
File without changes
|
package/lib/i18n/zh.json
CHANGED
|
File without changes
|
package/lib/index.js
CHANGED
|
File without changes
|
package/lib/repair.js
CHANGED
|
File without changes
|
package/lib/seq-gap-repair.js
CHANGED
|
File without changes
|
package/lib/session-codec.js
CHANGED
|
File without changes
|
package/lib/session-log.js
CHANGED
|
File without changes
|
package/lib/sessions/detach.js
CHANGED
|
File without changes
|
package/lib/sessions/events.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/sessions/list.js
CHANGED
|
File without changes
|
package/lib/sessions/lookup.js
CHANGED
|
File without changes
|
package/lib/sessions/route.js
CHANGED
|
File without changes
|
|
File without changes
|