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 +347 -41
- package/dsh/workbench.mjs +153 -30
- package/package.json +1 -1
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.
|
|
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 =
|
|
317
|
-
navigator.clipboard.writeText(text).then(function () { setStatus('
|
|
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 +
|
|
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: '
|
|
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, '
|
|
700
|
+
connection.rpc.call(WB_CHANNEL, 'workspaces', {})
|
|
449
701
|
.then(function (res) {
|
|
450
702
|
if (!live) return
|
|
451
|
-
const
|
|
452
|
-
if (Array.isArray(
|
|
453
|
-
setWorkspaces(
|
|
454
|
-
|
|
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 : '
|
|
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:
|
|
527
|
-
workspaces.
|
|
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('
|
|
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(
|
|
555
|
-
children.push(h(WbTrigger, { key: 'trigger', connection: connection, sessionId: sessionId, skills: skills, skill: skill, args: args,
|
|
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;
|
|
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 === '
|
|
201
|
+
if (endpoint === 'workspaces') {
|
|
187
202
|
try {
|
|
188
|
-
const
|
|
189
|
-
|
|
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'
|
|
331
|
+
const argText = typeof args === 'string' ? args.trim() : ''
|
|
266
332
|
const autoProceed = typeof payload === 'object' && payload !== null ? payload.autoProceed : undefined
|
|
267
|
-
const
|
|
268
|
-
const
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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(
|
|
370
|
+
const session = scoped.sessions.get(sid)
|
|
289
371
|
if (session && typeof session.append === 'function') {
|
|
290
|
-
session.append('user/message', message)
|
|
291
|
-
|
|
292
|
-
|
|
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:
|
|
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.
|
|
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",
|