solopup-memory 0.21.0-beta.2 → 0.22.0-beta.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/CHANGELOG.md +37 -0
- package/COMPATIBILITY.md +8 -5
- package/DETECTION.md +139 -0
- package/DREAMING-DESIGN.md +167 -0
- package/README.en.md +35 -6
- package/README.md +44 -7
- package/RELEASE.md +36 -0
- package/SECURITY.md +11 -0
- package/context-history.mjs +20 -3
- package/detect.mjs +441 -0
- package/eval/bench-detect.mjs +107 -0
- package/eval/semantic/cases.json +387 -0
- package/eval/semantic-suite.mjs +156 -0
- package/index.mjs +117 -27
- package/managed-policy.mjs +3 -1
- package/package.json +91 -68
- package/privacy.mjs +25 -0
- package/scripts/demo-source-recall.mjs +123 -0
- package/scripts/detect.mjs +69 -0
- package/scripts/dream.mjs +205 -0
- package/scripts/probe-history.mjs +92 -0
- package/scripts/probe-load.mjs +1 -1
- package/scripts/probe-package.mjs +6 -2
- package/scripts/security-check.mjs +173 -0
- package/scripts/soak.mjs +219 -0
- package/scripts/telemetry.mjs +187 -0
- package/scripts/verify.mjs +3 -1
- package/{test-adversarial.mjs → test/test-adversarial.mjs} +5 -5
- package/{test-belief-revision.mjs → test/test-belief-revision.mjs} +7 -7
- package/{test-candidates.mjs → test/test-candidates.mjs} +4 -4
- package/{test-capture-diagnostics.mjs → test/test-capture-diagnostics.mjs} +6 -6
- package/{test-capture-field-lineage.mjs → test/test-capture-field-lineage.mjs} +4 -4
- package/{test-capture-inputs.mjs → test/test-capture-inputs.mjs} +8 -8
- package/{test-capture-jobs.mjs → test/test-capture-jobs.mjs} +5 -5
- package/{test-capture-projections.mjs → test/test-capture-projections.mjs} +12 -12
- package/{test-capture-revision.mjs → test/test-capture-revision.mjs} +6 -6
- package/{test-capture-submission.mjs → test/test-capture-submission.mjs} +3 -3
- package/{test-capture-validation-flow.mjs → test/test-capture-validation-flow.mjs} +5 -5
- package/{test-capture-validation-job.mjs → test/test-capture-validation-job.mjs} +5 -5
- package/{test-capture-validation-lineage.mjs → test/test-capture-validation-lineage.mjs} +2 -2
- package/{test-capture-validation.mjs → test/test-capture-validation.mjs} +2 -2
- package/{test-capture.mjs → test/test-capture.mjs} +20 -20
- package/{test-compaction-compat.mjs → test/test-compaction-compat.mjs} +5 -5
- package/{test-compat.mjs → test/test-compat.mjs} +3 -3
- package/{test-concurrency.mjs → test/test-concurrency.mjs} +3 -3
- package/{test-conflict.mjs → test/test-conflict.mjs} +2 -2
- package/{test-context-history.mjs → test/test-context-history.mjs} +1 -1
- package/{test-control-schema.mjs → test/test-control-schema.mjs} +1 -1
- package/test/test-detect.mjs +125 -0
- package/test/test-dream-plan.mjs +72 -0
- package/test/test-dsh-registration.mjs +111 -0
- package/{test-episodes.mjs → test/test-episodes.mjs} +5 -5
- package/{test-explicit-memory.mjs → test/test-explicit-memory.mjs} +4 -4
- package/{test-field-lineage.mjs → test/test-field-lineage.mjs} +4 -4
- package/{test-history.mjs → test/test-history.mjs} +4 -4
- package/{test-inline-summary.mjs → test/test-inline-summary.mjs} +5 -5
- package/{test-legacy-source-identity.mjs → test/test-legacy-source-identity.mjs} +2 -2
- package/{test-links.mjs → test/test-links.mjs} +4 -4
- package/{test-managed-access.mjs → test/test-managed-access.mjs} +29 -5
- package/{test-managed-activation.mjs → test/test-managed-activation.mjs} +5 -5
- package/{test-meaning.mjs → test/test-meaning.mjs} +6 -6
- package/{test-native-service-lifecycle.mjs → test/test-native-service-lifecycle.mjs} +1 -1
- package/{test-native-write-validation.mjs → test/test-native-write-validation.mjs} +10 -4
- package/{test-packaging.mjs → test/test-packaging.mjs} +32 -5
- package/{test-paired-analysis.mjs → test/test-paired-analysis.mjs} +1 -1
- package/{test-patch.mjs → test/test-patch.mjs} +11 -11
- package/{test-plugin-contract.mjs → test/test-plugin-contract.mjs} +4 -4
- package/test/test-privacy.mjs +65 -0
- package/{test-project-stores.mjs → test/test-project-stores.mjs} +5 -5
- package/{test-publication.mjs → test/test-publication.mjs} +4 -4
- package/{test-restore.mjs → test/test-restore.mjs} +11 -11
- package/{test-retrieval.mjs → test/test-retrieval.mjs} +2 -2
- package/{test-revision-admission.mjs → test/test-revision-admission.mjs} +3 -3
- package/{test-rule-admission.mjs → test/test-rule-admission.mjs} +8 -8
- package/{test-runtime-startup.mjs → test/test-runtime-startup.mjs} +2 -2
- package/{test-schema.mjs → test/test-schema.mjs} +2 -2
- package/{test-security.mjs → test/test-security.mjs} +6 -6
- package/test/test-semantic-suite.mjs +20 -0
- package/{test-service-readiness.mjs → test/test-service-readiness.mjs} +1 -1
- package/{test-smell.mjs → test/test-smell.mjs} +4 -4
- package/{test-snapshots.mjs → test/test-snapshots.mjs} +3 -3
- package/{test-source-completeness.mjs → test/test-source-completeness.mjs} +6 -6
- package/{test-source-key.mjs → test/test-source-key.mjs} +2 -2
- package/{test-source-retirement.mjs → test/test-source-retirement.mjs} +4 -4
- package/{test-status-both.mjs → test/test-status-both.mjs} +12 -4
- package/{test-supersede.mjs → test/test-supersede.mjs} +3 -3
- package/test/test-telemetry.mjs +60 -0
- package/{test-text-admission.mjs → test/test-text-admission.mjs} +6 -6
- package/{test-tool-inputs.mjs → test/test-tool-inputs.mjs} +1 -1
- package/{test-tool-observations.mjs → test/test-tool-observations.mjs} +6 -6
- package/{test-tool-write-guard.mjs → test/test-tool-write-guard.mjs} +3 -3
- package/{test-tool-write-lifecycle.mjs → test/test-tool-write-lifecycle.mjs} +3 -3
- package/{test-usability.mjs → test/test-usability.mjs} +7 -7
- package/{test-validation-source-refs.mjs → test/test-validation-source-refs.mjs} +2 -2
- package/{test-validation-transition-context.mjs → test/test-validation-transition-context.mjs} +1 -1
- package/{test-withdrawal-archives.mjs → test/test-withdrawal-archives.mjs} +6 -6
- package/{test-withdrawal.mjs → test/test-withdrawal.mjs} +2 -2
- package/{test.mjs → test/test.mjs} +4 -4
- package/version.mjs +1 -1
|
@@ -4,10 +4,10 @@ import {mkdtempSync,realpathSync,mkdirSync,writeFileSync,readFileSync,readdirSyn
|
|
|
4
4
|
import {join} from 'node:path'
|
|
5
5
|
import {tmpdir} from 'node:os'
|
|
6
6
|
import {createHash} from 'node:crypto'
|
|
7
|
-
import {apply,storeDirFor,managedMemoryStatus,withdrawManagedSource} from '
|
|
8
|
-
import {writeFixtureMemory} from '
|
|
9
|
-
import {candidateSnapshot} from '
|
|
10
|
-
import {restorationJournalPath} from '
|
|
7
|
+
import {apply,storeDirFor,managedMemoryStatus,withdrawManagedSource} from '../index.mjs'
|
|
8
|
+
import {writeFixtureMemory} from '../fixture-host.mjs'
|
|
9
|
+
import {candidateSnapshot} from '../candidates.mjs'
|
|
10
|
+
import {restorationJournalPath} from '../restoration-lock.mjs'
|
|
11
11
|
const hash=x=>createHash('sha256').update(x).digest('hex')
|
|
12
12
|
async function box(run){const root=realpathSync(mkdtempSync(join(tmpdir(),'status-both-'))),home=join(root,'home'),cwd=join(root,'work'),previous=process.env.DSH_HOME;mkdirSync(home);mkdirSync(cwd);writeFileSync(join(cwd,'.dsh-root'),'');process.env.DSH_HOME=home;const f={root,home,cwd,data:join(home,'memory'),tools:[],effects:[],ctx:{}};f.ctx={systemPrompt:{context(){}},workingDirectory:{get:()=>f.cwd},tools:{register:t=>f.tools.push(t)},on(){},effect(fn){f.effects.push(fn())}};apply(f.ctx,{autoCapture:{enabled:false}});f.status=f.tools.find(t=>t.name==='memory_status');f.exec={agent:{session:{id:'status-owner'}}};try{return await run(f)}finally{for(const fn of f.effects.reverse())fn?.();if(previous===undefined)delete process.env.DSH_HOME;else process.env.DSH_HOME=previous;rmSync(root,{recursive:true,force:true})}}
|
|
13
13
|
function seed(dir,summary,scope='global'){return writeFixtureMemory({dir,summary,type:'user',scope,detail:'Synthetic detail '+summary})}
|
|
@@ -40,3 +40,11 @@ test('restoration guard produces only unavailable source control for either stat
|
|
|
40
40
|
test('both metadata never turns observation discovery into cross-session access',()=>box(async f=>{
|
|
41
41
|
const before=fingerprint(f.data);await assert.rejects(f.status.execute({scope:'both',includeObservations:'yes'},f.exec),/invalid-list-option/);assert.deepEqual(fingerprint(f.data),before);await assert.rejects(f.status.execute({scope:'both',includeObservations:true},f.exec),/source-unavailable/)
|
|
42
42
|
}))
|
|
43
|
+
test('status exposes a bounded L0 lint summary',()=>box(async f=>{
|
|
44
|
+
writeFileSync(join(f.data,'MEMORY.md'),'# 索引\n\n## user\n\n- [nope] 坏类型 — user.md\n')
|
|
45
|
+
const r=await f.status.execute({scope:'global'},f.exec)
|
|
46
|
+
assert.ok(r.lint.errors>=1)
|
|
47
|
+
assert.ok(r.lint.checks.some((check)=>check.id==='I1-invalid-index-line'))
|
|
48
|
+
const text=f.status.output.render({scope:'global'},r).map((item)=>item.text).join('\n')
|
|
49
|
+
assert.match(text,/检测(L0)/)
|
|
50
|
+
}))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {realpathSync} from 'node:fs'
|
|
2
|
-
import {applyWithOwnerFixture as apply} from '
|
|
2
|
+
import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
|
|
3
3
|
/**
|
|
4
|
-
* supersede(版本替代)与来源标记。跑法:node --test ~/.dsh/plugins/memory/test-supersede.mjs
|
|
4
|
+
* supersede(版本替代)与来源标记。跑法:node --test ~/.dsh/plugins/memory/test/test-supersede.mjs
|
|
5
5
|
*
|
|
6
6
|
* 取代是「修正过期事实」的唯一正确做法:再叠一条会让新旧两条同时被注入,
|
|
7
7
|
* 模型看到矛盾信息。cc-memory 把这叫 anti-patch,MemOS 把它建模成 MemCube 的版本关系。
|
|
@@ -13,7 +13,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, existsSync
|
|
|
13
13
|
import { join } from 'node:path'
|
|
14
14
|
import { tmpdir } from 'node:os'
|
|
15
15
|
|
|
16
|
-
import { storeDirFor, writeMemory } from '
|
|
16
|
+
import { storeDirFor, writeMemory } from '../index.mjs'
|
|
17
17
|
|
|
18
18
|
function sandbox() {
|
|
19
19
|
const root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-mem-sup-')))
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { tmpdir } from 'node:os'
|
|
6
|
+
|
|
7
|
+
const { collect } = await import('../scripts/telemetry.mjs')
|
|
8
|
+
|
|
9
|
+
const DAY = 86_400_000
|
|
10
|
+
|
|
11
|
+
function audit(now) {
|
|
12
|
+
const at = (daysAgo) => new Date(now - daysAgo * DAY).toISOString()
|
|
13
|
+
return [
|
|
14
|
+
{ op: 'capture', outcome: 'ok', inserted: 1, at: at(10) },
|
|
15
|
+
{ op: 'capture', outcome: 'ok', inserted: 0, at: at(9) },
|
|
16
|
+
{ op: 'capture', outcome: 'error', at: at(8) },
|
|
17
|
+
{ op: 'capture', outcome: 'error', at: at(7) },
|
|
18
|
+
{ op: 'capture', outcome: 'rejected', at: at(6) },
|
|
19
|
+
{ op: 'capture', outcome: 'skipped', reason: 'short-session-below-threshold', at: at(5) },
|
|
20
|
+
{ op: 'capture', outcome: 'skipped', reason: 'short-session-below-threshold', at: at(4) },
|
|
21
|
+
{ op: 'capture', outcome: 'skipped', reason: 'short-session-below-threshold', at: at(3) },
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('遥测:捕获成功率、零产出天数与短会话跳过都能算出来', () => {
|
|
26
|
+
const root = mkdtempSync(join(tmpdir(), 'dsh-telemetry-'))
|
|
27
|
+
const now = Date.parse('2026-11-08T10:00:00.000Z')
|
|
28
|
+
try {
|
|
29
|
+
writeFileSync(join(root, 'MEMORY.md'), '# 索引\n\n## project\n\n- [project] 一条 — project.md\n')
|
|
30
|
+
mkdirSync(join(root, 'projects', 'v2-x'), { recursive: true })
|
|
31
|
+
writeFileSync(join(root, 'projects', 'v2-x', 'MEMORY.md'), '# 索引\n\n## project\n\n- [project] 两条 — project.md\n')
|
|
32
|
+
writeFileSync(join(root, '.audit.jsonl'), audit(now).map((event) => JSON.stringify(event)).join('\n') + '\n')
|
|
33
|
+
const report = collect(root, now)
|
|
34
|
+
assert.equal(report.capture.total, 8)
|
|
35
|
+
assert.equal(report.capture.skipped, 3)
|
|
36
|
+
assert.equal(report.capture.successRate, 0.25)
|
|
37
|
+
assert.equal(report.output.zeroOutputDays, 10)
|
|
38
|
+
const ids = report.warnings.map((warning) => warning.id)
|
|
39
|
+
assert.ok(ids.includes('T1-low-capture-success'))
|
|
40
|
+
assert.ok(ids.includes('T2-zero-output'))
|
|
41
|
+
assert.ok(ids.includes('T3-short-sessions'))
|
|
42
|
+
assert.equal(report.stores.length, 2)
|
|
43
|
+
} finally {
|
|
44
|
+
rmSync(root, { recursive: true, force: true })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('遥测:没有账本时不编造成功率', () => {
|
|
49
|
+
const root = mkdtempSync(join(tmpdir(), 'dsh-telemetry-empty-'))
|
|
50
|
+
try {
|
|
51
|
+
writeFileSync(join(root, 'MEMORY.md'), '# 索引\n')
|
|
52
|
+
const report = collect(root, Date.parse('2026-11-08T10:00:00.000Z'))
|
|
53
|
+
assert.equal(report.capture.total, 0)
|
|
54
|
+
assert.equal(report.capture.successRate, null)
|
|
55
|
+
assert.equal(report.output.zeroOutputDays, null)
|
|
56
|
+
assert.ok(report.warnings.some((warning) => warning.id === 'T5-no-memory'))
|
|
57
|
+
} finally {
|
|
58
|
+
rmSync(root, { recursive: true, force: true })
|
|
59
|
+
}
|
|
60
|
+
})
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {applyWithValidationFixture as apply} from '
|
|
1
|
+
import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
|
|
2
2
|
import {test} from 'node:test'
|
|
3
3
|
import assert from 'node:assert/strict'
|
|
4
4
|
import {mkdtempSync,realpathSync,mkdirSync,readFileSync,existsSync,rmSync} from 'node:fs'
|
|
5
5
|
import {join} from 'node:path'
|
|
6
6
|
import {tmpdir} from 'node:os'
|
|
7
|
-
import {indexLine, searchStore} from '
|
|
8
|
-
import {prepareMountedFixture,writeFixtureMemory} from '
|
|
9
|
-
import {candidateSnapshot,listCandidates} from '
|
|
10
|
-
import {assessCandidate} from '
|
|
11
|
-
import {normalizeCaptureDiagnostic} from '
|
|
7
|
+
import {indexLine, searchStore} from '../index.mjs'
|
|
8
|
+
import {prepareMountedFixture,writeFixtureMemory} from '../fixture-host.mjs'
|
|
9
|
+
import {candidateSnapshot,listCandidates} from '../candidates.mjs'
|
|
10
|
+
import {assessCandidate} from '../capture-evidence.mjs'
|
|
11
|
+
import {normalizeCaptureDiagnostic} from '../capture-diagnostics.mjs'
|
|
12
12
|
|
|
13
13
|
async function box(run){
|
|
14
14
|
const dir=realpathSync(mkdtempSync(join(tmpdir(),'dsh-text-admission-'))),home=join(dir,'home'),cwd=join(dir,'project'),previous=process.env.DSH_HOME
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import {createToolInputTracker} from '
|
|
3
|
+
import {createToolInputTracker} from '../tool-inputs.mjs'
|
|
4
4
|
const message=(id,kind='user')=>({id,role:'user',source:{kind},content:[{type:'text',text:'Synthetic owner statement '+id}]})
|
|
5
5
|
function fixture(){const tracker=createToolInputTracker(),session={id:'synthetic-session'},agent={session};return{tracker,session,agent,pre:(turn,step,messages)=>tracker.preStep({agent,turn,step,messages}),admit:(m,seq)=>tracker.event(session,{type:'user/message',seq,data:m}),call:(callId,seq,turn=1,step=1)=>tracker.event(session,{type:'tool/call',seq,data:{callId,turn,step}}),exec:callId=>tracker.forExecution({agent,callId})}}
|
|
6
6
|
test('native call binds to the admitted owner event sequence and declares context-only basis',()=>{const f=fixture(),m=message('a');f.pre(1,1,[m]);f.admit(m,10);f.call('call',11);const proof=f.exec('call');assert.equal(proof.basis,'tool-context');assert.equal(proof.evidence[0].messageId,'synthetic-session:10');assert.match(proof.evidence[0].hash,/^[a-f0-9]{64}$/);assert.match(proof.evidence[0].quote,/owner statement/);assert.equal(JSON.stringify(proof).includes('Synthetic owner statement a'),true)})
|
|
@@ -4,12 +4,12 @@ import {mkdtempSync,realpathSync,mkdirSync,readFileSync,writeFileSync,rmSync} fr
|
|
|
4
4
|
import {join} from 'node:path'
|
|
5
5
|
import {tmpdir} from 'node:os'
|
|
6
6
|
import {createHash} from 'node:crypto'
|
|
7
|
-
import {createToolInputTracker} from '
|
|
8
|
-
import {createObservationTracker,normalizeObservationCertificate,observationRoots,argumentFingerprint} from '
|
|
9
|
-
import {apply,searchStore,withdrawManagedSource,indexLine} from '
|
|
10
|
-
import {candidateSnapshot} from '
|
|
11
|
-
import {prepareSourceRetirement,validateRetiredSource} from '
|
|
12
|
-
import {writeFixtureMemory,scriptedNativeValidationLlm} from '
|
|
7
|
+
import {createToolInputTracker} from '../tool-inputs.mjs'
|
|
8
|
+
import {createObservationTracker,normalizeObservationCertificate,observationRoots,argumentFingerprint} from '../tool-observations.mjs'
|
|
9
|
+
import {apply,searchStore,withdrawManagedSource,indexLine} from '../index.mjs'
|
|
10
|
+
import {candidateSnapshot} from '../candidates.mjs'
|
|
11
|
+
import {prepareSourceRetirement,validateRetiredSource} from '../source-retirement.mjs'
|
|
12
|
+
import {writeFixtureMemory,scriptedNativeValidationLlm} from '../fixture-host.mjs'
|
|
13
13
|
const hash=value=>createHash('sha256').update(value).digest('hex')
|
|
14
14
|
function fixture(){
|
|
15
15
|
const inputs=createToolInputTracker(),session={id:'native-observation-session',deriveMessages:()=>messages},agent={session},obs=createObservationTracker((session,callId)=>inputs.forExecution({agent:{session},callId})),messages=[]
|
|
@@ -5,9 +5,9 @@ import assert from 'node:assert/strict'
|
|
|
5
5
|
import {mkdtempSync,mkdirSync,rmSync,existsSync,readFileSync} from 'node:fs'
|
|
6
6
|
import {join} from 'node:path'
|
|
7
7
|
import {tmpdir} from 'node:os'
|
|
8
|
-
import {apply,storeDirFor} from '
|
|
9
|
-
import {candidateSnapshot} from '
|
|
10
|
-
import {scriptedNativeValidationLlm} from '
|
|
8
|
+
import {apply,storeDirFor} from '../index.mjs'
|
|
9
|
+
import {candidateSnapshot} from '../candidates.mjs'
|
|
10
|
+
import {scriptedNativeValidationLlm} from '../fixture-host.mjs'
|
|
11
11
|
async function box(run){const root=realpathSync(mkdtempSync(join(tmpdir(),'tool-write-guard-'))),home=join(root,'home'),previous=process.env.DSH_HOME;mkdirSync(home);process.env.DSH_HOME=home;const handlers=[],tools=[],effects=[],ctx={llm:scriptedNativeValidationLlm(),systemPrompt:{context(){}},workingDirectory:{get:()=>root},tools:{register:tool=>tools.push(tool)},on:(name,handler)=>handlers.push([name,handler]),effect:fn=>{const dispose=fn();if(typeof dispose==='function')effects.push(dispose)}};apply(ctx,{autoCapture:{enabled:false}});const session={id:'synthetic-owner-session'},agent={session,options:{provider:'fixture',model:'synthetic'}},tool=tools.find(tool=>tool.name==='memory_write'),emit=(name,...args)=>{for(const [event,handler] of handlers)if(event===name)handler(...args)},input={id:'owner-input',source:{kind:'user'},role:'user',content:[{type:'text',text:'Synthetic owner statement jade9876'}]},exec={agent,callId:'native-call',rootCallId:'native-call'};const admit=()=>{emit('agent/pre-step',{agent,turn:1,step:1,messages:[input]},()=>({kind:'enter'}));emit('session/event',session,{type:'user/message',seq:10,data:input});emit('session/event',session,{type:'tool/call',seq:11,data:{turn:1,step:1,callId:'native-call'}})};try{return await run({root,dir:join(home,'memory'),tool,exec,emit,input,admit,effects,session})}finally{for(const dispose of effects)dispose();if(previous===undefined)delete process.env.DSH_HOME;else process.env.DSH_HOME=previous;rmSync(root,{recursive:true,force:true})}}
|
|
12
12
|
const args={meaning:{kind:'claim'},type:'user',scope:'global',summary:'Synthetic selected jade9876',detail:'Synthetic detail jade9876'}
|
|
13
13
|
test('SDK tool rejects missing owner history, actor and forged model-supplied evidence',()=>box(async f=>{for(const exec of [f.exec,{}, {agent:{session:{id:'synthetic-other'}}}])await assert.rejects(()=>f.tool.execute({...args,evidence:[{messageId:'forged'}]},exec),/source-unavailable/);assert.equal(existsSync(join(f.dir,'MEMORY.md')),false)}))
|
|
@@ -3,9 +3,9 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
import {mkdtempSync,rmSync,readFileSync,writeFileSync,existsSync,cpSync,mkdirSync} from 'node:fs'
|
|
4
4
|
import {join} from 'node:path'
|
|
5
5
|
import {tmpdir} from 'node:os'
|
|
6
|
-
import {writeMemory,indexLine,searchStore,withdrawManagedSource,withdrawManagedSourceKey,forgetMemory,adoptCandidate,recoverMemoryPublication,storeRevision,lookupLinks} from '
|
|
7
|
-
import {candidateSnapshot,stageCandidate,withdrawSource,withdrawalPolicy,pruneCandidates,rejectCandidate} from '
|
|
8
|
-
import {filterWithdrawn} from '
|
|
6
|
+
import {writeMemory,indexLine,searchStore,withdrawManagedSource,withdrawManagedSourceKey,forgetMemory,adoptCandidate,recoverMemoryPublication,storeRevision,lookupLinks} from '../index.mjs'
|
|
7
|
+
import {candidateSnapshot,stageCandidate,withdrawSource,withdrawalPolicy,pruneCandidates,rejectCandidate} from '../candidates.mjs'
|
|
8
|
+
import {filterWithdrawn} from '../withdrawal.mjs'
|
|
9
9
|
const proof=(n=1)=>({sessionId:'synthetic-owner',basis:'tool-context',via:'memory_write',callHash:String(n).padStart(64,'a'),evidence:[{messageId:'owner-message-'+n,hash:'b'.repeat(64),quote:'Synthetic owner statement for this turn '+n}]})
|
|
10
10
|
const box=run=>{const root=mkdtempSync(join(tmpdir(),'tool-write-life-')),dir=join(root,'memory');mkdirSync(dir);try{return run(dir,root)}finally{rmSync(root,{recursive:true,force:true})}}
|
|
11
11
|
function write(dir,summary='Synthetic selected palette jade9876',options={}){const detail=options.detail??'Synthetic private detail jade9876';return writeMemory({dir,type:'user',scope:'global',summary,detail,line:indexLine('user',summary,options.expires,detail.trim().length>0),provenance:proof(options.n??1),...options})}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {writeFixtureMemory} from '
|
|
1
|
+
import {writeFixtureMemory} from '../fixture-host.mjs'
|
|
2
2
|
import {realpathSync} from 'node:fs'
|
|
3
|
-
import {applyWithOwnerFixture as apply} from '
|
|
3
|
+
import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
|
|
4
4
|
/**
|
|
5
|
-
* 可用性测试。跑法:node --test ~/.dsh/plugins/memory/test-usability.mjs
|
|
5
|
+
* 可用性测试。跑法:node --test ~/.dsh/plugins/memory/test/test-usability.mjs
|
|
6
6
|
*
|
|
7
7
|
* 这份文件验收五条指标(对应计划里的 U1–U5):
|
|
8
8
|
* U1 无死路:写入失败必须给出已存在的下一步;满索引一次 compact 恢复可写
|
|
@@ -22,7 +22,7 @@ import { join, dirname } from 'node:path'
|
|
|
22
22
|
import { tmpdir } from 'node:os'
|
|
23
23
|
import { fileURLToPath } from 'node:url'
|
|
24
24
|
|
|
25
|
-
import { countFrameTokens } from '
|
|
25
|
+
import { countFrameTokens } from '../security.mjs'
|
|
26
26
|
import {
|
|
27
27
|
|
|
28
28
|
pruneStore,
|
|
@@ -32,9 +32,9 @@ import {
|
|
|
32
32
|
statusOf,
|
|
33
33
|
storeDirFor,
|
|
34
34
|
writeMemory,
|
|
35
|
-
} from '
|
|
35
|
+
} from '../index.mjs'
|
|
36
36
|
|
|
37
|
-
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
37
|
+
const HERE = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
38
38
|
|
|
39
39
|
function sandbox() {
|
|
40
40
|
const root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-mem-usability-')))
|
|
@@ -405,7 +405,7 @@ test('compact:超大主题文件轮转,内容不丢,之后能继续追加'
|
|
|
405
405
|
assert.equal(existsSync(join(dir, 'user.md')), false)
|
|
406
406
|
assert.equal(statSync(join(dir, report.archived[0].to)).size, Buffer.byteLength(big), '内容必须逐字保留')
|
|
407
407
|
|
|
408
|
-
const { appendTopic } = await import('
|
|
408
|
+
const { appendTopic } = await import('../index.mjs')
|
|
409
409
|
appendTopic(dir, 'user', '轮转后仍可追加')
|
|
410
410
|
assert.match(readFileSync(join(dir, 'user.md'), 'utf8'), /轮转后仍可追加/)
|
|
411
411
|
} finally {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import test from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import {prepareValidationPrompt,ValidationSubmission,validateCandidatesWithLlm} from '
|
|
4
|
-
import {VALIDATION_FIELDS} from '
|
|
3
|
+
import {prepareValidationPrompt,ValidationSubmission,validateCandidatesWithLlm} from '../capture-validation.mjs'
|
|
4
|
+
import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
|
|
5
5
|
const messages=['跨所有项目的初稿反馈先列两处问题再给办法。','这一长期约定在正式稿上不适用。','请保留这项约定,以供之后的项目使用。'].map((text,i)=>({id:'synthetic-'+i,role:'user',text,hash:String(i).repeat(64)}))
|
|
6
6
|
const candidate={type:'user',scope:'global',basis:'explicit',summary:'跨项目的初稿反馈先列两处问题再给办法',meaning:{kind:'preference',conditions:[{dimension:'task',value:'review'},{dimension:'document_kind',value:'draft'}]},evidence:messages.slice(0,2).map(({id,text})=>({messageId:id,quote:text}))}
|
|
7
7
|
function setup(){const prepared=prepareValidationPrompt(messages,[candidate]),r=prepared.records[0],check={candidateId:r.candidateId,fields:Object.fromEntries(VALIDATION_FIELDS.map(k=>[k,'supported'])),taskRestriction:'none',sourceRefs:Object.keys(r.requiredSourceRefs),evidence:[]};return{prepared,check}}
|
package/{test-validation-transition-context.mjs → test/test-validation-transition-context.mjs}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import {validationTransitionContext as build} from '
|
|
3
|
+
import {validationTransitionContext as build} from '../validation-transition-context.mjs'
|
|
4
4
|
const record=(n,patch={})=>({id:String(n).padStart(64,'a'),status:'recorded',scope:'global',type:'user',summary:'Synthetic default palette '+n,indexLine:'- [user] Synthetic default palette '+n+' — 索引摘要',meaning:{kind:'preference',conditions:[],except:[]},...patch});
|
|
5
5
|
const target={summary:'Synthetic default palette new',scope:'global'};
|
|
6
6
|
test('current same-scope interpretations are data and cannot expose old quotes, bodies or other projects',()=>{
|
|
@@ -4,11 +4,11 @@ import { mkdtempSync, readFileSync, writeFileSync, readdirSync, rmSync, symlinkS
|
|
|
4
4
|
import { spawnSync } from 'node:child_process'
|
|
5
5
|
import { join } from 'node:path'
|
|
6
6
|
import { tmpdir } from 'node:os'
|
|
7
|
-
import { stageCandidate, listCandidates } from '
|
|
8
|
-
import { adoptCandidate, withdrawManagedSource, recoverMemoryPublication, storeRevision, searchStore, writeMemory, indexLine } from '
|
|
9
|
-
import { commitPublication } from '
|
|
10
|
-
import { withLock } from '
|
|
11
|
-
import { MANAGED_LOG_BYTES, MANAGED_LOG_TOTAL_BYTES, isManagedLogName } from '
|
|
7
|
+
import { stageCandidate, listCandidates } from '../candidates.mjs'
|
|
8
|
+
import { adoptCandidate, withdrawManagedSource, recoverMemoryPublication, storeRevision, searchStore, writeMemory, indexLine } from '../index.mjs'
|
|
9
|
+
import { commitPublication } from '../publication.mjs'
|
|
10
|
+
import { withLock } from '../security.mjs'
|
|
11
|
+
import { MANAGED_LOG_BYTES, MANAGED_LOG_TOTAL_BYTES, isManagedLogName } from '../managed-logs.mjs'
|
|
12
12
|
|
|
13
13
|
function box(run) {
|
|
14
14
|
const dir = mkdtempSync(join(tmpdir(), 'dsh-archive-withdrawal-'))
|
|
@@ -140,7 +140,7 @@ test('forty-one-file withdrawal survives actual SIGKILL and finishes history/arc
|
|
|
140
140
|
for (const base of ['.audit.jsonl', '.history.jsonl']) for (let suffix = 1; suffix <= 16; suffix++) writeFileSync(join(dir, `${base}.${suffix}`), readFileSync(join(dir, base)))
|
|
141
141
|
const preview = withdrawManagedSource(dir, 'archive-source', 'message-1')
|
|
142
142
|
const options = { apply: true, reviewedIndex: preview.reviewedIndex, candidateRevision: preview.candidateRevision }
|
|
143
|
-
const worker = `import {withdrawManagedSource} from ${JSON.stringify(new URL('
|
|
143
|
+
const worker = `import {withdrawManagedSource} from ${JSON.stringify(new URL('../index.mjs', import.meta.url).href)};withdrawManagedSource(process.argv[1],'archive-source','message-1',{...JSON.parse(process.argv[2]),publicationFault:event=>{if(event.phase==='file'&&event.index===34)process.kill(process.pid,'SIGKILL')}})`
|
|
144
144
|
const stopped = spawnSync(process.execPath, ['--input-type=module', '-e', worker, dir, JSON.stringify(options)], { encoding: 'utf8', timeout: 15000 })
|
|
145
145
|
assert.equal(stopped.signal, 'SIGKILL', stopped.stderr)
|
|
146
146
|
const journal=JSON.parse(readFileSync(join(dir, '.publication.json')))
|
|
@@ -4,8 +4,8 @@ import assert from 'node:assert/strict'
|
|
|
4
4
|
import { mkdtempSync, readFileSync, writeFileSync, rmSync, readdirSync, cpSync } from 'node:fs'
|
|
5
5
|
import { join } from 'node:path'
|
|
6
6
|
import { tmpdir } from 'node:os'
|
|
7
|
-
import { stageCandidate, listCandidates, pruneCandidates, withdrawSource } from '
|
|
8
|
-
import { adoptCandidate, withdrawManagedSource, recoverMemoryPublication, writeMemory, storeRevision, indexLine, searchStore, lookupLinks, statusOf, forgetMemory, apply } from '
|
|
7
|
+
import { stageCandidate, listCandidates, pruneCandidates, withdrawSource } from '../candidates.mjs'
|
|
8
|
+
import { adoptCandidate, withdrawManagedSource, recoverMemoryPublication, writeMemory, storeRevision, indexLine, searchStore, lookupLinks, statusOf, forgetMemory, apply } from '../index.mjs'
|
|
9
9
|
|
|
10
10
|
function box(run) { const dir = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-withdrawal-'))); try { return run(dir) } finally { rmSync(dir, { recursive: true, force: true }) } }
|
|
11
11
|
function adopted(dir, detail = '唯一细节:只用于室内对白镜头。', source = 'source-1') {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {prepareMountedFixture} from '
|
|
1
|
+
import {prepareMountedFixture} from '../fixture-host.mjs'
|
|
2
2
|
import {realpathSync} from 'node:fs'
|
|
3
|
-
import {applyWithOwnerFixture as apply} from '
|
|
3
|
+
import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
|
|
4
4
|
/**
|
|
5
|
-
* dsh-memory 的单元测试。跑法:node --test ~/.dsh/plugins/memory/test.mjs
|
|
5
|
+
* dsh-memory 的单元测试。跑法:node --test ~/.dsh/plugins/memory/test/test.mjs
|
|
6
6
|
*
|
|
7
7
|
* 不依赖 DSH 宿主:用假的 ctx 捕获注册结果,再直接调 provider 的 text() 和工具 execute。
|
|
8
8
|
* 两条必须守住的约定:
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
indexLine,
|
|
31
31
|
storeDirFor,
|
|
32
32
|
writeMemory,
|
|
33
|
-
} from '
|
|
33
|
+
} from '../index.mjs'
|
|
34
34
|
|
|
35
35
|
const CONTEXT_NAME = 'memory:index'
|
|
36
36
|
|
package/version.mjs
CHANGED