dsh-synapse 0.4.0 → 0.4.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/LICENSE +21 -21
- package/README.md +81 -81
- package/app.js +1694 -1140
- package/client.js +241 -217
- package/cordis.patch.yml +13 -13
- package/deepseek-mark.svg +3 -3
- package/docs/architecture.md +104 -104
- package/docs/development.md +137 -137
- package/docs/en/README.md +161 -161
- package/docs/zh-CN/README.md +154 -154
- package/index.js +799 -772
- package/package.json +45 -44
- package/styles.css +420 -310
package/client.js
CHANGED
|
@@ -1,217 +1,241 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({
|
|
2
|
-
id: 'dsh-synapse',
|
|
3
|
-
factory: () => {
|
|
4
|
-
const module = { exports: {} }
|
|
5
|
-
const currentSession = ctx => {
|
|
6
|
-
const snapshot = ctx.sessions.list.getSnapshot()
|
|
7
|
-
const id = snapshot.current
|
|
8
|
-
if (id === undefined) return null
|
|
9
|
-
const session = snapshot.byId[id]
|
|
10
|
-
return session === undefined ? null : { id, title: session.displayTitle, cwd: session.cwd ?? null }
|
|
11
|
-
}
|
|
12
|
-
const sessionSnapshot = ctx => {
|
|
13
|
-
const snapshot = ctx.sessions.list.getSnapshot()
|
|
14
|
-
return snapshot.ids.map(id => {
|
|
15
|
-
const session = snapshot.byId[id]
|
|
16
|
-
return session === undefined ? null : { id, title: session.displayTitle, cwd: session.cwd ?? null, parentId: session.parentId ?? null, blank: session.blank }
|
|
17
|
-
}).filter(Boolean)
|
|
18
|
-
}
|
|
19
|
-
const workspaceSnapshot = ctx => {
|
|
20
|
-
const sessions = ctx.sessions.list.getSnapshot()
|
|
21
|
-
const snapshot = ctx.workspaces.list.getSnapshot()
|
|
22
|
-
const accounted = new Set(snapshot.items.flatMap(workspace => workspace.sessionIds))
|
|
23
|
-
return [
|
|
24
|
-
...snapshot.items.map(workspace => ({ id: workspace.workspaceId, title: workspace.title, path: workspace.path, sessionIds: workspace.sessionIds })),
|
|
25
|
-
{ id: 'dsh-ungrouped', title: '未分组', path: null, sessionIds: sessions.ids.filter(id => !accounted.has(id)) },
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
module.exports.inject = ['sessions', 'workspaces']
|
|
30
|
-
module.exports.apply = ctx => {
|
|
31
|
-
const prompt = async (sessionId, text) => {
|
|
32
|
-
const scope = ctx.sessions.scope(sessionId)
|
|
33
|
-
const session = scope === undefined ? undefined : ctx.sessions.sessionOf(scope)
|
|
34
|
-
if (session === undefined) throw new Error('关联的 DSH 会话已不可用')
|
|
35
|
-
const result = await session.prompt([{ type: 'text', text }], 'queue')
|
|
36
|
-
if (!result.ok) throw new Error(result.error?.message ?? 'DSH 未接受这条消息')
|
|
37
|
-
}
|
|
38
|
-
const style = document.createElement('style')
|
|
39
|
-
style.textContent = '.dsh-synapse-switch{position:fixed;z-index:80;top:12px;left:50%;display:flex;gap:2px;transform:translateX(-50%);border:1px solid #d1d5db;border-radius:999px;background:rgba(255,255,255,.96);padding:3px;backdrop-filter:blur(10px)}.dsh-synapse-switch button{height:28px;border:0;border-radius:999px;background:transparent;padding:0 11px;color:#6b7280;font:600 12px Inter,system-ui,sans-serif;cursor:pointer;white-space:nowrap}.dsh-synapse-switch button:hover{background:#f3f4f6;color:#111827}.dsh-synapse-switch button.active{background:#111827;color:#fff}.dsh-synapse-switch button:focus-visible{outline:2px solid #111827;outline-offset:2px}.dsh-synapse-overlay{position:fixed;z-index:100;inset:0;background:#f5f7fa}.dsh-synapse-overlay.is-opening{visibility:hidden}.dsh-synapse-overlay[hidden]{display:none}.dsh-synapse-overlay iframe{display:block;width:100%;height:100%;border:0}'
|
|
40
|
-
document.head.append(style)
|
|
41
|
-
const host = document.createElement('div')
|
|
42
|
-
host.className = 'dsh-synapse-host'
|
|
43
|
-
host.innerHTML = '<div class="dsh-synapse-switch" role="group" aria-label="视图切换"><button type="button" data-view="dialog" class="active" aria-pressed="true">对话</button><button type="button" data-view="map" aria-pressed="false">会话地图</button></div><section class="dsh-synapse-overlay" hidden><iframe title="会话地图" src="/synapse/"></iframe></section>'
|
|
44
|
-
document.body.append(host)
|
|
45
|
-
const dialogButton = host.querySelector('[data-view="dialog"]')
|
|
46
|
-
const mapButton = host.querySelector('[data-view="map"]')
|
|
47
|
-
const overlay = host.querySelector('.dsh-synapse-overlay')
|
|
48
|
-
const frame = host.querySelector('iframe')
|
|
49
|
-
|
|
50
|
-
const setView = view => {
|
|
51
|
-
const showingMap = view === 'map'
|
|
52
|
-
dialogButton.classList.toggle('active', !showingMap)
|
|
53
|
-
dialogButton.setAttribute('aria-pressed', String(!showingMap))
|
|
54
|
-
mapButton.classList.toggle('active', showingMap)
|
|
55
|
-
mapButton.setAttribute('aria-pressed', String(showingMap))
|
|
56
|
-
}
|
|
57
|
-
const close = () => {
|
|
58
|
-
window.clearTimeout(mapOpenFallback)
|
|
59
|
-
mapOpening = false
|
|
60
|
-
overlay.classList.remove('is-opening')
|
|
61
|
-
overlay.hidden = true
|
|
62
|
-
setView('dialog')
|
|
63
|
-
}
|
|
64
|
-
const send = (type, payload) => { frame.contentWindow?.postMessage({ source: 'dsh-synapse', type, ...payload }, location.origin) }
|
|
65
|
-
let syncQueued = false
|
|
66
|
-
let knownSessionIds = new Set()
|
|
67
|
-
const liveUnsubscribers = new Map()
|
|
68
|
-
const syncLiveSessions = () => {
|
|
69
|
-
const snapshot = ctx.sessions.list.getSnapshot()
|
|
70
|
-
for (const id of snapshot.ids) {
|
|
71
|
-
if (liveUnsubscribers.has(id)) continue
|
|
72
|
-
const scope = ctx.sessions.scope(id)
|
|
73
|
-
const session = scope === undefined ? undefined : ctx.sessions.sessionOf(scope)
|
|
74
|
-
if (session === undefined) continue
|
|
75
|
-
const publish = () => {
|
|
76
|
-
if (overlay.hidden) return
|
|
77
|
-
const state = session.getSnapshot()
|
|
78
|
-
const text = state.partial?.blocks.filter(block => block.kind === 'text').map(block => block.text).join('\n') ?? ''
|
|
79
|
-
send('synapse:live-reply', { sessionId: id, running: state.running, text })
|
|
80
|
-
}
|
|
81
|
-
liveUnsubscribers.set(id, session.subscribe(publish))
|
|
82
|
-
publish()
|
|
83
|
-
}
|
|
84
|
-
for (const [id, unsubscribe] of liveUnsubscribers) if (!snapshot.ids.includes(id)) { unsubscribe(); liveUnsubscribers.delete(id) }
|
|
85
|
-
}
|
|
86
|
-
const syncSessions = () => {
|
|
87
|
-
if (syncQueued) return
|
|
88
|
-
syncQueued = true
|
|
89
|
-
queueMicrotask(() => {
|
|
90
|
-
syncQueued = false
|
|
91
|
-
const sessions = sessionSnapshot(ctx)
|
|
92
|
-
const sessionIds = new Set(sessions.map(session => session.id))
|
|
93
|
-
const removedSessionIds = [...knownSessionIds].filter(id => !sessionIds.has(id))
|
|
94
|
-
knownSessionIds = sessionIds
|
|
95
|
-
void fetch('/synapse/api/sessions/sync', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sessions, removedSessionIds }) }).catch(() => {})
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
const syncTheme = () => {
|
|
99
|
-
const dark = document.body?.hasAttribute?.('data-ds-dark-theme') === true
|
|
100
|
-
send('synapse:theme', { dark })
|
|
101
|
-
}
|
|
102
|
-
const syncCurrentSession = () => {
|
|
103
|
-
syncSessions()
|
|
104
|
-
syncLiveSessions()
|
|
105
|
-
syncTheme()
|
|
106
|
-
if (!overlay.hidden) {
|
|
107
|
-
send('synapse:workspaces', { workspaces: workspaceSnapshot(ctx) })
|
|
108
|
-
send('synapse:current-session', { session: currentSession(ctx) })
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
let mapOpenFallback = 0
|
|
112
|
-
let mapOpening = false
|
|
113
|
-
const showMapOverlay = () => {
|
|
114
|
-
window.clearTimeout(mapOpenFallback)
|
|
115
|
-
mapOpening = false
|
|
116
|
-
overlay.hidden = false
|
|
117
|
-
overlay.classList.remove('is-opening')
|
|
118
|
-
syncCurrentSession()
|
|
119
|
-
}
|
|
120
|
-
const open = () => {
|
|
121
|
-
window.clearTimeout(mapOpenFallback)
|
|
122
|
-
mapOpening = true
|
|
123
|
-
setView('map')
|
|
124
|
-
// Keep the iframe laid out while hidden so its canvas can receive a
|
|
125
|
-
// real scroll offset. display:none would clamp scrollTop back to zero.
|
|
126
|
-
overlay.hidden = false
|
|
127
|
-
overlay.classList.add('is-opening')
|
|
128
|
-
window.requestAnimationFrame(() => {
|
|
129
|
-
send('synapse:map-opened')
|
|
130
|
-
syncCurrentSession()
|
|
131
|
-
})
|
|
132
|
-
mapOpenFallback = window.setTimeout(showMapOverlay, 300)
|
|
133
|
-
}
|
|
134
|
-
const onFrameLoad = () => {
|
|
135
|
-
syncCurrentSession()
|
|
136
|
-
if (mapOpening) send('synapse:map-opened')
|
|
137
|
-
}
|
|
138
|
-
const onMessage = event => {
|
|
139
|
-
if (event.origin !== location.origin || event.data?.source !== 'dsh-synapse') return
|
|
140
|
-
if (event.data.type === 'synapse:close') return close()
|
|
141
|
-
if (event.data.type === 'synapse:map-ready') return showMapOverlay()
|
|
142
|
-
if (event.data.type === 'synapse:request-current') {
|
|
143
|
-
send('synapse:workspaces', { workspaces: workspaceSnapshot(ctx) })
|
|
144
|
-
return send('synapse:current-session', { session: currentSession(ctx) })
|
|
145
|
-
}
|
|
146
|
-
if (event.data.type === 'synapse:open-session') {
|
|
147
|
-
try { ctx.sessions.open(event.data.sessionId); close() } catch { send('synapse:bridge-error', { message: '关联的 DSH 会话已不可用' }) }
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: 'dsh-synapse',
|
|
3
|
+
factory: () => {
|
|
4
|
+
const module = { exports: {} }
|
|
5
|
+
const currentSession = ctx => {
|
|
6
|
+
const snapshot = ctx.sessions.list.getSnapshot()
|
|
7
|
+
const id = snapshot.current
|
|
8
|
+
if (id === undefined) return null
|
|
9
|
+
const session = snapshot.byId[id]
|
|
10
|
+
return session === undefined ? null : { id, title: session.displayTitle, cwd: session.cwd ?? null }
|
|
11
|
+
}
|
|
12
|
+
const sessionSnapshot = ctx => {
|
|
13
|
+
const snapshot = ctx.sessions.list.getSnapshot()
|
|
14
|
+
return snapshot.ids.map(id => {
|
|
15
|
+
const session = snapshot.byId[id]
|
|
16
|
+
return session === undefined ? null : { id, title: session.displayTitle, cwd: session.cwd ?? null, parentId: session.parentId ?? null, blank: session.blank }
|
|
17
|
+
}).filter(Boolean)
|
|
18
|
+
}
|
|
19
|
+
const workspaceSnapshot = ctx => {
|
|
20
|
+
const sessions = ctx.sessions.list.getSnapshot()
|
|
21
|
+
const snapshot = ctx.workspaces.list.getSnapshot()
|
|
22
|
+
const accounted = new Set(snapshot.items.flatMap(workspace => workspace.sessionIds))
|
|
23
|
+
return [
|
|
24
|
+
...snapshot.items.map(workspace => ({ id: workspace.workspaceId, title: workspace.title, path: workspace.path, sessionIds: workspace.sessionIds })),
|
|
25
|
+
{ id: 'dsh-ungrouped', title: '未分组', path: null, sessionIds: sessions.ids.filter(id => !accounted.has(id)) },
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports.inject = ['sessions', 'workspaces']
|
|
30
|
+
module.exports.apply = ctx => {
|
|
31
|
+
const prompt = async (sessionId, text) => {
|
|
32
|
+
const scope = ctx.sessions.scope(sessionId)
|
|
33
|
+
const session = scope === undefined ? undefined : ctx.sessions.sessionOf(scope)
|
|
34
|
+
if (session === undefined) throw new Error('关联的 DSH 会话已不可用')
|
|
35
|
+
const result = await session.prompt([{ type: 'text', text }], 'queue')
|
|
36
|
+
if (!result.ok) throw new Error(result.error?.message ?? 'DSH 未接受这条消息')
|
|
37
|
+
}
|
|
38
|
+
const style = document.createElement('style')
|
|
39
|
+
style.textContent = '.dsh-synapse-switch{position:fixed;z-index:80;top:12px;left:50%;display:flex;gap:2px;transform:translateX(-50%);border:1px solid #d1d5db;border-radius:999px;background:rgba(255,255,255,.96);padding:3px;backdrop-filter:blur(10px)}.dsh-synapse-switch button{height:28px;border:0;border-radius:999px;background:transparent;padding:0 11px;color:#6b7280;font:600 12px Inter,system-ui,sans-serif;cursor:pointer;white-space:nowrap}.dsh-synapse-switch button:hover{background:#f3f4f6;color:#111827}.dsh-synapse-switch button.active{background:#111827;color:#fff}.dsh-synapse-switch button:focus-visible{outline:2px solid #111827;outline-offset:2px}.dsh-synapse-overlay{position:fixed;z-index:100;inset:0;background:#f5f7fa}.dsh-synapse-overlay.is-opening{visibility:hidden}.dsh-synapse-overlay[hidden]{display:none}.dsh-synapse-overlay iframe{display:block;width:100%;height:100%;border:0}'
|
|
40
|
+
document.head.append(style)
|
|
41
|
+
const host = document.createElement('div')
|
|
42
|
+
host.className = 'dsh-synapse-host'
|
|
43
|
+
host.innerHTML = '<div class="dsh-synapse-switch" role="group" aria-label="视图切换"><button type="button" data-view="dialog" class="active" aria-pressed="true">对话</button><button type="button" data-view="map" aria-pressed="false">会话地图</button></div><section class="dsh-synapse-overlay" hidden><iframe title="会话地图" src="/synapse/"></iframe></section>'
|
|
44
|
+
document.body.append(host)
|
|
45
|
+
const dialogButton = host.querySelector('[data-view="dialog"]')
|
|
46
|
+
const mapButton = host.querySelector('[data-view="map"]')
|
|
47
|
+
const overlay = host.querySelector('.dsh-synapse-overlay')
|
|
48
|
+
const frame = host.querySelector('iframe')
|
|
49
|
+
|
|
50
|
+
const setView = view => {
|
|
51
|
+
const showingMap = view === 'map'
|
|
52
|
+
dialogButton.classList.toggle('active', !showingMap)
|
|
53
|
+
dialogButton.setAttribute('aria-pressed', String(!showingMap))
|
|
54
|
+
mapButton.classList.toggle('active', showingMap)
|
|
55
|
+
mapButton.setAttribute('aria-pressed', String(showingMap))
|
|
56
|
+
}
|
|
57
|
+
const close = () => {
|
|
58
|
+
window.clearTimeout(mapOpenFallback)
|
|
59
|
+
mapOpening = false
|
|
60
|
+
overlay.classList.remove('is-opening')
|
|
61
|
+
overlay.hidden = true
|
|
62
|
+
setView('dialog')
|
|
63
|
+
}
|
|
64
|
+
const send = (type, payload) => { frame.contentWindow?.postMessage({ source: 'dsh-synapse', type, ...payload }, location.origin) }
|
|
65
|
+
let syncQueued = false
|
|
66
|
+
let knownSessionIds = new Set()
|
|
67
|
+
const liveUnsubscribers = new Map()
|
|
68
|
+
const syncLiveSessions = () => {
|
|
69
|
+
const snapshot = ctx.sessions.list.getSnapshot()
|
|
70
|
+
for (const id of snapshot.ids) {
|
|
71
|
+
if (liveUnsubscribers.has(id)) continue
|
|
72
|
+
const scope = ctx.sessions.scope(id)
|
|
73
|
+
const session = scope === undefined ? undefined : ctx.sessions.sessionOf(scope)
|
|
74
|
+
if (session === undefined) continue
|
|
75
|
+
const publish = () => {
|
|
76
|
+
if (overlay.hidden) return
|
|
77
|
+
const state = session.getSnapshot()
|
|
78
|
+
const text = state.partial?.blocks.filter(block => block.kind === 'text').map(block => block.text).join('\n') ?? ''
|
|
79
|
+
send('synapse:live-reply', { sessionId: id, running: state.running, text })
|
|
80
|
+
}
|
|
81
|
+
liveUnsubscribers.set(id, session.subscribe(publish))
|
|
82
|
+
publish()
|
|
83
|
+
}
|
|
84
|
+
for (const [id, unsubscribe] of liveUnsubscribers) if (!snapshot.ids.includes(id)) { unsubscribe(); liveUnsubscribers.delete(id) }
|
|
85
|
+
}
|
|
86
|
+
const syncSessions = () => {
|
|
87
|
+
if (syncQueued) return
|
|
88
|
+
syncQueued = true
|
|
89
|
+
queueMicrotask(() => {
|
|
90
|
+
syncQueued = false
|
|
91
|
+
const sessions = sessionSnapshot(ctx)
|
|
92
|
+
const sessionIds = new Set(sessions.map(session => session.id))
|
|
93
|
+
const removedSessionIds = [...knownSessionIds].filter(id => !sessionIds.has(id))
|
|
94
|
+
knownSessionIds = sessionIds
|
|
95
|
+
void fetch('/synapse/api/sessions/sync', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sessions, removedSessionIds }) }).catch(() => {})
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
const syncTheme = () => {
|
|
99
|
+
const dark = document.body?.hasAttribute?.('data-ds-dark-theme') === true
|
|
100
|
+
send('synapse:theme', { dark })
|
|
101
|
+
}
|
|
102
|
+
const syncCurrentSession = () => {
|
|
103
|
+
syncSessions()
|
|
104
|
+
syncLiveSessions()
|
|
105
|
+
syncTheme()
|
|
106
|
+
if (!overlay.hidden) {
|
|
107
|
+
send('synapse:workspaces', { workspaces: workspaceSnapshot(ctx) })
|
|
108
|
+
send('synapse:current-session', { session: currentSession(ctx) })
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
let mapOpenFallback = 0
|
|
112
|
+
let mapOpening = false
|
|
113
|
+
const showMapOverlay = () => {
|
|
114
|
+
window.clearTimeout(mapOpenFallback)
|
|
115
|
+
mapOpening = false
|
|
116
|
+
overlay.hidden = false
|
|
117
|
+
overlay.classList.remove('is-opening')
|
|
118
|
+
syncCurrentSession()
|
|
119
|
+
}
|
|
120
|
+
const open = () => {
|
|
121
|
+
window.clearTimeout(mapOpenFallback)
|
|
122
|
+
mapOpening = true
|
|
123
|
+
setView('map')
|
|
124
|
+
// Keep the iframe laid out while hidden so its canvas can receive a
|
|
125
|
+
// real scroll offset. display:none would clamp scrollTop back to zero.
|
|
126
|
+
overlay.hidden = false
|
|
127
|
+
overlay.classList.add('is-opening')
|
|
128
|
+
window.requestAnimationFrame(() => {
|
|
129
|
+
send('synapse:map-opened')
|
|
130
|
+
syncCurrentSession()
|
|
131
|
+
})
|
|
132
|
+
mapOpenFallback = window.setTimeout(showMapOverlay, 300)
|
|
133
|
+
}
|
|
134
|
+
const onFrameLoad = () => {
|
|
135
|
+
syncCurrentSession()
|
|
136
|
+
if (mapOpening) send('synapse:map-opened')
|
|
137
|
+
}
|
|
138
|
+
const onMessage = event => {
|
|
139
|
+
if (event.origin !== location.origin || event.data?.source !== 'dsh-synapse') return
|
|
140
|
+
if (event.data.type === 'synapse:close') return close()
|
|
141
|
+
if (event.data.type === 'synapse:map-ready') return showMapOverlay()
|
|
142
|
+
if (event.data.type === 'synapse:request-current') {
|
|
143
|
+
send('synapse:workspaces', { workspaces: workspaceSnapshot(ctx) })
|
|
144
|
+
return send('synapse:current-session', { session: currentSession(ctx) })
|
|
145
|
+
}
|
|
146
|
+
if (event.data.type === 'synapse:open-session') {
|
|
147
|
+
try { ctx.sessions.open(event.data.sessionId); close() } catch { send('synapse:bridge-error', { message: '关联的 DSH 会话已不可用' }) }
|
|
148
|
+
// Best-effort anchor to the requested turn: chat nodes expose their
|
|
149
|
+
// source event seq (anchorSeq) and render with data-chat-anchor-key,
|
|
150
|
+
// so resolve seq -> node key -> scroll once the view materializes.
|
|
151
|
+
const seq = event.data.seq
|
|
152
|
+
if (Number.isInteger(seq)) {
|
|
153
|
+
const tryScroll = attempt => {
|
|
154
|
+
const scope = ctx.sessions.scope(event.data.sessionId)
|
|
155
|
+
const session = scope === undefined ? undefined : ctx.sessions.sessionOf(scope)
|
|
156
|
+
if (session === undefined) return
|
|
157
|
+
const chat = session.getSnapshot()?.chat
|
|
158
|
+
if (chat === undefined) return
|
|
159
|
+
let key = undefined
|
|
160
|
+
for (const node of chat.nodes.values()) {
|
|
161
|
+
if (node.anchorSeq === seq) { key = node.key; break }
|
|
162
|
+
}
|
|
163
|
+
if (key !== undefined) {
|
|
164
|
+
const row = document.querySelector(`[data-chat-anchor-key="${CSS.escape(key)}"]`)
|
|
165
|
+
if (row instanceof HTMLElement) row.scrollIntoView({ block: 'start' })
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
if (attempt < 3) window.setTimeout(() => tryScroll(attempt + 1), 500)
|
|
169
|
+
}
|
|
170
|
+
window.setTimeout(() => tryScroll(0), 300)
|
|
171
|
+
}
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
if (event.data.type === 'synapse:activate-session') {
|
|
175
|
+
// Bidirectional current-session sync: switch DSH's current session
|
|
176
|
+
// without closing the map; the sessions-list subscription re-sends
|
|
177
|
+
// synapse:current-session so the map follows the new highlight.
|
|
178
|
+
try { ctx.sessions.open(event.data.sessionId) } catch { send('synapse:bridge-error', { message: '关联的 DSH 会话已不可用' }) }
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
if (event.data.type === 'synapse:fork-session') {
|
|
182
|
+
const atSeq = Number.isInteger(event.data.atSeq) ? event.data.atSeq : undefined
|
|
183
|
+
ctx.sessions.fork({ sessionId: event.data.sessionId, atSeq, increaseTitle: true }).then(id => {
|
|
184
|
+
const snapshot = ctx.sessions.list.getSnapshot()
|
|
185
|
+
send('synapse:forked-session', { requestId: event.data.requestId, session: { id, title: snapshot.byId[id]?.displayTitle ?? 'DSH 分支' } })
|
|
186
|
+
}).catch(() => { send('synapse:bridge-error', { message: 'DSH 分支创建失败,请确认源会话已经完成当前轮次' }) })
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
if (event.data.type === 'synapse:send-message') {
|
|
190
|
+
const text = typeof event.data.text === 'string' ? event.data.text.trim() : ''
|
|
191
|
+
if (text === '') return send('synapse:bridge-error', { requestId: event.data.requestId, message: '消息不能为空' })
|
|
192
|
+
prompt(event.data.sessionId, text).then(() => {
|
|
193
|
+
send('synapse:message-sent', { requestId: event.data.requestId, sessionId: event.data.sessionId })
|
|
194
|
+
}).catch(error => {
|
|
195
|
+
send('synapse:bridge-error', { requestId: event.data.requestId, message: error instanceof Error ? error.message : 'DSH 消息发送失败' })
|
|
196
|
+
})
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
if (event.data.type === 'synapse:create-session') {
|
|
200
|
+
const workspaceId = typeof event.data.workspaceId === 'string' && event.data.workspaceId !== '' && event.data.workspaceId !== 'dsh-ungrouped' ? event.data.workspaceId : undefined
|
|
201
|
+
const cwd = typeof event.data.cwd === 'string' && event.data.cwd !== '' ? event.data.cwd : undefined
|
|
202
|
+
const create = workspaceId === undefined ? ctx.sessions.create(cwd === undefined ? {} : { cwd }) : ctx.sessions.create({ workspaceId })
|
|
203
|
+
create.then(id => {
|
|
204
|
+
const snapshot = ctx.sessions.list.getSnapshot()
|
|
205
|
+
send('synapse:created-session', { requestId: event.data.requestId, session: { id, title: snapshot.byId[id]?.displayTitle ?? '新会话', cwd: snapshot.byId[id]?.cwd ?? cwd ?? null } })
|
|
206
|
+
}).catch(() => { send('synapse:bridge-error', { requestId: event.data.requestId, message: 'DSH 会话创建失败,请先在 DSH 选择工作目录' }) })
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const onKeyDown = event => { if (event.key === 'Escape' && !overlay.hidden) close() }
|
|
210
|
+
// Follow DSH's live theme switch: body[data-ds-dark-theme] is the web
|
|
211
|
+
// client's dark-mode signal, mirrored into the map iframe via synapse:theme.
|
|
212
|
+
const themeObserver = typeof MutationObserver === 'undefined'
|
|
213
|
+
? null
|
|
214
|
+
: new MutationObserver(() => syncTheme())
|
|
215
|
+
if (themeObserver !== null && document.body) {
|
|
216
|
+
themeObserver.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })
|
|
217
|
+
}
|
|
218
|
+
const unsubscribeSessions = ctx.sessions.list.subscribe(syncCurrentSession)
|
|
219
|
+
const unsubscribeWorkspaces = ctx.workspaces.list.subscribe(syncCurrentSession)
|
|
220
|
+
dialogButton.addEventListener('click', close)
|
|
221
|
+
mapButton.addEventListener('click', open)
|
|
222
|
+
frame.addEventListener('load', onFrameLoad)
|
|
223
|
+
window.addEventListener('message', onMessage)
|
|
224
|
+
window.addEventListener('keydown', onKeyDown)
|
|
225
|
+
ctx.effect(() => () => {
|
|
226
|
+
dialogButton.removeEventListener('click', close)
|
|
227
|
+
mapButton.removeEventListener('click', open)
|
|
228
|
+
frame.removeEventListener('load', onFrameLoad)
|
|
229
|
+
window.removeEventListener('message', onMessage)
|
|
230
|
+
window.removeEventListener('keydown', onKeyDown)
|
|
231
|
+
themeObserver?.disconnect()
|
|
232
|
+
unsubscribeSessions()
|
|
233
|
+
unsubscribeWorkspaces()
|
|
234
|
+
for (const unsubscribe of liveUnsubscribers.values()) unsubscribe()
|
|
235
|
+
host.remove()
|
|
236
|
+
style.remove()
|
|
237
|
+
}, 'synapse: web workspace switch')
|
|
238
|
+
}
|
|
239
|
+
return module.exports
|
|
240
|
+
},
|
|
241
|
+
})
|
package/cordis.patch.yml
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# dsh-synapse only extends the Web profile: it reuses the existing DSH server
|
|
2
|
-
# rather than running a second application process.
|
|
3
|
-
- insert:
|
|
4
|
-
- id: synapse
|
|
5
|
-
name: dsh-synapse
|
|
6
|
-
config:
|
|
7
|
-
# A profile can override this path in its own cordis.patch.yml.
|
|
8
|
-
dataFile: !!js dshHomePath('synapse/workspaces.json')
|
|
9
|
-
autoProjection: true
|
|
10
|
-
projectionWorkspaceTitle: DSH 任务
|
|
11
|
-
# Extra authorities the /synapse Host check accepts (host or host:port);
|
|
12
|
-
# localhost and 127.0.0.1 are always allowed.
|
|
13
|
-
trustedHosts: []
|
|
1
|
+
# dsh-synapse only extends the Web profile: it reuses the existing DSH server
|
|
2
|
+
# rather than running a second application process.
|
|
3
|
+
- insert:
|
|
4
|
+
- id: synapse
|
|
5
|
+
name: dsh-synapse
|
|
6
|
+
config:
|
|
7
|
+
# A profile can override this path in its own cordis.patch.yml.
|
|
8
|
+
dataFile: !!js dshHomePath('synapse/workspaces.json')
|
|
9
|
+
autoProjection: true
|
|
10
|
+
projectionWorkspaceTitle: DSH 任务
|
|
11
|
+
# Extra authorities the /synapse Host check accepts (host or host:port);
|
|
12
|
+
# localhost and 127.0.0.1 are always allowed.
|
|
13
|
+
trustedHosts: []
|
package/deepseek-mark.svg
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
2
|
-
<path fill="currentColor" d="M23.0584 4.95203C22.8129 4.83203 22.7074 5.06103 22.5639 5.17704C22.5149 5.21454 22.4734 5.26354 22.4319 5.30854C22.0734 5.69155 21.6543 5.94306 21.1073 5.91306C20.3073 5.86806 19.6243 6.11957 19.0203 6.73158C18.8918 5.97706 18.4652 5.52655 17.8162 5.23754C17.4767 5.08753 17.1332 4.93703 16.8952 4.61052C16.7292 4.37801 16.6837 4.11901 16.6007 3.8635C16.5477 3.70949 16.4952 3.55199 16.3177 3.52549C16.1252 3.49549 16.0497 3.65699 15.9742 3.792C15.6722 4.34401 15.5552 4.95203 15.5667 5.56805C15.5932 6.95359 16.1782 8.05712 17.3407 8.84215C17.4727 8.93215 17.5067 9.02215 17.4652 9.15366C17.3857 9.42416 17.2917 9.68667 17.2087 9.95718C17.1557 10.1297 17.0767 10.1677 16.8917 10.0922C16.2537 9.82568 15.7027 9.43117 15.2156 8.95465C14.3891 8.15513 13.6416 7.2726 12.7096 6.58158C12.4906 6.42007 12.2716 6.27007 12.045 6.12707C11.094 5.20354 12.1696 4.44502 12.4186 4.35501C12.6791 4.26101 12.5091 3.938 11.6675 3.942C10.826 3.9455 10.056 4.22751 9.07446 4.60302C8.93096 4.65952 8.77995 4.70052 8.62545 4.73452C7.73492 4.56552 6.80989 4.52802 5.84386 4.63702C4.02481 4.83953 2.57177 5.69955 1.50373 7.1676C0.220694 8.93215 -0.0813148 10.9372 0.288196 13.0283C0.676708 15.2323 1.80174 17.0569 3.53029 18.4834C5.32285 19.9625 7.38741 20.6875 9.74298 20.5485C11.1735 20.466 12.7661 20.2745 14.5626 18.7539C15.0156 18.9795 15.4912 19.0695 16.2797 19.137C16.8872 19.1935 17.4722 19.107 17.9252 19.013C18.6347 18.8629 18.5857 18.2059 18.3292 18.0854C16.2497 17.1169 16.7062 17.5109 16.2912 17.1919C17.3477 15.9419 18.9618 13.7198 19.4598 10.6942C19.5088 10.3602 19.5713 9.88968 19.5638 9.61917C19.5598 9.45417 19.5978 9.39016 19.7863 9.37116C20.3073 9.31116 20.8128 9.16866 21.2773 8.91315C22.6249 8.17713 23.1684 6.96809 23.2964 5.51905C23.3154 5.29754 23.2924 5.06853 23.0584 4.95203ZM11.3165 17.9954C9.30097 16.4109 8.32344 15.8894 7.91992 15.9119C7.54241 15.9344 7.61042 16.3664 7.69342 16.6479C7.78042 16.9259 7.89342 17.1174 8.05193 17.3614C8.16143 17.5229 8.23694 17.7629 7.94243 17.9434C7.29341 18.3449 6.16487 17.8084 6.11187 17.7819C4.79833 17.0084 3.7003 15.9874 2.92628 14.5908C2.17875 13.2468 1.74474 11.8047 1.67324 10.2657C1.65424 9.89418 1.76374 9.76267 2.13375 9.69517C2.62077 9.60517 3.12278 9.58617 3.6093 9.65767C5.66636 9.95818 7.41741 10.8777 8.88545 12.3348C9.72348 13.1643 10.3575 14.1558 11.0105 15.1243C11.705 16.1529 12.4521 17.1329 13.4036 17.9364C13.7396 18.2179 14.0076 18.4319 14.2641 18.5899C13.4906 18.6764 12.1996 18.6949 11.3165 17.9964V17.9954ZM12.2826 11.7817C12.2826 11.6167 12.4146 11.4852 12.5806 11.4852C12.6181 11.4852 12.6521 11.4927 12.6826 11.5037C12.7241 11.5187 12.7621 11.5412 12.7921 11.5752C12.8451 11.6277 12.8751 11.7027 12.8751 11.7817C12.8751 11.9467 12.7431 12.0782 12.5771 12.0782C12.4111 12.0782 12.2826 11.9467 12.2826 11.7817ZM15.2831 13.3208C15.0906 13.3998 14.8981 13.4673 14.7131 13.4748C14.4261 13.4898 14.1131 13.3733 13.9431 13.2308C13.6791 13.0093 13.4901 12.8853 13.4111 12.4988C13.3771 12.3338 13.3961 12.0782 13.4261 11.9317C13.4941 11.6162 13.4186 11.4137 13.1961 11.2297C13.0151 11.0797 12.7846 11.0382 12.5316 11.0382C12.4371 11.0382 12.3506 10.9967 12.2861 10.9632C12.1806 10.9107 12.0936 10.7792 12.1766 10.6177C12.2031 10.5652 12.3316 10.4377 12.3616 10.4152C12.7051 10.2197 13.1011 10.2837 13.4676 10.4302C13.8071 10.5692 14.0641 10.8242 14.4336 11.1847C14.8111 11.6202 14.8791 11.7402 15.0941 12.0672C15.2641 12.3228 15.4186 12.5853 15.5247 12.8858C15.5887 13.0733 15.5057 13.2268 15.2831 13.3208Z"/>
|
|
3
|
-
</svg>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
2
|
+
<path fill="currentColor" d="M23.0584 4.95203C22.8129 4.83203 22.7074 5.06103 22.5639 5.17704C22.5149 5.21454 22.4734 5.26354 22.4319 5.30854C22.0734 5.69155 21.6543 5.94306 21.1073 5.91306C20.3073 5.86806 19.6243 6.11957 19.0203 6.73158C18.8918 5.97706 18.4652 5.52655 17.8162 5.23754C17.4767 5.08753 17.1332 4.93703 16.8952 4.61052C16.7292 4.37801 16.6837 4.11901 16.6007 3.8635C16.5477 3.70949 16.4952 3.55199 16.3177 3.52549C16.1252 3.49549 16.0497 3.65699 15.9742 3.792C15.6722 4.34401 15.5552 4.95203 15.5667 5.56805C15.5932 6.95359 16.1782 8.05712 17.3407 8.84215C17.4727 8.93215 17.5067 9.02215 17.4652 9.15366C17.3857 9.42416 17.2917 9.68667 17.2087 9.95718C17.1557 10.1297 17.0767 10.1677 16.8917 10.0922C16.2537 9.82568 15.7027 9.43117 15.2156 8.95465C14.3891 8.15513 13.6416 7.2726 12.7096 6.58158C12.4906 6.42007 12.2716 6.27007 12.045 6.12707C11.094 5.20354 12.1696 4.44502 12.4186 4.35501C12.6791 4.26101 12.5091 3.938 11.6675 3.942C10.826 3.9455 10.056 4.22751 9.07446 4.60302C8.93096 4.65952 8.77995 4.70052 8.62545 4.73452C7.73492 4.56552 6.80989 4.52802 5.84386 4.63702C4.02481 4.83953 2.57177 5.69955 1.50373 7.1676C0.220694 8.93215 -0.0813148 10.9372 0.288196 13.0283C0.676708 15.2323 1.80174 17.0569 3.53029 18.4834C5.32285 19.9625 7.38741 20.6875 9.74298 20.5485C11.1735 20.466 12.7661 20.2745 14.5626 18.7539C15.0156 18.9795 15.4912 19.0695 16.2797 19.137C16.8872 19.1935 17.4722 19.107 17.9252 19.013C18.6347 18.8629 18.5857 18.2059 18.3292 18.0854C16.2497 17.1169 16.7062 17.5109 16.2912 17.1919C17.3477 15.9419 18.9618 13.7198 19.4598 10.6942C19.5088 10.3602 19.5713 9.88968 19.5638 9.61917C19.5598 9.45417 19.5978 9.39016 19.7863 9.37116C20.3073 9.31116 20.8128 9.16866 21.2773 8.91315C22.6249 8.17713 23.1684 6.96809 23.2964 5.51905C23.3154 5.29754 23.2924 5.06853 23.0584 4.95203ZM11.3165 17.9954C9.30097 16.4109 8.32344 15.8894 7.91992 15.9119C7.54241 15.9344 7.61042 16.3664 7.69342 16.6479C7.78042 16.9259 7.89342 17.1174 8.05193 17.3614C8.16143 17.5229 8.23694 17.7629 7.94243 17.9434C7.29341 18.3449 6.16487 17.8084 6.11187 17.7819C4.79833 17.0084 3.7003 15.9874 2.92628 14.5908C2.17875 13.2468 1.74474 11.8047 1.67324 10.2657C1.65424 9.89418 1.76374 9.76267 2.13375 9.69517C2.62077 9.60517 3.12278 9.58617 3.6093 9.65767C5.66636 9.95818 7.41741 10.8777 8.88545 12.3348C9.72348 13.1643 10.3575 14.1558 11.0105 15.1243C11.705 16.1529 12.4521 17.1329 13.4036 17.9364C13.7396 18.2179 14.0076 18.4319 14.2641 18.5899C13.4906 18.6764 12.1996 18.6949 11.3165 17.9964V17.9954ZM12.2826 11.7817C12.2826 11.6167 12.4146 11.4852 12.5806 11.4852C12.6181 11.4852 12.6521 11.4927 12.6826 11.5037C12.7241 11.5187 12.7621 11.5412 12.7921 11.5752C12.8451 11.6277 12.8751 11.7027 12.8751 11.7817C12.8751 11.9467 12.7431 12.0782 12.5771 12.0782C12.4111 12.0782 12.2826 11.9467 12.2826 11.7817ZM15.2831 13.3208C15.0906 13.3998 14.8981 13.4673 14.7131 13.4748C14.4261 13.4898 14.1131 13.3733 13.9431 13.2308C13.6791 13.0093 13.4901 12.8853 13.4111 12.4988C13.3771 12.3338 13.3961 12.0782 13.4261 11.9317C13.4941 11.6162 13.4186 11.4137 13.1961 11.2297C13.0151 11.0797 12.7846 11.0382 12.5316 11.0382C12.4371 11.0382 12.3506 10.9967 12.2861 10.9632C12.1806 10.9107 12.0936 10.7792 12.1766 10.6177C12.2031 10.5652 12.3316 10.4377 12.3616 10.4152C12.7051 10.2197 13.1011 10.2837 13.4676 10.4302C13.8071 10.5692 14.0641 10.8242 14.4336 11.1847C14.8111 11.6202 14.8791 11.7402 15.0941 12.0672C15.2641 12.3228 15.4186 12.5853 15.5247 12.8858C15.5887 13.0733 15.5057 13.2268 15.2831 13.3208Z"/>
|
|
3
|
+
</svg>
|