dsh-plugin-xujie-memory 0.6.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 (61) hide show
  1. package/ACCEPTANCE.md +37 -0
  2. package/BUILD-PROVENANCE.json +53 -0
  3. package/CHANGELOG.md +12 -0
  4. package/README.md +48 -23
  5. package/README_EN.md +32 -9
  6. package/lib/client.js +17219 -0
  7. package/lib/controller.js +307 -0
  8. package/lib/index.js +87 -22
  9. package/lib/typert.host.d.ts +3 -0
  10. package/lib/typert.host.js +15104 -0
  11. package/lib/typert.remote-client.d.ts +40 -0
  12. package/lib/typert.remote-client.d.ts.map +1 -0
  13. package/lib/typert.remote-client.js +15093 -0
  14. package/lib/types.d.ts +13 -0
  15. package/lib/types.js +1 -0
  16. package/package.json +49 -5
  17. package/python/xujie_memory/__init__.py +123 -123
  18. package/python/xujie_memory/engine.py +1333 -1312
  19. package/python/xujie_memory/management.py +203 -0
  20. package/python/xujie_memory/memory.py +27 -21
  21. package/python/xujie_memory/privacy.py +105 -0
  22. package/python/xujie_memory/schema.sql +492 -480
  23. package/python/xujie_memory/server.py +52 -0
  24. package/scripts/build.mjs +19 -0
  25. package/scripts/prepack.mjs +9 -0
  26. package/scripts/test-all.mjs +9 -0
  27. package/scripts/test-node.mjs +10 -0
  28. package/src/client/MemoryPanel.tsx +536 -0
  29. package/src/client/NeuralField.tsx +88 -0
  30. package/src/client/css.d.ts +4 -0
  31. package/src/client/icons.tsx +28 -0
  32. package/src/client/index.tsx +59 -0
  33. package/src/client/memory-adapter.ts +67 -0
  34. package/src/client/model.ts +79 -0
  35. package/src/client/neural/engine.ts +999 -0
  36. package/src/client/neural/morphology.ts +219 -0
  37. package/src/client/panel.css +337 -0
  38. package/src/client/types.ts +6 -0
  39. package/src/controller.ts +88 -0
  40. package/src/generated/typert.host.js +449 -0
  41. package/src/generated/typert.remote-client.js +438 -0
  42. package/src/index.ts +2 -0
  43. package/src/types.ts +13 -0
  44. package/test/cordis-fixture.mjs +5 -0
  45. package/test/management_regression.py +187 -0
  46. package/test/node-regression.entry.mjs +52 -0
  47. package/vendor/DSH-LICENSE.txt +21 -0
  48. package/vendor/THIRD-PARTY.md +6 -0
  49. package/vendor/dsh-protocol/index.ts +484 -0
  50. package/vendor/dsh-protocol/json-value.ts +53 -0
  51. package/vendor/dsh-protocol/owned-value.ts +39 -0
  52. package/vendor/dsh-protocol/principal.ts +17 -0
  53. package/vendor/dsh-protocol/remote-access.ts +38 -0
  54. package/vendor/dsh-protocol/remote-error.ts +49 -0
  55. package/vendor/dsh-protocol/remote-info.ts +161 -0
  56. package/vendor/dsh-protocol/types.ts +939 -0
  57. package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
  58. package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
  59. package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
  60. package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
  61. package/vendor/licenses/zod-MIT.txt +21 -0
@@ -53,6 +53,7 @@ from . import __version__
53
53
  from .encoder import RuleEncoder
54
54
  from .engine import Engine, SpecRegistry
55
55
  from .memory import Memory
56
+ from .management import Management, ManagementError
56
57
  from .orchestrator import Orchestrator, TurnBudget
57
58
  from .profile import ProfileStore
58
59
 
@@ -95,6 +96,8 @@ class MemoryService:
95
96
  self.db_path = str(db_path)
96
97
  self.engine = Engine(Path(db_path), SpecRegistry.from_dir(Path(specs_dir)))
97
98
  self.profiles = ProfileStore(self.engine)
