@try-works/dsh-memory 0.2.1 → 0.2.3
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 +1 -1
- package/src/client/index.js +109 -3
- package/src/store.js +1 -1
- package/src/system-doc.js +2 -1
package/package.json
CHANGED
package/src/client/index.js
CHANGED
|
@@ -97,6 +97,30 @@ window.__ModuleLoader__.load({
|
|
|
97
97
|
' opacity: 0.6;',
|
|
98
98
|
' cursor: default;',
|
|
99
99
|
'}',
|
|
100
|
+
'.dsm-subtitle {',
|
|
101
|
+
' margin: 4px 0 2px;',
|
|
102
|
+
' font-size: 13px;',
|
|
103
|
+
' line-height: 20px;',
|
|
104
|
+
' font-weight: 500;',
|
|
105
|
+
' color: var(--dsw-alias-label-primary);',
|
|
106
|
+
'}',
|
|
107
|
+
'.dsm-switch-label {',
|
|
108
|
+
' display: flex;',
|
|
109
|
+
' align-items: center;',
|
|
110
|
+
' gap: 8px;',
|
|
111
|
+
' cursor: pointer;',
|
|
112
|
+
'}',
|
|
113
|
+
'.dsm-check {',
|
|
114
|
+
' width: 14px;',
|
|
115
|
+
' height: 14px;',
|
|
116
|
+
' margin: 0;',
|
|
117
|
+
' flex-shrink: 0;',
|
|
118
|
+
' accent-color: var(--dsw-alias-brand-primary);',
|
|
119
|
+
'}',
|
|
120
|
+
'.dsm-check:disabled {',
|
|
121
|
+
' opacity: 0.6;',
|
|
122
|
+
' cursor: default;',
|
|
123
|
+
'}',
|
|
100
124
|
'.dsm-actions {',
|
|
101
125
|
' display: flex;',
|
|
102
126
|
' align-items: center;',
|
|
@@ -648,6 +672,37 @@ window.__ModuleLoader__.load({
|
|
|
648
672
|
{ key: 'gitToken', label: 'Access token (optional)', kind: 'secret', hint: 'Personal access token for pushing. Stored by the host and never sent back to this page, so the field is always empty: type a new token to replace it, and leave it empty to keep the current one.' },
|
|
649
673
|
]
|
|
650
674
|
|
|
675
|
+
/**
|
|
676
|
+
* Asset-store fields, in their own card. Sizes are EDITED in MB/GB but WRITTEN in bytes,
|
|
677
|
+
* because the config stores exact byte counts and nobody should have to type 1073741824.
|
|
678
|
+
* The order matters: the master switch comes first, then the classes it governs.
|
|
679
|
+
*/
|
|
680
|
+
const ASSET_UNITS = { MB: 1024 * 1024, GB: 1024 * 1024 * 1024 }
|
|
681
|
+
const ASSET_FIELDS = [
|
|
682
|
+
{ key: 'assetsEnabled', kind: 'switch', defaultOn: false, label: 'Store files as assets', hint: 'Master switch. While this is off every asset tool refuses and nothing is captured; turning it off is never a deletion, stored assets stay readable and searchable.' },
|
|
683
|
+
{ key: 'assetsDocuments', kind: 'switch', defaultOn: true, label: 'Documents', hint: 'PDF, DOCX, PPTX, TXT and Markdown files.' },
|
|
684
|
+
{ key: 'assetsMedia', kind: 'switch', defaultOn: true, label: 'Media', hint: 'PNG, JPEG, WebP and GIF. An image attached to a message is stored automatically when this is on.' },
|
|
685
|
+
{ key: 'assetsData', kind: 'switch', defaultOn: true, label: 'Data', hint: 'CSV, TSV, JSON and XLSX files.' },
|
|
686
|
+
{ key: 'assetsDescribe', kind: 'switch', defaultOn: true, label: 'Describe each capture', hint: 'One model call writes what the file is and when to read it. Off keeps the deterministic fallback only.' },
|
|
687
|
+
{ key: 'assetsMaxBytes', kind: 'bytes', unit: 'MB', defaultBytes: 50 * 1024 * 1024, label: 'Maximum file size (MB)', hint: 'A larger file is refused with its size, not truncated.' },
|
|
688
|
+
{ key: 'assetsWorkspaceQuotaBytes', kind: 'bytes', unit: 'GB', defaultBytes: 1024 * 1024 * 1024, label: 'Workspace quota (GB)', hint: 'Total asset bytes per workspace. Over quota a capture asks you what to do; nothing is pruned.' },
|
|
689
|
+
{ key: 'assetsGlobalQuotaBytes', kind: 'bytes', unit: 'GB', defaultBytes: 1024 * 1024 * 1024, label: 'Global quota (GB)', hint: 'Total asset bytes in the global scope.' },
|
|
690
|
+
]
|
|
691
|
+
|
|
692
|
+
/** A saved byte count in the unit the page shows; '' when there is no saved value. */
|
|
693
|
+
function assetSizeValue(bytes, unit) {
|
|
694
|
+
const n = Number(bytes)
|
|
695
|
+
if (!Number.isFinite(n) || n <= 0) return ''
|
|
696
|
+
return String(Math.round((n / ASSET_UNITS[unit]) * 100) / 100)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** The page's unit back to bytes; undefined when the input is not a usable number. */
|
|
700
|
+
function assetSizeBytes(text, unit) {
|
|
701
|
+
const n = Number(text)
|
|
702
|
+
if (!Number.isFinite(n) || n < 0) return undefined
|
|
703
|
+
return Math.round(n * ASSET_UNITS[unit])
|
|
704
|
+
}
|
|
705
|
+
|
|
651
706
|
// The settings namespace is the plugin's ENTRY ID, not its package name. The bundle patch mounts
|
|
652
707
|
// the package @try-works/dsh-memory as the row `dsh-memory`, while a hand-written row may use
|
|
653
708
|
// a short id like `memory`. Both must work, so the candidates are tried in order and
|
|
@@ -716,6 +771,45 @@ window.__ModuleLoader__.load({
|
|
|
716
771
|
react.createElement('p', { className: 'dsm-field-help' }, field.hint))
|
|
717
772
|
}
|
|
718
773
|
|
|
774
|
+
/** One labelled checkbox. A switch reads the DRAFT first, then the saved value. */
|
|
775
|
+
function switchField(field, value, draft, edit, disabled) {
|
|
776
|
+
// An unset value falls back to the SCHEMA default, not to alse: showing the three
|
|
777
|
+
// classes as off while the config has them on would describe the plugin wrongly.
|
|
778
|
+
const saved = value[field.key] === undefined ? field.defaultOn === true : value[field.key] === true
|
|
779
|
+
const checked = draft[field.key] !== undefined ? draft[field.key] === true : saved
|
|
780
|
+
return react.createElement('div', { className: 'dsm-field', key: field.key },
|
|
781
|
+
react.createElement('label', { className: 'dsm-switch-label', htmlFor: 'dsm-' + field.key },
|
|
782
|
+
react.createElement('input', {
|
|
783
|
+
id: 'dsm-' + field.key,
|
|
784
|
+
className: 'dsm-check',
|
|
785
|
+
type: 'checkbox',
|
|
786
|
+
checked,
|
|
787
|
+
disabled,
|
|
788
|
+
onChange: (event) => edit(field.key, event.target.checked),
|
|
789
|
+
}),
|
|
790
|
+
react.createElement('span', { className: 'dsm-field-label' }, field.label)),
|
|
791
|
+
react.createElement('p', { className: 'dsm-field-help' }, field.hint))
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** One byte-size field, shown in MB or GB and written back in bytes on save. */
|
|
795
|
+
function bytesField(field, value, draft, edit, disabled) {
|
|
796
|
+
const savedBytes = value[field.key] === undefined ? field.defaultBytes : value[field.key]
|
|
797
|
+
const shown = draft[field.key] !== undefined ? draft[field.key] : assetSizeValue(savedBytes, field.unit)
|
|
798
|
+
return react.createElement('div', { className: 'dsm-field', key: field.key },
|
|
799
|
+
react.createElement('label', { className: 'dsm-field-label', htmlFor: 'dsm-' + field.key }, field.label),
|
|
800
|
+
react.createElement('input', {
|
|
801
|
+
id: 'dsm-' + field.key,
|
|
802
|
+
className: 'dsm-input',
|
|
803
|
+
type: 'number',
|
|
804
|
+
min: '0',
|
|
805
|
+
step: '1',
|
|
806
|
+
value: shown,
|
|
807
|
+
disabled,
|
|
808
|
+
onChange: (event) => edit(field.key, event.target.value),
|
|
809
|
+
}),
|
|
810
|
+
react.createElement('p', { className: 'dsm-field-help' }, field.hint))
|
|
811
|
+
}
|
|
812
|
+
|
|
719
813
|
/** The model picker: the configured catalog plus "session default" and any stale value. */
|
|
720
814
|
function modelField(value, draft, edit, disabled, models) {
|
|
721
815
|
const selected = fieldValue('summaryModel', value, draft)
|
|
@@ -792,13 +886,17 @@ window.__ModuleLoader__.load({
|
|
|
792
886
|
setStatus({ kind: 'busy', text: 'Saving...' })
|
|
793
887
|
try {
|
|
794
888
|
let refused = false
|
|
795
|
-
for (const field of FIELDS.concat([{ key: 'summaryModel' }])) {
|
|
889
|
+
for (const field of FIELDS.concat([{ key: 'summaryModel' }], ASSET_FIELDS)) {
|
|
796
890
|
const next = draft[field.key]
|
|
797
891
|
// An empty draft means 'not edited'. For a secret that is essential: writing an
|
|
798
892
|
// empty value would erase the token, and writing the redacted snapshot value
|
|
799
893
|
// would replace it with a placeholder.
|
|
800
|
-
if (next === undefined || next === ''
|
|
801
|
-
|
|
894
|
+
if (next === undefined || next === '') continue
|
|
895
|
+
// A byte-size field is edited in MB/GB and stored in bytes; an unusable number is
|
|
896
|
+
// skipped rather than written as NaN.
|
|
897
|
+
const written = field.kind === 'bytes' ? assetSizeBytes(next, field.unit) : next
|
|
898
|
+
if (written === undefined || written === value[field.key]) continue
|
|
899
|
+
const accepted = await form.set(field.key, written)
|
|
802
900
|
if (accepted === false) refused = true
|
|
803
901
|
}
|
|
804
902
|
setStatus(refused
|
|
@@ -823,6 +921,14 @@ window.__ModuleLoader__.load({
|
|
|
823
921
|
if (field.kind === 'model') return modelField(value, draft, edit, disabled, models)
|
|
824
922
|
return textField(field, value, draft, edit, disabled)
|
|
825
923
|
})),
|
|
924
|
+
react.createElement('div', { className: 'dsm-card' },
|
|
925
|
+
react.createElement('h3', { className: 'dsm-subtitle' }, 'Asset store'),
|
|
926
|
+
react.createElement('p', { className: 'dsm-field-help' },
|
|
927
|
+
'Files the agent keeps as memory: documents, media and data. Each entry records what the file is and when to read it; contents are never indexed. Bytes are stored once by content hash and synced with the store.'),
|
|
928
|
+
...ASSET_FIELDS.map((field) => {
|
|
929
|
+
if (field.kind === 'switch') return switchField(field, value, draft, edit, disabled)
|
|
930
|
+
return bytesField(field, value, draft, edit, disabled)
|
|
931
|
+
})),
|
|
826
932
|
react.createElement('div', { className: 'dsm-actions' },
|
|
827
933
|
react.createElement('button', {
|
|
828
934
|
className: 'dsm-button', type: 'button', disabled, onClick: save,
|
package/src/store.js
CHANGED
|
@@ -7,7 +7,7 @@ import { GLOBAL_LAYERED, LAYER_DIRS, LAYERED } from './entry.js'
|
|
|
7
7
|
import { sanitizeKey, deriveWorkspaceKey } from './workspace-key.js'
|
|
8
8
|
import { workingPreamble } from './working.js'
|
|
9
9
|
|
|
10
|
-
export const LAYER_FILES = ['INDEX.md', 'semantic.md', 'episodic.md', 'procedural.md', 'working.md', 'learnings.md', 'junk.md', 'codebase.md', 'history.md', 'decisions.md', 'state.md']
|
|
10
|
+
export const LAYER_FILES = ['INDEX.md', 'semantic.md', 'episodic.md', 'procedural.md', 'working.md', 'learnings.md', 'junk.md', 'codebase.md', 'history.md', 'decisions.md', 'assets.md', 'state.md']
|
|
11
11
|
|
|
12
12
|
/** Working memory is the current-state head of ONE repo/workspace, never of the
|
|
13
13
|
* whole store: a global working.md would mix unrelated repositories together. */
|
package/src/system-doc.js
CHANGED
|
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
14
14
|
import { LAYER_CODES, LAYER_DIRS, LAYERED, VOLATILITY_DAYS, WORKSPACE_ONLY_LAYERS } from './entry.js'
|
|
15
15
|
|
|
16
16
|
/** Bumped whenever the described behaviour changes, so a stale on-disk copy is detectable. */
|
|
17
|
-
export const SYSTEM_DOC_VERSION =
|
|
17
|
+
export const SYSTEM_DOC_VERSION = 3
|
|
18
18
|
|
|
19
19
|
/** The template ships beside this module; resolve it relative to the module, not the cwd. */
|
|
20
20
|
export function templatePath() {
|
|
@@ -83,6 +83,7 @@ const LAYER_PURPOSE = {
|
|
|
83
83
|
codebase: 'What a module, file or subsystem of THIS repository does. Workspace only.',
|
|
84
84
|
history: 'How the repository changed: one entry per commit, grouped by day. Workspace only.',
|
|
85
85
|
decision: 'A settled choice and why it won: dependencies, conventions, architecture. Available in both scopes.',
|
|
86
|
+
asset: 'A stored file - a document, an image or a data file - documented with what it is and when to read it. The bytes live under assets/objects and are fetched with memory_asset_get; the contents are never indexed.',
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
/**
|