dsh-plugin-xujie-memory 0.5.0 → 0.7.0

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.
Files changed (84) hide show
  1. package/ACCEPTANCE.md +37 -0
  2. package/BUILD-PROVENANCE.json +53 -0
  3. package/CHANGELOG.md +40 -1
  4. package/LICENSE +21 -21
  5. package/README.md +67 -17
  6. package/README_EN.md +54 -8
  7. package/cordis.patch.yml +49 -49
  8. package/lib/client.js +17219 -0
  9. package/lib/controller.js +307 -0
  10. package/lib/index.js +200 -14
  11. package/lib/typert.host.d.ts +3 -0
  12. package/lib/typert.host.js +15104 -0
  13. package/lib/typert.remote-client.d.ts +40 -0
  14. package/lib/typert.remote-client.d.ts.map +1 -0
  15. package/lib/typert.remote-client.js +15093 -0
  16. package/lib/types.d.ts +13 -0
  17. package/lib/types.js +1 -0
  18. package/package.json +49 -5
  19. package/python/specs/characters/aloof.json +14 -14
  20. package/python/specs/characters/gentle.json +14 -14
  21. package/python/specs/characters/tsundere.json +14 -14
  22. package/python/specs/debt.v1.json +31 -31
  23. package/python/specs/nickname.v1.json +16 -16
  24. package/python/specs/preference.v1.json +16 -16
  25. package/python/xujie_memory/__init__.py +123 -123
  26. package/python/xujie_memory/admission.py +211 -211
  27. package/python/xujie_memory/audit.py +377 -377
  28. package/python/xujie_memory/benchmark.py +281 -281
  29. package/python/xujie_memory/character.py +199 -199
  30. package/python/xujie_memory/console.py +41 -41
  31. package/python/xujie_memory/consolidate.py +356 -356
  32. package/python/xujie_memory/due.py +121 -121
  33. package/python/xujie_memory/emotion.py +573 -573
  34. package/python/xujie_memory/engine.py +1333 -1312
  35. package/python/xujie_memory/errors.py +98 -98
  36. package/python/xujie_memory/importance.py +104 -104
  37. package/python/xujie_memory/judge.py +350 -350
  38. package/python/xujie_memory/management.py +203 -0
  39. package/python/xujie_memory/memory.py +71 -10
  40. package/python/xujie_memory/orchestrator.py +690 -666
  41. package/python/xujie_memory/phrasing.py +292 -292
  42. package/python/xujie_memory/privacy.py +105 -0
  43. package/python/xujie_memory/schema.sql +492 -480
  44. package/python/xujie_memory/server.py +76 -0
  45. package/python/xujie_memory/specs.py +170 -170
  46. package/python/xujie_memory/summary.py +238 -238
  47. package/scripts/build.mjs +19 -0
  48. package/scripts/prepack.mjs +9 -0
  49. package/scripts/test-all.mjs +9 -0
  50. package/scripts/test-node.mjs +10 -0
  51. package/src/client/MemoryPanel.tsx +536 -0
  52. package/src/client/NeuralField.tsx +88 -0
  53. package/src/client/css.d.ts +4 -0
  54. package/src/client/icons.tsx +28 -0
  55. package/src/client/index.tsx +59 -0
  56. package/src/client/memory-adapter.ts +67 -0
  57. package/src/client/model.ts +79 -0
  58. package/src/client/neural/engine.ts +999 -0
  59. package/src/client/neural/morphology.ts +219 -0
  60. package/src/client/panel.css +337 -0
  61. package/src/client/types.ts +6 -0
  62. package/src/controller.ts +88 -0
  63. package/src/generated/typert.host.js +449 -0
  64. package/src/generated/typert.remote-client.js +438 -0
  65. package/src/index.ts +2 -0
  66. package/src/types.ts +13 -0
  67. package/test/cordis-fixture.mjs +5 -0
  68. package/test/management_regression.py +187 -0
  69. package/test/node-regression.entry.mjs +52 -0
  70. package/vendor/DSH-LICENSE.txt +21 -0
  71. package/vendor/THIRD-PARTY.md +6 -0
  72. package/vendor/dsh-protocol/index.ts +484 -0
  73. package/vendor/dsh-protocol/json-value.ts +53 -0
  74. package/vendor/dsh-protocol/owned-value.ts +39 -0
  75. package/vendor/dsh-protocol/principal.ts +17 -0
  76. package/vendor/dsh-protocol/remote-access.ts +38 -0
  77. package/vendor/dsh-protocol/remote-error.ts +49 -0
  78. package/vendor/dsh-protocol/remote-info.ts +161 -0
  79. package/vendor/dsh-protocol/types.ts +939 -0
  80. package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
  81. package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
  82. package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
  83. package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
  84. package/vendor/licenses/zod-MIT.txt +21 -0
