dsh-workspace-plus 0.7.0 → 0.7.1
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/README.md +3 -24
- package/cordis.patch.yml +0 -32
- package/lib/client.js +0 -401
- package/package.json +3 -11
- package/lib/sessionlog/icon.svg +0 -19
- package/lib/sessionlog/index.js +0 -636
- package/lib/sessionlog/locale/en.json +0 -6
- package/lib/sessionlog/locale/zh.json +0 -6
- package/lib/sessionlog/package.json +0 -6
- package/lib/sessionlog/redact.js +0 -142
- package/lib/sessionlog/settings.js +0 -436
- package/lib/sessionlog/wire.js +0 -235
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/dsh-workspace-plus)
|
|
4
4
|
[](https://dsh-plugin.org/plugins/sen70s/dsh-workspace-plus)
|
|
5
5
|
[](https://github.com/Sen70s/dsh-workspace-plus/blob/main/LICENSE)
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
**当前版本 `v0.7.1`** · 要求 DSH `>=0.1.7-rc.2`
|
|
7
|
+
DeepSeek Harness(DSH)的插件。一个插件,两个子功能,插件页里各占一行、可单独启停。
|
|
8
8
|
|
|
9
9
|
DeepSeek Harness(DSH)的插件。一个插件,三个子功能,插件页里各占一行、可单独启停。
|
|
10
10
|
|
|
@@ -12,7 +12,6 @@ DeepSeek Harness(DSH)的插件。一个插件,三个子功能,插件页
|
|
|
12
12
|
| --- | --- |
|
|
13
13
|
| **workspace+** | 一个工作区登记多个目录,每个目录一个标签。标签表注入每次请求,输入框打 `@` 也能直接引用 |
|
|
14
14
|
| **通知 / notice** | 对话请求权限、询问用户、任务完成、中断、出错时弹 Windows 原生通知,点通知直接回到那个对话 |
|
|
15
|
-
| **会话日志 / sessionlog** | 把会话日志上传给 DeepSeek(帮助改进 DeepSeek 模型与产品)。**默认关闭**;开启后与使用哪个模型 API 无关 |
|
|
16
15
|
|
|
17
16
|
## 安装
|
|
18
17
|
|
|
@@ -26,8 +25,6 @@ dsh plugin --profile desktop add dsh-workspace-plus
|
|
|
26
25
|
|
|
27
26
|
> 自 0.6.0 起通知(notice)已并入本包。若你此前装过独立的 `dsh-notice` 插件,先从 profile 里移除它再装本包 —— 两者的路由、协议与设置存储完全相同,同装会同名冲突。
|
|
28
27
|
|
|
29
|
-
> 自 0.7.0 起会话日志上传(sessionlog)并入本包,作为第三行子功能,**默认关闭**。
|
|
30
|
-
|
|
31
28
|
## workspace+:一个工作区,多个目录
|
|
32
29
|
|
|
33
30
|
一个工作区(DSH 里 = 一个目录)装不下真实项目。前后端分两个仓库、文档在第三个目录时,把这三个目录登记成标签,agent 就不再需要你每次手写绝对路径。
|
|
@@ -67,23 +64,6 @@ agent 直接去 D:\proj\backend,不会因为旁边有个 backend-legacy 而改
|
|
|
67
64
|
|
|
68
65
|
设置存在 `~/.dsh/notice/`,协议 scheme 是 `dsh-notice:` —— 从独立 dsh-notice 插件升级过来时,已有设置与协议注册自动生效。
|
|
69
66
|
|
|
70
|
-
## 会话日志:换个模型 API 也照样上传
|
|
71
|
-
|
|
72
|
-
DSH 自己有一个开关叫「在使用官方模型 API 时上传 Session Log」(设置 → 通用),但它**只在官方 DeepSeek 适配器真的发出一次模型请求时**才生效:那个字段是挂在 `/messages` 请求体上的。所以一旦你把模型换成第三方 API,这个开关就再也不会有任何动静。
|
|
73
|
-
|
|
74
|
-
本包的第三个子功能把这件事补全,**默认关闭**,在设置 → `会话日志` 里打开:
|
|
75
|
-
|
|
76
|
-
- **主通道与模型供应商无关。** 它监听规范会话流,把每条事件交给 DSH 自带的 OTLP 会话日志通道,发往 `https://dsh-otel-collector.deepseeksvc.com/v1/logs`(与 DSH 自己的会话遥测同一個 collector)。端点可用页面上填写,或用 `DSH_TELEMETRY_OTLP_URL` 覆盖。换用任何模型 API 都不影响这一路。
|
|
77
|
-
- **发出前强制脱敏。** 会话日志里常会夹着工具输出——`npm`/`gh` 的 token、`Authorization` 头、带口令的 git 地址、私钥正文。这些在发出前会被换成 `[REDACTED]`,**没有开关可以关掉**,设置页会显示已脱敏的处数。原始会话日志不受影响。
|
|
78
|
-
- **不覆盖进程级关闭。** 只要你设了 `DSH_TELEMETRY_DISABLED`(任何非空值)或 `DSH_TELEMETRY_MODE=DISABLED`,本子功能就不会发送任何内容,页面上会明说原因。
|
|
79
|
-
- **`dsh_session_log` 请求字段是可选补充,默认关闭。** DSH 不检查 DeepSeek 适配器的 `baseURL` 是否指向官方域名 —— 那个字段会跟着请求发到**它配置的那个端点**。所以这一项只在你自己确认端点可信时再打开;内置的 `dsh-session-log-deepseek` 在场时它会自动跳过。
|
|
80
|
-
|
|
81
|
-
状态文件在 `~/.dsh/sessionlog/settings.json`,里面同时存着已接受的水位(重启后不会把同一段重复上传)。
|
|
82
|
-
|
|
83
|
-
> 这一路会把**会话内容**(含消息、工具输出、命令结果)发到远端。已知形状的凭据会被脱敏,但脱敏是模式匹配、不是万能的:自己写进对话里的其他敏感信息(内网地址、路径、业务数据)不会自动处理。开启前请自行确认这不违反你所在组织的要求。
|
|
84
|
-
|
|
85
|
-
实现细节、DSH 原功能的取证(含一个和字面文案不符的地方)与已知限制,见 [docs/session-log-upload.md](https://github.com/Sen70s/dsh-workspace-plus/blob/main/docs/session-log-upload.md)。
|
|
86
|
-
|
|
87
67
|
## 自检
|
|
88
68
|
|
|
89
69
|
不需要安装、不需要 DSH 在跑:
|
|
@@ -92,14 +72,13 @@ DSH 自己有一个开关叫「在使用官方模型 API 时上传 Session Log
|
|
|
92
72
|
node tools/verify.mjs
|
|
93
73
|
```
|
|
94
74
|
|
|
95
|
-
|
|
75
|
+
521 项断言,覆盖 manifest、patch 层、宿主半(含 `/workspaceplus/api` 在认证服务缺失时的拒绝)、浏览器半、工作区行按钮、`@` 候选源、弹窗形状,以及通知点击启动器的编译与回退(含真实 `csc.exe` 编译一次并自检)。
|
|
96
76
|
|
|
97
77
|
## 其它
|
|
98
78
|
|
|
99
79
|
- `lib/` 是手写源码,**不需要打包步骤**(没有 bundler):从 npm、GitHub、本地目录三种方式装到的都是可直接加载的代码。唯一的例外是通知点击助手 —— 它由宿主在首次注册协议跳转时(插件加载后就会发生)用系统自带的 `csc.exe` 从 `lib/notice/click-host.cs` 现编到 `~/.dsh/notice/` 并缓存;编译不可用时自动退化为 PowerShell 启动器,插件其余部分不受影响。
|
|
100
80
|
- workspace+ 的状态文件在 `~/.dsh/workspaceplus/directories.json`,可以直接手改,改完刷新页面即可。
|
|
101
81
|
- workspace+ 的浏览器接口 `/workspaceplus/api` 每次请求都要过宿主的 `connection.admit` 认证。认证服务缺失、`admit` 不存在或报错时一律 **503**,读写都不放行——这个接口写下的目录备注会进入模型上下文,不能让未认证的调用方有机会修改它。
|
|
102
|
-
- 会话日志的设置与水位在 `~/.dsh/sessionlog/settings.json`,同样可以直接手改。
|
|
103
82
|
- [LICENSE](./LICENSE) · MIT
|
|
104
83
|
|
|
105
84
|
## Star History
|
package/cordis.patch.yml
CHANGED
|
@@ -25,16 +25,6 @@
|
|
|
25
25
|
# poll ride in the shared browser half, which serves them
|
|
26
26
|
# regardless and degrades silently when this row is off.
|
|
27
27
|
#
|
|
28
|
-
# sessionlog 会话日志上传 (exports "./sessionlog" ->
|
|
29
|
-
# lib/sessionlog/index.js). Node half only, and OFF by
|
|
30
|
-
# default. DSH's own "在使用官方模型 API 时上传 Session Log"
|
|
31
|
-
# only ever fires while the official DeepSeek adapter performs
|
|
32
|
-
# the request; this row reproduces it and adds a
|
|
33
|
-
# provider-independent path, so a user on a third-party model
|
|
34
|
-
# API can still hand the session log to DeepSeek. Its settings
|
|
35
|
-
# page rides in the shared browser half on the same terms as
|
|
36
|
-
# the notice page.
|
|
37
|
-
#
|
|
38
28
|
# Turning one row off removes exactly that half from the host while the bundle
|
|
39
29
|
# and the other sub-features keep working. The 通知 settings page also carries a
|
|
40
30
|
# runtime "启用桌面通知" switch, so notifications can be silenced without
|
|
@@ -61,25 +51,3 @@
|
|
|
61
51
|
# done: true # 任务完成
|
|
62
52
|
# aborted: true # 任务中断
|
|
63
53
|
# error: true # 任务出错
|
|
64
|
-
- id: sessionlog
|
|
65
|
-
name: 'dsh-workspace-plus/sessionlog'
|
|
66
|
-
# config: # deployment defaults for the session-log
|
|
67
|
-
# enabled: false # sub-feature. Ships OFF on purpose: this
|
|
68
|
-
# # hands conversation content to a remote
|
|
69
|
-
# # collector, so it must be an explicit act.
|
|
70
|
-
# # The 会话日志 settings page can still turn
|
|
71
|
-
# # it on per host.
|
|
72
|
-
# endpoint: '' # '' = $DSH_TELEMETRY_OTLP_URL, else the
|
|
73
|
-
# # DeepSeek collector the base profile uses
|
|
74
|
-
# requestField: false # also contribute the dsh_session_log request
|
|
75
|
-
# # field when nothing else owns it. OFF by
|
|
76
|
-
# # default: DSH performs no official-endpoint
|
|
77
|
-
# # check before attaching that field, so on a
|
|
78
|
-
# # host whose deepseek-official route points at
|
|
79
|
-
# # a relay it would send content to that relay.
|
|
80
|
-
# # Skipped anyway when the shipped
|
|
81
|
-
# # dsh-session-log-deepseek owns the field.
|
|
82
|
-
# includeHistory: true # when switched on mid-run, also send the
|
|
83
|
-
# # sessions that are already in the log
|
|
84
|
-
# maxRequestBytes: 4000000 # per-request ceiling for one upload
|
|
85
|
-
# shutdownTimeoutMillis: 3000 # drain deadline when the row unloads
|
package/lib/client.js
CHANGED
|
@@ -71,18 +71,6 @@
|
|
|
71
71
|
* click → `dsh-notice:` protocol → host route → pending
|
|
72
72
|
* intent → THIS half → `ctx.uiWorkspace.openSession()`.
|
|
73
73
|
*
|
|
74
|
-
* The 会话日志 (sessionlog) sub-feature — the plugin's own version of DSH's
|
|
75
|
-
* "在使用官方模型 API 时上传 Session Log" preference, shipped as the
|
|
76
|
-
* `dsh-workspace-plus/sessionlog` loader row and OFF by default — adds one more
|
|
77
|
-
* seat here, described in detail at its section below:
|
|
78
|
-
*
|
|
79
|
-
* `settings.section` the 会话日志 settings page, writing through
|
|
80
|
-
* `/dsh-sessionlog/api`. The host half owns the upload
|
|
81
|
-
* channel; this page only renders what the host reports
|
|
82
|
-
* and posts one switch or endpoint change at a time. When
|
|
83
|
-
* the sessionlog row is off the host answers 404 and the
|
|
84
|
-
* page explains that state instead of erroring.
|
|
85
|
-
*
|
|
86
74
|
* Only the shell's frozen platform module table may be `require`d here (react,
|
|
87
75
|
* react-dom, react/jsx-runtime, @deepseek-ai/cordis,
|
|
88
76
|
* @deepseek-ai/dsh-client-store, @deepseek-ai/dsh-client-ui-slots,
|
|
@@ -867,356 +855,6 @@ window.__ModuleLoader__.load({
|
|
|
867
855
|
document.head.appendChild(tag)
|
|
868
856
|
}
|
|
869
857
|
|
|
870
|
-
// -----------------------------------------------------------------------
|
|
871
|
-
// The 会话日志 (sessionlog) sub-feature — client side
|
|
872
|
-
//
|
|
873
|
-
// DSH's own "在使用官方模型 API 时上传 Session Log" switch lives in
|
|
874
|
-
// Settings → General and only ever fires while the official DeepSeek adapter
|
|
875
|
-
// performs the request. The `dsh-workspace-plus/sessionlog` loader row
|
|
876
|
-
// (lib/sessionlog/index.js) reproduces that behaviour and adds a
|
|
877
|
-
// provider-independent OTLP channel, so the switch means the same thing for
|
|
878
|
-
// a user on a third-party model API. It ships OFF: handing conversation
|
|
879
|
-
// content to a remote collector has to be an explicit act.
|
|
880
|
-
//
|
|
881
|
-
// This page is deliberately a *report*, not a second source of truth: the
|
|
882
|
-
// host owns enablement, the endpoint, the channel state and the counters,
|
|
883
|
-
// and answers every mutation with a fresh snapshot. A 404 means the loader
|
|
884
|
-
// row is switched off on the plugin page — a state, not a failure — and is
|
|
885
|
-
// rendered as an explanation.
|
|
886
|
-
// -----------------------------------------------------------------------
|
|
887
|
-
|
|
888
|
-
/** Settings section identity of the 会话日志 page. */
|
|
889
|
-
const SESSIONLOG_SETTINGS_SECTION_ID = 'sessionlog'
|
|
890
|
-
const SESSIONLOG_SETTINGS_SECTION_LABEL = '会话日志'
|
|
891
|
-
const SESSIONLOG_SETTINGS_SECTION_ORDER = 27
|
|
892
|
-
|
|
893
|
-
/** Host route this page talks to; must match lib/sessionlog/index.js. */
|
|
894
|
-
const SESSIONLOG_API_PATH = '/dsh-sessionlog/api'
|
|
895
|
-
|
|
896
|
-
/** Stylesheet identity for the 会话日志 settings page (one tag, rewritten on reload). */
|
|
897
|
-
const SESSIONLOG_STYLE_ID = 'dsh-workspace-plus/sessionlog.css'
|
|
898
|
-
|
|
899
|
-
/** Prefix for every class the sessionlog page injects, so it can never collide. */
|
|
900
|
-
const SESSIONLOG_CSS = `
|
|
901
|
-
.dsh-sessionlog-section { display: flex; flex-direction: column; gap: 14px; }
|
|
902
|
-
.dsh-sessionlog-group { display: flex; flex-direction: column; gap: 10px; }
|
|
903
|
-
.dsh-sessionlog-group-title { color: var(--dsw-alias-label-secondary); font-size: 12px; font-weight: 600; letter-spacing: .04em; }
|
|
904
|
-
.dsh-sessionlog-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
|
|
905
|
-
.dsh-sessionlog-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
906
|
-
.dsh-sessionlog-title { color: var(--dsw-alias-label-primary); font-size: 14px; font-weight: 600; }
|
|
907
|
-
.dsh-sessionlog-desc { color: var(--dsw-alias-label-secondary); font-size: 12.5px; line-height: 1.5; }
|
|
908
|
-
.dsh-sessionlog-switch { position: relative; flex: none; width: 38px; height: 22px; margin-top: 2px; padding: 0; cursor: pointer; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px; background: var(--dsw-alias-bg-layer-2); transition: background .15s ease, border-color .15s ease; }
|
|
909
|
-
.dsh-sessionlog-switch[aria-checked="true"] { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
|
|
910
|
-
.dsh-sessionlog-switch:disabled { cursor: default; opacity: .5; }
|
|
911
|
-
.dsh-sessionlog-knob { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--dsw-alias-bg-base); transition: transform .15s ease; }
|
|
912
|
-
.dsh-sessionlog-switch[aria-checked="true"] .dsh-sessionlog-knob { transform: translateX(16px); }
|
|
913
|
-
.dsh-sessionlog-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
914
|
-
.dsh-sessionlog-button { cursor: pointer; padding: 5px 12px; font-size: 13px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
915
|
-
.dsh-sessionlog-button:hover:not(:disabled) { border-color: var(--dsw-alias-border-l1); }
|
|
916
|
-
.dsh-sessionlog-button:disabled { cursor: default; opacity: .5; }
|
|
917
|
-
.dsh-sessionlog-input { flex: 1 1 260px; min-width: 0; padding: 5px 9px; font-size: 12.5px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); }
|
|
918
|
-
.dsh-sessionlog-status { display: flex; flex-direction: column; gap: 4px; padding: 10px 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary); font-size: 12.5px; line-height: 1.6; }
|
|
919
|
-
.dsh-sessionlog-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; word-break: break-all; }
|
|
920
|
-
.dsh-sessionlog-error { color: var(--dsw-alias-state-error-primary); }
|
|
921
|
-
.dsh-sessionlog-ok { color: var(--dsw-alias-state-success-primary); }
|
|
922
|
-
`
|
|
923
|
-
|
|
924
|
-
/** Install the sessionlog stylesheet, exactly one tag per document (rewritten on reload). */
|
|
925
|
-
function ensureSessionlogStyles() {
|
|
926
|
-
if (typeof document === 'undefined') return
|
|
927
|
-
const existing = document.querySelector('style[data-plugin-css=' + JSON.stringify(SESSIONLOG_STYLE_ID) + ']')
|
|
928
|
-
if (existing !== null) {
|
|
929
|
-
existing.textContent = SESSIONLOG_CSS
|
|
930
|
-
return
|
|
931
|
-
}
|
|
932
|
-
const tag = document.createElement('style')
|
|
933
|
-
tag.dataset.plugin = 'dsh-workspace-plus'
|
|
934
|
-
tag.dataset.pluginCss = SESSIONLOG_STYLE_ID
|
|
935
|
-
tag.textContent = SESSIONLOG_CSS
|
|
936
|
-
document.head.appendChild(tag)
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
/**
|
|
940
|
-
* Fetch the sessionlog host's snapshot.
|
|
941
|
-
*
|
|
942
|
-
* @param signal - caller cancellation.
|
|
943
|
-
* @returns the parsed payload.
|
|
944
|
-
*/
|
|
945
|
-
async function fetchSessionlogSnapshot(signal) {
|
|
946
|
-
const response = await fetch(SESSIONLOG_API_PATH, { cache: 'no-store', signal })
|
|
947
|
-
if (!response.ok) throw new Error(`host responded ${response.status}`)
|
|
948
|
-
return await response.json()
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Post one sessionlog control operation.
|
|
953
|
-
*
|
|
954
|
-
* @param body - operation payload.
|
|
955
|
-
* @returns the parsed payload.
|
|
956
|
-
*/
|
|
957
|
-
async function postSessionlogOperation(body) {
|
|
958
|
-
const response = await fetch(SESSIONLOG_API_PATH, {
|
|
959
|
-
method: 'POST',
|
|
960
|
-
cache: 'no-store',
|
|
961
|
-
headers: { 'content-type': 'application/json' },
|
|
962
|
-
body: JSON.stringify(body),
|
|
963
|
-
})
|
|
964
|
-
if (!response.ok && response.status >= 500) throw new Error(`host responded ${response.status}`)
|
|
965
|
-
return await response.json()
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
/**
|
|
969
|
-
* One labelled switch row of the 会话日志 page.
|
|
970
|
-
*
|
|
971
|
-
* @param props - `{ title, description, checked, disabled, onChange }`.
|
|
972
|
-
* @returns the row element.
|
|
973
|
-
*/
|
|
974
|
-
function SessionlogSwitchRow(props) {
|
|
975
|
-
return React.createElement(
|
|
976
|
-
'div',
|
|
977
|
-
{ className: 'dsh-sessionlog-row' },
|
|
978
|
-
React.createElement(
|
|
979
|
-
'div',
|
|
980
|
-
{ className: 'dsh-sessionlog-text' },
|
|
981
|
-
React.createElement('div', { className: 'dsh-sessionlog-title' }, props.title),
|
|
982
|
-
props.description ? React.createElement('div', { className: 'dsh-sessionlog-desc' }, props.description) : null,
|
|
983
|
-
),
|
|
984
|
-
React.createElement(
|
|
985
|
-
'button',
|
|
986
|
-
{
|
|
987
|
-
type: 'button',
|
|
988
|
-
role: 'switch',
|
|
989
|
-
'aria-checked': props.checked === true,
|
|
990
|
-
'aria-label': props.title,
|
|
991
|
-
disabled: props.disabled === true,
|
|
992
|
-
className: 'dsh-sessionlog-switch',
|
|
993
|
-
onClick: () => props.onChange(props.checked !== true),
|
|
994
|
-
},
|
|
995
|
-
React.createElement('span', { className: 'dsh-sessionlog-knob' }),
|
|
996
|
-
),
|
|
997
|
-
)
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
/** Human label for the request-field channel's current state. */
|
|
1001
|
-
function sessionlogRequestFieldLine(channel) {
|
|
1002
|
-
switch (channel?.requestField) {
|
|
1003
|
-
case 'active':
|
|
1004
|
-
return 'dsh_session_log 请求字段:由本插件提供(官方 DeepSeek 适配器请求时随请求发出)。'
|
|
1005
|
-
case 'taken':
|
|
1006
|
-
return 'dsh_session_log 请求字段:已由其它插件提供(通常是内置的 dsh-session-log-deepseek),本插件自动跳过。'
|
|
1007
|
-
case 'unavailable':
|
|
1008
|
-
return 'dsh_session_log 请求字段:当前组合里没有 DeepSeek 扩展注册表,这一路不可用。'
|
|
1009
|
-
default:
|
|
1010
|
-
return 'dsh_session_log 请求字段:未启用。'
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
/**
|
|
1015
|
-
* The 会话日志 settings page. It never predicts what the host accepted: every
|
|
1016
|
-
* switch posts one key and adopts the snapshot that comes back, and a 404 —
|
|
1017
|
-
* the sessionlog loader row being switched off — renders as an explanation
|
|
1018
|
-
* rather than an error.
|
|
1019
|
-
*
|
|
1020
|
-
* `props.initial` is a test seam (the shell never passes it), matching the
|
|
1021
|
-
* workspace+ page: it seeds the snapshot/status and skips the mount fetch so
|
|
1022
|
-
* a test can render the ready and failure states without a live host.
|
|
1023
|
-
*
|
|
1024
|
-
* @param props - optional `{ initial }`.
|
|
1025
|
-
* @returns the settings section element.
|
|
1026
|
-
*/
|
|
1027
|
-
function SessionlogSettings(props) {
|
|
1028
|
-
const injected = props !== null && typeof props === 'object' ? props.initial : undefined
|
|
1029
|
-
const [snapshot, setSnapshot] = React.useState(injected?.snapshot ?? null)
|
|
1030
|
-
const [status, setStatus] = React.useState(injected?.status ?? { kind: 'loading', message: '' })
|
|
1031
|
-
const [busy, setBusy] = React.useState(injected?.busy === true)
|
|
1032
|
-
const [draft, setDraft] = React.useState(String(injected?.draft ?? ''))
|
|
1033
|
-
|
|
1034
|
-
React.useEffect(() => {
|
|
1035
|
-
if (injected !== undefined) return undefined
|
|
1036
|
-
let cancelled = false
|
|
1037
|
-
const controller = typeof AbortController === 'undefined' ? undefined : new AbortController()
|
|
1038
|
-
;(async () => {
|
|
1039
|
-
try {
|
|
1040
|
-
const payload = await fetchSessionlogSnapshot(controller?.signal)
|
|
1041
|
-
if (cancelled) return
|
|
1042
|
-
setSnapshot(payload)
|
|
1043
|
-
setDraft(String(payload?.settings?.endpoint ?? ''))
|
|
1044
|
-
setStatus({ kind: 'ready', message: '' })
|
|
1045
|
-
} catch (error) {
|
|
1046
|
-
if (cancelled) return
|
|
1047
|
-
setStatus({ kind: 'error', message: String((error && error.message) || error) })
|
|
1048
|
-
}
|
|
1049
|
-
})()
|
|
1050
|
-
return () => {
|
|
1051
|
-
cancelled = true
|
|
1052
|
-
controller?.abort()
|
|
1053
|
-
}
|
|
1054
|
-
}, [])
|
|
1055
|
-
|
|
1056
|
-
const send = React.useCallback(async (body) => {
|
|
1057
|
-
setBusy(true)
|
|
1058
|
-
try {
|
|
1059
|
-
const payload = await postSessionlogOperation(body)
|
|
1060
|
-
// A rejected operation still carries a snapshot (the host always
|
|
1061
|
-
// answers with one), so the page can adopt it and show the reason.
|
|
1062
|
-
if (payload !== null && typeof payload === 'object' && payload.settings !== undefined) {
|
|
1063
|
-
setSnapshot(payload)
|
|
1064
|
-
setDraft(String(payload.settings.endpoint ?? ''))
|
|
1065
|
-
}
|
|
1066
|
-
setStatus({
|
|
1067
|
-
kind: payload !== null && payload.ok === true ? 'ready' : 'error',
|
|
1068
|
-
message: String(payload?.message ?? ''),
|
|
1069
|
-
})
|
|
1070
|
-
} catch (error) {
|
|
1071
|
-
setStatus({ kind: 'error', message: String((error && error.message) || error) })
|
|
1072
|
-
} finally {
|
|
1073
|
-
setBusy(false)
|
|
1074
|
-
}
|
|
1075
|
-
}, [])
|
|
1076
|
-
|
|
1077
|
-
if (snapshot === null) {
|
|
1078
|
-
const message = status.kind === 'error' ? status.message : ''
|
|
1079
|
-
if (status.kind === 'error' && message.includes('404')) {
|
|
1080
|
-
return React.createElement(
|
|
1081
|
-
'div',
|
|
1082
|
-
{ className: 'dsh-sessionlog-section' },
|
|
1083
|
-
React.createElement(
|
|
1084
|
-
'div',
|
|
1085
|
-
{ className: 'dsh-sessionlog-desc' },
|
|
1086
|
-
'会话日志子功能当前未启用:在侧栏「插件」页的 dsh-workspace-plus 卡片里打开 sessionlog 这一行即可开启。',
|
|
1087
|
-
),
|
|
1088
|
-
)
|
|
1089
|
-
}
|
|
1090
|
-
return React.createElement(
|
|
1091
|
-
'div',
|
|
1092
|
-
{ className: 'dsh-sessionlog-section' },
|
|
1093
|
-
React.createElement(
|
|
1094
|
-
'div',
|
|
1095
|
-
{ className: 'dsh-sessionlog-desc' },
|
|
1096
|
-
status.kind === 'error' ? `读取设置失败:${message}` : '正在读取设置…',
|
|
1097
|
-
),
|
|
1098
|
-
)
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
const settings = snapshot.settings ?? {}
|
|
1102
|
-
const channel = snapshot.channel ?? {}
|
|
1103
|
-
const stats = snapshot.stats ?? {}
|
|
1104
|
-
const on = settings.enabled === true
|
|
1105
|
-
const endpointSource =
|
|
1106
|
-
snapshot.endpointSource === 'settings' ? '设置页指定' : snapshot.endpointSource === 'environment' ? '环境变量' : '出厂默认'
|
|
1107
|
-
const reporting = channel.reporting === true
|
|
1108
|
-
const lastError = typeof stats.lastError === 'string' && stats.lastError !== '' ? stats.lastError : ''
|
|
1109
|
-
|
|
1110
|
-
return React.createElement(
|
|
1111
|
-
'div',
|
|
1112
|
-
{ className: 'dsh-sessionlog-section' },
|
|
1113
|
-
React.createElement(
|
|
1114
|
-
'div',
|
|
1115
|
-
{ className: 'dsh-sessionlog-group' },
|
|
1116
|
-
React.createElement(SessionlogSwitchRow, {
|
|
1117
|
-
title: '上传 Session Log(帮助改进 DeepSeek 模型与产品)',
|
|
1118
|
-
description:
|
|
1119
|
-
'默认关闭。开启后,本机会话日志的内容会发送到 DeepSeek 的收集端点;与使用哪个模型 API 无关,因此换用第三方模型 API 时同样生效。',
|
|
1120
|
-
checked: on,
|
|
1121
|
-
disabled: busy,
|
|
1122
|
-
onChange: (next) => void send({ op: 'patch', patch: { enabled: next } }),
|
|
1123
|
-
}),
|
|
1124
|
-
React.createElement(
|
|
1125
|
-
'div',
|
|
1126
|
-
{ className: 'dsh-sessionlog-status' },
|
|
1127
|
-
React.createElement('div', null, reporting ? `上报中:${stats.events ?? 0} 条事件 / ${stats.sessions ?? 0} 个会话` : on ? '已开启,等待通道就绪…' : '未开启:本机不会向 DeepSeek 发送任何会话内容。'),
|
|
1128
|
-
React.createElement('div', null, channel.otlpAvailable === true ? 'OTLP 会话日志通道:可用。' : 'OTLP 会话日志通道:当前组合未挂载 otel 服务,无法上传。'),
|
|
1129
|
-
React.createElement('div', null, sessionlogRequestFieldLine(channel)),
|
|
1130
|
-
// The host's process-level opt-out outranks this page's switch, and
|
|
1131
|
-
// saying so is the difference between "the switch is broken" and
|
|
1132
|
-
// "this machine has been told not to report".
|
|
1133
|
-
snapshot.killSwitch
|
|
1134
|
-
? React.createElement('div', { className: 'dsh-sessionlog-error' }, `进程级遥测开关:${String(snapshot.killSwitch)} —— 本页的开关不会覆盖它。`)
|
|
1135
|
-
: null,
|
|
1136
|
-
React.createElement(
|
|
1137
|
-
'div',
|
|
1138
|
-
{ className: 'dsh-sessionlog-mono' },
|
|
1139
|
-
`目标端点(${endpointSource}):${String(snapshot.effectiveEndpoint ?? '')}`,
|
|
1140
|
-
),
|
|
1141
|
-
// Redaction is unconditional, so this is a receipt rather than a control.
|
|
1142
|
-
stats.redactions > 0
|
|
1143
|
-
? React.createElement(
|
|
1144
|
-
'div',
|
|
1145
|
-
{ className: 'dsh-sessionlog-ok' },
|
|
1146
|
-
`已在发出前脱敏 ${String(stats.redactions)} 处凭据(token / 密钥 / Authorization)。`,
|
|
1147
|
-
)
|
|
1148
|
-
: null,
|
|
1149
|
-
lastError ? React.createElement('div', { className: 'dsh-sessionlog-error' }, `最近一次错误:${lastError}`) : null,
|
|
1150
|
-
),
|
|
1151
|
-
),
|
|
1152
|
-
React.createElement(
|
|
1153
|
-
'div',
|
|
1154
|
-
{ className: 'dsh-sessionlog-group' },
|
|
1155
|
-
React.createElement('div', { className: 'dsh-sessionlog-group-title' }, '上传范围'),
|
|
1156
|
-
React.createElement(SessionlogSwitchRow, {
|
|
1157
|
-
title: '同时提供 dsh_session_log 请求字段',
|
|
1158
|
-
description:
|
|
1159
|
-
'默认关闭。仅当官方 DeepSeek 适配器承担这次请求、且没有别的插件占用该字段时才生效;内置的 dsh-session-log-deepseek 在场时会自动跳过。注意:DSH 不检查该适配器的 baseURL 是否指向官方域名,字段会随请求发到它配置的任意端点。',
|
|
1160
|
-
checked: settings.requestField === true,
|
|
1161
|
-
disabled: busy || !on,
|
|
1162
|
-
onChange: (next) => void send({ op: 'patch', patch: { requestField: next } }),
|
|
1163
|
-
}),
|
|
1164
|
-
React.createElement(SessionlogSwitchRow, {
|
|
1165
|
-
title: '开启时补传已有会话历史',
|
|
1166
|
-
description: '关闭后,只上传开启之后新产生的事件;已经存在的对话不会补传。',
|
|
1167
|
-
checked: settings.includeHistory === true,
|
|
1168
|
-
disabled: busy || !on,
|
|
1169
|
-
onChange: (next) => void send({ op: 'patch', patch: { includeHistory: next } }),
|
|
1170
|
-
}),
|
|
1171
|
-
),
|
|
1172
|
-
React.createElement(
|
|
1173
|
-
'div',
|
|
1174
|
-
{ className: 'dsh-sessionlog-group' },
|
|
1175
|
-
React.createElement('div', { className: 'dsh-sessionlog-group-title' }, '收集端点'),
|
|
1176
|
-
React.createElement(
|
|
1177
|
-
'div',
|
|
1178
|
-
{ className: 'dsh-sessionlog-actions' },
|
|
1179
|
-
React.createElement('input', {
|
|
1180
|
-
type: 'text',
|
|
1181
|
-
className: 'dsh-sessionlog-input',
|
|
1182
|
-
spellCheck: false,
|
|
1183
|
-
placeholder: '留空则使用环境变量 DSH_TELEMETRY_OTLP_URL,否则用出厂端点',
|
|
1184
|
-
value: draft,
|
|
1185
|
-
disabled: busy,
|
|
1186
|
-
onChange: (event) => setDraft(String(event?.target?.value ?? '')),
|
|
1187
|
-
}),
|
|
1188
|
-
React.createElement(
|
|
1189
|
-
'button',
|
|
1190
|
-
{
|
|
1191
|
-
type: 'button',
|
|
1192
|
-
className: 'dsh-sessionlog-button',
|
|
1193
|
-
disabled: busy || draft === String(settings.endpoint ?? ''),
|
|
1194
|
-
onClick: () => void send({ op: 'patch', patch: { endpoint: draft } }),
|
|
1195
|
-
},
|
|
1196
|
-
'保存',
|
|
1197
|
-
),
|
|
1198
|
-
React.createElement(
|
|
1199
|
-
'button',
|
|
1200
|
-
{
|
|
1201
|
-
type: 'button',
|
|
1202
|
-
className: 'dsh-sessionlog-button',
|
|
1203
|
-
disabled: busy,
|
|
1204
|
-
onClick: () => void send({ op: 'reset' }),
|
|
1205
|
-
},
|
|
1206
|
-
'全部恢复默认',
|
|
1207
|
-
),
|
|
1208
|
-
),
|
|
1209
|
-
React.createElement(
|
|
1210
|
-
'div',
|
|
1211
|
-
{ className: 'dsh-sessionlog-desc' },
|
|
1212
|
-
'设置文件:' + String(snapshot.storePath ?? ''),
|
|
1213
|
-
),
|
|
1214
|
-
),
|
|
1215
|
-
status.kind === 'error' && status.message !== ''
|
|
1216
|
-
? React.createElement('div', { className: 'dsh-sessionlog-error' }, status.message)
|
|
1217
|
-
: null,
|
|
1218
|
-
)
|
|
1219
|
-
}
|
|
1220
858
|
|
|
1221
859
|
ensureStyles()
|
|
1222
860
|
|
|
@@ -1225,11 +863,6 @@ window.__ModuleLoader__.load({
|
|
|
1225
863
|
// `<style>` element of its own and reloads stay duplicate-free.
|
|
1226
864
|
ensureNoticeStyles()
|
|
1227
865
|
|
|
1228
|
-
// The 会话日志 page's stylesheet follows the same rule and MUST be appended
|
|
1229
|
-
// last: the shell's own stylesheet checks read `head.children[0]`/`[1]` as
|
|
1230
|
-
// "workspace+ then notice", so a new sheet only ever goes on the end.
|
|
1231
|
-
ensureSessionlogStyles()
|
|
1232
|
-
|
|
1233
866
|
// -----------------------------------------------------------------------
|
|
1234
867
|
// The workspace-directory feature
|
|
1235
868
|
// -----------------------------------------------------------------------
|
|
@@ -2882,29 +2515,6 @@ window.__ModuleLoader__.load({
|
|
|
2882
2515
|
),
|
|
2883
2516
|
)
|
|
2884
2517
|
|
|
2885
|
-
// The 会话日志 sub-feature's settings page. Same guard as the notice page:
|
|
2886
|
-
// the browser half always loads with the bundle, so the page has to survive
|
|
2887
|
-
// its host row being switched off (the host then answers 404 and the page
|
|
2888
|
-
// renders that as a state), and a registration failure must not take the
|
|
2889
|
-
// rest of this bundle down with it.
|
|
2890
|
-
registerNotice(ctx, '会话日志设置页', () =>
|
|
2891
|
-
ctx.effect(
|
|
2892
|
-
() =>
|
|
2893
|
-
ctx.slots.inject('settings.section', () =>
|
|
2894
|
-
ctx.slots.register(
|
|
2895
|
-
{
|
|
2896
|
-
name: 'settings.section',
|
|
2897
|
-
id: SESSIONLOG_SETTINGS_SECTION_ID,
|
|
2898
|
-
order: SESSIONLOG_SETTINGS_SECTION_ORDER,
|
|
2899
|
-
label: SESSIONLOG_SETTINGS_SECTION_LABEL,
|
|
2900
|
-
},
|
|
2901
|
-
SessionlogSettings,
|
|
2902
|
-
),
|
|
2903
|
-
),
|
|
2904
|
-
'dsh-workspace-plus/sessionlog: settings section',
|
|
2905
|
-
),
|
|
2906
|
-
)
|
|
2907
|
-
|
|
2908
2518
|
// The dialog lives in the frame-wide overlay: the same seat the shell's own
|
|
2909
2519
|
// workspace Rename/Delete dialogs occupy.
|
|
2910
2520
|
ctx.effect(
|
|
@@ -3048,17 +2658,6 @@ window.__ModuleLoader__.load({
|
|
|
3048
2658
|
exports.NOTICE_PENDING_PATH = NOTICE_PENDING_PATH
|
|
3049
2659
|
exports.NOTICE_POLL_INTERVAL_MS = NOTICE_POLL_INTERVAL_MS
|
|
3050
2660
|
exports.NOTICE_POLL_RETRY_MS = NOTICE_POLL_RETRY_MS
|
|
3051
|
-
exports.SESSIONLOG_SETTINGS_SECTION_ID = SESSIONLOG_SETTINGS_SECTION_ID
|
|
3052
|
-
exports.SESSIONLOG_SETTINGS_SECTION_LABEL = SESSIONLOG_SETTINGS_SECTION_LABEL
|
|
3053
|
-
exports.SESSIONLOG_SETTINGS_SECTION_ORDER = SESSIONLOG_SETTINGS_SECTION_ORDER
|
|
3054
|
-
exports.SESSIONLOG_API_PATH = SESSIONLOG_API_PATH
|
|
3055
|
-
exports.SESSIONLOG_STYLE_ID = SESSIONLOG_STYLE_ID
|
|
3056
|
-
exports.ensureSessionlogStyles = ensureSessionlogStyles
|
|
3057
|
-
exports.fetchSessionlogSnapshot = fetchSessionlogSnapshot
|
|
3058
|
-
exports.postSessionlogOperation = postSessionlogOperation
|
|
3059
|
-
exports.SessionlogSwitchRow = SessionlogSwitchRow
|
|
3060
|
-
exports.SessionlogSettings = SessionlogSettings
|
|
3061
|
-
exports.sessionlogRequestFieldLine = sessionlogRequestFieldLine
|
|
3062
2661
|
exports.fetchNoticeSnapshot = fetchNoticeSnapshot
|
|
3063
2662
|
exports.postNoticeOperation = postNoticeOperation
|
|
3064
2663
|
exports.NoticeSwitchRow = NoticeSwitchRow
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-workspace-plus",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "DeepSeek Harness plugin with
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"description": "DeepSeek Harness plugin with two switchable sub-features: workspace+ (label several directories inside one workspace and reference them by absolute path — the @ composer source, the row button, the workspace_dirs tool) and 通知/notice (Windows native notifications for running conversations, merged from dsh-notice). Each sub-feature is its own loader row and can be toggled on the plugin page.",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -21,12 +21,7 @@
|
|
|
21
21
|
"default": "./lib/notice/index.js"
|
|
22
22
|
},
|
|
23
23
|
"./notice/package.json": "./lib/notice/package.json",
|
|
24
|
-
"./notice/locale/*.json": "./lib/notice/locale/*.json"
|
|
25
|
-
"./sessionlog": {
|
|
26
|
-
"default": "./lib/sessionlog/index.js"
|
|
27
|
-
},
|
|
28
|
-
"./sessionlog/package.json": "./lib/sessionlog/package.json",
|
|
29
|
-
"./sessionlog/locale/*.json": "./lib/sessionlog/locale/*.json"
|
|
24
|
+
"./notice/locale/*.json": "./lib/notice/locale/*.json"
|
|
30
25
|
},
|
|
31
26
|
"dsh": {
|
|
32
27
|
"manifestVersion": 1,
|
|
@@ -63,9 +58,6 @@
|
|
|
63
58
|
"context-injection",
|
|
64
59
|
"notification",
|
|
65
60
|
"toast",
|
|
66
|
-
"session-log",
|
|
67
|
-
"telemetry",
|
|
68
|
-
"otlp",
|
|
69
61
|
"windows"
|
|
70
62
|
],
|
|
71
63
|
"author": "Sen70s",
|
package/lib/sessionlog/icon.svg
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="dsh-sessionlog">
|
|
2
|
-
<!-- A log document with an arrow leaving the machine: the plugin's whole job
|
|
3
|
-
is handing the canonical session log to DeepSeek. -->
|
|
4
|
-
<defs>
|
|
5
|
-
<linearGradient id="page" x1="0" y1="0" x2="0" y2="1">
|
|
6
|
-
<stop offset="0" stop-color="#4d8dff"/>
|
|
7
|
-
<stop offset="1" stop-color="#2470e8"/>
|
|
8
|
-
</linearGradient>
|
|
9
|
-
</defs>
|
|
10
|
-
<rect width="64" height="64" rx="14" fill="#0f1420"/>
|
|
11
|
-
<path d="M18 11h18l10 10v32a2 2 0 0 1-2 2H18a2 2 0 0 1-2-2V13a2 2 0 0 1 2-2Z" fill="url(#page)"/>
|
|
12
|
-
<path d="M36 11v8a2 2 0 0 0 2 2h8Z" fill="#8fb6ff"/>
|
|
13
|
-
<g fill="#0f1420" opacity=".62">
|
|
14
|
-
<rect x="21" y="26" width="16" height="2.6" rx="1.3"/>
|
|
15
|
-
<rect x="21" y="32" width="22" height="2.6" rx="1.3"/>
|
|
16
|
-
<rect x="21" y="38" width="12" height="2.6" rx="1.3"/>
|
|
17
|
-
</g>
|
|
18
|
-
<path d="M43 41.5h9.5m0 0-4-4m4 4-4 4" stroke="#39d98a" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
19
|
-
</svg>
|