Skip to content
Open
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
Expand Up @@ -5,7 +5,7 @@ import { localizedCapabilityFieldCopy } from "./capability-localization.ts";
import { taskSessionStatus } from "./goal-activity.ts";
import "./monitor-readback.test.mjs";

const source = (name) => readFileSync(new URL(name, import.meta.url), "utf8");
const source = (name) => readFileSync(new URL(name, import.meta.url), "utf8").replace(/\r\n/g, "\n");
const projectionAgent = { agentId: "status-only", adapterKind: "status_projection", available: true, label: "Status" };
const missingAgent = { agentId: "codex", available: false, label: "Codex" };
const availableAgent = { agentId: "claude", available: true, label: "Claude" };
Expand Down Expand Up @@ -118,7 +118,8 @@ assert.doesNotMatch(page.match(/function operationProposalFields[\s\S]*?\n\}/)?.
assert.match(page, /t\("proposal\.primary\.operationGroup"\)/, "Operation confirmation routes users to the bound group");
assert.match(chatData, /result_delivery:/, "Dashboard retains operation result-delivery readback");
assert.match(chatData, /return_delivery\??:/, "Chat messages retain manager return-delivery readback");
assert.match(dashboard, /reconcileConversationReturns\(/, "Manager return polling uses the shared conversation-return read model");
assert.match(dashboard, /readConversationReturns\(/, "Manager return polling uses the shared conversation-return observation");
assert.match(dashboard, /reconcileConversationHistory\(/, "Manager return polling reconciles the shared conversation history");
assert.match(conversationReturns, /source\.return_delivery \?\? row\.returnDelivery/, "Manager return polling refreshes delivery state after the message arrives");
assert.match(timeline + page, /ReturnDeliveryStatus/, "Both manager conversation surfaces render return delivery state");
for (const state of ["delivered", "verification_required", "explicit_unverified"]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
.goal-report-reviews :is([data-result-catalog], [data-result-reader]):focus { outline: 2px solid var(--pw-blue-ink); outline-offset: 4px; }
.report-review-tools, .report-review-tools > button, .goal-report-reviews .result-catalog-return { display: inline-flex; align-items: center; gap: 8px; }
.report-review-reader { min-width: 0; margin-top: 8px; }
.report-review-masthead { padding: 8px 0; border-bottom: 1px solid var(--pw-border); }
.report-review-masthead { padding: 8px 0; border-bottom: 1px solid var(--pw-line); }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 请把一次性截图移出 Git。更新后的 PR 描述已嵌入桌面和窄屏对照,但四张图仍直接引用这个提交新增的 PNG,仓库内没有文档、产品或测试消费者。请把图改放 PR 附件或仓库外的固定版本产物,保留现有嵌入,并标明 Before 实际采集的构建修订。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已在 1881dd77ea1bc1d5f87750f54fbac397cf665fd1 更新:

  • 857d497d7 从当前树删除全部四张一次性 PNG;没有新增画廊、空文档或伪造消费者,也没有重写历史。
  • 已从两个独立打包构建重新采集对照:Before 00a8393ac41e6555d2b4008e3e1823de11cb23e4,After 86baa0bfab5185fc90c2daf59206e367e3ec18b4,视口为 1512×982 和 390×844。两版采用同一公开合成材料,未注入旧 CSS;边框读回分别为 0px 和 1px。当前 head 的前端源码与 After 相同。
  • 已在描述明确旧嵌入图是新前端加旧 CSS 的隔离对照,不是基线构建。保留现有嵌入,避免证据断链。附件上传当前不可用,新图仍在忽略输出中;替换为附件后才能关闭本 P2,本轮不声明已解决。

另修复了新触达的两个 CI 夹具:goal-masthead 的受限输入继续保留原来的未知值验收;安装器用标准相对 RECORD 兼容 Python 3.11。完整 masthead 场景通过,安装归属/新鲜度在 Python 3.11、3.12 各 52 项通过。完整本地 Windows 浏览器链仍有 steering 提示超时记录,未冒充通过。监控恢复 8642/7500 的独立严格资格阻塞保持不变。未请求合并。

.report-review-masthead h2 { margin: 8px 0; font-size: var(--text-title); line-height: var(--leading-title); overflow-wrap: anywhere; }
.report-review-masthead time, .report-review-masthead span { font-size: var(--text-caption); line-height: var(--leading-caption); color: var(--pw-muted); }
.report-review-actions { display: flex; flex-wrap: wrap; align-items: start; gap: 8px; padding: 8px 0 16px; }
.report-review-actions .personal-decision-bar { flex: 1; }
.report-review-actions .personal-compact-menu { flex: 0 0 auto; }
.report-review-reader iframe { display: block; width: 100%; height: min(1000px, 80vh); min-height: 560px; border: 1px solid var(--pw-border); border-radius: 6px; background: var(--pw-surface); }
.report-review-reader iframe { display: block; width: 100%; height: min(1000px, 80vh); min-height: 560px; border: 1px solid var(--pw-line); border-radius: 6px; background: var(--pw-card); }
.report-review-prose { font-size: var(--text-lead); line-height: var(--leading-prose); }
.report-review-reader > details { padding: 16px 0; overflow-wrap: anywhere; }
@media (max-width: 640px) { .report-review-masthead > div { flex-wrap: wrap; } .report-review-reader iframe { min-height: 640px; } .report-review-actions .personal-decision-bar { flex-basis: 100%; } }
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ export const conversationActivityScenario = {
const answer = "检查完成,下一步已列出。";
const runtime = page.__loopxRuntime;
runtime.sessions.set(next.sessionId, { ...runtime.sessions.get(next.sessionId), active_turn_id: null, status: "ready" });
runtime.messages.get(next.sessionId).push({ message_id: `${next.turnId}-assistant`, turn_id: next.turnId, role: "assistant", text: answer });
runtime.messages.get(next.sessionId).push({ message_id: `${next.turnId}-assistant`, turn_id: next.turnId, role: "assistant", text: answer,
created_at: await page.evaluate(() => new Date().toISOString()) });
response.end(event(5, "turn.completed", { response: { schema_version: "loopx_chat_agent_response_v0", message: answer, proposals: [], gate: null } }));
await route.fulfill({ json: { ok: true, session_id: next.sessionId, turn_id: next.turnId, status: "completed" } });
});
Expand Down Expand Up @@ -246,7 +247,8 @@ export const conversationActivityScenario = {
const runtime = page.__loopxRuntime;
runtime.turnMessages.set(turnId, body.message);
runtime.sessions.set(sessionId, {...runtime.sessions.get(sessionId), active_turn_id: turnId, status: "busy"});
runtime.messages.get(sessionId).push({message_id: `${turnId}-user`, turn_id: turnId, role: "user", text: body.message});
runtime.messages.get(sessionId).push({message_id: `${turnId}-user`, turn_id: turnId, role: "user", text: body.message,
created_at: await page.evaluate(() => new Date().toISOString())});
api.turnRequests.push({message: body.message, sessionId, turnId});
let holdFirstRead = true;
await page.route(`**/api/chat/sessions/${sessionId}`, snapshot => {
Expand Down
10 changes: 7 additions & 3 deletions examples/personal-workspace-browser/goal-masthead.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,9 @@ export const goalMastheadScenario = {
throw error;
}
// The canonical producer retains 21 open decisions and bounds its overview
// to three. Pass its payload through the shipped parser
// and adapter, rather than manufacturing a complete three-item queue.
// to three. Complete cases use the cold read; partial cases supply the
// producer's bounded rows to both groups consumed by the shipped adapter.
// Keep the full owner count: three listed rows do not classify all 21.
const fixtureEnv = { ...process.env, LOOPX_USAGE_PING: "0" };
if (!["LOOPX_TEST_PYTHON", "LOOPX_PYTHON_BIN", "LOOPX_PYTHON"].some(key => process.env[key])) fixtureEnv.PYTHONPATH = repoRoot;
const native = JSON.parse(execFileSync(resolveTestPython(), [
Expand Down Expand Up @@ -320,6 +321,9 @@ export const goalMastheadScenario = {
const source = mode === "mixed" ? mixed : mode === "complete" ? complete
: mode === "mixed-complete" ? mixedComplete : mode === "optional" ? optionalComplete : native;
Object.assign(queue, structuredClone(source));
if (!["complete", "mixed-complete", "optional"].includes(mode)) {
queue.user_todos.items = structuredClone(queue.project_asset.user_todos.items);
}
if (mode === "empty") {
queue.user_todos.items = [];
queue.project_asset.user_todos.items = [];
Expand All @@ -336,7 +340,7 @@ export const goalMastheadScenario = {
await openGoal(ui.page, title);
const needs = await vital(ui.page, locale === "en" ? "Needs you" : "等你决定");
const expected = mode === "complete" ? "21" : mode === "mixed-complete" ? "1" : mode === "optional" ? "0" : "—";
assert.equal(needs.value, expected, "Only complete classified rows qualify a blocking total; open User Todo totals do not classify hidden rows");
assert.equal(needs.value, expected, `Only complete classified rows qualify a blocking total; open User Todo totals do not classify hidden rows (${locale}/${mode})`);
const partial = !["complete", "mixed-complete", "optional"].includes(mode);
const coverageHelp = partial ? await readVitalHelp(ui.page, needs.cell) : "";
if (partial) assert.match(coverageHelp, locale === "en" ? /oldest unknown/ : /最早时间未知/);
Expand Down
3 changes: 2 additions & 1 deletion examples/personal-workspace-browser/turn-steps.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ export const turnStepsScenario = {
const answer = "两位负责人都保留,中英文分别择时发布。";
const runtime = page.__loopxRuntime;
runtime.sessions.set(turn.sessionId, { ...runtime.sessions.get(turn.sessionId), active_turn_id: null, status: "ready" });
runtime.messages.get(turn.sessionId).push({ message_id: `${turn.turnId}-assistant`, turn_id: turn.turnId, role: "assistant", text: answer });
runtime.messages.get(turn.sessionId).push({ message_id: `${turn.turnId}-assistant`, turn_id: turn.turnId, role: "assistant", text: answer,
created_at: await page.evaluate(() => new Date().toISOString()) });
live.end(event("turn.completed", { response: { schema_version: "loopx_chat_agent_response_v0", message: answer, proposals: [], gate: null } }));
await page.getByRole("button", { name: "中断本轮", exact: true }).waitFor({ state: "detached" });
const done = page.locator(".personal-message .personal-turn-steps").last();
Expand Down
8 changes: 4 additions & 4 deletions loopx/canary/module_metric_baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@
"dict_any_count": 0
},
"loopx/control_plane/goals/goal_frontier/terminal.py": {
"any_count": 22,
"any_count": 15,
"dict_any_count": 0
},
"loopx/control_plane/work_items/interaction_contract.py": {
"any_count": 107,
"any_count": 85,
"dict_any_count": 0
},
"loopx/extensions/lark/goal_topic_runtime.py": {
Expand All @@ -93,7 +93,7 @@
"dict_any_count": 0
},
"loopx/pr_review.py": {
"any_count": 50,
"any_count": 49,
"dict_any_count": 0
},
"loopx/presentation/renderers/status_markdown.py": {
Expand All @@ -106,7 +106,7 @@
"lines": 1999
},
"loopx/slash_command_install.py": {
"any_count": 12,
"any_count": 11,
"dict_any_count": 0
},
"loopx/state_refresh.py": {
Expand Down
22 changes: 11 additions & 11 deletions loopx/control_plane/goals/goal_frontier/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def _terminal_todo_source_state(
done = summary.get("done_count")
deferred = summary.get("deferred_count")
watch_only = summary.get("watch_only_monitor_count", 0)
monitor_open_items = summary.get("monitor_open_items")
if not (
type(total) is int
and total >= 0
Expand All @@ -42,8 +43,8 @@ def _terminal_todo_source_state(
and done + watch_only == total
and _strict_zero(summary.get("convergence_open_count", summary.get("open_count")))
and _strict_zero(deferred)
and isinstance(summary.get("monitor_open_items"), list)
and len(cast(list[Any], summary.get("monitor_open_items"))) == watch_only
and isinstance(monitor_open_items, list)
and len(monitor_open_items) == watch_only
):
return "invalid", False
intent = summary.get("closure_intent")
Expand All @@ -67,14 +68,14 @@ def _projection_has_empty_terminal_frontier(projection: dict[str, Any] | None) -
frontier = projection.get("remaining_advancement_frontier")
monitors = projection.get("monitor_only_lanes")
successors = projection.get("deferred_successors")
if not all(isinstance(value, dict) for value in (normalized, frontier, monitors, successors)):
if not (
isinstance(normalized, dict)
and isinstance(frontier, dict)
and isinstance(monitors, dict)
and isinstance(successors, dict)
):
return False

normalized = cast(dict[str, Any], normalized)
frontier = cast(dict[str, Any], frontier)
monitors = cast(dict[str, Any], monitors)
successors = cast(dict[str, Any], successors)

required_keys = (
(normalized, {"user_open_count", "agent_open_count", "agent_advancement_open_count", "agent_monitor_open_count", "agent_monitor_due_count"}),
(frontier, {"current_agent_claimed_advancement_count", "unclaimed_advancement_count", "other_agent_claimed_advancement_count"}),
Expand Down Expand Up @@ -114,7 +115,7 @@ def _terminal_todo_source_contract(
) -> tuple[dict[str, Any], bool]:
user_status, user_intent = _terminal_todo_source_state(user_todo_summary, role="user")
agent_status, agent_intent = _terminal_todo_source_state(agent_todo_summary, role="agent")
source_completeness: dict[str, Any] = {
source_completeness: dict[str, object] = {
"schema_version": GOAL_TERMINAL_SOURCE_COMPLETENESS_SCHEMA_VERSION,
"user_todos": user_status,
"agent_todos": agent_status,
Expand Down Expand Up @@ -180,9 +181,8 @@ def _terminal_no_followup_resolves_vision_checkpoint(


def goal_frontier_is_terminal_no_followup(*, projection: dict[str, Any] | None) -> bool:
if not _projection_has_empty_terminal_frontier(projection):
if projection is None or not _projection_has_empty_terminal_frontier(projection):
return False
projection = cast(dict[str, Any], projection)
terminal_state = projection.get("terminal_state")
completeness = projection.get("source_completeness")
return bool(
Expand Down
Loading
Loading