@heixui/dsh-session-suite 0.8.1 → 0.8.2
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/client.js +13 -48
- package/lib/memory-engine.js +64 -0
- package/lib/service.js +12 -0
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -3437,20 +3437,12 @@ window.__ModuleLoader__.load({
|
|
|
3437
3437
|
|
|
3438
3438
|
// Which models can be called, loaded from the Host rather than from config.
|
|
3439
3439
|
//
|
|
3440
|
-
// The list
|
|
3441
|
-
//
|
|
3442
|
-
//
|
|
3443
|
-
//
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
let alive = true
|
|
3447
|
-
call({ action: 'longterm.models' })
|
|
3448
|
-
.then(list => alive && setModels(Array.isArray(list) ? list : []))
|
|
3449
|
-
.catch(() => alive && setModels([]))
|
|
3450
|
-
return () => {
|
|
3451
|
-
alive = false
|
|
3452
|
-
}
|
|
3453
|
-
}, [rev])
|
|
3440
|
+
// The model list is no longer fetched.
|
|
3441
|
+
//
|
|
3442
|
+
// It existed only to populate the "model for automatic extraction" picker, which
|
|
3443
|
+
// is gone along with the background extraction pipeline. Asking the Host for the
|
|
3444
|
+
// full model catalogue on every panel refresh was a real cost for a control that
|
|
3445
|
+
// no longer exists.
|
|
3454
3446
|
|
|
3455
3447
|
const detected = useWorkspacePath()
|
|
3456
3448
|
|
|
@@ -3785,40 +3777,13 @@ window.__ModuleLoader__.load({
|
|
|
3785
3777
|
//
|
|
3786
3778
|
// This has to be an explicit choice. An earlier build silently fell back to
|
|
3787
3779
|
// the Host's default model whenever no chain was configured, which spends the
|
|
3788
|
-
//
|
|
3789
|
-
//
|
|
3790
|
-
//
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
h(
|
|
3796
|
-
'select',
|
|
3797
|
-
{
|
|
3798
|
-
className: 'dss-select',
|
|
3799
|
-
value: settings?.implicitModel ?? 'off',
|
|
3800
|
-
disabled: busy === 'implicitModel',
|
|
3801
|
-
onChange: event =>
|
|
3802
|
-
run('implicitModel', () =>
|
|
3803
|
-
call({ action: 'longterm.settings', patch: { implicitModel: event.target.value } }),
|
|
3804
|
-
),
|
|
3805
|
-
},
|
|
3806
|
-
h('option', { value: 'off' }, t('memory.implicitModelOff')),
|
|
3807
|
-
h('option', { value: 'current' }, t('memory.implicitModelCurrent')),
|
|
3808
|
-
// Real models from the Host, grouped by provider so a long list stays
|
|
3809
|
-
// readable. `models` is null while loading and [] when the Host reports
|
|
3810
|
-
// none — both render no group, which is honest: the two above are the
|
|
3811
|
-
// only choices that exist without a callable model.
|
|
3812
|
-
...(Array.isArray(models) ? models : []).map(entry =>
|
|
3813
|
-
h(
|
|
3814
|
-
'option',
|
|
3815
|
-
{ value: `${entry.provider}/${entry.model}`, key: `${entry.provider}/${entry.model}` },
|
|
3816
|
-
entry.label ?? `${entry.provider}/${entry.model}`,
|
|
3817
|
-
),
|
|
3818
|
-
),
|
|
3819
|
-
),
|
|
3820
|
-
h('div', { className: 'dss-curate-hint' }, t('memory.implicitModelHint')),
|
|
3821
|
-
),
|
|
3780
|
+
// The "model for automatic extraction" selector is REMOVED.
|
|
3781
|
+
//
|
|
3782
|
+
// It configured which model a background per-turn extraction call would use.
|
|
3783
|
+
// That pipeline no longer exists: memories are written by the model calling the
|
|
3784
|
+
// `remember` tool mid-conversation, so there is no separate model to choose and
|
|
3785
|
+
// no background cost to authorise. A control that configures nothing is worse
|
|
3786
|
+
// than no control — it implies a cost that is not being incurred.
|
|
3822
3787
|
h(
|
|
3823
3788
|
'button',
|
|
3824
3789
|
{
|
package/lib/memory-engine.js
CHANGED
|
@@ -1725,6 +1725,70 @@ export class MemoryEngine {
|
|
|
1725
1725
|
return { removed }
|
|
1726
1726
|
}
|
|
1727
1727
|
|
|
1728
|
+
/**
|
|
1729
|
+
* Remove every durable fact that came from one session.
|
|
1730
|
+
*
|
|
1731
|
+
* Deleting a session used to leave its long-term memories behind: the per-session
|
|
1732
|
+
* ledger was purged (lib/memory.js) but the durable library was not, because the two
|
|
1733
|
+
* are different stores. A fact carries `sessionId` of the conversation that produced
|
|
1734
|
+
* it, so those rows survived as orphans — invisible in the session, still injected
|
|
1735
|
+
* into later prompts, and impossible to attribute once the session was gone.
|
|
1736
|
+
*
|
|
1737
|
+
* Scope matters: only entries whose `sessionId` matches are removed. Facts the user
|
|
1738
|
+
* promoted to `global` deliberately, and facts with no sessionId at all (imported, or
|
|
1739
|
+
* written by the tool outside a session), are left alone.
|
|
1740
|
+
*
|
|
1741
|
+
* @param {string} sessionId session whose facts should go
|
|
1742
|
+
* @returns {Promise<{ removed: number, workspaces: number }>} what was deleted
|
|
1743
|
+
*/
|
|
1744
|
+
async purgeSession(sessionId) {
|
|
1745
|
+
const target = String(sessionId ?? '')
|
|
1746
|
+
if (!target) return { removed: 0, workspaces: 0 }
|
|
1747
|
+
|
|
1748
|
+
let removed = 0
|
|
1749
|
+
let workspaces = 0
|
|
1750
|
+
|
|
1751
|
+
/** @param {string} scope @param {string} key @returns {Promise<number>} rows removed */
|
|
1752
|
+
const purgeOne = async (scope, key) => {
|
|
1753
|
+
const store = await this.#readStore(scope, key)
|
|
1754
|
+
if (!Array.isArray(store.entries) || store.entries.length === 0) return 0
|
|
1755
|
+
const kept = store.entries.filter(entry => String(entry.sessionId ?? '') !== target)
|
|
1756
|
+
const dropped = store.entries.length - kept.length
|
|
1757
|
+
if (dropped === 0) return 0
|
|
1758
|
+
store.entries = kept
|
|
1759
|
+
await this.#writeStore(scope, key, store)
|
|
1760
|
+
|
|
1761
|
+
// The cold store holds compressed/archived rows; they belong to the same fact and
|
|
1762
|
+
// must go too, or the session's content survives in a quieter place.
|
|
1763
|
+
const cold = await this.#readCold(scope, key)
|
|
1764
|
+
if (Array.isArray(cold.entries) && cold.entries.length > 0) {
|
|
1765
|
+
const coldKept = cold.entries.filter(entry => String(entry.sessionId ?? '') !== target)
|
|
1766
|
+
if (coldKept.length !== cold.entries.length) {
|
|
1767
|
+
await this.#writeCold(scope, key, { version: 2, scope, entries: coldKept })
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
return dropped
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// Global store first: a fact written from this session may have been promoted.
|
|
1774
|
+
removed += await purgeOne('global', 'global')
|
|
1775
|
+
|
|
1776
|
+
// Then every registered workspace. Enumerating them (rather than purging only the
|
|
1777
|
+
// caller's cwd) is deliberate: the session may have written into another workspace.
|
|
1778
|
+
const registered = await this.workspaces().catch(() => [])
|
|
1779
|
+
for (const workspace of registered) {
|
|
1780
|
+
const key = typeof workspace?.key === 'string' ? workspace.key : undefined
|
|
1781
|
+
if (!key) continue
|
|
1782
|
+
const dropped = await purgeOne('workspace', key)
|
|
1783
|
+
if (dropped > 0) {
|
|
1784
|
+
removed += dropped
|
|
1785
|
+
workspaces += 1
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
return { removed, workspaces }
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1728
1792
|
/**
|
|
1729
1793
|
* @param {object} [input] export input
|
|
1730
1794
|
* @param {string} [input.cwd] workspace
|
package/lib/service.js
CHANGED
|
@@ -884,6 +884,18 @@ export class SessionSuiteService {
|
|
|
884
884
|
removed.checkpoints = true
|
|
885
885
|
await this.memory.purge(sessionId).catch(() => {})
|
|
886
886
|
removed.memory = true
|
|
887
|
+
// The durable library is a SEPARATE store from the per-session ledger above, and it
|
|
888
|
+
// was never purged — so deleting a session left its long-term facts behind as
|
|
889
|
+
// orphans: still injected into later prompts, and unattributable once the session
|
|
890
|
+
// was gone. Facts carry `sessionId`, so they can be removed precisely.
|
|
891
|
+
//
|
|
892
|
+
// Guarded separately: a Host without the long-term engine must still be able to
|
|
893
|
+
// delete a session.
|
|
894
|
+
try {
|
|
895
|
+
removed.durableMemory = await this.longTerm?.purgeSession?.(sessionId)
|
|
896
|
+
} catch (error) {
|
|
897
|
+
this.log(`could not purge durable memory for ${sessionId}`, error)
|
|
898
|
+
}
|
|
887
899
|
for (const undo of await this.listUndos(sessionId)) await this.#discardUndo(undo.id).catch(() => {})
|
|
888
900
|
|
|
889
901
|
// Finally, drop the archive mark itself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heixui/dsh-session-suite",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Session checkpoints for DSH: rewind a conversation with its files and memory, permanent session deletion, and shareable exports",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|