Jelajahi Sumber

feat(research67): 展示分项科研结果与反馈处理路径

避免把运行结束误读为计算、解释与科学结论同时通过,让客户端与服务端使用一致的科研交付状态。

- 工具输出和终端卡片显示方案、计算、解释、科学验证及下一步,兼容旧服务响应
- 更新科研智能体指引,按任务类型交付并复用有效计算,反馈修订不默认触发重算
- 连接回归 86 项、终端卡片 22 项通过,两个包类型检查通过
邵勃源 1 jam lalu
induk
melakukan
054c4759f4

+ 4 - 0
.opencode/agent/research67.md

@@ -78,3 +78,7 @@ resources.datasets 中的 profile 是对注册输入的确定性概况,用于
 结果综合中的 numeric_check 只核对模型已提取且绑定结果字段的数值。显示提取数量与核对状态;0 条不能解释为“所有数字正确”,checked 也不等于完整语义或独立科学验证。
 若 numeric_check.coverage 存在,同时说明候选数字片段、已关联和未关联数量。未关联项可能是计划值、标识或漏提的结果数字,不直接判错;需要解释时读取 numeric_check.artifact 中的 quantity_coverage.unlinked,按原文位置核对。所有片段均关联也不代表语义绑定无误。综合结论和审阅意见使用该研究项 observation_ids 中的实验,不能从其他实验挑选相同数值作为依据。
 numeric_check.followup 记录一次有界遗漏补查。分别说明恢复关联、模型判断非结果及暂无法对应的数量;分类理由保存在其 artifact 中。not_result_mentions 是模型分类,不是程序证明不需核验;unresolved 不能当作正确。补查 pending 时保留首次结果,提醒查看未关联项,不自动重跑整个实验。
+
+有 `science.outcome` 时优先按其中的方案、计算、解释、科学验证和下一步汇报。计算完成与解释待修订可以同时成立;`new_executions=0` 不代表没有可复用结果,不能为补审阅而自动重复计算。`scientific_validation=not_established` 不抹除已有产物。旧服务未提供该字段时保留原有状态说明。
+
+`science.outcome.delivery` 按任务类型记录交付:方案/候选假设无需额外运行实验才可交付;计算类需核对当前计算与解释。`repair_plan` 显示反馈归属、可复用与需重算子任务;反馈本身不等于计算失效,只有定义、输入或前置结果实际变化后才重算。不要将全部交付就绪表述为科学问题已解决。

+ 7 - 3
.opencode/lib/agentpaas.ts

