dsh-session-mng 0.1.2 → 0.1.4
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 +42 -1
- package/README.md +20 -11
- package/ROADMAP.md +18 -3
- package/client.js +293 -65
- package/docs/01-design.md +23 -13
- package/docs/02-open-source-and-marketplace.md +15 -0
- package/docs/HANDOVER-2026-10-27.md +6 -6
- package/docs/PLAN-2026-10-28-branch-visibility.md +116 -0
- package/docs/PLAN-2026-10-28-legacy-fork-safety.md +62 -0
- package/docs/RELEASE-CHECKLIST-0.1.2.md +7 -7
- package/docs/RELEASE-CHECKLIST-0.1.3.md +40 -0
- package/docs/RELEASE-CHECKLIST-0.1.4.md +33 -0
- package/lib/service.js +64 -16
- package/lib/state.js +54 -9
- package/package.json +4 -3
package/lib/state.js
CHANGED
|
@@ -140,10 +140,31 @@ function headerOf(row, snapshotsById, ctx) {
|
|
|
140
140
|
return snapshotsById.get(row.sessionId)?.header
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Classify one session from whatever identifying fields are available.
|
|
145
|
+
*
|
|
146
|
+
* Callers pass a stored header, a list summary, or a merged view, so both
|
|
147
|
+
* field spellings are accepted (`parentSession` on a header, `parentSessionId`
|
|
148
|
+
* on a summary). Keeping this in one place is what makes the listing, the
|
|
149
|
+
* delete scope and the move guard agree on what an ordinary fork is: only
|
|
150
|
+
* delegation (`origin: 'subagent'` or a delegation depth) marks an internal
|
|
151
|
+
* session. A parent alone is not enough — an ordinary fork carries one too,
|
|
152
|
+
* and historical forks still record it.
|
|
153
|
+
*/
|
|
154
|
+
export function classifySession(fields) {
|
|
155
|
+
if (fields?.origin === 'subagent') return 'subagent'
|
|
156
|
+
if (typeof fields?.delegationDepth === 'number' && fields.delegationDepth >= 1) return 'subagent'
|
|
157
|
+
const parent = fields?.parentSession ?? fields?.parentSessionId
|
|
158
|
+
return parent === undefined || parent === null || parent === '' ? 'root' : 'fork'
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Classify one row of the listing from its header and summary. */
|
|
162
|
+
function classifyKind(header, summary) {
|
|
163
|
+
return classifySession({
|
|
164
|
+
origin: header?.origin ?? summary?.origin,
|
|
165
|
+
delegationDepth: header?.delegationDepth ?? summary?.delegationDepth,
|
|
166
|
+
parentSession: header?.parentSession ?? summary?.parentSessionId,
|
|
167
|
+
})
|
|
147
168
|
}
|
|
148
169
|
|
|
149
170
|
/**
|
|
@@ -201,6 +222,7 @@ export async function buildState(ctx, config, options = {}) {
|
|
|
201
222
|
for (const id of snapshotById.keys()) knownIds.add(id)
|
|
202
223
|
const rows = []
|
|
203
224
|
const missingTitles = []
|
|
225
|
+
let hiddenSubagents = 0
|
|
204
226
|
|
|
205
227
|
const pushRow = (base) => {
|
|
206
228
|
const id = base.sessionId
|
|
@@ -208,8 +230,11 @@ export async function buildState(ctx, config, options = {}) {
|
|
|
208
230
|
seen.add(id)
|
|
209
231
|
const header = headerOf(base, snapshotById, ctx)
|
|
210
232
|
const summary = base
|
|
211
|
-
const
|
|
212
|
-
if (subagent && !includeSubagents)
|
|
233
|
+
const kind = classifyKind(header, summary)
|
|
234
|
+
if (kind === 'subagent' && !includeSubagents) {
|
|
235
|
+
hiddenSubagents += 1
|
|
236
|
+
return
|
|
237
|
+
}
|
|
213
238
|
|
|
214
239
|
const cwd = summary.cwd ?? header?.cwd ?? ''
|
|
215
240
|
const workspace =
|
|
@@ -234,13 +259,15 @@ export async function buildState(ctx, config, options = {}) {
|
|
|
234
259
|
running,
|
|
235
260
|
blank: summary.blank === true,
|
|
236
261
|
origin: header?.origin ?? summary.origin,
|
|
237
|
-
|
|
262
|
+
kind,
|
|
263
|
+
isSubagent: kind === 'subagent',
|
|
264
|
+
isFork: kind === 'fork',
|
|
265
|
+
parentSessionId: header?.parentSession ?? summary.parentSessionId ?? null,
|
|
238
266
|
agentPreset: header?.agentPreset ?? '',
|
|
239
267
|
createdAt: header?.createdAt ?? summary.updatedAt ?? 0,
|
|
240
268
|
updatedAt: summary.updatedAt ?? header?.createdAt ?? 0,
|
|
241
269
|
sizeBytes: typeof snapshot?.sizeBytes === 'number' ? snapshot.sizeBytes : 0,
|
|
242
270
|
formatStatus: summary.formatStatus ?? snapshot?.formatStatus,
|
|
243
|
-
isSubagent: subagent,
|
|
244
271
|
})
|
|
245
272
|
}
|
|
246
273
|
|
|
@@ -268,6 +295,22 @@ export async function buildState(ctx, config, options = {}) {
|
|
|
268
295
|
if (row.title === '') row.title = titles.get(row.id) ?? ''
|
|
269
296
|
}
|
|
270
297
|
|
|
298
|
+
// A fork points at its parent by id only; resolving the title here lets the
|
|
299
|
+
// list show the relationship without a second round trip. `parentPresent`
|
|
300
|
+
// lets the client tell "untitled parent" apart from "the parent session is
|
|
301
|
+
// gone" (possible after the parent is permanently deleted).
|
|
302
|
+
const titlesById = new Map(rows.map((row) => [row.id, row.title]))
|
|
303
|
+
const forkCountByParent = new Map()
|
|
304
|
+
for (const row of rows) {
|
|
305
|
+
row.parentTitle = row.kind === 'fork' ? (titlesById.get(row.parentSessionId) ?? '') : ''
|
|
306
|
+
row.parentPresent = row.kind === 'fork' && titlesById.has(row.parentSessionId)
|
|
307
|
+
if (row.kind !== 'fork' || row.parentSessionId === null) continue
|
|
308
|
+
forkCountByParent.set(row.parentSessionId, (forkCountByParent.get(row.parentSessionId) ?? 0) + 1)
|
|
309
|
+
}
|
|
310
|
+
for (const row of rows) {
|
|
311
|
+
row.forkCount = forkCountByParent.get(row.id) ?? 0
|
|
312
|
+
}
|
|
313
|
+
|
|
271
314
|
const jsonl =
|
|
272
315
|
typeof ctx.sessionPersistence?.locate === 'function' && typeof ctx.sessionPersistence?.stat === 'function'
|
|
273
316
|
const capabilities = {
|
|
@@ -282,7 +325,7 @@ export async function buildState(ctx, config, options = {}) {
|
|
|
282
325
|
title: capabilities.title,
|
|
283
326
|
archive: !row.archived,
|
|
284
327
|
restore: row.archived,
|
|
285
|
-
move: capabilities.move && !row.live && !row.running &&
|
|
328
|
+
move: capabilities.move && !row.live && !row.running && row.kind === 'root',
|
|
286
329
|
delete: capabilities.delete && row.archived && !row.live,
|
|
287
330
|
open: true,
|
|
288
331
|
}
|
|
@@ -323,6 +366,8 @@ export async function buildState(ctx, config, options = {}) {
|
|
|
323
366
|
active: rows.filter((row) => !row.archived).length,
|
|
324
367
|
archived: rows.filter((row) => row.archived).length,
|
|
325
368
|
subagents: rows.filter((row) => row.isSubagent).length,
|
|
369
|
+
hiddenSubagents,
|
|
370
|
+
forks: rows.filter((row) => row.isFork).length,
|
|
326
371
|
bytes: totalBytes,
|
|
327
372
|
},
|
|
328
373
|
limits: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-mng",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Session management for DeepSeek Harness: one Settings page for every session — whole-session title generation, archive/restore, cross-workspace move, and permanent deletion of archived sessions.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/ZilongYang/dsh-session-mng.git"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://
|
|
13
|
+
"homepage": "https://dsh.zlmix.com/session-mng/",
|
|
14
14
|
"bugs": {
|
|
15
15
|
"url": "https://github.com/ZilongYang/dsh-session-mng/issues"
|
|
16
16
|
},
|
|
@@ -71,7 +71,8 @@
|
|
|
71
71
|
"compatibility": {
|
|
72
72
|
"dsh": ">=0.2.0-rc.1 <0.3.0-0",
|
|
73
73
|
"dshReleases": {
|
|
74
|
-
"0.2.1-rc.2": "compatible"
|
|
74
|
+
"0.2.1-rc.2": "compatible",
|
|
75
|
+
"0.2.1-rc.3": "compatible"
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
}
|