@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 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 used to come from `settings.memoryModels`, which is a patch value —
3441
- // and this profile has no patch entry, so the picker offered only "off" and
3442
- // "use current" while the user had several working providers in
3443
- // Settings → 模型. The Host knows what it can actually call; ask it.
3444
- const [models, setModels] = useState(null)
3445
- useEffect(() => {
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
- // user's tokens once per turn on a model they never picked for this job. A
3789
- // background cost has to be visible and opt-in, so the choice is a control
3790
- // with an explicit "off" that costs nothing.
3791
- h(
3792
- 'div',
3793
- { className: 'dss-curate-field' },
3794
- h('label', { className: 'dss-curate-label' }, t('memory.implicitModel')),
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
  {
@@ -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.1",
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",