Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bd5e643
feat: add scoped full Explore history reads
loopx-agent Oct 16, 2026
d2758fb
feat: read recorded research history in Goal Overview
loopx-agent Oct 16, 2026
dc57656
test: qualify history pagination versions and visual composition
loopx-agent Oct 16, 2026
17ac06f
Merge remote-tracking branch 'origin/main' into codex/research-histor…
loopx-agent Oct 16, 2026
f29968e
Merge remote-tracking branch 'origin/main' into codex/research-histor…
loopx-agent Oct 16, 2026
94e1c54
test: distinguish overview and Files report revalidation
loopx-agent Oct 16, 2026
70f6a24
test(workspace): isolate overview research reads in packaged journeys
loopx-agent Oct 16, 2026
ee177d2
test(research): preserve a redacted EdgeBench history capture for dis…
loopx-agent Oct 16, 2026
4e0e7cf
test(research): preserve UTC day buckets and freeze capture identity
loopx-agent Oct 16, 2026
64de053
Merge latest main into research history delivery
loopx-agent Oct 16, 2026
dc090df
test(research): capture default history and focused evidence views
loopx-agent Oct 16, 2026
388dbbb
feat(research): time-anchored Explore history lens and evidence sections
huangruiteng Oct 16, 2026
916cec6
feat(research): reusable Lark whiteboard renderer for the time-anchor…
huangruiteng Oct 16, 2026
a3185ae
feat(workspace): integrate full research history with time and relati…
loopx-agent Oct 16, 2026
58fd9e6
Merge current main into research history integration
loopx-agent Oct 16, 2026
d8ef36c
Merge latest full-history reader fixes and visual capture coverage
loopx-agent Oct 16, 2026
21a64a1
docs(research): show packaged history views and clarify metric bounda…
loopx-agent Oct 16, 2026
f8c6c00
test(workspace): retain shared browser console error expectations
loopx-agent Oct 16, 2026
f657a65
fix(research): label recorded finding status bars
loopx-agent Oct 16, 2026
6660772
Merge remote-tracking branch 'origin/main' into codex/research-histor…
loopx-agent Oct 16, 2026
dbfc1a5
Merge remote-tracking branch 'origin/main' into codex/research-histor…
loopx-agent Oct 16, 2026
ca65b0f
Merge remote-tracking branch 'origin/main' into codex/research-histor…
loopx-agent Oct 16, 2026
7b43e07
Merge remote-tracking branch 'origin/main' into codex/research-histor…
loopx-agent Oct 16, 2026
407ff78
fix(workspace): read real checkpoint histories with precise selection
loopx-agent Oct 16, 2026
a923276
docs(workspace): pin three real research checkpoints and trajectories
loopx-agent Oct 16, 2026
44c7af3
Merge commit '6a4b4e7d50d9ab8368e48a4755221a4ddb89ed7a' into codex/re…
loopx-agent Oct 16, 2026
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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1,952 changes: 1,952 additions & 0 deletions apps/presentation/dashboard/src/data/fixtures/edgebench-research-history.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { relativeTime } from "./goal-activity-view";
import { goalAcceptanceContractCopy } from "./delivery-review-copy";
import type {GoalWorkspaceDigest} from "../../../../../../loopx/control_plane/presentation/goal_workspace_digest.js";
import { GoalAcceptanceObservationCard } from "./goal-acceptance-observation-card";
import { GoalResearchHistory } from "./goal-research-history";
import { GoalAcceptanceContractSection } from "./goal-acceptance-contract";
import type { WorkspaceActionPreview, WorkspaceActionPreviewRequest, WorkspaceDrawerSelection, WorkspaceGoal, WorkspaceGoalTab, WorkspaceModel, WorkspaceTimelineItem } from "./personal-workspace-model";
import { useWorkspaceI18n } from "./i18n";
Expand Down Expand Up @@ -116,6 +117,7 @@ export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenD
{moreAttention ? <button className="goal-overview-more" type="button" onClick={() => onView("tasks")}>{copy.tasks}{totalAttention !== null ? ` (${totalAttention})` : ""}<ArrowRight aria-hidden size={14} /></button> : null}
</section> : null}
</div>
{!readOnly ? <GoalResearchHistory key={goal.goalId} goalId={goal.goalId} active={active} zh={locale === "zh-CN"} refreshRevision={historyRefreshRevision}/> : null}
<GoalWorkspaceWork onOpenAgentConversation={onOpenAgentConversation} goal={goal} digest={digest} items={items} userTodos={userTodos} selectedTodoId={selectedTodoId} onSelect={onSelect}
readOnly={readOnly} active={active} railOpen={railOpen} onCloseRail={onCloseRail} historyRefreshRevision={historyRefreshRevision} proposals={proposals} onPreview={onPreview} board={board} results={results}
map={readOnly ? <p className="goal-work-scope">{copy.remote}</p> : <DeliveryWorkMap goal={goal} userTodos={userTodos} review={review} onSelect={onSelect} selectedTodoId={selectedTodoId}/>}/>
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions docs/architecture/rfcs/goal-workspace-surface-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,23 @@ This documentation establishes neither implementation, an available analysis
provider, evaluator calibration, installed behavior nor scientific benefit.
Any new first-screen composition still needs its concrete preview approval.

