dsh-aris-panel 0.1.4 → 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
@@ -222,25 +222,25 @@ function inferStage(ov) {
222
222
  }
223
223
 
224
224
  const WORKFLOW_CHIPS = [
225
- { num: 'W1', label: 'Idea Discovery 想法发现', entry: 'idea-discovery', 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', desc: '把实验计划变成运行结果:解析 EXPERIMENT_PLAN.md → 实现实验代码(带 argparse/日志/种子) → GPT-5.6-Sol 审代码抓逻辑 bug → 最小实验 sanity check → 部署 GPU 跑全套 → 收集结果更新实验追踪。' },
227
- { num: 'W2', label: 'Auto Review Loop 自动审稿循环', entry: 'auto-review-loop', desc: '无人值守审稿循环:外部审稿人深审论文/claims/实验指出弱点 → 自动修复(重写章节/加 baseline/补实验) → 重新评估,循环直到 ≥6/10 或 4 轮上限;>4 GPU 小时的实验自动跳过并标记人工跟进,断点可从 REVIEW_STATE.json 恢复。' },
228
- { num: 'W3', label: 'Paper Writing 论文写作', entry: 'paper-writing', desc: '从叙事报告到可投稿 PDF:claims-evidence 矩阵 + 章节规划 → 数据驱动图表 → 逐节 LaTeX → 编译 PDF 修错 + 页数检查 → 两轮外部内容审 + 最终格式检查。' },
229
- { num: 'W4', label: 'Rebuttal 回复审稿', entry: 'rebuttal', desc: '安全回复审稿意见:解析意见拆成 issue 卡片 → 全局策略 + 逐审稿人优先级 → 可选自动补实验 → 起草回复 → 6 项安全检查(覆盖/溯源/承诺/语气/一致性/字数) → GPT 压力测试 → 输出限字版 + 富文本版。' },
230
- { num: 'W5', label: 'Resubmit 换会重投', entry: 'resubmit-pipeline', desc: '换会议重投:硬约束(不加实验/不改 bib/不改框架/不覆盖旧稿) → 物理隔离 + 5 层匿名检查 → 白名单微编辑 → kill-argument 对抗门 → RESUBMIT_REPORT 七态账本。' },
231
- { num: 'W6', label: 'Conference Talk 会议演讲', entry: 'paper-talk', desc: '从论文到会议演讲:演讲大纲 → Beamer + PPTX + 讲稿 + Q&A 准备 → 逐页打磨 → claim/citation/匿名审计门。' },
225
+ { 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 跑全套 → 收集结果更新实验追踪。' },
227
+ { num: 'W2', label: 'Auto Review Loop 自动审稿循环', entry: 'auto-review-loop', when: '论文/结果需要迭代打磨、想挂机自动改进时', desc: '无人值守审稿循环:外部审稿人深审论文/claims/实验指出弱点 → 自动修复(重写章节/加 baseline/补实验) → 重新评估,循环直到 ≥6/10 或 4 轮上限;>4 GPU 小时的实验自动跳过并标记人工跟进,断点可从 REVIEW_STATE.json 恢复。' },
228
+ { num: 'W3', label: 'Paper Writing 论文写作', entry: 'paper-writing', when: '叙事报告就绪、准备把研究写成论文时', desc: '从叙事报告到可投稿 PDF:claims-evidence 矩阵 + 章节规划 → 数据驱动图表 → 逐节 LaTeX → 编译 PDF 修错 + 页数检查 → 两轮外部内容审 + 最终格式检查。' },
229
+ { num: 'W4', label: 'Rebuttal 回复审稿', entry: 'rebuttal', when: '收到会议/期刊审稿意见时', desc: '安全回复审稿意见:解析意见拆成 issue 卡片 → 全局策略 + 逐审稿人优先级 → 可选自动补实验 → 起草回复 → 6 项安全检查(覆盖/溯源/承诺/语气/一致性/字数) → GPT 压力测试 → 输出限字版 + 富文本版。' },
230
+ { num: 'W5', label: 'Resubmit 换会重投', entry: 'resubmit-pipeline', when: '论文需在硬约束下换到另一个会议重投时', desc: '换会议重投:硬约束(不加实验/不改 bib/不改框架/不覆盖旧稿) → 物理隔离 + 5 层匿名检查 → 白名单微编辑 → kill-argument 对抗门 → RESUBMIT_REPORT 七态账本。' },
231
+ { num: 'W6', label: 'Conference Talk 会议演讲', entry: 'paper-talk', when: '论文录用后准备会议演讲时', desc: '从论文到会议演讲:演讲大纲 → Beamer + PPTX + 讲稿 + Q&A 准备 → 逐页打磨 → claim/citation/匿名审计门。' },
232
232
  ]
233
233
  const AUDIT_CHIPS = [
234
- { label: '实验诚实度审计', skill: 'experiment-audit', desc: '审计 eval 代码是否诚实:检查是否有假 ground truth、分数自归一化、幻影结果、范围不足——结果可信前必跑。' },
235
- { label: '结果→声明判定', skill: 'result-to-claim', desc: '判定结果支持什么:Codex 评估实验对既定 claims 的支持度,路由下一步(转向/补充/确认),实验完成后、写论文前跑。' },
236
- { label: '论文数字核对', skill: 'paper-claim-audit', desc: '核对论文数字:零上下文审稿人核对每个数字/比较/范围声明是否与原始结果一致,防止确认偏差。' },
237
- { label: '引用真实性审计', skill: 'citation-audit', desc: '审计每条引用:文献必须真实存在、归属正确、上下文确实支持——抓幻觉作者/错年份/假 venue/错上下文。' },
238
- { label: '最强拒稿模拟', skill: 'kill-argument', desc: '拒稿压力测试:新审稿人写 200 词最强拒稿信,第二名独立审稿人逐点辩护并暴露仍未解决的关键问题。' },
234
+ { label: '实验诚实度审计', skill: 'experiment-audit', when: '实验完成后、写 claim 之前', desc: '审计 eval 代码是否诚实:检查是否有假 ground truth、分数自归一化、幻影结果、范围不足——结果可信前必跑。' },
235
+ { label: '结果→声明判定', skill: 'result-to-claim', when: '实验完成、写论文之前', desc: '判定结果支持什么:Codex 评估实验对既定 claims 的支持度,路由下一步(转向/补充/确认),实验完成后、写论文前跑。' },
236
+ { label: '论文数字核对', skill: 'paper-claim-audit', when: '论文初稿完成后、投稿前', desc: '核对论文数字:零上下文审稿人核对每个数字/比较/范围声明是否与原始结果一致,防止确认偏差。' },
237
+ { label: '引用真实性审计', skill: 'citation-audit', when: '投稿前', desc: '审计每条引用:文献必须真实存在、归属正确、上下文确实支持——抓幻觉作者/错年份/假 venue/错上下文。' },
238
+ { label: '最强拒稿模拟', skill: 'kill-argument', when: '投稿前 / 冲顶会前', desc: '拒稿压力测试:新审稿人写 200 词最强拒稿信,第二名独立审稿人逐点辩护并暴露仍未解决的关键问题。' },
239
239
  ]
240
240
  const MEMORY_CHIPS = [
241
- { label: 'research-wiki 知识库', skill: 'research-wiki', desc: '跨会话研究记忆:论文/想法/实验/声明四类实体 + 图谱边;失败想法成为反重复记忆,下次构思自动避开踩过的坑。' },
242
- { label: 'wiki 补全', skill: 'wiki-enrich', desc: '补全 research-wiki 的论文页 TODO 区,把批量导入留下的骨架填充成完整摘要。' },
243
- { label: 'meta-optimize 自我优化', skill: 'meta-optimize', desc: 'ARIS 自我优化:分析 .aris/meta 使用日志,提议改进 SKILL.md 与工作流默认值,改动需审稿门控 + 人工批准。' },
241
+ { label: 'research-wiki 知识库', skill: 'research-wiki', when: '项目初始化时初始化一次,之后持续使用', desc: '跨会话研究记忆:论文/想法/实验/声明四类实体 + 图谱边;失败想法成为反重复记忆,下次构思自动避开踩过的坑。' },
242
+ { label: 'wiki 补全', skill: 'wiki-enrich', when: '批量导入论文后', desc: '补全 research-wiki 的论文页 TODO 区,把批量导入留下的骨架填充成完整摘要。' },
243
+ { label: 'meta-optimize 自我优化', skill: 'meta-optimize', when: '使用一段时间、积累了运行日志后', desc: 'ARIS 自我优化:分析 .aris/meta 使用日志,提议改进 SKILL.md 与工作流默认值,改动需审稿门控 + 人工批准。' },
244
244
  ]
245
245
  function WbPanoCard(props) {
246
246
  const it = props.item
@@ -253,6 +253,7 @@ function WbPanoCard(props) {
253
253
  h('div', { className: 'wb-pano-card-head' },
254
254
  it.num ? h('span', { className: 'wb-pano-num' }, it.num) : null,
255
255
  h('span', { className: 'wb-pano-card-title' }, it.label)),
256
+ it.when ? h('div', { className: 'wb-pano-card-when' }, '何时调用: ' + it.when) : null,
256
257
  it.desc ? h('div', { className: 'wb-pano-card-desc' }, it.desc) : null,
257
258
  h('div', { className: 'wb-pano-card-entry' }, '入口: ' + entry))
258
259
  }
@@ -287,6 +288,137 @@ function WbGuide(props) {
287
288
  cur ? h('div', { className: 'wb-guide-next' }, '下一步建议:' + cur.next) : null)
288
289
  }
289
290
 
291
+
292
+ const SKILL_ZH = {
293
+ "ablation-planner": "消融实验规划(主结果通过后规划消融)",
294
+ "academic-research-suite": "学术研究套件(深度调研/写作/审稿)",
295
+ "agent-browser": "浏览器自动化",
296
+ "alphaxiv": "论文速读(AlphaXiv 摘要)",
297
+ "analyze-results": "实验结果分析统计",
298
+ "arxiv": "arXiv 论文搜索下载",
299
+ "auto-paper-improvement-loop": "论文自动改进循环(2轮外部审+修复)",
300
+ "auto-review-loop": "自动审稿循环(审→修→再审)",
301
+ "auto-review-loop-llm": "LLM 版自动审稿循环",
302
+ "auto-review-loop-minimax": "MiniMax 版自动审稿循环",
303
+ "baseline": "基线管理(复现/对比)",
304
+ "canvas-design": "画布设计(海报/艺术图)",
305
+ "citation-audit": "引用审计(防幻觉引用)",
306
+ "claims-drafting": "专利权利要求撰写",
307
+ "claude-api": "Claude API 应用开发",
308
+ "comm-lit-review": "通信领域文献综述",
309
+ "context-window-management": "上下文窗口管理",
310
+ "deepxiv": "DeepXiv 分层论文检索",
311
+ "document-skills": "办公文档处理套件",
312
+ "docx": "Word 文档处理",
313
+ "dse-loop": "设计空间探索循环(EDA)",
314
+ "embodiment-description": "专利实施例撰写",
315
+ "exa-search": "Exa AI 网络搜索",
316
+ "experiment-audit": "实验诚实度审计",
317
+ "experiment-bridge": "实验桥接(计划→代码→结果)",
318
+ "experiment-plan": "实验路线图规划",
319
+ "experiment-queue": "实验队列调度(多任务)",
320
+ "feishu-notify": "飞书通知",
321
+ "figure-description": "专利附图说明",
322
+ "figure-spec": "确定性矢量图(FigureSpec→SVG)",
323
+ "find-skills": "技能查找与安装",
324
+ "firecrawl": "网页抓取(Firecrawl)",
325
+ "formula-derivation": "公式推导",
326
+ "gemini-search": "Gemini 论文搜索",
327
+ "gpt-image-2": "GPT Image 2 图像生成",
328
+ "grant-proposal": "基金申请书撰写",
329
+ "guizang-ppt-skill": "网页 PPT 生成",
330
+ "idea": "想法假设分析",
331
+ "idea-creator": "头脑风暴生成想法",
332
+ "idea-discovery": "想法发现全流程(W1)",
333
+ "idea-discovery-robot": "机器人领域想法发现",
334
+ "integrity-forensics": "论文诚信取证审计",
335
+ "interview-cheatsheet": "面试速查表生成",
336
+ "invention-structuring": "发明披露结构化",
337
+ "j-space": "内心工作空间(长任务推理)",
338
+ "jurisdiction-format": "专利国别格式转换",
339
+ "kill-argument": "最强拒稿模拟",
340
+ "latex-paper-en": "英文 LaTeX 论文助手",
341
+ "latex-thesis-zh": "中文 LaTeX 学位论文助手",
342
+ "mano-cua": "图形界面自动化(VLA)",
343
+ "markitdown": "文档转 Markdown",
344
+ "mermaid-diagram": "Mermaid 图表生成",
345
+ "meta-apply": "优化补丁落地(需人工批准)",
346
+ "meta-optimize": "ARIS 自我优化",
347
+ "monitor-experiment": "实验监控",
348
+ "novelty-check": "新颖性查新",
349
+ "openalex": "OpenAlex 学术检索",
350
+ "overleaf-sync": "Overleaf 双向同步",
351
+ "paper-claim-audit": "论文数字核对",
352
+ "paper-compile": "论文编译(LaTeX→PDF)",
353
+ "paper-figure": "论文图表生成",
354
+ "paper-illustration": "AI 插图生成(Gemini)",
355
+ "paper-illustration-image2": "AI 插图生成(GPT-image)",
356
+ "paper-plan": "论文大纲规划",
357
+ "paper-poster": "会议海报(已废弃)",
358
+ "paper-poster-html": "HTML 会议海报",
359
+ "paper-slides": "会议幻灯片生成",
360
+ "paper-talk": "会议演讲全流程",
361
+ "paper-write": "论文写作(逐节 LaTeX)",
362
+ "paper-writing": "论文写作全流程(W3)",
363
+ "patent-novelty-check": "专利查新",
364
+ "patent-pipeline": "专利申请全流程",
365
+ "patent-review": "专利审查意见",
366
+ "pixel-art": "像素画 SVG",
367
+ "ppt-master": "多格式 PPT 生成",
368
+ "prior-art-search": "现有技术检索",
369
+ "proof-checker": "数学证明验证",
370
+ "proof-orchestrator": "定理证明项目管理",
371
+ "proof-writer": "数学证明写作",
372
+ "qzcli": "启智平台作业管理",
373
+ "rebuttal": "审稿意见回复(W4)",
374
+ "render-html": "Markdown 产物渲染 HTML",
375
+ "research-lit": "文献调研",
376
+ "research-pipeline": "研究全流程(W1→W3)",
377
+ "research-refine": "研究方案精炼",
378
+ "research-refine-pipeline": "方案精炼+实验规划串联",
379
+ "research-review": "研究深度审稿",
380
+ "research-wiki": "研究知识库",
381
+ "resubmit-pipeline": "换会重投(W5)",
382
+ "result-to-claim": "结果→声明判定",
383
+ "run-experiment": "跑实验(GPU)",
384
+ "scientific-agent-skills-suite": "科研技能套件入口",
385
+ "semantic-scholar": "Semantic Scholar 检索",
386
+ "serverless-modal": "Modal 无服务器 GPU",
387
+ "slides-polish": "幻灯片逐页打磨",
388
+ "specification-writing": "专利说明书撰写",
389
+ "system-profile": "系统性能分析",
390
+ "training-check": "训练健康检查",
391
+ "vast-gpu": "Vast.ai 租卡",
392
+ "web-access": "联网访问",
393
+ "web-debug-search": "开发者问题检索",
394
+ "wiki-enrich": "wiki 补全",
395
+ "writing-systems-papers": "系统论文写作指南",
396
+ "xlsx": "Excel 表格处理",
397
+ }
398
+
399
+ function WbProgress(props) {
400
+ const ov = props.overview
401
+ const stage = props.stage
402
+ const pipeline = ov && ov.pipeline
403
+ const phases = pipeline && pipeline.phases ? pipeline.phases : []
404
+ const doneCount = phases.filter(function (p) { return p.status === 'accepted' || p.status === 'done' }).length
405
+ const active = phases.some(function (p) { return p.status === 'running' || p.status === 'pending' })
406
+ const pct = phases.length > 0 ? Math.round(doneCount / phases.length * 100) : 0
407
+ const cur = FLOW_STAGES.find(function (s) { return s.id === stage })
408
+ return h('div', { className: 'wb-progress' },
409
+ h('div', { className: 'wb-guide-title' }, '执行进度' + (active ? '(工作流执行中,每 10 秒自动刷新)' : '')),
410
+ pipeline ? h('div', null,
411
+ wbKey('工作流', (pipeline.runId || '(unnamed run)') + ' · executor: ' + (pipeline.executor || 'unknown')),
412
+ h('div', { className: 'wb-bar' }, h('div', { className: 'wb-bar-fill', style: { width: pct + '%' } })),
413
+ wbKey('阶段进度', doneCount + '/' + phases.length + ' 完成 (' + pct + '%)'),
414
+ h('ul', { className: 'wb-list' }, phases.map(function (p) {
415
+ return h('li', { key: p.phase },
416
+ h('span', { className: 'wb-phase-' + p.status }, p.status),
417
+ ' ', h('code', null, p.phase),
418
+ p.artifact ? ' → ' + p.artifact : '')
419
+ }))) : wbNote('尚无执行中的工作流 — 从上方「功能全景」触发一个工作流后,这里显示实时进度'),
420
+ cur ? h('div', { className: 'wb-guide-next' }, '当前阶段(我的5步): ' + cur.title + ' → 下一步: ' + cur.next) : null)
421
+ }
290
422
  function WbTrigger(props) {
291
423
  const connection = props.connection
292
424
  const sessionId = props.sessionId
@@ -295,12 +427,21 @@ function WbTrigger(props) {
295
427
  const args = props.args
296
428
  const [status, setStatus] = React.useState(undefined)
297
429
  const skillError = props.skillError
430
+ const [effort, setEffort] = React.useState('balanced')
431
+ const [assurance, setAssurance] = React.useState('draft')
432
+ const [difficulty, setDifficulty] = React.useState('medium')
433
+ const [extra, setExtra] = React.useState('')
434
+ const [venue, setVenue] = React.useState('')
435
+ const [gpu, setGpu] = React.useState('')
436
+ const [reviewer, setReviewer] = React.useState('auto')
437
+ const [humanCheckpoint, setHumanCheckpoint] = React.useState(false)
438
+ const [autoProceed, setAutoProceed] = React.useState(true)
298
439
  const [busy, setBusy] = React.useState(false)
299
440
 
300
441
  const doLaunch = function () {
301
442
  if (!skill) { setStatus('请先选择技能'); return }
302
443
  setBusy(true)
303
- connection.rpc.call(WB_CHANNEL, 'launch', { sessionId: sessionId, skill: skill, args: args, autoProceed: props.autoProceed })
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 })
304
445
  .then(function (res) {
305
446
  if (res.ok) {
306
447
  setStatus(res.value.injected ? '已注入会话(' + res.value.method + '),执行已唤醒,请到会话窗口查看进度' : '未能注入,请复制提示词手动发送:' + res.value.prompt)
@@ -311,22 +452,81 @@ function WbTrigger(props) {
311
452
  .catch(function (e) { setStatus('触发失败:' + String(e)) })
312
453
  .finally(function () { setBusy(false) })
313
454
  }
455
+ const buildCmd = function () {
456
+ const params = []
457
+ if (autoProceed === false) params.push('AUTO_PROCEED: false')
458
+ if (effort !== 'balanced') params.push('effort: ' + effort)
459
+ if (assurance !== 'draft') params.push('assurance: ' + assurance)
460
+ if (difficulty !== 'medium') params.push('difficulty: ' + difficulty)
461
+ if (humanCheckpoint === true) params.push('human checkpoint: true')
462
+ if (venue) params.push('venue: ' + venue)
463
+ if (gpu) params.push('gpu: ' + gpu)
464
+ if (reviewer !== 'auto') params.push('reviewer: ' + reviewer)
465
+ const ex = extra.trim()
466
+ if (ex) params.push(ex)
467
+ const arg = args.trim()
468
+ return '/' + skill + (arg ? ' "' + arg + '"' : '') + (params.length > 0 ? ' — ' + params.join(', ') : '')
469
+ }
314
470
  const doCopy = function () {
315
471
  if (!skill) { setStatus('请先选择技能'); return }
316
- const text = '请使用 ARIS 技能 ' + WB_TICK + skill + WB_TICK + ' 完成对应工作流。' + (args ? ' 参数/方向:' + args : '')
317
- navigator.clipboard.writeText(text).then(function () { setStatus('提示词已复制,粘贴到会话发送即可') }).catch(function () { setStatus('复制失败,请手动复制:' + text) })
472
+ const text = buildCmd()
473
+ navigator.clipboard.writeText(text).then(function () { setStatus('命令已复制,粘贴到会话发送即可') }).catch(function () { setStatus('复制失败,请手动复制:' + text) })
318
474
  }
319
475
 
320
476
  const selectOptions = [h('option', { value: '', key: '' }, '— 选择 ARIS 技能 —')]
321
477
  ;(skills || []).forEach(function (s) {
322
- selectOptions.push(h('option', { key: s.name, value: s.name }, s.name + (s.description ? ' — ' + s.description.slice(0, 60) : '')))
478
+ selectOptions.push(h('option', { key: s.name, value: s.name }, s.name + ' — ' + (SKILL_ZH[s.name] || s.description || '').slice(0, 60)))
323
479
  })
324
480
 
325
481
  return h('div', { className: 'wb-trigger' },
326
482
  h('div', { className: 'wb-guide-title' }, '技能触发'),
327
483
  h('div', { className: 'wb-trigger-row' },
328
484
  h('select', { className: 'wb-skill-select', value: skill, onChange: function (e) { props.onSkill(e.target.value) } }, selectOptions),
329
- h('input', { className: 'wb-args', placeholder: '参数 / 研究方向(可选)', value: args, onChange: function (e) { props.onArgs(e.target.value) } })),
485
+ h('input', { className: 'wb-args', placeholder: '研究方向 (arguments):例如 discrete diffusion LMs 的 factorized gap', value: args, onChange: function (e) { props.onArgs(e.target.value) } })),
486
+ h('div', { className: 'wb-trigger-row wb-param-row' },
487
+ h('label', { className: 'wb-param-label' }, '无人值守: '),
488
+ h('select', { className: 'wb-param-select', value: autoProceed ? 'on' : 'off', onChange: function (e) { setAutoProceed(e.target.value === 'on') } },
489
+ h('option', { value: 'on' }, 'AUTO_PROCEED: true (自动继续)'),
490
+ h('option', { value: 'off' }, 'AUTO_PROCEED: false (每步确认)')),
491
+ h('label', { className: 'wb-param-label' }, '深度: '),
492
+ h('select', { className: 'wb-param-select', value: effort, onChange: function (e) { setEffort(e.target.value) } },
493
+ h('option', { value: 'lite' }, 'lite (轻量)'),
494
+ h('option', { value: 'balanced' }, 'balanced (默认)'),
495
+ h('option', { value: 'max' }, 'max (深度)'),
496
+ h('option', { value: 'beast' }, 'beast (极限)')),
497
+ h('label', { className: 'wb-param-label' }, '保证级: '),
498
+ h('select', { className: 'wb-param-select', value: assurance, onChange: function (e) { setAssurance(e.target.value) } },
499
+ h('option', { value: 'draft' }, 'draft (草稿)'),
500
+ h('option', { value: 'polished' }, 'polished (精修)'),
501
+ h('option', { value: 'conference-ready' }, 'conference-ready'),
502
+ h('option', { value: 'submission' }, 'submission (投稿级全审计)')),
503
+ h('label', { className: 'wb-param-label' }, '审稿强度: '),
504
+ h('select', { className: 'wb-param-select', value: difficulty, onChange: function (e) { setDifficulty(e.target.value) } },
505
+ h('option', { value: 'medium' }, 'medium (默认)'),
506
+ h('option', { value: 'hard' }, 'hard (更强)'),
507
+ h('option', { value: 'nightmare' }, 'nightmare (极限)'))),
508
+ h('div', { className: 'wb-trigger-row wb-param-row' },
509
+ h('label', { className: 'wb-param-label' }, '会议: '),
510
+ h('select', { className: 'wb-param-select', value: venue, onChange: function (e) { setVenue(e.target.value) } },
511
+ h('option', { value: '' }, '(默认)'),
512
+ h('option', { value: 'ICLR' }, 'ICLR'), h('option', { value: 'NeurIPS' }, 'NeurIPS'),
513
+ h('option', { value: 'ICML' }, 'ICML'), h('option', { value: 'CVPR' }, 'CVPR'),
514
+ h('option', { value: 'AAAI' }, 'AAAI'), h('option', { value: 'ACL' }, 'ACL')),
515
+ h('label', { className: 'wb-param-label' }, 'GPU: '),
516
+ h('select', { className: 'wb-param-select', value: gpu, onChange: function (e) { setGpu(e.target.value) } },
517
+ h('option', { value: '' }, '(默认)'), h('option', { value: 'local' }, 'local'),
518
+ h('option', { value: 'remote' }, 'remote'), h('option', { value: 'vast' }, 'vast'),
519
+ h('option', { value: 'modal' }, 'modal')),
520
+ h('label', { className: 'wb-param-label' }, '审稿路由: '),
521
+ h('select', { className: 'wb-param-select', value: reviewer, onChange: function (e) { setReviewer(e.target.value) } },
522
+ h('option', { value: 'auto' }, 'auto (默认)'), h('option', { value: 'codex' }, 'codex'),
523
+ h('option', { value: 'oracle-pro' }, 'oracle-pro'), h('option', { value: 'manual' }, 'manual')),
524
+ h('label', { className: 'wb-param-label' }, '人工确认: '),
525
+ h('select', { className: 'wb-param-select', value: humanCheckpoint ? 'true' : 'false', onChange: function (e) { setHumanCheckpoint(e.target.value === 'true') } },
526
+ h('option', { value: 'false' }, 'false (默认)'), h('option', { value: 'true' }, 'true (暂停批准)'))),
527
+ h('div', { className: 'wb-trigger-row' },
528
+ 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,
330
530
  h('div', { className: 'wb-trigger-row' },
331
531
  h('button', { className: 'wb-btn', onClick: doLaunch, disabled: busy }, busy ? '触发中…' : '触发'),
332
532
  h('button', { className: 'wb-btn wb-btn-copy', onClick: doCopy }, '复制提示词')),
@@ -334,6 +534,15 @@ function WbTrigger(props) {
334
534
  status ? h('div', { className: 'wb-status' }, status) : null)
335
535
  }
336
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
+ }
337
546
  function wbKey(label, value) {
338
547
  return h('div', { className: 'wb-row' }, h('span', { className: 'wb-key' }, label), h('span', { className: 'wb-val' }, value))
339
548
  }
@@ -352,6 +561,48 @@ function WbArtifacts(props) {
352
561
  ' ', h('code', null, item.path))
353
562
  }))
354
563
  }
