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
1 change: 1 addition & 0 deletions apps/presentation/dashboard/src/data/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ export const runRecordSchema = z.object({
});

export const runGoalSchema = z.object({
goal_objective: z.string().nullable().optional().catch(null),
zcode_goal_eligible_agent_ids: z.array(z.string()).optional().default([]).catch([]),
acceptance_observation: goalAcceptanceObservationSchema.optional().nullable().catch(null),
id: z.string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ export function GoalAcceptanceContractSection({goalId, contract: retained, copy,
} finally {if (token === generation.current) {flight.current = false; setBusy(false);}}
}
const unavailable = read.isError || actions.isError || !!error;
if (!management && !enabled && !plan && !unavailable) return <>{children}{notice ? <p role="status">{notice}</p> : null}</>;
if (!management && !enabled && !plan && !unavailable) return <>{children}
{overview ? <p className="goal-overview-note">{contract?.enabled === false
? (zh ? "验收标准未配置(可选)。" : "Acceptance criteria are not configured (optional).")
: (zh ? "此来源未提供验收标准(可选)。" : "This source has not provided acceptance criteria (optional).")}</p> : null}
{notice ? <p role="status">{notice}</p> : null}</>;
const readOnly = !canManage;
const criterionText = (item: typeof criteria[number]) => item.oracle
? ({current: zh ? "当前版本已核对" : "Current versions checked", recorded: zh ? "历史结果,当前版本未核对" : "Recorded; current versions not checked", stale: zh ? "来源或版本已变化,请重新检查" : "Source or versions changed; check again", failed: zh ? "检查失败;修复交付物后可再次检查" : "Checks failed; repair the delivery before checking again", unverified: zh ? "尚未检查" : "Not checked", unavailable: zh ? "证据不可用" : "Evidence unavailable"})[fresh ? item.oracle.state : "recorded"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function useGoalWorkspaceDigest(goal: WorkspaceGoal | null, userTodos: Wo
const byTodo = new Map(map?.nodes.flatMap(node => (node.refs.todo_ids ?? []).map(id => [id, node] as const)));
return compileGoalWorkspaceDigest({
observedAt: new Date().toISOString(),
objective: goal?.objective,
activation: goal?.activationState ?? "active",
todos: (goal?.agentTodos ?? []).map(todo => ({
todoId: todo.todoId, done: todo.done, status: todo.status, taskClass: todo.taskClass,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useEffect, useState, type ReactNode} from "react";
import {type ReactNode} from "react";
import {type DeliveryReviewRead} from "./delivery-review-state";
import {GoalWorkspaceWork} from "./goal-workspace-work";
import { ArrowRight, Hand, Info } from "lucide-react";
Expand Down Expand Up @@ -40,23 +40,15 @@ export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenD
readOnly: boolean; onOpenDetails: () => void; onSelect: (selection: WorkspaceDrawerSelection | null) => void; onView: (view: WorkspaceGoalTab) => void;
}) {
const { t, locale } = useWorkspaceI18n();
const [narrow, setNarrow] = useState(() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(max-width: 640px)").matches);
const [directionExpanded, setDirectionExpanded] = useState(false);
useEffect(() => {
const media = window.matchMedia("(max-width: 640px)");
const update = () => setNarrow(media.matches);
update(); media.addEventListener("change", update);
return () => media.removeEventListener("change", update);
}, []);
const copy = locale === "zh-CN" ? {
direction: "方向", current: "当前推进", agentSummary: "Agent 的进展摘要", attention: "需要你", details: "Goal 信息",
tasks: "查看任务", execution: "执行记录", optional: "{count} 项可选",
noTarget: "尚未声明可衡量目标。目标通过 Goal 的验收配置声明。",
objectiveUnavailable: "此来源尚未提供 Goal 的目标声明。",
remote: "此来源仅提供同步的状态与验收观察,交付链需要实时本机来源。",
} : {
direction: "Direction", current: "Current work", agentSummary: "Agent's progress summary", attention: "Needs you", details: "Goal information",
tasks: "View tasks", execution: "Execution", optional: "{count} optional",
noTarget: "No measurable target declared. Targets are declared through the Goal's acceptance configuration.",
objectiveUnavailable: "This source has not provided the Goal's declared objective.",
remote: "This source provides synchronized status and acceptance observations. The delivery chain requires the live local source.",
};
const attention = userTodos.filter(item => item.goalId === goal.goalId);
Expand All @@ -68,7 +60,6 @@ export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenD
const moreAttention = needs.availability !== "ok" || needs.coverage !== "complete" || (totalAttention ?? attention.length) > 3;
const run = items.find((item): item is Extract<WorkspaceTimelineItem, { kind: "run" }> => item.kind === "run" && item.run.goalId === goal.goalId);
const observation = review.snapshot?.acceptance ?? (goal.acceptanceObservation?.goal_id === goal.goalId ? goal.acceptanceObservation : null);
const contract = observation?.goal_acceptance_contract?.enabled === true ? observation.goal_acceptance_contract : null;
const acceptanceCopy = goalAcceptanceContractCopy[locale === "zh-CN" ? "zh-CN" : "en"];
const sinceParts = sinceYouLooked ? [
sinceYouLooked.newlyDone ? t("since.newlyDone", { count: sinceYouLooked.newlyDone }) : null,
Expand All @@ -86,19 +77,15 @@ export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenD
<div className={`goal-overview-summary${showAttention ? " has-attention" : ""}`}>
<section className="goal-overview-direction" aria-label={copy.direction}>
<header className="goal-overview-heading">
<h2>{contract ? copy.direction : copy.current}</h2>
<h2>{copy.direction}</h2>
<button onClick={onOpenDetails} type="button"><Info aria-hidden size={14} />{copy.details}</button>
</header>
<GoalAcceptanceContractSection overview goalId={goal.goalId} contract={observation?.goal_acceptance_contract} copy={acceptanceCopy} current={review.usable} canManage={!readOnly && active && review.usable}>
<button className="goal-direction-toggle" type="button" aria-expanded={!narrow || directionExpanded} onClick={() => setDirectionExpanded(value => !value)}>
{locale === "zh-CN" ? (directionExpanded ? "收起目标与条件" : "展开目标与条件") : (directionExpanded ? "Hide direction and criteria" : "Show direction and criteria")}
</button>
<div className="goal-direction-content" hidden={narrow && !directionExpanded}>
<p className="goal-overview-objective goal-overview-current">{goal.nextSentence}</p>
<p className="goal-overview-objective" data-testid="goal-declared-objective">{digest.objective ?? copy.objectiveUnavailable}</p>
<details className="goal-overview-agent-note"><summary>{copy.current}</summary>
<p className="goal-overview-note">{goal.nextSentence}</p>
{goal.agentSentence && goal.agentSentence !== goal.nextSentence ? <details className="goal-overview-agent-note"><summary>{copy.agentSummary}</summary><p className="goal-overview-note">{goal.agentSentence}</p></details> : null}
<p className="goal-overview-note">{copy.noTarget}</p>
</div>
</GoalAcceptanceContractSection>
</details>
<GoalAcceptanceContractSection overview goalId={goal.goalId} contract={observation?.goal_acceptance_contract} copy={acceptanceCopy} current={review.usable} canManage={!readOnly && active && review.usable}/>
{run ? <button className="goal-overview-run" type="button" onClick={() => onSelect({ kind: "run", item: run.run })}><small>{copy.execution} · {run.run.agentLabel}</small><ArrowRight aria-hidden size={15} /></button> : null}
</section>
{showAttention ? <section className="goal-overview-attention" aria-label={copy.attention} id={goalOverviewAttentionId} tabIndex={-1}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ export type WorkspaceGoalSubagentConfiguration = {
};

export type WorkspaceGoal = {
/** Bounded, safe text from this Goal's declared source document. */
objective?: string | null;
acceptanceObservation?: GoalAcceptanceObservation | null;
loadState?: "loading" | "error";
loadError?: WorkspaceLoadError;
Expand Down
1 change: 1 addition & 0 deletions apps/presentation/dashboard/src/views/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,7 @@ function buildPersonalHomeModel(
.find((value) => value !== "" && value !== "暂无") ?? t("projection.nextUpdatePending");
return [{
activationState: goal.activation_state,
objective: goal.goal_objective ?? null,
agentId: agentRow?.agentId ?? registeredAgentIds[0] ?? "codex",
agentLaneCount: goalAgentLanes.length,
agentLanes: goalAgentLanes,
Expand Down
32 changes: 17 additions & 15 deletions docs/architecture/rfcs/goal-workspace-surface-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Facts at `5c62c5c03`:
| Tasks | `GoalTasksView` | Lanes: awaiting confirmation, pending/running, scheduled, completed; agent-lane filter; completed history from `/api/chat/completed-todos` | Not grouped by responsibility; text assignee; no observed elapsed time |
| Usage | `usage_summary` | 24h/7d tokens, `cost_usd`, `duration_ms`; fields absent when unmeasured; `sample_run_count`, `available` | No lifetime aggregate; no explicit per-runtime coverage |
| Acceptance | Goal authority via `goal-acceptance` (preview, provider CAS, exact replay); Dashboard reads only | Objective, criteria, coverage scope, bindings; observation projection is bounded and partial by design | No App declaration path; target absence is silent |
| Goal brief | Status Goal snapshot | No general objective field; the Agent's progress sentence and, when enabled, the acceptance contract objective | The page has no stable brief for Goals without a contract |
| Goal brief | Status Goal snapshot | Bounded declared Goal-document objective; progress and optional contract stay separate | Missing source content stays unavailable |
| Todo identity | Status `todoItemSchema` | Native Todos are addressed by `todo_id` and intentionally carry no synthetic index; legacy Markdown Todos keep `index` | A numbered rail would invent identity |
| Activity | `goal-activity.ts`, agent-lane `lastActivityAt`, host-thread activity, run timeline | Work-kind chip and per-lane observation | Not summarized as one observed last-activity fact |
| Themes | `workspace-theme.ts` (`loopx`, `paper`, `brutal`) | Light-first presets | No dark inverse of the LoopX theme |
Expand All @@ -154,7 +154,7 @@ owns cross-region page semantics, so this RFC owns composition only.
| --- | --- | --- |
| Masthead | Goal registry and lifecycle activation | `goal_lifecycle` typed action |
| Vitals | Todo projection, attention queue, `usage_summary`, execution observation | None; each cell focuses its region |
| Direction | Acceptance contract and observation; progress summary (Section 12 D6) | Acceptance owner configuration (M3, Section 12 D3) |
| Direction | Recorded Goal-document objective; separate acceptance contract and progress observation (Section 12 D6) | Acceptance owner configures checks, not the Goal's recorded objective (M3, Section 12 D3) |
| Needs you | Attention and gate owners; action review plan | Existing gate decision actions |
| Now, rail | Todo projection, execution observation, run timeline, `goal_task_map_v0` relations | Todo create dry-run/apply; existing drawer actions |
| Results | Files and Goal-results owner; acceptance owner for badges | Existing open/read actions |
Expand All @@ -178,7 +178,7 @@ screen.*
| 1 | **Masthead**: breadcrumb (Workspace / Goals / Goal), title, team line (Agent label or count, repository), Stop/Resume, settings | Always, on every Goal view; compacts on scroll | Identity and the one consequential control stay one glance away |
| 2 | **Vitals strip**: at most six cells; each cell is a button that focuses its region | Always | Answers moving / waiting on me / how far / what cost in one line |
| 3 | **Delta line**: "Since you looked at 09:12: 2 done · 1 new decision" | Only for a material delta; cost omitted until its owner qualifies the interval | The return moment the product promises |
| 4 | **Direction**: objective, acceptance criteria with verification state | Always; criteria only with a contract | What "done" means and how close it is |
| 4 | **Direction**: recorded Goal objective, separate optional acceptance criteria and verification state | Always; criteria only with a contract | What the Goal is for; what its configured checks establish |
| 5 | **Needs you**: decision cards with recommendation, consequence and actions | Only when non-empty | Decisions are the owner's actual work |
| 6 | **Now**: lens switch (By owner / Board / Map) and Add task | Open work exists | Who is doing what, who is blocked |
| 7 | **Results**: returned outputs, with acceptance shown separately | At least one result | What came back and whether it is trusted |
Expand Down Expand Up @@ -320,18 +320,20 @@ qualification remain required.

### 5.8 Direction and measurable target

- When an acceptance contract is enabled, show its objective and criteria.
Otherwise show the current progress sentence, labeled as the Agent's
summary. Status projects no general Goal objective today; adding one is an
owner decision (Section 12 D6), not a frontend inference from chat or Todo
text.
- Always show the Goal's recorded objective independently of acceptance. Status
transports the existing Goal document's `objective` metadata through the
typed workspace digest, with the existing public-safety filter and a 1,000
character bound. It performs no additional source read. Missing or unreadable
content stays unavailable; a title, Todo, progress sentence, Agent Vision or
acceptance contract cannot fill it. Current progress is a separate disclosure.
An enabled acceptance contract keeps its own scoped objective and criteria.
- Each criterion shows its owner state: verified (with time), failing, not yet
verified, or waiting on work. A summary reads "1 of 3 verified". Observation
partiality stays visible; an absence of gaps is not acceptance.
- **No contract:** a quiet line, "No measurable target declared", with a
"Declare target" action once M3 admits an App path. Until then, the line
explains plainly that targets are declared through the Goal owner's
acceptance configuration. A command-copy button is not offered as the action.
- **No contract:** a quiet "Acceptance criteria are not configured (optional)"
line when that absence is observed; an older or incomplete source says that
criteria were not provided. Existing Goal settings own configuration. This
never denies the Goal's objective, enables a contract or creates a new gate.
- **M3:** declaration goes through the acceptance owner: objective, criteria
descriptions, coverage scope (`selected_work` or `all_advancement`) and
bindings. It follows preview, one confirmation, provider CAS and readback, and
Expand Down Expand Up @@ -394,7 +396,7 @@ separate identity.
- At 1200px and wider: sidebar, one content frame and the rail.
- From 768px to 1199px: the rail becomes a drawer toggled from the masthead.
- At 640px and narrower: one column in this order: masthead with an icon Stop,
vitals in a 3×2 grid, the delta line, Needs you and Now. Direction collapses,
vitals in a 3×2 grid, the delta line, Needs you and Now. Current progress collapses; the recorded Goal objective stays visible,
and All work opens from the top bar.

![Phone composition](assets/goal-workspace-surface-v0/goal-workspace-phone.jpg)
Expand Down Expand Up @@ -654,7 +656,7 @@ local packaged rendering, not cross-host latency or W2 live scale. V6 desktop/mo
have been inspected; the PR's final validation record owns the full regression result. Activity covers
material observations available in the existing timeline, not a complete audit or independent acceptance.
Results keep their existing readback validators; run summaries cannot prove adoption.
The approved narrow-screen presentation folds Direction on phones and opens All work from the
The narrow-screen presentation keeps the Goal objective visible, folds current progress and opens All work from the
masthead in a native dialog. Escape restores launcher focus; selecting a task returns to the existing
inspector. History keeps its loaded state when reads are inactive, and missing remote Todo links
show an unavailable notice without querying local owners.
Expand Down Expand Up @@ -754,7 +756,7 @@ introduced; six uncalibrated process judgments retain their existing role.
| D3 | App authoring scope for acceptance targets | Goal acceptance authority | Descriptions, scope and bindings with checks kept local; full check authoring; none | The first option | M3 |
| D4 | Usage coverage signal | Usage RFC owner | Sample counts only; explicit measured versus observed runtimes | An explicit field before "partial" means more than sampling | M1 "partial" beyond sampling |
| D5 | Dark theme default | Presentation owner | Opt-in; follow system preference | Opt-in until AA review on real data | M4 |
| D6 | Goal objective in status | Goal registry / Vision owner | Contract objective or progress summary only; an additive projected objective from the Goal's recorded creation or Vision text | The additive field, public-safe and length-bounded | A brief for Goals without a contract |
| D6 | Goal objective in status | Goal registry / Vision owner | Recorded Goal-document objective, independent of acceptance and progress; unavailable remains explicit | The additive field, public-safe and length-bounded | A brief for Goals without a contract |
| D7 | Since-last-look spend | Usage RFC owner | Omit cost; owner-qualified interval aggregate or compatible monotonic counter | Omit cost until source/scope/currency/generation, interval coverage and retention are qualified; no rolling-total subtraction | Any cost in the M3 delta line |

## Appendix A: Execution ledger
Expand Down
Loading
Loading