dsh-session-messaging 0.2.0 → 0.3.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/CHANGELOG.md +15 -0
- package/README.md +8 -1
- package/README.zh-CN.md +8 -1
- package/client/client.js +242 -0
- package/lib/config.d.ts +24 -2
- package/lib/config.js +1 -1
- package/lib/service.d.ts +15 -3
- package/lib/service.js +2 -2
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.3.0] - 2026-10-18
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- A client-side sidebar panel, “Session Messaging”, with:
|
|
12
|
+
- a cross-directory toggle persisted through the dsh settings form
|
|
13
|
+
(`sameDirectoryOnly` is now a volatile config field);
|
|
14
|
+
- an npm update check against the published package;
|
|
15
|
+
- links to npm, GitHub, and the author's blog.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- The `sameDirectoryOnly` config field is volatile, so the panel can edit it
|
|
20
|
+
at runtime without editing the profile patch by hand.
|
|
21
|
+
|
|
7
22
|
## [0.2.0] - 2026-10-18
|
|
8
23
|
|
|
9
24
|
### Changed
|
package/README.md
CHANGED
|
@@ -40,11 +40,18 @@ Two user-launched sessions used the plugin to coordinate a real fix. One session
|
|
|
40
40
|
|
|
41
41
|
See [CHANGELOG.md](CHANGELOG.md) for the `0.1.2` entry.
|
|
42
42
|
|
|
43
|
+
### Settings panel
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
The sidebar entry opens a small panel: toggle cross-directory messaging (persisted through the dsh settings form), check the latest npm version, and open npm, GitHub, or the author's blog. After installing or upgrading the bundle, refresh the Web UI once so the client half joins the module graph.
|
|
48
|
+
|
|
43
49
|
## Features
|
|
44
50
|
|
|
45
51
|
- `session_peers` — list live, same-directory, top-level sessions and their reception state.
|
|
46
52
|
- `session_send` — relay a coordination message, optionally declaring `writeScopes` and replying to a message.
|
|
47
53
|
- `session_reception` — pause/resume receiving peer messages for the current session and declare its write scopes.
|
|
54
|
+
- **Sidebar panel** — a “Session Messaging” entry in the left sidebar with a cross-directory toggle, update check, and author links.
|
|
48
55
|
- Messages appear inline in the target conversation, attributed to the sending session — no separate inbox UI.
|
|
49
56
|
- Advisory **write-scope conflict detection**: if a peer declared an overlapping path, the receipt and the message envelope carry a `conflict-warning`.
|
|
50
57
|
- No new Session event types and no `SESSION_FORMAT_VERSION` change: the relay is a durable `user/message` with a declared `session-message` source.
|
|
@@ -80,7 +87,7 @@ The envelope always states that the message comes from another session, not the
|
|
|
80
87
|
dsh plugin --profile <profile> add dsh-session-messaging
|
|
81
88
|
```
|
|
82
89
|
|
|
83
|
-
The package is published as [`dsh-session-messaging`](https://www.npmjs.com/package/dsh-session-messaging) (latest `0.
|
|
90
|
+
The package is published as [`dsh-session-messaging`](https://www.npmjs.com/package/dsh-session-messaging) (latest `0.3.0`). For library use: `npm install dsh-session-messaging`.
|
|
84
91
|
|
|
85
92
|
### From GitHub
|
|
86
93
|
|
package/README.zh-CN.md
CHANGED
|
@@ -36,11 +36,18 @@ macOS 桌面客户端(`0.2.1-rc.2`)真实运行:另一个用户启动的
|
|
|
36
36
|
|
|
37
37
|
`0.1.2` 的变更见 [CHANGELOG.md](CHANGELOG.md)。
|
|
38
38
|
|
|
39
|
+
### 设置面板
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
侧边栏入口打开一个设置面板:切换跨目录消息(通过 dsh 设置表单持久化)、检查 npm 最新版本,并打开 npm / GitHub / 作者博客。安装或升级 bundle 后刷新一次 Web UI,客户端模块才会进入模块图。
|
|
44
|
+
|
|
39
45
|
## 功能
|
|
40
46
|
|
|
41
47
|
- `session_peers`:列出同目录、live 的顶层会话及其接收状态。
|
|
42
48
|
- `session_send`:发送协调消息,可附带 `writeScopes` 与 `replyTo`。
|
|
43
49
|
- `session_reception`:暂停/恢复本会话接收对等消息,并声明写范围。
|
|
50
|
+
- **侧边栏面板**:左侧“跨会话消息”入口,提供跨目录开关、版本更新检查与作者链接。
|
|
44
51
|
- 消息直接内联显示在目标会话中,来源归属于发送会话,不需要独立收件箱。
|
|
45
52
|
- 建议性**写范围冲突检测**:与 peer 声明重叠时,回执与消息信封带 `conflict-warning`。
|
|
46
53
|
- 不新增 Session 事件类型、不改 `SESSION_FORMAT_VERSION`:消息就是带 `session-message` source 的 `user/message`。
|
|
@@ -76,7 +83,7 @@ macOS 桌面客户端(`0.2.1-rc.2`)真实运行:另一个用户启动的
|
|
|
76
83
|
dsh plugin --profile <profile> add dsh-session-messaging
|
|
77
84
|
```
|
|
78
85
|
|
|
79
|
-
包已发布为 [`dsh-session-messaging`](https://www.npmjs.com/package/dsh-session-messaging)(最新 `0.
|
|
86
|
+
包已发布为 [`dsh-session-messaging`](https://www.npmjs.com/package/dsh-session-messaging)(最新 `0.3.0`)。库用法:`npm install dsh-session-messaging`。
|
|
80
87
|
|
|
81
88
|
### 从 GitHub 安装
|
|
82
89
|
|
package/client/client.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser half: a sidebar entry and its panel for session-messaging settings,
|
|
3
|
+
* update check, and author links. Built as a ModuleLoader factory (see the
|
|
4
|
+
* dsh plugin UI template); the Host half carries the service and tools.
|
|
5
|
+
*/
|
|
6
|
+
window.__ModuleLoader__.load({
|
|
7
|
+
id: 'dsh-session-messaging',
|
|
8
|
+
factory(require) {
|
|
9
|
+
const React = require('react')
|
|
10
|
+
const h = React.createElement
|
|
11
|
+
const NS = 'session-messaging'
|
|
12
|
+
const PANEL_ID = 'session-messaging'
|
|
13
|
+
const PACKAGE_NAME = 'dsh-session-messaging'
|
|
14
|
+
const FALLBACK_VERSION = '0.3.0'
|
|
15
|
+
const NPM_URL = 'https://www.npmjs.com/package/dsh-session-messaging'
|
|
16
|
+
const GITHUB_URL = 'https://github.com/XBXyftx/dsh-session-messaging'
|
|
17
|
+
const BLOG_URL = 'https://xbxyftx.top'
|
|
18
|
+
|
|
19
|
+
const zh = {
|
|
20
|
+
'panel.label': '跨会话消息',
|
|
21
|
+
'page.title': '跨会话消息',
|
|
22
|
+
'page.subtitle': '让同一 Host 内的多个用户会话互相发消息、协调任务与写入范围。',
|
|
23
|
+
'setting.title': '允许跨工作目录消息',
|
|
24
|
+
'setting.desc': '开启后,不同工作目录的顶层会话也能互相发现和发送消息。',
|
|
25
|
+
'setting.unavailable': '设置服务当前不可用,无法在线修改。',
|
|
26
|
+
'setting.loading': '正在读取配置…',
|
|
27
|
+
'setting.saveFailed': '保存失败,请重试。',
|
|
28
|
+
'update.title': '版本更新',
|
|
29
|
+
'update.checking': '正在检查更新…',
|
|
30
|
+
'update.upToDate': '已是最新版本',
|
|
31
|
+
'update.available': '发现新版本',
|
|
32
|
+
'update.failed': '更新检查失败',
|
|
33
|
+
'update.button': '检查更新',
|
|
34
|
+
'author.title': '作者',
|
|
35
|
+
'author.desc': '由 XBXyftx 开发,MIT 开源。',
|
|
36
|
+
'links.npm': 'npm',
|
|
37
|
+
'links.github': 'GitHub',
|
|
38
|
+
'links.blog': '作者博客',
|
|
39
|
+
}
|
|
40
|
+
const en = {
|
|
41
|
+
'panel.label': 'Session Messaging',
|
|
42
|
+
'page.title': 'Session Messaging',
|
|
43
|
+
'page.subtitle': 'Let user-launched sessions in the same Host message each other, coordinate tasks, and declare write scopes.',
|
|
44
|
+
'setting.title': 'Allow cross-directory messages',
|
|
45
|
+
'setting.desc': 'When on, top-level sessions in different working directories can discover and message each other.',
|
|
46
|
+
'setting.unavailable': 'The settings service is unavailable; this value cannot be edited here.',
|
|
47
|
+
'setting.loading': 'Reading configuration…',
|
|
48
|
+
'setting.saveFailed': 'Saving failed. Try again.',
|
|
49
|
+
'update.title': 'Updates',
|
|
50
|
+
'update.checking': 'Checking for updates…',
|
|
51
|
+
'update.upToDate': 'Up to date',
|
|
52
|
+
'update.available': 'Update available',
|
|
53
|
+
'update.failed': 'Update check failed',
|
|
54
|
+
'update.button': 'Check for updates',
|
|
55
|
+
'author.title': 'Author',
|
|
56
|
+
'author.desc': 'Built by XBXyftx. MIT licensed.',
|
|
57
|
+
'links.npm': 'npm',
|
|
58
|
+
'links.github': 'GitHub',
|
|
59
|
+
'links.blog': 'Blog',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const color = {
|
|
63
|
+
text: 'var(--dsw-alias-label-primary)',
|
|
64
|
+
secondary: 'var(--dsw-alias-label-secondary)',
|
|
65
|
+
border: 'var(--dsw-alias-border-l1)',
|
|
66
|
+
surface: 'var(--dsw-alias-bg-layer-1)',
|
|
67
|
+
accent: 'var(--dsw-alias-brand-primary)',
|
|
68
|
+
error: 'var(--dsw-alias-state-error-primary)',
|
|
69
|
+
}
|
|
70
|
+
const pageStyle = {
|
|
71
|
+
display: 'flex', flexDirection: 'column', gap: 16, width: '100%',
|
|
72
|
+
maxWidth: 760, margin: '0 auto', padding: '28px 32px', boxSizing: 'border-box',
|
|
73
|
+
color: color.text, overflowY: 'auto',
|
|
74
|
+
}
|
|
75
|
+
const cardStyle = {
|
|
76
|
+
display: 'flex', flexDirection: 'column', gap: 14,
|
|
77
|
+
background: color.surface, border: `1px solid ${color.border}`,
|
|
78
|
+
borderRadius: 12, padding: 18,
|
|
79
|
+
}
|
|
80
|
+
const rowStyle = { display: 'flex', alignItems: 'center', gap: 16, justifyContent: 'space-between' }
|
|
81
|
+
const titleStyle = { fontSize: 15, fontWeight: 600, margin: 0, color: color.text }
|
|
82
|
+
const descStyle = { fontSize: 13, lineHeight: 1.5, margin: '6px 0 0', color: color.secondary }
|
|
83
|
+
const linkStyle = { color: color.accent, textDecoration: 'none', fontSize: 13 }
|
|
84
|
+
const buttonStyle = {
|
|
85
|
+
appearance: 'none', border: `1px solid ${color.border}`, borderRadius: 8,
|
|
86
|
+
background: 'transparent', color: color.text, padding: '6px 12px',
|
|
87
|
+
fontSize: 13, cursor: 'pointer',
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Icon({ size = 16 }) {
|
|
91
|
+
return h('svg', {
|
|
92
|
+
viewBox: '0 0 24 24', width: size, height: size, fill: 'none',
|
|
93
|
+
stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round',
|
|
94
|
+
strokeLinejoin: 'round', 'aria-hidden': true, style: { display: 'block' },
|
|
95
|
+
},
|
|
96
|
+
h('path', { d: 'M4 6a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H8l-4 3z' }),
|
|
97
|
+
h('path', { d: 'M9 13h9a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-2l-3 2v-2h-4a2 2 0 0 1-2-2z' }))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function compareVersions(a, b) {
|
|
101
|
+
const pa = String(a).split('-')[0].split('.').map(Number)
|
|
102
|
+
const pb = String(b).split('-')[0].split('.').map(Number)
|
|
103
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
|
|
104
|
+
const x = pa[i] ?? 0
|
|
105
|
+
const y = pb[i] ?? 0
|
|
106
|
+
if (x !== y) return x - y
|
|
107
|
+
}
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Panel({ t, form, loadCurrentVersion }) {
|
|
112
|
+
const snapshot = React.useSyncExternalStore(
|
|
113
|
+
React.useCallback(listener => form.subscribe(listener), [form]),
|
|
114
|
+
React.useCallback(() => form.getSnapshot(), [form]),
|
|
115
|
+
React.useCallback(() => form.getSnapshot(), [form]),
|
|
116
|
+
)
|
|
117
|
+
const [saving, setSaving] = React.useState(false)
|
|
118
|
+
const [saveFailed, setSaveFailed] = React.useState(false)
|
|
119
|
+
const [currentVersion, setCurrentVersion] = React.useState(FALLBACK_VERSION)
|
|
120
|
+
const [update, setUpdate] = React.useState({ status: 'checking', latest: undefined })
|
|
121
|
+
const ready = snapshot.status === 'ready'
|
|
122
|
+
const enabled = (snapshot.value && snapshot.value.sameDirectoryOnly) === true
|
|
123
|
+
|
|
124
|
+
const check = React.useCallback(async () => {
|
|
125
|
+
setUpdate(previous => ({ ...previous, status: 'checking' }))
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch('https://registry.npmjs.org/dsh-session-messaging/latest')
|
|
128
|
+
if (!response.ok) throw new Error(String(response.status))
|
|
129
|
+
const data = await response.json()
|
|
130
|
+
setUpdate({ status: 'done', latest: typeof data.version === 'string' ? data.version : undefined })
|
|
131
|
+
} catch (_error) {
|
|
132
|
+
setUpdate({ status: 'failed', latest: undefined })
|
|
133
|
+
}
|
|
134
|
+
}, [])
|
|
135
|
+
|
|
136
|
+
React.useEffect(() => {
|
|
137
|
+
let cancelled = false
|
|
138
|
+
void loadCurrentVersion().then(version => { if (!cancelled) setCurrentVersion(version) })
|
|
139
|
+
void check()
|
|
140
|
+
return () => { cancelled = true }
|
|
141
|
+
}, [check, loadCurrentVersion])
|
|
142
|
+
|
|
143
|
+
const toggle = async () => {
|
|
144
|
+
if (!ready || saving) return
|
|
145
|
+
setSaving(true)
|
|
146
|
+
setSaveFailed(false)
|
|
147
|
+
try {
|
|
148
|
+
const accepted = await form.set('sameDirectoryOnly', !enabled)
|
|
149
|
+
if (!accepted) setSaveFailed(true)
|
|
150
|
+
} catch (_error) {
|
|
151
|
+
setSaveFailed(true)
|
|
152
|
+
} finally {
|
|
153
|
+
setSaving(false)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const newer = update.status === 'done' && update.latest !== undefined
|
|
158
|
+
&& compareVersions(update.latest, currentVersion) > 0
|
|
159
|
+
|
|
160
|
+
const settingHint = snapshot.status === 'loading'
|
|
161
|
+
? t('setting.loading')
|
|
162
|
+
: snapshot.status === 'unavailable'
|
|
163
|
+
? t('setting.unavailable')
|
|
164
|
+
: saveFailed ? t('setting.saveFailed') : t('setting.desc')
|
|
165
|
+
|
|
166
|
+
return h('div', { style: pageStyle },
|
|
167
|
+
h('div', null,
|
|
168
|
+
h('h1', { style: { fontSize: 22, fontWeight: 650, margin: 0 } }, t('page.title')),
|
|
169
|
+
h('p', { style: { ...descStyle, fontSize: 14, marginTop: 6 } }, t('page.subtitle')),
|
|
170
|
+
),
|
|
171
|
+
h('section', { style: cardStyle },
|
|
172
|
+
h('div', { style: rowStyle },
|
|
173
|
+
h('div', null,
|
|
174
|
+
h('h2', { style: titleStyle }, t('setting.title')),
|
|
175
|
+
h('p', { style: { ...descStyle, color: saveFailed ? color.error : color.secondary } }, settingHint),
|
|
176
|
+
),
|
|
177
|
+
h('input', {
|
|
178
|
+
type: 'checkbox', checked: enabled, disabled: !ready || saving,
|
|
179
|
+
onChange: () => { void toggle() }, 'aria-label': t('setting.title'),
|
|
180
|
+
style: { width: 18, height: 18, accentColor: color.accent, cursor: ready ? 'pointer' : 'default' },
|
|
181
|
+
}),
|
|
182
|
+
),
|
|
183
|
+
),
|
|
184
|
+
h('section', { style: cardStyle },
|
|
185
|
+
h('div', { style: rowStyle },
|
|
186
|
+
h('div', null,
|
|
187
|
+
h('h2', { style: titleStyle }, t('update.title')),
|
|
188
|
+
h('p', { style: descStyle },
|
|
189
|
+
update.status === 'checking' ? t('update.checking')
|
|
190
|
+
: update.status === 'failed' ? t('update.failed')
|
|
191
|
+
: newer ? `${t('update.available')} · v${update.latest}`
|
|
192
|
+
: `${t('update.upToDate')} · v${currentVersion}`),
|
|
193
|
+
),
|
|
194
|
+
h('button', { type: 'button', style: buttonStyle, onClick: () => { void check() }, disabled: update.status === 'checking' },
|
|
195
|
+
t('update.button')),
|
|
196
|
+
),
|
|
197
|
+
newer ? h('a', { href: NPM_URL, target: '_blank', rel: 'noreferrer', style: linkStyle }, t('links.npm') + ' · v' + update.latest) : null,
|
|
198
|
+
),
|
|
199
|
+
h('section', { style: cardStyle },
|
|
200
|
+
h('div', { style: rowStyle },
|
|
201
|
+
h('div', null,
|
|
202
|
+
h('h2', { style: titleStyle }, t('author.title')),
|
|
203
|
+
h('p', { style: descStyle }, t('author.desc')),
|
|
204
|
+
),
|
|
205
|
+
h('div', { style: { display: 'flex', gap: 14 } },
|
|
206
|
+
h('a', { href: NPM_URL, target: '_blank', rel: 'noreferrer', style: linkStyle }, t('links.npm')),
|
|
207
|
+
h('a', { href: GITHUB_URL, target: '_blank', rel: 'noreferrer', style: linkStyle }, t('links.github')),
|
|
208
|
+
h('a', { href: BLOG_URL, target: '_blank', rel: 'noreferrer', style: linkStyle }, t('links.blog')),
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
inject: ['slots', 'locale', 'configForms', 'remote', 'remote.pluginManager'],
|
|
217
|
+
apply(ctx) {
|
|
218
|
+
const t = ctx.locale.bind(NS)
|
|
219
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-session-messaging: locale')
|
|
220
|
+
const form = ctx.configForms.get(PANEL_ID)
|
|
221
|
+
|
|
222
|
+
const currentVersion = () => ctx.remote.pluginManager.listBundles()
|
|
223
|
+
.then(rows => {
|
|
224
|
+
const rowsArray = Array.isArray(rows) ? rows : []
|
|
225
|
+
const own = rowsArray.find(row => row && row.name === PACKAGE_NAME)
|
|
226
|
+
return own && typeof own.version === 'string' ? own.version : FALLBACK_VERSION
|
|
227
|
+
})
|
|
228
|
+
.catch(() => FALLBACK_VERSION)
|
|
229
|
+
|
|
230
|
+
ctx.slots.inject('main', () => ctx.slots.register({
|
|
231
|
+
name: 'main', key: PANEL_ID, locale: NS,
|
|
232
|
+
inject: () => ({ t, form, loadCurrentVersion: currentVersion }),
|
|
233
|
+
}, Panel))
|
|
234
|
+
|
|
235
|
+
ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
|
|
236
|
+
name: 'sidebar.panellist', id: PANEL_ID, order: 5,
|
|
237
|
+
label: () => t('panel.label'), locale: NS,
|
|
238
|
+
}, Icon))
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
})
|
package/lib/config.d.ts
CHANGED
|
@@ -13,10 +13,32 @@ export interface SessionMessagingConfig {
|
|
|
13
13
|
readonly maxMessagesPerMinute: number;
|
|
14
14
|
/** Require the target session to opt in before it can receive peer messages. Off by default: sessions are mutually visible and can message each other unless they pause reception. */
|
|
15
15
|
readonly requireTargetOptIn: boolean;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Restrict peers to the sender's working directory. Off by default:
|
|
18
|
+
* cross-directory peers are allowed. Declared volatile so the sidebar panel
|
|
19
|
+
* can edit it through the dsh settings form.
|
|
20
|
+
*/
|
|
17
21
|
readonly sameDirectoryOnly: boolean;
|
|
18
22
|
/** Allow a session to relay to itself (diagnostics only; off by default). */
|
|
19
23
|
readonly allowSelfSend: boolean;
|
|
20
24
|
}
|
|
21
25
|
/** Schemastery schema for the plugin row's `config` block. */
|
|
22
|
-
export declare const Config: Schema<
|
|
26
|
+
export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
27
|
+
maxMessageChars: Schema<number, number, "defined">;
|
|
28
|
+
maxMessagesPerMinute: Schema<number, number, "defined">;
|
|
29
|
+
requireTargetOptIn: Schema<boolean, boolean, "defined">;
|
|
30
|
+
sameDirectoryOnly: Schema<boolean, boolean, "volatile-defined">;
|
|
31
|
+
allowSelfSend: Schema<boolean, boolean, "defined">;
|
|
32
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
33
|
+
maxMessageChars: Schema<number, number, "defined">;
|
|
34
|
+
maxMessagesPerMinute: Schema<number, number, "defined">;
|
|
35
|
+
requireTargetOptIn: Schema<boolean, boolean, "defined">;
|
|
36
|
+
sameDirectoryOnly: Schema<boolean, boolean, "volatile-defined">;
|
|
37
|
+
allowSelfSend: Schema<boolean, boolean, "defined">;
|
|
38
|
+
}>>, "plain">;
|
|
39
|
+
/** Plugin runtime config: volatile fields arrive as stable references. */
|
|
40
|
+
export interface ResolvedSessionMessagingConfig extends Omit<SessionMessagingConfig, 'sameDirectoryOnly'> {
|
|
41
|
+
readonly sameDirectoryOnly: {
|
|
42
|
+
get(): boolean;
|
|
43
|
+
};
|
|
44
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -11,6 +11,6 @@ export const Config = Schema.object({
|
|
|
11
11
|
maxMessageChars: Schema.number().min(1).default(DEFAULT_MAX_MESSAGE_CHARS),
|
|
12
12
|
maxMessagesPerMinute: Schema.number().min(1).default(DEFAULT_MAX_MESSAGES_PER_MINUTE),
|
|
13
13
|
requireTargetOptIn: Schema.boolean().default(false),
|
|
14
|
-
sameDirectoryOnly: Schema.boolean().default(false),
|
|
14
|
+
sameDirectoryOnly: Schema.boolean().default(false).volatile(),
|
|
15
15
|
allowSelfSend: Schema.boolean().default(false),
|
|
16
16
|
});
|
package/lib/service.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { Service, type Context } from '@deepseek-ai/cordis';
|
|
10
10
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
11
11
|
import { Config } from './config.ts';
|
|
12
|
-
import type {
|
|
12
|
+
import type { ResolvedSessionMessagingConfig } from './config.ts';
|
|
13
13
|
import type { PeerView, SendReceipt, SendRequest } from './protocol.ts';
|
|
14
14
|
/** One session's receive consent and advisory write scopes. */
|
|
15
15
|
interface ConsentEntry {
|
|
@@ -31,7 +31,19 @@ declare module '@deepseek-ai/cordis' {
|
|
|
31
31
|
export declare class SessionMessagingService extends Service {
|
|
32
32
|
static inject: string[];
|
|
33
33
|
/** Loader-read configuration schema; defaults apply when the row omits `config`. */
|
|
34
|
-
static Config: import("@deepseek-ai/schemastery").default<
|
|
34
|
+
static Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<NoInfer<{
|
|
35
|
+
maxMessageChars: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
36
|
+
maxMessagesPerMinute: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
37
|
+
requireTargetOptIn: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
38
|
+
sameDirectoryOnly: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
39
|
+
allowSelfSend: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
40
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
41
|
+
maxMessageChars: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
42
|
+
maxMessagesPerMinute: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
43
|
+
requireTargetOptIn: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
44
|
+
sameDirectoryOnly: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
45
|
+
allowSelfSend: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
46
|
+
}>>, "plain">;
|
|
35
47
|
private readonly config;
|
|
36
48
|
private readonly limiter;
|
|
37
49
|
private readonly consent;
|
|
@@ -39,7 +51,7 @@ export declare class SessionMessagingService extends Service {
|
|
|
39
51
|
* @param ctx - Cordis context with `agents` and `sessions` ready.
|
|
40
52
|
* @param config - validated plugin configuration.
|
|
41
53
|
*/
|
|
42
|
-
constructor(ctx: Context, config:
|
|
54
|
+
constructor(ctx: Context, config: ResolvedSessionMessagingConfig);
|
|
43
55
|
/**
|
|
44
56
|
* List live user-launched peers the caller may address.
|
|
45
57
|
* @param caller - the agent whose tool call is inspecting peers.
|
package/lib/service.js
CHANGED
|
@@ -47,7 +47,7 @@ export class SessionMessagingService extends Service {
|
|
|
47
47
|
continue;
|
|
48
48
|
if (!isTopLevelSessionHeader(candidate.session.header))
|
|
49
49
|
continue;
|
|
50
|
-
if (this.config.sameDirectoryOnly && !sameDirectory(callerDirectory, candidate.session.header.cwd)) {
|
|
50
|
+
if (this.config.sameDirectoryOnly.get() && !sameDirectory(callerDirectory, candidate.session.header.cwd)) {
|
|
51
51
|
continue;
|
|
52
52
|
}
|
|
53
53
|
const consent = this.consent.get(candidate.id);
|
|
@@ -137,7 +137,7 @@ export class SessionMessagingService extends Service {
|
|
|
137
137
|
if (!isTopLevelSessionHeader(target.session.header)) {
|
|
138
138
|
throw new MessagingError('target-not-top-level', `session ${targetId} is not a user-launched top-level session`);
|
|
139
139
|
}
|
|
140
|
-
if (this.config.sameDirectoryOnly && !sameDirectory(caller.session.header.cwd, target.session.header.cwd)) {
|
|
140
|
+
if (this.config.sameDirectoryOnly.get() && !sameDirectory(caller.session.header.cwd, target.session.header.cwd)) {
|
|
141
141
|
throw new MessagingError('cross-directory', 'the target session works in a different directory');
|
|
142
142
|
}
|
|
143
143
|
const consent = this.consent.get(targetId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-messaging",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Peer messaging between user-launched sessions in one workspace: discover peers, relay coordination messages, pause reception, and get advisory write-scope conflict warnings.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"types": "./lib/tools.d.ts",
|
|
16
16
|
"default": "./lib/tools.js"
|
|
17
17
|
},
|
|
18
|
+
"./client": "./client/client.js",
|
|
18
19
|
"./locale/*.json": "./locale/*.json",
|
|
19
20
|
"./tools/locale/*.json": "./tools/locale/*.json",
|
|
20
21
|
"./tools/icon": "./assets/tools-icon.svg",
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"lib",
|
|
27
|
+
"client",
|
|
26
28
|
"locale",
|
|
27
29
|
"tools/locale",
|
|
28
30
|
"assets",
|
|
@@ -83,6 +85,14 @@
|
|
|
83
85
|
"dsh": {
|
|
84
86
|
"bundle": {
|
|
85
87
|
"patch": "./cordis.patch.yml"
|
|
88
|
+
},
|
|
89
|
+
"client": {
|
|
90
|
+
"platform": "web",
|
|
91
|
+
"immediately": true,
|
|
92
|
+
"inject": [
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-layout"
|
|
95
|
+
]
|
|
86
96
|
}
|
|
87
97
|
},
|
|
88
98
|
"icon": "./assets/icon.svg",
|