564
+ function renderAudits(audits) {
565
+ if (!audits) return wbNote('(no data)')
566
+ const ORDER = [
567
+ ['experiment-audit', '实验诚实度审计'],
568
+ ['result-to-claim', '结果→声明判定'],
569
+ ['paper-claim-audit', '论文数字核对'],
570
+ ['citation-audit', '引用真实性审计'],
571
+ ['kill-argument', '最强拒稿模拟'],
572
+ ]
573
+ const keys = Object.keys(audits)
574
+ const done = keys.map(function (k) { return k.replace(/\.(md|json)$/, '') })
575
+ const next = ORDER.find(function (o) { return done.indexOf(o[0]) === -1 })
576
+ const children = []
577
+ children.push(h('div', { className: 'wb-audit-order' }, '建议顺序: ' + ORDER.map(function (o) { return o[1] }).join(' → ')))
578
+ if (keys.length === 0) {
579
+ children.push(wbNote('尚无审计产物'))
580
+ } else {
581
+ children.push(h('ul', { className: 'wb-list' }, keys.map(function (k) {
582
+ const a = audits[k]
583
+ const v = a.verdict || '?'
584
+ const cls = 'wb-verdict wb-verdict-' + v.toLowerCase()
585
+ return h('li', { key: k },
586
+ h('span', { className: cls }, v),
587
+ ' ', h('code', null, k))
588
+ })))
589
+ }
590
+ if (next) children.push(wbNote('下一步建议: 先跑 ' + next[0] + '(' + next[1] + ')'))
591
+ return h('div', null, children)
592
+ }
593
+
594
+ function renderArtifactChain(artifacts) {
595
+ if (!artifacts) return wbNote('(no data)')
596
+ const CHAIN = ['IDEA_REPORT.md', 'refine-logs/FINAL_PROPOSAL.md', 'refine-logs/EXPERIMENT_PLAN.md', 'EXPERIMENT_LOG.md', 'NARRATIVE_REPORT.md', 'paper/main.pdf']
597
+ const has = function (pp) { const e = artifacts[pp]; return !!e && e.exists }
598
+ return h('ul', { className: 'wb-list' }, CHAIN.map(function (pp, i) {
599
+ return h('li', { key: pp },
600
+ h('span', { className: has(pp) ? 'wb-dot-ok' : 'wb-dot-missing' }, has(pp) ? '●' : '○'),
601
+ ' ', h('code', null, pp),
602
+ i < CHAIN.length - 1 ? ' →' : '')
603
+ }))
604
+ }
605
+
355
606
  function renderIdeaBoard(ideas) {
356
607
  if (!ideas) return wbNote('(no data)')
357
608
  const out = []
@@ -440,21 +691,24 @@ function ArisWorkbenchView(props) {
440
691
  const [skill, setSkill] = React.useState('')
441
692
  const [args, setArgs] = React.useState('')
442
693
  const [permPreset, setPermPreset] = React.useState('read-only')
443
- const [autoProceed, setAutoProceed] = React.useState(true)
444
694
  const [permStatus, setPermStatus] = React.useState(undefined)
695
+ const [targetSessionId, setTargetSessionId] = React.useState(undefined)
696
+ const [workspaceId, setWorkspaceId] = React.useState(undefined)
445
697
 
446
698
  React.useEffect(function () {
447
699
  let live = true
448
- connection.rpc.call(WB_CHANNEL, 'sessions', {})
700
+ connection.rpc.call(WB_CHANNEL, 'workspaces', {})
449
701
  .then(function (res) {
450
702
  if (!live) return
451
- const ws = res.ok && res.value ? res.value.sessions : undefined
452
- if (Array.isArray(ws)) {
453
- setWorkspaces(ws)
454
- 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)
455
709
  } else {
456
710
  setWorkspaces([])
457
- setError(res.error ? res.error.message : 'sessions RPC failed')
711
+ setError(res.error ? res.error.message : 'workspaces RPC failed')
458
712
  }
459
713
  })
460
714
  .catch(function (cause) { if (live) { setError(String(cause)); setWorkspaces([]) } })
@@ -477,9 +731,9 @@ function ArisWorkbenchView(props) {
477
731
 
478
732
  React.useEffect(function () { fetchSkills() }, [fetchSkills])
479
733
 
480
- const load = React.useCallback(function () {
734
+ const load = React.useCallback(function (silent) {
481
735
  if (!sessionId) return
482
- setBusy(true)
736
+ if (!silent) setBusy(true)
483
737
  const timeout = new Promise(function (_, reject) {
484
738
  setTimeout(function () { reject(new Error('RPC 超时(6s)')) }, 6000)
485
739
  })
@@ -489,6 +743,7 @@ function ArisWorkbenchView(props) {
489
743
  connection.rpc.call(WB_CHANNEL, 'wiki', { sessionId: sessionId }),
490
744
  connection.rpc.call(WB_CHANNEL, 'ideas', { sessionId: sessionId }),
491
745
  connection.rpc.call(WB_CHANNEL, 'experiments', { sessionId: sessionId }),
746
+ connection.rpc.call(WB_CHANNEL, 'audits', { sessionId: sessionId }),
492
747
  ]),
493
748
  timeout,
494
749
  ]).then(function (results) {
@@ -498,21 +753,33 @@ function ArisWorkbenchView(props) {
498
753
  setData(undefined)
499
754
  return
500
755
  }
501
- setData({ overview: results[0].value, wiki: results[1].value, ideas: results[2].value, experiments: results[3].value })
756
+ setData({ overview: results[0].value, wiki: results[1].value, ideas: results[2].value, experiments: results[3].value, audits: results[4].value })
502
757
  setError(undefined)
503
758
  }).catch(function (cause) {
504
759
  setError(String(cause))
505
760
  setData(undefined)
506
- }).finally(function () { setBusy(false) })
761
+ }).finally(function () { if (!silent) setBusy(false) })
507
762
  }, [connection, sessionId])
508
763
 
509
764
  React.useEffect(function () { load() }, [load])
510
765
 
766
+ // 自动轮询:工作流执行中每 10 秒静默刷新进度
767
+ React.useEffect(function () {
768
+ const timer = setInterval(function () {
769
+ const p = data && data.overview && data.overview.pipeline
770
+ const active = p && p.phases && p.phases.some(function (x) { return x.status === 'running' || x.status === 'pending' })
771
+ if (active) load(true)
772
+ }, 10000)
773
+ return function () { clearInterval(timer) }
774
+ }, [data, load])
775
+
511
776
  const ov = data && data.overview
512
777
  const wiki = data && data.wiki
513
778
  const ideas = data && data.ideas
514
779
  const exps = data && data.experiments
515
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 }) : []
516
783
 
