dsh-session-conductor 1.3.10 → 1.3.11
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 +2 -1
- package/lib/apply.js +77 -14
- package/lib/client-parts/components/processing.js +18 -11
- package/lib/client.js +18 -11
- package/lib/core/config.js +42 -22
- package/lib/core/index-store.js +28 -23
- package/lib/eio-repair.js +2 -1
- package/lib/features/continue/eligibility.js +1 -1
- package/lib/features/member-model/command.js +33 -15
- package/lib/features/member-model/create-teammate.js +208 -0
- package/lib/features/member-model/locate.js +31 -17
- package/lib/features/member-model/switch.js +144 -65
- package/lib/features/memory-recall/index.js +39 -30
- package/lib/features/rename/analysis.js +25 -16
- package/lib/features/rename/title.js +26 -14
- package/lib/features/rename/value.js +38 -26
- package/lib/features/session-messaging/logic.js +7 -14
- package/lib/features/session-ops-routes.js +178 -253
- package/lib/features/session-ops.js +32 -29
- package/lib/repair.js +5 -3
- package/lib/seq-gap-repair.js +5 -5
- package/lib/sessions/events.js +11 -5
- package/lib/sessions/interruption.js +2 -2
- package/lib/sessions/lookup.js +50 -0
- package/lib/sessions/route.js +1 -1
- package/lib/shared/atomic-file.js +38 -0
- package/lib/shared/json-store.js +54 -0
- package/lib/shared/timing.js +37 -0
- package/lib/zstd-frames.js +6 -11
- package/package.json +1 -1
- package/skills/dsh-session-conductor-functions.md +1 -1
- package/skills/dsh-session-conductor.md +1 -1
package/lib/seq-gap-repair.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// 修复:动态定位 GAP/offset → 3 类引用映射(值 ≥ GAP 则 +offset)→ seq 重编号 → 自愈补全缺失引用
|
|
8
8
|
// → 官方 foldSurface 校验 → token-surface 协议校验 → packChunkRuns → 多帧 zstd 原子写回。
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
11
|
+
import { replaceFileSync } from "./shared/atomic-file.js";
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
11
13
|
import { join, dirname } from 'node:path';
|
|
12
14
|
import { findSessionLog } from './session-log.js';
|
|
13
15
|
import { zstdCompress, constants } from 'node:zlib';
|
|
@@ -22,7 +24,7 @@ const SURFACE_TYPES = new Set(['user/message', 'assistant/message', 'tool/result
|
|
|
22
24
|
|
|
23
25
|
/** 解码会话文件 → { header, events }。返回 null 表示无法解析(非 seq-gap 损坏)。 */
|
|
24
26
|
async function loadSessionFile(path) {
|
|
25
|
-
const orig =
|
|
27
|
+
const orig = await readFile(path);
|
|
26
28
|
const text = await decodeAllFrames(orig);
|
|
27
29
|
const lines = text.split('\n');
|
|
28
30
|
const header = JSON.parse(lines[0]);
|
|
@@ -192,9 +194,7 @@ export async function repairSeqGap(path, { dryRun = false, backupDir } = {}) {
|
|
|
192
194
|
const bodyFrame = await zstdCompressAsync(Buffer.from(bodyText, 'utf8'), CHECKSUM_OPTIONS);
|
|
193
195
|
const newBuf = Buffer.concat([headerFrame, bodyFrame]);
|
|
194
196
|
report.sizeAfter = newBuf.length;
|
|
195
|
-
|
|
196
|
-
writeFileSync(fixedTmpPath, newBuf);
|
|
197
|
-
renameSync(fixedTmpPath, path);
|
|
197
|
+
replaceFileSync(path, newBuf, { tmpSuffix: '.fixing' }); // 统一原子替换(写 tmp → 继承权限 → rename)
|
|
198
198
|
report.ok = true;
|
|
199
199
|
return report;
|
|
200
200
|
}
|
package/lib/sessions/events.js
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
// 宿主包**懒加载**:顶层 `import` 会让本文件在「工作区自测」环境直接解析失败
|
|
17
17
|
// (仓库里踩过同类坑),而 events.js 是最需要被单测覆盖的模块之一(冷会话读法)。
|
|
18
18
|
// 这里改成顶层 await import + 兜底:没有宿主依赖时退化为「不加闭合器」,其余逻辑照常可测。
|
|
19
|
-
import { existsSync,
|
|
19
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
20
|
+
import { readFile } from "node:fs/promises";
|
|
20
21
|
import { join } from "node:path";
|
|
21
22
|
import { StringDecoder } from "node:string_decoder";
|
|
22
23
|
|
|
@@ -43,6 +44,11 @@ function withInterruptedClosers(events) {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/** 只读尾部时默认保留的事件行数(提出来是为了让 2000 这个数字有名字)。 */
|
|
48
|
+
const DEFAULT_TAIL_KEEP = 2000;
|
|
49
|
+
/** 单条消息默认截断字符数。 */
|
|
50
|
+
const DEFAULT_MAX_CHARS_PER_ITEM = 800;
|
|
51
|
+
|
|
46
52
|
/**
|
|
47
53
|
* 该子会话是否归属 API remote(不该被插件当普通会话处理)。
|
|
48
54
|
* 等价判定:header.origin==="subagent",或 parent live agent 拥有该 child(agents.isOwnedBy)。
|
|
@@ -166,8 +172,8 @@ export function findSessionLogById(sessionId, root = sessionsRootOf()) {
|
|
|
166
172
|
export async function readColdSessionSummary(ctx, sessionId, opts = {}) {
|
|
167
173
|
const file = findSessionLogById(sessionId, opts.root ?? sessionsRootOf());
|
|
168
174
|
if (!file) return null;
|
|
169
|
-
const tailKeep = Number.isSafeInteger(opts.tailKeep) && opts.tailKeep >= 0 ? opts.tailKeep :
|
|
170
|
-
const buf =
|
|
175
|
+
const tailKeep = Number.isSafeInteger(opts.tailKeep) && opts.tailKeep >= 0 ? opts.tailKeep : DEFAULT_TAIL_KEEP;
|
|
176
|
+
const buf = await readFile(file); // 压缩态(最大 ~50 MB),不是解压态
|
|
171
177
|
const decoder = new StringDecoder("utf8");
|
|
172
178
|
let pending = "";
|
|
173
179
|
let meta = null;
|
|
@@ -268,8 +274,8 @@ export async function readRecentUserTurns(ctx, sessionId, opts = {}) {
|
|
|
268
274
|
const file = findSessionLogById(sessionId, opts.root ?? sessionsRootOf());
|
|
269
275
|
if (!file) return null;
|
|
270
276
|
const rounds = Number.isSafeInteger(opts.rounds) && opts.rounds > 0 ? opts.rounds : 3;
|
|
271
|
-
const perItem = Number.isSafeInteger(opts.maxCharsPerItem) && opts.maxCharsPerItem > 0 ? opts.maxCharsPerItem :
|
|
272
|
-
const buf =
|
|
277
|
+
const perItem = Number.isSafeInteger(opts.maxCharsPerItem) && opts.maxCharsPerItem > 0 ? opts.maxCharsPerItem : DEFAULT_MAX_CHARS_PER_ITEM;
|
|
278
|
+
const buf = await readFile(file);
|
|
273
279
|
const decoder = new StringDecoder("utf8");
|
|
274
280
|
let pending = "";
|
|
275
281
|
const turns = [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// dsh-session-conductor —
|
|
1
|
+
// dsh-session-conductor — 回合中断判定(自动续跑资格 + 标题状态后缀共用)
|
|
2
2
|
//
|
|
3
3
|
// 【为什么放在 sessions 域】「这个会话是不是中断了、能不能自动续」是会话自身的属性判定,
|
|
4
4
|
// 既被续跑域用(是否触发续跑),也被标题域用(是否加「已中断」后缀)——放在中立位置避免相互依赖。
|
|
@@ -20,7 +20,7 @@ export function interruptionInfo(events) {
|
|
|
20
20
|
const reason = event.data?.reason;
|
|
21
21
|
if (reason === null || typeof reason !== "object") return null;
|
|
22
22
|
if (reason.kind === "interrupted") {
|
|
23
|
-
return { kind: "interrupted", seq: event.seq, message: "
|
|
23
|
+
return { kind: "interrupted", seq: event.seq, message: "回合因进程中断/运行失败而未完成(崩溃修复)" };
|
|
24
24
|
}
|
|
25
25
|
if (reason.kind === "error") {
|
|
26
26
|
const code = typeof reason.error?.code === "string" ? reason.error.code : "UNKNOWN";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// 会话「便宜元信息」的唯一查找入口。
|
|
2
|
+
//
|
|
3
|
+
// 【为什么抽出来】同一件事在两个地方各写了一遍:
|
|
4
|
+
// · send_to_session 的 dryRun 预检(要报「目标是否活跃/在哪个目录」)
|
|
5
|
+
// · 回收站标记删除(要存一份标题/目录快照供回收站列表展示)
|
|
6
|
+
// 两处都是「先看内存里的 live 会话,再看落盘索引」,逻辑重复且口径容易漂。
|
|
7
|
+
//
|
|
8
|
+
// 【铁律】只查这两个来源,**绝不**为了一个标题或目录去跑全量列表构建
|
|
9
|
+
// (冷缓存实测 41~226 秒,曾把 dryRun 预检拖成「发消息前卡死」)。
|
|
10
|
+
// 查不到就返回 null,由调用方如实说「查不到」,不升级成昂贵查询。
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 取会话元信息:live 优先,其次落盘索引。
|
|
14
|
+
* @param {object} ctx 插件上下文
|
|
15
|
+
* @param {string} sessionId 会话 id
|
|
16
|
+
* @returns {Promise<{live:boolean,title:string|null,cwd:string|null,updatedAt:number|null,archived:boolean}|null>}
|
|
17
|
+
*/
|
|
18
|
+
export async function sessionMetaOf(ctx, sessionId) {
|
|
19
|
+
if (typeof sessionId !== "string" || sessionId === "") return null;
|
|
20
|
+
try {
|
|
21
|
+
const live = ctx.get?.("sessions")?.get?.(sessionId);
|
|
22
|
+
if (live) {
|
|
23
|
+
return {
|
|
24
|
+
live: true,
|
|
25
|
+
title: live.title ?? null,
|
|
26
|
+
cwd: live.header?.cwd ?? null,
|
|
27
|
+
updatedAt: null,
|
|
28
|
+
archived: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// live 查询不可用:退回落盘索引
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const { loadListDiskCache } = await import("./list-cache.js");
|
|
36
|
+
const entry = loadListDiskCache(ctx)?.entries?.[sessionId];
|
|
37
|
+
if (entry) {
|
|
38
|
+
return {
|
|
39
|
+
live: false,
|
|
40
|
+
title: entry.title ?? null,
|
|
41
|
+
cwd: entry.cwd ?? null,
|
|
42
|
+
updatedAt: typeof entry.updatedAt === "number" ? entry.updatedAt : null,
|
|
43
|
+
archived: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// 索引不可用:按「查不到」处理
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
package/lib/sessions/route.js
CHANGED
|
@@ -40,7 +40,7 @@ export function defaultModelSelection(ctx) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* 折叠日志里最后一次 `model/selection` 事件(切模型意图——官方 selectForNextRequest 也写这个事件)。
|
|
44
44
|
* 【为什么】resume 时若只用「最后请求头」当选择,会把切换覆盖回原模型
|
|
45
45
|
* (表现为「切了只生效一个回合」);优先用该事件可与官方 selectionFor 的投影恢复保持一致。
|
|
46
46
|
* @returns {{provider: string, model: string} | null}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// 文件级原子操作 —— 全仓唯一实现。
|
|
2
|
+
//
|
|
3
|
+
// 【为什么抽出来】「写 tmp → 复制原文件权限 → rename 覆盖」这段在 zstd-frames(帧修复、截断修复)
|
|
4
|
+
// 与 session-ops(撤回消息写回)各写了一遍,共 3 份;备份那段(已存在不覆盖)也重复了 3 次。
|
|
5
|
+
// 这类代码一旦哪份漏了 chmod 或 rename,表现是「文件权限被重置」或「留下半个日志」,
|
|
6
|
+
// 而且都发生在**修复会话日志**这种最不能出错的路径上 —— 收敛成一处才好审。
|
|
7
|
+
//
|
|
8
|
+
// 说明:CIFS/网络盘上 chmod 可能直接失败,因此权限保留是「尽力而为」,失败不影响替换本身。
|
|
9
|
+
|
|
10
|
+
import { writeFileSync, renameSync, chmodSync, statSync, existsSync, copyFileSync } from "node:fs";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 原子替换文件内容:写 tmp → 继承原文件权限 → rename 覆盖。
|
|
14
|
+
* @param {string} targetPath 目标文件
|
|
15
|
+
* @param {string|Buffer} data 新内容
|
|
16
|
+
* @param {{tmpSuffix?: string}} [opts] tmp 后缀(默认 .tmp,便于排查是哪条路径留下的)
|
|
17
|
+
* @returns {string} 实际使用的 tmp 路径(已被 rename,仅供日志)
|
|
18
|
+
*/
|
|
19
|
+
export function replaceFileSync(targetPath, data, { tmpSuffix = ".tmp" } = {}) {
|
|
20
|
+
const tmpPath = targetPath + tmpSuffix;
|
|
21
|
+
writeFileSync(tmpPath, data);
|
|
22
|
+
try {
|
|
23
|
+
chmodSync(tmpPath, statSync(targetPath).mode);
|
|
24
|
+
} catch {
|
|
25
|
+
// CIFS/网络盘无 chmod:权限沿用默认,替换本身照做
|
|
26
|
+
}
|
|
27
|
+
renameSync(tmpPath, targetPath);
|
|
28
|
+
return tmpPath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 备份一次(目标已存在则不覆盖,避免二次操作把原始备份冲掉)。
|
|
33
|
+
* @returns {string} 备份路径
|
|
34
|
+
*/
|
|
35
|
+
export function ensureBackupSync(srcPath, backupPath) {
|
|
36
|
+
if (!existsSync(backupPath)) copyFileSync(srcPath, backupPath);
|
|
37
|
+
return backupPath;
|
|
38
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// 原子 JSON 读写 —— 全仓唯一实现。
|
|
2
|
+
//
|
|
3
|
+
// 【为什么抽出来】config / index-store / memory-recall 三处各写了一遍「写 tmp → rename」:
|
|
4
|
+
// · 任何一处漏掉 rename 都会留下半个文件(下次启动读到坏 JSON);
|
|
5
|
+
// · tmp 文件名若不带 pid,两个进程/两次并发写会互相踩;
|
|
6
|
+
// · 每处还要各自处理「目录不存在」「文件损坏」。
|
|
7
|
+
// 现在统一到这里,调用方只管传 file + data。
|
|
8
|
+
//
|
|
9
|
+
// 同步版给「一次性初始化 / CLI」用;异步版给「请求路径」用(避免阻塞事件循环)。
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { readFile, writeFile, rename, mkdir } from "node:fs/promises";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
/** tmp 文件名带上 pid,避免并发写互相覆盖。 */
|
|
16
|
+
function tmpPathOf(file) {
|
|
17
|
+
return `${file}.tmp-${process.pid}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 同步原子写:先写 tmp 再 rename(rename 在同一文件系统上是原子的)。失败向上抛。 */
|
|
21
|
+
export function writeJsonAtomicSync(file, data) {
|
|
22
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
23
|
+
const tmpPath = tmpPathOf(file);
|
|
24
|
+
writeFileSync(tmpPath, JSON.stringify(data), "utf8");
|
|
25
|
+
renameSync(tmpPath, file);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 异步原子写:语义同上,给请求路径用。失败向上抛。 */
|
|
29
|
+
export async function writeJsonAtomic(file, data) {
|
|
30
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
31
|
+
const tmpPath = tmpPathOf(file);
|
|
32
|
+
await writeFile(tmpPath, JSON.stringify(data), "utf8");
|
|
33
|
+
await rename(tmpPath, file);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 同步读 JSON:文件不存在 / 损坏 / 不是对象 → 返回 null(绝不抛)。 */
|
|
37
|
+
export function readJsonSafeSync(file) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
40
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 异步读 JSON:语义同上。 */
|
|
47
|
+
export async function readJsonSafe(file) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
50
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// 分阶段计时 —— 只做观测,不改变任何行为。
|
|
2
|
+
//
|
|
3
|
+
// 【为什么需要】「切模型要等很久」这类问题,光看总耗时没法定位:一次切换里串了
|
|
4
|
+
// 写意图 / 定位成员 / 等 idle / 写会话日志(整份解码+重编码)/ 官方 @Remote 调用 好几段。
|
|
5
|
+
// 每段各自打点,末尾输出一行「写意图 12ms | 等空闲 60000ms | …」,
|
|
6
|
+
// 一眼能看出慢在哪一段。
|
|
7
|
+
//
|
|
8
|
+
// 【为什么不设超时】超时代码会改变行为、还会掩盖真实的慢;这里只测量、只记录。
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 创建分阶段计时器。
|
|
12
|
+
* @returns {{mark:(name:string)=>number, summary:()=>string, total:()=>number, stages:()=>Array<{name:string,ms:number}>}}
|
|
13
|
+
*/
|
|
14
|
+
export function createStageTimer() {
|
|
15
|
+
const marks = [];
|
|
16
|
+
let lastAt = Date.now();
|
|
17
|
+
return {
|
|
18
|
+
/** 记录一个阶段(从上次打点到现在)。 */
|
|
19
|
+
mark(name) {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
marks.push({ name, ms: now - lastAt });
|
|
22
|
+
lastAt = now;
|
|
23
|
+
return now;
|
|
24
|
+
},
|
|
25
|
+
/** 一行汇总,如「写意图 12ms | 等空闲 60000ms」。 */
|
|
26
|
+
summary() {
|
|
27
|
+
return marks.map((m) => `${m.name} ${m.ms}ms`).join(" | ");
|
|
28
|
+
},
|
|
29
|
+
/** 各阶段耗时合计(不含未被标记覆盖的空隙)。 */
|
|
30
|
+
total() {
|
|
31
|
+
return marks.reduce((sum, m) => sum + m.ms, 0);
|
|
32
|
+
},
|
|
33
|
+
stages() {
|
|
34
|
+
return marks.slice();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
package/lib/zstd-frames.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// fixZstdFile(多帧正确写回,原子+备份)/ scanAllCorruptFrames(全仓扫描损坏)。
|
|
7
7
|
// 与 fix-zstd-frames-v2.mjs 同逻辑,DSH_HOME 参数化,供插件 API 与 CLI 共用。
|
|
8
8
|
|
|
9
|
-
import fs, { readFileSync,
|
|
9
|
+
import fs, { readFileSync, mkdirSync, statSync, readdirSync, existsSync } from 'node:fs';
|
|
10
|
+
import { replaceFileSync, ensureBackupSync } from "./shared/atomic-file.js";
|
|
10
11
|
import { join, dirname } from 'node:path';
|
|
11
12
|
import { findSessionLog } from './session-log.js';
|
|
12
13
|
import { createZstdDecompress, zstdCompress, constants } from 'node:zlib';
|
|
@@ -184,11 +185,8 @@ export async function fixZstdFile(path, backupDir = join(dirname(path), '.zstd-f
|
|
|
184
185
|
mkdirSync(backupDir, { recursive: true });
|
|
185
186
|
const rel = path.replace(/^.*\/sessions\//, '').replace(/\//g, '__');
|
|
186
187
|
const bak = join(backupDir, rel);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
writeFileSync(fixedTmpPath, newBuf);
|
|
190
|
-
try { chmodSync(fixedTmpPath, statSync(path).mode); } catch { /* 尽力 */ }
|
|
191
|
-
renameSync(fixedTmpPath, path);
|
|
188
|
+
ensureBackupSync(path, bak);
|
|
189
|
+
replaceFileSync(path, newBuf, { tmpSuffix: '.fixtmp' });
|
|
192
190
|
return { ok: true, id: header.id, eventCount: events.length, fixed: true, backup: bak, origSize: origBuf.length, newSize: newBuf.length };
|
|
193
191
|
}
|
|
194
192
|
|
|
@@ -238,11 +236,8 @@ async function truncateToDecodablePrefix(path, buf, originalError, backupDir) {
|
|
|
238
236
|
mkdirSync(backupDir, { recursive: true });
|
|
239
237
|
const rel = path.replace(/^.*\/sessions\//, '').replace(/\//g, '__');
|
|
240
238
|
const bak = join(backupDir, rel);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
writeFileSync(tmp, buf.subarray(0, safeEnd));
|
|
244
|
-
try { chmodSync(tmp, statSync(path).mode); } catch { /* 尽力 */ }
|
|
245
|
-
renameSync(tmp, path);
|
|
239
|
+
ensureBackupSync(path, bak);
|
|
240
|
+
replaceFileSync(path, buf.subarray(0, safeEnd), { tmpSuffix: '.fixtmp' });
|
|
246
241
|
return {
|
|
247
242
|
ok: true,
|
|
248
243
|
id: head.header.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-conductor",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.11",
|
|
4
4
|
"description": "DSH session management enhancement: archive/restore sessions, batch & conditional delete, AI auto-rename, interrupted-session auto-resume, release idle sessions, workspace grouping, undo last message, repair damaged session logs, value analysis, full-text search, per-member model switching (list_models / set_member_model), and plan/closing template injection (system-prompt + context).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -188,7 +188,7 @@ whenToUse: 需要了解 dsh-session-conductor 某个功能怎么用/参数是什
|
|
|
188
188
|
2. 建新会话:`list_models()` 查确切 provider/model → `create_session({provider, model, dryRun:true})` 预览 → 确认后去掉 dryRun 真建
|
|
189
189
|
3. 给已有会话发消息:`send_to_session({sessionId, text, dryRun:true})` 预览 → 确认后真投
|
|
190
190
|
|
|
191
|
-
- **`create_session` 缺省工作目录 = 调用方会话自己的 cwd**(发起它的那个 AI 在哪干活,新会话就落在哪)。**不要**依赖本体默认:那是 DSH 安装目录(实测
|
|
191
|
+
- **`create_session` 缺省工作目录 = 调用方会话自己的 cwd**(发起它的那个 AI 在哪干活,新会话就落在哪)。**不要**依赖本体默认:那是 DSH 安装目录(实测 `<DSH 安装目录>`),新会话落在程序目录里对干活毫无意义。显式给 `cwd` 或 `workspaceId` 可覆盖(二者互斥)。
|
|
192
192
|
- **dryRun(两个写操作工具都支持)**:只做校验与预览、**不产生任何副作用**。`create_session` 的 dryRun 还会**核对 provider/model 是否真在模型目录里**;`send_to_session` 的 dryRun 会**报告目标状态与限流余量**,且**不消耗限流配额**(可以先 dryRun 多次再真投)。dryRun 同样执行全部本地校验(自投/超长/跳数等照常被拒)。
|
|
193
193
|
- **状态含义**(`list_sessions` 输出):`running` = 正在跑回合;`idle` = 已挂载但空闲;`inactive` = 冷会话(未挂载,投递时会被唤醒)。缺 id 的脏条目会被跳过并如实标注(不会渲染成 `undefined`)。
|
|
194
194
|
- **会话列表的内存边界(实测,别再踩)**:宿主的 `handle.read(offset, length)` **不能流式读** —— offset/length 只是**内存数组切片**,后端每次调用都会把整份日志读进来解压(`session-persistence-jsonl/src/storage.ts` 的 `readCurrent`)。实测单份日志 49.2 MB 压缩 → **173.3 MB 解压**(3.2~5.0×)。
|
|
@@ -128,7 +128,7 @@ pnpm add github:EIGHTfs/dsh-session-conductor#<commit>
|
|
|
128
128
|
1. **本机要有 `npm`** —— pnpm 构建 git 来源的包时会在临时目录执行 `npm install`;
|
|
129
129
|
缺 npm 时报 `ERR_PNPM_PREPARE_PACKAGE` + `spawn ENOENT: npm: command not found`。
|
|
130
130
|
(本机 2026-10-12 已装 npm 10.9.2:解包到 `<APP>/lib/node_modules/npm`,再链到
|
|
131
|
-
|
|
131
|
+
`<DSH 安装目录>/bin/npm` —— 系统级 bin 目录不可写。)
|
|
132
132
|
2. **pnpm 11 默认禁止 git 源依赖执行构建脚本**(供应链策略):只有带 `prepare`/`prepack` 的插件
|
|
133
133
|
需要放行,在 profile 的 `pnpm-workspace.yaml` 里加,否则报 `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`:
|
|
134
134
|
|