dsh-redteam-memory 0.4.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.
@@ -0,0 +1,15 @@
1
+ // 常驻(静态)客户端半边 —— 浏览器 bundle 形态(由 tools/build-lib.mjs 生成)。
2
+ //
3
+ // client-modules 是 CJS 懒执行模型:bundle 只【注册】工厂,副作用留在闭包内,
4
+ // 首次 require 时物化。因此这里用 window.__ModuleLoader__.load({id, factory}) 注册。
5
+ //
6
+ // 包装刻意保持极薄(只做作用域与导出),全部改造集中在主体自己的 applyClient 里,
7
+ // 见 lib/parts/client.shim.js。
8
+ window.__ModuleLoader__.load({
9
+ id: '__PKG_NAME__',
10
+ factory: (require) => {
11
+ let React = require('react');
12
+ var module = { exports: {} };
13
+ var exports = module.exports;
14
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
15
+
@@ -0,0 +1,45 @@
1
+ function applyClient(ctx) {
2
+ // ── 静态形态垫片(动态半边的闭包符号在静态包里不存在)──
3
+ //
4
+ // 1) host.call:转到宿主 HTTP 路由(见 lib/host.js 的 __ROUTE_BASE__/rpc)。
5
+ // 选 HTTP 而非 ctx.remote:Remote 需 typert 代码生成(zod schema + 生成绑定),
6
+ // 而本插件有 20 个无类型 JSON 句柄,为此引入整套生成链不划算;
7
+ // 且 fetch 只被【动态】半边屏蔽,静态模块可直接用。
8
+ // 2) styles.insert:动态 runner 把它作为闭包参数注入,静态 bundle 里没有,
9
+ // 故自行插入 <style> 元素(浏览器全局可用),并登记到 fiber 便于卸载清理。
10
+ // 路径必须由生成器按包名填充(__ROUTE_BASE__)。这里曾经写死成
11
+ // `/dsh-redteam-asset-graph/rpc` —— 那是从资产图谱早期版本复制骨架时带过来的缺陷:
12
+ // 本插件的面板会去打资产图谱的路由,请求全 404,两者同时安装还会读到对方的数据
13
+ // (资产图谱 README 记的那次事故是同一个根因)。测试里钉住了实际请求的 url。
14
+ const RPC_PATH = '__ROUTE_BASE__/rpc'
15
+ const host = {
16
+ call(method, args) {
17
+ return fetch(RPC_PATH, {
18
+ method: 'POST',
19
+ headers: { 'content-type': 'application/json' },
20
+ body: JSON.stringify({ method: method, args: args === undefined ? null : args }),
21
+ }).then(function (res) {
22
+ return res.json().catch(function () { return null }).then(function (payload) {
23
+ if (!res.ok || !payload || payload.ok !== true) {
24
+ const detail = (payload && payload.error) || ('HTTP ' + res.status)
25
+ throw new Error('__PLUGIN_NAME__ rpc ' + method + ' 失败:' + detail)
26
+ }
27
+ return payload.result
28
+ })
29
+ })
30
+ },
31
+ }
32
+
33
+ const STYLE_ID = '__PLUGIN_NAME__-styles'
34
+ const styles = {
35
+ insert(css) {
36
+ if (typeof document === 'undefined') return function () {}
37
+ let el = document.getElementById(STYLE_ID)
38
+ if (!el) { el = document.createElement('style'); el.id = STYLE_ID; document.head.appendChild(el) }
39
+ el.textContent += String(css) + '\n'
40
+ const dispose = function () { if (el && el.parentNode) el.parentNode.removeChild(el) }
41
+ try { ctx.effect(function () { return dispose }, '__PLUGIN_NAME__: styles') } catch (e) { return dispose }
42
+ return dispose
43
+ },
44
+ }
45
+
@@ -0,0 +1,5 @@
1
+ exports.inject = ['slots', 'timer']
2
+ exports.apply = function (ctx) { return applyClient(ctx) }
3
+ return module.exports
4
+ }
5
+ });
@@ -0,0 +1,80 @@
1
+ // 常驻(静态)Host 半边。
2
+ //
3
+ // 主体逻辑与 src/host.js 完全一致(未改一行),差异只在于动态半边的三个符号
4
+ // (harness.defineTool / harness.registerTool / harness.handle)在静态包里不存在,
5
+ // 因此这里提供一个薄垫片 harness:
6
+ //
7
+ // defineTool / registerTool -> @deepseek-ai/dsh-tools 的 defineTool + ctx.tools.register
8
+ // handle -> 收进 handlers 表,供宿主 HTTP 路由转发(见 rpcRoute)
9
+ //
10
+ // 这样做的理由:机械改写上千行主体逻辑的风险远高于加一层适配,
11
+ // 而且适配层把「动态 ↔ 静态」的差异集中在一个地方,便于日后核对。
12
+ //
13
+ // ── defineTool 的入参形态差异(实测踩坑,务必保留转换)────────────────────────
14
+ // 动态半边的 harness.defineTool 由 dsh-cordis-host-runner 的 guard 提供,它按
15
+ // 「JSON Schema」接受 parameters({ type:'object', properties, required })。
16
+ // 静态包的 defineTool 来自 @deepseek-ai/dsh-tools,它要的是 ParameterSchemaSpec:
17
+ // 一个**扁平的属性表**,必填写成每个属性上的 required: true,且根对象没有 type 字段。
18
+ // 直接把 JSON Schema 喂给静态 defineTool 会抛
19
+ // JsonSchemaError: unsupported JSON schema: parameters.type must be a value schema object
20
+ // —— 工具会在 apply 时全部注册失败。
21
+ // 所以这里做一次转换,src/ 保持动态形态不变。
22
+ import { defineTool } from '@deepseek-ai/dsh-tools'
23
+
24
+ // JSON Schema 属性节点 -> ParameterSchemaSpec 属性节点。只带上工具真的用到的键,
25
+ // 不搬运 pattern / format 之类静态编译器不接受的约束。
26
+ function toPropertySpec(node) {
27
+ if (!node || typeof node !== 'object') return { type: 'string' }
28
+ const annotations = {}
29
+ if (typeof node.description === 'string') annotations.description = node.description
30
+ if (node.default !== undefined) annotations.default = node.default
31
+ if (Array.isArray(node.examples)) annotations.examples = node.examples
32
+ const t = node.type
33
+ if (t === 'array') {
34
+ const spec = { type: 'array', items: toPropertySpec(node.items), ...annotations }
35
+ if (typeof node.minItems === 'number') spec.minItems = node.minItems
36
+ if (typeof node.maxItems === 'number') spec.maxItems = node.maxItems
37
+ return spec
38
+ }
39
+ if (t === 'object') {
40
+ return { type: 'object', additionalProperties: node.additionalProperties === false ? false : true, properties: toPropertyMap(node.properties), ...annotations }
41
+ }
42
+ const spec = { type: t || 'string', ...annotations }
43
+ if (Array.isArray(node.enum)) spec.enum = node.enum.slice()
44
+ if (node.const !== undefined) spec.const = node.const
45
+ return spec
46
+ }
47
+
48
+ function toPropertyMap(props) {
49
+ const out = {}
50
+ if (props && typeof props === 'object') for (const key of Object.keys(props)) out[key] = toPropertySpec(props[key])
51
+ return out
52
+ }
53
+
54
+ // 接受动态形态的 parameters;已是扁平属性表时原样返回(幂等,便于两种写法共存)。
55
+ function toParameterSpec(parameters, required) {
56
+ if (!parameters || typeof parameters !== 'object') return { type: 'object', properties: {}, additionalProperties: false }
57
+ let props = parameters.properties
58
+ if (props === undefined && parameters.type !== 'object') props = parameters
59
+ const map = toPropertyMap(props)
60
+ const req = Array.isArray(required) ? required : (Array.isArray(parameters.required) ? parameters.required : [])
61
+ for (const name of req) if (map[name] && typeof map[name] === 'object') map[name].required = true
62
+ return map
63
+ }
64
+
65
+ // 工具定义里除了 parameters 之外都与静态 defineTool 兼容,只替换这一个字段。
66
+ function toStaticToolDefinition(definition) {
67
+ const rest = {}
68
+ for (const key of Object.keys(definition)) if (key !== 'parameters') rest[key] = definition[key]
69
+ rest.parameters = toParameterSpec(definition.parameters, definition.required)
70
+ return rest
71
+ }
72
+
73
+ function applyHost(ctx) {
74
+ const handlers = Object.create(null)
75
+ const harness = {
76
+ defineTool(definition) { return defineTool(toStaticToolDefinition(definition)) },
77
+ registerTool(c, tool) { return c.tools.register(tool) },
78
+ handle(method, handler) { handlers[method] = handler; return () => { delete handlers[method] } },
79
+ }
80
+
@@ -0,0 +1,37 @@
1
+
2
+ // 插件自己的 JSON-RPC 端点。
3
+ // 客户端 bundle 用 fetch 调它(静态模块可用 fetch;动态半边才被屏蔽)。
4
+ // 全部挂在 __ROUTE_BASE__ 命名空间下,避免与其它插件的路由相撞。
5
+ const RPC_PATH = '__ROUTE_BASE__/rpc'
6
+
7
+ // 把 20 个动态 RPC 句柄经宿主 HTTP 路由暴露给客户端半边。
8
+ // 客户端是普通模块,可以直接 fetch(动态半边才有 fetch 屏蔽)。
9
+ ctx.effect(() => ctx.webServer.register({
10
+ kind: 'exact',
11
+ path: RPC_PATH,
12
+ handler: async (req, res) => {
13
+ if (req.method !== 'POST') { res.statusCode = 405; res.end(); return }
14
+ let body = ''
15
+ try { for await (const chunk of req) body += chunk } catch (e) {}
16
+ let payload = null
17
+ try { payload = JSON.parse(body || '{}') } catch (e) {}
18
+ const method = payload && typeof payload.method === 'string' ? payload.method : ''
19
+ const fn = handlers[method]
20
+ res.setHeader('content-type', 'application/json; charset=utf-8')
21
+ if (!fn) { res.statusCode = 404; res.end(JSON.stringify({ error: 'unknown method: ' + method })); return }
22
+ try {
23
+ const result = await fn(payload.args === undefined ? null : payload.args)
24
+ res.statusCode = 200
25
+ res.end(JSON.stringify({ ok: true, result: result === undefined ? null : result }))
26
+ } catch (e) {
27
+ res.statusCode = 500
28
+ res.end(JSON.stringify({ ok: false, error: String((e && e.message) || e) }))
29
+ }
30
+ },
31
+ }), 'rtasset: host rpc route')
32
+ }
33
+
34
+ export const name = '__PLUGIN_NAME__'
35
+ // 三个工具注册进宿主 tools 注册表;这里声明本半边硬依赖的服务。
36
+ export const inject = ['fs', 'shell', 'timer', 'webServer', 'tools']
37
+ export { applyHost as apply }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "dsh-redteam-memory",
3
+ "version": "0.4.0",
4
+ "description": "DSH 红队记忆:本地知识库、Milvus 向量检索与 MinIO,提供统一红队设置页",
5
+ "license": "MIT",
6
+ "author": "fasthei",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Fasthei/DSHairedPlugin.git",
10
+ "directory": "packages/memory"
11
+ },
12
+ "keywords": [
13
+ "dsh",
14
+ "cordis",
15
+ "plugin",
16
+ "redteam",
17
+ "memory",
18
+ "context"
19
+ ],
20
+ "type": "module",
21
+ "main": "lib/host.js",
22
+ "exports": {
23
+ ".": {
24
+ "default": "./lib/host.js"
25
+ },
26
+ "./client": {
27
+ "default": "./lib/client.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "files": [
32
+ "lib",
33
+ "src",
34
+ "tools",
35
+ "cordis.patch.yml",
36
+ "LICENSE",
37
+ "README.md"
38
+ ],
39
+ "dsh": {
40
+ "bundle": {
41
+ "patch": "./cordis.patch.yml"
42
+ },
43
+ "client": {
44
+ "platform": "web"
45
+ },
46
+ "dynamic": {
47
+ "hostHalf": "src/host.js",
48
+ "clientHalf": "src/client.js"
49
+ }
50
+ },
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "registry": "https://registry.npmjs.org/"
54
+ },
55
+ "peerDependencies": {
56
+ "@deepseek-ai/dsh-tools": "*"
57
+ },
58
+ "scripts": {
59
+ "build:lib": "node tools/build-lib.mjs",
60
+ "check:lib": "node tools/build-lib.mjs --check",
61
+ "prepack": "node tools/build-lib.mjs --check",
62
+ "publish:gh": "node tools/prepare-gh-packages.mjs",
63
+ "test": "node test/memory-flow.mjs && node test/render-smoke.mjs",
64
+ "test:flow": "node test/memory-flow.mjs",
65
+ "test:render": "node test/render-smoke.mjs"
66
+ },
67
+ "engines": {
68
+ "node": ">=20"
69
+ }
70
+ }