dsh-aris-panel 0.1.5 → 0.1.7

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.
Files changed (79) hide show
  1. package/dsh/client.js +203 -21
  2. package/dsh/index.mjs +17 -0
  3. package/dsh/workbench.mjs +214 -30
  4. package/mcp-servers/claude-review/README.md +3 -0
  5. package/package.json +51 -51
  6. package/skills/auto-paper-improvement-loop/SKILL.md +50 -6
  7. package/skills/auto-paper-improvement-loop-claude/SKILL.md +633 -0
  8. package/skills/auto-review-loop-claude/SKILL.md +524 -0
  9. package/skills/citation-audit-claude/SKILL.md +512 -0
  10. package/skills/experiment-audit/SKILL.md +4 -4
  11. package/skills/experiment-audit-claude/SKILL.md +321 -0
  12. package/skills/experiment-bridge/SKILL.md +13 -0
  13. package/skills/gpt-image-2/SKILL.md +67 -0
  14. package/skills/idea-creator/SKILL.md +59 -17
  15. package/skills/idea-discovery/SKILL.md +24 -5
  16. package/skills/idea-discovery-robot/SKILL.md +13 -0
  17. package/skills/kill-argument/SKILL.md +7 -7
  18. package/skills/kill-argument-claude/SKILL.md +447 -0
  19. package/skills/meta-apply/SKILL.md +1 -1
  20. package/skills/meta-apply-claude/SKILL.md +151 -0
  21. package/skills/novelty-check/SKILL.md +49 -8
  22. package/skills/novelty-check-claude/SKILL.md +151 -0
  23. package/skills/paper-claim-audit/SKILL.md +3 -3
  24. package/skills/paper-claim-audit-claude/SKILL.md +358 -0
  25. package/skills/paper-figure/SKILL.md +36 -2
  26. package/skills/paper-figure-claude/SKILL.md +327 -0
  27. package/skills/paper-plan-claude/SKILL.md +295 -0
  28. package/skills/paper-poster-html/SKILL.md +13 -0
  29. package/skills/paper-talk/SKILL.md +13 -0
  30. package/skills/paper-write/SKILL.md +81 -3
  31. package/skills/paper-write-claude/SKILL.md +504 -0
  32. package/skills/paper-writing/SKILL.md +41 -5
  33. package/skills/proof-checker/SKILL.md +11 -11
  34. package/skills/proof-checker-claude/SKILL.md +876 -0
  35. package/skills/rebuttal/SKILL.md +13 -0
  36. package/skills/research-pipeline/SKILL.md +25 -6
  37. package/skills/research-refine-claude/SKILL.md +740 -0
  38. package/skills/research-refine-pipeline/SKILL.md +13 -0
  39. package/skills/research-review/SKILL.md +33 -8
  40. package/skills/research-review-claude/SKILL.md +185 -0
  41. package/skills/resubmit-pipeline/SKILL.md +14 -1
  42. package/skills/result-to-claim/SKILL.md +2 -2
  43. package/skills/result-to-claim-claude/SKILL.md +321 -0
  44. package/skills/shared-references/effort-contract.md +1 -1
  45. package/skills/shared-references/fan-out-pattern.md +1 -1
  46. package/skills/shared-references/reviewer-routing.md +10 -10
  47. package/skills/skills-codex/auto-paper-improvement-loop/SKILL.md +49 -5
  48. package/skills/skills-codex/experiment-audit/SKILL.md +5 -5
  49. package/skills/skills-codex/idea-creator/SKILL.md +46 -13
  50. package/skills/skills-codex/idea-discovery/SKILL.md +7 -5
  51. package/skills/skills-codex/kill-argument/SKILL.md +5 -5
  52. package/skills/skills-codex/meta-apply/SKILL.md +1 -1
  53. package/skills/skills-codex/novelty-check/SKILL.md +49 -8
  54. package/skills/skills-codex/paper-claim-audit/SKILL.md +3 -3
  55. package/skills/skills-codex/paper-write/SKILL.md +78 -2
  56. package/skills/skills-codex/paper-writing/SKILL.md +17 -4
  57. package/skills/skills-codex/proof-checker/SKILL.md +9 -9
  58. package/skills/skills-codex/research-pipeline/SKILL.md +12 -6
  59. package/skills/skills-codex/research-review/SKILL.md +32 -7
  60. package/skills/skills-codex/resubmit-pipeline/SKILL.md +1 -1
  61. package/skills/skills-codex/result-to-claim/SKILL.md +2 -2
  62. package/skills/skills-codex/shared-references/effort-contract.md +1 -1
  63. package/skills/skills-codex/shared-references/reviewer-routing.md +7 -7
  64. package/skills/skills-codex-claude-review/auto-paper-improvement-loop/SKILL.md +49 -5
  65. package/skills/skills-codex-claude-review/auto-review-loop/SKILL.md +8 -2
  66. package/skills/skills-codex-claude-review/novelty-check/SKILL.md +49 -8
  67. package/skills/skills-codex-claude-review/paper-write/SKILL.md +78 -2
  68. package/skills/skills-codex-claude-review/research-review/SKILL.md +29 -1
  69. package/skills/skills-codex-gemini-review/auto-paper-improvement-loop/SKILL.md +61 -16
  70. package/skills/skills-codex-gemini-review/idea-creator/SKILL.md +37 -13
  71. package/skills/skills-codex-gemini-review/idea-discovery/SKILL.md +4 -4
  72. package/skills/skills-codex-gemini-review/novelty-check/SKILL.md +49 -8
  73. package/skills/skills-codex-gemini-review/paper-write/SKILL.md +78 -2
  74. package/skills/skills-codex-gemini-review/paper-writing/SKILL.md +17 -4
  75. package/skills/skills-codex-gemini-review/research-review/SKILL.md +26 -1
  76. package/tools/convert_skills_to_llm_chat.py +11 -11
  77. package/tools/generate_codex_claude_review_overrides.py +67 -11
  78. package/skills/skills-codex/render-html/scripts/__pycache__/render_html.cpython-314.pyc +0 -0
  79. package/tools/__pycache__/check_skills_inventory.cpython-314.pyc +0 -0
