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
2 changes: 2 additions & 0 deletions apps/presentation/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"smoke:presentation-surface-schema": "rm -rf /tmp/loopx-presentation-surface-schema-smoke && tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --skipLibCheck --strict --resolveJsonModule --esModuleInterop --outDir /tmp/loopx-presentation-surface-schema-smoke smoke/presentation-surface-schema-smoke.ts src/data/status.ts src/data/decision-research.ts src/data/goal-channel-frontstage.ts && NODE_PATH=\"$PWD/node_modules\" node /tmp/loopx-presentation-surface-schema-smoke/apps/presentation/dashboard/smoke/presentation-surface-schema-smoke.js",
"smoke:projection-localization": "rm -rf /tmp/loopx-projection-localization-smoke && tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck --strict --outDir /tmp/loopx-projection-localization-smoke smoke/projection-localization-smoke.ts src/features/personal-workspace/projection-localization.ts && node /tmp/loopx-projection-localization-smoke/smoke/projection-localization-smoke.js",
"smoke:pwa-bundle": "python3 ../../../examples/dashboard-pwa-bundle-smoke.py",
"smoke:goal-acceptance-browser": "node ../../../examples/dashboard-goal-acceptance-browser-smoke.mjs",
"smoke:goal-acceptance-packaged": "LOOPX_GOAL_ACCEPTANCE_PACKAGED=1 LOOPX_GOAL_ACCEPTANCE_PORT=5292 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/dashboard-goal-acceptance-browser-smoke.mjs",
"smoke:status-projection-contract": "rm -rf /tmp/loopx-status-projection-contract-smoke && tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --skipLibCheck --strict --resolveJsonModule --esModuleInterop --outDir /tmp/loopx-status-projection-contract-smoke smoke/status-projection-contract-smoke.ts src/data/status.ts src/data/status-merge.ts src/data/status-request-fence.ts && NODE_PATH=\"$PWD/node_modules\" node /tmp/loopx-status-projection-contract-smoke/apps/presentation/dashboard/smoke/status-projection-contract-smoke.js",
"smoke:status-source-switch-browser": "node ../../../examples/status-source-switch-browser-smoke.mjs",
"smoke:status-source-switch-packaged": "LOOPX_STATUS_SOURCE_SWITCH_PACKAGED=1 LOOPX_STATUS_SOURCE_SWITCH_PORT=5198 node ../../../examples/status-source-switch-browser-smoke.mjs",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { goalAcceptanceObservationSchema } from "../src/data/goal-acceptance-observation.js";
import {
exampleStatusPayload,
parseStatusPayload,
Expand Down Expand Up @@ -330,3 +331,14 @@ verifyProjectionFetchContract()
console.error(error);
throw error;
});

// This bounded observation must not claim the distinct full lifecycle RFC id.
const acceptanceObservation = {
schema_version: "goal_acceptance_observation_projection_v0",
goal_id: "synthetic-goal", read_only: true, acceptance_assessed: false,
coverage: "partial", missing_sources: [], truncated: false,
historical_progress: [], acceptance_gaps: [], guards: [],
next_action: null, next_action_source: null,
};
assert(goalAcceptanceObservationSchema.safeParse(acceptanceObservation).success, "acceptance observation v0 must parse");
assert(!goalAcceptanceObservationSchema.safeParse({ ...acceptanceObservation, schema_version: "goal_artifact_lifecycle_projection_v0" }).success, "full lifecycle v0 is a distinct contract, not an observation alias");
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { z } from "zod";

