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
Expand Up @@ -178,3 +178,14 @@ assert.deepEqual(goalReplanCadenceEditorValue(null, enabledStrideDefault), enabl
assert.equal(goalReplanCadenceEditorValue({stride_strategy: "off"}, enabledStrideDefault).stride_strategy, "off");
assert.equal(goalReplanCadenceEditorValue({count_unit: "effective_turns", count: 3}, enabledStrideDefault).stride_strategy, "off");
assert.equal(projectEditableCapabilityConfiguration(cadenceEditor, {}, enabledStrideDefault).stride_strategy, "evidence_conditioned_v1");

// Independent Yardstick inheritance must not be pinned by editing the interval.
const yardstickEffective = {...inheritedReview, yardstick_enabled: true};
assert.equal(goalReplanCadenceEditorValue({count_unit: "effective_turns", count: 2}, yardstickEffective).yardstick_enabled, true);
assert.deepEqual(goalReplanCadenceWriteValue({...yardstickEffective, count: 2}, true, false, false),
{...inheritedReview, count: 2}, "an unchanged inherited measurement choice stays live");
for (const enabled of [true, false]) {
assert.deepEqual(goalReplanCadenceWriteValue({...yardstickEffective, yardstick_enabled: enabled}, false, false, true),
{stride_strategy: "off", yardstick_enabled: enabled}, "explicit measurement opt-in and opt-out survive form saving");
}
assert.equal(replanCadenceEditorDefaults(yardstickEffective, {...cadenceDefault, yardstick_enabled: false}).yardstick_enabled, true);
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function replanCadenceEditorDefaults(effective: unknown, fallback: unknow
if (Object.hasOwn(live, "count_unit")) defaults.count_unit = live.count_unit;
if (Object.hasOwn(live, "count")) defaults.count = live.count;
if (Object.hasOwn(live, "planning_horizon_goal")) defaults.planning_horizon_goal = live.planning_horizon_goal;
if (Object.hasOwn(live, "yardstick_enabled")) defaults.yardstick_enabled = live.yardstick_enabled;
// Strategy omission keeps the existing default-off meaning.
return defaults;
}
Expand All @@ -102,13 +103,15 @@ export function goalReplanCadenceWriteValue(
draft: Record<string, unknown>,
cadenceOverride: boolean,
horizonOverride = Object.hasOwn(draft, "planning_horizon_goal"),
yardstickOverride = Object.hasOwn(draft, "yardstick_enabled"),
): Record<string, unknown> {
const value = { ...draft };
if (!cadenceOverride) {
delete value.count_unit;
delete value.count;
}
if (!horizonOverride) delete value.planning_horizon_goal;
if (!yardstickOverride) delete value.yardstick_enabled;
return value;
}

Expand Down
5 changes: 5 additions & 0 deletions apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {normalizeWorkYardstickCapture} from "../../../../../loopx/control_plane/work_items/yardstick_decision.js";
import {sourceReadApiUrl} from "./local-status-query";
import type {OracleEvidence} from "./goal-acceptance-observation";
import { HANDOFF_MODES, EXECUTION_HANDOFF_MODES, type ExecutionHandoffMode } from "../../../../../loopx/control_plane/coordination/handoff_mode_vocabulary.js";
Expand Down Expand Up @@ -3008,6 +3009,7 @@ const exploreResultPageSchema = z.object({
evidence_refs: z.array(z.string()), last_updated_at: z.string(),
measurements: z.unknown().transform(value => normalizeExploreMeasurements(value)).optional(),
measurement_error: z.string().optional(),
work_yardstick: z.unknown().transform(capture => normalizeWorkYardstickCapture({capture})).optional(),
linked_todos_available: z.boolean().optional(),
linked_todos: z.array(z.object({
todo_id: z.string(), text: z.string(), status: z.string(), claimed_by: z.string(),
Expand All @@ -3019,5 +3021,8 @@ export async function fetchExploreResults(goalId: string, cursor?: string, readS
const query = new URLSearchParams({goal_id: goalId, view: "findings", ...(cursor ? {cursor} : {})});
const page = exploreResultPageSchema.parse(await requestJson<unknown>(sourceReadApiUrl(readSourceUrl, `/api/chat/explore-results?${query}`)));
if (page.goal_id !== goalId) throw new Error("Explore result Goal mismatch");
for (const item of page.items) if (item.work_yardstick) normalizeWorkYardstickCapture({
capture: item.work_yardstick, goal_id: goalId, agent_id: item.agent_id, evidence_refs: item.evidence_refs,
});
return page;
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ const fieldCopy: Record<WorkspaceLocale, FieldCopy> = {
executor_reasoning_effort: { label: "Reasoning effort", description: "Optional reasoning effort for the selected executor. Leave blank to keep the executor's own default." },
count_unit: { label: "Review after", description: "Work Turns require accepted settlement; polls and retries do not count.", options: { completed_todos: "Completed Todos", effective_turns: "Settled work Turns" } },
planning_horizon_goal: { label: "Show Goal in planning horizon", description: "Default on. Keep the original Goal and acceptance visible before choosing work. Turn off for rollback or comparisons; replan, stride and execution authority are unchanged." },
yardstick_enabled: { label: "Review measurement criteria", description: "Default off. Review measurement scope and feedback using available Explore evidence. Requires Explore evidence; does not enable it or authorize execution." },
stride_strategy: { label: "Stride strategy", description: "Structured work scope, route and validation proposals are required for enabled fresh Vision reviews. Does not grant execution authority.", options: { off: "Off", evidence_conditioned_v1: "Evidence-conditioned · v1" } },
count: { label: "Number between reviews", description: "Settled work Turns: 1–6; completed Todos: 1–5. Goal overrides take precedence over device defaults." },
allowed_domains: { label: "Allowed responsibility domains", description: "Enter one bounded, public-safe domain per line." },
Expand Down Expand Up @@ -203,6 +204,7 @@ const fieldCopy: Record<WorkspaceLocale, FieldCopy> = {
executor_reasoning_effort: { label: "推理档位", description: "所选执行器使用的推理档位,可留空;留空表示沿用执行器自身的默认档位。" },
count_unit: { label: "复核计数依据", description: "有效工作 Turn 须完成结算;轮询和重复重试不计数。", options: { completed_todos: "已完成 Todo", effective_turns: "已结算工作 Turn" } },
planning_horizon_goal: { label: "在规划视野中显示 Goal", description: "默认开启,选择工作前可见原目标与验收要求。可为回滚或对照实验关闭;不改变复盘、步幅策略或执行权限。" },
yardstick_enabled: { label: "复核衡量标准", description: "默认关闭。使用已有 Explore 证据复核测量范围与反馈;需有可用的 Explore 证据,不会自动启用它或授予执行权限。" },
stride_strategy: { label: "步幅策略", description: "启用后,新的 Vision 路线复盘须提交工作大小、路线及验证范围的结构化提案,不授予执行权限。", options: { off: "关闭", evidence_conditioned_v1: "依据证据调整 · v1" } },
count: { label: "两次复核间的数量", description: "已结算工作 Turn 为 1–6,已完成 Todo 为 1–5;Goal 显式设置优先于设备默认值。" },
allowed_domains: { label: "允许的职责域", description: "每行填写一个有边界、可公开的职责域。" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,22 @@ type CapabilityMutationState = Readonly<{
draft: Record<string, unknown>;
cadenceOverride: boolean;
horizonOverride: boolean;
yardstickOverride: boolean;
exploreModeOverride: boolean;
partialWrite: GoalConfigurationPartialWrite | null;
readbackPending: boolean;
preview: GoalConfigurationPreview | null;
}>;

function goalWriteDraft(selected: CapabilityConfigurationCatalog["capabilities"][number], draft: Record<string, unknown>, editorMode: "guided" | "json", cadenceOverride: boolean, horizonOverride: boolean, exploreModeOverride: boolean) {
function goalWriteDraft(selected: CapabilityConfigurationCatalog["capabilities"][number], draft: Record<string, unknown>, editorMode: "guided" | "json", cadenceOverride: boolean, horizonOverride: boolean, exploreModeOverride: boolean, yardstickOverride: boolean) {
const defaults = selected.capability_id === "todo_replan_cadence"
? editorMode === "json" ? undefined : replanCadenceEditorDefaults(selected.effective_configuration?.configuration, selected.default)
: selected.default;
const writable = projectEditableCapabilityConfiguration(selected.configuration_editor, draft, defaults);
if (selected.capability_id === "reward_memory") return rewardMemoryWriteValue(writable);
if (selected.capability_id === "explore_harness") return goalExploreWriteValue(writable, exploreModeOverride);
if (selected.capability_id === "todo_replan_cadence" && editorMode === "guided") {
return goalReplanCadenceWriteValue(writable, cadenceOverride, horizonOverride);
return goalReplanCadenceWriteValue(writable, cadenceOverride, horizonOverride, yardstickOverride);
}
if (selected.capability_id === "pull_request_review" && !Object.hasOwn(selected.current ?? {}, "owner_logins")
&& JSON.stringify(writable.owner_logins ?? []) === JSON.stringify(selected.effective_configuration?.configuration?.owner_logins ?? [])) {
Expand Down Expand Up @@ -83,6 +84,7 @@ function useCapabilityMutation({ goalId, loading, onApplied, selected, t }: Read
draft: {},
cadenceOverride: false,
horizonOverride: false,
yardstickOverride: false,
exploreModeOverride: false,
partialWrite: null,
readbackPending: false,
Expand Down Expand Up @@ -122,6 +124,7 @@ function useCapabilityMutation({ goalId, loading, onApplied, selected, t }: Read
),
cadenceOverride: hasReplanCadenceOverride(selected?.current),
horizonOverride: Object.hasOwn(selected?.current ?? {}, "planning_horizon_goal"),
yardstickOverride: Object.hasOwn(selected?.current ?? {}, "yardstick_enabled"),
exploreModeOverride: Object.hasOwn(selected?.current ?? {}, "mode"),
partialWrite: null,
readbackPending: false,
Expand All @@ -137,7 +140,7 @@ function useCapabilityMutation({ goalId, loading, onApplied, selected, t }: Read
setMutation((current) => ({ ...current, busy: "preview", partialWrite: null, readbackPending: false }));
setError(null);
try {
const nextPreview = await previewGoalConfiguration(goalId, selected.capability_id, configuration === null ? null : goalWriteDraft(selected, configuration, editorMode, mutation.cadenceOverride, mutation.horizonOverride, mutation.exploreModeOverride));
const nextPreview = await previewGoalConfiguration(goalId, selected.capability_id, configuration === null ? null : goalWriteDraft(selected, configuration, editorMode, mutation.cadenceOverride, mutation.horizonOverride, mutation.exploreModeOverride, mutation.yardstickOverride));
if (generation === editorGeneration.current) setMutation((current) => ({ ...current, preview: nextPreview }));
} catch (reason) {
if (generation === editorGeneration.current) setError(reason instanceof Error ? reason.message : t("capabilities.previewFailed"));
Expand All @@ -152,7 +155,7 @@ function useCapabilityMutation({ goalId, loading, onApplied, selected, t }: Read
setMutation((current) => ({ ...current, busy: "apply" }));
setError(null);
try {
const writableDraft = goalWriteDraft(selected, mutation.draft, editorMode, mutation.cadenceOverride, mutation.horizonOverride, mutation.exploreModeOverride);
const writableDraft = goalWriteDraft(selected, mutation.draft, editorMode, mutation.cadenceOverride, mutation.horizonOverride, mutation.exploreModeOverride, mutation.yardstickOverride);
const result = await applyGoalConfiguration(
goalId,
selected.capability_id,
Expand Down Expand Up @@ -190,6 +193,7 @@ function useCapabilityMutation({ goalId, loading, onApplied, selected, t }: Read
: { ...current.draft, [key]: value },
cadenceOverride: current.cadenceOverride || (selected?.capability_id === "todo_replan_cadence" && (key === "count_unit" || key === "count")),
horizonOverride: current.horizonOverride || (selected?.capability_id === "todo_replan_cadence" && key === "planning_horizon_goal"),
yardstickOverride: current.yardstickOverride || (selected?.capability_id === "todo_replan_cadence" && key === "yardstick_enabled"),
exploreModeOverride: current.exploreModeOverride || (selected?.capability_id === "explore_harness" && key === "mode"),
preview: null,
}));
Expand All @@ -212,6 +216,8 @@ function useCapabilityMutation({ goalId, loading, onApplied, selected, t }: Read
? hasReplanCadenceOverride(parsed) : current.cadenceOverride,
horizonOverride: selected.capability_id === "todo_replan_cadence" && parsed
? Object.hasOwn(parsed, "planning_horizon_goal") : current.horizonOverride,
yardstickOverride: selected.capability_id === "todo_replan_cadence" && parsed
? Object.hasOwn(parsed, "yardstick_enabled") : current.yardstickOverride,
exploreModeOverride: selected.capability_id === "explore_harness" && parsed
? Object.hasOwn(parsed, "mode") : current.exploreModeOverride,
}));
Expand All @@ -222,7 +228,7 @@ function useCapabilityMutation({ goalId, loading, onApplied, selected, t }: Read
if (loading || mutation.busy || !jsonValid) return;
if (editorMode === "guided" && selected) {
const jsonValue = ["todo_replan_cadence", "explore_harness"].includes(selected.capability_id)
? goalWriteDraft(selected, mutation.draft, editorMode, mutation.cadenceOverride, mutation.horizonOverride, mutation.exploreModeOverride)
? goalWriteDraft(selected, mutation.draft, editorMode, mutation.cadenceOverride, mutation.horizonOverride, mutation.exploreModeOverride, mutation.yardstickOverride)
: mutation.draft;
setJsonDraft(JSON.stringify(jsonValue, null, 2));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {WorkYardstickReadback} from "./work-yardstick-readback";
import {exploreFindingCopy} from "./explore-finding-copy";
import {useEffect, useRef, useState} from "react";
import {AlertTriangle, ChevronRight, RefreshCw} from "lucide-react";
Expand Down Expand Up @@ -127,6 +128,7 @@ export function GoalExploreResults({goalId, zh, active = true, readSourceUrl}: {
<ul>{sections.decision![key].map((text, index) => <li key={index}>{text}</li>)}</ul>
</div> : null)}</section> : null}
</div> : <p className="explore-finding-raw">{selected.summary}</p>}
{selected.work_yardstick ? <WorkYardstickReadback capture={selected.work_yardstick} zh={zh}/> : null}
<details className="explore-reader-details"><summary>{zh ? "来源与范围" : "Sources and scope"}</summary>
<dl><dt>{zh ? "记录时间" : "Recorded at"}</dt><dd><time dateTime={selected.last_updated_at}>{selected.last_updated_at}</time></dd>
<dt>{zh ? "记录者" : "Recorded by"}</dt><dd>{selected.agent_id || "—"}</dd>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {WorkYardstickReadback} from "./work-yardstick-readback";
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";
Expand Down Expand Up @@ -143,7 +144,7 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
const findingBody = (finding: ExploreHistoryEvent) => {
const execution = finding.execution_observation;
const measured = (value: unknown) => value === null ? (zh ? "未测量" : "Not measured") : String(value);
return <>{finding.measurements ? <ExploreMeasurements measurements={finding.measurements} zh={zh}/> : null}{findingNarrative(finding)}{execution ? <details><summary><span>{zh ? "实际执行与资源观测" : "Execution and resource observations"}</span> <ContextHelp label={zh ? "执行观测说明" : "Execution observation guide"}><p>{zh ? "来源记录的执行观测;不等于方法有效或当前候选已通过验收。" : "Source-recorded execution observations; not proof of method benefit or current-candidate acceptance."}</p></ContextHelp></summary>
return <>{finding.measurements ? <ExploreMeasurements measurements={finding.measurements} zh={zh}/> : null}{findingNarrative(finding)}{finding.work_yardstick ? <WorkYardstickReadback capture={finding.work_yardstick} zh={zh}/> : null}{execution ? <details><summary><span>{zh ? "实际执行与资源观测" : "Execution and resource observations"}</span> <ContextHelp label={zh ? "执行观测说明" : "Execution observation guide"}><p>{zh ? "来源记录的执行观测;不等于方法有效或当前候选已通过验收。" : "Source-recorded execution observations; not proof of method benefit or current-candidate acceptance."}</p></ContextHelp></summary>
<dl className="research-finding-sections">
<dt>{zh ? "计划方法 → 实际方法" : "Planned → executed method"}</dt><dd>{String(execution.planned_method_id)} → {String(execution.executed_method_id)}</dd>
{execution.change_reason ? <><dt>{zh ? "改向原因" : "Reason for change"}</dt><dd>{String(execution.change_reason)}</dd></> : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type {WorkYardstickCapture, WorkYardstickProfile} from "../../../../../../loopx/control_plane/work_items/yardstick_decision.js";

/** A source record, never an acceptance or permission indicator. */
export function WorkYardstickReadback({capture, zh}: {capture: WorkYardstickCapture; zh: boolean}) {
const {decision, binding} = capture;
const profile = (value: WorkYardstickProfile) => <>{value.metric} ({value.unit}) {value.higher_is_better ? "↑" : "↓"}
<br/><code>{value.comparison_scope} · {value.input_revision} · {value.evaluation_ref}</code></>;
return <details className="explore-reader-details work-yardstick-readback">
<summary>{zh ? "目标与衡量标准" : "Target and yardstick"}</summary>
<dl className="research-finding-sections">
<dt>{zh ? "目标" : "Target"}</dt><dd>{decision.target.summary}</dd>
<dt>{zh ? "适用范围" : "Scope"}</dt><dd>{decision.target.scope}</dd>
<dt>{zh ? "成果指标" : "Outcome metric"}</dt><dd>{profile(decision.authoritative)}</dd>
<dt>{zh ? "代理指标" : "Proxy metric"}</dt><dd>{decision.proxy ? profile(decision.proxy) : zh ? "直接评估" : "Direct evaluation"}</dd>
{decision.counterevidence_refs.length ? <><dt>{zh ? "代理反证" : "Proxy counterevidence"}</dt><dd>{decision.counterevidence_refs.map(ref => <code key={ref}>{ref}</code>)}</dd></> : null}
<dt>{zh ? "下一步" : "Next action"}</dt><dd>{decision.next_action.reason}<br/><code>{decision.next_action.method_id}</code></dd>
<dt>{zh ? "成本边界" : "Cost bound"}</dt><dd>{decision.next_action.cost_bound}</dd>
<dt>{zh ? "验收来源" : "Acceptance source"}</dt><dd><code>{decision.target.acceptance_ref}</code></dd>
<dt>{zh ? "任务" : "Task"}</dt><dd><code>{binding.todo_id}</code></dd>
{decision.prior_finding_id ? <><dt>{zh ? "上次判断" : "Previous decision"}</dt><dd><code>{decision.prior_finding_id}</code></dd></> : null}
</dl>
<p>{zh ? "这是记录者的研究判断;代理有效性与成果验收仍需独立证据。" : "This records the author's research decision; proxy fidelity and outcome acceptance still require independent evidence."}</p>
</details>;
}
Loading
Loading