The current author candidate combines the scoped paginated history reader with
a default record-time lens and optional relationship view. Reading thirds,
density, source labels and explicit decisions remain presentation projections;
repeated decisions and delayed backfills must survive phase selection. Packaged
validation now reads three authorized real research checkpoints (Portfolio,
Graph and Ad placement), selected for actual downstream restoration, with all
315 original Explore records preserved. Complete retained trajectories and
source/restore manifests are pinned externally; the
[reproduction guide](../../../examples/personal-workspace-browser/research-history.md)
records hashes, coverage and redactions. All three sources lack explicit edges
and parent links; date or touch-target grouping does not fill that collection
gap. Independent coincident-point cases qualify precise pointer/keyboard access,
including refresh and narrow screens, without altering the real captures.
This is partial V11 evidence, not installed delivery or a completed V12 service.
The static Lark example discloses its route/decision excerpt limits; it does not
qualify the complete interactive history journey.

## 12. Open decisions

| # | Decision | Owner | Options | Recommendation | Needed before |
Expand Down
10 changes: 10 additions & 0 deletions docs/architecture/rfcs/goal-workspace-surface-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,16 @@ V2 的指标条与看板共用工作判定;V4 的真实隔离 Chat 动作服
交互与结果消费。本文档不证明实现、分析 provider 已可用、evaluator 已校准、安装行为或
科学收益;新的首屏组合仍须提供具体预览并获批准。

当前候选将限定来源、完整分页的历史 reader 与默认记录时间视图、可选关系图组合。
三等分、密度、来源标签和显式决策均是展示投影;同一问题多轮决策与延后回填不能在
时段筛选时消失。打包验证已改用获准的 Portfolio、Graph、广告优化三个真实研究
checkpoint,按后续实际恢复接续选取,完整保留 315 条 Explore 原记录。完整保留轨迹、
来源与恢复清单固定在外部版本;[复现说明](../../../examples/personal-workspace-browser/research-history.md)
记录哈希、覆盖与脱敏。三个来源均未记录显式边或父子关系,日期及命中区域分组不得
补造该缺口。独立同刻点反例验证指针、键盘、刷新和窄屏的准确访问,不改写真实记录。
这些仅构成部分 V11 证据,不代表已安装交付或完成 V12 服务。静态飞书示例明确路线和
决策摘录的数量限制,不据此认定完整交互历史旅程已经验收。

## 12. 未决事项