const nullableText = z.string().nullable();
export const goalAcceptanceObservationSchema = z.object({
schema_version: z.literal("goal_acceptance_observation_projection_v0"),
goal_id: z.string(),
read_only: z.literal(true),
acceptance_assessed: z.literal(false),
coverage: z.enum(["partial", "unavailable"]),
missing_sources: z.array(z.string()),
truncated: z.boolean(),
historical_progress: z.array(z.object({ kind: z.string(), observed_at: nullableText, source: z.string(), evidence_refs: z.array(z.string()) })),
acceptance_gaps: z.array(z.object({ kind: z.string(), owner: nullableText, reason: nullableText, evidence_required: nullableText, observed_at: nullableText, source: z.string() })),
guards: z.array(z.object({ kind: z.string(), todo_id: nullableText, blocks_agent: nullableText, owner: nullableText, reason: nullableText, evidence_required: nullableText, decision_scope: nullableText })),
next_action: nullableText,
next_action_source: nullableText,
});
export type GoalAcceptanceObservation = z.infer<typeof goalAcceptanceObservationSchema>;
3 changes: 3 additions & 0 deletions apps/presentation/dashboard/src/data/status.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import rawStatus from "../../../../../examples/status.example.json";
import { z } from "zod";

import { goalAcceptanceObservationSchema } from "./goal-acceptance-observation";

import { goalChannelProjectionSchema } from "./goal-channel-frontstage";

