dsh-instruction-memory 1.5.5 → 1.5.6
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/CHANGELOG.md +27 -0
- package/README.md +22 -10
- package/lib/client.js +43 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@
|
|
|
7
7
|
> 1.0.13 起由 CI 经 OIDC Trusted Publishing 发布(首个带 provenance 的版本)。
|
|
8
8
|
> 这些历史 tag **不应回填**——npm 上的版本号已被占用。
|
|
9
9
|
|
|
10
|
+
## [1.5.6] — 2026-11-13
|
|
11
|
+
|
|
12
|
+
### 新增
|
|
13
|
+
|
|
14
|
+
- **导出可以真正落到文件(仅在 DSH Go 里生效)**:`doExport()` 现在先探测 `window.DshGoIsland`
|
|
15
|
+
——那是 DSH Go 的 island 桥。存在时把 JSON 按 6,000 字符一块(不切断代理对)`postMessage` 给 App,
|
|
16
|
+
由 App 写文件并唤起系统分享面板,面板提示「已交给 DSH Go 保存」。不存在时行为不变:普通 WebView
|
|
17
|
+
回退到面板内 JSON,浏览器/Electron 仍是 `<a download>`。
|
|
18
|
+
壳侧需要配套补丁(island 协议新增 `t:'save'` 分块消息),已在仓库外单独产出,见
|
|
19
|
+
`D:\Users\34332\AI\dsh-go-patch\README-PATCH.md`;本仓库不含任何上游代码。
|
|
20
|
+
|
|
21
|
+
### 文档更正
|
|
22
|
+
|
|
23
|
+
只改文档的部分,**更正 1.5.5 发布说明里两处对 DSH Go 壳的描述**——都是读源码(`ccch1mneyyy/dsh-go`,2026-11-12)后确认写错的:
|
|
24
|
+
|
|
25
|
+
- **引擎环境不是 `/usr/bin/env -i`**:壳用 `ProcessBuilder`,`environment().clear()` 后逐项填
|
|
26
|
+
`PATH/HOME/TMPDIR/ANDROID_ROOT/ANDROID_DATA/PROOT_TMP_DIR`,再合并运行时清单的 `launchEnv`。
|
|
27
|
+
`HOME` 是 App 的 `filesDir`,不是 `/root`。结论不变(宿主变量不继承、备份开关仍不可达),
|
|
28
|
+
但机制与取值之前写错了。
|
|
29
|
+
- **文件选择器壳里已经实现**:`island_handle.dart:197` 的 `_files.attach()` →
|
|
30
|
+
`web_file_chooser.dart:26` 的 `setOnShowFileSelector` → 原生 `dsh/web-files` 通道
|
|
31
|
+
(content:// 校验、按文档代际取消)。所以 1.5.5 里"导入依赖文件选择器、壳没实现"的说法不成立;
|
|
32
|
+
面板那行提示保留为兜底(非 http(s) 页面、`canChoose()` 为假、旧版 App)。
|
|
33
|
+
|
|
34
|
+
仍未实现、且确实在壳侧的只有三件:**导出用的保存/分享桥**(island 消息协议上限 16 KB,需分块)、
|
|
35
|
+
`onJsPrompt`(webview_flutter 不暴露,插件已绕开,可不做)、**备份开关的环境变量**(壳能加,现未加)。
|
|
36
|
+
|
|
10
37
|
## [1.5.5] — 2026-11-12
|
|
11
38
|
|
|
12
39
|
DSH Go(Android / WebView)适配。**没有真机验证**(本机 `adb devices` 为空),所以这一版只做
|
package/README.md
CHANGED
|
@@ -956,9 +956,14 @@ FAIL LIVE GUARD: the installed host 0.1.8-rc.1 is admitted by both modes -> ..
|
|
|
956
956
|
|
|
957
957
|
DSH Go(Android 客户端)内置的引擎就是**真实 DSH**,锁定 `@deepseek-ai/dsh` **`0.2.1-rc.4`**
|
|
958
958
|
(`runtime/locks/dsh-0.2.1-rc.4/package.json`),在 PRoot rootfs 内以
|
|
959
|
-
`dsh web --patch /etc/dsh-app/dsh-web.patch.yml --host 127.0.0.1 --port N --no-open`
|
|
960
|
-
|
|
961
|
-
|
|
959
|
+
`dsh web --patch /etc/dsh-app/dsh-web.patch.yml --host 127.0.0.1 --port N --no-open` 运行。
|
|
960
|
+
|
|
961
|
+
引擎进程的环境**不是** `/usr/bin/env -i`(早先这里写错了,2026-11-12 读源码更正):壳用
|
|
962
|
+
`ProcessBuilder(argv)`,先 `environment().clear()`,再逐个填 `PATH` / `HOME`(= **App 的
|
|
963
|
+
`filesDir`**,不是 `/root`)/ `TMPDIR` / `ANDROID_ROOT` / `ANDROID_DATA` / `PROOT_TMP_DIR`,
|
|
964
|
+
最后合并运行时清单的 `launchEnv` 与 guest 环境(`DshProcess.kt:172-188`)。也就是说:宿主变量
|
|
965
|
+
一律不继承(这一点与 `env -i` 等效),但**壳想加什么变量都能加**——只是它目前没有加
|
|
966
|
+
`DSH_INSTRUCTION_MEMORY_ALLOW_EXTERNAL_BACKUP`,所以备份目录开关在手机上仍然不可达。
|
|
962
967
|
|
|
963
968
|
- **兼容性**:`0.2.1-rc.4` 正好是上表①档已实测的版本。2026-10-31 又用同一个 rc.4 在隔离的
|
|
964
969
|
`DSH_HOME` 上复跑一遍 **21/21**(`compat-1016-live-check.mjs`;1.5.1 起该脚本第四个参数给启动
|
|
@@ -980,21 +985,28 @@ DSH Go(Android 客户端)内置的引擎就是**真实 DSH**,锁定 `@deep
|
|
|
980
985
|
#### Android / WebView 适配(1.5.5 起)
|
|
981
986
|
|
|
982
987
|
WebView 不是浏览器:它能把 `window.prompt` 保留成函数却永不弹框,能暴露 `<a download>` 却没有
|
|
983
|
-
|
|
984
|
-
|
|
988
|
+
下载管线。这两处以前都是**点了没反应、也不报错**,而它们恰好是手机上数据进出口的一半。现在插件侧
|
|
989
|
+
这样处理:
|
|
985
990
|
|
|
986
991
|
| 场景 | WebView 上的行为 | 依据 |
|
|
987
992
|
| --- | --- | --- |
|
|
988
993
|
| 导出 | 不再点击一个必然失败的链接,直接把完整 JSON 渲染在面板里(全选即可复制) | `client.js` 的 `isAndroidWebView()` + `exportFallback` 分支 |
|
|
989
|
-
| 导入 / 从备份恢复 |
|
|
994
|
+
| 导入 / 从备份恢复 | **壳已实现文件选择**(`island_handle.dart:197` 的 `_files.attach()` → `web_file_chooser.dart:26` 的 `setOnShowFileSelector` → 原生 `dsh/web-files` 通道,content:// 校验 + 按文档代际取消);面板那行提示只是兜底:页面不是 http(s)、`canChoose()` 为假、或旧版 App 时,请改用「源码模式」粘贴 JSON | 2026-11-12 读 DSH Go 源码确认 |
|
|
990
995
|
| 批量加标签 | 不再调用 `window.prompt`:WebView 返回的 `null` 被判为"问不了",直接走内联输入框 | `batchTagOutcome(..., { nullMeansUnavailable: true })` |
|
|
991
996
|
| 实时更新 | 连续 6 次(≈30 秒)订阅失败后显示「实时更新已断开,请重新载入」;收到下一帧自动清除 | `source.onerror` 计数 |
|
|
992
997
|
| 字号 / 手势条 | `text-size-adjust:100%` 挡住系统字号重排;挂载失败提示避开安全区 | CSS |
|
|
993
998
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
999
|
+
**插件侧做不到、必须平台提供的**(逐条都已核对 DSH Go 源码):
|
|
1000
|
+
|
|
1001
|
+
1. **保存/分享桥**:`<a download>` 的 `blob:` 字节 WebView 不会交给 `DownloadListener`,插件无法
|
|
1002
|
+
自救。壳已有 `DshGoIsland` 通道和 `share_plus`,但没有任何"把这段文本存成文件"的消息类型 →
|
|
1003
|
+
要新增一条(注意 island 消息协议上限只有 **16 KB**,`IslandMessageLimits.maxBytes`,而一次导出
|
|
1004
|
+
可达 MB 级,必须分块)。
|
|
1005
|
+
2. **`onJsPrompt`**:webview_flutter 不暴露 JS 对话框,壳也没包 `WebChromeClient`;插件已改走内联
|
|
1006
|
+
表单,所以**这是可选项**,不做也不影响功能。
|
|
1007
|
+
3. **环境变量白名单**:壳会 `environment().clear()` 后逐项填(`DshProcess.kt:172-188`),加一个
|
|
1008
|
+
`DSH_INSTRUCTION_MEMORY_ALLOW_EXTERNAL_BACKUP=1` 就能让备份目录走出 rootfs。
|
|
1009
|
+
4. **WebView 销毁/后台时关闭 SSE**,否则半开连接会占满 32 条上限。
|
|
998
1010
|
|
|
999
1011
|
**纵深开关**:Android 的回环是全机共享的,没有宿主鉴权门时"仅回环"不构成边界。设置
|
|
1000
1012
|
`DSH_INSTRUCTION_MEMORY_REQUIRE_AUTH_FENCE=1` 后,缺少 `connection` 服务的 profile **直接
|
package/lib/client.js
CHANGED
|
@@ -366,6 +366,7 @@ window.__ModuleLoader__.load({
|
|
|
366
366
|
'profiles.omitted': '本轮未包含',
|
|
367
367
|
'profiles.omittedTitle': '这张形象因注入字数上限未进入本轮提示',
|
|
368
368
|
'export.saved': '已开始下载;若没有文件出现,请检查浏览器的下载列表',
|
|
369
|
+
'export.savedToApp': '已交给 DSH Go 保存:在系统分享面板里选「保存到文件」即可',
|
|
369
370
|
'export.fallbackHint': '当前环境无法触发文件下载,完整 JSON 已放在下面:全选复制后自行保存',
|
|
370
371
|
'export.fallbackClose': '关闭',
|
|
371
372
|
'import.webviewHint': 'Android / WebView 上若文件选择器不弹出,请改用「源码模式」粘贴 JSON',
|
|
@@ -663,6 +664,7 @@ window.__ModuleLoader__.load({
|
|
|
663
664
|
'profiles.omitted': 'Not in this turn',
|
|
664
665
|
'profiles.omittedTitle': 'This persona was dropped by the injection limit and did not reach this turn',
|
|
665
666
|
'export.saved': 'Download started — if no file appears, check your browser’s download list',
|
|
667
|
+
'export.savedToApp': 'Handed to DSH Go — pick “Save to Files” in the system share sheet',
|
|
666
668
|
'export.fallbackHint': 'This environment cannot start a file download; the full JSON is below — select all and copy it',
|
|
667
669
|
'export.fallbackClose': 'Close',
|
|
668
670
|
'import.webviewHint': 'On Android / WebView, if the file picker does not open, paste the JSON into “Source mode” instead',
|
|
@@ -2736,6 +2738,39 @@ window.__ModuleLoader__.load({
|
|
|
2736
2738
|
}
|
|
2737
2739
|
}
|
|
2738
2740
|
|
|
2741
|
+
/**
|
|
2742
|
+
* Hand `text` to the DSH Go island bridge, in chunks that fit its 16 KB
|
|
2743
|
+
* message cap (`IslandMessageLimits.maxBytes`). The app reassembles them
|
|
2744
|
+
* and opens the system share sheet — the only export path a WebView has,
|
|
2745
|
+
* since it never hands a `blob:` to a download listener.
|
|
2746
|
+
*
|
|
2747
|
+
* Returns false when the page is not inside DSH Go (or the bridge is
|
|
2748
|
+
* missing), which leaves the browser path untouched.
|
|
2749
|
+
*/
|
|
2750
|
+
async function sendToAppBridge(text, name) {
|
|
2751
|
+
try {
|
|
2752
|
+
const bridge = typeof window !== 'undefined' ? window.DshGoIsland : undefined
|
|
2753
|
+
if (bridge === undefined || bridge === null || typeof bridge.postMessage !== 'function') return false
|
|
2754
|
+
const id = 'im-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8)
|
|
2755
|
+
const CHUNK = 6000
|
|
2756
|
+
let i = 0
|
|
2757
|
+
while (i < text.length) {
|
|
2758
|
+
let end = Math.min(text.length, i + CHUNK)
|
|
2759
|
+
// Never split a surrogate pair across two messages: each one is its
|
|
2760
|
+
// own JSON document and a lone surrogate would not survive it.
|
|
2761
|
+
const code = text.charCodeAt(end - 1)
|
|
2762
|
+
if (end < text.length && code >= 0xd800 && code <= 0xdbff) end += 1
|
|
2763
|
+
bridge.postMessage(JSON.stringify({
|
|
2764
|
+
t: 'save', id, name, data: text.slice(i, end), last: end >= text.length,
|
|
2765
|
+
}))
|
|
2766
|
+
i = end
|
|
2767
|
+
}
|
|
2768
|
+
return true
|
|
2769
|
+
} catch {
|
|
2770
|
+
return false
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2739
2774
|
async function doExport() {
|
|
2740
2775
|
try {
|
|
2741
2776
|
const result = await callHost('export-data', null)
|
|
@@ -2744,6 +2779,13 @@ window.__ModuleLoader__.load({
|
|
|
2744
2779
|
return
|
|
2745
2780
|
}
|
|
2746
2781
|
const text = JSON.stringify(result.export, null, 2) + '\n'
|
|
2782
|
+
const fileName = 'dsh-instruction-memory-' + new Date().toISOString().slice(0, 10) + '.json'
|
|
2783
|
+
// Inside DSH Go the app can actually write the file; prefer that over
|
|
2784
|
+
// both the download link and the in-panel copy.
|
|
2785
|
+
if (await sendToAppBridge(text, fileName)) {
|
|
2786
|
+
setNotice(t('export.savedToApp'))
|
|
2787
|
+
return
|
|
2788
|
+
}
|
|
2747
2789
|
/**
|
|
2748
2790
|
* A WebView can expose `<a download>` and still have nowhere to put
|
|
2749
2791
|
* the bytes: Android only saves when the embedding app implements a
|
|
@@ -2761,7 +2803,7 @@ window.__ModuleLoader__.load({
|
|
|
2761
2803
|
const url = URL.createObjectURL(blob)
|
|
2762
2804
|
const link = document.createElement('a')
|
|
2763
2805
|
link.href = url
|
|
2764
|
-
link.download =
|
|
2806
|
+
link.download = fileName
|
|
2765
2807
|
document.body.appendChild(link)
|
|
2766
2808
|
link.click()
|
|
2767
2809
|
if (link.parentNode !== null) link.parentNode.removeChild(link)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-instruction-memory",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.6",
|
|
4
4
|
"description": "User-only instruction memory for DSH: long-term instructions maintained in the settings UI and auto-injected into every conversation — the model has no write access. 指令记忆:只由用户在设置页维护的长期指令,自动注入此后每轮对话;模型没有任何写入口。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|