dsh-agent-office 0.0.0-stage → 4.1.8

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/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mayc0202
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ 本仓库打包进 lib/panel.html 的 Three.js 代码按其自身 MIT 许可分发,版权归
24
+ three.js authors,详见 NOTICE。
package/NOTICE ADDED
@@ -0,0 +1,28 @@
1
+ # NOTICE
2
+
3
+ ## 打包产物里的第三方代码
4
+
5
+ `lib/panel.html` 内联的 JS 里包含 **Three.js**(r171)的代码:
6
+
7
+ - 版权:Copyright © 2010-2024 three.js authors
8
+ - 许可:MIT
9
+ - 仓库:https://github.com/mrdoob/three.js
10
+
11
+ Three.js 以 MIT 许可随本仓库一起分发;其许可证文本见 https://github.com/mrdoob/three.js/blob/dev/LICENSE
12
+
13
+ 其余代码(插件宿主、数据层、客户端、3D 面板源码 `panel-src/`)均为本仓库作者原创。
14
+
15
+
16
+ ## 角色图片与 Catora 标识
17
+
18
+ Catora 角色形象、`panel-src/assets/exp-*.png` 表情素材以及 “Catora” 名称/标识
19
+ 均由本项目作者 **mayc0202** 原创并所有,商标注册申请已提交、审核中(™)。
20
+
21
+ - 这些素材随本插件一起分发,但**不纳入**本仓库的 MIT 代码许可范围;
22
+ - 未经作者书面许可,不得将 Catora 角色形象或名称单独抽取、再分发或用于其他产品;
23
+ - 在商标注册完成前,请使用 “Catora™(申请中)” 的表述,不要标注 ®。
24
+
25
+ ## 非官方声明
26
+
27
+ 本项目是第三方插件,与 DeepSeek 官方无隶属、合作或背书关系。
28
+ “DeepSeek”“DeepSeek Harness”“DSH”仅用于描述兼容对象。
package/README.md CHANGED
@@ -1,3 +1,170 @@
1
- # Temporary Holding Version
1
+ # 智能体办公室 · DSH 插件版
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > 非官方第三方插件,与 DeepSeek 官方无隶属或背书关系。
4
+
5
+ 把 DeepSeek Harness 的全部会话变成一间实时 3D 办公室:右下角一个猫头按钮,点开就是全屏面板。
6
+
7
+ 这也是 **Catora** 产品在 DSH 里的第一块拼图:Catora 正式发布后,会把这套「会话可视化 + 知识工作流」抽离成 MCP 服务,让用户通过 DeepSeek Harness 直接使用 Catora 的笔记、思维导图、私人知识库与 RAG 检索能力。
8
+ 3D 场景用 **Three.js** 自绘(楼层、工位、Agent 小人、霓虹灯牌、动态状态动画),不依赖第三方页面模板,也不依赖 ntfy 或常驻外部进程。
9
+
10
+ ![全局视角](screenshot.png)
11
+
12
+ ![单层聚焦](screenshot-focus.png)
13
+
14
+ ## 特性
15
+
16
+ - 🎨 **夜景 3D 办公室**:透视镜头、楼层按活跃工作区自动增加;每层有沙发、咖啡吧、大屏、玻璃机房、彩色货架;
17
+ - 🌃 **昼夜切换**:跟随系统时间自动切换(06:00–18:00 白天,其余夜晚),左上角月亮/太阳图标显示当前时段;场景灯光、天空、城市、霓虹与曝光平滑过渡;
18
+ - 🏙️ **写字楼夜景**:程序化高低错落的写字楼(亮窗/楼顶信标/霓虹竖条);背墙为透明玻璃;
19
+ - 🔧 **机房 LED**:机柜指示灯持续闪烁;货架固定在楼层内;
20
+ - 📟 **工作区大屏**:每层墙面大屏显示该工作区(团队)的本周 TOKEN、今日 TOKEN、会话数和占比条,随轮询实时更新;
21
+ - 💳 **DeepSeek 余额卡**:读取本机 DSH 登录凭证,查询平台充值钱包余额与赠金(只展示币种/金额,token 不出本机进程);
22
+ - 🖥️ **显示器状态**:休息/完成时黑屏待机;工作中显示代码编辑器(滚动行 + 光标);工作超过 5 分钟或卡住时显示红色「HIGH LOAD / 执行任务时间过长」告警;
23
+ - 🐱 **猫猫 Agent(Catora 主题)**:用 catora-cat 的 logo / 表情差分 PNG 做成平面贴图立牌(Three.js Sprite),**永远面向相机**,颜色按 Agent 调色;空闲时用 sleepy 表情 + 飘「Z」睡觉,其他状态用 happy/surprised/dizzy/loved;猫咪用 GSAP 做上下浮动动画,座垫上带随浮动变化的软阴影;
24
+ - 🖼️ **自定义猫咪图片**:DSH 设置 →「智能体办公室」上传图片即可替换所有 Agent 头像(本地 localStorage 保存,不上传服务器);
25
+
26
+ - 🏢 **3D 实时办公室**:Three.js 自绘的错层办公室——每层有工位、显示器、会议桌、白板、服务器机柜、玻璃护栏和吊灯;每个会话是一个简洁的立方体 Agent(按状态做呼吸/浮动脉冲,工作/等待/卡住/待机各有反馈);
27
+ - 🖱️ **楼层聚焦**:左键点任意楼层(或左侧楼层列表)→ 只显示并看清这一层;右键 /「全局视角」按钮 / `Esc` 回到整栋楼的全局视角;
28
+ - 📡 **真实数据**:状态、今日执行次数、本周/今日 **Token 用量** 全部来自本机会话日志,数字不编造;
29
+ - 🖱️ **可交互**:拖动旋转、滚轮缩放、点击立方体或名字看详情、详情卡 ×/Esc 关闭;
30
+ - 🧩 **原生插件**:bundle 插件,装完即用(宿主路由 + 浏览器半侧悬浮按钮),无外部依赖;
31
+ - 🔒 **只读本机**:只读 `$DSH_HOME/sessions`,不联网、不上传,没有频道名/密钥要配。
32
+
33
+ ## 安装
34
+
35
+ ### 方式 A:从 npm 安装(推荐)
36
+
37
+ 包名 `dsh-agent-office` 发布后,在 DSH 插件页搜索 **dsh-agent-office**,或命令行:
38
+
39
+ ```bash
40
+ DSH="/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh"
41
+ "$DSH" plugin --profile desktop add dsh-agent-office
42
+ ```
43
+
44
+ ### 方式 B:从 GitHub 安装
45
+
46
+ DSH 侧边栏 → **插件** → 安装组合包,填:
47
+
48
+ ```
49
+ github:mayc0202/dsh-agent-office
50
+ ```
51
+
52
+ 或命令行:
53
+
54
+ ```bash
55
+ DSH="/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh"
56
+ "$DSH" plugin --profile desktop add github:mayc0202/dsh-agent-office
57
+ ```
58
+
59
+ 安装后在「插件」页勾选 **智能体办公室**(或把 `dsh-agent-office` 追加到
60
+ `~/.dsh/profiles/desktop/package.json` 的 `dsh.profile.bundles`),重启 DSH 生效。
61
+
62
+ ### 方式 C:本地目录
63
+
64
+ ```bash
65
+ git clone https://github.com/mayc0202/dsh-agent-office
66
+ "$DSH" plugin --profile desktop add link:"$(pwd)/dsh-agent-office"
67
+ ```
68
+
69
+ ### 安装后
70
+
71
+ - 右下角出现猫头按钮,点开全屏面板,`Esc` 关闭;
72
+ - 或直接访问 `http://127.0.0.1:<DSH端口>/agent-office/`。
73
+
74
+ ## 先预览(不安装)
75
+
76
+ ```bash
77
+ npm install # 安装 three / esbuild(仅构建/预览需要)
78
+ node dev/dev-server.mjs
79
+ # 打开 http://127.0.0.1:8899/agent-office/
80
+ ```
81
+
82
+ `dev/dev-server.mjs` 只起一个最小 HTTP 服务(同一份面板 + 同款 API),用于验证页面与数据。
83
+
84
+ ![悬浮按钮 + 全屏面板](screenshot-client.png)
85
+
86
+ ## 工作原理
87
+
88
+ | 半侧 | 文件 | 作用 |
89
+ | --- | --- | --- |
90
+ | 宿主 | `lib/index.js` | 在 DSH 的 Web 服务器上挂 `GET /agent-office/`(页面)和 `GET /agent-office/api/state`(数据) |
91
+ | 数据 | `lib/state.js` | 读 `~/.dsh/sessions/<workspace>/<session>/session.v4.jsonl.zstd`(多帧 zstd 逐帧解压,按 mtime 缓存);从 `turn/start|end`、`tool/call`、`assistant/message.usage`、`approval/asked|decided`、`ask_user_question` 推导状态与用量 |
92
+ | 客户端 | `lib/client.js` | 右下角「Catora」悬浮按钮 + 全屏 iframe 覆盖层,可「在新标签打开」 |
93
+ | 3D 页面 | `lib/panel.html` | 单文件构建产物(Three.js 场景 + HUD),同源轮询 API |
94
+ | 页面源码 | `panel-src/` + `panel/build.mjs` | 可读源码 + esbuild 打包脚本 |
95
+
96
+ 数据口径:
97
+
98
+ - 名册:最近 `activeWindowHours`(默认 24 小时)内活跃的会话,最多 `maxAgents`(默认 7)个,名字取会话标题;
99
+ - 楼层:会话所在工作区(`Harness` / `Catora` / 其他);
100
+ - 今日执行:当天 `turn/start` 次数;
101
+ - 本周占比:该会话本周(周一至今)Token 占全部展示会话的百分比;
102
+ - 今日/本周 Token:`assistant/message` 里 `usage.totalTokens` 的真实累计。
103
+
104
+ ## 配置
105
+
106
+ 在 profile 的 `cordis.patch.yml` 里加 `config`(或改本包 patch):
107
+
108
+ ```yaml
109
+ - insert:
110
+ - id: agent-office
111
+ name: dsh-agent-office
112
+ config:
113
+ maxAgents: 7 # 名册最多显示几个会话
114
+ activeWindowHours: 24 # 只统计最近多少小时内活跃的会话
115
+ sessionsDir: null # 默认 $DSH_HOME/sessions
116
+ ```
117
+
118
+ ## 开发构建
119
+
120
+ ```bash
121
+ npm install
122
+ node panel/build.mjs # panel-src/ → lib/panel.html(单文件)
123
+ node dev/dev-server.mjs # 本地预览
124
+ node --check lib/index.js lib/state.js lib/client.js
125
+ ```
126
+
127
+ ## 交互
128
+
129
+ | 操作 | 结果 |
130
+ | --- | --- |
131
+ | 左键点楼层 / 左侧「楼层」 | 聚焦该层,只显示这一层 |
132
+ | 右键 / 顶栏「全局视角」/ `Esc` | 回到整栋楼全局视角 |
133
+ | 拖动 / 滚轮 | 旋转 / 缩放(聚焦时也可以) |
134
+ | 点机器人或名字标签 / 左侧榜单 | 打开该会话详情卡 |
135
+ | `Esc` | 有详情卡时先关卡片,再按一次回全局 |
136
+
137
+ ## 已知限制
138
+
139
+ - 只读本机会话日志;日志是追加写的多帧 zstd,正在写入时最后一帧可能不完整(会跳过不完整部分);
140
+ - 只展示最近活跃的会话,更早的会话不会进入名册;
141
+ - 3D 场景按最多 4 个团队楼层设计。
142
+
143
+ ## 许可
144
+
145
+ MIT(见 `LICENSE`)。打包产物内联了 Three.js(MIT,© three.js authors),见 `NOTICE`。
146
+
147
+ ---
148
+
149
+ ## English
150
+
151
+ **Agent Office** is a DSH plugin that turns every DeepSeek Harness session into a live 3D office built with Three.js.
152
+
153
+ - Install: `dsh plugin --profile desktop add github:mayc0202/dsh-agent-office`, enable **Agent Office** in the plugin manager, restart DSH. A floating cat-head button appears in the bottom-right corner; clicking it opens `/agent-office/`.
154
+ - Data: reads `$DSH_HOME/sessions/**/session.v4.jsonl.zstd` (read-only, no network). Status, turn counts and token usage are derived from `turn/*`, `tool/call`, `assistant/message.usage` and approval events.
155
+ - Preview: `npm install && node dev/dev-server.mjs` → http://127.0.0.1:8899/agent-office/
156
+ - License: MIT; bundled Three.js is MIT (see `NOTICE`).
157
+
158
+
159
+ ## 版权与声明
160
+
161
+ - 本插件是**非官方第三方插件**,与 DeepSeek 官方无隶属或背书关系;`DeepSeek` / `DSH` 仅用于说明兼容对象。
162
+ - 插件代码(宿主、数据层、客户端、3D 面板源码)为本仓库作者原创,按 MIT 授权。
163
+ - **Catora 角色形象、猫咪表情素材和 “Catora” 名称/标识均为作者 mayc0202 原创并所有**,商标注册申请已提交、审核中(™)。这些素材随插件分发,但不纳入 MIT 代码许可;未经书面许可,不得单独抽取再分发或用于其他产品。详见 `NOTICE`。
164
+
165
+
166
+ ## 路线
167
+
168
+ - **当前**:3D 智能体办公室,实时展示 DSH 会话状态、Token 用量与团队楼层。
169
+ - **Catora 发布后**:抽离为 MCP 服务,接入 Catora 的笔记、思维导图、私人知识库与 RAG 检索。
170
+ - **目标**:在 DeepSeek Harness 内完成「观察 Agent → 沉淀知识 → 检索复用」的闭环。
@@ -0,0 +1,4 @@
1
+ # 智能体办公室插件注册
2
+ - insert:
3
+ - id: agent-office
4
+ name: dsh-agent-office
package/icon.png ADDED
Binary file
package/icon.svg ADDED
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2
+ <defs>
3
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#FF8A5C"/>
5
+ <stop offset="1" stop-color="#FF5E30"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect x="2" y="2" width="60" height="60" rx="14" fill="#0E0E10" stroke="#2A2A30" stroke-width="2"/>
9
+ <path d="M14 46 L32 14 L50 46 Z" fill="none" stroke="url(#g)" stroke-width="3" stroke-linejoin="round"/>
10
+ <circle cx="32" cy="38" r="4.5" fill="#FFD2BF"/>
11
+ <path d="M20 52 h24" stroke="#5A5A64" stroke-width="2" stroke-linecap="round"/>
12
+ </svg>
package/lib/client.js ADDED
@@ -0,0 +1,217 @@
1
+ /**
2
+ * 智能体办公室 —— DSH 客户端半侧。
3
+ *
4
+ * 在页面右下角放一个猫头悬浮按钮,点击打开全屏 iframe(/agent-office/)。
5
+ * 同时在 DSH 的「设置」里注册一个「智能体办公室」设置分区,用来上传自定义猫咪图片。
6
+ * 纯 DOM + React 插槽,不依赖宿主 side 的额外接口。
7
+ */
8
+ const CUSTOM_FACE_KEY = 'agent-office-custom-face'
9
+ const CAT_BUTTON = 'data:image/png;base64,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'
10
+
11
+ window.__ModuleLoader__.load({
12
+ id: 'dsh-agent-office',
13
+ factory: require => {
14
+ const module = { exports: {} }
15
+ const react = (() => {
16
+ try { return require('react') } catch { return null }
17
+ })()
18
+ const jsx = (() => {
19
+ try { return require('react/jsx-runtime').jsx } catch { return null }
20
+ })()
21
+
22
+ function resizeImage(file) {
23
+ return new Promise((resolve, reject) => {
24
+ const url = URL.createObjectURL(file)
25
+ const image = new Image()
26
+ image.onload = () => {
27
+ try {
28
+ const size = 256
29
+ const canvas = document.createElement('canvas')
30
+ canvas.width = size
31
+ canvas.height = size
32
+ const ctx = canvas.getContext('2d')
33
+ const scale = Math.min(size / image.width, size / image.height)
34
+ const w = Math.max(1, Math.round(image.width * scale))
35
+ const h = Math.max(1, Math.round(image.height * scale))
36
+ ctx.clearRect(0, 0, size, size)
37
+ ctx.drawImage(image, (size - w) / 2, (size - h) / 2, w, h)
38
+ resolve(canvas.toDataURL('image/png'))
39
+ } catch (error) {
40
+ reject(error)
41
+ } finally {
42
+ URL.revokeObjectURL(url)
43
+ }
44
+ }
45
+ image.onerror = () => {
46
+ URL.revokeObjectURL(url)
47
+ reject(new Error('图片读取失败'))
48
+ }
49
+ image.src = url
50
+ })
51
+ }
52
+
53
+ function makeSettingsSection(react, jsx) {
54
+ const { useState } = react
55
+ const readCustom = () => {
56
+ try { return localStorage.getItem(CUSTOM_FACE_KEY) ?? '' } catch { return '' }
57
+ }
58
+ return function NightShiftSettings() {
59
+ const [preview, setPreview] = useState(readCustom)
60
+ const [message, setMessage] = useState('')
61
+ const [busy, setBusy] = useState(false)
62
+
63
+ const choose = async event => {
64
+ const file = event.target.files?.[0]
65
+ if (!file) return
66
+ if (file.size > 6 * 1024 * 1024) {
67
+ setMessage('图片太大,请选择 6MB 以内的图片')
68
+ return
69
+ }
70
+ setBusy(true)
71
+ setMessage('正在处理图片…')
72
+ try {
73
+ const data = await resizeImage(file)
74
+ localStorage.setItem(CUSTOM_FACE_KEY, data)
75
+ setPreview(data)
76
+ setMessage('已应用,刷新页面或重新打开面板后生效')
77
+ } catch (error) {
78
+ setMessage('图片处理失败:' + String(error?.message ?? error))
79
+ } finally {
80
+ setBusy(false)
81
+ event.target.value = ''
82
+ }
83
+ }
84
+
85
+ const reset = () => {
86
+ localStorage.removeItem(CUSTOM_FACE_KEY)
87
+ setPreview('')
88
+ setMessage('已恢复默认 Catora 猫咪')
89
+ }
90
+
91
+ const titleStyle = { fontSize: 15, fontWeight: 600, margin: '0 0 6px' }
92
+ const textStyle = { fontSize: 13, lineHeight: 1.6, margin: '0 0 10px', opacity: 0.72 }
93
+ const buttonStyle = { height: 32, padding: '0 14px', borderRadius: 8, border: '1px solid rgba(127,127,127,.35)', background: 'transparent', color: 'inherit', cursor: 'pointer', fontSize: 13 }
94
+
95
+ return jsx('div', {
96
+ style: { padding: '4px 0', maxWidth: 520 },
97
+ children: [
98
+ jsx('h3', { style: titleStyle, children: '智能体办公室' }),
99
+ jsx('p', { style: textStyle, children: '上传一张图片,替换 3D 面板里所有 Agent 的猫咪头像(本地保存,不会上传到服务器)。' }),
100
+ preview ? jsx('img', {
101
+ src: preview,
102
+ alt: '自定义猫咪预览',
103
+ style: { width: 96, height: 96, objectFit: 'contain', borderRadius: 12, border: '1px solid rgba(127,127,127,.3)', background: 'rgba(127,127,127,.08)', display: 'block', marginBottom: 12 },
104
+ }) : null,
105
+ jsx('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } }, [
106
+ jsx('label', {
107
+ style: { ...buttonStyle, display: 'inline-flex', alignItems: 'center', opacity: busy ? 0.6 : 1 },
108
+ children: [
109
+ busy ? '处理中…' : '上传图片',
110
+ jsx('input', { type: 'file', accept: 'image/*', style: { display: 'none' }, onChange: choose, disabled: busy }),
111
+ ],
112
+ }),
113
+ jsx('button', { type: 'button', style: buttonStyle, onClick: reset, children: '恢复默认猫猫' }),
114
+ ]),
115
+ message ? jsx('p', { style: { ...textStyle, marginTop: 10, marginBottom: 0 }, children: message }) : null,
116
+ ],
117
+ })
118
+ }
119
+ }
120
+
121
+ module.exports.inject = ['slots']
122
+ module.exports.apply = ctx => {
123
+ for (const stale of document.querySelectorAll('.dsh-ns-host')) stale.remove()
124
+
125
+ const style = document.createElement('style')
126
+ style.textContent = `
127
+ .dsh-ns-host{position:fixed;z-index:1200;right:20px;bottom:20px;display:flex;flex-direction:column;align-items:flex-end;gap:10px;font-family:Inter,system-ui,-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;-webkit-app-region:no-drag}
128
+ .dsh-ns-pill{position:relative;display:grid;place-items:center;width:52px;height:52px;padding:0;border:0;background:transparent;box-shadow:none;cursor:pointer;transition:transform .15s ease}
129
+ .dsh-ns-pill:hover{transform:translateY(-2px) scale(1.04)}
130
+ .dsh-ns-pill img{width:46px;height:46px;object-fit:contain;display:block;pointer-events:none;filter:drop-shadow(0 6px 14px rgba(0,0,0,.55))}
131
+ .dsh-ns-eye{position:absolute;width:5px;height:11px;border-radius:50%;background:#0b0e13;pointer-events:none;left:35.8%;top:57.7%;transform:translate(calc(-50% + var(--ex,0px)), calc(-50% + var(--ey,0px)));transition:transform .08s linear}
132
+ .dsh-ns-eye.right{left:63.5%}
133
+ .dsh-ns-pill:focus-visible{outline:2px solid #3de1c6;outline-offset:4px;border-radius:50%}
134
+ .dsh-ns-overlay{position:fixed;z-index:1300;inset:0;background:#060607}
135
+ .dsh-ns-overlay[hidden]{display:none}
136
+ .dsh-ns-overlay iframe{display:block;width:100%;height:100%;border:0;background:#060607}
137
+ .dsh-ns-close{position:absolute;top:auto;bottom:18px;right:16px;z-index:2;width:34px;height:34px;border-radius:50%;border:1px solid rgba(255,255,255,.18);background:rgba(20,20,23,.86);color:#f2f0ea;font-size:18px;line-height:1;cursor:pointer;backdrop-filter:blur(8px)}
138
+ .dsh-ns-close:hover{background:rgba(40,40,45,.95)}
139
+ .dsh-ns-open{position:absolute;top:auto;bottom:18px;right:58px;z-index:2;height:34px;padding:0 12px;border-radius:999px;border:1px solid rgba(255,255,255,.18);background:rgba(20,20,23,.86);color:#f2f0ea;font-size:12px;font-weight:600;cursor:pointer;backdrop-filter:blur(8px)}
140
+ .dsh-ns-open:hover{background:rgba(40,40,45,.95)}
141
+ html[data-fullscreen] .dsh-ns-pill{bottom:12px}
142
+ `
143
+ document.head.append(style)
144
+
145
+ const host = document.createElement('div')
146
+ host.className = 'dsh-ns-host'
147
+ host.innerHTML = `
148
+ <button type="button" class="dsh-ns-pill" aria-label="打开智能体办公室" title="打开智能体办公室"><img alt="" /><span class="dsh-ns-eye left"></span><span class="dsh-ns-eye right"></span></button>
149
+ <section class="dsh-ns-overlay" hidden>
150
+ <button type="button" class="dsh-ns-open">在新标签打开</button>
151
+ <button type="button" class="dsh-ns-close" aria-label="关闭">×</button>
152
+ <iframe title="智能体办公室 3D 面板"></iframe>
153
+ </section>
154
+ `
155
+ document.body.append(host)
156
+
157
+ const pill = host.querySelector('.dsh-ns-pill')
158
+ const pillCat = pill.querySelector('img')
159
+ if (pillCat) pillCat.src = CAT_BUTTON
160
+ const eyes = [...pill.querySelectorAll('.dsh-ns-eye')]
161
+ const moveEyes = event => {
162
+ const rect = pill.getBoundingClientRect()
163
+ const cx = rect.left + rect.width / 2
164
+ const cy = rect.top + rect.height / 2
165
+ const dx = Math.max(-1, Math.min(1, (event.clientX - cx) / 140))
166
+ const dy = Math.max(-1, Math.min(1, (event.clientY - cy) / 140))
167
+ for (const eye of eyes) {
168
+ eye.style.setProperty('--ex', (dx * 3.0).toFixed(2) + 'px')
169
+ eye.style.setProperty('--ey', (dy * 2.2).toFixed(2) + 'px')
170
+ }
171
+ }
172
+ window.__NS_CAT_EYES_CLEANUP__?.()
173
+ window.__NS_CAT_EYES_CLEANUP__ = () => window.removeEventListener('pointermove', moveEyes)
174
+ window.addEventListener('pointermove', moveEyes, { passive: true })
175
+ const overlay = host.querySelector('.dsh-ns-overlay')
176
+ const frame = host.querySelector('iframe')
177
+ const closedByEscape = event => {
178
+ if (event.key === 'Escape') close()
179
+ }
180
+ const open = () => {
181
+ if (frame.getAttribute('src') !== '/agent-office/') frame.setAttribute('src', '/agent-office/')
182
+ overlay.hidden = false
183
+ document.addEventListener('keydown', closedByEscape, true)
184
+ }
185
+ const close = () => {
186
+ overlay.hidden = true
187
+ document.removeEventListener('keydown', closedByEscape, true)
188
+ }
189
+ pill.addEventListener('click', () => (overlay.hidden ? open() : close()))
190
+ host.querySelector('.dsh-ns-close').addEventListener('click', close)
191
+ host.querySelector('.dsh-ns-open').addEventListener('click', () => window.open('/agent-office/', '_blank', 'noopener'))
192
+
193
+ // 在 DSH 设置中注册「智能体办公室」分区:上传自定义猫咪图片
194
+ if (react && jsx && ctx?.slots?.inject) {
195
+ try {
196
+ const SettingsSection = makeSettingsSection(react, jsx)
197
+ ctx.slots.inject('settings.section', function* () {
198
+ yield ctx.slots.register({
199
+ name: 'settings.section',
200
+ id: 'agent-office',
201
+ order: 36,
202
+ label: () => '智能体办公室',
203
+ inject: () => ({}),
204
+ }, SettingsSection)
205
+ })
206
+ } catch (error) {
207
+ console.warn('[agent-office] 设置分区注册失败:' + String(error?.message ?? error))
208
+ }
209
+ }
210
+
211
+ // 注意:apply 不能返回 module.exports,cordis 会把返回值当作 effect,
212
+ // 非函数/可迭代对象会抛 “Invalid effect”,导致设置分区注册失败。
213
+ return undefined
214
+ }
215
+ return module.exports
216
+ },
217
+ })
package/lib/index.js ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * 智能体办公室 —— DSH 插件宿主半侧。
3
+ *
4
+ * 在 DSH 现有 Web 服务器上挂三条路由:
5
+ * GET /agent-office/ 3D 办公室页面(单文件面板)
6
+ * GET /agent-office/api/state 实时名册 / 事件 / Token 用量(读本机会话日志)
7
+ *
8
+ * 不依赖 ntfy、不依赖外部进程;面板与 DSH 同源,直接轮询本机 API。
9
+ */
10
+ import { readFile } from 'node:fs/promises'
11
+ import { createNightShiftStore, fetchDeepSeekBalance } from './state.js'
12
+
13
+ export const name = 'agent-office'
14
+ export const inject = ['webServer']
15
+
16
+ const PANEL = new URL('./panel.html', import.meta.url)
17
+
18
+ export function apply(ctx, config) {
19
+ const store = createNightShiftStore({
20
+ sessionsDir: config?.sessionsDir,
21
+ maxAgents: config?.maxAgents,
22
+ activeWindowHours: config?.activeWindowHours,
23
+ })
24
+
25
+ const send = (res, type, body, code = 200) => {
26
+ res.writeHead(code, {
27
+ 'content-type': type,
28
+ 'cache-control': 'no-store',
29
+ 'x-content-type-options': 'nosniff',
30
+ })
31
+ res.end(body)
32
+ }
33
+ const json = (res, payload, code = 200) => send(res, 'application/json; charset=utf-8', JSON.stringify(payload), code)
34
+
35
+ const trusted = new Set(['localhost', '127.0.0.1', ...[...(config?.trustedHosts ?? [])].map(host => String(host).trim().toLowerCase()).filter(Boolean)])
36
+ const hostTrusted = req => trusted.has(String(req.headers.host ?? '').replace(/:\d+$/, '').toLowerCase())
37
+
38
+ ctx.effect(() => ctx.webServer.register({
39
+ kind: 'exact',
40
+ path: '/agent-office',
41
+ handler: (_req, res) => {
42
+ res.writeHead(302, { location: '/agent-office/' })
43
+ res.end()
44
+ },
45
+ }), 'agent-office: redirect')
46
+
47
+ ctx.effect(() => ctx.webServer.register({
48
+ kind: 'exact',
49
+ path: '/agent-office/',
50
+ handler: async (_req, res) => {
51
+ try {
52
+ send(res, 'text/html; charset=utf-8', await readFile(PANEL, 'utf8'))
53
+ } catch (error) {
54
+ ctx.logger.error(error instanceof Error ? error : new Error(String(error)))
55
+ send(res, 'text/plain; charset=utf-8', '智能体办公室页面暂时不可用', 500)
56
+ }
57
+ },
58
+ }), 'agent-office: page')
59
+
60
+ ctx.effect(() => ctx.webServer.register({
61
+ kind: 'exact',
62
+ path: '/agent-office/api/state',
63
+ handler: (req, res) => {
64
+ if (!hostTrusted(req)) return json(res, { error: '不被信任的 Host' }, 403)
65
+ try {
66
+ return json(res, store.state())
67
+ } catch (error) {
68
+ ctx.logger.error(error instanceof Error ? error : new Error(String(error)))
69
+ return json(res, { error: '状态暂时不可用' }, 500)
70
+ }
71
+ },
72
+ }), 'agent-office: api')
73
+
74
+ ctx.effect(() => ctx.webServer.register({
75
+ kind: 'exact',
76
+ path: '/agent-office/api/balance',
77
+ handler: async (req, res) => {
78
+ if (!hostTrusted(req)) return json(res, { ok: false, error: '不被信任的 Host' }, 403)
79
+ try {
80
+ return json(res, await fetchDeepSeekBalance({ credentialsPath: config?.credentialsPath, clientVersion: config?.clientVersion }))
81
+ } catch (error) {
82
+ ctx.logger.error(error instanceof Error ? error : new Error(String(error)))
83
+ return json(res, { ok: false, error: '余额暂时不可用' }, 500)
84
+ }
85
+ },
86
+ }), 'agent-office: balance')
87
+ }