dsh-cortex-memory 0.1.0 → 0.2.0-rc.13
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/LICENSE +3 -3
- package/README.md +85 -146
- package/bench/ab-engine.mjs +60 -0
- package/bench/ann-bench.mjs +78 -0
- package/bench/hnsw-bench.mjs +79 -0
- package/bench/metrics.js +8 -0
- package/bench/migration-bench.mjs +69 -0
- package/bench/run.js +5 -2
- package/bench/synthetic.js +51 -7
- package/bench/vector-ab.mjs +76 -0
- package/bin/cortex.mjs +2 -3
- package/client.js +286 -4
- package/cordis.patch.yml +1 -1
- package/docs/install-windows.md +115 -27
- package/docs/models.md +165 -30
- package/docs/multimodal.md +112 -0
- package/docs/scopes.md +142 -0
- package/examples/README.md +72 -0
- package/examples/setup-cortex-bge.ps1 +66 -0
- package/examples/uninstall-cortex.ps1 +91 -0
- package/index.js +15 -1
- package/package.json +5 -12
- package/src/adapters/markdown-import.js +20 -1
- package/src/adapters/markdown.js +12 -2
- package/src/adapters/shiori.js +1 -1
- package/src/api/routes.js +148 -4
- package/src/api/validate.js +26 -0
- package/src/core/attachment-reader.js +31 -0
- package/src/core/attachments.js +39 -0
- package/src/core/config.js +36 -1
- package/src/core/model-config.js +3 -1
- package/src/core/profile.js +32 -0
- package/src/engines/develop.js +1 -1
- package/src/engines/encode.js +68 -30
- package/src/engines/extract.js +13 -8
- package/src/engines/growth.js +1 -1
- package/src/engines/reflect.js +1 -1
- package/src/engines/retrieve.js +44 -13
- package/src/host/runtime.js +1 -1
- package/src/memory/query.js +1 -1
- package/src/memory/tokenizer.js +19 -4
- package/src/security/scope-grants.js +73 -0
- package/src/self/appraisal.js +3 -3
- package/src/self/emotion.js +1 -1
- package/src/self/identity.js +1 -1
- package/src/self/relationship.js +2 -2
- package/src/service/cortex.js +191 -3
- package/src/store/database.js +33 -1
- package/src/store/repositories/edges.js +48 -4
- package/src/store/repositories/traces.js +23 -12
- package/src/store/schema.sql +1 -0
- package/src/tools/definitions.js +64 -5
- package/src/vector/external-ann.js +37 -0
- package/src/vector/hnsw.js +246 -0
- package/src/vector/index.js +85 -2
- package/docs/compatibility.md +0 -39
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Cortex bge 可选 profile
|
|
2
|
+
|
|
3
|
+
默认 **不开启** bge。这个示例只给想要本地语义召回的人用:
|
|
4
|
+
|
|
5
|
+
- 需要本地 Ollama / OpenAI-compatible embedding 服务
|
|
6
|
+
- 需要 `bge-m3`(1024 维)
|
|
7
|
+
- 查询延迟会明显高于 FTS
|
|
8
|
+
- 没有服务时不要开;失败会退开放,不会假装成功
|
|
9
|
+
|
|
10
|
+
## 一键创建独立 profile
|
|
11
|
+
|
|
12
|
+
```powershell
|
|
13
|
+
powershell -ExecutionPolicy Bypass -File .\examples\setup-cortex-bge.ps1 `
|
|
14
|
+
-Dsh "D:\path\to\dsh.cmd" `
|
|
15
|
+
-Profile cortex-bge `
|
|
16
|
+
-Package "C:\path\to\dsh-cortex-memory-0.1.0.tgz"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
首次先用 Ollama 启动模型服务:
|
|
20
|
+
|
|
21
|
+
```powershell
|
|
22
|
+
ollama serve
|
|
23
|
+
ollama pull bge-m3
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
脚本会:
|
|
27
|
+
|
|
28
|
+
1. 用 DSH 的 `web` 模板新建一个空 profile
|
|
29
|
+
2. 安装 cortex 包
|
|
30
|
+
3. 写独立的 `dataRoot/dbFile`
|
|
31
|
+
4. 写 `features.vector: true`
|
|
32
|
+
5. 写 `model-config.json`(local / bge-m3 / 1024)
|
|
33
|
+
|
|
34
|
+
它**不会**改 desktop,也不会自动启动服务。
|
|
35
|
+
|
|
36
|
+
## 启动
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
& $Dsh --profile cortex-bge --port 0 --no-open
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
打开后到 Cortex 设置里确认:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
embedding mode: local
|
|
46
|
+
model: bge-m3
|
|
47
|
+
dim: 1024
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 关闭
|
|
51
|
+
|
|
52
|
+
移除 profile 或把 `features.vector` 改回 `false`。
|
|
53
|
+
不想用语义模型时,FTS 仍是默认路径。
|
|
54
|
+
|
|
55
|
+
## 卸载 cortex
|
|
56
|
+
|
|
57
|
+
`dsh plugin remove` 不会删手写 patch。用:
|
|
58
|
+
|
|
59
|
+
```powershell
|
|
60
|
+
powershell -ExecutionPolicy Bypass -File .\examples\uninstall-cortex.ps1 `
|
|
61
|
+
-Dsh "D:\path\to\dsh.cmd" `
|
|
62
|
+
-Profile cortex-bge
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
脚本会:
|
|
66
|
+
|
|
67
|
+
1. 若已安装则 `plugin remove`
|
|
68
|
+
2. 只删除 `- id: cortex` 段,保留其他 patch
|
|
69
|
+
3. 跑一次 `--dump-config`
|
|
70
|
+
4. **不删 dataRoot**
|
|
71
|
+
|
|
72
|
+
如果已经手动移除,脚本会跳过 remove,仍清理残留 patch。
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param(
|
|
3
|
+
[string]$Dsh = 'dsh',
|
|
4
|
+
[string]$Profile = 'cortex-bge',
|
|
5
|
+
[string]$Package = 'dsh-cortex-memory',
|
|
6
|
+
[string]$DataRoot = (Join-Path $HOME '.dsh\cortex-bge'),
|
|
7
|
+
[string]$BaseUrl = 'http://127.0.0.1:11434/v1',
|
|
8
|
+
[string]$Model = 'bge-m3',
|
|
9
|
+
[int]$Dim = 1024
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
$ErrorActionPreference = 'Stop'
|
|
13
|
+
$dshCommand = Get-Command $Dsh -ErrorAction SilentlyContinue
|
|
14
|
+
if (-not $dshCommand) { throw "dsh command not found: $Dsh. Pass -Dsh with the path to dsh.cmd." }
|
|
15
|
+
$dshPath = $dshCommand.Source
|
|
16
|
+
$profilePath = Join-Path (Join-Path $HOME '.dsh\profiles') $Profile
|
|
17
|
+
if (Test-Path $profilePath) { throw "profile already exists: $profilePath. Choose another -Profile or remove it first." }
|
|
18
|
+
|
|
19
|
+
if ($Package -match '\.tgz$') {
|
|
20
|
+
$Package = (Resolve-Path $Package).Path
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
& $dshPath --profile $Profile --from-default-profile web --dump-config | Out-Null
|
|
24
|
+
& $dshPath plugin --profile $Profile add $Package
|
|
25
|
+
if ($LASTEXITCODE -ne 0) { throw "plugin install failed with exit code $LASTEXITCODE" }
|
|
26
|
+
|
|
27
|
+
New-Item -ItemType Directory -Force -Path $DataRoot | Out-Null
|
|
28
|
+
$modelConfig = [ordered]@{
|
|
29
|
+
embedding = [ordered]@{
|
|
30
|
+
mode = 'local'
|
|
31
|
+
baseUrl = $BaseUrl
|
|
32
|
+
model = $Model
|
|
33
|
+
dim = $Dim
|
|
34
|
+
apiKey = ''
|
|
35
|
+
}
|
|
36
|
+
rerank = [ordered]@{
|
|
37
|
+
enabled = $false
|
|
38
|
+
baseUrl = ''
|
|
39
|
+
model = ''
|
|
40
|
+
apiKey = ''
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
|
44
|
+
[System.IO.File]::WriteAllText(
|
|
45
|
+
(Join-Path $DataRoot 'model-config.json'),
|
|
46
|
+
(($modelConfig | ConvertTo-Json -Depth 5) + "`n"),
|
|
47
|
+
$utf8
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
$patchFile = Join-Path $profilePath 'cordis.patch.yml'
|
|
51
|
+
$patch = @"
|
|
52
|
+
# bge profile generated by examples/setup-cortex-bge.ps1
|
|
53
|
+
- id: cortex
|
|
54
|
+
name: dsh-cortex
|
|
55
|
+
config:
|
|
56
|
+
dataRoot: '$($DataRoot.Replace('\','/'))'
|
|
57
|
+
dbFile: '$((Join-Path $DataRoot 'cortex.sqlite').Replace('\','/'))'
|
|
58
|
+
features:
|
|
59
|
+
vector: true
|
|
60
|
+
"@
|
|
61
|
+
[System.IO.File]::WriteAllText($patchFile, $patch + "`n", $utf8)
|
|
62
|
+
|
|
63
|
+
Write-Host "profile: $profilePath"
|
|
64
|
+
Write-Host "dataRoot: $DataRoot"
|
|
65
|
+
Write-Host "Next: & '$dshPath' --profile $Profile --port 0 --no-open"
|
|
66
|
+
Write-Host "Start the local embedding service first: $BaseUrl"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param(
|
|
3
|
+
[string]$Dsh = 'dsh',
|
|
4
|
+
[string]$Profile = 'desktop',
|
|
5
|
+
[string]$Package = 'dsh-cortex',
|
|
6
|
+
[switch]$KeepPatch
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
$dshCommand = Get-Command $Dsh -ErrorAction SilentlyContinue
|
|
11
|
+
if (-not $dshCommand) { throw "dsh command not found: $Dsh. Pass -Dsh with the path to dsh.cmd." }
|
|
12
|
+
$dshPath = $dshCommand.Source
|
|
13
|
+
$profilePath = Join-Path (Join-Path $HOME '.dsh\profiles') $Profile
|
|
14
|
+
if (-not (Test-Path $profilePath)) { throw "profile not found: $profilePath" }
|
|
15
|
+
|
|
16
|
+
$packageJson = Join-Path $profilePath 'package.json'
|
|
17
|
+
$installed = $false
|
|
18
|
+
if (Test-Path $packageJson) {
|
|
19
|
+
try {
|
|
20
|
+
$manifest = Get-Content -LiteralPath $packageJson -Raw | ConvertFrom-Json
|
|
21
|
+
$installed = $null -ne $manifest.dependencies.$Package
|
|
22
|
+
} catch { $installed = $false }
|
|
23
|
+
}
|
|
24
|
+
if ($installed) {
|
|
25
|
+
& $dshPath plugin --profile $Profile remove $Package
|
|
26
|
+
if ($LASTEXITCODE -ne 0) { throw "plugin remove failed with exit code $LASTEXITCODE" }
|
|
27
|
+
} else {
|
|
28
|
+
Write-Host "$Package is not installed in $Profile; skipping remove"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
$patchFile = Join-Path $profilePath 'cordis.patch.yml'
|
|
32
|
+
$configuredDataRoot = $null
|
|
33
|
+
if (Test-Path $patchFile) {
|
|
34
|
+
foreach ($line in [System.IO.File]::ReadAllLines($patchFile)) {
|
|
35
|
+
if ($line -match '^\s*dataRoot:\s*(.+)$') {
|
|
36
|
+
$configuredDataRoot = $matches[1].Trim().Trim("'").Trim('"')
|
|
37
|
+
break
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (-not $configuredDataRoot) {
|
|
42
|
+
$dshHome = Join-Path $HOME '.dsh'
|
|
43
|
+
$configuredDataRoot = if ($Profile -eq 'desktop') { Join-Path $dshHome 'cortex' } else { Join-Path (Join-Path $dshHome 'cortex\profiles') $Profile }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (-not $KeepPatch) {
|
|
47
|
+
if (Test-Path $patchFile) {
|
|
48
|
+
$lines = [System.IO.File]::ReadAllLines($patchFile)
|
|
49
|
+
$ranges = New-Object System.Collections.Generic.List[object]
|
|
50
|
+
$start = -1
|
|
51
|
+
for ($i = 0; $i -lt $lines.Count; $i += 1) {
|
|
52
|
+
if ($lines[$i] -match '^- ') {
|
|
53
|
+
if ($start -ge 0) { $ranges.Add([pscustomobject]@{ From = $start; To = ($i - 1) }) }
|
|
54
|
+
$start = $i
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if ($start -ge 0) { $ranges.Add([pscustomobject]@{ From = $start; To = ($lines.Count - 1) }) }
|
|
58
|
+
|
|
59
|
+
$drop = @{}
|
|
60
|
+
foreach ($range in $ranges) {
|
|
61
|
+
$from = [int]$range.From
|
|
62
|
+
$to = [int]$range.To
|
|
63
|
+
$header = $lines[$from]
|
|
64
|
+
$body = ($lines[$from..$to] -join "`n")
|
|
65
|
+
if ($header -match '^- id:\s*cortex\s*$' -and $body -match 'name:\s*[''"]?dsh-cortex') {
|
|
66
|
+
for ($i = $from; $i -le $to; $i += 1) { $drop[$i] = $true }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ($drop.Count -gt 0) {
|
|
71
|
+
$remaining = @()
|
|
72
|
+
for ($i = 0; $i -lt $lines.Count; $i += 1) {
|
|
73
|
+
if (-not $drop.ContainsKey($i)) { $remaining += $lines[$i] }
|
|
74
|
+
}
|
|
75
|
+
$meaningful = @($remaining | Where-Object { $_.Trim() -and -not $_.Trim().StartsWith('#') })
|
|
76
|
+
$text = if ($meaningful.Count -eq 0) { "[]`n" } else { ($remaining -join "`n") + "`n" }
|
|
77
|
+
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
|
78
|
+
[System.IO.File]::WriteAllText($patchFile, $text, $utf8)
|
|
79
|
+
Write-Host "removed cortex patch block from $patchFile"
|
|
80
|
+
} else {
|
|
81
|
+
Write-Host "no cortex patch block found in $patchFile"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
& $dshPath --profile $Profile --dump-config | Out-Null
|
|
86
|
+
if ($LASTEXITCODE -ne 0) { Write-Warning "dump-config still fails; inspect $profilePath\cordis.patch.yml" }
|
|
87
|
+
else { Write-Host "dump-config ok" }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Write-Host "profile left in place: $profilePath"
|
|
91
|
+
Write-Host "cortex data was NOT deleted: $configuredDataRoot"
|
package/index.js
CHANGED
|
@@ -22,12 +22,18 @@ export const Config = Schema ? Schema.object({
|
|
|
22
22
|
dataRoot: Schema.string().description('Cortex 数据目录(默认 DSH_HOME/cortex)'),
|
|
23
23
|
dbFile: Schema.string().description('SQLite 文件;默认 dataRoot/cortex.sqlite'),
|
|
24
24
|
scope: Schema.string().default('global').description('默认 scope'),
|
|
25
|
+
readOnlyScopes: Schema.array(Schema.string()).default([]).description('只读共享 scope 列表'),
|
|
26
|
+
scopeWriteGrants: Schema.array(Schema.any()).default([]).description('显式写授权:scope:agent 或 {grant, until}'),
|
|
25
27
|
contextBudget: Schema.natural().default(2200).description('检索注入的 token 预算'),
|
|
26
28
|
promptBudget: Schema.natural().default(1600).description('常驻自我/工作记忆提示词预算'),
|
|
27
29
|
recallTimeoutMs: Schema.natural().default(1200).description('前台召回硬截止'),
|
|
28
30
|
asyncJobs: Schema.boolean().default(true).description('允许后台任务'),
|
|
29
31
|
captureSubagents: Schema.boolean().default(false).description('是否把子代理会话也写入记忆'),
|
|
30
32
|
vectorEnabled: Schema.boolean().default(false).description('启用向量检索(默认用离线 hash embedder 兜底)'),
|
|
33
|
+
vectorAnnMin: Schema.natural().default(20000).description('向量 ANN 生效的最小条数'),
|
|
34
|
+
vectorAnnEngine: Schema.string().default('hnsw').description('ANN 引擎:hnsw(默认)或 nsw(旧实现)'),
|
|
35
|
+
vectorAnnEndpoint: Schema.string().default('').description('外置 ANN HTTP endpoint;空则不用外置'),
|
|
36
|
+
vectorAnnTimeoutMs: Schema.natural().default(800).description('外置 ANN 超时毫秒'),
|
|
31
37
|
scheduler: Schema.boolean().default(true).description('启用宿主心跳(安全自主 tick/作业/保留计划)'),
|
|
32
38
|
schedulerIntervalMs: Schema.natural().default(60000).description('心跳间隔毫秒'),
|
|
33
39
|
maxCallsPerHour: Schema.natural().default(120).description('检索调用预算(宿主 prefetch 不计入)'),
|
|
@@ -48,6 +54,9 @@ export const Config = Schema ? Schema.object({
|
|
|
48
54
|
reflect: Schema.boolean().default(true),
|
|
49
55
|
growth: Schema.boolean().default(true),
|
|
50
56
|
vector: Schema.boolean().default(false),
|
|
57
|
+
vectorAnn: Schema.boolean().default(false),
|
|
58
|
+
vectorAnnExternal: Schema.boolean().default(false),
|
|
59
|
+
attachmentReader: Schema.boolean().default(false),
|
|
51
60
|
graph: Schema.boolean().default(true),
|
|
52
61
|
llm: Schema.boolean().default(false),
|
|
53
62
|
api: Schema.boolean().default(true),
|
|
@@ -109,7 +118,11 @@ export async function apply(ctx, config = {}) {
|
|
|
109
118
|
for (const tool of createToolDefinitions(cortex)) disposers.push(ctx.tools.register(tool));
|
|
110
119
|
if (runtime) disposers.push(runtime.attach(ctx));
|
|
111
120
|
if (scheduler) scheduler.start();
|
|
112
|
-
if (config.features?.api !== false) disposers.push(mountApi(ctx, {
|
|
121
|
+
if (config.features?.api !== false) disposers.push(mountApi(ctx, {
|
|
122
|
+
cortex,
|
|
123
|
+
logger: ctx.logger,
|
|
124
|
+
getExtractor: () => ctx.get?.('cortexExtractor') ?? null,
|
|
125
|
+
}));
|
|
113
126
|
if (typeof ctx.systemPrompt?.section === 'function') {
|
|
114
127
|
disposers.push(ctx.systemPrompt.section({
|
|
115
128
|
name: 'cortex-self',
|
|
@@ -125,6 +138,7 @@ export async function apply(ctx, config = {}) {
|
|
|
125
138
|
search: cortex.search,
|
|
126
139
|
get: cortex.get,
|
|
127
140
|
write: cortex.write,
|
|
141
|
+
ingestExtractedText: cortex.ingestExtractedText,
|
|
128
142
|
self: cortex.self,
|
|
129
143
|
health: cortex.health,
|
|
130
144
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cortex-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-rc.13",
|
|
4
4
|
"description": "A layered, temporal, self-developing memory engine for DeepSeek Harness: working, episodic, semantic, procedural and reflective memory with hybrid retrieval, DSR forgetting, reflection, consolidation, gated self-evolution and guardian.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"README.md",
|
|
29
29
|
"docs/install-windows.md",
|
|
30
30
|
"docs/models.md",
|
|
31
|
-
"docs/
|
|
31
|
+
"docs/scopes.md",
|
|
32
|
+
"docs/multimodal.md",
|
|
33
|
+
"examples",
|
|
32
34
|
"LICENSE"
|
|
33
35
|
],
|
|
34
36
|
"scripts": {
|
|
@@ -72,14 +74,5 @@
|
|
|
72
74
|
"self-evolving",
|
|
73
75
|
"sqlite"
|
|
74
76
|
],
|
|
75
|
-
"license": "MIT"
|
|
76
|
-
"author": "dsh-cortex-memory contributors",
|
|
77
|
-
"repository": {
|
|
78
|
-
"type": "git",
|
|
79
|
-
"url": "https://github.com/Lh1314-China/dsh-cortex-memory.git"
|
|
80
|
-
},
|
|
81
|
-
"homepage": "https://github.com/Lh1314-China/dsh-cortex-memory#readme",
|
|
82
|
-
"bugs": {
|
|
83
|
-
"url": "https://github.com/Lh1314-China/dsh-cortex-memory/issues"
|
|
84
|
-
}
|
|
77
|
+
"license": "MIT"
|
|
85
78
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { hashText } from '../core/ids.js';
|
|
4
|
+
import { normalizeAttachments } from '../core/attachments.js';
|
|
4
5
|
|
|
5
6
|
function parseFrontmatter(text) {
|
|
6
7
|
const source = String(text ?? '');
|
|
@@ -14,6 +15,10 @@ function parseFrontmatter(text) {
|
|
|
14
15
|
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
15
16
|
if (!match) continue;
|
|
16
17
|
let value = match[2].trim();
|
|
18
|
+
if (match[1] === 'attachments') {
|
|
19
|
+
meta.attachments = value;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
17
22
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
|
|
18
23
|
if (value.startsWith('[') && value.endsWith(']')) value = value.slice(1, -1).split(',').map((item) => item.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
|
|
19
24
|
meta[match[1]] = value;
|
|
@@ -24,6 +29,14 @@ function parseFrontmatter(text) {
|
|
|
24
29
|
export function parseMemoryMarkdown(text) {
|
|
25
30
|
const { meta, body } = parseFrontmatter(text);
|
|
26
31
|
const title = meta.title || body.split(/\r?\n/).find((line) => line.startsWith('# '))?.slice(2).trim() || '未命名记忆';
|
|
32
|
+
let attachments = [];
|
|
33
|
+
if (meta.attachments) {
|
|
34
|
+
try {
|
|
35
|
+
attachments = normalizeAttachments(JSON.parse(meta.attachments));
|
|
36
|
+
} catch {
|
|
37
|
+
attachments = [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
27
40
|
return {
|
|
28
41
|
id: meta.id || null,
|
|
29
42
|
kind: meta.kind || 'utterance',
|
|
@@ -33,6 +46,7 @@ export function parseMemoryMarkdown(text) {
|
|
|
33
46
|
importance: Number(meta.importance ?? 0.5),
|
|
34
47
|
recordedAt: meta.recorded_at || meta.date || null,
|
|
35
48
|
state: meta.state || 'active',
|
|
49
|
+
attachments,
|
|
36
50
|
};
|
|
37
51
|
}
|
|
38
52
|
|
|
@@ -103,6 +117,7 @@ export function importMarkdown(cortex, root, { dryRun = false, scopeId = 'global
|
|
|
103
117
|
kind: parsed.kind,
|
|
104
118
|
tags: parsed.tags,
|
|
105
119
|
importance: parsed.importance,
|
|
120
|
+
attachments: parsed.attachments,
|
|
106
121
|
explicit: true,
|
|
107
122
|
operationId: `markdown:${parsed.id ?? hashText(`${file}:${parsed.title}:${parsed.content}`)}`,
|
|
108
123
|
now: parsed.recordedAt ? Date.parse(parsed.recordedAt) : undefined,
|
|
@@ -142,10 +157,12 @@ export function syncMarkdown(cortex, root, { dryRun = false, scopeId = 'global',
|
|
|
142
157
|
if (parsed.id) {
|
|
143
158
|
const current = cortex.repos.traces.get(parsed.id);
|
|
144
159
|
if (current) {
|
|
160
|
+
const currentAttachments = current.metadata?.attachments ?? [];
|
|
145
161
|
const changed = current.content.trim() !== parsed.content.trim()
|
|
146
162
|
|| (current.title ?? '') !== (parsed.title ?? '')
|
|
147
163
|
|| JSON.stringify(current.tags ?? []) !== JSON.stringify(parsed.tags ?? [])
|
|
148
|
-
|| Number(current.importance) !== Number(parsed.importance)
|
|
164
|
+
|| Number(current.importance) !== Number(parsed.importance)
|
|
165
|
+
|| JSON.stringify(currentAttachments) !== JSON.stringify(parsed.attachments ?? []);
|
|
149
166
|
if (!changed) {
|
|
150
167
|
items.push({ file, action: 'unchanged', traceId: parsed.id });
|
|
151
168
|
continue;
|
|
@@ -156,6 +173,7 @@ export function syncMarkdown(cortex, root, { dryRun = false, scopeId = 'global',
|
|
|
156
173
|
content: parsed.content,
|
|
157
174
|
tags: parsed.tags,
|
|
158
175
|
importance: parsed.importance,
|
|
176
|
+
metadata: { ...(current.metadata ?? {}), attachments: parsed.attachments },
|
|
159
177
|
});
|
|
160
178
|
cortex.repos.audit?.record?.({ actor: 'user', action: 'markdown.sync.update', targetType: 'trace', targetId: parsed.id, detail: { file } });
|
|
161
179
|
}
|
|
@@ -171,6 +189,7 @@ export function syncMarkdown(cortex, root, { dryRun = false, scopeId = 'global',
|
|
|
171
189
|
kind: parsed.kind,
|
|
172
190
|
tags: parsed.tags,
|
|
173
191
|
importance: parsed.importance,
|
|
192
|
+
attachments: parsed.attachments,
|
|
174
193
|
explicit: true,
|
|
175
194
|
operationId: `markdown:${parsed.id ?? hashText(`${file}:${parsed.title}:${parsed.content}`)}`,
|
|
176
195
|
now: parsed.recordedAt ? Date.parse(parsed.recordedAt) : undefined,
|
package/src/adapters/markdown.js
CHANGED
|
@@ -14,7 +14,8 @@ function writeAtomic(file, content) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function traceToMarkdown(trace) {
|
|
17
|
-
|
|
17
|
+
const attachments = Array.isArray(trace.metadata?.attachments) ? trace.metadata.attachments : [];
|
|
18
|
+
const lines = [
|
|
18
19
|
'---',
|
|
19
20
|
`id: "${front(trace.id)}"`,
|
|
20
21
|
`kind: "${front(trace.kind)}"`,
|
|
@@ -26,11 +27,20 @@ export function traceToMarkdown(trace) {
|
|
|
26
27
|
`importance: ${Number(trace.importance ?? 0)}`,
|
|
27
28
|
`confidence: ${Number(trace.confidence ?? 0)}`,
|
|
28
29
|
`tags: [${(trace.tags ?? []).map((tag) => `"${front(tag)}"`).join(', ')}]`,
|
|
30
|
+
...(attachments.length ? [`attachments: ${JSON.stringify(attachments)}`] : []),
|
|
29
31
|
'---',
|
|
30
32
|
'',
|
|
31
33
|
trace.content ?? '',
|
|
32
34
|
'',
|
|
33
|
-
]
|
|
35
|
+
];
|
|
36
|
+
if (attachments.length) {
|
|
37
|
+
lines.push('## Attachments (metadata only)', '');
|
|
38
|
+
for (const item of attachments) {
|
|
39
|
+
lines.push(`- ${item.kind}: ${item.name ?? item.uri}${item.mime ? ` (${item.mime})` : ''}${item.bytes ? ` · ${item.bytes} B` : ''}`);
|
|
40
|
+
}
|
|
41
|
+
lines.push('');
|
|
42
|
+
}
|
|
43
|
+
return lines.join('\n');
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
export function exportMarkdown(cortex, targetDir, { scopeId = 'global' } = {}) {
|
package/src/adapters/shiori.js
CHANGED
|
@@ -43,7 +43,7 @@ export function classifySparkCard(card = {}) {
|
|
|
43
43
|
const text = `${card.title ?? ''} ${card.content ?? card.contentPreview ?? card.summary ?? ''} ${(card.triggers ?? []).join(' ')}`;
|
|
44
44
|
if (/应该|必须|不要|先|再|每次|以后|记得|避免|优先|禁止|务必|规则|教训/.test(text)) return 'instruction';
|
|
45
45
|
if (/插件|安装|配置|命令|路径|报错|测试|接口|版本|构建|项目|函数|文件|系统|git|npm|dsh|api|bug|fix|build|deploy|代码|模块|记忆区域|海马体|语义|面板|开关|检索|时间线|叙事|焦点|健康|体检|策略|权限|工具|上下文|注入|向量|图谱|工作卡|火花|协议|服务|进程|数据库|SQLite|脚本|日志/i.test(text)) return 'observation';
|
|
46
|
-
if (
|
|
46
|
+
if (/主人|本鲸|喜欢|爱|难过|开心|心疼|陪|声音|白饭|抱|想|梦|日记|称呼|相处|情绪|温暖|孤单|甜|笑|哭|晚安|早安/.test(text)) return 'preference';
|
|
47
47
|
return 'utterance';
|
|
48
48
|
}
|
|
49
49
|
|