517
784
  const children = []
518
785
  children.push(h('div', { className: 'wb-header', key: 'head' },
@@ -523,17 +790,31 @@ function ArisWorkbenchView(props) {
523
790
  if (workspaces && workspaces.length > 1) {
524
791
  children.push(h('div', { className: 'wb-picker', key: 'picker' },
525
792
  h('label', null, '工作区: '),
526
- h('select', { value: sessionId, onChange: function (e) { setSessionId(e.target.value) } },
527
- 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
+ }))))
528
803
  }
529
804
  if (workspaces && workspaces.length === 0) {
530
- children.push(wbNote('无活动会话 — 请先在 DSH 中打开/新建一个工作区会话,然后点「刷新」'))
805
+ children.push(wbNote('无工作区数据 — 请先在 DSH 中打开一个工作区,然后点「刷新」'))
531
806
  }
532
807
  if (error) children.push(h('div', { className: 'wb-error', key: 'err' }, error))
533
808
  if (sessionId === undefined && workspaces === undefined) children.push(wbNote('正在连接工作区…'))
534
809
  if (sessionId !== undefined) {
535
810
 
536
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
+ })),
537
818
  h('label', null, '权限: '),
538
819
  h('select', { className: 'wb-perm-select', value: permPreset, onChange: function (e) {
539
820
  const v = e.target.value
@@ -545,14 +826,10 @@ function ArisWorkbenchView(props) {
545
826
  h('option', { value: 'read-only' }, 'Plan (只读)'),
546
827
  h('option', { value: 'workspace-write' }, 'Accept Edits (可改文件)'),
547
828
  h('option', { value: 'danger-full-access' }, 'Full Access (全自动)')),
548
- h('label', null, '无人值守: '),
549
- h('select', { className: 'wb-perm-select', value: autoProceed ? 'on' : 'off', onChange: function (e) { setAutoProceed(e.target.value === 'on') } },
550
- h('option', { value: 'on' }, 'AUTO_PROCEED on (自动继续)'),
551
- h('option', { value: 'off' }, 'AUTO_PROCEED off (每步确认)')),
552
829
  permStatus ? h('span', { className: 'wb-perm-status' }, permStatus) : null))
553
830
  children.push(h(WbPanorama, { key: 'pano', onPick: function (name) { setSkill(name) } }))
554
- children.push(h(WbGuide, { key: 'guide', stage: stage, onPick: function (name) { setSkill(name) } }))
555
- children.push(h(WbTrigger, { key: 'trigger', connection: connection, sessionId: sessionId, skills: skills, skill: skill, args: args, autoProceed: autoProceed, skillError: skillError, onSkill: setSkill, onArgs: setArgs }))
831
+ children.push(h(WbProgress, { key: 'progress', overview: ov, stage: stage }))
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 }))
556
833
  }
