feat(web): back review surfaces with backend data
This commit is contained in:
parent
57d38762f0
commit
8bf0bb5452
@ -10,7 +10,6 @@ import { TaskReviewPanel } from "@/components/TaskReviewPanel";
|
|||||||
import { ArtifactPanel } from "@/components/ArtifactPanel";
|
import { ArtifactPanel } from "@/components/ArtifactPanel";
|
||||||
import { AgentTimeline } from "@/components/AgentTimeline";
|
import { AgentTimeline } from "@/components/AgentTimeline";
|
||||||
import { getNoteDetail } from "@/lib/notes-client";
|
import { getNoteDetail } from "@/lib/notes-client";
|
||||||
import { mockAgentTimeline } from "@/lib/review-data";
|
|
||||||
import type { NoteDetail } from "@/lib/types";
|
import type { NoteDetail } from "@/lib/types";
|
||||||
|
|
||||||
export default function NoteDetailPage() {
|
export default function NoteDetailPage() {
|
||||||
@ -57,7 +56,7 @@ export default function NoteDetailPage() {
|
|||||||
<LinkedNotesPanel linkedNotes={note.linkedNotes} />
|
<LinkedNotesPanel linkedNotes={note.linkedNotes} />
|
||||||
<TaskReviewPanel tasks={note.tasks} />
|
<TaskReviewPanel tasks={note.tasks} />
|
||||||
<ArtifactPanel artifacts={note.artifacts} />
|
<ArtifactPanel artifacts={note.artifacts} />
|
||||||
<AgentTimeline items={mockAgentTimeline} />
|
<AgentTimeline items={note.timeline} />
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
|
|||||||
@ -1,10 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { AppShell } from "@/components/AppShell";
|
import { AppShell } from "@/components/AppShell";
|
||||||
import { AgentTimeline } from "@/components/AgentTimeline";
|
import { AgentTimeline } from "@/components/AgentTimeline";
|
||||||
import { ProposalReviewCard } from "@/components/ProposalReviewCard";
|
import { ProposalReviewCard } from "@/components/ProposalReviewCard";
|
||||||
import { mockOperatorWorkflows } from "@/lib/mock-data";
|
import { mockOperatorWorkflows } from "@/lib/mock-data";
|
||||||
import { mockAgentTimeline, mockApprovalQueue } from "@/lib/review-data";
|
import { listAgentTimeline, listApprovalQueue } from "@/lib/review-client";
|
||||||
|
import type { AgentTimelineItem, ApprovalQueueItem } from "@/lib/types";
|
||||||
|
|
||||||
export default function ReviewsPage() {
|
export default function ReviewsPage() {
|
||||||
|
const [approvalQueue, setApprovalQueue] = useState<ApprovalQueueItem[]>([]);
|
||||||
|
const [timeline, setTimeline] = useState<AgentTimelineItem[]>([]);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const [nextQueue, nextTimeline] = await Promise.all([
|
||||||
|
listApprovalQueue(),
|
||||||
|
listAgentTimeline(),
|
||||||
|
]);
|
||||||
|
setApprovalQueue(nextQueue);
|
||||||
|
setTimeline(nextTimeline);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Unable to load review queue");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const featuredProposal = useMemo(() => approvalQueue[0] ?? null, [approvalQueue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
title="Agent review"
|
title="Agent review"
|
||||||
@ -38,8 +63,9 @@ export default function ReviewsPage() {
|
|||||||
<span className="badge">owner:any</span>
|
<span className="badge">owner:any</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{error ? <div style={{ color: "var(--ml-text-secondary)" }}>{error}</div> : null}
|
||||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||||
{mockApprovalQueue.map((item) => (
|
{approvalQueue.map((item) => (
|
||||||
<div key={item.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", alignItems: "center", flexWrap: "wrap" }}>
|
<div key={item.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", alignItems: "center", flexWrap: "wrap" }}>
|
||||||
<div style={{ display: "grid", gap: 4 }}>
|
<div style={{ display: "grid", gap: 4 }}>
|
||||||
<strong>{item.title}</strong>
|
<strong>{item.title}</strong>
|
||||||
@ -55,13 +81,15 @@ export default function ReviewsPage() {
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<ProposalReviewCard
|
{featuredProposal ? (
|
||||||
title="Summary rewrite proposal"
|
<ProposalReviewCard
|
||||||
before={"The note lists required launch items but mixes current scope with later operational work."}
|
title={featuredProposal.title}
|
||||||
after={"The note now separates must-ship launch items from explicitly deferred operational follow-ups, improving review clarity for both humans and agents."}
|
before={featuredProposal.before ?? "No prior summary captured."}
|
||||||
/>
|
after={featuredProposal.after ?? "No proposed change summary captured yet."}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<AgentTimeline items={mockAgentTimeline} />
|
<AgentTimeline items={timeline} />
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,4 @@
|
|||||||
export interface AgentTimelineItem {
|
import type { AgentTimelineItem } from "@/lib/types";
|
||||||
id: string;
|
|
||||||
actor: string;
|
|
||||||
action: string;
|
|
||||||
timestamp: string;
|
|
||||||
status: "draft" | "proposed" | "approved" | "rejected" | "applied";
|
|
||||||
summary: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AgentTimeline({ items }: { items: AgentTimelineItem[] }) {
|
export function AgentTimeline({ items }: { items: AgentTimelineItem[] }) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
|
AgentTimelineItem,
|
||||||
NoteDetail,
|
NoteDetail,
|
||||||
NoteSummary,
|
NoteSummary,
|
||||||
OperatorWorkflowSummary,
|
OperatorWorkflowSummary,
|
||||||
@ -136,6 +137,33 @@ export const mockOperatorWorkflows: OperatorWorkflowSummary[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mockTimeline: AgentTimelineItem[] = [
|
||||||
|
{
|
||||||
|
id: "event-1",
|
||||||
|
actor: "Research Agent",
|
||||||
|
action: "Proposed metadata enrichment",
|
||||||
|
timestamp: "2026-03-10 08:11",
|
||||||
|
status: "proposed",
|
||||||
|
summary: "Suggested new tags, source metadata, and a research confidence note.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "event-2",
|
||||||
|
actor: "Workflow Agent",
|
||||||
|
action: "Extracted task candidates",
|
||||||
|
timestamp: "2026-03-10 08:08",
|
||||||
|
status: "draft",
|
||||||
|
summary: "Produced three task candidates linked to the current note body.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "event-3",
|
||||||
|
actor: "Operator",
|
||||||
|
action: "Approved editorial summary",
|
||||||
|
timestamp: "2026-03-10 08:03",
|
||||||
|
status: "approved",
|
||||||
|
summary: "Accepted a summary rewrite and preserved original source wording in history.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const mockNoteDetails: Record<string, NoteDetail> = {
|
export const mockNoteDetails: Record<string, NoteDetail> = {
|
||||||
"note-prd-cutline": {
|
"note-prd-cutline": {
|
||||||
...mockNotes[0],
|
...mockNotes[0],
|
||||||
@ -160,6 +188,7 @@ export const mockNoteDetails: Record<string, NoteDetail> = {
|
|||||||
{ id: "artifact-prd", name: "PRD v2.0", type: "document", status: "ready" },
|
{ id: "artifact-prd", name: "PRD v2.0", type: "document", status: "ready" },
|
||||||
{ id: "artifact-roadmap", name: "Roadmap execution sheet", type: "document", status: "ready" },
|
{ id: "artifact-roadmap", name: "Roadmap execution sheet", type: "document", status: "ready" },
|
||||||
],
|
],
|
||||||
|
timeline: mockTimeline,
|
||||||
},
|
},
|
||||||
"note-web-shell": {
|
"note-web-shell": {
|
||||||
...mockNotes[1],
|
...mockNotes[1],
|
||||||
@ -181,6 +210,7 @@ export const mockNoteDetails: Record<string, NoteDetail> = {
|
|||||||
artifacts: [
|
artifacts: [
|
||||||
{ id: "artifact-shell", name: "UI scaffold", type: "ui", status: "processing" },
|
{ id: "artifact-shell", name: "UI scaffold", type: "ui", status: "processing" },
|
||||||
],
|
],
|
||||||
|
timeline: mockTimeline,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { createApiClient } from "@bytelyst/api-client";
|
import { createApiClient } from "@bytelyst/api-client";
|
||||||
import { NOTES_API_URL, PRODUCT_ID } from "@/lib/product-config";
|
import { NOTES_API_URL, PRODUCT_ID } from "@/lib/product-config";
|
||||||
import type { NoteDetail, NoteSummary, WorkspaceSummary } from "@/lib/types";
|
import type { AgentTimelineItem, ArtifactSummary, NoteDetail, NoteSummary, NoteTask, WorkspaceSummary } from "@/lib/types";
|
||||||
|
|
||||||
type NoteDoc = {
|
type NoteDoc = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -32,6 +32,48 @@ type WorkspaceListResponse = {
|
|||||||
items: WorkspaceDoc[];
|
items: WorkspaceDoc[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NoteTaskDoc = {
|
||||||
|
id: string;
|
||||||
|
noteId: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
status: "open" | "in_progress" | "completed" | "canceled";
|
||||||
|
source: "manual" | "extracted";
|
||||||
|
};
|
||||||
|
|
||||||
|
type NoteArtifactDoc = {
|
||||||
|
id: string;
|
||||||
|
noteId: string;
|
||||||
|
artifactType: "file" | "summary" | "extraction" | "citation" | "export";
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NoteAgentActionDoc = {
|
||||||
|
id: string;
|
||||||
|
noteId: string;
|
||||||
|
actorId: string;
|
||||||
|
actorType: "agent" | "human";
|
||||||
|
actionType: "create" | "update" | "summarize" | "extract_tasks" | "attach_citation";
|
||||||
|
state: "draft" | "proposed" | "approved" | "rejected" | "applied";
|
||||||
|
reason?: string;
|
||||||
|
beforeSummary?: string;
|
||||||
|
afterSummary?: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NoteTaskListResponse = {
|
||||||
|
items: NoteTaskDoc[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type NoteArtifactListResponse = {
|
||||||
|
items: NoteArtifactDoc[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type NoteAgentActionListResponse = {
|
||||||
|
items: NoteAgentActionDoc[];
|
||||||
|
};
|
||||||
|
|
||||||
function getAccessToken(): string | null {
|
function getAccessToken(): string | null {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return null;
|
return null;
|
||||||
@ -83,6 +125,50 @@ function toWorkspaceSummary(workspace: WorkspaceDoc, notes: NoteDoc[]): Workspac
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toNoteTask(task: NoteTaskDoc): NoteTask {
|
||||||
|
return {
|
||||||
|
id: task.id,
|
||||||
|
title: task.title,
|
||||||
|
status:
|
||||||
|
task.status === "open"
|
||||||
|
? "todo"
|
||||||
|
: task.status === "completed"
|
||||||
|
? "done"
|
||||||
|
: "in_progress",
|
||||||
|
source: task.source === "extracted" ? "agent" : "manual",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArtifactSummary(artifact: NoteArtifactDoc): ArtifactSummary {
|
||||||
|
return {
|
||||||
|
id: artifact.id,
|
||||||
|
name: artifact.title,
|
||||||
|
type: artifact.artifactType,
|
||||||
|
status: artifact.description ? "ready" : "processing",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toTimelineItem(action: NoteAgentActionDoc): AgentTimelineItem {
|
||||||
|
return {
|
||||||
|
id: action.id,
|
||||||
|
actor: action.actorId,
|
||||||
|
action: `${action.actorType} ${action.actionType.replaceAll("_", " ")}`,
|
||||||
|
timestamp: action.updatedAt,
|
||||||
|
status: action.state,
|
||||||
|
summary: action.afterSummary ?? action.reason ?? action.actionType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toReviewState(
|
||||||
|
status: AgentTimelineItem["status"] | undefined
|
||||||
|
): NoteDetail["metadata"]["reviewState"] {
|
||||||
|
if (status === "proposed" || status === "approved" || status === "rejected") {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
export async function listWorkspaceSummaries(): Promise<WorkspaceSummary[]> {
|
export async function listWorkspaceSummaries(): Promise<WorkspaceSummary[]> {
|
||||||
const api = createNotesApiClient();
|
const api = createNotesApiClient();
|
||||||
const [workspaceResponse, noteResponse] = await Promise.all([
|
const [workspaceResponse, noteResponse] = await Promise.all([
|
||||||
@ -119,6 +205,23 @@ export async function getNoteDetail(noteId: string): Promise<NoteDetail | null>
|
|||||||
|
|
||||||
const workspaceMap = buildWorkspaceMap(workspaceResponse.items);
|
const workspaceMap = buildWorkspaceMap(workspaceResponse.items);
|
||||||
const workspace = workspaceMap.get(note.workspaceId);
|
const workspace = workspaceMap.get(note.workspaceId);
|
||||||
|
const [taskResponse, artifactResponse, actionResponse] = await Promise.all([
|
||||||
|
api.fetch<NoteTaskListResponse>(
|
||||||
|
`/note-tasks?workspaceId=${encodeURIComponent(note.workspaceId)}¬eId=${encodeURIComponent(note.id)}`
|
||||||
|
),
|
||||||
|
api.fetch<NoteArtifactListResponse>(
|
||||||
|
`/note-artifacts?workspaceId=${encodeURIComponent(note.workspaceId)}¬eId=${encodeURIComponent(note.id)}`
|
||||||
|
),
|
||||||
|
api.fetch<NoteAgentActionListResponse>(
|
||||||
|
`/note-agent-actions?workspaceId=${encodeURIComponent(note.workspaceId)}¬eId=${encodeURIComponent(note.id)}`
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tasks = taskResponse.items.map(toNoteTask);
|
||||||
|
const artifacts = artifactResponse.items.map(toArtifactSummary);
|
||||||
|
const timeline = actionResponse.items
|
||||||
|
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
||||||
|
.map(toTimelineItem);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toNoteSummary(note),
|
...toNoteSummary(note),
|
||||||
@ -126,12 +229,13 @@ export async function getNoteDetail(noteId: string): Promise<NoteDetail | null>
|
|||||||
metadata: {
|
metadata: {
|
||||||
owner: workspace?.members.find((member) => member.role === "owner")?.userId ?? note.updatedBy,
|
owner: workspace?.members.find((member) => member.role === "owner")?.userId ?? note.updatedBy,
|
||||||
source: note.sourceType ?? "manual",
|
source: note.sourceType ?? "manual",
|
||||||
reviewState: "none",
|
reviewState: toReviewState(timeline[0]?.status),
|
||||||
taskCount: 0,
|
taskCount: tasks.length,
|
||||||
artifactCount: 0,
|
artifactCount: artifacts.length,
|
||||||
},
|
},
|
||||||
linkedNotes: [],
|
linkedNotes: [],
|
||||||
tasks: [],
|
tasks,
|
||||||
artifacts: [],
|
artifacts,
|
||||||
|
timeline,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
112
web/src/lib/review-client.ts
Normal file
112
web/src/lib/review-client.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { createApiClient } from "@bytelyst/api-client";
|
||||||
|
import { NOTES_API_URL, PRODUCT_ID } from "@/lib/product-config";
|
||||||
|
import type { AgentTimelineItem, ApprovalQueueItem } from "@/lib/types";
|
||||||
|
import { listWorkspaceSummaries } from "@/lib/notes-client";
|
||||||
|
|
||||||
|
type NoteAgentActionDoc = {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
noteId: string;
|
||||||
|
actorId: string;
|
||||||
|
actorType: "agent" | "human";
|
||||||
|
toolName: string;
|
||||||
|
actionType: "create" | "update" | "summarize" | "extract_tasks" | "attach_citation";
|
||||||
|
state: "draft" | "proposed" | "approved" | "rejected" | "applied";
|
||||||
|
reason?: string;
|
||||||
|
beforeSummary?: string;
|
||||||
|
afterSummary?: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NoteAgentActionListResponse = {
|
||||||
|
items: NoteAgentActionDoc[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function getAccessToken(): string | null {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return localStorage.getItem(`${PRODUCT_ID}_access_token`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNotesApiClient() {
|
||||||
|
return createApiClient({
|
||||||
|
baseUrl: NOTES_API_URL,
|
||||||
|
getToken: getAccessToken,
|
||||||
|
defaultHeaders: {
|
||||||
|
"x-product-id": PRODUCT_ID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSeverity(actionType: NoteAgentActionDoc["actionType"]): ApprovalQueueItem["severity"] {
|
||||||
|
if (actionType === "update" || actionType === "extract_tasks") {
|
||||||
|
return "medium";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionType === "attach_citation") {
|
||||||
|
return "low";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "high";
|
||||||
|
}
|
||||||
|
|
||||||
|
function toTimelineItem(action: NoteAgentActionDoc): AgentTimelineItem {
|
||||||
|
return {
|
||||||
|
id: action.id,
|
||||||
|
actor: action.actorId,
|
||||||
|
action: `${action.actorType} ${action.actionType.replaceAll("_", " ")}`,
|
||||||
|
timestamp: action.updatedAt,
|
||||||
|
status: action.state,
|
||||||
|
summary: action.afterSummary ?? action.reason ?? action.toolName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toApprovalQueueItem(action: NoteAgentActionDoc): ApprovalQueueItem {
|
||||||
|
return {
|
||||||
|
id: action.id,
|
||||||
|
title: action.afterSummary ?? action.reason ?? `${action.actionType} proposal`,
|
||||||
|
owner: action.actorId,
|
||||||
|
severity: toSeverity(action.actionType),
|
||||||
|
status: action.state,
|
||||||
|
noteId: action.noteId,
|
||||||
|
workspaceId: action.workspaceId,
|
||||||
|
before: action.beforeSummary,
|
||||||
|
after: action.afterSummary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listAgentActionsForWorkspace(workspaceId: string): Promise<NoteAgentActionDoc[]> {
|
||||||
|
const api = createNotesApiClient();
|
||||||
|
const response = await api.fetch<NoteAgentActionListResponse>(
|
||||||
|
`/note-agent-actions?workspaceId=${encodeURIComponent(workspaceId)}`
|
||||||
|
);
|
||||||
|
return response.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listApprovalQueue(): Promise<ApprovalQueueItem[]> {
|
||||||
|
const workspaces = await listWorkspaceSummaries();
|
||||||
|
const actionGroups = await Promise.all(
|
||||||
|
workspaces.map((workspace) => listAgentActionsForWorkspace(workspace.id))
|
||||||
|
);
|
||||||
|
|
||||||
|
return actionGroups
|
||||||
|
.flat()
|
||||||
|
.filter((action) => action.state === "draft" || action.state === "proposed")
|
||||||
|
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
||||||
|
.map(toApprovalQueueItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listAgentTimeline(noteId?: string): Promise<AgentTimelineItem[]> {
|
||||||
|
const workspaces = await listWorkspaceSummaries();
|
||||||
|
const actionGroups = await Promise.all(
|
||||||
|
workspaces.map((workspace) => listAgentActionsForWorkspace(workspace.id))
|
||||||
|
);
|
||||||
|
|
||||||
|
return actionGroups
|
||||||
|
.flat()
|
||||||
|
.filter((action) => (noteId ? action.noteId === noteId : true))
|
||||||
|
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
||||||
|
.map(toTimelineItem);
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { AgentTimelineItem } from "@/components/AgentTimeline";
|
import type { AgentTimelineItem } from "@/lib/types";
|
||||||
|
|
||||||
export const mockAgentTimeline: AgentTimelineItem[] = [
|
export const mockAgentTimeline: AgentTimelineItem[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -70,6 +70,28 @@ export interface NoteDetail extends NoteSummary {
|
|||||||
linkedNotes: LinkedNote[];
|
linkedNotes: LinkedNote[];
|
||||||
tasks: NoteTask[];
|
tasks: NoteTask[];
|
||||||
artifacts: ArtifactSummary[];
|
artifacts: ArtifactSummary[];
|
||||||
|
timeline: AgentTimelineItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentTimelineItem {
|
||||||
|
id: string;
|
||||||
|
actor: string;
|
||||||
|
action: string;
|
||||||
|
timestamp: string;
|
||||||
|
status: "draft" | "proposed" | "approved" | "rejected" | "applied";
|
||||||
|
summary: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApprovalQueueItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
owner: string;
|
||||||
|
severity: "low" | "medium" | "high";
|
||||||
|
status: "draft" | "proposed" | "approved" | "rejected" | "applied";
|
||||||
|
noteId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
before?: string;
|
||||||
|
after?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductUser {
|
export interface ProductUser {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user