dsh-cerebrum 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/publish.md +1 -1
- package/lib/api.js +47 -19
- package/lib/config.js +2 -0
- package/lib/index.js +1 -1
- package/package.json +4 -2
package/docs/publish.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **发布状态(2026-10-28)**
|
|
4
4
|
> - GitHub 公开仓库:https://github.com/liu-wen-wu/dsh-cerebrum(main + tag v0.1.1,SSH 推送)
|
|
5
|
-
> - npm:`dsh-cerebrum@0.1.
|
|
5
|
+
> - npm:`dsh-cerebrum@0.1.2` 已上线(latest;0.1.1 上一版,含内置宠物桥接)
|
|
6
6
|
> - 市场收录申请:https://github.com/polang233/dsh-plugin-index/issues/121
|
|
7
7
|
> - 本地安装:`dsh-cerebrum@link:D:/workspace/dsh-cerebrum`(desktop profile,已热加载验证)
|
|
8
8
|
|
package/lib/api.js
CHANGED
|
@@ -18,40 +18,64 @@
|
|
|
18
18
|
import http from 'node:http'
|
|
19
19
|
import { INTERACTION_KINDS } from './core/petbrain.js'
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const CORS_METHODS = 'GET, OPTIONS'
|
|
22
|
+
const LOOPBACK = /^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/
|
|
23
|
+
const LOOPBACK_ORIGIN = /^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Access control for the loopback API (review feedback on dshplugin-index#121):
|
|
27
|
+
* - Host must be a loopback host (blocks DNS rebinding).
|
|
28
|
+
* - If the request carries an Origin (cross-origin browser fetch), it must also
|
|
29
|
+
* be a loopback origin — an arbitrary website can no longer read the brain.
|
|
30
|
+
* - Optional shared token (`config.api.token`) for users who want a second lock.
|
|
31
|
+
*/
|
|
32
|
+
const checkAccess = (req, token) => {
|
|
33
|
+
const host = String(req.headers.host ?? '')
|
|
34
|
+
if (!LOOPBACK.test(host)) return { ok: false, status: 403, reason: 'host-not-allowed' }
|
|
35
|
+
const origin = req.headers.origin
|
|
36
|
+
if (origin && !LOOPBACK_ORIGIN.test(String(origin))) return { ok: false, status: 403, reason: 'origin-not-allowed' }
|
|
37
|
+
if (token && req.headers['x-cerebrum-token'] !== token) return { ok: false, status: 401, reason: 'token-required' }
|
|
38
|
+
return { ok: true }
|
|
26
39
|
}
|
|
27
40
|
|
|
28
|
-
const send = (res, status, payload) => {
|
|
29
|
-
|
|
41
|
+
const send = (res, status, payload, origin) => {
|
|
42
|
+
if (origin) res.setHeader('Access-Control-Allow-Origin', String(origin))
|
|
43
|
+
res.setHeader('Access-Control-Allow-Methods', CORS_METHODS)
|
|
44
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, x-cerebrum-token')
|
|
45
|
+
res.setHeader('Vary', 'Origin')
|
|
46
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
30
47
|
res.statusCode = status
|
|
31
48
|
res.end(JSON.stringify(payload))
|
|
32
49
|
}
|
|
33
50
|
|
|
34
|
-
export const startBrainApi = (service, { port = 18734, ui = { enabled: true }, logger = console } = {}) => {
|
|
51
|
+
export const startBrainApi = (service, { port = 18734, ui = { enabled: true }, token = '', logger = console } = {}) => {
|
|
52
|
+
// Interaction rate limit: /interact is the only mutation; cap it to stop flooding.
|
|
53
|
+
let interactWindow = { at: 0, count: 0 }
|
|
35
54
|
const server = http.createServer((req, res) => {
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
const origin = req.headers.origin
|
|
56
|
+
const allowedOrigin = origin && LOOPBACK_ORIGIN.test(String(origin)) ? String(origin) : null
|
|
57
|
+
const access = checkAccess(req, token)
|
|
58
|
+
if (!access.ok) return send(res, access.status, { error: access.reason }, allowedOrigin)
|
|
59
|
+
if (req.method === 'OPTIONS') return send(res, 204, {}, allowedOrigin)
|
|
60
|
+
if (req.method !== 'GET') return send(res, 405, { error: 'method not allowed' }, allowedOrigin)
|
|
38
61
|
const url = new URL(req.url, `http://127.0.0.1:${port}`)
|
|
62
|
+
const reply = (status, payload) => send(res, status, payload, allowedOrigin)
|
|
39
63
|
try {
|
|
40
64
|
switch (url.pathname) {
|
|
41
65
|
case '/ping':
|
|
42
|
-
return
|
|
66
|
+
return reply(200, { ok: true, name: 'dsh-cerebrum', uptimeMs: Date.now() - service.startedAt })
|
|
43
67
|
case '/ui':
|
|
44
68
|
// The client respects this: ui.enabled=false removes the chip entirely.
|
|
45
|
-
return
|
|
69
|
+
return reply(200, { enabled: ui.enabled !== false, drivePet: ui.drivePet !== false })
|
|
46
70
|
case '/state': {
|
|
47
71
|
const sessionId = url.searchParams.get('session') || undefined
|
|
48
|
-
return
|
|
72
|
+
return reply(200, service.state(sessionId))
|
|
49
73
|
}
|
|
50
74
|
case '/recall': {
|
|
51
75
|
const query = url.searchParams.get('q') ?? ''
|
|
52
76
|
const sessionId = url.searchParams.get('session') || undefined
|
|
53
77
|
const hits = service.recall(query, { k: 3, sessionId })
|
|
54
|
-
return
|
|
78
|
+
return reply(200, {
|
|
55
79
|
query,
|
|
56
80
|
memories: hits.map((hit) => ({
|
|
57
81
|
summary: hit.episode.summary,
|
|
@@ -63,21 +87,25 @@ export const startBrainApi = (service, { port = 18734, ui = { enabled: true }, l
|
|
|
63
87
|
}
|
|
64
88
|
case '/render-hints': {
|
|
65
89
|
const sessionId = url.searchParams.get('session') || undefined
|
|
66
|
-
return
|
|
90
|
+
return reply(200, service.renderHintsFor(sessionId))
|
|
67
91
|
}
|
|
68
92
|
case '/interact': {
|
|
69
93
|
// The only mutation the API allows: affect-only pet interactions.
|
|
94
|
+
const now = Date.now()
|
|
95
|
+
if (now - interactWindow.at > 60_000) interactWindow = { at: now, count: 0 }
|
|
96
|
+
interactWindow.count++
|
|
97
|
+
if (interactWindow.count > 30) return reply(429, { error: 'too-many-interactions' })
|
|
70
98
|
const kind = url.searchParams.get('kind') ?? ''
|
|
71
99
|
const sessionId = url.searchParams.get('session') || undefined
|
|
72
100
|
const hints = service.interact(kind, sessionId)
|
|
73
|
-
if (!hints) return
|
|
74
|
-
return
|
|
101
|
+
if (!hints) return reply(400, { error: `unknown interaction kind, use one of ${INTERACTION_KINDS.join(', ')}` })
|
|
102
|
+
return reply(200, hints)
|
|
75
103
|
}
|
|
76
104
|
default:
|
|
77
|
-
return
|
|
105
|
+
return reply(404, { error: 'unknown endpoint' })
|
|
78
106
|
}
|
|
79
107
|
} catch (error) {
|
|
80
|
-
return
|
|
108
|
+
return reply(500, { error: error?.message ?? 'internal error' })
|
|
81
109
|
}
|
|
82
110
|
})
|
|
83
111
|
|
package/lib/config.js
CHANGED
|
@@ -45,6 +45,8 @@ export const DEFAULT_CONFIG = {
|
|
|
45
45
|
api: {
|
|
46
46
|
enabled: true,
|
|
47
47
|
port: 18734,
|
|
48
|
+
/** Optional shared secret; when set, every request must send x-cerebrum-token. */
|
|
49
|
+
token: '',
|
|
48
50
|
},
|
|
49
51
|
/** Client UI (the brain chip/dashboard). Users who dislike it can switch it off. */
|
|
50
52
|
ui: {
|
package/lib/index.js
CHANGED
|
@@ -57,7 +57,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
57
57
|
|
|
58
58
|
// Read-only loopback API powering the optional client UI (dock + dashboard).
|
|
59
59
|
if (config.api.enabled) {
|
|
60
|
-
const api = startBrainApi(service, { port: config.api.port, ui: config.ui })
|
|
60
|
+
const api = startBrainApi(service, { port: config.api.port, ui: config.ui, token: config.api.token })
|
|
61
61
|
ctx.effect(() => () => api.close(), 'cerebrum:brain-api')
|
|
62
62
|
}
|
|
63
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cerebrum",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Brain-inspired cognitive architecture for DeepSeek Harness: hippocampal episodic memory, cortical semantic memory, spreading-activation recall, appraisal-based emotion, neuromodulator state, Big Five personality and dual-process judgment.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -90,6 +90,8 @@
|
|
|
90
90
|
"forecast": "node examples/forecast.mjs",
|
|
91
91
|
"pet": "node examples/pet-bridge.mjs",
|
|
92
92
|
"pet-hook": "node examples/pet-hook.mjs",
|
|
93
|
-
"privacy": "node scripts/privacy-check.mjs"
|
|
93
|
+
"privacy": "node scripts/privacy-check.mjs",
|
|
94
|
+
"release": "node scripts/release.mjs",
|
|
95
|
+
"market": "node scripts/check-market.mjs"
|
|
94
96
|
}
|
|
95
97
|
}
|