99
+ from .privacy import repair_legacy
100
+ repair_legacy(self.engine, self.profiles)
98
101
  self.orchestrator = Orchestrator(
99
102
  self.engine,
100
103
  profiles=self.profiles,
@@ -106,6 +109,9 @@ class MemoryService:
106
109
  # `INSERT OR IGNORE` 兜住了,这里只是避免无谓的重复计算。
107
110
  self._scopes: dict[str, str] = {}
108
111
  self._lock = threading.Lock()
112
+ # Serialize HTTP writes with reads of dependent projections. In
113
+ # particular, a /turn cannot republish a half-edited graph.
114
+ self.request_lock = threading.RLock()
109
115
 
110
116
  # ------------------------------------------------------------ scope
111
117
  def scope_of(self, session: str, user: str = "user") -> str:
@@ -342,7 +348,12 @@ class _Handler(BaseHTTPRequestHandler):
342
348
  self._json(400, {"ok": False, "error": f"bad_json: {exc}"})
343
349
  return
344
350
 
351
+ self.service.request_lock.acquire()
345
352
  try:
353
+ if route.startswith("/manage/"):
354
+ payload = self._manage(route, body)
355
+ self._json(200, payload)
356
+ return
346
357
  session = _str_field(body, "session")
347
358
  if not session:
348
359
  self._json(400, {"ok": False, "error": "missing_session"})
@@ -390,6 +401,13 @@ class _Handler(BaseHTTPRequestHandler):
390
401
  except ValueError as exc:
391
402
  # 库里没有这条 / 不属于这个 scope:调用方参数问题,不是服务端崩了。
392
403
  raise BadRequest(str(exc)) from exc
404
+ elif route == "/memory/edit":
405
+ payload = Management(self.service).edit(
406
+ self.service.scope_of(session, user),
407
+ _str_field(body, "trace_id"),
408
+ _str_field(body, "content", limit=MAX_TEXT_CHARS),
409
+ _str_field(body, "expected_hash"),
410
+ )
393
411
  elif route in ("/memory/mark", "/memory/unmark", "/memory/curve"):
394
412
  trace_id = _str_field(body, "trace_id")
395
413
  if not trace_id:
@@ -410,6 +428,12 @@ class _Handler(BaseHTTPRequestHandler):
410
428
  else:
411
429
  self._json(404, {"ok": False, "error": "not_found"})
412
430
  return
431
+ except ManagementError as exc:
432
+ status = 409 if exc.code == "version_conflict" else (
433
+ 404 if exc.code.endswith("_not_found") else 400
434
+ )
435
+ self._json(status, {"ok": False, "error": exc.code})
436
+ return
413
437
  except BadRequest as exc:
414
438
  self._json(400, {"ok": False, "error": "bad_request", "detail": str(exc)})
415
439
  return
@@ -422,9 +446,37 @@ class _Handler(BaseHTTPRequestHandler):
422
446
  self._json(500, {"ok": False, "error": type(exc).__name__,
423
447
  "detail": str(exc)[:300]})
424
448
  return
449
+ finally:
450
+ self.service.request_lock.release()
425
451
 
426
452
  self._json(200, payload)
427
453
 
454
+ def _manage(self, route: str, body: dict[str, Any]) -> dict[str, Any]:
455
+ manager = Management(self.service)
456
+ if route == "/manage/catalog":
457
+ return manager.catalog()
458
+ scope = _str_field(body, "scope_id")
459
+ if not scope:
460
+ raise BadRequest("missing_scope_id")
461
+ if route == "/manage/snapshot":
462
+ return manager.snapshot(scope, int(_num_field(body, "limit", 250, 1, 500)))
463
+ if route == "/manage/search":
464
+ return manager.search(
465
+ scope, _str_field(body, "query", limit=MAX_TEXT_CHARS),
466
+ int(_num_field(body, "limit", 30, 1, 50)),
467
+ )
468
+ trace = _str_field(body, "trace_id")
469
+ if route == "/manage/detail":
470
+ return manager.detail(scope, trace)
471
+ expected = _str_field(body, "expected_hash")
472
+ if route == "/manage/edit":
473
+ return manager.edit(
474
+ scope, trace, _str_field(body, "content", limit=MAX_TEXT_CHARS), expected
475
+ )
476
+ if route == "/manage/forget":
477
+ return manager.forget(scope, trace, expected)
478
+ raise ManagementError("route_not_found")
479
+
428
480
  # ---------------------------------------------------------- 工具
429
481
  def _read_json(self) -> dict[str, Any]:
430
482
  try:
@@ -0,0 +1,19 @@
1
+ import { build } from 'esbuild'
2
+ import { readFileSync, writeFileSync } from 'node:fs'
3
+ import { resolve } from 'node:path'
4
+ const root = resolve(import.meta.dirname, '..')
5
+ const alias = {
6
+ '@deepseek-ai/dsh-typert-protocol/remote-info': resolve(root, 'vendor/dsh-protocol/remote-info.ts'),
7
+ '@deepseek-ai/dsh-typert-protocol': resolve(root, 'vendor/dsh-protocol/index.ts'),
8
+ }
9
+ const common = { bundle:true,format:'esm',target:'es2022',logLevel:'warning',alias }
10
+ await build({...common,entryPoints:[resolve(root,'src/controller.ts')],platform:'node',external:['@deepseek-ai/cordis'],outfile:resolve(root,'lib/controller.js')})
11
+ for (const name of ['typert.host','typert.remote-client']) {
12
+ await build({...common,entryPoints:[resolve(root,`src/generated/${name}.js`)],platform:'neutral',outfile:resolve(root,`lib/${name}.js`)})
13
+ }
14
+ writeFileSync(resolve(root,'lib/types.d.ts'),readFileSync(resolve(root,'src/types.ts')))
15
+ writeFileSync(resolve(root,'lib/types.js'),'export {};\n')
16
+ if (process.argv.includes('--host-only')) process.exit(0)
17
+ const manifest = JSON.parse(readFileSync(resolve(root,'package.json'),'utf8'))
18
+ await build({...common,entryPoints:[resolve(root,'src/client/index.tsx')],platform:'browser',format:'cjs',external:['react','react/jsx-runtime','react-dom','react-dom/client','@deepseek-ai/cordis'],jsx:'automatic',outfile:resolve(root,'lib/client.js'),loader:{'.css':'text'},banner:{js:`window.__ModuleLoader__.load({id:${JSON.stringify(manifest.name)},factory:(require)=>{var module={exports:{}};var exports=module.exports;`},footer:{js:'return module.exports;}});'}})
19
+ console.log('Built host controller, strict contracts and DSH client factory.')
@@ -0,0 +1,9 @@
1
+ import {readdirSync,rmSync,statSync,existsSync,readFileSync} from 'node:fs'
2
+ import {resolve,join} from 'node:path'
3
+ const root=resolve(import.meta.dirname,'..')
4
+ function clean(dir){for(const entry of readdirSync(dir,{withFileTypes:true})){const p=join(dir,entry.name);if(entry.isDirectory()){if(entry.name==='__pycache__')rmSync(p,{recursive:true,force:true});else clean(p)}else if(entry.name.endsWith('.pyc'))rmSync(p)}}
5
+ clean(join(root,'python'))
6
+ for(const name of ['lib/index.js','lib/controller.js','lib/client.js','lib/typert.host.js','lib/typert.remote-client.js','python/xujie_memory/management.py','ACCEPTANCE.md'])if(!existsSync(join(root,name)))throw new Error('Missing release artifact: '+name)
7
+ const m=JSON.parse(readFileSync(join(root,'package.json'),'utf8'))
8
+ if(m.version!=='0.7.0'||m.engines.dsh!=='0.2.1-rc.5')throw new Error('Unreviewed package/host version')
9
+ console.log('Release artifacts verified; Python bytecode caches excluded.')
@@ -0,0 +1,9 @@
1
+ import {spawnSync} from 'node:child_process'
2
+ import {resolve} from 'node:path'
3
+ const root=resolve(import.meta.dirname,'..')
4
+ const python=process.env.PYTHON ?? (process.platform==='win32'?resolve(root,'runtime/win32-x64/python.exe'):'python3')
5
+ for (const [cmd,args] of [[python,['test/management_regression.py']],[process.execPath,['scripts/test-node.mjs']]]) {
6
+ const r=spawnSync(cmd,args,{cwd:root,stdio:'inherit',env:{...process.env,PYTHON:python}})
7
+ if(r.error)throw r.error
8
+ if(r.status!==0)process.exit(r.status??1)
9
+ }
@@ -0,0 +1,10 @@
1
+ import {build} from 'esbuild'
2
+ import {spawnSync} from 'node:child_process'
3
+ import {mkdtempSync,rmSync} from 'node:fs'
4
+ import {tmpdir} from 'node:os'
5
+ import {join,resolve} from 'node:path'
6
+ const root=resolve(import.meta.dirname,'..'),dir=mkdtempSync(join(tmpdir(),'xujie-test-build-')),outfile=join(dir,'test.mjs')
7
+ try{
8
+ await build({entryPoints:[join(root,'test/node-regression.entry.mjs')],bundle:true,platform:'node',format:'esm',target:'node22',outfile,external:['node:*'],alias:{'@deepseek-ai/cordis':join(root,'test/cordis-fixture.mjs')},logLevel:'warning'})
9
+ const r=spawnSync(process.execPath,[outfile],{cwd:root,stdio:'inherit',env:process.env});process.exitCode=r.status??1
10
+ }finally{rmSync(dir,{recursive:true,force:true})}