@@ -0,0 +1,187 @@
1
+ import json
2
+ import sys
3
+ import tempfile
4
+ import threading
5
+ import unittest
6
+ import urllib.request
7
+ import urllib.error
8
+ from pathlib import Path
9
+ from unittest.mock import patch
10
+ ROOT = Path(__file__).resolve().parents[1]
11
+ sys.path.insert(0, str(ROOT / 'python'))
12
+ from xujie_memory.server import MemoryService, serve
13
+ from xujie_memory.management import Management, ManagementError
14
+ from xujie_memory.summary import SummaryStore
15
+ from xujie_memory.profile import ProfileStore
16
+
17
+ class ManagementRegression(unittest.TestCase):
18
+ def setUp(self):
19
+ self.tmp = tempfile.TemporaryDirectory()
20
+ self.db = Path(self.tmp.name) / 'memory.db'
21
+ self.svc = MemoryService(self.db, ROOT / 'python/specs')
22
+ self.m = Management(self.svc)
23
+ self.svc.turn('one', '我不吃辣', user='owner', context={'peer_id':'assistant'})
24
+ self.scope = self.svc.scope_of('one', 'owner')
25
+ self.tid = self.svc.engine._conn().execute('SELECT trace_id FROM traces WHERE scope_id=?', (self.scope,)).fetchone()[0]
26
+ self.hash = self.m.detail(self.scope,self.tid)['trace']['content_hash']
27
+ def tearDown(self):
28
+ self.svc.close()
29
+ self.tmp.cleanup()
30
+ def test_catalog_and_graph_use_real_records(self):
31
+ self.assertEqual(self.m.catalog()['scopes'][0]['scope_id'],self.scope)
32
+ snap = self.m.snapshot(self.scope)
33
+ self.assertTrue(any(n['id']==self.tid for n in snap['nodes']))
34
+ self.assertTrue(any(n['layer']=='working' for n in snap['nodes']))
35
+ self.assertTrue(any(n['layer']=='semantic' for n in snap['nodes']))
36
+ self.assertTrue(snap['cards'])
37
+ self.assertTrue(all(e['kind'] in ('evidence','working_projection') for e in snap['edges']))
38
+ def test_scope_isolation(self):
39
+ other = self.svc.scope_of('other','different-user')
40
+ for method,args in [(self.m.detail,(other,self.tid)),(self.m.edit,(other,self.tid,'我喜欢咖啡',self.hash)),(self.m.forget,(other,self.tid,self.hash))]:
41
+ with self.assertRaisesRegex(ManagementError,'trace_not_found'): method(*args)
42
+ self.assertEqual(self.m.search(other,'吃辣')['hits'],[])
43
+ def test_cross_session_user_scope(self):
44
+ self.assertEqual(self.scope,self.svc.scope_of('two','owner'))
45
+ def test_management_search_finds_old_records_after_2000_new_traces(self):
46
+ engine = self.svc.engine
47
+ with engine._tx() as conn:
48
+ conn.execute("UPDATE traces SET recorded_at='2000-01-01T00:00:00Z' WHERE trace_id=?", (self.tid,))
49
+ for i in range(2000):
50
+ engine.record_trace(self.scope, f'stress:{i}', 'owner', 'user_statement', f'测试记忆编号{i:04d}')
51
+ self.assertEqual(self.m.snapshot(self.scope)['stats']['traces'], 2001)
52
+ self.assertTrue(self.m.snapshot(self.scope)['truncated'])
53
+ self.assertEqual(self.m.search(self.scope, '我不吃辣')['hits'][0]['trace_id'], self.tid)
54
+ self.assertFalse(any(hit['trace_id'] == self.tid for hit in self.m.memory.search(self.scope, '我不吃辣')))
55
+ self.m.forget(self.scope, self.tid, self.hash)
56
+ self.assertFalse(any(hit['trace_id'] == self.tid for hit in self.m.search(self.scope, '我不吃辣')['hits']))
57
+ def test_edit_replaces_and_reindexes_persistently(self):
58
+ result = self.m.edit(self.scope,self.tid,'我喜欢咖啡',self.hash)
59
+ new = result['trace']['trace_id']
60
+ self.assertNotEqual(new,self.tid)
61
+ self.assertEqual(self.svc.engine.get_trace(self.tid)['content'],'')
62
+ self.assertEqual(self.m.search(self.scope,'咖啡')['hits'][0]['trace_id'],new)
63
+ self.assertFalse(self.m.search(self.scope,'吃辣')['hits'])
64
+ card = ProfileStore(self.svc.engine).card(self.scope,'owner')
65
+ self.assertTrue(any('咖啡' in t['value'] for t in card['tags']))
66
+ self.assertFalse(any('辣' in t['value'] for t in card['tags']))
67
+ self.svc.close()
68
+ self.svc = MemoryService(self.db, ROOT/'python/specs')
69
+ self.m = Management(self.svc)
70
+ self.assertEqual(self.m.detail(self.scope,new)['trace']['content'],'我喜欢咖啡')
71
+ def test_stale_edit_and_delete_are_rejected(self):
72
+ for method,args in [(self.m.edit,(self.scope,self.tid,'我喜欢咖啡','stale')),(self.m.forget,(self.scope,self.tid,'stale'))]:
73
+ with self.assertRaisesRegex(ManagementError,'version_conflict'): method(*args)
74
+ self.assertEqual(self.m.detail(self.scope,self.tid)['trace']['content'],'我不吃辣')
75
+ def test_edit_is_atomic_on_encoder_failure(self):
76
+ with patch.object(self.svc.orchestrator.ingest.encoder,'encode',side_effect=RuntimeError('synthetic failure')):
77
+ with self.assertRaisesRegex(RuntimeError,'synthetic failure'):
78
+ self.m.edit(self.scope,self.tid,'我喜欢咖啡',self.hash)
79
+ self.assertEqual(self.m.detail(self.scope,self.tid)['trace']['content'],'我不吃辣')
80
+ self.assertTrue(self.m.search(self.scope,'吃辣')['hits'])
81
+ self.assertEqual(self.svc.engine._conn().execute('SELECT COUNT(*) FROM memory_edits').fetchone()[0],0)
82
+ def test_edit_preserves_anchor(self):
83
+ self.m.memory.mark(self.scope,self.tid,5)
84
+ result = self.m.edit(self.scope,self.tid,'我喜欢咖啡',self.hash)
85
+ self.assertTrue(result['trace']['anchor'])
86
+ self.assertEqual(result['trace']['importance'],5)
87
+ def test_edit_preserves_original_source_and_event_time(self):
88
+ original_time = "2025-03-12T08:30:00+00:00"
89
+ self.svc.engine._conn().execute(
90
+ "UPDATE traces SET source_time=?,source_time_precision='second' WHERE trace_id=?",
91
+ (original_time,self.tid))
92
+ result = self.m.edit(self.scope,self.tid,'我喜欢咖啡',self.hash)
93
+ replacement = self.svc.engine.get_trace(result['trace']['trace_id'])
94
+ self.assertEqual(replacement['source_time'],original_time)
95
+ self.assertEqual(replacement['source_time_precision'],'second')
96
+ rows = self.svc.engine._conn().execute(
97
+ 'SELECT e.effective_at FROM events e JOIN event_evidence v ON v.event_id=e.event_id '
98
+ 'WHERE v.trace_id=? AND e.void=0',(replacement['trace_id'],)).fetchall()
99
+ self.assertTrue(rows)
100
+ self.assertTrue(all(row['effective_at']==original_time for row in rows))
101
+ def test_summary_and_anchor_projections_keep_real_evidence(self):
102
+ self.m.memory.mark(self.scope,self.tid,5)
103
+ summary = SummaryStore(self.svc.engine).create(self.scope,[self.tid])
104
+ snap = self.m.snapshot(self.scope)
105
+ self.assertTrue(any(n['kind']=='summary' and self.tid in n.get('evidence',[]) for n in snap['nodes']))
106
+ self.assertTrue(any(n['kind']=='anchor' and n['trace_id']==self.tid for n in snap['nodes']))
107
+ self.assertTrue(any(e['kind']=='summary_input' and e['source']==self.tid for e in snap['edges']))
108
+ self.assertTrue(any(e['kind']=='anchor_projection' and e['source']==self.tid for e in snap['edges']))
109
+ def test_delete_purges_summary_and_person_card(self):
110
+ summary = SummaryStore(self.svc.engine).create(self.scope,[self.tid])
111
+ self.m.forget(self.scope,self.tid,self.hash)
112
+ saved = SummaryStore(self.svc.engine).get(summary.summary_id)
113
+ self.assertEqual(saved['text'],'')
114
+ self.assertEqual(saved['lifecycle_state'],'archived')
115
+ self.assertFalse(self.m.search(self.scope,'吃辣')['hits'])
116
+ self.assertFalse(any('辣' in t['value'] for t in ProfileStore(self.svc.engine).card(self.scope,'owner')['tags']))
117
+ self.assertEqual(self.svc.engine._conn().execute('SELECT COUNT(*) FROM traces_fts WHERE trace_id=?',(self.tid,)).fetchone()[0],0)
118
+ self.svc.close()
119
+ self.svc = MemoryService(self.db, ROOT/'python/specs')
120
+ self.assertTrue(self.svc.engine.get_trace(self.tid)['erased_at'])
121
+ def test_upgrade_repairs_old_erased_summary_and_keeps_private_backup(self):
122
+ summary = SummaryStore(self.svc.engine).create(self.scope,[self.tid])
123
+ self.m.forget(self.scope,self.tid,self.hash)
124
+ # Reproduce the pre-0.7 residue; the old package is not required by tests.
125
+ c = self.svc.engine._conn()
126
+ c.execute("UPDATE summaries SET text='我不吃辣',lifecycle_state='active' WHERE summary_id=?",(summary.summary_id,))
127
+ c.execute('DROP TABLE memory_edits')
128
+ c.execute('DROP TABLE memory_maintenance')
129
+ self.svc.close()
130
+ self.svc = MemoryService(self.db, ROOT/'python/specs')
131
+ self.m = Management(self.svc)
132
+ saved = SummaryStore(self.svc.engine).get(summary.summary_id)
133
+ self.assertEqual(saved['text'],'')
134
+ self.assertEqual(saved['lifecycle_state'],'archived')
135
+ self.assertTrue(Path(str(self.db)+'.pre-0.7.0.bak').is_file())
136
+ self.assertFalse(self.m.search(self.scope,'吃辣')['hits'])
137
+ def test_session_scope_remains_separate(self):
138
+ other = MemoryService(Path(self.tmp.name)/'session.db',ROOT/'python/specs',scope_mode='session')
139
+ try:
140
+ self.assertNotEqual(other.scope_of('s1','owner'),other.scope_of('s2','owner'))
141
+ finally:
142
+ other.close()
143
+ def test_unchanged_edit_is_noop(self):
144
+ self.assertFalse(self.m.edit(self.scope,self.tid,'我不吃辣',self.hash)['changed'])
145
+ def test_invalid_text_rejected(self):
146
+ for s in (' ', '字'*2001):
147
+ with self.assertRaisesRegex(ManagementError,'invalid_content_length'):
148
+ self.m.edit(self.scope,self.tid,s,self.hash)
149
+ def test_concurrent_edit_has_exactly_one_winner(self):
150
+ outcomes = []
151
+ def edit(content):
152
+ try: outcomes.append(self.m.edit(self.scope,self.tid,content,self.hash)['ok'])
153
+ except ManagementError: outcomes.append(False)
154
+ ts = [threading.Thread(target=edit,args=(s,)) for s in ('我喜欢咖啡','我喜欢茶')]
155
+ for t in ts:t.start()
156
+ for t in ts:t.join()
157
+ self.assertEqual(sorted(outcomes),[False,True])
158
+
159
+ class HttpRegression(unittest.TestCase):
160
+ def test_http_auth_read_write_conflict_and_no_cors(self):
161
+ with tempfile.TemporaryDirectory() as d:
162
+ h = serve(Path(d)/'m.db',ROOT/'python/specs',port=0,token='test-token-only')
163
+ thread = threading.Thread(target=h.serve_forever,daemon=True);thread.start()
164
+ url = f'http://127.0.0.1:{h.server_port}'
165
+ def post(path,body,token='test-token-only'):
166
+ req=urllib.request.Request(url+path,data=json.dumps(body).encode(),headers={'Content-Type':'application/json','X-Xujie-Token':token})
167
+ try:
168
+ with urllib.request.urlopen(req) as r:return r.status,json.load(r),r.headers
169
+ except urllib.error.HTTPError as r:return r.code,json.load(r),r.headers
170
+ try:
171
+ self.assertEqual(post('/manage/catalog',{},'wrong')[0],401)
172
+ status,_,headers=post('/manage/catalog',{})
173
+ self.assertEqual(status,200);self.assertNotIn('Access-Control-Allow-Origin',headers)
174
+ self.assertEqual(post('/turn',{'session':'s','text':'我不吃辣'})[0],200)
175
+ scope=post('/manage/catalog',{})[1]['scopes'][0]['scope_id']
176
+ snap=post('/manage/snapshot',{'scope_id':scope})[1]
177
+ trace=next(n for n in snap['nodes'] if n['kind']=='trace')
178
+ args={'scope_id':scope,'trace_id':trace['trace_id'],'content':'我喜欢咖啡','expected_hash':'stale'}
179
+ self.assertEqual(post('/manage/edit',args)[0],409)
180
+ args['expected_hash']=trace['content_hash']
181
+ status,edited,_=post('/manage/edit',args);self.assertEqual(status,200)
182
+ new=edited['trace']
183
+ self.assertEqual(post('/manage/forget',{'scope_id':scope,'trace_id':new['trace_id'],'expected_hash':new['content_hash']})[0],200)
184
+ finally:
185
+ h.shutdown();h.server_close();h.service.close();thread.join()
186
+
187
+ if __name__=='__main__':unittest.main(verbosity=2)
@@ -0,0 +1,52 @@
1
+ import assert from 'node:assert/strict'
2
+ import {mkdtempSync,writeFileSync,rmSync} from 'node:fs'
3
+ import {tmpdir} from 'node:os'
4
+ import {join} from 'node:path'
5
+ import {spawn} from 'node:child_process'
6
+ import {once} from 'node:events'
7
+ import {apply} from '../lib/index.js'
8
+ import {TYPERT_REMOTE} from '../lib/typert.remote-client.js'
9
+ const dir=mkdtempSync(join(tmpdir(),'xujie-node-'))
10
+ const token='isolated-test-token'
11
+ writeFileSync(join(dir,'token'),token,{mode:0o600})
12
+ const python=process.env.PYTHON ?? 'python3'
13
+ const kernel=spawn(python,['-u','-c',`from xujie_memory.server import serve\nfrom pathlib import Path\nh=serve(Path(${JSON.stringify(join(dir,'memory.db'))}),Path(${JSON.stringify(join(process.cwd(),'python/specs'))}),port=0,token=${JSON.stringify(token)})\nprint(h.server_port,flush=True)\nh.serve_forever()`],{env:{...process.env,PYTHONPATH:join(process.cwd(),'python')},stdio:['ignore','pipe','pipe']})
14
+ let checks=0
15
+ const check=(label,fn)=>{fn();checks++;console.log('PASS',label)}
16
+ const cleanups=[]
17
+ try {
18
+ let text='';const port=await new Promise((resolve,reject)=>{const timeout=setTimeout(()=>reject(new Error('kernel startup timeout')),10000);kernel.stdout.on('data',chunk=>{text+=chunk;if(text.includes('\n')){clearTimeout(timeout);resolve(Number(text.trim().split('\n')[0]))}});kernel.once('error',reject);kernel.once('exit',code=>{if(!text)reject(new Error('kernel early exit '+code))})})
19
+ const endpoint=`http://127.0.0.1:${port}`
20
+ const handlers=new Map(),tools=new Map();let section
21
+ const ctx={_services:{},invocation:{peer:{principal:{kind:'owner'}}},logger:{info(){},warn(){},debug(){}},tools:{register(t){tools.set(t.name,t);return()=>tools.delete(t.name)}},effect(fn){const dispose=fn();if(typeof dispose==='function')cleanups.push(dispose);return dispose},inject(deps,fn){fn(ctx)},get(key){if(key==='systemPrompt')return{section(s){section=s}};return ctx._services[key]},on(key,fn){handlers.set(key,fn);return()=>handlers.delete(key)}}
22
+ apply(ctx,{autoStart:false,endpoint,tokenFile:join(dir,'token')})
23
+ check('four memory tools are registered',()=>assert.deepEqual([...tools.keys()].sort(),['memory_edit','memory_forget','memory_search','memory_status']))
24
+ const alice={id:'alice-one',userId:'alice'},bob={id:'bob-one',userId:'bob'}
25
+ const event=text=>({type:'user/message',data:{source:{kind:'user'},content:[{type:'text',text}]}})
26
+ handlers.get('session/event')(alice,event('我不吃辣'))
27
+ const stats=await tools.get('memory_status').execute({}, {session:alice})
28
+ check('automatic turn writes through real Python HTTP kernel',()=>assert.equal(stats.traces,1))
29
+ check('prompt contains stored memory',()=>assert.ok(section.text({agent:{session:alice}}).includes('辣')))
30
+ check('new user cannot inherit another user fallback',()=>assert.equal(section.text({agent:{session:bob}}),''))
31
+ check('assembly without session does not expose fallback',()=>assert.equal(section.text({}),''))
32
+ const controller=ctx._services.xujieMemory
33
+ const catalog=await controller.catalog(),scope=catalog.scopes[0].scope_id
34
+ const snapshot=await controller.snapshot(scope)
35
+ check('generated snapshot codec accepts real projected DTO',()=>assert.ok(TYPERT_REMOTE.methods['xujieMemory/snapshot'].result.create().safeParse(snapshot).success))
36
+ const node=snapshot.nodes.find(n=>n.kind==='trace')
37
+ const saved=await controller.edit(scope,node.trace_id,'我喜欢咖啡',node.content_hash)
38
+ check('generated edit codec accepts actual result',()=>assert.ok(TYPERT_REMOTE.methods['xujieMemory/edit'].result.create().safeParse(saved).success))
39
+ check('UI mutation invalidates prompt cache',()=>assert.equal(section.text({agent:{session:alice}}),''))
40
+ const hits=await controller.search(scope,'咖啡')
41
+ check('search returns persisted replacement evidence',()=>assert.equal(hits[0].trace_id,saved.trace.trace_id))
42
+ check('generated search codec accepts actual result',()=>assert.ok(TYPERT_REMOTE.methods['xujieMemory/search'].result.create().safeParse(hits).success))
43
+ ctx.invocation={peer:{principal:{kind:'guest'}}}
44
+ await assert.rejects(()=>controller.catalog(),e=>e.code==='xujie-memory/owner-required');checks++;console.log('PASS guest owner-only boundary')
45
+ ctx.invocation={peer:{principal:{kind:'owner'}}}
46
+ const modelEdit=await tools.get('memory_edit').execute({trace_id:hits[0].trace_id,content:'我喜欢绿茶',expected_hash:hits[0].content_hash},{session:alice})
47
+ check('model correction tool writes the actual memory DB',()=>assert.equal(modelEdit.trace.content,'我喜欢绿茶'))
48
+ await controller.forget(scope,modelEdit.trace.trace_id,modelEdit.trace.content_hash)
49
+ const remaining=await controller.search(scope,'绿茶')
50
+ check('GUI forget removes raw text from live DB search',()=>assert.deepEqual(remaining,[]))
51
+ } catch(error){console.error(error);process.exitCode=1}
52
+ finally{for(const dispose of cleanups.reverse())try{dispose()}catch{};kernel.kill();await Promise.race([once(kernel,'exit'),new Promise(r=>setTimeout(r,2000))]);rmSync(dir,{recursive:true,force:true});console.log('Completed checks:',checks)}
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bundled source and runtime notices
2
+
3
+ - `dsh-protocol/`: copies of DeepSeek Harness's MIT-licensed Typert protocol helpers at the exact rc.5 source commit recorded in BUILD-PROVENANCE.json. See DSH-LICENSE.txt.
4
+ - Compiled strict contract descriptors bundle Zod and source-map helper code. Their MIT notices are retained under licenses/.
5
+ - React/JSX runtime and Cordis are shared host externals, not bundled copies.
6
+ - The bundled Windows Python runtime and its existing license files are unchanged from the supplied 0.6.0 distribution.