dsh-aris-panel 0.1.5 → 0.1.6

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/dsh/client.js CHANGED
@@ -441,7 +441,7 @@ function WbTrigger(props) {
441
441
  const doLaunch = function () {
442
442
  if (!skill) { setStatus('请先选择技能'); return }
443
443
  setBusy(true)
444
- connection.rpc.call(WB_CHANNEL, 'launch', { sessionId: sessionId, skill: skill, args: args, autoProceed: autoProceed, effort: effort, assurance: assurance, difficulty: difficulty, venue: venue, gpu: gpu, reviewer: reviewer, humanCheckpoint: humanCheckpoint, extra: extra })
444
+ connection.rpc.call(WB_CHANNEL, 'launch', { sessionId: sessionId, skill: skill, args: args, autoProceed: autoProceed, effort: effort, assurance: assurance, difficulty: difficulty, venue: venue, gpu: gpu, reviewer: reviewer, humanCheckpoint: humanCheckpoint, targetSessionId: props.targetSessionId && props.targetSessionId !== '__new__' ? props.targetSessionId : undefined, newSession: props.targetSessionId === '__new__', extra: extra })
445
445
  .then(function (res) {
446
446
  if (res.ok) {
447
447
  setStatus(res.value.injected ? '已注入会话(' + res.value.method + '),执行已唤醒,请到会话窗口查看进度' : '未能注入,请复制提示词手动发送:' + res.value.prompt)
@@ -534,6 +534,15 @@ function WbTrigger(props) {
534
534
  status ? h('div', { className: 'wb-status' }, status) : null)
535
535
  }
536
536
  function wbNote(text) { return h('p', { className: 'wb-note' }, text) }
537
+ function wbWhen(ms) {
538
+ const seconds = Math.max(0, Math.round((Date.now() - ms) / 1000))
539
+ if (seconds < 60) return seconds + 's前'
540
+ const minutes = Math.round(seconds / 60)
541
+ if (minutes < 60) return minutes + '分钟前'
542
+ const hours = Math.round(minutes / 60)
543
+ if (hours < 24) return hours + '小时前'
544
+ return Math.round(hours / 24) + '天前'
545
+ }
537
546
  function wbKey(label, value) {
538
547
  return h('div', { className: 'wb-row' }, h('span', { className: 'wb-key' }, label), h('span', { className: 'wb-val' }, value))
539
548
  }
@@ -683,19 +692,23 @@ function ArisWorkbenchView(props) {
683
692
  const [args, setArgs] = React.useState('')
684
693
  const [permPreset, setPermPreset] = React.useState('read-only')
685
694
  const [permStatus, setPermStatus] = React.useState(undefined)
695
+ const [targetSessionId, setTargetSessionId] = React.useState(undefined)
696
+ const [workspaceId, setWorkspaceId] = React.useState(undefined)
686
697
 
687
698
  React.useEffect(function () {
688
699
  let live = true
689
- connection.rpc.call(WB_CHANNEL, 'sessions', {})
700
+ connection.rpc.call(WB_CHANNEL, 'workspaces', {})
690
701
  .then(function (res) {
691
702
  if (!live) return
692
- const ws = res.ok && res.value ? res.value.sessions : undefined
693
- if (Array.isArray(ws)) {
694
- setWorkspaces(ws)
695
- if (sessionId === undefined && ws.length > 0) setSessionId(ws[0].id)
703
+ const list = res.ok && res.value ? res.value.workspaces : undefined
704
+ if (Array.isArray(list) && list.length > 0) {
705
+ setWorkspaces(list)
706
+ const first = list[0]
707
+ setWorkspaceId(first.id)
708
+ if (sessionId === undefined && first.sessions && first.sessions.length > 0) setSessionId(first.sessions[0].id)
696
709
  } else {
697
710
  setWorkspaces([])
698
- setError(res.error ? res.error.message : 'sessions RPC failed')
711
+ setError(res.error ? res.error.message : 'workspaces RPC failed')
699
712
  }
700
713
  })
701
714
  .catch(function (cause) { if (live) { setError(String(cause)); setWorkspaces([]) } })
@@ -765,6 +778,8 @@ function ArisWorkbenchView(props) {
765
778
  const ideas = data && data.ideas
766
779
  const exps = data && data.experiments
767
780
  const stage = ov ? inferStage(ov) : 1
781
+ const curWs = workspaces && workspaceId ? workspaces.find(function (w) { return w.id === workspaceId }) : (workspaces && workspaces.length > 0 ? workspaces[0] : undefined)
782
+ const wsSessions = curWs && Array.isArray(curWs.sessions) ? curWs.sessions.filter(function (s) { return s.title || s.msgCount > 0 }) : []
768
783
 
769
784
  const children = []
770
785
  children.push(h('div', { className: 'wb-header', key: 'head' },
@@ -775,17 +790,31 @@ function ArisWorkbenchView(props) {
775
790
  if (workspaces && workspaces.length > 1) {
776
791
  children.push(h('div', { className: 'wb-picker', key: 'picker' },
777
792
  h('label', null, '工作区: '),
778
- h('select', { value: sessionId, onChange: function (e) { setSessionId(e.target.value) } },
779
- workspaces.map(function (s) { return h('option', { key: s.id, value: s.id }, s.cwd || s.id) }))))
793
+ h('select', { value: workspaceId, onChange: function (e) {
794
+ const w = workspaces.find(function (x) { return x.id === e.target.value })
795
+ setWorkspaceId(e.target.value)
796
+ setTargetSessionId(undefined)
797
+ if (w && w.sessions && w.sessions.length > 0) setSessionId(w.sessions[0].id)
798
+ } },
799
+ workspaces.map(function (w) {
800
+ const base = w.path ? String(w.path).split(/[\\/]/).filter(Boolean).pop() : ''
801
+ return h('option', { key: w.id, value: w.id }, w.title + (base && base !== w.title ? ' (' + base + ')' : ''))
802
+ }))))
780
803
  }
781
804
  if (workspaces && workspaces.length === 0) {
782
- children.push(wbNote('无活动会话 — 请先在 DSH 中打开/新建一个工作区会话,然后点「刷新」'))
805
+ children.push(wbNote('无工作区数据 — 请先在 DSH 中打开一个工作区,然后点「刷新」'))
783
806
  }
784
807
  if (error) children.push(h('div', { className: 'wb-error', key: 'err' }, error))
785
808
  if (sessionId === undefined && workspaces === undefined) children.push(wbNote('正在连接工作区…'))
786
809
  if (sessionId !== undefined) {
787
810
 
788
811
  children.push(h('div', { className: 'wb-picker', key: 'pctl' },
812
+ h('label', null, '会话: '),
813
+ h('select', { className: 'wb-perm-select', value: targetSessionId || (wsSessions.length > 0 ? wsSessions[0].id : ''), onChange: function (e) { setTargetSessionId(e.target.value) } },
814
+ wsSessions.map(function (s) {
815
+ const label = (s.title || '未命名会话') + ' [' + s.id.slice(0, 8) + ']' + (s.updatedAt ? ' · ' + wbWhen(s.updatedAt) : '') + (s.msgCount ? ' (' + s.msgCount + '条)' : '')
816
+ return h('option', { key: s.id, value: s.id, title: s.id }, label)
817
+ })),
789
818
  h('label', null, '权限: '),
790
819
  h('select', { className: 'wb-perm-select', value: permPreset, onChange: function (e) {
791
820
  const v = e.target.value
@@ -800,7 +829,7 @@ function ArisWorkbenchView(props) {
800
829
  permStatus ? h('span', { className: 'wb-perm-status' }, permStatus) : null))
801
830
  children.push(h(WbPanorama, { key: 'pano', onPick: function (name) { setSkill(name) } }))
802
831
  children.push(h(WbProgress, { key: 'progress', overview: ov, stage: stage }))
803
- children.push(h(WbTrigger, { key: 'trigger', connection: connection, sessionId: sessionId, skills: skills, skill: skill, args: args, skillError: skillError, onSkill: setSkill, onArgs: setArgs }))
832
+ children.push(h(WbTrigger, { key: 'trigger', connection: connection, sessionId: sessionId, skills: skills, skill: skill, args: args, targetSessionId: targetSessionId, skillError: skillError, onSkill: setSkill, onArgs: setArgs }))
804
833
  }
805
834
  if (sessionId !== undefined && data === undefined && !error) children.push(wbNote('加载中…'))
806
835
  if (data) {
package/dsh/workbench.mjs CHANGED
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { readdir, readFile, stat } from 'node:fs/promises'
11
11
  import { isAbsolute, join } from 'node:path'
12
+ import { homedir } from 'node:os'
12
13
 
13
14
  export const WORKBENCH_RPC_CHANNEL = '/aris-wb'
14
15
 
@@ -178,17 +179,78 @@ async function readSkills() {
178
179
  return skills
179
180
  }
180
181
 
182
+ const PROJCACHE_PATH = join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'storages', 'session_projcache.json')
183
+
184
+ /** Read the DSH session projection cache (lightweight title index). */
185
+ async function readProjCache() {
186
+ try {
187
+ const text = await readFile(PROJCACHE_PATH, 'utf8')
188
+ const parsed = JSON.parse(text)
189
+ return parsed && parsed.tables && parsed.tables.sessions ? parsed.tables.sessions : undefined
190
+ } catch {
191
+ return undefined
192
+ }
193
+ }
194
+
195
+
181
196
  export function registerWorkbench(ctx) {
182
- ctx.inject(['connection', 'sessions', 'agents'], (scoped) => {
197
+ ctx.inject(['connection', 'sessions', 'agents', 'workspaceRegistry', 'sessionPersistence'], (scoped) => {
183
198
  scoped.effect(() => scoped.connection.rpc.handle(
184
199
  WORKBENCH_RPC_CHANNEL,
185
200
  async (endpoint, payload) => {
186
- if (endpoint === 'sessions') {
201
+ if (endpoint === 'workspaces') {
187
202
  try {
188
- const list = scoped.sessions.list().map(s => ({ id: s.id, cwd: s.header?.cwd ?? undefined }))
189
- return { ok: true, value: { sessions: list } }
203
+ const proj = await readProjCache()
204
+ const sessionMetaOf = function (sid) {
205
+ let evs = undefined
206
+ try {
207
+ const s = scoped.sessions.get(sid)
208
+ if (s && Array.isArray(s.events)) evs = s.events
209
+ } catch (e) { /* ignore */ }
210
+ const out = { id: sid, title: undefined, updatedAt: undefined, msgCount: 0 }
211
+ if (Array.isArray(evs)) {
212
+ out.msgCount = evs.filter(function (ev) { return ev && ev.type === 'user/message' }).length
213
+ for (let i = evs.length - 1; i >= 0; i--) {
214
+ const ev = evs[i]
215
+ if (!ev) continue
216
+ if (out.updatedAt === undefined && typeof ev.time === 'number') out.updatedAt = ev.time
217
+ if (ev.type === 'session/title' && ev.data && typeof ev.data.title === 'string' && ev.data.title.trim() !== '' && out.title === undefined) out.title = ev.data.title
218
+ }
219
+ }
220
+ if (out.title === undefined && proj && proj[sid] && proj[sid].rows && proj[sid].rows.title) {
221
+ const v = proj[sid].rows.title.val
222
+ if (typeof v === 'string' && v.trim() !== '') out.title = v
223
+ }
224
+ if (out.title === undefined && Array.isArray(evs)) {
225
+ for (const ev of evs) {
226
+ if (ev && ev.type === 'user/message' && ev.data && ev.data.source && ev.data.source.kind === 'user') {
227
+ const blocks = ev.data.content
228
+ const text = Array.isArray(blocks) ? blocks.filter(function (b) { return b && b.type === 'text' }).map(function (b) { return b.text }).join(' ') : ''
229
+ const cleaned = String(text).replace(/\s+/g, ' ').trim()
230
+ if (cleaned !== '') {
231
+ const words = cleaned.split(' ').filter(Boolean).slice(0, 10).join(' ')
232
+ out.title = words.length > 32 ? words.slice(0, 32) : words
233
+ break
234
+ }
235
+ }
236
+ }
237
+ }
238
+ return out
239
+ }
240
+ const reg = scoped.workspaceRegistry
241
+ let archived = []
242
+ try {
243
+ const a = reg.archivedSessionIds
244
+ archived = Array.isArray(a) ? a : (typeof a === 'function' ? a() : [])
245
+ } catch (e) { /* ignore */ }
246
+ const list = typeof reg.list === 'function' ? reg.list().map(function (w) {
247
+ const sids = (Array.isArray(w.sessionIds) ? w.sessionIds : []).filter(function (sid) { return archived.indexOf(sid) === -1 })
248
+ const sessions = sids.map(sessionMetaOf)
249
+ return { id: w.id, path: w.path, title: w.title, sessions: sessions }
250
+ }) : []
251
+ return { ok: true, value: { workspaces: list } }
190
252
  } catch (error) {
191
- return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: {} } }
253
+ return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: { issues: [] } } }
192
254
  }
193
255
  }
194
256
  if (endpoint === 'skills') {
@@ -197,28 +259,28 @@ export function registerWorkbench(ctx) {
197
259
  return { ok: true, value: { skills: skills } }
198
260
  } catch (error) {
199
261
  console.error('[aris-wb] skills failed:', error)
200
- return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: {} } }
262
+ return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: { issues: [] } } }
201
263
  }
202
264
  }
203
265
  const sessionId = typeof payload === 'object' && payload !== null ? payload.sessionId : undefined
204
266
  if (typeof sessionId !== 'string') {
205
- return { ok: false, error: { code: 'bad-request', message: 'sessionId is required', details: {} } }
267
+ return { ok: false, error: { code: 'bad-request', message: 'sessionId is required', details: { issues: [] } } }
206
268
  }
207
269
  if (endpoint === 'permission') {
208
270
  const preset = typeof payload === 'object' && payload !== null ? payload.preset : undefined
209
271
  const allowed = ['read-only', 'workspace-write', 'danger-full-access']
210
272
  if (typeof preset !== 'string' || allowed.indexOf(preset) === -1) {
211
- return { ok: false, error: { code: 'bad-request', message: 'preset must be one of ' + allowed.join(', '), details: {} } }
273
+ return { ok: false, error: { code: 'bad-request', message: 'preset must be one of ' + allowed.join(', '), details: { issues: [] } } }
212
274
  }
213
275
  try {
214
276
  const session = scoped.sessions.get(sessionId)
215
277
  if (!session || typeof session.append !== 'function') {
216
- return { ok: false, error: { code: 'internal', message: 'session unavailable', details: {} } }
278
+ return { ok: false, error: { code: 'internal', message: 'session unavailable', details: { issues: [] } } }
217
279
  }
218
280
  session.append('permission/preset', { preset: preset })
219
281
  return { ok: true, value: { preset: preset } }
220
282
  } catch (error) {
221
- return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: {} } }
283
+ return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: { issues: [] } } }
222
284
  }
223
285
  }
224
286
  const workspace = scoped.sessions.get(sessionId)?.header?.cwd
@@ -264,7 +326,7 @@ export function registerWorkbench(ctx) {
264
326
  const skill = typeof payload === 'object' && payload !== null ? payload.skill : undefined
265
327
  const args = typeof payload === 'object' && payload !== null ? payload.args : undefined
266
328
  if (typeof skill !== 'string' || skill === '') {
267
- return { ok: false, error: { code: 'bad-request', message: 'skill is required', details: {} } }
329
+ return { ok: false, error: { code: 'bad-request', message: 'skill is required', details: { issues: [] } } }
268
330
  }
269
331
  const argText = typeof args === 'string' ? args.trim() : ''
270
332
  const autoProceed = typeof payload === 'object' && payload !== null ? payload.autoProceed : undefined
@@ -298,27 +360,53 @@ export function registerWorkbench(ctx) {
298
360
  }
299
361
  let injected = false
300
362
  let method = 'none'
301
- try {
302
- const agent = scoped.agents && typeof scoped.agents.get === 'function' ? scoped.agents.get(sessionId) : undefined
303
- if (agent && typeof agent.send === 'function') {
304
- agent.send(message, 'next-turn', true)
305
- injected = true
306
- method = 'agent.send'
307
- }
308
- } catch (e) { /* fall through to session.append */ }
309
- if (!injected) {
363
+ let targetId = sessionId
364
+ const newSession = typeof payload === 'object' && payload !== null ? payload.newSession === true : false
365
+ const targetSessionId = typeof payload === 'object' && payload !== null ? payload.targetSessionId : undefined
366
+ const deliver = function (sid) {
367
+ let logged = false
368
+ let woken = false
310
369
  try {
311
- const session = scoped.sessions.get(sessionId)
370
+ const session = scoped.sessions.get(sid)
312
371
  if (session && typeof session.append === 'function') {
313
- session.append('user/message', message)
314
- injected = true
315
- method = 'session.append'
372
+ session.append('user/message', message, { surfaceOp: 'append' })
373
+ logged = true
374
+ }
375
+ } catch (e) { /* ignore */ }
376
+ try {
377
+ const agent = scoped.agents && typeof scoped.agents.get === 'function' ? scoped.agents.get(sid) : undefined
378
+ if (agent && typeof agent.send === 'function') {
379
+ agent.send(message, 'next-turn', true)
380
+ woken = true
381
+ }
382
+ } catch (e) { /* ignore */ }
383
+ return logged || woken
384
+ }
385
+ if (newSession) {
386
+ try {
387
+ const newId = 'session-aris-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6)
388
+ const handle = await scoped.agents.create({ sessionId: newId, meta: { cwd: workspace } })
389
+ if (handle) {
390
+ if (deliver(newId)) {
391
+ targetId = newId
392
+ injected = true
393
+ method = 'agent.create+deliver'
394
+ }
316
395
  }
317
396
  } catch (e) {
318
- return { ok: false, error: { code: 'internal', message: String(e && e.message || e), details: {} } }
397
+ return { ok: false, error: { code: 'internal', message: '新会话创建失败: ' + String(e && e.message || e), details: { issues: [] } } }
319
398
  }
399
+ } else {
400
+ if (typeof targetSessionId === 'string' && targetSessionId !== '') targetId = targetSessionId
401
+ if (deliver(targetId)) {
402
+ injected = true
403
+ method = 'append+send'
404
+ }
405
+ }
406
+ if (!injected) {
407
+ return { ok: false, error: { code: 'internal', message: '注入失败:目标会话不可用', details: { issues: [] } } }
320
408
  }
321
- return { ok: true, value: { injected, method, prompt, sessionId, messageId } }
409
+ return { ok: true, value: { injected, method, prompt, sessionId: targetId, messageId } }
322
410
  }
323
411
 
324
412
  case 'audits': {
@@ -336,7 +424,7 @@ export function registerWorkbench(ctx) {
336
424
  return { ok: false, error: { code: 'internal', message: 'unknown ARIS workbench endpoint \"' + endpoint + '\"' } }
337
425
  }
338
426
  } catch (error) {
339
- return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: {} } }
427
+ return { ok: false, error: { code: 'internal', message: String(error && error.message || error), details: { issues: [] } } }
340
428
  }
341
429
  },
342
430
  { authority: 'loopback' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-aris-panel",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "ARIS research-workflow skills + interactive workbench panel for DeepSeek Harness (user fork of dsh-aris: 82 skills, cross-model Codex review, idea/workflow launcher, research status board).",
5
5
  "license": "MIT",
6
6
  "type": "module",