export const quotaSchema = z.object({
Expand Down Expand Up @@ -481,6 +483,7 @@ export const runRecordSchema = z.object({
});

export const runGoalSchema = z.object({
acceptance_observation: goalAcceptanceObservationSchema.optional().nullable().catch(null),
id: z.string(),
activation_state: z.enum(["active", "stopped"]).optional().default("active"),
display_name: z.string().optional().nullable(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GoalAcceptanceObservationCard } from "./goal-acceptance-observation-card";
import { AttentionDetailCard } from "./attention-detail-card";
import { attentionSuccessor, canReviewAttention } from "./attention-details";
import { useCallback, useEffect, useRef, useState } from "react";
Expand Down Expand Up @@ -615,6 +616,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
<div><dt>{t("drawer.duration")}</dt><dd>{formatUsageValue(selection.item.usage?.durationMs24h, t("drawer.usageNotMeasured"), formatDurationMs)} / {formatUsageValue(selection.item.usage?.durationMs7d, t("drawer.usageNotMeasured"), formatDurationMs)}</dd></div>
</dl>
</section>
<GoalAcceptanceObservationCard goal={selection.item} />
{(() => {
const notification = goalNotifications.find((row) => row.goalId === selection.item.goalId);
const connection = larkConnections.find((row) => row.goal_id === selection.item.goalId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { WorkspaceGoal } from "./personal-workspace-model";
import { useWorkspaceI18n } from "./i18n";

export function GoalAcceptanceObservationCard({ goal }: { goal: WorkspaceGoal }) {
const { t } = useWorkspaceI18n();
const labels = {
connected: t("acceptance.connected"), mapped: t("acceptance.mapped"), refreshed: t("acceptance.refreshed"),
adapter_inspected: t("acceptance.inspected"), run_recorded: t("acceptance.recorded"), reward_judged: t("acceptance.judged"),
operator_approved: t("acceptance.approved"), controller_ready: t("acceptance.ready"),
attention_queue: t("acceptance.attentionSource"), agent_vision: t("acceptance.visionSource"),
todo_projection: t("acceptance.todoSource"), current_run: t("acceptance.runSource"),
};
const label = (key: string) => labels[key as keyof typeof labels] ?? t("acceptance.unknown");
const projection = goal.acceptanceObservation;
const unavailable = goal.loadState || !projection || projection.goal_id !== goal.goalId || projection.coverage === "unavailable";
return <section className="personal-detail-card personal-goal-acceptance" aria-label={t("acceptance.title")}>
<div className="personal-detail-card-title"><h3>{t("acceptance.title")}</h3><em>{t("common.readOnly")}</em></div>
<p role="status">{t(unavailable ? "acceptance.unavailable" : "acceptance.partial")}</p>
{!unavailable && projection ? <>
<h4>{t("acceptance.gaps")}</h4>
{projection.acceptance_gaps.length ? projection.acceptance_gaps.map((gap, index) => <div className="personal-acceptance-observation" key={`${gap.kind}:${gap.owner}:${index}`}>
<p>{gap.reason ?? t("acceptance.reasonUnknown")}</p>
<dl><div><dt>{t("common.owner")}</dt><dd>{gap.owner ?? t("acceptance.unknown")}</dd></div>
<div><dt>{t("acceptance.required")}</dt><dd>{gap.evidence_required ?? t("acceptance.unknown")}</dd></div>
<div><dt>{t("acceptance.observed")}</dt><dd>{gap.observed_at ?? t("acceptance.unknown")}</dd></div></dl>
</div>) : <p>{t("acceptance.noGaps")}</p>}
<h4>{t("acceptance.guards")}</h4>
{projection.guards.length ? projection.guards.map((guard, index) => <div className="personal-acceptance-observation" key={`${guard.todo_id}:${index}`}>
<p>{guard.reason ?? t("acceptance.reasonUnknown")}</p>
<dl><div><dt>{t("common.owner")}</dt><dd>{guard.owner ?? t("acceptance.unknown")}</dd></div>
{guard.blocks_agent ? <div><dt>{t("common.agent")}</dt><dd>{guard.blocks_agent}</dd></div> : null}
{guard.todo_id ? <div><dt>{t("common.task")}</dt><dd>{guard.todo_id}</dd></div> : null}
<div><dt>{t("acceptance.required")}</dt><dd>{guard.evidence_required ?? t("acceptance.unknown")}</dd></div>
<div><dt>{t("acceptance.scope")}</dt><dd>{guard.decision_scope ?? t("acceptance.unknown")}</dd></div></dl>
</div>) : <p>{t("acceptance.noGuards")}</p>}
<h4>{t("acceptance.next")}</h4><p>{projection.next_action ?? t("acceptance.unknown")}</p>
<details><summary>{t("acceptance.historical_progress")} · {projection.historical_progress.length}</summary>
<p>{t("acceptance.historical")}</p>
{projection.historical_progress.map((observation) => <p key={observation.kind}><strong>{label(observation.kind)}</strong> · {observation.observed_at ?? t("acceptance.unknown")} {observation.evidence_refs.join(", ")}</p>)}
</details>
{projection.missing_sources.length ? <p>{t("acceptance.missing")} {projection.missing_sources.map(label).join(", ")}</p> : null}
{projection.truncated ? <p>{t("acceptance.truncated")}</p> : null}
</> : null}
</section>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,36 @@ export const workspaceLocaleStorageKey = "loopx-pw-locale";
export type WorkspaceLocale = "en" | "zh-CN";

const en = {
"acceptance.connected": "Connected",
"acceptance.mapped": "Project mapped",
"acceptance.refreshed": "State refreshed",
"acceptance.inspected": "Adapter inspected",
"acceptance.recorded": "Run recorded",
"acceptance.judged": "Feedback recorded",
"acceptance.approved": "Approval recorded",
"acceptance.ready": "Controller readiness recorded",
"acceptance.attentionSource": "Current status",
"acceptance.visionSource": "Agent acceptance criteria",
"acceptance.todoSource": "Task state",
"acceptance.runSource": "Fresh run evidence",
"acceptance.title": "Acceptance observations",
"acceptance.unavailable": "Acceptance observations are unavailable. Goal completion is unknown.",
"acceptance.partial": "Partial observations only. Completed tasks and an empty gap list do not prove Goal acceptance.",
"acceptance.gaps": "Evidence still required",
"acceptance.reasonUnknown": "Reason not provided by the source",
"acceptance.unknown": "Unknown",
"acceptance.required": "Required evidence or condition",
"acceptance.observed": "Observed at",
"acceptance.noGaps": "No gaps in the available observations. Full acceptance has not been assessed.",
"acceptance.guards": "Pending gates",
"acceptance.noGuards": "No pending gates in the available observations.",
"acceptance.scope": "Decision scope",
"acceptance.next": "Next action from current status",
"acceptance.historical_progress": "Recorded progress",
"acceptance.historical": "Historical lifecycle observations do not grant permission or certify acceptance.",
"acceptance.missing": "Sources not available:",
"acceptance.truncated": "Only the first 12 observations are shown.",

"common.actions": "Actions",
"common.agent": "Agent",
"common.allMessages": "All messages",
Expand Down Expand Up @@ -932,6 +962,35 @@ const en = {
export type WorkspaceMessageKey = keyof typeof en;

const zhCN: Record<WorkspaceMessageKey, string> = {
"acceptance.connected": "已连接",
"acceptance.mapped": "已建立项目映射",
"acceptance.refreshed": "已刷新状态",
"acceptance.inspected": "已检查适配器",
"acceptance.recorded": "已记录执行",
"acceptance.judged": "已记录反馈",
"acceptance.approved": "已记录批准",
"acceptance.ready": "已记录控制器就绪",
"acceptance.attentionSource": "当前状态",
"acceptance.visionSource": "Agent 验收条件",
"acceptance.todoSource": "任务状态",
"acceptance.runSource": "最新执行证据",
"acceptance.title": "验收观察",
"acceptance.unavailable": "验收观测不可用,Goal 是否达成仍未知。",
"acceptance.partial": "当前仅有部分观测。任务完成或缺口列表为空,都不能证明 Goal 已通过验收。",
"acceptance.gaps": "仍需补充的证据",
"acceptance.reasonUnknown": "来源未提供原因",
"acceptance.unknown": "未知",
"acceptance.required": "所需证据或条件",
"acceptance.observed": "观测时间",
"acceptance.noGaps": "现有观测中没有缺口,尚未评估完整验收条件。",
"acceptance.guards": "待处理的门禁决策",
"acceptance.noGuards": "现有观测中没有待处理的门禁决策。",
"acceptance.scope": "决策范围",
"acceptance.next": "当前状态给出的下一步",
"acceptance.historical_progress": "已记录的历史进展",
"acceptance.historical": "历史生命周期记录不授予权限,也不代表通过验收。",
"acceptance.missing": "未获取的来源:",
"acceptance.truncated": "仅展示前 12 条观测。",
"common.actions": "操作",
"common.agent": "Agent",
"common.allMessages": "所有消息",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { GoalAcceptanceObservation } from "../../data/goal-acceptance-observation";
import type { AttentionDetails } from "./attention-details";
import type { WorkspaceLoadError } from "../../data/workspace-progressive-status";
export type WorkspaceGoalState =
Expand Down Expand Up @@ -66,6 +67,7 @@ export type WorkspaceGoalSubagentConfiguration = {
};

export type WorkspaceGoal = {
acceptanceObservation?: GoalAcceptanceObservation | null;
loadState?: "loading" | "error";
loadError?: WorkspaceLoadError;
activationState: "active" | "stopped";
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions apps/presentation/dashboard/src/views/dashboard-page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { GoalAcceptanceObservation } from "../data/goal-acceptance-observation";
import { attentionDetails, sourceAttention } from "../features/personal-workspace/attention-details";
import type { AttentionDetails } from "../features/personal-workspace/attention-details";
import { directoryStatusPayload, fetchWorkspaceDirectory, loadWorkspaceGoalSnapshots, type WorkspaceProgress, type WorkspaceLoadError } from "../data/workspace-progressive-status";
Expand Down Expand Up @@ -441,6 +442,7 @@ type PersonalRunEvidence = {
};

type PersonalGoalItem = {
acceptanceObservation?: GoalAcceptanceObservation | null;
loadState?: "loading" | "error";
loadError?: WorkspaceLoadError;
activationState: "active" | "stopped";
Expand Down Expand Up @@ -1233,6 +1235,7 @@ function buildPersonalHomeModel(
agentSentence: personalAgentSentence(payload, row, state, t),
agentTodos: [...goalAgentTodos, ...agentTodoFacts.recentCompleted],
doneTodoCount: agentTodoFacts.doneTodoCount,
acceptanceObservation: goal.acceptance_observation,
goalId: goal.id,
latestActivity: row.latestRun?.generated_at ?? "",
needsYou,
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ High-traffic read paths:

- [agent_scoped_evidence_ledger_v0](protocols/agent-scoped-evidence-ledger-v0.md):
thin, per-agent evidence chronology used before replan or handoff.
- [Goal acceptance observations](goal-acceptance-observations.md): bounded,
read-only Goal acceptance gaps, pending gates, and historical progress
read from `run_history.goals[].acceptance_observation`, and the Dashboard entry
that renders them. Partial observations never certify acceptance.
Loading