dsh-session-mng 0.1.3 → 0.2.0
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 +51 -0
- package/README.md +31 -15
- package/ROADMAP.md +20 -12
- package/client.js +1360 -344
- package/docs/01-design.md +49 -2
- package/docs/02-open-source-and-marketplace.md +24 -0
- package/docs/HANDOVER-2026-10-29.md +105 -0
- package/docs/PLAN-2026-10-29.md +245 -0
- package/docs/RELEASE-CHECKLIST-0.1.3.md +7 -5
- package/docs/RELEASE-CHECKLIST-0.1.4.md +34 -0
- package/docs/RELEASE-CHECKLIST-0.2.0.md +76 -0
- package/lib/api.js +44 -11
- package/lib/catalog.js +773 -0
- package/lib/service.js +456 -0
- package/lib/state.js +46 -25
- package/lib/store-jsonl.js +76 -2
- package/package.json +1 -1
package/lib/store-jsonl.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import zlib from 'node:zlib'
|
|
23
23
|
import { homedir } from 'node:os'
|
|
24
|
-
import { dirname, join } from 'node:path'
|
|
24
|
+
import { basename, dirname, join } from 'node:path'
|
|
25
25
|
import { access, cp, mkdir, open, readdir, readFile, rename, rm, stat } from 'node:fs/promises'
|
|
26
26
|
|
|
27
27
|
import { SessionMngError, messageOf } from './errors.js'
|
|
@@ -160,7 +160,81 @@ export async function currentLogFile(dir) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
if (best === undefined) return undefined
|
|
163
|
-
return { path: join(dir, best.name), name: best.name, compressed: best.compressed }
|
|
163
|
+
return { path: join(dir, best.name), name: best.name, compressed: best.compressed, generation: bestGeneration }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Size and format facts of one stored session's current log, at the cost of
|
|
168
|
+
* exactly one readdir plus one stat.
|
|
169
|
+
*
|
|
170
|
+
* The directory comes from the backend's own zero-I/O locator, then
|
|
171
|
+
* {@link currentLogFile} picks the highest released generation inside it. That
|
|
172
|
+
* is deliberately NOT `findSessionDirs`/`stat(id)`: both fall back to scanning
|
|
173
|
+
* every project directory, which must never happen on a page request. The
|
|
174
|
+
* format status is inferred by comparing the selected generation with the one
|
|
175
|
+
* `locate()` names as current, so an older-generation log is reported honestly
|
|
176
|
+
* instead of being labelled current.
|
|
177
|
+
*
|
|
178
|
+
* @param {object} persistence - the host's JSONL persistence service.
|
|
179
|
+
* @param {string} id - stored session id.
|
|
180
|
+
* @param {string} [cwd] - the session's stored working directory.
|
|
181
|
+
* @returns {Promise<{sizeBytes: number, formatStatus: string, logName: string}|undefined>}
|
|
182
|
+
*/
|
|
183
|
+
export async function readLogFacts(persistence, id, cwd) {
|
|
184
|
+
let located
|
|
185
|
+
try {
|
|
186
|
+
located = persistence?.locate?.({ id, cwd })
|
|
187
|
+
} catch {
|
|
188
|
+
return undefined
|
|
189
|
+
}
|
|
190
|
+
const path = typeof located?.path === 'string' ? located.path : ''
|
|
191
|
+
if (path === '') return undefined
|
|
192
|
+
const currentGeneration = generationOf(basename(path))
|
|
193
|
+
const log = await currentLogFile(dirname(path))
|
|
194
|
+
if (log === undefined) return undefined
|
|
195
|
+
let size
|
|
196
|
+
try {
|
|
197
|
+
size = (await stat(log.path)).size
|
|
198
|
+
} catch {
|
|
199
|
+
return undefined
|
|
200
|
+
}
|
|
201
|
+
const migrationRequired = currentGeneration !== undefined && log.generation < currentGeneration
|
|
202
|
+
return {
|
|
203
|
+
sizeBytes: size,
|
|
204
|
+
formatStatus: migrationRequired ? 'migration-required' : 'current',
|
|
205
|
+
logName: log.name,
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Maximum concurrent per-log stat batches; small enough to stay polite. */
|
|
210
|
+
const FACT_CONCURRENCY = 8
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Read {@link readLogFacts} for a bounded batch (one page at most in practice).
|
|
214
|
+
*
|
|
215
|
+
* @param {object} persistence
|
|
216
|
+
* @param {{id: string, cwd?: string}[]} items
|
|
217
|
+
* @param {number} [concurrency]
|
|
218
|
+
* @returns {Promise<Map<string, {sizeBytes: number, formatStatus: string, logName: string}>>}
|
|
219
|
+
*/
|
|
220
|
+
export async function readLogFactsBatch(persistence, items, concurrency = FACT_CONCURRENCY) {
|
|
221
|
+
const found = new Map()
|
|
222
|
+
if (!Array.isArray(items) || items.length === 0) return found
|
|
223
|
+
let next = 0
|
|
224
|
+
const worker = async () => {
|
|
225
|
+
for (;;) {
|
|
226
|
+
const index = next
|
|
227
|
+
next += 1
|
|
228
|
+
if (index >= items.length) return
|
|
229
|
+
const item = items[index]
|
|
230
|
+
if (typeof item?.id !== 'string') continue
|
|
231
|
+
const facts = await readLogFacts(persistence, item.id, item.cwd).catch(() => undefined)
|
|
232
|
+
if (facts !== undefined) found.set(item.id, facts)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const width = Math.max(1, Math.min(Math.trunc(concurrency) || 1, items.length))
|
|
236
|
+
await Promise.all(Array.from({ length: width }, worker))
|
|
237
|
+
return found
|
|
164
238
|
}
|
|
165
239
|
|
|
166
240
|
/** Every offset where a Zstandard frame magic begins. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-mng",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|