@@ -392,6 +392,9 @@ export function formatRunResult(agentName: string, run: Record<string, unknown>,
   const artifacts = Array.isArray(run.artifacts) ? run.artifacts : []
   const metadata: Record<string, unknown> = { ...run, ...extra, status, artifacts }
   const science = asRecord(asRecord(run.research)?.science)
+  const outcome = asRecord(science?.outcome)
+  const outcomes = outcome?.schema_version === 1 && Array.isArray(outcome.lines)
+    ? outcome.lines.filter((line): line is string => typeof line === "string").map(line => line.replace(/[\r\n\x00-\x1f]/g, " ")) : []
   const operation = asRecord(science?.operation)
   const resultOnly = ["review_results", "clarify"].includes(getString(operation?.operation))
   const acceptance = asRecord(asRecord(science?.acceptance)?.dimensions)
@@ -424,11 +427,12 @@ export function formatRunResult(agentName: string, run: Record<string, unknown>,
     title:
       status === "completed"
         ? science
-          ? scientificLabel(science.status)
+          ? outcomes.length ? getString(outcome?.headline) : scientificLabel(science.status)
           : `AgentPaaS ${agentName}`
         : `AgentPaaS ${agentName}(${labels[status] ?? "状态待核查"})`,
     output: [
-      ...(science ? [`研究状态:${scientificLabel(science.status)}。执行结束不代表科学结论成立。`] : []),
+      ...outcomes,
+      ...(science && !outcomes.length ? [`研究状态:${scientificLabel(science.status)}。执行结束不代表科学结论成立。`] : []),
       ...(operation?.operation === "review_results" ? ["本次操作:复核已有结果;保留研究定义与实验记录,未重新规划或执行实验。"] : []),
       ...(operation?.operation === "clarify" ? ["本次操作:等待说明研究意图;未启动研究或实验。"] : []),
       ...(asRecord(acceptance?.plan_review)?.status === "not_reassessed" ? ["原方案:本次未重审,原有待修订项继续保留;review-parent-gate.json。"] : []),
@@ -441,7 +445,7 @@ export function formatRunResult(agentName: string, run: Record<string, unknown>,
         ...(!resultOnly ? [`高层研究计划含 ${researchIR.planned_actions ?? "未报告"} 项行动,不代表全部已执行;详情见 execution-ir.json。`] : []),
         ...(researchIR.engine === "lambda_cek" ? ["执行引擎:Lambda / CEK;程序凭据不代表科学结论成立。"] : []),
       ] : []),
-      ...(science?.execution_status === "conditional_diagnostic_completed"
+      ...(!outcomes.length && science?.execution_status === "conditional_diagnostic_completed"
         ? ["模拟状态:已完成设定模型下的诊断;证据充分性与现实适用性须另行判断。"]
         : []),
       ...(status === "failed"

+ 21 - 0
packages/opencode/test/tool/agentpaas-outcome.test.ts

@@ -0,0 +1,21 @@
+import { expect, test } from "bun:test"
+import { formatRunResult } from "../../../../.opencode/lib/agentpaas"
+
+test("tool result presents completed calculation and pending interpretation independently", () => {
+  const lines = ["计算:计算已完成;本次新增 0 次执行", "解释:解释待修订", "下一步:修订解释,不重复计算"]
+  const result = formatRunResult("科研", { status: "completed", research: { science: {
+    status: "needs_revision", execution_status: "conditional_diagnostic_completed",
+    outcome: { schema_version: 1, headline: "计算已完成 · 解释待修订", lines },
+  } } })
+  expect(result.title).toBe("计算已完成 · 解释待修订")
+  for (const line of lines) expect(result.output).toContain(line)
+  expect(result.output).not.toContain("模拟状态:")
+})
+
+test("legacy server results retain their original explanation", () => {
+  const result = formatRunResult("科研", { status: "completed", research: { science: {
+    status: "needs_evidence", execution_status: "conditional_diagnostic_completed",
+  } } })
+  expect(result.output).toContain("研究状态:")
+  expect(result.output).toContain("模拟状态:")
+})

+ 6 - 2
packages/tui/src/util/agentpaas.ts

@@ -35,6 +35,9 @@ export function paasCard(metadata: Record<string, unknown>, input: Record<string
     unknown: "用原 run_id 查询;不要重新提交",
   }
   const science = record(record(metadata.research)?.science)
+  const outcome = record(science?.outcome)
+  const outcomes = outcome?.schema_version === 1 && Array.isArray(outcome.lines)
+    ? outcome.lines.filter((line): line is string => typeof line === "string").map(text) : []
   const operation = record(science?.operation)
   const researchIR = record(science?.research_ir)
   const observations = record(science?.observations)
@@ -61,7 +64,7 @@ export function paasCard(metadata: Record<string, unknown>, input: Record<string
       })
     : []
   return {
-    title: `${text(metadata.agent_name) || text(input.agent_name) || text(metadata.agent_id) || text(input.agent_id) || "PaaS"} · ${labels[status] || labels.unknown}`,
+    title: `${text(metadata.agent_name) || text(input.agent_name) || text(metadata.agent_id) || text(input.agent_id) || "PaaS"} · ${status === "completed" && outcomes.length ? text(outcome?.headline) : labels[status] || labels.unknown}`,
     alert: ["failed", "partial", "needs_clarification", "waiting_human", "waiting_confirmation", "unknown"].includes(
       status,
     ),
@@ -76,7 +79,8 @@ export function paasCard(metadata: Record<string, unknown>, input: Record<string
             return row ? [`${text(row.stage_name) || text(row.stage_id)} · ${text(row.status)}`] : []
           })
         : []),
-      science ? `研究状态:${text(science.status) || "未报告"};执行结束不代表科学结论成立` : "",
+      ...outcomes,
+      science && !outcomes.length ? `研究状态:${text(science.status) || "未报告"};执行结束不代表科学结论成立` : "",
       operation?.operation === "review_results" ? "本次操作:复核已有结果;保留研究定义与实验记录,未重新规划或执行实验" : "",
       operation?.operation === "clarify" ? "本次操作:等待说明研究意图;未启动研究或实验" : "",
       record(acceptance?.plan_review)?.status === "not_reassessed" ? "原方案:本次未重审,原有待修订项继续保留;review-parent-gate.json" : "",

+ 11 - 0
packages/tui/test/util/agentpaas.test.ts

@@ -228,3 +228,14 @@ test("research internal limits name their dimension rather than actual tokens",
   expect(oversized.lines.join("\n")).toContain("压缩上下文或缩小任务")
   expect(oversized.lines.join("\n")).not.toContain("调整至")
 })
+
+
+test("server outcome distinguishes reused computation from interpretation revision", () => {
+  const lines = ["计算:计算已完成;本次新增 0 次执行", "解释:解释待修订", "下一步:修订解释"]
+  const card = paasCard({ status: "completed", research: { science: {
+    status: "needs_revision", outcome: { schema_version: 1, headline: "计算已完成 · 解释待修订", lines },
+  } } })
+  expect(card.title).toContain("计算已完成 · 解释待修订")
+  for (const line of lines) expect(card.lines).toContain(line)
+  expect(card.lines.join("\n")).not.toContain("研究状态:needs_revision")
+})