dsh-workspace-plus 0.6.1 → 0.7.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/README.md +28 -4
- package/cordis.patch.yml +33 -1
- package/lib/client.js +448 -11
- package/lib/index.js +63 -6
- package/lib/notice/click-host.cs +515 -0
- package/lib/notice/index.js +114 -20
- package/lib/notice/launcher.js +455 -0
- package/lib/notice/win32.js +26 -2
- package/lib/sessionlog/icon.svg +19 -0
- package/lib/sessionlog/index.js +636 -0
- package/lib/sessionlog/locale/en.json +6 -0
- package/lib/sessionlog/locale/zh.json +6 -0
- package/lib/sessionlog/package.json +6 -0
- package/lib/sessionlog/redact.js +142 -0
- package/lib/sessionlog/settings.js +436 -0
- package/lib/sessionlog/wire.js +235 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
[](https://dsh-plugin.org/plugins/sen70s/dsh-workspace-plus)
|
|
5
5
|
[](https://github.com/Sen70s/dsh-workspace-plus/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
**当前版本 `v0.
|
|
7
|
+
**当前版本 `v0.7.0`** · 要求 DSH `>=0.1.7-rc.2`
|
|
8
8
|
|
|
9
|
-
DeepSeek Harness(DSH
|
|
9
|
+
DeepSeek Harness(DSH)的插件。一个插件,三个子功能,插件页里各占一行、可单独启停。
|
|
10
10
|
|
|
11
11
|
| 子功能 | 说明 |
|
|
12
12
|
| --- | --- |
|
|
13
13
|
| **workspace+** | 一个工作区登记多个目录,每个目录一个标签。标签表注入每次请求,输入框打 `@` 也能直接引用 |
|
|
14
14
|
| **通知 / notice** | 对话请求权限、询问用户、任务完成、中断、出错时弹 Windows 原生通知,点通知直接回到那个对话 |
|
|
15
|
+
| **会话日志 / sessionlog** | 把会话日志上传给 DeepSeek(帮助改进 DeepSeek 模型与产品)。**默认关闭**;开启后与使用哪个模型 API 无关 |
|
|
15
16
|
|
|
16
17
|
## 安装
|
|
17
18
|
|
|
@@ -25,6 +26,8 @@ dsh plugin --profile desktop add dsh-workspace-plus
|
|
|
25
26
|
|
|
26
27
|
> 自 0.6.0 起通知(notice)已并入本包。若你此前装过独立的 `dsh-notice` 插件,先从 profile 里移除它再装本包 —— 两者的路由、协议与设置存储完全相同,同装会同名冲突。
|
|
27
28
|
|
|
29
|
+
> 自 0.7.0 起会话日志上传(sessionlog)并入本包,作为第三行子功能,**默认关闭**。
|
|
30
|
+
|
|
28
31
|
## workspace+:一个工作区,多个目录
|
|
29
32
|
|
|
30
33
|
一个工作区(DSH 里 = 一个目录)装不下真实项目。前后端分两个仓库、文档在第三个目录时,把这三个目录登记成标签,agent 就不再需要你每次手写绝对路径。
|
|
@@ -58,10 +61,29 @@ agent 直接去 D:\proj\backend,不会因为旁边有个 backend-legacy 而改
|
|
|
58
61
|
|
|
59
62
|
装好即用。请求权限、询问用户、任务完成、中断、出错都会弹 toast,点一下就打开对应的对话。
|
|
60
63
|
|
|
64
|
+
正常情况下,点通知不会再先闪一个终端窗口。首次注册 `dsh-notice:` 协议时(插件加载后就会发生),宿主会用系统自带的 `csc.exe` 把一个小助手(约 10 KB 的 `click-host.exe`)编译到 `~/.dsh/notice/` 并缓存;之后点击由这个助手在无窗口的情况下完成,窗口被别的程序挡住、最小化甚至收进托盘,它都能恢复并切到前台(最大化状态保持不变)。编译不可用时自动退化为无窗口 PowerShell(改由 Harness 自己把窗口切到前台),再不行就退化为普通 PowerShell 兼容模式(可能有窗口闪烁);当前用的是哪一种,设置 → 通知 的状态区会直接写明。
|
|
65
|
+
|
|
61
66
|
设置 → 通知 里可以选通知类型、声音、时长、注册协议、发一条测试通知。这里还有一个「启用桌面通知」开关,不动插件页也能静音。
|
|
62
67
|
|
|
63
68
|
设置存在 `~/.dsh/notice/`,协议 scheme 是 `dsh-notice:` —— 从独立 dsh-notice 插件升级过来时,已有设置与协议注册自动生效。
|
|
64
69
|
|
|
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
|
+
|
|
65
87
|
## 自检
|
|
66
88
|
|
|
67
89
|
不需要安装、不需要 DSH 在跑:
|
|
@@ -70,12 +92,14 @@ agent 直接去 D:\proj\backend,不会因为旁边有个 backend-legacy 而改
|
|
|
70
92
|
node tools/verify.mjs
|
|
71
93
|
```
|
|
72
94
|
|
|
73
|
-
|
|
95
|
+
641 项断言,覆盖 manifest、patch 层、宿主半(含 `/workspaceplus/api` 在认证服务缺失时的拒绝)、浏览器半、工作区行按钮、`@` 候选源、弹窗形状、通知点击启动器的编译与回退(含真实 `csc.exe` 编译一次并自检),以及会话日志通道的设置归一化、线格式、发出前脱敏与两条上传路径。
|
|
74
96
|
|
|
75
97
|
## 其它
|
|
76
98
|
|
|
77
|
-
- `lib/`
|
|
99
|
+
- `lib/` 是手写源码,**不需要打包步骤**(没有 bundler):从 npm、GitHub、本地目录三种方式装到的都是可直接加载的代码。唯一的例外是通知点击助手 —— 它由宿主在首次注册协议跳转时(插件加载后就会发生)用系统自带的 `csc.exe` 从 `lib/notice/click-host.cs` 现编到 `~/.dsh/notice/` 并缓存;编译不可用时自动退化为 PowerShell 启动器,插件其余部分不受影响。
|
|
78
100
|
- workspace+ 的状态文件在 `~/.dsh/workspaceplus/directories.json`,可以直接手改,改完刷新页面即可。
|
|
101
|
+
- workspace+ 的浏览器接口 `/workspaceplus/api` 每次请求都要过宿主的 `connection.admit` 认证。认证服务缺失、`admit` 不存在或报错时一律 **503**,读写都不放行——这个接口写下的目录备注会进入模型上下文,不能让未认证的调用方有机会修改它。
|
|
102
|
+
- 会话日志的设置与水位在 `~/.dsh/sessionlog/settings.json`,同样可以直接手改。
|
|
79
103
|
- [LICENSE](./LICENSE) · MIT
|
|
80
104
|
|
|
81
105
|
## Star History
|
package/cordis.patch.yml
CHANGED
|
@@ -25,8 +25,18 @@
|
|
|
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
|
+
#
|
|
28
38
|
# Turning one row off removes exactly that half from the host while the bundle
|
|
29
|
-
# and the other sub-
|
|
39
|
+
# and the other sub-features keep working. The 通知 settings page also carries a
|
|
30
40
|
# runtime "启用桌面通知" switch, so notifications can be silenced without
|
|
31
41
|
# touching the row at all.
|
|
32
42
|
- insert:
|
|
@@ -51,3 +61,25 @@
|
|
|
51
61
|
# done: true # 任务完成
|
|
52
62
|
# aborted: true # 任务中断
|
|
53
63
|
# 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,6 +71,18 @@
|
|
|
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
|
+
*
|
|
74
86
|
* Only the shell's frozen platform module table may be `require`d here (react,
|
|
75
87
|
* react-dom, react/jsx-runtime, @deepseek-ai/cordis,
|
|
76
88
|
* @deepseek-ai/dsh-client-store, @deepseek-ai/dsh-client-ui-slots,
|
|
@@ -388,8 +400,36 @@ window.__ModuleLoader__.load({
|
|
|
388
400
|
} AUMID 发言:${toast.appId ?? '(未知)'}`
|
|
389
401
|
})()
|
|
390
402
|
|
|
403
|
+
// How a click is actually delivered, as the host reports it after
|
|
404
|
+
// registering the protocol. The host prefers a compiled windowless
|
|
405
|
+
// helper and falls back to windowless PowerShell, then to plain
|
|
406
|
+
// PowerShell, so the status line names the mode instead of promising the
|
|
407
|
+
// best case. The helper restores and focuses the window itself, but only
|
|
408
|
+
// when `focusOnClick` is on — with the switch off the host registers the
|
|
409
|
+
// same helper without that step — so the sentence must not claim a
|
|
410
|
+
// foreground window in that state. A host that predates `launcher` has no
|
|
411
|
+
// value here, and then this returns '' so the sentence reads exactly as
|
|
412
|
+
// it did before.
|
|
413
|
+
const launcherNote = (() => {
|
|
414
|
+
const focus = settings.focusOnClick === true
|
|
415
|
+
if (protocol.launcher === 'helper') {
|
|
416
|
+
return focus ? '点击助手会在无窗口的情况下把 Harness 切到前台' : '由无窗口的点击助手处理,不改变窗口的前后顺序'
|
|
417
|
+
}
|
|
418
|
+
if (protocol.launcher === 'windowless-powershell') {
|
|
419
|
+
return focus
|
|
420
|
+
? '使用无窗口 PowerShell 助手,由 Harness 负责切到前台;首次点击可能稍慢'
|
|
421
|
+
: '使用无窗口 PowerShell 助手;首次点击可能稍慢'
|
|
422
|
+
}
|
|
423
|
+
if (protocol.launcher === 'powershell') return '兼容模式:由 PowerShell 启动,可能有窗口闪烁'
|
|
424
|
+
return ''
|
|
425
|
+
})()
|
|
426
|
+
|
|
391
427
|
const protocolLine = (() => {
|
|
392
|
-
if (protocol.registered === true)
|
|
428
|
+
if (protocol.registered === true) {
|
|
429
|
+
return launcherNote
|
|
430
|
+
? `点击通知可打开对应对话(已注册 ${protocol.scheme}:,${launcherNote})。`
|
|
431
|
+
: `点击通知可打开对应对话(已注册 ${protocol.scheme}:)。`
|
|
432
|
+
}
|
|
393
433
|
if (settings.registerProtocol === false) return '已关闭协议注册:点击通知只会把 Harness 窗口切到前台。'
|
|
394
434
|
return `尚未注册 ${protocol.scheme ?? 'dsh-notice'}:,点击通知只会把 Harness 窗口切到前台。`
|
|
395
435
|
})()
|
|
@@ -434,7 +474,7 @@ window.__ModuleLoader__.load({
|
|
|
434
474
|
}),
|
|
435
475
|
React.createElement(NoticeSwitchRow, {
|
|
436
476
|
title: '点击时把 Harness 窗口切到前台',
|
|
437
|
-
description: '
|
|
477
|
+
description: '开启后点击通知会尽量把窗口从最小化、被遮挡或收进托盘的状态恢复并切到前台;关闭则只切换对话,不改变窗口的前后顺序。',
|
|
438
478
|
checked: settings.focusOnClick === true,
|
|
439
479
|
disabled,
|
|
440
480
|
onChange: (next) => void send({ op: 'patch', patch: { focusOnClick: next } }),
|
|
@@ -827,6 +867,357 @@ window.__ModuleLoader__.load({
|
|
|
827
867
|
document.head.appendChild(tag)
|
|
828
868
|
}
|
|
829
869
|
|
|
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
|
+
|
|
830
1221
|
ensureStyles()
|
|
831
1222
|
|
|
832
1223
|
// The notice page's stylesheet follows the same one-tag rule; calling it in
|
|
@@ -834,6 +1225,11 @@ window.__ModuleLoader__.load({
|
|
|
834
1225
|
// `<style>` element of its own and reloads stay duplicate-free.
|
|
835
1226
|
ensureNoticeStyles()
|
|
836
1227
|
|
|
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
|
+
|
|
837
1233
|
// -----------------------------------------------------------------------
|
|
838
1234
|
// The workspace-directory feature
|
|
839
1235
|
// -----------------------------------------------------------------------
|
|
@@ -1871,11 +2267,16 @@ window.__ModuleLoader__.load({
|
|
|
1871
2267
|
const bodyBlocks = (() => {
|
|
1872
2268
|
if (state.status === 'error') {
|
|
1873
2269
|
const unauthorized = state.message.includes('401')
|
|
2270
|
+
const unavailable = state.message.includes('503')
|
|
1874
2271
|
const unrouted = state.message.includes('404')
|
|
1875
2272
|
/**
|
|
1876
|
-
* The
|
|
2273
|
+
* The four failures need different advice:
|
|
1877
2274
|
* - 401: this document never completed the token exchange, so the
|
|
1878
2275
|
* connection layer has no authority to admit the request.
|
|
2276
|
+
* - 503: the Host has no usable authentication service, so the route
|
|
2277
|
+
* refused the request instead of accepting an unauthenticated
|
|
2278
|
+
* change. Restarting is the ordinary fix; the route stays shut until
|
|
2279
|
+
* the connection service is mounted.
|
|
1879
2280
|
* - 404: the request reached the Host and nothing claimed the path.
|
|
1880
2281
|
* The browser half hot-reloads from the served bundle, the host half
|
|
1881
2282
|
* does not, so a stale Host process is the ordinary cause.
|
|
@@ -1883,14 +2284,16 @@ window.__ModuleLoader__.load({
|
|
|
1883
2284
|
*/
|
|
1884
2285
|
const diagnosis = unauthorized
|
|
1885
2286
|
? '这个页面没有带授权的会话凭据。请用 `dsh` 启动时打印的带 token 的地址重新打开 GUI(它会写入 cookie,然后跳转到干净地址)。'
|
|
1886
|
-
:
|
|
1887
|
-
? '
|
|
1888
|
-
|
|
1889
|
-
'
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
'
|
|
2287
|
+
: unavailable
|
|
2288
|
+
? '宿主进程没有可用的认证服务,接口按安全默认拒绝了这次请求(503)。重启 dsh 通常即可恢复;如果一直出现,说明这个宿主没有挂载 connection 认证服务,这个接口在它上面不会放行任何读写。'
|
|
2289
|
+
: unrouted
|
|
2290
|
+
? '请求到达了 DSH 宿主进程,但宿主里没有注册 ' +
|
|
2291
|
+
API_PATH +
|
|
2292
|
+
' 这条路由——通常意味着宿主进程还在跑旧版插件代码。浏览器半会随 bundle 热更新,宿主半不会:重启 dsh 即可。' +
|
|
2293
|
+
'重启后终端会出现 [dsh-workspace-plus] host half loaded 一行;若没有,请到 设置 → 插件 查看该插件行的状态。'
|
|
2294
|
+
: '这个面板通过 ' +
|
|
2295
|
+
API_PATH +
|
|
2296
|
+
' 与宿主进程通信,而当前页面没有可用的 HTTP 通道(例如页面不是 dsh 提供的、或宿主进程还没起来)。'
|
|
1894
2297
|
return [
|
|
1895
2298
|
noticeBlock(
|
|
1896
2299
|
'error',
|
|
@@ -2479,6 +2882,29 @@ window.__ModuleLoader__.load({
|
|
|
2479
2882
|
),
|
|
2480
2883
|
)
|
|
2481
2884
|
|
|
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
|
+
|
|
2482
2908
|
// The dialog lives in the frame-wide overlay: the same seat the shell's own
|
|
2483
2909
|
// workspace Rename/Delete dialogs occupy.
|
|
2484
2910
|
ctx.effect(
|
|
@@ -2622,6 +3048,17 @@ window.__ModuleLoader__.load({
|
|
|
2622
3048
|
exports.NOTICE_PENDING_PATH = NOTICE_PENDING_PATH
|
|
2623
3049
|
exports.NOTICE_POLL_INTERVAL_MS = NOTICE_POLL_INTERVAL_MS
|
|
2624
3050
|
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
|
|
2625
3062
|
exports.fetchNoticeSnapshot = fetchNoticeSnapshot
|
|
2626
3063
|
exports.postNoticeOperation = postNoticeOperation
|
|
2627
3064
|
exports.NoticeSwitchRow = NoticeSwitchRow
|