package/dsh/client.js CHANGED
@@ -204,6 +204,39 @@ window.__ModuleLoader__.load({
204
204
 
205
205
  const WB_CHANNEL = '/aris-wb'
206
206
  const WB_TICK = String.fromCharCode(96)
207
+ const IEEE_CONF_VENUES = ['ICASSP', 'ICIP', 'ICC', 'GLOBECOM', 'INFOCOM', 'ISCAS', 'IJCNN', 'WCNC', 'VTC', 'ICME', 'ACSSC', 'MMSP']
208
+ const IEEE_JOURNAL_VENUES = ['T-PAMI', 'TIP', 'TSP', 'TWC', 'TCOM', 'JSAC', 'TASLP', 'TNNLS', 'TCSVT', 'SPL', 'TIFS', 'TCYB']
209
+ const CLAUDE_VARIANT_MAP = {
210
+ 'research-review': 'research-review-claude',
211
+ 'novelty-check': 'novelty-check-claude',
212
+ 'research-refine': 'research-refine-claude',
213
+ 'auto-review-loop': 'auto-review-loop-claude',
214
+ 'paper-plan': 'paper-plan-claude',
215
+ 'paper-figure': 'paper-figure-claude',
216
+ 'paper-write': 'paper-write-claude',
217
+ 'auto-paper-improvement-loop': 'auto-paper-improvement-loop-claude',
218
+ 'proof-checker': 'proof-checker-claude',
219
+ 'kill-argument': 'kill-argument-claude',
220
+ 'paper-claim-audit': 'paper-claim-audit-claude',
221
+ 'citation-audit': 'citation-audit-claude',
222
+ 'result-to-claim': 'result-to-claim-claude',
223
+ 'experiment-audit': 'experiment-audit-claude',
224
+ 'meta-apply': 'meta-apply-claude',
225
+ }
226
+ const CLAUDE_VARIANT_NAMES = 'research-review、novelty-check、research-refine、auto-review-loop、paper-plan、paper-figure、paper-write、auto-paper-improvement-loop、proof-checker、kill-argument、paper-claim-audit、citation-audit、result-to-claim、experiment-audit、meta-apply'
227
+ const CLAUDE_WORKFLOWS = ['idea-discovery', 'idea-discovery-robot', 'research-pipeline', 'research-refine-pipeline', 'experiment-bridge', 'paper-writing', 'rebuttal', 'resubmit-pipeline', 'paper-talk', 'paper-poster-html']
228
+ function wbClaudeUnsupportedHint(skillName) {
229
+ return '当前技能「' + String(skillName || '') + '」暂无 Claude 审稿变体,也不是受支持的工作流。' +
230
+ 'Claude 审稿可用:独立审稿/写作技能(' + CLAUDE_VARIANT_NAMES + '),或工作流(' + CLAUDE_WORKFLOWS.join('、') + ')。请改选其一,或把审稿路由改回 codex/auto。'
231
+ }
232
+ function wbStripParam(text, key) {
233
+ if (typeof text !== 'string' || text === '') return ''
234
+ const safe = String(key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
235
+ let out = text.replace(new RegExp('(^|,\\s*)' + safe + '\\s*:\\s*[^,]+', 'gi'), ',')
236
+ out = out.replace(/,\s*,/g, ',').replace(/^,\s*/, '').replace(/,\s*$/, '').trim()
237
+ return out
238
+ }
239
+ function wbStripReviewerModel(text) { return wbStripParam(text, 'reviewer-model') }
207
240
  const FLOW_STAGES = [
208
241
  { id: 1, title: '课题现状与可行性', desc: '提出方向,调研现状与可行性', skills: ['research-lit', 'idea-discovery', 'arxiv', 'deepxiv'], next: '用 research-lit 调研现状,或用 idea-discovery 一次跑完「现状→想法→排名」' },
209
242
  { id: 2, title: '细化理解形成思路', desc: '来回交流,打磨出研究思路', skills: ['idea-creator', 'novelty-check', 'kill-argument', 'research-review'], next: 'idea-creator 补新想法、novelty-check 查新、kill-argument 对抗检验后敲定思路' },
@@ -223,7 +256,7 @@ function inferStage(ov) {
223
256
 
224
257
  const WORKFLOW_CHIPS = [
225
258
  { num: 'W1', label: 'Idea Discovery 想法发现', entry: 'idea-discovery', when: '开始新研究,有方向但还没想法时', desc: '完整想法发现管线:多源文献调研 → 头脑风暴 8-12 个具体想法 → 可行性与算力过滤 → 深度新颖性验证 + 魔鬼代言人审 → top 2-3 想法并行 GPU 试跑(30min-2h) → 按实验信号排名 → 精炼出问题锚定提案 → claim 驱动的实验路线图。产出 IDEA_REPORT.md 排名报告 + FINAL_PROPOSAL + EXPERIMENT_PLAN。' },
226
- { num: 'W1.5', label: 'Experiment Bridge 实验桥接', entry: 'experiment-bridge', when: '已有实验计划,需要落地实现代码并跑出结果时', desc: '把实验计划变成运行结果:解析 EXPERIMENT_PLAN.md → 实现实验代码(带 argparse/日志/种子) → GPT-5.6-Sol 审代码抓逻辑 bug → 最小实验 sanity check → 部署 GPU 跑全套 → 收集结果更新实验追踪。' },
259
+ { num: 'W1.5', label: 'Experiment Bridge 实验桥接', entry: 'experiment-bridge', when: '已有实验计划,需要落地实现代码并跑出结果时', desc: '把实验计划变成运行结果:解析 EXPERIMENT_PLAN.md → 实现实验代码(带 argparse/日志/种子) → GPT-6-Astra 审代码抓逻辑 bug → 最小实验 sanity check → 部署 GPU 跑全套 → 收集结果更新实验追踪。' },
227
260
  { num: 'W2', label: 'Auto Review Loop 自动审稿循环', entry: 'auto-review-loop', when: '论文/结果需要迭代打磨、想挂机自动改进时', desc: '无人值守审稿循环:外部审稿人深审论文/claims/实验指出弱点 → 自动修复(重写章节/加 baseline/补实验) → 重新评估,循环直到 ≥6/10 或 4 轮上限;>4 GPU 小时的实验自动跳过并标记人工跟进,断点可从 REVIEW_STATE.json 恢复。' },
228
261
  { num: 'W3', label: 'Paper Writing 论文写作', entry: 'paper-writing', when: '叙事报告就绪、准备把研究写成论文时', desc: '从叙事报告到可投稿 PDF:claims-evidence 矩阵 + 章节规划 → 数据驱动图表 → 逐节 LaTeX → 编译 PDF 修错 + 页数检查 → 两轮外部内容审 + 最终格式检查。' },
229
262
  { num: 'W4', label: 'Rebuttal 回复审稿', entry: 'rebuttal', when: '收到会议/期刊审稿意见时', desc: '安全回复审稿意见:解析意见拆成 issue 卡片 → 全局策略 + 逐审稿人优先级 → 可选自动补实验 → 起草回复 → 6 项安全检查(覆盖/溯源/承诺/语气/一致性/字数) → GPT 压力测试 → 输出限字版 + 富文本版。' },
@@ -394,6 +427,14 @@ const SKILL_ZH = {
394
427
  "wiki-enrich": "wiki 补全",
395
428
  "writing-systems-papers": "系统论文写作指南",
396
429
  "xlsx": "Excel 表格处理",
430
+ "research-review-claude": "研究深度审稿(Claude Code)",
431
+ "novelty-check-claude": "新颖性查新(Claude Code)",
432
+ "research-refine-claude": "研究方案精炼(Claude Code)",
433
+ "auto-review-loop-claude": "自动审稿循环(Claude Code)",
434
+ "paper-plan-claude": "论文大纲规划(Claude Code)",
435
+ "paper-figure-claude": "论文图表生成(Claude Code)",
436
+ "paper-write-claude": "论文写作(Claude Code)",
437
+ "auto-paper-improvement-loop-claude": "论文自动改进循环(Claude Code)",
397
438
  }
398
439
 
399
440
  function WbProgress(props) {
@@ -430,18 +471,68 @@ function WbTrigger(props) {
430
471
  const [effort, setEffort] = React.useState('balanced')
431
472
  const [assurance, setAssurance] = React.useState('draft')
432
473
  const [difficulty, setDifficulty] = React.useState('medium')
474
+ const [illustration, setIllustration] = React.useState('figurespec')
475
+ const [figureCheck, setFigureCheck] = React.useState('visual')
433
476
  const [extra, setExtra] = React.useState('')
434
477
  const [venue, setVenue] = React.useState('')
478
+ const [ieeeVenue, setIeeeVenue] = React.useState('')
435
479
  const [gpu, setGpu] = React.useState('')
436
480
  const [reviewer, setReviewer] = React.useState('auto')
481
+ const [reviewerModel, setReviewerModel] = React.useState('')
482
+ const [codexModels, setCodexModels] = React.useState([])
483
+ const [claudeModels, setClaudeModels] = React.useState([])
437
484
  const [humanCheckpoint, setHumanCheckpoint] = React.useState(false)
438
485
  const [autoProceed, setAutoProceed] = React.useState(true)
439
486
  const [busy, setBusy] = React.useState(false)
487
+ const [modelOpen, setModelOpen] = React.useState(false)
488
+ const modelDirtyRef = React.useRef(false)
489
+
490
+ const loadModels = function (route) {
491
+ const isClaude = route === 'claude'
492
+ if (!isClaude && route !== 'codex' && route !== 'auto') {
493
+ setCodexModels([])
494
+ if (!modelDirtyRef.current) setReviewerModel('')
495
+ return
496
+ }
497
+ const apply = function (ok, models, suggested) {
498
+ if (isClaude) setClaudeModels(models); else setCodexModels(models)
499
+ if (modelDirtyRef.current) return
500
+ const preferred = isClaude ? 'claude-fable-5' : 'gpt-5.6-sol'
501
+ const sug = ok && typeof suggested === 'string' && suggested !== '' ? suggested : ''
502
+ const hasPreferred = models.some(function (m) { return m && m.slug === preferred })
503
+ setReviewerModel(sug !== '' ? sug : (hasPreferred ? preferred : (models.length > 0 ? models[0].slug : preferred)))
504
+ }
505
+ if (isClaude) setClaudeModels([]); else setCodexModels([])
506
+ connection.rpc.call(WB_CHANNEL, isClaude ? 'claude-models' : 'codex-models', {}).then(function (res) {
507
+ const ok = !!(res && res.ok)
508
+ apply(ok, ok && Array.isArray(res.value.models) ? res.value.models : [], ok ? res.value.suggested : undefined)
509
+ }).catch(function () { apply(false, [], undefined) })
510
+ }
511
+ React.useEffect(function () { loadModels(reviewer) }, [reviewer])
512
+ React.useEffect(function () {
513
+ if (String(skill || '').endsWith('-claude') && reviewer !== 'claude') {
514
+ modelDirtyRef.current = false
515
+ loadModels('claude')
516
+ }
517
+ }, [skill])
440
518
 
441
519
  const doLaunch = function () {
442
520
  if (!skill) { setStatus('请先选择技能'); return }
521
+ let effSkill = skill
522
+ if (reviewer === 'claude' && !String(skill || '').endsWith('-claude')) {
523
+ effSkill = CLAUDE_VARIANT_MAP[skill] || (CLAUDE_WORKFLOWS.indexOf(String(skill || '')) !== -1 ? skill : '')
524
+ }
525
+ if (reviewer === 'claude' && !effSkill) {
526
+ setStatus(wbClaudeUnsupportedHint(skill))
527
+ return
528
+ }
529
+ const effVenue = venue === 'ieee-conf' || venue === 'ieee-journal' ? ieeeVenue : venue
530
+ if ((venue === 'ieee-conf' || venue === 'ieee-journal') && !ieeeVenue) {
531
+ setStatus('已选 IEEE 大类:请在下拉中选择具体的会议/期刊')
532
+ return
533
+ }
443
534
  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 })
535
+ connection.rpc.call(WB_CHANNEL, 'launch', { sessionId: sessionId, skill: effSkill || skill, args: args, autoProceed: autoProceed, effort: effort, assurance: assurance, difficulty: difficulty, illustration: illustration, figureCheck: figureCheck, venue: effVenue, gpu: gpu, reviewer: reviewer, reviewerModel: reviewerModel, humanCheckpoint: humanCheckpoint, targetSessionId: props.targetSessionId && props.targetSessionId !== '__new__' ? props.targetSessionId : undefined, newSession: props.targetSessionId === '__new__', extra: extra })
445
536
  .then(function (res) {
446
537
  if (res.ok) {
447
538
  setStatus(res.value.injected ? '已注入会话(' + res.value.method + '),执行已唤醒,请到会话窗口查看进度' : '未能注入,请复制提示词手动发送:' + res.value.prompt)
@@ -458,14 +549,33 @@ function WbTrigger(props) {
458
549
  if (effort !== 'balanced') params.push('effort: ' + effort)
459
550
  if (assurance !== 'draft') params.push('assurance: ' + assurance)
460
551
  if (difficulty !== 'medium') params.push('difficulty: ' + difficulty)
552
+ if (illustration && illustration !== 'figurespec') params.push('illustration: ' + illustration)
553
+ if (figureCheck && figureCheck !== 'visual') params.push('figure check: ' + figureCheck)
461
554
  if (humanCheckpoint === true) params.push('human checkpoint: true')
462
- if (venue) params.push('venue: ' + venue)
555
+ const effVenue = venue === 'ieee-conf' || venue === 'ieee-journal' ? ieeeVenue : venue
556
+ if (effVenue) params.push('venue: ' + effVenue)
463
557
  if (gpu) params.push('gpu: ' + gpu)
464
- if (reviewer !== 'auto') params.push('reviewer: ' + reviewer)
465
- const ex = extra.trim()
558
+ const isClaudeSkill = String(skill || '').endsWith('-claude')
559
+ if (reviewer !== 'auto' && (reviewer !== 'claude' || !isClaudeSkill)) params.push('reviewer: ' + reviewer)
560
+ let ex = extra.trim()
561
+ if (effVenue) ex = wbStripParam(ex, 'venue')
562
+ const useClaude = reviewer === 'claude' || isClaudeSkill
563
+ const rm = reviewerModel.trim()
564
+ if (rm && useClaude) {
565
+ params.push('reviewer-model: ' + rm)
566
+ ex = wbStripReviewerModel(ex)
567
+ } else if (rm && (reviewer === 'auto' || reviewer === 'codex')) {
568
+ params.push('reviewer-model: ' + rm)
569
+ ex = wbStripReviewerModel(ex)
570
+ }
466
571
  if (ex) params.push(ex)
467
572
  const arg = args.trim()
468
- return '/' + skill + (arg ? ' "' + arg + '"' : '') + (params.length > 0 ? ' — ' + params.join(', ') : '')
573
+ let effSkill = String(skill || '')
574
+ if (reviewer === 'claude' && !effSkill.endsWith('-claude')) {
575
+ effSkill = CLAUDE_VARIANT_MAP[skill] || (CLAUDE_WORKFLOWS.indexOf(skill) !== -1 ? skill : '')
576
+ }
577
+ if (reviewer === 'claude' && !effSkill) return ''
578
+ return '/' + effSkill + (arg ? ' "' + arg + '"' : '') + (params.length > 0 ? ' — ' + params.join(', ') : '')
469
579
  }
470
580
  const doCopy = function () {
471
581
  if (!skill) { setStatus('请先选择技能'); return }
@@ -477,11 +587,13 @@ function WbTrigger(props) {
477
587
  ;(skills || []).forEach(function (s) {
478
588
  selectOptions.push(h('option', { key: s.name, value: s.name }, s.name + ' — ' + (SKILL_ZH[s.name] || s.description || '').slice(0, 60)))
479
589
  })
590
+ const isClaudeModel = reviewer === 'claude' || String(skill || '').endsWith('-claude')
591
+ const modelChoices = isClaudeModel ? claudeModels : codexModels
480
592
 
481
593
  return h('div', { className: 'wb-trigger' },
482
594
  h('div', { className: 'wb-guide-title' }, '技能触发'),
483
595
  h('div', { className: 'wb-trigger-row' },
484
- h('select', { className: 'wb-skill-select', value: skill, onChange: function (e) { props.onSkill(e.target.value) } }, selectOptions),
596
+ h('select', { className: 'wb-skill-select', value: skill, onChange: function (e) { const v = e.target.value; props.onSkill(v); if (String(v).endsWith('-claude')) { if (reviewer !== 'claude') { modelDirtyRef.current = false; loadModels('claude') } } else if (reviewer !== 'claude') loadModels(reviewer) } }, selectOptions),
485
597
  h('input', { className: 'wb-args', placeholder: '研究方向 (arguments):例如 discrete diffusion LMs 的 factorized gap', value: args, onChange: function (e) { props.onArgs(e.target.value) } })),
486
598
  h('div', { className: 'wb-trigger-row wb-param-row' },
487
599
  h('label', { className: 'wb-param-label' }, '无人值守: '),
@@ -505,28 +617,60 @@ function WbTrigger(props) {
505
617
  h('option', { value: 'medium' }, 'medium (默认)'),
506
618
  h('option', { value: 'hard' }, 'hard (更强)'),
507
619
  h('option', { value: 'nightmare' }, 'nightmare (极限)'))),
620
+ h('div', { className: 'wb-trigger-row wb-param-row' },
621
+ h('label', { className: 'wb-param-label' }, '示意图: '),
622
+ h('select', { className: 'wb-param-select', value: illustration, onChange: function (e) { setIllustration(e.target.value) } },
623
+ h('option', { value: 'figurespec' }, 'figurespec (默认)'),
624
+ h('option', { value: 'gemini' }, 'gemini (AI)'),
625
+ h('option', { value: 'codex-image2' }, 'codex-image2 (AI)'),
626
+ h('option', { value: 'gpt-image-2' }, 'gpt-image-2 (本机转接)'),
627
+ h('option', { value: 'mermaid' }, 'mermaid'),
628
+ h('option', { value: 'false' }, 'false (手动)')),
629
+ h('label', { className: 'wb-param-label' }, '图面检查: '),
630
+ h('select', { className: 'wb-param-select', value: figureCheck, onChange: function (e) { setFigureCheck(e.target.value) } },
631
+ h('option', { value: 'visual' }, 'visual (看图评分, 默认)'),
632
+ h('option', { value: 'text' }, 'text (仅文字)'),
633
+ h('option', { value: 'off' }, 'off (跳过)'))),
508
634
  h('div', { className: 'wb-trigger-row wb-param-row' },
509
635
  h('label', { className: 'wb-param-label' }, '会议: '),
510
- h('select', { className: 'wb-param-select', value: venue, onChange: function (e) { setVenue(e.target.value) } },
636
+ h('select', { className: 'wb-param-select', value: venue, onChange: function (e) { const v = e.target.value; setVenue(v); if (v !== 'ieee-conf' && v !== 'ieee-journal') setIeeeVenue('') } },
511
637
  h('option', { value: '' }, '(默认)'),
512
638
  h('option', { value: 'ICLR' }, 'ICLR'), h('option', { value: 'NeurIPS' }, 'NeurIPS'),
513
639
  h('option', { value: 'ICML' }, 'ICML'), h('option', { value: 'CVPR' }, 'CVPR'),
514
- h('option', { value: 'AAAI' }, 'AAAI'), h('option', { value: 'ACL' }, 'ACL')),
640
+ h('option', { value: 'AAAI' }, 'AAAI'), h('option', { value: 'ACL' }, 'ACL'),
641
+ h('option', { value: 'ieee-conf' }, 'IEEE Conf (IEEEtran)…'),
642
+ h('option', { value: 'ieee-journal' }, 'IEEE Journal (IEEEtran)…')),
643
+ (venue === 'ieee-conf' || venue === 'ieee-journal') ? h('select', { className: 'wb-param-select', value: ieeeVenue, onChange: function (e) { setIeeeVenue(e.target.value) } },
644
+ h('option', { value: '' }, '(选择具体' + (venue === 'ieee-conf' ? '会议' : '期刊') + '…)'),
645
+ (venue === 'ieee-conf' ? IEEE_CONF_VENUES : IEEE_JOURNAL_VENUES).map(function (v) { return h('option', { key: v, value: v }, v) })) : null,
515
646
  h('label', { className: 'wb-param-label' }, 'GPU: '),
516
647
  h('select', { className: 'wb-param-select', value: gpu, onChange: function (e) { setGpu(e.target.value) } },
517
648
  h('option', { value: '' }, '(默认)'), h('option', { value: 'local' }, 'local'),
518
649
  h('option', { value: 'remote' }, 'remote'), h('option', { value: 'vast' }, 'vast'),
519
650
  h('option', { value: 'modal' }, 'modal')),
520
651
  h('label', { className: 'wb-param-label' }, '审稿路由: '),
521
- h('select', { className: 'wb-param-select', value: reviewer, onChange: function (e) { setReviewer(e.target.value) } },
652
+ h('select', { className: 'wb-param-select', value: reviewer, onChange: function (e) { const v = e.target.value; modelDirtyRef.current = false; setReviewer(v) } },
522
653
  h('option', { value: 'auto' }, 'auto (默认)'), h('option', { value: 'codex' }, 'codex'),
654
+ h('option', { value: 'claude' }, 'claude (Claude Code)'),
523
655
  h('option', { value: 'oracle-pro' }, 'oracle-pro'), h('option', { value: 'manual' }, 'manual')),
656
+ (reviewer === 'auto' || reviewer === 'codex' || reviewer === 'claude' || String(skill || '').endsWith('-claude')) ? [
657
+ h('label', { className: 'wb-param-label', key: 'wb-rm-label' }, '审稿模型: '),
658
+ h('span', { className: 'wb-model-combo', key: 'wb-rm-combo' },
659
+ h('input', { className: 'wb-param-input', placeholder: isClaudeModel ? '选择或手动输入;留空=claude-fable-5' : '选择或手动输入;留空=默认 gpt-5.6-sol', value: reviewerModel, onFocus: function () { setModelOpen(true) }, onBlur: function () { setTimeout(function () { setModelOpen(false) }, 150) }, onChange: function (e) { modelDirtyRef.current = true; setReviewerModel(e.target.value); setModelOpen(false) } }),
660
+ h('button', { type: 'button', className: 'wb-model-btn', onMouseDown: function (e) { e.preventDefault() }, onClick: function () { setModelOpen(!modelOpen) } }, '▾'),
661
+ modelOpen ? h('div', { className: 'wb-model-pop', key: 'wb-rm-pop' },
662
+ modelChoices.length > 0 ? modelChoices.map(function (m) {
663
+ return h('div', { className: 'wb-model-opt', key: m.slug, onMouseDown: function (e) { e.preventDefault() }, onClick: function () { modelDirtyRef.current = true; setReviewerModel(m.slug); setModelOpen(false) } },
664
+ (m.display || m.slug) + (Array.isArray(m.levels) && m.levels.length ? ' · 推理档:' + m.levels.join('/') : ''))
665
+ }) : [h('div', { className: 'wb-model-empty', key: 'empty' }, isClaudeModel ? 'Claude 模型列表加载中或不可用,可手动输入' : 'Codex 模型列表加载中或不可用,可手动输入')])
666
+ : null),
667
+ ] : null,
524
668
  h('label', { className: 'wb-param-label' }, '人工确认: '),
525
669
  h('select', { className: 'wb-param-select', value: humanCheckpoint ? 'true' : 'false', onChange: function (e) { setHumanCheckpoint(e.target.value === 'true') } },
526
670
  h('option', { value: 'false' }, 'false (默认)'), h('option', { value: 'true' }, 'true (暂停批准)'))),
527
671
  h('div', { className: 'wb-trigger-row' },
528
672
  h('input', { className: 'wb-extra', placeholder: '其他参数 key: value, key2: value2(可选,如 sources: arxiv, web / gpu: vast)', value: extra, onChange: function (e) { setExtra(e.target.value) } })),
529
- skill ? h('div', { className: 'wb-cmd-preview' }, 'ARIS 命令: ' + buildCmd()) : null,
673
+ skill ? h('div', { className: 'wb-cmd-preview', style: buildCmd() ? undefined : { color: '#c42b1c' } }, 'ARIS 命令: ' + (buildCmd() || wbClaudeUnsupportedHint(skill))) : null,
530
674
  h('div', { className: 'wb-trigger-row' },
531
675
  h('button', { className: 'wb-btn', onClick: doLaunch, disabled: busy }, busy ? '触发中…' : '触发'),
532
676
  h('button', { className: 'wb-btn wb-btn-copy', onClick: doCopy }, '复制提示词')),
@@ -534,6 +678,15 @@ function WbTrigger(props) {
534
678
  status ? h('div', { className: 'wb-status' }, status) : null)
535
679
  }
536
680
  function wbNote(text) { return h('p', { className: 'wb-note' }, text) }
681
+ function wbWhen(ms) {
682
+ const seconds = Math.max(0, Math.round((Date.now() - ms) / 1000))
683
+ if (seconds < 60) return seconds + 's前'
684
+ const minutes = Math.round(seconds / 60)
685
+ if (minutes < 60) return minutes + '分钟前'
686
+ const hours = Math.round(minutes / 60)
687
+ if (hours < 24) return hours + '小时前'
688
+ return Math.round(hours / 24) + '天前'
689
+ }
537
690
  function wbKey(label, value) {
538
691
  return h('div', { className: 'wb-row' }, h('span', { className: 'wb-key' }, label), h('span', { className: 'wb-val' }, value))
539
692
  }
@@ -683,19 +836,23 @@ function ArisWorkbenchView(props) {
683
836
  const [args, setArgs] = React.useState('')
684
837
  const [permPreset, setPermPreset] = React.useState('read-only')
685
838
  const [permStatus, setPermStatus] = React.useState(undefined)
839
+ const [targetSessionId, setTargetSessionId] = React.useState(undefined)
840
+ const [workspaceId, setWorkspaceId] = React.useState(undefined)
686
841
 
687
842
  React.useEffect(function () {
688
843
  let live = true
689
- connection.rpc.call(WB_CHANNEL, 'sessions', {})
844
+ connection.rpc.call(WB_CHANNEL, 'workspaces', {})
690
845
  .then(function (res) {
691
846
  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)
847
+ const list = res.ok && res.value ? res.value.workspaces : undefined
848
+ if (Array.isArray(list) && list.length > 0) {
849
+ setWorkspaces(list)
850
+ const first = list[0]
851
+ setWorkspaceId(first.id)
852
+ if (sessionId === undefined && first.sessions && first.sessions.length > 0) setSessionId(first.sessions[0].id)
696
853
  } else {
697
854
  setWorkspaces([])
698
- setError(res.error ? res.error.message : 'sessions RPC failed')
855
+ setError(res.error ? res.error.message : 'workspaces RPC failed')
699
856
  }
700
857
  })
701
858
  .catch(function (cause) { if (live) { setError(String(cause)); setWorkspaces([]) } })
@@ -765,6 +922,8 @@ function ArisWorkbenchView(props) {
765
922
  const ideas = data && data.ideas
766
923
  const exps = data && data.experiments
767
924
  const stage = ov ? inferStage(ov) : 1
925
+ const curWs = workspaces && workspaceId ? workspaces.find(function (w) { return w.id === workspaceId }) : (workspaces && workspaces.length > 0 ? workspaces[0] : undefined)
926
+ const wsSessions = curWs && Array.isArray(curWs.sessions) ? curWs.sessions.filter(function (s) { return s.title || s.msgCount > 0 }) : []
768
927
 
769
928
  const children = []
770
929
  children.push(h('div', { className: 'wb-header', key: 'head' },
@@ -775,17 +934,31 @@ function ArisWorkbenchView(props) {
775
934
  if (workspaces && workspaces.length > 1) {
776
935
  children.push(h('div', { className: 'wb-picker', key: 'picker' },
777
936
  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) }))))
937
+ h('select', { value: workspaceId, onChange: function (e) {
938
+ const w = workspaces.find(function (x) { return x.id === e.target.value })
939
+ setWorkspaceId(e.target.value)
940
+ setTargetSessionId(undefined)
941
+ if (w && w.sessions && w.sessions.length > 0) setSessionId(w.sessions[0].id)
942
+ } },
943
+ workspaces.map(function (w) {
944
+ const base = w.path ? String(w.path).split(/[\\/]/).filter(Boolean).pop() : ''
945
+ return h('option', { key: w.id, value: w.id }, w.title + (base && base !== w.title ? ' (' + base + ')' : ''))
946
+ }))))
780
947
  }
781
948
  if (workspaces && workspaces.length === 0) {
782
- children.push(wbNote('无活动会话 — 请先在 DSH 中打开/新建一个工作区会话,然后点「刷新」'))
949
+ children.push(wbNote('无工作区数据 — 请先在 DSH 中打开一个工作区,然后点「刷新」'))
783
950
  }
784
951
  if (error) children.push(h('div', { className: 'wb-error', key: 'err' }, error))
785
952
  if (sessionId === undefined && workspaces === undefined) children.push(wbNote('正在连接工作区…'))
786
953
  if (sessionId !== undefined) {
787
954
 
788
955
  children.push(h('div', { className: 'wb-picker', key: 'pctl' },
956
+ h('label', null, '会话: '),
957
+ h('select', { className: 'wb-perm-select', value: targetSessionId || (wsSessions.length > 0 ? wsSessions[0].id : ''), onChange: function (e) { setTargetSessionId(e.target.value) } },
958
+ wsSessions.map(function (s) {
959
+ const label = (s.title || '未命名会话') + ' [' + s.id.slice(0, 8) + ']' + (s.updatedAt ? ' · ' + wbWhen(s.updatedAt) : '') + (s.msgCount ? ' (' + s.msgCount + '条)' : '')
960
+ return h('option', { key: s.id, value: s.id, title: s.id }, label)
961
+ })),
789
962
  h('label', null, '权限: '),
790
963
  h('select', { className: 'wb-perm-select', value: permPreset, onChange: function (e) {
791
964
  const v = e.target.value
@@ -800,7 +973,7 @@ function ArisWorkbenchView(props) {
800
973
  permStatus ? h('span', { className: 'wb-perm-status' }, permStatus) : null))
801
974
  children.push(h(WbPanorama, { key: 'pano', onPick: function (name) { setSkill(name) } }))
802
975
  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 }))
976
+ 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
977
  }
805
978
  if (sessionId !== undefined && data === undefined && !error) children.push(wbNote('加载中…'))
806
979
  if (data) {
@@ -919,6 +1092,15 @@ const WB_CSS = `.wb-overlay { position: fixed; inset: 0; z-index: 9999; backgrou
919
1092
  .wb-verdict-? { background: #f6f8fa; color: #57606a; }
920
1093
  .wb-extra { flex: 1 1 100%; background: #ffffff; color: #1f1f1f; border: 1px solid #c8c8c8; border-radius: 4px; padding: 5px 10px; font-size: 12px; min-width: 200px; }
921
1094
  .wb-cmd-preview { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: #24292e; background: #f5f5f5; border: 1px solid #e0e0e0; border-radius: 6px; padding: 8px 10px; margin: 6px 0 10px; word-break: break-all; }
1095
+ .wb-param-input { background: #ffffff; color: #1f1f1f; border: 1px solid #c8c8c8; border-radius: 4px; padding: 3px 6px; font-size: 12px; min-width: 170px; width: 200px; }
1096
+ .wb-model-combo { position: relative; display: inline-flex; align-items: center; }
1097
+ .wb-model-btn { margin-left: 3px; padding: 2px 7px; background: #f2f2f2; color: #1f1f1f; border: 1px solid #c8c8c8; border-radius: 4px; font-size: 11px; cursor: pointer; line-height: 1.4; }
1098
+ .wb-model-btn:hover { background: #e3e3e3; }
1099
+ .wb-model-pop { position: absolute; top: calc(100% + 4px); left: 0; z-index: 80; background: #ffffff; color: #1f1f1f; border: 1px solid #c8c8c8; border-radius: 4px; box-shadow: 0 2px 10px rgba(0, 0, 0, .18); max-height: 240px; overflow-y: auto; min-width: 330px; }
1100
+ .wb-model-opt { padding: 6px 10px; font-size: 12px; cursor: pointer; border-bottom: 1px solid #f0f0f0; white-space: nowrap; }
1101
+ .wb-model-opt:hover { background: #e8f0fe; }
1102
+ .wb-model-opt:last-child { border-bottom: none; }
1103
+ .wb-model-empty { padding: 6px 10px; font-size: 12px; color: #6e7781; }
922
1104
 
923
1105
  .wb-pano-card-when { font-size: 13px; color: #1f1f1f; }
924
1106
 
package/dsh/index.mjs CHANGED
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { fileURLToPath } from 'node:url'
12
12
  import * as skillFilesystem from '@deepseek-ai/dsh-skill-filesystem'
13
+ import * as mcpClient from '@deepseek-ai/dsh-mcp-client'
13
14
  import { registerRunStatus } from './run-status.mjs'
14
15
  import { registerWorkbench } from './workbench.mjs'
15
16
 
@@ -19,6 +20,7 @@ export const inject = ['skills', 'tools']
19
20
  /** Package root; identical in a git checkout and under node_modules. */
20
21
  const PACKAGE_ROOT = fileURLToPath(new URL('../', import.meta.url))
21
22
  const SKILL_ROOT = fileURLToPath(new URL('../skills/', import.meta.url))
23
+ const CLAUDE_REVIEW_SERVER = fileURLToPath(new URL('../mcp-servers/claude-review/server.py', import.meta.url))
22
24
 
23
25
  /** Codex MCP tools whose reply carries a continuation threadId. */
24
26
  const CODEX_TOOLS = new Set(['mcp__codex__codex', 'mcp__codex__codex-reply'])
@@ -44,6 +46,21 @@ export async function apply(ctx) {
44
46
  watch: false,
45
47
  })
46
48
 
49
+ // Claude reviewer bridge (claude-review MCP): drives the local Claude Code CLI
50
+ // as an external reviewer. Each review call may carry its own `model`; the
51
+ // workbench picker resolves to a default of claude-fable-5. Not fatal on
52
+ // startup failure — the Codex route keeps working and the Claude route shows
53
+ // the underlying error.
54
+ await ctx.plugin(mcpClient, {
55
+ serverName: 'claude-review',
56
+ transport: 'stdio',
57
+ command: 'python',
58
+ args: [CLAUDE_REVIEW_SERVER],
59
+ cwd: PACKAGE_ROOT,
60
+ toolCallTimeoutMs: 1200000,
61
+ failOnStartupError: false,
62
+ })
63
+
47
64
  registerRunStatus(ctx)
48
65
  registerWorkbench(ctx)
49
66