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
10 changes: 2 additions & 8 deletions apps/presentation/dashboard/smoke/usage-progress-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ function excludes(source: string, snippet: string, label: string) {

const statusSource = readFileSync("src/data/status.ts", "utf8");
const dashboardSource = readFileSync("src/views/dashboard-page.tsx", "utf8");
const drawerSource = readFileSync("src/features/personal-workspace/context-drawer.tsx", "utf8");
const workspaceI18nSource = readFileSync("src/features/personal-workspace/i18n.tsx", "utf8");
const packageSource = readFileSync("package.json", "utf8");
const exampleStatus = readFileSync("../../../examples/status.example.json", "utf8");
Expand Down Expand Up @@ -168,12 +167,7 @@ for (const [snippet, label] of [
includes(workspaceI18nSource, snippet, label);
}

for (const [snippet, label] of [
["formatUsageValue(selection.item.usage?.tokens24h", "drawer unknown token value"],
["formatUsageValue(selection.item.usage?.costUsd24h", "drawer unknown cost value"],
["formatUsageValue(selection.item.usage?.durationMs24h", "drawer unknown duration value"],
] as const) {
includes(drawerSource, snippet, label);
}
// The packaged Goal workspace scene checks unknown windows and measured zero
// through the rendered reader. Source spelling is not a usage invariant.

console.log("usage-progress smoke ok");
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import type {
} from "./personal-workspace-model";
import type { LarkGoalConnection } from "../../data/chat";
import { localizedGoalState, localizedSessionStatus, useWorkspaceI18n } from "./i18n";
import { formatCostUsd, formatDurationMs, formatTokenCount, formatUsageValue } from "./personal-workspace-model";
import { formatCostUsd, formatDurationMs, formatTokenCount, formatUsageValue, hasGoalUsage } from "./personal-workspace-model";
import { TeamPlanResult } from "./team-plan-result";
import { TaskRequest } from "./task-request";
import { parseTodoResumeCondition } from "./todo-resume-condition";
Expand Down Expand Up @@ -616,7 +616,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
role="dialog"
>
<header className={`personal-drawer-header${selection.kind === "todo" ? " is-task-inspector" : ""}`}>
<div><h2 id="personal-drawer-title" ref={titleRef} tabIndex={-1}>{title}</h2><p>{contextLabel}</p></div>
<div><h2 id="personal-drawer-title" ref={titleRef} tabIndex={-1}>{title}</h2>{selection.kind !== "goal" ? <p>{contextLabel}</p> : null}</div>
<div className="personal-drawer-header-actions">
{selection.kind === "todo" && onToggleInspectorSize ? <button aria-label={inspectorExpanded ? t("drawer.inspectorHalf") : t("drawer.inspectorFull")} className="personal-icon-button personal-inspector-size" onClick={onToggleInspectorSize} title={inspectorExpanded ? t("drawer.inspectorHalfView") : t("drawer.inspectorFullView")} type="button">
{inspectorExpanded ? <Minimize2 size={17} /> : <Maximize2 size={17} />}
Expand Down Expand Up @@ -762,17 +762,23 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention

{selection.kind === "goal" ? (
<>
<section className="personal-detail-card">
<section className="personal-detail-card personal-goal-detail-summary">
<small>{localizedGoalState(selection.item.state, locale)}</small>
<h3>{selection.item.title}</h3>
<p>{selection.item.agentSentence}</p>
<dl>
<div><dt>{t("drawer.tokens")}</dt><dd>{formatUsageValue(selection.item.usage?.tokens24h, t("drawer.usageNotMeasured"), formatTokenCount)} / {formatUsageValue(selection.item.usage?.tokens7d, t("drawer.usageNotMeasured"), formatTokenCount)}</dd></div>
<div><dt>{t("drawer.cost")}</dt><dd>{formatUsageValue(selection.item.usage?.costUsd24h, t("drawer.usageNotMeasured"), formatCostUsd)} / {formatUsageValue(selection.item.usage?.costUsd7d, t("drawer.usageNotMeasured"), formatCostUsd)}</dd></div>
<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} />
<section className="personal-detail-card personal-goal-detail-usage">
<h3>{t("drawer.goalUsage")}</h3>
{hasGoalUsage(selection.item.usage) ? <table>
<thead><tr><td /><th scope="col">{t("drawer.period24h")}</th><th scope="col">{t("drawer.period7d")}</th></tr></thead>
<tbody>
<tr><th scope="row">{t("drawer.tokensShort")}</th><td>{formatUsageValue(selection.item.usage.tokens24h, t("drawer.usageNotMeasured"), formatTokenCount)}</td><td>{formatUsageValue(selection.item.usage.tokens7d, t("drawer.usageNotMeasured"), formatTokenCount)}</td></tr>
<tr><th scope="row">{t("drawer.costShort")}</th><td>{formatUsageValue(selection.item.usage.costUsd24h, t("drawer.usageNotMeasured"), formatCostUsd)}</td><td>{formatUsageValue(selection.item.usage.costUsd7d, t("drawer.usageNotMeasured"), formatCostUsd)}</td></tr>
<tr><th scope="row">{t("drawer.durationShort")}</th><td>{formatUsageValue(selection.item.usage.durationMs24h, t("drawer.usageNotMeasured"), formatDurationMs)}</td><td>{formatUsageValue(selection.item.usage.durationMs7d, t("drawer.usageNotMeasured"), formatDurationMs)}</td></tr>
</tbody>
</table> : <p>{t("drawer.usageNotMeasured")}</p>}
</section>
{(() => {
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
@@ -1,5 +1,6 @@
import type { WorkspaceGoal } from "./personal-workspace-model";
import { useWorkspaceI18n } from "./i18n";
import { ContextHelp } from "./context-help";

export function GoalAcceptanceObservationCard({ goal, overview = false }: { goal: WorkspaceGoal; overview?: boolean }) {
const { t, locale } = useWorkspaceI18n();
Expand All @@ -22,37 +23,47 @@ export function GoalAcceptanceObservationCard({ goal, overview = false }: { goal
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";
const coverageDetail = projection?.missing_sources.length ? t("acceptance.incompleteSources")
: projection?.truncated ? t("acceptance.partialRecords") : null;
const content = <section className="personal-detail-card personal-goal-acceptance" aria-label={t("acceptance.title")}>
{!overview ? <div className="personal-detail-card-title"><h3>{t("acceptance.title")}</h3><em>{t("common.readOnly")}</em></div> : null}
<p role="status">{t(unavailable ? "acceptance.unavailable" : "acceptance.partial")}</p>
<div className="personal-acceptance-coverage"><p role="status">{t(unavailable ? "acceptance.unavailable" : "acceptance.partialShort")}{!unavailable && coverageDetail ? ` · ${coverageDetail}` : ""}</p>
{!unavailable ? <ContextHelp label={t("acceptance.title")}>
<p>{t("acceptance.partial")}</p>
{projection?.missing_sources.length ? <p>{t("acceptance.missing")} {projection.missing_sources.map(label).join(", ")}</p> : null}
{projection?.truncated ? <p>{t("acceptance.truncated")}</p> : null}
</ContextHelp> : null}</div>
{!unavailable && projection ? <>
<div className="personal-acceptance-next"><h4>{t("acceptance.next")}</h4><p>{projection.next_action ?? t("acceptance.unknown")}</p></div>
<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>
{gap.resolution_hint ? <p>{gap.resolution_hint}</p> : null}
{gap.component_checks ? <div>{Object.entries(gap.component_checks).map(([key, passed]) =>
<p key={key}>{checkLabels[key as keyof typeof checkLabels]}: {locale === "zh-CN" ? (passed ? "通过" : "未通过") : (passed ? "Passed" : "Failed")}</p>
)}</div> : null}
<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>
<dl><div><dt>{t("acceptance.required")}</dt><dd>{gap.evidence_required ?? t("acceptance.unknown")}</dd></div></dl>
{gap.component_checks && Object.values(gap.component_checks).some(passed => !passed) ? <p className="personal-acceptance-check-failure">{t("acceptance.failedChecks", { count: Object.values(gap.component_checks).filter(passed => !passed).length })}</p> : null}
<details><summary>{t("acceptance.details")}</summary>
<dl><div><dt>{t("common.owner")}</dt><dd>{gap.owner ?? t("acceptance.unknown")}</dd></div>
<div><dt>{t("acceptance.observed")}</dt><dd>{gap.observed_at ?? t("acceptance.unknown")}</dd></div></dl>
{gap.component_checks ? <div>{Object.entries(gap.component_checks).map(([key, passed]) =>
<p key={key}>{checkLabels[key as keyof typeof checkLabels]}: {locale === "zh-CN" ? (passed ? "通过" : "未通过") : (passed ? "Passed" : "Failed")}</p>
)}</div> : null}
</details>
</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>
<dl><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>
<details><summary>{t("acceptance.details")}</summary>
<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}</dl>
</details>
</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>;
if (!overview) return content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ const en = {
"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.partialShort": "Unverified",
"acceptance.incompleteSources": "Sources incomplete",
"acceptance.partialRecords": "Partial records",
"acceptance.details": "Source and checks",
"acceptance.failedChecks": "{count} checks failed",
"acceptance.gaps": "Evidence still required",
"acceptance.reasonUnknown": "Reason not provided by the source",
"acceptance.unknown": "Unknown",
Expand Down Expand Up @@ -303,6 +308,7 @@ const en = {
"drawer.tokens": "Tokens 24h / 7d",
"drawer.tokensShort": "tokens",
"drawer.usageNotMeasured": "Not measured",
"drawer.goalUsage": "Usage",
"drawer.currentGoal": "Current Goal",
"attentionDetail.title": "Request details",
"attentionDetail.requestedBy": "From Agent {agent}",
Expand Down Expand Up @@ -1799,6 +1805,11 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"acceptance.title": "验收观察",
"acceptance.unavailable": "验收观测不可用,Goal 是否达成仍未知。",
"acceptance.partial": "当前仅有部分观测。任务完成或缺口列表为空,都不能证明 Goal 已通过验收。",
"acceptance.partialShort": "待核验",
"acceptance.incompleteSources": "来源不全",
"acceptance.partialRecords": "部分记录",
"acceptance.details": "来源与检查",
"acceptance.failedChecks": "{count} 项检查未通过",
"acceptance.gaps": "仍需补充的证据",
"acceptance.reasonUnknown": "来源未提供原因",
"acceptance.unknown": "未知",
Expand Down Expand Up @@ -1946,6 +1957,7 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"drawer.tokens": "Token 24 小时 / 7 天",
"drawer.tokensShort": "Token",
"drawer.usageNotMeasured": "未采集",
"drawer.goalUsage": "用量",
"drawer.currentGoal": "当前 Goal",
"attentionDetail.title": "事项说明",
"attentionDetail.requestedBy": "提出请求:Agent {agent}",
Expand Down

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

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.
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.
Loading
Loading