memory-eternal 0.11.0 → 0.13.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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.en.md +22 -4
- package/README.md +22 -4
- package/cursor-extension/README.md +41 -0
- package/cursor-extension/extension.js +58 -0
- package/cursor-extension/icon.png +0 -0
- package/cursor-extension/package.json +54 -0
- package/dsh-tui-plugin/README.md +119 -0
- package/dsh-tui-plugin/cordis.patch.yml +3 -0
- package/dsh-tui-plugin/demo-audit.mjs +535 -0
- package/dsh-tui-plugin/demo.mjs +743 -0
- package/dsh-tui-plugin/dsh-plugin.json +42 -0
- package/dsh-tui-plugin/main.js +97 -0
- package/dsh-tui-plugin/package.json +31 -0
- package/dsh-tui-plugin/scene.js +151 -0
- package/lib/client.js +1 -1
- package/orca-plugin/README.md +58 -0
- package/orca-plugin/assets/icon.png +0 -0
- package/orca-plugin/orca-plugin.json +36 -0
- package/orca-plugin/panel.html +215 -0
- package/package.json +4 -1
- package/skills/memory-open/SKILL.md +22 -0
- package/web/app.js +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "urn:dsh-std:community-draft:dsh-plugin:0.15",
|
|
3
|
+
"id": "memory-eternal-tui",
|
|
4
|
+
"name": "memory-eternal-tui",
|
|
5
|
+
"version": "0.13.0",
|
|
6
|
+
"manifestVersion": "0.15",
|
|
7
|
+
"description": "Open Memory Atlas from dsh-TUI: native /audit scene plus browser launcher.",
|
|
8
|
+
"facets": {
|
|
9
|
+
"host": {
|
|
10
|
+
"entry": "main.js",
|
|
11
|
+
"apiVersion": "v1alpha1"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"requires": {
|
|
15
|
+
"contracts": [
|
|
16
|
+
{
|
|
17
|
+
"apiVersion": "commands.dsh/v1alpha1",
|
|
18
|
+
"kind": "Command"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"permissions": [],
|
|
23
|
+
"contributes": {
|
|
24
|
+
"commands": [
|
|
25
|
+
{
|
|
26
|
+
"id": "memory-eternal-tui.open",
|
|
27
|
+
"title": "memory",
|
|
28
|
+
"description": "Open Memory Atlas in the browser"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "memory-eternal-tui.audit",
|
|
32
|
+
"title": "audit",
|
|
33
|
+
"description": "Open the native Memory Atlas audit scene"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"subscriptions": [],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"source": {
|
|
40
|
+
"repository": "https://github.com/EternalNight996/memory-eternal"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// memory-eternal-tui — dsh-TUI 的官方命令入口。
|
|
2
|
+
//
|
|
3
|
+
// 这里不做第二套 TUI UI:命令只负责把用户送到同一套 Memory Atlas Web UI。
|
|
4
|
+
// 审批仍然在 Web 里由人完成,不新增 agent 工具,也不触碰审核守卫。
|
|
5
|
+
import { spawn } from 'node:child_process'
|
|
6
|
+
import { AuditScene } from './scene.js'
|
|
7
|
+
|
|
8
|
+
export const name = 'memory-eternal-tui'
|
|
9
|
+
export const inject = ['agents']
|
|
10
|
+
|
|
11
|
+
const URL = 'http://127.0.0.1:7999/'
|
|
12
|
+
const COMMAND_ID = 'memory-eternal-tui.open'
|
|
13
|
+
const AUDIT_COMMAND_ID = 'memory-eternal-tui.audit'
|
|
14
|
+
const AUDIT_SCENE_ID = 'memory-eternal-tui-audit'
|
|
15
|
+
|
|
16
|
+
function openUrl() {
|
|
17
|
+
const cmd = process.platform === 'win32' ? 'rundll32' : process.platform === 'darwin' ? 'open' : 'xdg-open'
|
|
18
|
+
const args = process.platform === 'win32' ? ['url.dll,FileProtocolHandler', URL] : [URL]
|
|
19
|
+
try {
|
|
20
|
+
const child = spawn(cmd, args, { detached: true, stdio: 'ignore', windowsHide: true })
|
|
21
|
+
child.on('error', () => {})
|
|
22
|
+
child.unref()
|
|
23
|
+
} catch { /* 平台没有 opener 时由调用方提示手动访问 */ }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function openMemory() {
|
|
27
|
+
// 优先让 CLI 确保常驻 Web 已启动并打开浏览器;命令不存在时直接打开 URL。
|
|
28
|
+
try {
|
|
29
|
+
const child = spawn('dsh-memory', ['open'], {
|
|
30
|
+
detached: true,
|
|
31
|
+
stdio: 'ignore',
|
|
32
|
+
shell: true,
|
|
33
|
+
windowsHide: true,
|
|
34
|
+
})
|
|
35
|
+
child.on('error', () => openUrl())
|
|
36
|
+
child.on('exit', (code) => { if (code !== 0) openUrl() })
|
|
37
|
+
child.unref()
|
|
38
|
+
} catch {
|
|
39
|
+
openUrl()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function apply(ctx) {
|
|
44
|
+
const host = ctx.get('tuiPluginHost', false)
|
|
45
|
+
if (!host) return
|
|
46
|
+
|
|
47
|
+
// tuiScenes can mount after this activation on a cold boot. Soft-probe and
|
|
48
|
+
// retry briefly instead of silently leaving /audit dead for the session.
|
|
49
|
+
let sceneDisposer = null
|
|
50
|
+
let sceneTimer = null
|
|
51
|
+
let attempts = 0
|
|
52
|
+
const registerScene = () => {
|
|
53
|
+
sceneTimer = null
|
|
54
|
+
const scenes = ctx.get('tuiScenes', false)
|
|
55
|
+
if (scenes && !sceneDisposer) {
|
|
56
|
+
try {
|
|
57
|
+
sceneDisposer = scenes.register({ id: AUDIT_SCENE_ID, title: 'Memory Atlas / 待审', component: AuditScene }, ctx)
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.warn('[memory-eternal-tui] scene registration failed:', error)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!sceneDisposer && attempts < 40) {
|
|
63
|
+
attempts += 1
|
|
64
|
+
sceneTimer = setTimeout(registerScene, 50)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
registerScene()
|
|
68
|
+
ctx.effect(() => {
|
|
69
|
+
if (sceneTimer) clearTimeout(sceneTimer)
|
|
70
|
+
if (sceneDisposer) sceneDisposer()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const dispose = host.registerCommand(ctx, COMMAND_ID, {
|
|
74
|
+
recordInput: false,
|
|
75
|
+
handler: () => {
|
|
76
|
+
openMemory()
|
|
77
|
+
return { kind: 'success', text: `Memory Atlas 已打开:${URL}` }
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
ctx.effect(() => dispose)
|
|
81
|
+
|
|
82
|
+
const disposeAudit = host.registerCommand(ctx, AUDIT_COMMAND_ID, {
|
|
83
|
+
recordInput: false,
|
|
84
|
+
handler: () => {
|
|
85
|
+
const runtime = ctx.get('tuiScenes', false)
|
|
86
|
+
if (!runtime) return { kind: 'error', text: '当前 dsh-tui 没有 scenes 运行时' }
|
|
87
|
+
if (runtime.active?.id === AUDIT_SCENE_ID) runtime.close()
|
|
88
|
+
const opened = runtime.open(AUDIT_SCENE_ID)
|
|
89
|
+
return opened
|
|
90
|
+
? { kind: 'success', text: 'Memory Atlas / 待审 已打开' }
|
|
91
|
+
: { kind: 'error', text: '审核场景未注册' }
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
ctx.effect(() => disposeAudit)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default { name, inject, apply }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "memory-eternal-tui",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "Open Memory Atlas from dsh-TUI with /memory.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "main.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"main.js",
|
|
9
|
+
"scene.js",
|
|
10
|
+
"demo.mjs",
|
|
11
|
+
"dsh-plugin.json",
|
|
12
|
+
"cordis.patch.yml",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"dsh",
|
|
17
|
+
"dsh-tui",
|
|
18
|
+
"memory-eternal",
|
|
19
|
+
"memory",
|
|
20
|
+
"plugin"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dsh": {
|
|
24
|
+
"bundle": {
|
|
25
|
+
"patch": "./cordis.patch.yml"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// dsh-tui 原生审核场景(最小闭环)
|
|
2
|
+
//
|
|
3
|
+
// 不复制 Web UI:只把「待审队列 + 全选 + 批准/驳回」做成 dsh-tui 原生场景。
|
|
4
|
+
// 日常浏览/长文仍可回到 Web 或外部 TUI 客户端。
|
|
5
|
+
|
|
6
|
+
const API = process.env.MEMORY_ETERNAL_API || 'http://127.0.0.1:7999/memory-eternal/api'
|
|
7
|
+
|
|
8
|
+
async function apiJson(path, options = {}) {
|
|
9
|
+
const res = await fetch(API + path, { ...options, signal: AbortSignal.timeout(10000) })
|
|
10
|
+
const data = await res.json().catch(() => null)
|
|
11
|
+
if (!res.ok || !data || data.ok === false) throw new Error((data && data.error) || `HTTP ${res.status}`)
|
|
12
|
+
return data
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function timeOf(value) {
|
|
16
|
+
const d = new Date(value || Date.now())
|
|
17
|
+
return Number.isNaN(d.getTime()) ? '' : d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function AuditScene(props) {
|
|
21
|
+
const { React, ui, close } = props
|
|
22
|
+
const { Box, Text, useInput, useTerminalSize } = ui
|
|
23
|
+
const { useState, useEffect } = React
|
|
24
|
+
const size = useTerminalSize ? useTerminalSize() : { columns: 110, rows: 34 }
|
|
25
|
+
const columns = size.columns || 110
|
|
26
|
+
const rows = size.rows || 34
|
|
27
|
+
|
|
28
|
+
const [cards, setCards] = useState([])
|
|
29
|
+
const [index, setIndex] = useState(0)
|
|
30
|
+
const [marked, setMarked] = useState(() => new Set())
|
|
31
|
+
const [status, setStatus] = useState('读取待审队列…')
|
|
32
|
+
const [loading, setLoading] = useState(true)
|
|
33
|
+
|
|
34
|
+
const load = async () => {
|
|
35
|
+
setLoading(true)
|
|
36
|
+
setStatus('读取待审队列…')
|
|
37
|
+
try {
|
|
38
|
+
const data = await apiJson('/cards?status=pending&limit=300')
|
|
39
|
+
setCards((data.cards || []).map((c) => ({
|
|
40
|
+
path: c.path,
|
|
41
|
+
kind: c.kind || 'knowledge',
|
|
42
|
+
title: c.title || '无标题',
|
|
43
|
+
summary: String(c.summary || '').replace(/^#{1,6}\s*/gm, '').replace(/\s+/g, ' ').slice(0, 260),
|
|
44
|
+
source: c.submittedBy || 'memory',
|
|
45
|
+
time: timeOf(c.updated || c.created),
|
|
46
|
+
})))
|
|
47
|
+
setIndex(0)
|
|
48
|
+
setMarked(new Set())
|
|
49
|
+
setStatus('')
|
|
50
|
+
} catch (err) {
|
|
51
|
+
setStatus('读取失败:' + err.message)
|
|
52
|
+
} finally {
|
|
53
|
+
setLoading(false)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
useEffect(() => { void load() }, [])
|
|
58
|
+
|
|
59
|
+
const current = cards[index]
|
|
60
|
+
const visible = cards
|
|
61
|
+
const selected = marked.size
|
|
62
|
+
const apply = async (decision) => {
|
|
63
|
+
const targets = selected ? visible.filter((c) => marked.has(c.path)) : (current ? [current] : [])
|
|
64
|
+
if (!targets.length) return
|
|
65
|
+
setStatus(decision === 'a' ? `批准 ${targets.length} 张…` : `驳回 ${targets.length} 张…`)
|
|
66
|
+
try {
|
|
67
|
+
await Promise.all(targets.map((c) => apiJson('/audit/' + (decision === 'a' ? 'approve' : 'reject'), {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: { 'Content-Type': 'application/json' },
|
|
70
|
+
body: JSON.stringify({ path: c.path }),
|
|
71
|
+
})))
|
|
72
|
+
const ids = new Set(targets.map((c) => c.path))
|
|
73
|
+
setCards((prev) => prev.filter((c) => !ids.has(c.path)))
|
|
74
|
+
setIndex(0)
|
|
75
|
+
setMarked(new Set())
|
|
76
|
+
setStatus(`✓ ${decision === 'a' ? '已批准' : '已驳回'} ${targets.length} 张`)
|
|
77
|
+
} catch (err) {
|
|
78
|
+
setStatus('操作失败:' + err.message)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
useInput((input, key) => {
|
|
83
|
+
if (key.escape || input === 'q') { close(); return }
|
|
84
|
+
if (input === 'r' && !key.ctrl) { void apply('r'); return }
|
|
85
|
+
if (input === 'a' && !key.ctrl) { void apply('a'); return }
|
|
86
|
+
if (input === 's' || (key.ctrl && input === 'a')) {
|
|
87
|
+
setMarked((prev) => {
|
|
88
|
+
const all = visible.length > 0 && visible.every((c) => prev.has(c.path))
|
|
89
|
+
return all ? new Set() : new Set(visible.map((c) => c.path))
|
|
90
|
+
})
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
if (key.upArrow) { setIndex((i) => Math.max(0, i - 1)); return }
|
|
94
|
+
if (key.downArrow) { setIndex((i) => Math.min(Math.max(0, visible.length - 1), i + 1)); return }
|
|
95
|
+
if (key.space || input === ' ') {
|
|
96
|
+
if (!current) return
|
|
97
|
+
setMarked((prev) => {
|
|
98
|
+
const next = new Set(prev)
|
|
99
|
+
if (next.has(current.path)) next.delete(current.path)
|
|
100
|
+
else next.add(current.path)
|
|
101
|
+
return next
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const listH = Math.max(3, rows - 7)
|
|
107
|
+
const rowW = Math.max(36, Math.floor((columns - 3) * 0.42))
|
|
108
|
+
const previewW = Math.max(20, columns - rowW - 3)
|
|
109
|
+
const start = Math.max(0, Math.min(index - Math.floor(listH / 2), Math.max(0, visible.length - listH)))
|
|
110
|
+
const list = (visible || []).slice(start, start + listH)
|
|
111
|
+
const previewLines = []
|
|
112
|
+
if (current) {
|
|
113
|
+
previewLines.push(current.kind + ' · ' + current.time + ' · ' + current.source)
|
|
114
|
+
previewLines.push('')
|
|
115
|
+
previewLines.push(current.title)
|
|
116
|
+
previewLines.push('')
|
|
117
|
+
for (let i = 0; i < current.summary.length; i += Math.max(16, previewW - 4)) {
|
|
118
|
+
previewLines.push(current.summary.slice(i, i + Math.max(16, previewW - 4)))
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
previewLines.push(loading ? '读取中…' : '没有待审卡')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const h = React.createElement
|
|
125
|
+
return h(Box, { flexDirection: 'column', width: columns, height: rows },
|
|
126
|
+
h(Box, { flexShrink: 0 },
|
|
127
|
+
h(Text, { color: 'remember', bold: true }, '● MEMORY ATLAS'),
|
|
128
|
+
h(Text, { dimColor: true }, ` / 待审 ${cards.length} 张` + (selected ? ` 已选 ${selected}` : '')),
|
|
129
|
+
),
|
|
130
|
+
h(Text, { dimColor: true }, '─'.repeat(Math.max(1, columns - 1))),
|
|
131
|
+
h(Box, { flexDirection: 'row', flexGrow: 1, overflow: 'hidden' },
|
|
132
|
+
h(Box, { flexDirection: 'column', width: rowW, borderStyle: 'round', flexShrink: 0 },
|
|
133
|
+
h(Text, { dimColor: true }, ` ${selected ? '▣' : '☐'} 全选 ${visible.length} s 全选`),
|
|
134
|
+
...list.map((c, i) => {
|
|
135
|
+
const on = start + i === index
|
|
136
|
+
const mark = marked.has(c.path) ? '◉' : '○'
|
|
137
|
+
const label = ` ${on ? '▌' : ' '}${mark} [${c.kind}] ${c.title}`
|
|
138
|
+
return h(Text, { key: c.path, inverse: on, wrap: 'truncate' }, label.slice(0, rowW - 2))
|
|
139
|
+
}),
|
|
140
|
+
),
|
|
141
|
+
h(Box, { flexDirection: 'column', width: previewW, borderStyle: 'round', flexGrow: 1 },
|
|
142
|
+
...previewLines.slice(0, Math.max(1, rows - 4)).map((line, i) =>
|
|
143
|
+
h(Text, { key: i, wrap: 'truncate', color: i === 0 ? 'remember' : undefined }, ' ' + line)),
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
h(Text, { color: status.startsWith('✓') ? 'success' : status.startsWith('操作失败') || status.startsWith('读取失败') ? 'error' : undefined, dimColor: !status },
|
|
147
|
+
' ' + (status || '↑↓ 移动 Space 多选 s 全选 a 批准 r 驳回 Esc 退出')),
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default AuditScene
|
package/lib/client.js
CHANGED
|
@@ -701,7 +701,7 @@ Option A, because:
|
|
|
701
701
|
.mc-audit-batchcard > .mc-btn { width: 100%; }
|
|
702
702
|
.mc-audit-batchcard > div[style*="flex: 1"] { display: none; }
|
|
703
703
|
}
|
|
704
|
-
`;function Ea(t){t.effect(()=>t.locale.register(kt,{zh:da,en:ca}),"memory-eternal: locale");let a=t.locale.bind(kt);t.effect(()=>t.slots.inject("settings.section",()=>t.slots.register({name:"settings.section",id:kt,order:25,label:()=>a("nav"),locale:kt,inject:()=>({})},()=>o.default.createElement(Wa,{t:a}))),"memory-eternal: settings section"),t.effect(()=>t.slots.inject("sidebar.footer.action",()=>t.slots.register({name:"sidebar.footer.action",id:`${kt}:footer`,order:100,label:()=>a("nav"),locale:kt,inject:()=>({})},i=>o.default.createElement($a,{t:a,locale:t.locale,wide:!(i&&i.wide===!1)}))),"memory-eternal: sidebar footer action")}function Oa(){let[t,a]=(0,o.useState)($+"/ui/app");return(0,o.useEffect)(()=>{let i=!0,g=()=>fetch("/memory-eternal/api/web-info").then(c=>c.json()).then(c=>{i&&c&&c.ok&&c.alive!==!1&&c.url&&a(c.url.endsWith("/")?c.url:c.url+"/")}).catch(()=>{});return fetch($+"/ui/app",{method:"HEAD"}).then(c=>{if(i&&!c.ok)return g()}).catch(()=>i?g():void 0),()=>{i=!1}},[]),t}function Wa({t}){return(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsx)(ua,{t,onReload:()=>{},version:0})]})}function $a({t,locale:a,wide:i}){let[g,c]=(0,o.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${i?"":" rail"}`,children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>c(!0),"aria-label":t("nav"),title:t("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(Ua,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:t("nav")})]}),g&&(0,e.jsx)(_a,{t,locale:a,onClose:()=>c(!1)})]})}function _a({t,locale:a,onClose:i,tab:g}){let[c,s]=(0,o.useState)(!1),h=(0,o.useRef)(null),k=Oa(),S=(0,o.useCallback)(N=>a&&typeof a.subscribe=="function"?a.subscribe(N):()=>{},[a]),L=(0,o.useCallback)(()=>a&&typeof a.getSnapshot=="function"?a.getSnapshot():null,[a]),M=(0,o.useSyncExternalStore)(S,L),x=M&&(M.active==="zh"||M.active==="en")?M.active:null,m=(0,o.useRef)(null);m.current===null&&x&&(m.current=x);let z=(0,o.useMemo)(()=>{let N=m.current?`lang=${m.current}`:"";if(g==="config")return`${$}/ui/config?tab=config${N?`&${N}`:""}`;let P=k.replace(/\/$/,""),T=P.endsWith("/ui/app")?"?":"/?";return g?`${P}${T}tab=${g}${N?`&${N}`:""}`:N?`${P}${T}${N}`:k},[k,g]);return(0,o.useEffect)(()=>{let N=h.current&&h.current.contentWindow;N&&x&&N.postMessage({source:"memory-eternal",type:"locale",lang:x},"*")},[x]),(0,o.useEffect)(()=>{let N=P=>{P.key==="Escape"&&i()};return window.addEventListener("keydown",N),()=>window.removeEventListener("keydown",N)},[i]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:i,children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsx)("div",{className:"me-modal"+(c?" me-modal-full":""),onClick:N=>N.stopPropagation(),children:(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0,position:"relative"},children:[(0,e.jsxs)("div",{style:{position:"absolute",top:8,right:8,zIndex:10,display:"flex",gap:4,padding:"4px 6px",background:"var(--dsw-alias-bg-overlay, rgba(17,24,39,0.45))",borderRadius:8,backdropFilter:"blur(4px)"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>s(N=>!N),"aria-label":t(c?"exitFullscreen":"enterFullscreen"),title:t(c?"exitFullscreen":"enterFullscreen"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:14,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:c?"\u2921":"\u2922"}),(0,e.jsx)("button",{type:"button",onClick:i,"aria-label":t("close"),title:t("close"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:16,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:"\xD7"})]}),(0,e.jsx)("iframe",{ref:h,src:z,title:"memory-eternal",allow:"clipboard-write; clipboard-read",style:{width:"100%",flex:1,border:0,borderRadius:"inherit",background:"var(--dsw-alias-bg-base, #fff)"}})]})})]})}function Ua(){return(0,e.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,e.jsx)("defs",{children:(0,e.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,e.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,e.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,e.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,e.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,e.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}var St="0.11.0";function Ga({t,inModal:a,onClose:i}){let g=typeof window<"u"&&(!!window.__DSH_BOOT__||String(location.pathname||"").includes("/memory-eternal/api/ui/app")),[c,s]=(0,o.useState)(null),[h,k]=(0,o.useState)([]),[S,L]=(0,o.useState)(!0),[M,x]=(0,o.useState)(null),[m,z]=(0,o.useState)(""),[N,P]=(0,o.useState)("all"),[T,E]=(0,o.useState)(""),[K,se]=(0,o.useState)("all"),[V,Z]=(0,o.useState)("all"),[te,G]=(0,o.useState)(()=>{try{return new Set(JSON.parse(localStorage.getItem("me-pinned-v5")||"[]"))}catch{return new Set}}),[fe,ue]=(0,o.useState)(!1),_=(0,o.useCallback)(b=>{G(r=>{let v=new Set(r);v.has(b)?v.delete(b):v.add(b);try{localStorage.setItem("me-pinned-v5",JSON.stringify([...v]))}catch{}return v})},[]),[Q,je]=(0,o.useState)(""),[O,Be]=(0,o.useState)(()=>{try{let b=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),r={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(b&&r[b])return r[b]}catch{}return"cards"}),[f,F]=(0,o.useState)(()=>new Set([O])),W=(0,o.useCallback)(b=>{F(r=>r.has(b)?r:new Set(r).add(b)),Be(b)},[]),[B,Ce]=(0,o.useState)("recent"),[ze,pe]=(0,o.useState)(null),ve=(0,o.useRef)(null),y=(0,o.useRef)(null),[ae,X]=(0,o.useState)(!1),[Ne,ne]=(0,o.useState)(null),[xe,Ge]=(0,o.useState)(!1),[p,D]=(0,o.useState)(!1),[A,Y]=(0,o.useState)(!1),[Se,le]=(0,o.useState)("group"),[H,we]=(0,o.useState)(!1),be=(0,o.useCallback)(()=>{we(!1);try{localStorage.setItem("me-onboard-v2","1")}catch{}},[]),u=(0,o.useCallback)((b="group")=>{le(b),Y(!0)},[]);(0,o.useEffect)(()=>{try{if(localStorage.getItem("me-onboard-v2")||O!=="cards")return;let b=setTimeout(()=>we(!0),900);return()=>clearTimeout(b)}catch{}},[O]);let[d,J]=(0,o.useState)(!0),[he,Me]=(0,o.useState)(null),[Te,n]=(0,o.useState)(0),ot=(0,o.useCallback)(()=>n(b=>b+1),[]),ct=c?.quarantine?.pending??0,Ke=c?.quarantine?.deleted??0,Pe=(0,o.useRef)(null),[Xe,bt]=(0,o.useState)(0),[it,Re]=(0,o.useState)(!1),et=(0,o.useRef)(null),Fe=(0,o.useRef)(null),l=(0,o.useCallback)(async(b=N,r=T,v=V,C={})=>{let R=C.append===!0;try{R?Re(!0):(L(!0),z(""));let j=new URLSearchParams;b&&b!=="all"&&j.set("kind",b),r.trim()&&j.set("q",r.trim());let ee=C.tag!==void 0?C.tag:Q;ee&&j.set("tag",ee),j.set("status",v||"all"),j.set("limit",String(ja)),j.set("offset",String(C.offset||0)),B==="title"&&j.set("sort","title");let ge=await(await fetch(`${$}/cards?${j.toString()}`)).json();if(ge.ok){let Ae=ge.cards||[];k(De=>R?[...De,...Ae.filter(ye=>!De.some(ke=>ke.path===ye.path))]:Ae),bt(Number(ge.total)||Ae.length)}}catch(j){z(String(j&&j.message?j.message:j))}finally{R?Re(!1):L(!1)}},[N,T,V,B,Q]),I=(0,o.useCallback)(async()=>{try{let[b,r]=await Promise.all([fetch(`${$}/overview`).then(v=>v.json()),fetch(`${$}/capture-log`).then(v=>v.json()).catch(()=>null)]);b.ok&&s(b),x(r&&r.ok&&r.available&&r.health||null)}catch(b){z(String(b&&b.message?b.message:b))}finally{L(!1)}},[]),U=async b=>{try{let v=await(await fetch(`${$}/delete?path=${encodeURIComponent(b)}`)).json();v.ok?(pe({ok:!0,msg:t("deleted")}),await I(),ot()):pe({ok:!1,msg:v.error||t("deleteFail")})}catch{pe({ok:!1,msg:t("deleteFail")})}ve.current&&clearTimeout(ve.current),ve.current=setTimeout(()=>pe(null),2e3)};(0,o.useEffect)(()=>(I(),()=>{Pe.current&&clearTimeout(Pe.current)}),[I]);let oe=b=>{E(b),Pe.current&&clearTimeout(Pe.current),Pe.current=setTimeout(()=>l(N,b,V),280)},ie=b=>{P(b),l(b,T,V)},q=b=>{let r=String(b||"");je(r),ne(null),l(N,T,V,{tag:r})};(0,o.useEffect)(()=>{O==="cards"&&l(N,T,V)},[V,O,Te,B,Q]),(0,o.useEffect)(()=>{if(O!=="cards"||S||it||h.length>=Xe)return;let b=et.current,r=Fe.current;if(!b)return;let v=new IntersectionObserver(C=>{C[0]&&C[0].isIntersecting&&l(N,T,V,{append:!0,offset:h.length})},{root:r||null,rootMargin:"400px"});return v.observe(b),()=>v.disconnect()},[O,h,Xe,S,it,N,T,V,l]);let re=async b=>{try{let r=b.status;if(!r&&b.path)try{let ee=((await fetch(`${$}/cards?status=all&q=${encodeURIComponent(b.title||"")}&limit=50`).then(de=>de.json())).cards||[]).find(de=>de.path===b.path);ee&&(r=ee.status)}catch{}let v=r&&r!=="approved"?`&status=${encodeURIComponent(r)}`:"",C=await fetch(`${$}/card?path=${encodeURIComponent(b.path||b.id)}${v}`),R=await C.json();R.ok?ne({path:R.path,title:b.title,text:R.text}):z(R.error||`HTTP ${C.status}`)}catch(r){z(String(r&&r.message?r.message:r))}},He=async(b,r)=>{X(!0);try{let v=await fetch(`${$}/export`);if(!v.ok)throw new Error("HTTP "+v.status);let C=await v.json();if(!C.ok)throw new Error(C.error||"export failed");let R,j,ee;if(b==="json"){j="memory-vault.json",ee="application/json";let Ae=C.cards.map(De=>({path:De.path,title:De.title,kind:De.kind,status:De.status,store:De.store,text:De.text}));R=JSON.stringify({format:C.format||"memory-eternal-vault",formatVersion:C.formatVersion||1,exportedAt:C.exportedAt||new Date().toISOString(),count:Ae.length,cards:Ae},null,2)}else j="memory-vault.md",ee="text/markdown;charset=utf-8",R=C.cards.map(Ae=>Ae.text.trim()).filter(Boolean).join(`
|
|
704
|
+
`;function Ea(t){t.effect(()=>t.locale.register(kt,{zh:da,en:ca}),"memory-eternal: locale");let a=t.locale.bind(kt);t.effect(()=>t.slots.inject("settings.section",()=>t.slots.register({name:"settings.section",id:kt,order:25,label:()=>a("nav"),locale:kt,inject:()=>({})},()=>o.default.createElement(Wa,{t:a}))),"memory-eternal: settings section"),t.effect(()=>t.slots.inject("sidebar.footer.action",()=>t.slots.register({name:"sidebar.footer.action",id:`${kt}:footer`,order:100,label:()=>a("nav"),locale:kt,inject:()=>({})},i=>o.default.createElement($a,{t:a,locale:t.locale,wide:!(i&&i.wide===!1)}))),"memory-eternal: sidebar footer action")}function Oa(){let[t,a]=(0,o.useState)($+"/ui/app");return(0,o.useEffect)(()=>{let i=!0,g=()=>fetch("/memory-eternal/api/web-info").then(c=>c.json()).then(c=>{i&&c&&c.ok&&c.alive!==!1&&c.url&&a(c.url.endsWith("/")?c.url:c.url+"/")}).catch(()=>{});return fetch($+"/ui/app",{method:"HEAD"}).then(c=>{if(i&&!c.ok)return g()}).catch(()=>i?g():void 0),()=>{i=!1}},[]),t}function Wa({t}){return(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsx)(ua,{t,onReload:()=>{},version:0})]})}function $a({t,locale:a,wide:i}){let[g,c]=(0,o.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${i?"":" rail"}`,children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>c(!0),"aria-label":t("nav"),title:t("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(Ua,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:t("nav")})]}),g&&(0,e.jsx)(_a,{t,locale:a,onClose:()=>c(!1)})]})}function _a({t,locale:a,onClose:i,tab:g}){let[c,s]=(0,o.useState)(!1),h=(0,o.useRef)(null),k=Oa(),S=(0,o.useCallback)(N=>a&&typeof a.subscribe=="function"?a.subscribe(N):()=>{},[a]),L=(0,o.useCallback)(()=>a&&typeof a.getSnapshot=="function"?a.getSnapshot():null,[a]),M=(0,o.useSyncExternalStore)(S,L),x=M&&(M.active==="zh"||M.active==="en")?M.active:null,m=(0,o.useRef)(null);m.current===null&&x&&(m.current=x);let z=(0,o.useMemo)(()=>{let N=m.current?`lang=${m.current}`:"";if(g==="config")return`${$}/ui/config?tab=config${N?`&${N}`:""}`;let P=k.replace(/\/$/,""),T=P.endsWith("/ui/app")?"?":"/?";return g?`${P}${T}tab=${g}${N?`&${N}`:""}`:N?`${P}${T}${N}`:k},[k,g]);return(0,o.useEffect)(()=>{let N=h.current&&h.current.contentWindow;N&&x&&N.postMessage({source:"memory-eternal",type:"locale",lang:x},"*")},[x]),(0,o.useEffect)(()=>{let N=P=>{P.key==="Escape"&&i()};return window.addEventListener("keydown",N),()=>window.removeEventListener("keydown",N)},[i]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:i,children:[(0,e.jsx)("style",{children:Ue}),(0,e.jsx)("div",{className:"me-modal"+(c?" me-modal-full":""),onClick:N=>N.stopPropagation(),children:(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0,position:"relative"},children:[(0,e.jsxs)("div",{style:{position:"absolute",top:8,right:8,zIndex:10,display:"flex",gap:4,padding:"4px 6px",background:"var(--dsw-alias-bg-overlay, rgba(17,24,39,0.45))",borderRadius:8,backdropFilter:"blur(4px)"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>s(N=>!N),"aria-label":t(c?"exitFullscreen":"enterFullscreen"),title:t(c?"exitFullscreen":"enterFullscreen"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:14,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:c?"\u2921":"\u2922"}),(0,e.jsx)("button",{type:"button",onClick:i,"aria-label":t("close"),title:t("close"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:16,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:"\xD7"})]}),(0,e.jsx)("iframe",{ref:h,src:z,title:"memory-eternal",allow:"clipboard-write; clipboard-read",style:{width:"100%",flex:1,border:0,borderRadius:"inherit",background:"var(--dsw-alias-bg-base, #fff)"}})]})})]})}function Ua(){return(0,e.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,e.jsx)("defs",{children:(0,e.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,e.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,e.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,e.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,e.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,e.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}var St="0.13.0";function Ga({t,inModal:a,onClose:i}){let g=typeof window<"u"&&(!!window.__DSH_BOOT__||String(location.pathname||"").includes("/memory-eternal/api/ui/app")),[c,s]=(0,o.useState)(null),[h,k]=(0,o.useState)([]),[S,L]=(0,o.useState)(!0),[M,x]=(0,o.useState)(null),[m,z]=(0,o.useState)(""),[N,P]=(0,o.useState)("all"),[T,E]=(0,o.useState)(""),[K,se]=(0,o.useState)("all"),[V,Z]=(0,o.useState)("all"),[te,G]=(0,o.useState)(()=>{try{return new Set(JSON.parse(localStorage.getItem("me-pinned-v5")||"[]"))}catch{return new Set}}),[fe,ue]=(0,o.useState)(!1),_=(0,o.useCallback)(b=>{G(r=>{let v=new Set(r);v.has(b)?v.delete(b):v.add(b);try{localStorage.setItem("me-pinned-v5",JSON.stringify([...v]))}catch{}return v})},[]),[Q,je]=(0,o.useState)(""),[O,Be]=(0,o.useState)(()=>{try{let b=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),r={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(b&&r[b])return r[b]}catch{}return"cards"}),[f,F]=(0,o.useState)(()=>new Set([O])),W=(0,o.useCallback)(b=>{F(r=>r.has(b)?r:new Set(r).add(b)),Be(b)},[]),[B,Ce]=(0,o.useState)("recent"),[ze,pe]=(0,o.useState)(null),ve=(0,o.useRef)(null),y=(0,o.useRef)(null),[ae,X]=(0,o.useState)(!1),[Ne,ne]=(0,o.useState)(null),[xe,Ge]=(0,o.useState)(!1),[p,D]=(0,o.useState)(!1),[A,Y]=(0,o.useState)(!1),[Se,le]=(0,o.useState)("group"),[H,we]=(0,o.useState)(!1),be=(0,o.useCallback)(()=>{we(!1);try{localStorage.setItem("me-onboard-v2","1")}catch{}},[]),u=(0,o.useCallback)((b="group")=>{le(b),Y(!0)},[]);(0,o.useEffect)(()=>{try{if(localStorage.getItem("me-onboard-v2")||O!=="cards")return;let b=setTimeout(()=>we(!0),900);return()=>clearTimeout(b)}catch{}},[O]);let[d,J]=(0,o.useState)(!0),[he,Me]=(0,o.useState)(null),[Te,n]=(0,o.useState)(0),ot=(0,o.useCallback)(()=>n(b=>b+1),[]),ct=c?.quarantine?.pending??0,Ke=c?.quarantine?.deleted??0,Pe=(0,o.useRef)(null),[Xe,bt]=(0,o.useState)(0),[it,Re]=(0,o.useState)(!1),et=(0,o.useRef)(null),Fe=(0,o.useRef)(null),l=(0,o.useCallback)(async(b=N,r=T,v=V,C={})=>{let R=C.append===!0;try{R?Re(!0):(L(!0),z(""));let j=new URLSearchParams;b&&b!=="all"&&j.set("kind",b),r.trim()&&j.set("q",r.trim());let ee=C.tag!==void 0?C.tag:Q;ee&&j.set("tag",ee),j.set("status",v||"all"),j.set("limit",String(ja)),j.set("offset",String(C.offset||0)),B==="title"&&j.set("sort","title");let ge=await(await fetch(`${$}/cards?${j.toString()}`)).json();if(ge.ok){let Ae=ge.cards||[];k(De=>R?[...De,...Ae.filter(ye=>!De.some(ke=>ke.path===ye.path))]:Ae),bt(Number(ge.total)||Ae.length)}}catch(j){z(String(j&&j.message?j.message:j))}finally{R?Re(!1):L(!1)}},[N,T,V,B,Q]),I=(0,o.useCallback)(async()=>{try{let[b,r]=await Promise.all([fetch(`${$}/overview`).then(v=>v.json()),fetch(`${$}/capture-log`).then(v=>v.json()).catch(()=>null)]);b.ok&&s(b),x(r&&r.ok&&r.available&&r.health||null)}catch(b){z(String(b&&b.message?b.message:b))}finally{L(!1)}},[]),U=async b=>{try{let v=await(await fetch(`${$}/delete?path=${encodeURIComponent(b)}`)).json();v.ok?(pe({ok:!0,msg:t("deleted")}),await I(),ot()):pe({ok:!1,msg:v.error||t("deleteFail")})}catch{pe({ok:!1,msg:t("deleteFail")})}ve.current&&clearTimeout(ve.current),ve.current=setTimeout(()=>pe(null),2e3)};(0,o.useEffect)(()=>(I(),()=>{Pe.current&&clearTimeout(Pe.current)}),[I]);let oe=b=>{E(b),Pe.current&&clearTimeout(Pe.current),Pe.current=setTimeout(()=>l(N,b,V),280)},ie=b=>{P(b),l(b,T,V)},q=b=>{let r=String(b||"");je(r),ne(null),l(N,T,V,{tag:r})};(0,o.useEffect)(()=>{O==="cards"&&l(N,T,V)},[V,O,Te,B,Q]),(0,o.useEffect)(()=>{if(O!=="cards"||S||it||h.length>=Xe)return;let b=et.current,r=Fe.current;if(!b)return;let v=new IntersectionObserver(C=>{C[0]&&C[0].isIntersecting&&l(N,T,V,{append:!0,offset:h.length})},{root:r||null,rootMargin:"400px"});return v.observe(b),()=>v.disconnect()},[O,h,Xe,S,it,N,T,V,l]);let re=async b=>{try{let r=b.status;if(!r&&b.path)try{let ee=((await fetch(`${$}/cards?status=all&q=${encodeURIComponent(b.title||"")}&limit=50`).then(de=>de.json())).cards||[]).find(de=>de.path===b.path);ee&&(r=ee.status)}catch{}let v=r&&r!=="approved"?`&status=${encodeURIComponent(r)}`:"",C=await fetch(`${$}/card?path=${encodeURIComponent(b.path||b.id)}${v}`),R=await C.json();R.ok?ne({path:R.path,title:b.title,text:R.text}):z(R.error||`HTTP ${C.status}`)}catch(r){z(String(r&&r.message?r.message:r))}},He=async(b,r)=>{X(!0);try{let v=await fetch(`${$}/export`);if(!v.ok)throw new Error("HTTP "+v.status);let C=await v.json();if(!C.ok)throw new Error(C.error||"export failed");let R,j,ee;if(b==="json"){j="memory-vault.json",ee="application/json";let Ae=C.cards.map(De=>({path:De.path,title:De.title,kind:De.kind,status:De.status,store:De.store,text:De.text}));R=JSON.stringify({format:C.format||"memory-eternal-vault",formatVersion:C.formatVersion||1,exportedAt:C.exportedAt||new Date().toISOString(),count:Ae.length,cards:Ae},null,2)}else j="memory-vault.md",ee="text/markdown;charset=utf-8",R=C.cards.map(Ae=>Ae.text.trim()).filter(Boolean).join(`
|
|
705
705
|
|
|
706
706
|
---
|
|
707
707
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Orca plugin — Memory Atlas 启动器
|
|
2
|
+
|
|
3
|
+
在 Orca 右侧栏添加一个「记忆」面板,一键在终端执行 `dsh-memory open`,打开本地 Memory Atlas Web UI。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
### 推荐:Git URL
|
|
8
|
+
|
|
9
|
+
Orca → Settings → Plugins → Install plugin → **Git URL**:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
https://github.com/EternalNight996/memory-eternal-orca#v0.12.1
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Orca 会固定到 `v0.12.0` tag。
|
|
16
|
+
|
|
17
|
+
### 本地目录
|
|
18
|
+
|
|
19
|
+
1. 确认本机已安装并运行 `memory-eternal`(默认 Web 服务 `http://127.0.0.1:7999/`)。
|
|
20
|
+
2. 确认终端能找到 CLI:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
dsh-memory --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. 在 Orca 中安装本目录:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
Orca → Plugins → Install plugin → 选择 orca-plugin/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
4. 打开右侧栏的「记忆」图标,选择终端,点「打开 Memory Atlas」。
|
|
33
|
+
|
|
34
|
+
## 它做什么
|
|
35
|
+
|
|
36
|
+
- 调 Orca 官方 `workspace.readContext` 读取当前工作区终端
|
|
37
|
+
- 调 Orca 官方 `terminal.sendText` 执行 `dsh-memory open`
|
|
38
|
+
- 不 iframe、不 fetch、不复制第二套 UI
|
|
39
|
+
|
|
40
|
+
## 为什么不是完整内嵌 UI
|
|
41
|
+
|
|
42
|
+
Orca panel 使用严格沙箱 CSP:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
default-src 'none'
|
|
46
|
+
connect-src 'none'
|
|
47
|
+
img-src data:
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
因此 panel 内不能 iframe `127.0.0.1:7999`、不能 fetch API、不能 `window.open`。
|
|
51
|
+
完整 Memory Atlas 仍由 Web UI 提供;Orca 只做官方能力内的启动器。
|
|
52
|
+
|
|
53
|
+
## 权限
|
|
54
|
+
|
|
55
|
+
- `workspace:read`:读取当前工作区终端列表
|
|
56
|
+
- `terminal:send`:向指定终端发送命令
|
|
57
|
+
|
|
58
|
+
没有任何内存卡写入、审核批准或 agent 权限。
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifestVersion": 1,
|
|
3
|
+
"id": "memory-eternal",
|
|
4
|
+
"publisher": "eternalnight996",
|
|
5
|
+
"name": "memory-eternal",
|
|
6
|
+
"version": "0.13.0",
|
|
7
|
+
"description": "Memory Atlas for Orca: open the local memory vault, pending review and knowledge graph from a sandboxed Orca panel.",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "EternalNight996",
|
|
10
|
+
"url": "https://github.com/EternalNight996"
|
|
11
|
+
},
|
|
12
|
+
"repository": "https://github.com/EternalNight996/memory-eternal",
|
|
13
|
+
"icon": "assets/icon.png",
|
|
14
|
+
"engines": {
|
|
15
|
+
"orca": ">=1.4.0"
|
|
16
|
+
},
|
|
17
|
+
"pluginApi": 1,
|
|
18
|
+
"contributes": {
|
|
19
|
+
"panels": [
|
|
20
|
+
{
|
|
21
|
+
"id": "memory",
|
|
22
|
+
"title": "记忆",
|
|
23
|
+
"icon": "brain",
|
|
24
|
+
"entry": "panel.html"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"capabilities": [
|
|
29
|
+
{
|
|
30
|
+
"kind": "workspace:read"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"kind": "terminal:send"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|