Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** Source-reported finding meaning, shared by history and saved-result readers.
* These labels do not classify evidence or schedule verification. */
export function exploreFindingCopy(zh: boolean) {
return zh ? {
labels: {tentative: "暂定结论", confirmed: "来源记为确认", refuted: "已反驳"},
help: "结论状态说明",
meaning: "暂定结论表示来源尚未确认或反驳该结论,可能已有观测与证据;不表示有检查任务排队。确认与反驳也仅表示来源在所述适用范围内的判断,不等于独立验证或目标完成。研究问题是否收束、下一步继续或转向,分别记录。",
} : {
labels: {tentative: "Tentative", confirmed: "Recorded confirmation", refuted: "Refuted"},
help: "About finding states",
meaning: "Tentative means the source has not confirmed or refuted the finding; observations and evidence may already exist. It does not mean a check is queued. Confirmation and refutation are source judgments within the stated scope, not independent verification or Goal completion. Question closure and the decision to continue or replan are recorded separately.",
};
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {exploreFindingCopy} from "./explore-finding-copy";
import {useEffect, useRef, useState} from "react";
import {ChevronRight, RefreshCw} from "lucide-react";
import {fetchExploreResults, readTodoRequest, type ExploreResultPage} from "../../data/chat";
Expand Down Expand Up @@ -59,9 +60,10 @@ export function GoalExploreResults({goalId, zh, active = true, readSourceUrl}: {
}, [goalId, cursor, revision, active, readSourceUrl]);
const selected = page?.items.find(item => item.finding_id === selectedId);
const sections = selected ? parseExploreFindingSections(selected.summary) : null;
const findingCopy = exploreFindingCopy(zh);
const statuses: Record<string, string> = zh
? {tentative: "待检查", confirmed: "来源记为确认", refuted: "已反驳", open: "未完成", done: "已完成", blocked: "受阻", deferred: "已延后"}
: {tentative: "Tentative", confirmed: "Recorded confirmation", refuted: "Refuted", open: "Open", done: "Done", blocked: "Blocked", deferred: "Deferred"};
? {...findingCopy.labels, open: "未完成", done: "已完成", blocked: "受阻", deferred: "已延后"}
: {...findingCopy.labels, open: "Open", done: "Done", blocked: "Blocked", deferred: "Deferred"};
const outcomes: Record<string, string> = zh
? {replan: "转向", continue: "继续", stop: "停止", wait: "等待", no_change: "无变化", ask_human: "请人决定"}
: {replan: "Replan", continue: "Continue", stop: "Stop", wait: "Wait", no_change: "No change", ask_human: "Ask human"};
Expand All @@ -81,6 +83,7 @@ export function GoalExploreResults({goalId, zh, active = true, readSourceUrl}: {
<header><div className="explore-reader-heading"><h3>{zh ? "已保存的结论" : "Saved findings"}</h3>
<ContextHelp label={zh ? "结论与关联说明" : "About findings and links"}>
<p>{zh ? "来源记录的观察与结论,保存不代表已经验证或被后续决策采用。关联任务是原记录的显式关联。" : "Source-recorded observations and conclusions. Saving does not establish validation or adoption. Linked tasks are explicit source relationships."}</p>
<p>{findingCopy.meaning}</p>
</ContextHelp></div>
<button type="button" disabled={busy} aria-label={zh ? "刷新证据" : "Refresh evidence"} onClick={refresh}><RefreshCw size={16} aria-hidden/></button></header>
{busy ? <p role="status">{zh ? "正在读取证据…" : "Reading evidence…"}</p> : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {exploreFindingCopy} from "./explore-finding-copy";
import {AlertCircle, ArrowLeft, ArrowRight, Circle, CircleHelp, FlaskConical, GitBranch, RefreshCw, Search} from "lucide-react";
import {Fragment, useEffect, useId, useLayoutEffect, useMemo, useRef, useState} from "react";
import {defaultParseSearch} from "@tanstack/react-router";
Expand Down Expand Up @@ -106,7 +107,8 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
};
// Labels reuse Explore's existing vocabulary; they confer no verification state.
const kindLabels: Record<string, string> = zh ? {question: "问题", hypothesis: "假设", experiment: "尝试", artifact: "产物", area: "路线"} : {question: "Question", hypothesis: "Hypothesis", experiment: "Attempt", artifact: "Artifact", area: "Route"};
const stateLabels: Record<string, string> = zh ? {open: "待探索", exploring: "探索中", blocked: "受阻", resolved: "已收束", dead_end: "已排除", tentative: "待检查", confirmed: "来源记为确认", refuted: "已反驳"} : {open: "Open", exploring: "Exploring", blocked: "Blocked", resolved: "Resolved", dead_end: "Ruled out", tentative: "Tentative", confirmed: "Recorded confirmation", refuted: "Refuted"};
const findingCopy = exploreFindingCopy(zh);
const stateLabels: Record<string, string> = zh ? {open: "待探索", exploring: "探索中", blocked: "受阻", resolved: "已收束", dead_end: "已排除", ...findingCopy.labels} : {open: "Open", exploring: "Exploring", blocked: "Blocked", resolved: "Resolved", dead_end: "Ruled out", ...findingCopy.labels};
const relationLabels: Record<string, string> = zh ? {subtopic_of: "属于", depends_on: "依赖", answers: "回答", supports: "支持", refutes: "反驳", leads_to: "引向"} : {subtopic_of: "Part of", depends_on: "Depends on", answers: "Answers", supports: "Supports", refutes: "Refutes", leads_to: "Leads to"};
const kindLabel = (kind: string) => kindLabels[kind] ?? kind;
const stateLabel = (state: string) => stateLabels[state] ?? state;
Expand Down Expand Up @@ -464,7 +466,7 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
<div className="research-reader-content">
<div className="research-reader-location"><button type="button" className="research-reader-back" onClick={closeReader}><ArrowLeft size={14} aria-hidden/>{readerParent.current ? (zh ? "返回问题列表" : "Back to questions") : copy.all}</button>
<div className="research-reader-states">
<p className="research-detail-kind"><GitBranch size={14} aria-hidden/>{kindLabel(selected.latest.node_kind)} · {stateLabel(selected.latest.status)}</p>
<div className="research-detail-kind"><GitBranch size={14} aria-hidden/>{kindLabel(selected.latest.node_kind)} · {stateLabel(selected.latest.status)} <ContextHelp label={findingCopy.help}><p>{findingCopy.meaning}</p></ContextHelp></div>
{selected.findings.length > 1 && findingExceptions ? <p className="research-finding-exceptions">{findingExceptions}</p> : null}
</div></div>
{selected.findings.length ? selectedFinding?.title !== selected.latest.title ? <details className="research-reader-question" key={selected.id} open={questionExpanded} onToggle={event => setQuestionExpanded(event.currentTarget.open)}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {exploreFindingCopy} from "./explore-finding-copy";
import {CircleHelp} from "lucide-react";
import {useLayoutEffect, useRef, useState, type CSSProperties} from "react";
import type {ExploreTimeline, TimelineFinding, TimelineNode, TimelinePhase} from "../../../../../../loopx/control_plane/presentation/explore_timeline.js";
Expand Down Expand Up @@ -32,6 +33,7 @@ export function GoalResearchTimeline({timeline, zh, selectedIds, selectedDay, da
observer.observe(element);
return () => observer.disconnect();
}, [timeline.start, timeline.end]);
const findingCopy = exploreFindingCopy(zh);
const copy: Copy = zh ? {
early: "前段", middle: "中段", late: "后段", items: "研究项", findings: "结论",
decisions: "显式决策", none: "未记录显式决策", changed: "变化", stopped: "停止", retained: "保留",
Expand All @@ -40,7 +42,7 @@ export function GoalResearchTimeline({timeline, zh, selectedIds, selectedDay, da
routes: "研究路线", routeHint: "仅由显式关联或父子分组相连", singles: "未关联研究项",
trail: "决策轨迹", more: "显示更多决策", all: "全部时段",
span: "记录跨度", start: "起点", end: "末端", replan: "转向", continue: "继续", stop: "停止", wait: "等待", no_change: "无变化", ask_human: "请人决定",
confirmed: "来源记为确认", refuted: "已反驳", tentative: "待检查", unlabeled: "无结论", phaseNote: "按记录时间三等分,仅为阅读分组,不是预声明的评估窗口。",
...findingCopy.labels, unlabeled: "无结论", phaseNote: "按记录时间三等分,仅为阅读分组,不是预声明的评估窗口。",
noTime: "没有可用的记录时间,无法放上时间轴;下方记录仍可完整阅读。",
} : {
early: "Early", middle: "Middle", late: "Late", items: "Research items", findings: "Findings",
Expand All @@ -50,7 +52,7 @@ export function GoalResearchTimeline({timeline, zh, selectedIds, selectedDay, da
routes: "Research routes", routeHint: "Connected only by recorded relationships or parent grouping", singles: "Unlinked research items",
trail: "Decision history", more: "Show more decisions", all: "Whole history",
span: "Recorded span", start: "Start", end: "End", replan: "Replan", continue: "Continue", stop: "Stop", wait: "Wait", no_change: "No change", ask_human: "Ask human",
confirmed: "Recorded confirmation", refuted: "Refuted", tentative: "Tentative", unlabeled: "No finding", phaseNote: "Equal thirds of record time, for reading only; not a pre-declared evaluation window.",
...findingCopy.labels, unlabeled: "No finding", phaseNote: "Equal thirds of record time, for reading only; not a pre-declared evaluation window.",
noTime: "No usable record time; the history cannot be placed on a time axis. All records remain readable below.",
};
const {start, end} = timeline;
Expand Down Expand Up @@ -115,7 +117,7 @@ export function GoalResearchTimeline({timeline, zh, selectedIds, selectedDay, da
<p>{zh ? "未关联问题按首次记录 UTC 日期收纳,不推断路线或因果。决策轨迹保留各轮显式决策,最新记录在前。" : "Unlinked questions group by first-record UTC date, without inferring a route or cause. Decision history retains each explicit decision, newest record first."}</p>
<dl className="research-phase-details">{timeline.phases.map(item => <div key={item.index}><dt>{phaseName(item)}</dt><dd>{phaseDescription(item)}</dd></div>)}</dl>
</ContextHelp>{phase !== null ? <button type="button" className="research-link" onClick={() => onPhase(null)}>{copy.all}</button> : null}</div>
<span className="research-phase-legend">{LABELS.map(key => <span key={key}><i className={tone(key)} aria-hidden="true"/>{label(key)}</span>)}</span>
<div className="research-phase-legend">{LABELS.map(key => <span key={key}><i className={tone(key)} aria-hidden="true"/>{label(key)}</span>)}<ContextHelp label={findingCopy.help}><p>{findingCopy.meaning}</p></ContextHelp></div>
<div className="research-phases" role="group" aria-label={copy.phaseNote}>{timeline.phases.map(phaseCard)}</div>
<div className="research-timeline-grid">
<div className="research-row-label"><strong>{copy.density}</strong><small>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,12 @@ feature-off behavior; live long-horizon trajectories must still establish actual
model adoption and continuation. The mode vocabulary is local to Explore configuration,
not a new kernel lifecycle or scheduler contract.

Finding readers distinguish tentative source judgments from queued verification.
Writeback templates require an explicit finding status and retain optional question
closure independently of finding status. This improves authoring and readback;
correct interpretation, actual closure and model adoption still require evidence.
Historical records, CLI defaults and execution authority remain unchanged.

## 20. Acceptance Criteria for the RFC

Merge accepts this design basis. Implementation qualification still requires evidence that:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,10 @@ CLI/File 状态与打包设置验证模式切换、过期预览恢复和关闭
仍须验证模型实际采纳和后续推进。模式词汇仅属于 Explore 配置,不是新的 kernel
生命周期或调度契约。

结论阅读器区分来源的暂定判断与排队检查。写回模板要求明确选择结论状态,
保留独立于结论状态的可选问题收束。这改善填写和回读;正确解读、实际收束与
模型采纳仍须证据证明。历史记录、CLI 默认值与执行权限保持不变。

## 20. RFC 验收标准

RFC 合入即接受设计依据。实现资格仍需要以下证据:
Expand Down
2 changes: 1 addition & 1 deletion docs/development/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ shift the controls. Reflow the composition on narrow screens without hiding
action consequences or relying on fixed alignment offsets.

选中问题后聚焦阅读一条结论,完整正文与记录状态保留;全部结论可直接切换,反驳和
待检查计数仍可见。刷新与返回保留所选结论,全景可原位展开或通过原入口返回,
暂定结论计数仍可见。刷新与返回保留所选结论,全景可原位展开或通过原入口返回,
不再把整张图和全部折叠标题堆在所选正文之前。
路线标题区分起点与末端并自然换行;列表收起长标题时,一次操作进入现有阅读器查看
完整原文,键盘返回准确入口。视图、分析、帮助和刷新共用主控件行,模型、用量与
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Finding state reading

Packaged frontend against the real isolated research-history HTTP reader, with
60 synthetic tentative findings on three nodes. No model or live Goal is used.
The default view keeps the recorded counts and source judgments; the new help
explains that tentative findings are not queued checks. On a 390px viewport,
a resolved question and tentative findings remain separately visible, with
observations and decisions readable in full. No horizontal overflow was observed.
Source readback reported zero mutation attempts and unchanged fixture bytes.

| Before | After |
| --- | --- |
| ![Earlier desktop](before-desktop.png) | ![Tentative findings](after-desktop.png) |

![State explanation on demand](after-help-desktop.png)

![Resolved question with tentative findings on phone](after-reader-phone.png)

The packaged `research-route-reading` smoke additionally exercises mixed source
states, English/Chinese, desktop/narrow layouts, keyboard help dismissal and
focus return. This validates display and transport, not scientific correctness,
live model adoption, historical reclassification or an installed App upgrade.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions examples/personal-workspace-browser/research-route-reading.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ export const researchRouteReadingScenario = {
await page.reload({waitUntil: "domcontentloaded"});
await caption.first().waitFor();
assert.equal(await caption.count(), 3, "Every recorded route remains in the whole history");
const legend = history.locator(".research-phase-legend");
assert((await legend.innerText()).includes(language === "zh-CN" ? "暂定结论" : "Tentative"));
const findingHelp = legend.getByRole("button", {name: language === "zh-CN" ? "结论状态说明" : "About finding states", exact: true});
await findingHelp.focus();
const explanation = page.locator(".context-help-popover:popover-open");
await explanation.waitFor();
assert((await explanation.innerText()).includes(language === "zh-CN" ? "不表示有检查任务排队" : "does not mean a check is queued"));
await page.keyboard.press("Escape");
assert.equal(await explanation.count(), 0);
assert(await findingHelp.evaluate(element => element === document.activeElement), "Closing state help retains keyboard context");
const titleLayout = await caption.first().locator("strong").first().evaluate(element => ({height: element.clientHeight, lineHeight: parseFloat(getComputedStyle(element).lineHeight), fontSize: parseFloat(getComputedStyle(element).fontSize)}));
assert(titleLayout.height >= titleLayout.lineHeight * 1.9 && titleLayout.fontSize >= 16, "Long captions wrap into readable list entries");
if (language === "zh-CN" && width !== 768) {
Expand Down
10 changes: 10 additions & 0 deletions loopx/capabilities/explore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,16 @@ or block valid writeback. Successful replay needs no second graph event or Todo
mutation. This changes enabled evidence capture and explicit Todo-link edits;
ordinary work and feature-off hooks retain their existing behavior.

Writeback templates leave `status` blank so the caller explicitly selects
`tentative`, `confirmed`, or `refuted`. `tentative` means the interpretation is
unresolved, not that a verification job is queued. Choose `confirmed` or
`refuted` only for the stated conclusion within its recorded applicability;
evidence references or a successful check alone do not settle broader claims.
Keep established observations, remaining uncertainty and the next useful check
in the interpretation. Use the existing optional `question_update` to close an
answered question independently of finding status or overall Goal completion.
Existing recorded statuses and the CLI finding default are unchanged.

Use `tentative` for inconclusive observations or prerequisite failures. Neither
compilation failure nor a score alone supplies a scientific interpretation.
Routine work without reusable evidence needs no attachment. Omitting the option
Expand Down
10 changes: 10 additions & 0 deletions loopx/capabilities/explore/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -629,3 +629,13 @@ python3 -m pytest -q \
smoke 证明投影契约(折叠、blocked reasons、树、Mermaid)、记录时路径拒绝、dry-run 默认、分页发现、零写幂等 resync、单行漂移修复、嵌套 create-receipt 处理、shared 可见性脱敏、免传输卡片内容、opt-in todo branch-plan packet、adaptive resilient worker harness 配置档,以及针对临时 registry 的 CLI 表面,无需 live Lark credentials。它还证明 worker-lane router 契约:请求宽度不再被静默压到 worker 上限以下、独立 lane 准入下空闲 lanes 是队列耗尽(不是上限)、routing bias 在不动 value bookkeeping 的情况下重排 lanes、confident-prefix bundles 在校准阈值截断并对 reject-heavy families 坍缩、router-state novelty ledger 跨 epochs 去重而 coverage debt 累积 bias、观测负载配置档通过 CLI flags 校准准入。

runtime smoke 与聚焦 pytest 模块还覆盖:可恢复 prefix 复用、prefix/suffix novelty 与双复用记账、按 suffix 数加权的 router acceptance、显式 legacy fallback、重启兼容、recorded 与 fatal 失败下的清理、无可变状态别名的并发组、fatal 结构预检、record-mode 畸形 item 隔离与 resume 连续性、聚合指标投影,以及无 legacy `execute` 方法的 episode-only adapters。

### 结论状态与问题收束

写回模板的 `status` 留空,由调用方明确选择 `tentative`、`confirmed` 或
`refuted`。`tentative` 在研究界面显示为“暂定结论”,表示尚未确认或反驳,
不表示有检查任务排队。确认与反驳仅适用于记录中声明的结论和范围;证据引用、
检查通过或某个分数不能自动证明更广泛的收益。
在解读中区分已建立的观察、仍未知的结论和下一步有用的验证。问题已回答时,
使用已有可选 `question_update` 收束,不因整体 Goal 未完成而一直保持开放,
也不因结论被确认就自动关闭问题。历史状态与 CLI finding 默认值保持兼容。
Loading
Loading