dsh-aris-panel 0.1.8 → 0.1.9
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/dsh/workbench.mjs +130 -3
- package/package.json +1 -1
package/dsh/workbench.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { readdir, readFile, stat } from 'node:fs/promises'
|
|
|
11
11
|
import { isAbsolute, join } from 'node:path'
|
|
12
12
|
import { homedir } from 'node:os'
|
|
13
13
|
import { DatabaseSync } from 'node:sqlite'
|
|
14
|
+
import { zstdDecompressSync } from 'node:zlib'
|
|
14
15
|
|
|
15
16
|
export const WORKBENCH_RPC_CHANNEL = '/aris-wb'
|
|
16
17
|
|
|
@@ -236,6 +237,122 @@ async function readProjCache() {
|
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Persisted-session metadata fallback (titles / message counts / last activity
|
|
242
|
+
// for sessions that are not live and missing from the projection cache).
|
|
243
|
+
// The durable log is a concatenation of independent Zstandard frames, each
|
|
244
|
+
// storing one event batch (see dsh-session-persistence-jsonl).
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
const DSH_HOME = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
248
|
+
const SESSIONS_DIR = join(DSH_HOME, 'sessions')
|
|
249
|
+
const ZSTD_MAGIC = 4247762216
|
|
250
|
+
const persistedMetaCache = new Map() // sid -> { at, title, msgCount, updatedAt }
|
|
251
|
+
|
|
252
|
+
/** Split a buffer into complete Zstandard frame ranges (torn tail ignored). */
|
|
253
|
+
function scanZstdFrames(buffer) {
|
|
254
|
+
const frames = []
|
|
255
|
+
let offset = 0
|
|
256
|
+
while (offset < buffer.length) {
|
|
257
|
+
const start = offset
|
|
258
|
+
if (buffer.length - offset < 4 || buffer.readUInt32LE(offset) !== ZSTD_MAGIC) break
|
|
259
|
+
offset += 4
|
|
260
|
+
if (offset === buffer.length) break
|
|
261
|
+
const descriptor = buffer.readUInt8(offset)
|
|
262
|
+
offset += 1
|
|
263
|
+
if ((descriptor & 24) !== 0) break
|
|
264
|
+
const contentSizeFlag = descriptor >>> 6
|
|
265
|
+
const singleSegment = (descriptor & 32) !== 0
|
|
266
|
+
const checksum = (descriptor & 4) !== 0
|
|
267
|
+
const dictionaryFlag = descriptor & 3
|
|
268
|
+
const dictionaryBytes = dictionaryFlag === 3 ? 4 : dictionaryFlag
|
|
269
|
+
const contentSizeBytes = contentSizeFlag === 0 ? (singleSegment ? 1 : 0) : (1 << contentSizeFlag)
|
|
270
|
+
const remainingHeaderBytes = (singleSegment ? 0 : 1) + dictionaryBytes + contentSizeBytes
|
|
271
|
+
if (buffer.length - offset < remainingHeaderBytes) break
|
|
272
|
+
offset += remainingHeaderBytes
|
|
273
|
+
for (;;) {
|
|
274
|
+
if (buffer.length - offset < 3) return frames
|
|
275
|
+
const blockHeader = buffer.readUIntLE(offset, 3)
|
|
276
|
+
offset += 3
|
|
277
|
+
const lastBlock = (blockHeader & 1) !== 0
|
|
278
|
+
const blockType = (blockHeader >>> 1) & 3
|
|
279
|
+
const blockSize = blockHeader >>> 3
|
|
280
|
+
if (blockType === 3) return frames
|
|
281
|
+
const payloadBytes = blockType === 1 ? 1 : blockSize
|
|
282
|
+
if (buffer.length - offset < payloadBytes) return frames
|
|
283
|
+
offset += payloadBytes
|
|
284
|
+
if (lastBlock) break
|
|
285
|
+
}
|
|
286
|
+
if (checksum) {
|
|
287
|
+
if (buffer.length - offset < 4) return frames
|
|
288
|
+
offset += 4
|
|
289
|
+
}
|
|
290
|
+
frames.push({ start, end: offset })
|
|
291
|
+
}
|
|
292
|
+
return frames
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Decompress the full multi-frame zstd event log to text. */
|
|
296
|
+
function decompressSessionLog(buffer) {
|
|
297
|
+
const frames = scanZstdFrames(buffer)
|
|
298
|
+
if (frames.length === 0) return undefined
|
|
299
|
+
try {
|
|
300
|
+
const parts = []
|
|
301
|
+
for (const f of frames) parts.push(zstdDecompressSync(buffer.subarray(f.start, f.end)))
|
|
302
|
+
return Buffer.concat(parts).toString('utf8')
|
|
303
|
+
} catch {
|
|
304
|
+
return undefined
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Best-effort title / user-message count / last-activity from the durable log. */
|
|
309
|
+
async function readPersistedSessionMeta(sid, cwd) {
|
|
310
|
+
const cached = persistedMetaCache.get(sid)
|
|
311
|
+
if (cached && Date.now() - cached.at < 60000) return cached
|
|
312
|
+
const out = { at: Date.now(), title: undefined, msgCount: 0, updatedAt: undefined }
|
|
313
|
+
try {
|
|
314
|
+
let dir
|
|
315
|
+
if (cwd) {
|
|
316
|
+
const bucket = '--' + String(cwd).replace(/[\\/:]+/g, '-') + '--'
|
|
317
|
+
const cand = join(SESSIONS_DIR, bucket, sid)
|
|
318
|
+
try { if ((await stat(cand)).isDirectory()) dir = cand } catch { /* miss */ }
|
|
319
|
+
}
|
|
320
|
+
if (!dir) {
|
|
321
|
+
try {
|
|
322
|
+
const buckets = await readdir(SESSIONS_DIR)
|
|
323
|
+
for (const b of buckets) {
|
|
324
|
+
const cand = join(SESSIONS_DIR, b, sid)
|
|
325
|
+
try {
|
|
326
|
+
if ((await stat(cand)).isDirectory()) { dir = cand; break }
|
|
327
|
+
} catch { /* miss */ }
|
|
328
|
+
}
|
|
329
|
+
} catch { /* ignore */ }
|
|
330
|
+
}
|
|
331
|
+
if (!dir) return out
|
|
332
|
+
const buffer = await readFile(join(dir, 'session.jsonl.zstd'))
|
|
333
|
+
const text = decompressSessionLog(buffer)
|
|
334
|
+
if (typeof text === 'string') {
|
|
335
|
+
let msgCount = 0
|
|
336
|
+
let title
|
|
337
|
+
let updatedAt
|
|
338
|
+
for (const line of text.split('\n')) {
|
|
339
|
+
if (!line) continue
|
|
340
|
+
let ev
|
|
341
|
+
try { ev = JSON.parse(line) } catch { continue }
|
|
342
|
+
if (!ev || typeof ev !== 'object') continue
|
|
343
|
+
if (typeof ev.time === 'number' && (updatedAt === undefined || ev.time > updatedAt)) updatedAt = ev.time
|
|
344
|
+
if (ev.type === 'user/message') msgCount++
|
|
345
|
+
else if (ev.type === 'session/title' && ev.data && typeof ev.data.title === 'string' && ev.data.title.trim() !== '') title = ev.data.title
|
|
346
|
+
}
|
|
347
|
+
out.title = title
|
|
348
|
+
out.msgCount = msgCount
|
|
349
|
+
out.updatedAt = updatedAt
|
|
350
|
+
}
|
|
351
|
+
} catch { /* ignore */ }
|
|
352
|
+
persistedMetaCache.set(sid, out)
|
|
353
|
+
return out
|
|
354
|
+
}
|
|
355
|
+
|
|
239
356
|
|
|
240
357
|
export function registerWorkbench(ctx) {
|
|
241
358
|
ctx.inject(['connection', 'sessions', 'agents', 'workspaceRegistry', 'sessionPersistence'], (scoped) => {
|
|
@@ -287,11 +404,21 @@ export function registerWorkbench(ctx) {
|
|
|
287
404
|
const a = reg.archivedSessionIds
|
|
288
405
|
archived = Array.isArray(a) ? a : (typeof a === 'function' ? a() : [])
|
|
289
406
|
} catch (e) { /* ignore */ }
|
|
290
|
-
const list = typeof reg.list === 'function' ? reg.list().map(function (w) {
|
|
407
|
+
const list = typeof reg.list === 'function' ? await Promise.all(reg.list().map(async function (w) {
|
|
291
408
|
const sids = (Array.isArray(w.sessionIds) ? w.sessionIds : []).filter(function (sid) { return archived.indexOf(sid) === -1 })
|
|
292
|
-
const sessions = sids.map(
|
|
409
|
+
const sessions = await Promise.all(sids.map(async function (sid) {
|
|
410
|
+
const out = sessionMetaOf(sid)
|
|
411
|
+
if (out.title === undefined || out.msgCount === 0) {
|
|
412
|
+
// 历史会话不在内存、也可能不在投影缓存:从持久化日志补标题/消息数/最近活动
|
|
413
|
+
const pm = await readPersistedSessionMeta(sid, w.path)
|
|
414
|
+
if (out.title === undefined && pm.title !== undefined) out.title = pm.title
|
|
415
|
+
if (out.msgCount === 0 && pm.msgCount > 0) out.msgCount = pm.msgCount
|
|
416
|
+
if (out.updatedAt === undefined && pm.updatedAt !== undefined) out.updatedAt = pm.updatedAt
|
|
417
|
+
}
|
|
418
|
+
return out
|
|
419
|
+
}))
|
|
293
420
|
return { id: w.id, path: w.path, title: w.title, sessions: sessions }
|
|
294
|
-
}) : []
|
|
421
|
+
})) : []
|
|
295
422
|
return { ok: true, value: { workspaces: list } }
|
|
296
423
|
} catch (error) {
|
|
297
424
|
return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: { issues: [] } } }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-aris-panel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "ARIS research-workflow skills + interactive workbench panel for DeepSeek Harness (user fork of dsh-aris: 82 skills, cross-model Codex review, idea/workflow launcher, research status board).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|