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/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
- /** Whether a header/summary pair describes a subagent session. */
144
- function isSubagent(header, summary) {
145
- if (header?.origin === 'subagent' || summary?.origin === 'subagent') return true
146
- return Boolean(header?.parentSession ?? summary?.parentSessionId)
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 subagent = isSubagent(header, summary)
212
- if (subagent && !includeSubagents) return
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
- parentSessionId: header?.parentSession ?? summary.parentSessionId,
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 && !row.isSubagent && row.parentSessionId === undefined,
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.2",
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://github.com/ZilongYang/dsh-session-mng#readme",
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
  }