557
834
  if (sessionId !== undefined && data === undefined && !error) children.push(wbNote('加载中…'))
558
835
  if (data) {
@@ -571,6 +848,10 @@ function ArisWorkbenchView(props) {
571
848
  wbKey('Last verdict', ov.state.last_verdict || '(n/a)')) : wbNote('尚无 review-stage/REVIEW_STATE.json(未跑过 auto-review-loop)'),
572
849
  h(WbArtifacts, (ov.artifacts && Object.keys(ov.artifacts).map(function (k) { return { path: k, exists: ov.artifacts[k].exists } })) || [])))
573
850
  children.push(h('div', { className: 'wb-grid2', key: 'status' }, [
851
+ h(WbSection, { title: '产物链', key: 'chain' },
852
+ renderArtifactChain(data.overview && data.overview.artifacts)),
853
+ h(WbSection, { title: '审计链状态', key: 'aud' },
854
+ renderAudits(data.audits)),
574
855
  h(WbSection, { title: 'Idea 看板', key: 'id' }, renderIdeaBoard(ideas)),
575
856
  h(WbSection, { title: '实验', key: 'ex' }, renderExperiments(exps)),
576
857
  h(WbSection, { title: 'Wiki 图谱', key: 'wk' }, renderWiki(wiki)),
@@ -648,12 +929,37 @@ const WB_CSS = `.wb-overlay { position: fixed; inset: 0; z-index: 9999; backgrou
648
929
  .wb-pano-card-head { display: flex; align-items: baseline; gap: 6px; }
649
930
  .wb-pano-num { font-weight: 800; color: #007acc; font-size: 12px; }
650
931
  .wb-pano-card-title { font-size: 13px; font-weight: 600; }
651
- .wb-pano-card-desc { font-size: 11px; opacity: .75; line-height: 1.45; }
932
+ .wb-pano-card-desc { font-size: 11px; color: #1f1f1f; line-height: 1.45; }
652
933
  .wb-pano-card-entry { font-size: 11px; color: #0066b8; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }.wb-perm-select { background: #ffffff; color: #1f1f1f; border: 1px solid #c8c8c8; border-radius: 4px; padding: 3px 8px; font-size: 12px; margin-right: 12px; }
653
934
  .wb-perm-status { font-size: 11px; color: #107c10; margin-left: 6px; }
654
935
 
655
936
  .wb-pano-card { min-height: 158px; padding: 12px 14px; }
656
937
  .wb-pano-card-desc { line-height: 1.55; }
938
+ .wb-param-row { align-items: center; }
939
+ .wb-param-label { font-size: 11px; opacity: .7; }
940
+ .wb-param-select { background: #ffffff; color: #1f1f1f; border: 1px solid #c8c8c8; border-radius: 4px; padding: 3px 6px; font-size: 12px; }
941
+ .wb-verdict { display: inline-block; border-radius: 8px; padding: 1px 8px; font-size: 10px; font-weight: 700; margin-right: 6px; }
942
+ .wb-verdict-pass { background: #dcffe4; color: #107c10; }
943
+ .wb-verdict-warn { background: #fff8c5; color: #9a6700; }
944
+ .wb-verdict-fail { background: #ffebe9; color: #c42b1c; }
945
+ .wb-verdict-blocked { background: #ffe4e6; color: #b91c1c; }
946
+ .wb-verdict-error { background: #f6f8fa; color: #57606a; }
947
+ .wb-verdict-not_applicable { background: #f6f8fa; color: #57606a; }
948
+ .wb-verdict-? { background: #f6f8fa; color: #57606a; }
949
+ .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; }
950
+ .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; }
951
+
952
+ .wb-pano-card-when { font-size: 13px; color: #1f1f1f; }
953
+
954
+ .wb-audit-order { font-size: 12px; color: #1f1f1f; margin-bottom: 6px; }
955
+ .wb-progress { margin-bottom: 18px; border: 1px solid #e0e0e0; border-radius: 6px; padding: 12px 16px; background: #f7f7f7; }
956
+ .wb-bar { height: 8px; background: #e8e8e8; border-radius: 4px; overflow: hidden; margin: 6px 0; }
957
+ .wb-bar-fill { height: 100%; background: #007acc; border-radius: 4px; transition: width .3s; }
958
+ .wb-phase-running { color: #0969da; font-weight: 600; }
959
+ .wb-phase-accepted { color: #107c10; font-weight: 600; }
960
+ .wb-phase-done { color: #9a6700; }
961
+ .wb-phase-failed { color: #c42b1c; }
962
+ .wb-phase-pending, .wb-phase-skipped { opacity: .5; }
657
963
  `
658
964
 
659
965
  const CSS = `
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
@@ -242,6 +304,10 @@ export function registerWorkbench(ctx) {
242
304
  'refine-logs/EXPERIMENT_PLAN.md',
243
305
  'research-wiki/index.md',
244
306
  'research-wiki/query_pack.md',
307
+ 'EXPERIMENT_LOG.md',
308
+ 'NARRATIVE_REPORT.md',
309
+ 'paper/main.tex',
310
+ 'paper/main.pdf',
245
311
  ]) {
246
312
  artifacts[rel] = await statFile(workspace, rel)
247
313
  }
@@ -260,12 +326,31 @@ export function registerWorkbench(ctx) {
260
326
  const skill = typeof payload === 'object' && payload !== null ? payload.skill : undefined
261
327
  const args = typeof payload === 'object' && payload !== null ? payload.args : undefined
262
328
  if (typeof skill !== 'string' || skill === '') {
263
- 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: [] } } }
264
330
  }
265
- const argText = typeof args === 'string' && args.trim() !== '' ? ' 参数/方向:' + args.trim() : ''
331
+ const argText = typeof args === 'string' ? args.trim() : ''
266
332
  const autoProceed = typeof payload === 'object' && payload !== null ? payload.autoProceed : undefined
267
- const autoText = autoProceed === false ? ' 请将 AUTO_PROCEED 设为 false,在每个选择点停下来等我确认。' : (autoProceed === true ? ' 请将 AUTO_PROCEED 设为 true,自动继续、无人值守。' : '')
268
- const prompt = '请使用 ARIS 技能 ' + TICK + skill + TICK + ' 完成对应工作流。' + argText + autoText
333
+ const effort = typeof payload === 'object' && payload !== null ? payload.effort : undefined
334
+ const assurance = typeof payload === 'object' && payload !== null ? payload.assurance : undefined
335
+ const difficulty = typeof payload === 'object' && payload !== null ? payload.difficulty : undefined
336
+ const extra = typeof payload === 'object' && payload !== null ? payload.extra : undefined
337
+ const venue = typeof payload === 'object' && payload !== null ? payload.venue : undefined
338
+ const gpu = typeof payload === 'object' && payload !== null ? payload.gpu : undefined
339
+ const reviewer = typeof payload === 'object' && payload !== null ? payload.reviewer : undefined
340
+ const humanCheckpoint = typeof payload === 'object' && payload !== null ? payload.humanCheckpoint : undefined
341
+ const extraText = typeof extra === 'string' ? extra.trim() : ''
342
+ const params = []
343
+ if (autoProceed === false) params.push('AUTO_PROCEED: false')
344
+ if (humanCheckpoint === true) params.push('human checkpoint: true')
345
+ if (effort && effort !== 'balanced') params.push('effort: ' + effort)
346
+ if (assurance && assurance !== 'draft') params.push('assurance: ' + assurance)
347
+ if (difficulty && difficulty !== 'medium') params.push('difficulty: ' + difficulty)
348
+ if (venue) params.push('venue: ' + venue)
349
+ if (gpu) params.push('gpu: ' + gpu)
350
+ if (reviewer && reviewer !== 'auto') params.push('reviewer: ' + reviewer)
351
+ if (extraText) params.push(extraText)
352
+ const cmd = '/' + skill + (argText ? ' "' + argText + '"' : '') + (params.length > 0 ? ' — ' + params.join(', ') : '')
353
+ const prompt = '请执行以下 ARIS 命令:\n' + cmd
269
354
  const messageId = 'aris-launch-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8)
270
355
  const message = {
271
356
  id: messageId,
@@ -275,33 +360,71 @@ export function registerWorkbench(ctx) {
275
360
  }
276
361
  let injected = false
277
362
  let method = 'none'
278
- try {
279
- const agent = scoped.agents && typeof scoped.agents.get === 'function' ? scoped.agents.get(sessionId) : undefined
280
- if (agent && typeof agent.send === 'function') {
281
- agent.send(message, 'next-turn', true)
282
- injected = true
283
- method = 'agent.send'
284
- }
285
- } catch (e) { /* fall through to session.append */ }
286
- 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
287
369
  try {
288
- const session = scoped.sessions.get(sessionId)
370
+ const session = scoped.sessions.get(sid)
289
371
  if (session && typeof session.append === 'function') {
290
- session.append('user/message', message)
291
- injected = true
292
- 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
+ }
293
395
  }
294
396
  } catch (e) {
295
- 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: [] } } }
296
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: [] } } }
408
+ }
409
+ return { ok: true, value: { injected, method, prompt, sessionId: targetId, messageId } }
410
+ }
411
+
412
+ case 'audits': {
413
+ const auditFiles = ['EXPERIMENT_AUDIT.md', 'EXPERIMENT_AUDIT.json', 'PAPER_CLAIM_AUDIT.md', 'PAPER_CLAIM_AUDIT.json', 'CITATION_AUDIT.md', 'CITATION_AUDIT.json', 'KILL_ARGUMENT.md', 'KILL_ARGUMENT.json']
414
+ const out = {}
415
+ for (const rel of auditFiles) {
416
+ const text = await readText(workspace, rel)
417
+ if (text === undefined) continue
418
+ const verdictMatch = /(PASS|WARN|FAIL|BLOCKED|ERROR|NOT_APPLICABLE)/.exec(text)
419
+ out[rel] = { exists: true, verdict: verdictMatch ? verdictMatch[1] : undefined, head: text.split(String.fromCharCode(10)).slice(0, 6) }
297
420
  }
298
- return { ok: true, value: { injected, method, prompt, sessionId, messageId } }
421
+ return { ok: true, value: out }
299
422
  }
300
423
  default:
301
424
  return { ok: false, error: { code: 'internal', message: 'unknown ARIS workbench endpoint \"' + endpoint + '\"' } }
302
425
  }
303
426
  } catch (error) {
304
- 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: [] } } }
305
428
  }
306
429
  },
307
430
  { authority: 'loopback' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-aris-panel",
3
- "version": "0.1.4",
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",