| # | 事项 | Owner | 选项 | 建议 | 需在何时前决定 |
Expand Down
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.
2 changes: 2 additions & 0 deletions examples/personal-workspace-browser-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { teamEvidenceScenario } from "./personal-workspace-browser/team-evidence
import { taskInspectorReturnScenario } from "./personal-workspace-browser/task-inspector-return.mjs";
import { managedGoalResultsScenario } from "./personal-workspace-browser/managed-goal-results.mjs";
import { researchResultsScenario } from "./personal-workspace-browser/research-results.mjs";
import { researchHistoryScenario } from "./personal-workspace-browser/research-history.mjs";
import { loopxModeScenario } from "./personal-workspace-browser/loopx-mode.mjs";
import { progressiveLoadingScenario } from "./personal-workspace-browser/progressive-loading.mjs";
import { stewardJourneyScenario } from "./personal-workspace-browser/steward-journey.mjs";
Expand Down Expand Up @@ -112,6 +113,7 @@ scenarioCatalog.push(configurationBackupScenario);
scenarioCatalog.push(exploreCompositionOwnerScenario);
scenarioCatalog.push(attentionUserActionScenario);
scenarioCatalog.push(researchResultsScenario);
scenarioCatalog.push(researchHistoryScenario);
scenarioCatalog.push(prReviewAgentOrderScenario);
scenarioCatalog.push(taskInspectorReturnScenario);
scenarioCatalog.push(replanCadenceScenario);
Expand Down
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/fixture.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,16 @@ export async function installApi(page, { fixturePort = port, goalSubagentConfigu
status: 200,
});
});
// Real status fixtures advertise this same-origin reader too. Keep the
// default empty extension inventory inside the fixture rather than reading
// an unrelated installed service. Research scenarios replace this route
// with their published surfaces or explicitly isolated native owner.
await page.route("**/extension-presentation-surfaces", route => route.fulfill({json: {
ok: true, presentation_surfaces: {
schema_version: "extension_presentation_surfaces_v0", count: 0,
ready_count: 0, review_due_count: 0, empty_count: 0, invalid_count: 0, items: [],
},
}}));
const sidebarOrders = new Map();
await page.route("**/api/chat/**", async (route) => {
const request = route.request();
Expand Down
11 changes: 9 additions & 2 deletions examples/personal-workspace-browser/managed-goal-results.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ export const managedGoalResultsScenario = {
let stale = false;
let malformed = false;
let reads = 0;
const readOrder = [];
await page.route("**/api/chat/goal-results**", route => {
const request = new URL(route.request().url());
if (request.pathname.endsWith("/todo_lead-report")) {
reads++;
readOrder.push("body");
return stale
? route.fulfill({status: 409, json: {error: "acceptance changed"}})
: route.fulfill({json: {
Expand All @@ -24,6 +26,7 @@ export const managedGoalResultsScenario = {
text: "# Revised conclusion\n\n| Measure | Value |\n| --- | --- |\n| Free cash flow | 25 |\n",
}});
}
readOrder.push("inventory");
if (malformed) return route.fulfill({json: {ok: true}});
return route.fulfill({json: {
ok: true, items: [{todo_id: "todo_lead-report", title: "Revised cash flow",
Expand All @@ -34,13 +37,17 @@ export const managedGoalResultsScenario = {
try {
assert.equal(reads, 0, "The directory must not read a Goal's report body");
await page.locator(".personal-goal-link", {hasText: "Product Release"}).click();
// Overview and Files each own an exact acceptance read. Wait for the
// first surface before navigating; racing it must not decide the count.
const overviewResults = page.getByRole("region", {name: "已验收的团队报告"});
await overviewResults.getByRole("table").waitFor();
assert.equal(reads, 1, "The visible overview Results must exact-read the selected report");
assert.equal(reads, 1, "Overview must revalidate the selected body once");
assert.deepEqual(readOrder, ["inventory", "body"]);
await page.getByRole("navigation", {name: "Goal 视图"}).getByRole("button", {name: "成果", exact: true}).click();
const results = page.getByRole("region", {name: "已验收的团队报告"});
await results.getByRole("table").waitFor();
assert.equal(reads, 2, "Opening Files must revalidate the body after its fresh inventory read");
assert.equal(reads, 2, "Files must independently revalidate the selected body once");
assert.deepEqual(readOrder, ["inventory", "body", "inventory", "body"]);
assert.match(await results.textContent(), /Revised cash flow/);
assert.equal(await results.locator("script,img").count(), 0);
await page.screenshot({path: resolve(outputDir, "managed-goal-results-desktop.png"), animations: "disabled"});
Expand Down
130 changes: 130 additions & 0 deletions examples/personal-workspace-browser/research-history-backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""Disposable read-only HTTP owner for a captured Explore event log.

The default is three real EdgeBench research checkpoints. The history route
and pagination are production code; execution state is never restored and
model dispatch is never called. Explicit fixture paths support regressions.
"""
import argparse
import hashlib
import json
from pathlib import Path
import signal
import tempfile
from urllib.parse import urlparse

from loopx.capabilities.explore.result_log import validate_explore_result_event
from loopx.chat_action_store import ChatActionStore
from loopx.chat_actions import ChatActionService
from loopx.chat_runtime import ChatRuntimeController
from loopx.chat_server import (
ChatHTTPServer, ChatRequestHandler, default_chat_assets_dir, resolve_lark_cli_for_runtime,
DEFAULT_EXTENSION_PRESENTATION_SURFACES_PATH, DEFAULT_EXTENSION_PROJECTION_PATH,
)
from loopx.chat_store import ChatSessionStore
from loopx.control_plane.effect_runtime import restart_effect_runtime


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--port", type=int, default=0)
parser.add_argument("--fixture", type=Path, action="append",
help="Captured-event wrapper; repeat for independently selectable Goals.")
args = parser.parse_args()
fixtures = Path(__file__).resolve().parents[2] / "apps/presentation/dashboard/src/data/fixtures"
sources = args.fixture or [fixtures / name for name in (
"edgebench-research-history.json", "edgebench-graph-checkpoint-history.json",
"edgebench-ad-checkpoint-history.json")]
captures = []
original = {}
for source in sources:
source_bytes = source.read_bytes()
capture = json.loads(source_bytes)
capture["events"] = [validate_explore_result_event(event) for event in capture["events"]]
assert all(event["goal_id"] == capture["goal_id"] for event in capture["events"])
assert capture["goal_id"] not in {item["goal_id"] for item in captures}, "Duplicate captured Goal"
captures.append(capture)
original[source] = source_bytes
goal_id = captures[0]["goal_id"]
source_digests = {capture["goal_id"]: hashlib.sha256(original[source]).hexdigest()
for source, capture in zip(sources, captures)}
with tempfile.TemporaryDirectory(prefix="loopx-research-display-") as directory:
root = Path(directory)
runtime = root / "runtime"
goals = []
for capture in captures:
captured_goal = capture["goal_id"]
state = root / f"{captured_goal}.md"
state.write_text("# Read-only captured research history\n\n## Agent Todo\n\n## Completed Work Archive\n")
goals.append({"id": captured_goal,
"display_name": capture.get("display_name", "Captured research history"),
"repo": str(root), "state_file": state.name,
"domain": "project-goal-control-plane",
"adapter": {"kind": "read_only_project_map_v0", "status": "connected-read-only"}})
log = runtime / "goals" / captured_goal / "explore-result-log.jsonl"
log.parent.mkdir(parents=True)
log.write_text("".join(json.dumps(event, ensure_ascii=False) + "\n" for event in capture["events"]))
original.update({path: path.read_bytes() for path in (state, log)})
registry = root / "registry.json"
registry.write_text(json.dumps({"schema_version": 1, "common_runtime_root": str(runtime), "goals": goals}))
original[registry] = registry.read_bytes()
metrics = {"history_reads": 0, "mutation_attempts": 0}

class Handler(ChatRequestHandler):
def do_GET(self):
path = urlparse(self.path).path
if path == "/fixture/readback":
return self._send_json({"ok": True, **metrics,
"source_unchanged": all(path.read_bytes() == value for path, value in original.items()),
"fixture_sha256": source_digests[goal_id], "fixtures": source_digests})
if path == "/api/chat/explore-results":
metrics["history_reads"] += 1
if path in {"/healthz", "/status.json", "/api/chat/explore-results",
"/api/chat/capabilities", "/api/chat/endpoints", "/api/chat/sessions",
"/api/actions", "/api/chat/goals/contexts", "/api/chat/goal-results",
"/api/chat/sidebar-goal-order", "/api/chat/usage-statistics",
"/api/chat/delivery-review", "/api/chat/goal-acceptance", "/api/chat/projects",
DEFAULT_EXTENSION_PRESENTATION_SURFACES_PATH, DEFAULT_EXTENSION_PROJECTION_PATH,
} or path.startswith("/chat/"):
return super().do_GET()
self._send_error("This display fixture does not configure execution services.", status=503)

def do_POST(self):
metrics["mutation_attempts"] += 1
self._send_error("The research display fixture is read-only.", status=405)

do_PUT = do_POST
do_DELETE = do_POST

server = ChatHTTPServer(("127.0.0.1", args.port), Handler)
server.registry_path, server.runtime_root = registry, runtime
server.runtime_root_override, server.selected_goal_id = str(runtime), None
server.scan_roots, server.limit, server.verbose = [], 10, False
server.assets_dir = default_chat_assets_dir()
# Native empty stores keep ordinary read-only workspace hydration real.
# No captured execution state or machine runtime is admitted here.
server.chat_store = ChatSessionStore(runtime)
server.action_store = ChatActionStore(runtime / "chat/actions")
unavailable = str(root / "no-execution-binary")
server.runtime_controller = ChatRuntimeController(store=server.chat_store, registry_path=registry,
codex_bin=unavailable, claude_bin=unavailable, kiro_cli_bin=unavailable)
server.action_service = ChatActionService(store=server.action_store, registry_path=registry)
server.goal_subagent_configuration_enabled = False
server.lark_cli_resolution = resolve_lark_cli_for_runtime(runtime_root=runtime, explicit=unavailable)
def terminate(*_):
raise SystemExit(0)

signal.signal(signal.SIGTERM, terminate)
print(json.dumps({"port": server.server_port, "goal_id": goal_id,
"goal_ids": [item["goal_id"] for item in captures],
"event_count": sum(len(item["events"]) for item in captures),
"url": f"http://127.0.0.1:{server.server_port}/chat/?goalId={goal_id}&view=overview"}), flush=True)
try:
server.serve_forever()
finally:
server.server_close()
server.runtime_controller.close()
restart_effect_runtime()


if __name__ == "__main__":
main()
Loading
Loading