@try-works/dsh-memory 0.1.4 → 0.1.6
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/package.json +3 -2
- package/src/client/index.js +108 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Git-backed, workspace-scoped memory for the DeepSeek Harness (global layer + per-workspace layers)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"client": {
|
|
49
49
|
"platform": "web",
|
|
50
50
|
"inject": [
|
|
51
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
52
53
|
],
|
|
53
54
|
"external": []
|
|
54
55
|
}
|
package/src/client/index.js
CHANGED
|
@@ -338,6 +338,77 @@ window.__ModuleLoader__.load({
|
|
|
338
338
|
const bodyStyle = { padding: '16px', display: 'flex', flexDirection: 'column', gap: '14px' }
|
|
339
339
|
return box(cardStyle, [head, box(Object.assign({ key: 'b' }, bodyStyle), body)]);
|
|
340
340
|
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* The Memory tab: this session's memory activity, in one place.
|
|
344
|
+
*
|
|
345
|
+
* Its data is the most recent memory_map result in this session. That is not a
|
|
346
|
+
* convenience: a browser half cannot ask the host for anything, so the session record is
|
|
347
|
+
* the only channel, and a tool result carries its structured value on `meta` (ui-chat's
|
|
348
|
+
* conversation-nodes/tool.ts does `meta: match.event.data.meta`).
|
|
349
|
+
*
|
|
350
|
+
* READING IT: a conversation view gets data through a registered standard hook, so the
|
|
351
|
+
* plugin provides `memoryMap` via ctx.uiSession.provide and this component receives
|
|
352
|
+
* `useMemoryMap` as a prop, the way ui-tool's rows receive their history hook. The hook
|
|
353
|
+
* resolves by scanning the Trajectory target's event nodes for a settled memory_map tool
|
|
354
|
+
* call; the Trajectory target is the shipped assembly of this session's nodes, and its
|
|
355
|
+
* snapshot contract is eventNodes: ConversationNode[] with { kind: 'tool', root }.
|
|
356
|
+
*/
|
|
357
|
+
function findMemorySnapshot(snapshot) {
|
|
358
|
+
if (!snapshot || !Array.isArray(snapshot.eventNodes)) return null
|
|
359
|
+
// Newest first: the tab describes the session as it stands now, not as it first was.
|
|
360
|
+
for (let i = snapshot.eventNodes.length - 1; i >= 0; i -= 1) {
|
|
361
|
+
const node = snapshot.eventNodes[i]
|
|
362
|
+
const root = node && node.kind === 'tool' ? node.root : null
|
|
363
|
+
if (!root || root.isError) continue
|
|
364
|
+
const name = root.call && root.call.name
|
|
365
|
+
if (name !== 'memory_map') continue
|
|
366
|
+
if (root.meta && typeof root.meta === 'object') return root.meta
|
|
367
|
+
}
|
|
368
|
+
return null
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function MemoryTabView(props) {
|
|
372
|
+
const useMemoryMap = props ? props.useMemoryMap : null
|
|
373
|
+
const meta = typeof useMemoryMap === 'function' ? useMemoryMap() : null
|
|
374
|
+
const page = { padding: '32px 28px 0 28px', display: 'flex', flexDirection: 'column', gap: '0' }
|
|
375
|
+
if (!meta) {
|
|
376
|
+
// Nothing to draw yet, and saying so beats drawing an empty chart that reads as zero.
|
|
377
|
+
const empty = { display: 'flex', flexDirection: 'column', gap: '10px', maxWidth: '520px' }
|
|
378
|
+
return box(page, [box(empty, [
|
|
379
|
+
react.createElement('div', { key: 'h', style: { fontFamily: GEIST, fontSize: '15px', fontWeight: 500, color: INK } }, 'No memory snapshot in this session yet'),
|
|
380
|
+
react.createElement('div', { key: 'p', style: { fontFamily: GEIST, fontSize: '13px', lineHeight: 1.6, color: MUTED } }, 'This tab shows what memory put in front of this conversation, what the conversation wrote back, and the state of the store. It fills in once memory_map runs in this session.'),
|
|
381
|
+
])]);
|
|
382
|
+
}
|
|
383
|
+
const store = meta.store || null
|
|
384
|
+
const session = meta.session || null
|
|
385
|
+
const parts = []
|
|
386
|
+
if (session) {
|
|
387
|
+
const who = session.workspace ? 'workspace ' + session.workspace : 'global layer'
|
|
388
|
+
parts.push(react.createElement('div', { key: 'id', style: { fontFamily: GEIST, fontSize: '13px', color: '#3A3A38', paddingBottom: '26px' } }, 'This session · ' + who));
|
|
389
|
+
if (session.received) parts.push(box({ key: 'r', display: 'flex', flexDirection: 'column', gap: '12px', maxWidth: '460px' }, [react.createElement(ReceivedPanel, { key: 'p', received: session.received })]));
|
|
390
|
+
if (session.written) parts.push(box({ key: 'w', marginTop: '34px', paddingTop: '26px', borderTop: '1px solid ' + LINE, display: 'flex', flexDirection: 'column' }, [react.createElement(WrittenPanel, { key: 'p', written: session.written })]));
|
|
391
|
+
}
|
|
392
|
+
if (store) {
|
|
393
|
+
const attention = (store.queue && store.queue.attention) || []
|
|
394
|
+
const chips = attention.slice(0, 3).map(function (row) { return react.createElement(Chip, { key: row.state + row.reason, text: fmt(row.count) + ' ' + row.state, hot: true }) });
|
|
395
|
+
if (store.queue && store.queue.waiting) chips.push(react.createElement(Chip, { key: 'waiting', text: fmt(store.queue.waiting) + ' waiting' }));
|
|
396
|
+
parts.push(box({ key: 's', marginTop: '34px', paddingTop: '26px', borderTop: '1px solid ' + LINE, display: 'flex', flexDirection: 'column', gap: '15px' }, [
|
|
397
|
+
sectionLabel('SNAPSHOT', fmt(store.total) + ' entries · ' + fmt(store.scopeTotal) + ' scopes'),
|
|
398
|
+
react.createElement(LayerBar, { key: 'bar', layers: store.layers, height: '30px' }),
|
|
399
|
+
react.createElement(Legend, { key: 'leg', layers: store.layers }),
|
|
400
|
+
box({ key: 'f', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '16px' }, [
|
|
401
|
+
box({ key: 'st', display: 'flex', gap: '18px' }, [
|
|
402
|
+
react.createElement(Stat, { key: 'a', value: fmt(store.freshness.fresh), label: 'fresh' }),
|
|
403
|
+
store.freshness.stale ? react.createElement(Stat, { key: 'b', value: fmt(store.freshness.stale), label: 'stale', color: RUST }) : null,
|
|
404
|
+
store.freshness.skip ? react.createElement(Stat, { key: 'c', value: fmt(store.freshness.skip), label: 'superseded' }) : null,
|
|
405
|
+
]),
|
|
406
|
+
box({ key: 'q', display: 'flex', gap: '5px', flexWrap: 'wrap' }, chips),
|
|
407
|
+
]),
|
|
408
|
+
]));
|
|
409
|
+
}
|
|
410
|
+
return box(page, parts);
|
|
411
|
+
}
|
|
341
412
|
/** Fields rendered on this page, in order. */
|
|
342
413
|
const FIELDS = [
|
|
343
414
|
{ key: 'memoryRoot', label: 'Memory root', hint: 'Local folder or drive that holds the store.', placeholder: 'D:\\Memory' },
|
|
@@ -547,18 +618,54 @@ window.__ModuleLoader__.load({
|
|
|
547
618
|
name: 'tool.call.toolview',
|
|
548
619
|
key: 'memory_map',
|
|
549
620
|
}, MemoryMapCard)))
|
|
621
|
+
// The settings page registers FIRST, and deliberately. It is the established surface;
|
|
622
|
+
// the tab below is newer and depends on two services the page does not need. Ordering
|
|
623
|
+
// the proven surface before the unproven one means a failure in the tab cannot take the
|
|
624
|
+
// settings page down with it - both halves share one file and one apply().
|
|
550
625
|
disposers.push(ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
551
626
|
name: 'settings.section',
|
|
552
627
|
id: 'memory',
|
|
553
628
|
order: 80,
|
|
554
629
|
label: 'Memory',
|
|
555
630
|
}, () => react.createElement(MemorySettings))))
|
|
631
|
+
// The Memory tab. Data arrives through a registered standard hook, because a
|
|
632
|
+
// conversation view is a slot and slots are fed by hooks, not by direct reads.
|
|
633
|
+
// The Trajectory target is the shipped assembly of this session's nodes, so the
|
|
634
|
+
// source re-derives the memory snapshot from it on every projection.
|
|
635
|
+
try {
|
|
636
|
+
ctx.effect(() => ctx.uiSession.provide({
|
|
637
|
+
hooks: ['memoryMap'],
|
|
638
|
+
resolve: (binding) => {
|
|
639
|
+
const target = ctx.uiConversation.binding(binding).target('trajectory')
|
|
640
|
+
return {
|
|
641
|
+
hooks: {
|
|
642
|
+
memoryMap: {
|
|
643
|
+
getSnapshot: () => findMemorySnapshot(target.getSnapshot()),
|
|
644
|
+
subscribe: (listener) => target.subscribe(listener),
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
}), 'dsh-memory: memory map hook')
|
|
650
|
+
disposers.push(ctx.slots.inject('conversation.view', () => ctx.slots.register({
|
|
651
|
+
name: 'conversation.view',
|
|
652
|
+
id: 'memory',
|
|
653
|
+
// ui-chat holds 0 and ui-trajectory 10, so Memory sits after both without
|
|
654
|
+
// reordering anyone else's tab.
|
|
655
|
+
order: 20,
|
|
656
|
+
label: 'Memory',
|
|
657
|
+
}, MemoryTabView)))
|
|
658
|
+
} catch (error) {
|
|
659
|
+
// Loud, not silent. A tab that fails to register is invisible by definition, so this
|
|
660
|
+
// warning is the only trace the failure can leave.
|
|
661
|
+
ctx.logger.warn('dsh-memory: Memory tab unavailable: ' + (error && error.message ? error.message : String(error)))
|
|
662
|
+
}
|
|
556
663
|
return () => { for (const dispose of disposers) dispose() }
|
|
557
664
|
}, 'dsh-memory: settings section')
|
|
558
665
|
}
|
|
559
666
|
|
|
560
667
|
exports.name = 'dsh-memory'
|
|
561
|
-
exports.inject = ['slots', 'configForms', 'remote.session']
|
|
668
|
+
exports.inject = ['slots', 'configForms', 'remote.session', 'uiSession', 'uiConversation']
|
|
562
669
|
exports.apply = apply
|
|
563
670
|
return module.exports
|
|
564
671
|
},
|