@sentry/junior-dashboard 0.268.0 → 0.270.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +7 -105
  2. package/dist/app.js +12 -9
  3. package/dist/client.js +81 -81
  4. package/dist/index.js +12 -9
  5. package/dist/tailwind.css +1 -1
  6. package/package.json +5 -4
  7. package/src/api/schema.ts +2 -1
  8. package/src/app.ts +11 -7
  9. package/src/client/App.tsx +15 -9
  10. package/src/client/api.ts +4 -19
  11. package/src/client/components/AGENTS.md +65 -0
  12. package/src/client/components/ActorAvatar.tsx +1 -1
  13. package/src/client/components/AutomationCard.tsx +2 -1
  14. package/src/client/components/ParticipantAvatarStack.tsx +1 -0
  15. package/src/client/components/ProfileMenu.tsx +88 -29
  16. package/src/client/components/UserInputForm.tsx +110 -37
  17. package/src/client/components/charts/AGENTS.md +15 -0
  18. package/src/client/conversations/AGENTS.md +38 -0
  19. package/src/client/conversations/ConversationBrief.tsx +55 -36
  20. package/src/client/conversations/ConversationComposer.tsx +27 -8
  21. package/src/client/conversations/ConversationEventLog.tsx +1 -0
  22. package/src/client/conversations/ConversationHomeList.tsx +43 -26
  23. package/src/client/conversations/ConversationPage.tsx +90 -56
  24. package/src/client/conversations/ConversationSectionHeading.tsx +44 -0
  25. package/src/client/conversations/ConversationSidebar.tsx +55 -25
  26. package/src/client/conversations/ConversationTranscript.tsx +3 -2
  27. package/src/client/conversations/ConversationWatches.tsx +4 -1
  28. package/src/client/conversations/ConversationWorkspace.tsx +43 -25
  29. package/src/client/conversations/EventDetails.tsx +6 -9
  30. package/src/client/conversations/PendingMailboxStack.tsx +5 -2
  31. package/src/client/conversations/TranscriptContextEventView.tsx +3 -4
  32. package/src/client/conversations/TranscriptEventView.tsx +9 -3
  33. package/src/client/conversations/TranscriptMessageView.tsx +6 -5
  34. package/src/client/conversations/TranscriptReasoningView.tsx +4 -5
  35. package/src/client/conversations/TranscriptRows.tsx +6 -1
  36. package/src/client/conversations/TranscriptStructuredEventView.tsx +5 -4
  37. package/src/client/conversations/TranscriptToolView.tsx +2 -1
  38. package/src/client/conversations/TranscriptView.tsx +6 -6
  39. package/src/client/conversations/conversationOutbox.ts +13 -3
  40. package/src/client/conversations/conversationSections.ts +36 -8
  41. package/src/client/conversations/eventTranscript.ts +1 -0
  42. package/src/client/conversations/transcriptBottomPinning.ts +171 -81
  43. package/src/client/conversations/transcriptScrollAnchor.ts +101 -0
  44. package/src/client/conversations/useCollapsedConversationSections.ts +48 -0
  45. package/src/client/display-text/DisplayText.tsx +68 -0
  46. package/src/client/display-text/emoji.ts +54 -0
  47. package/src/client/{conversations/transcriptMarkdownLinks.ts → display-text/links.ts} +16 -3
  48. package/src/client/{conversations/TranscriptMarkdown.tsx → display-text/markdown.tsx} +83 -19
  49. package/src/client/display-text/mentions.tsx +148 -0
  50. package/src/client/display-text/plainText.ts +29 -0
  51. package/src/client/format.ts +7 -23
  52. package/src/client/pages/automations/AGENTS.md +34 -0
  53. package/src/client/pages/automations/AutomationActions.tsx +18 -20
  54. package/src/client/pages/automations/AutomationEditor.tsx +3 -4
  55. package/src/client/pages/automations/AutomationFilters.tsx +6 -6
  56. package/src/client/pages/automations/AutomationOverview.tsx +5 -5
  57. package/src/client/pages/automations/AutomationStatus.tsx +2 -2
  58. package/src/client/pages/automations/AutomationVersionsTab.tsx +12 -8
  59. package/src/client/pages/code/RepositoryTabs.tsx +6 -1
  60. package/src/client/pages/memory/MemoryDetails.tsx +5 -13
  61. package/src/client/pages/system/AGENTS.md +12 -0
  62. package/src/client/review-desk/ConversationReviewBar.tsx +14 -9
  63. package/src/client/review-desk/ReviewDeskPage.tsx +99 -79
  64. package/src/client/review-desk/ReviewItemActions.tsx +6 -2
  65. package/src/client/review-desk/ReviewItemCard.tsx +58 -17
  66. package/src/client/review-desk/ReviewItemDetail.tsx +17 -9
  67. package/src/client/review-desk/UnsentAnswers.tsx +148 -0
  68. package/src/client/review-desk/queries.ts +25 -16
  69. package/src/client/review-desk/reviewItemFormat.ts +29 -1
  70. package/src/mock-reporting/routes.ts +1 -1
  71. package/src/tailwind.css +1 -1
  72. package/src/client/components/README.md +0 -219
  73. package/src/client/components/charts/README.md +0 -38
  74. package/src/client/conversations/README.md +0 -159
  75. package/src/client/conversations/TranscriptText.tsx +0 -47
  76. package/src/client/conversations/UserInputRequestCard.tsx +0 -39
  77. package/src/client/conversations/conversationActivityPreview.ts +0 -16
  78. package/src/client/conversations/transcriptMentions.tsx +0 -134
  79. package/src/client/conversations/userInputRequest.ts +0 -56
  80. package/src/client/pages/automations/README.md +0 -97
  81. package/src/client/pages/system/README.md +0 -23
@@ -1,159 +0,0 @@
1
- # Transcript components
2
-
3
- Keep transcript layout in these feature components. Use the dashboard UI kit for
4
- shared controls and overlays.
5
-
6
- - `TranscriptRows` and `TranscriptRow` own the top-level gap, cell padding, and
7
- activity indent. Loading rows and the typing indicator use the same layout.
8
- The 4px gap and two 6px padding edges leave 16px between row content. Horizontal
9
- padding stays inside each mobile row's paint bounds so focus outlines are not
10
- clipped. Negative horizontal margins preserve the text edge.
11
- - `TranscriptMessageShell` owns the 32px avatar column and 12px message gutter.
12
- `TranscriptMessageHeading` aligns authors, timestamps, and the context action.
13
- The action stays visible on touch devices and while open. Mouse users see it
14
- on message hover; keyboard users see it on focus.
15
- - `TranscriptSummaryTrigger` owns disclosure padding, hover, and keyboard
16
- focus for a `ui/collapsible` trigger. Use `flush` when the text already sits
17
- on the transcript edge. It extends the padded control around that text. Do
18
- not repeat negative margins at call sites. The caller owns the `Collapsible`
19
- and its content. Transcript search holds event and context rows open, and
20
- shows activity group rows without the group.
21
- - Activity rows (tools, reasoning, and grouped rail events) share one
22
- layout: a 1rem icon column, an 8px gap, then the title and metadata.
23
- Tool and reasoning rows use `ui/trace`. Transcript search forces them open.
24
- Expanded content sits in `TraceCollapsibleContent` under the title.
25
- `TranscriptActivityNesting` tells a rail event that it is inside an activity
26
- group. Outside a group, the rail icon sits in the avatar column so the text
27
- lines up with message text.
28
- - `TranscriptSubagentView` renders a child agent as a card with its name,
29
- status, run time, and title. It matches the child agent rows in the details
30
- drawer and opens the child transcript.
31
- - `TranscriptAutomationTriggerView` renders the input of an Automation run as
32
- one card. The report names that message in `sourceTask.triggerMessageId`.
33
- The card shows the Automation name and a link to it. A second line shows the
34
- trigger, the run time, and whose credentials the run used. The credential
35
- part comes from the run's dispatch record, not the Automation's current
36
- setting. It is left out when that record is gone. The run input is not from
37
- a person, so it stays collapsed behind "Show prompt".
38
- - `TranscriptAttachments` owns wrapping for both message attachments and file
39
- deliveries. `ImageAttachment` owns image previews.
40
- - Display text keeps Slack tokens such as `<@U123>` and `<#C123|name>`. The
41
- API resolves the people in that text. It returns them as `mentions` next to
42
- the object that owns the text: an event, an activity preview, a Brief, or an
43
- Automation. Render this text with `TranscriptText` or `MentionText`. Both
44
- require `mentions`, so a caller cannot forget them. Do not resolve names in
45
- the dashboard, and do not rewrite tokens into links on the server.
46
- - `TranscriptTurnContextView` owns context content. A transcript with a
47
- `turnContextPanel` shows the context in the page panel. See "Page panel".
48
- Other transcripts, such as a subagent drawer, open a modal `Drawer`. Each
49
- `Transcript` sets the panel for its own rows, so a nested transcript does not
50
- use the panel of the page behind it. The panel keys context by message id,
51
- so every control for one message shows the same open state.
52
-
53
- Review these components together on the mock Conversations. Run
54
- `JUNIOR_DASHBOARD_MOCK_CONVERSATIONS=true pnpm dev` for messages, events,
55
- activity, context, typing, and attachments. Keep visual checks out of automated
56
- assertions; browser tests cover opening, closing, and keyboard interaction.
57
-
58
- ## Conversation details
59
-
60
- `ConversationPage` owns whether details are open. At 1440px and above, details
61
- open by default. An explicit choice lasts for the current Conversation. The page
62
- is keyed by the Conversation, so switching Conversations resets that choice.
63
- `ConversationHeader` toggles details and restores focus through explicit control
64
- refs after dismissal.
65
-
66
- ## Page panel
67
-
68
- The Conversation page has one panel on the right. It shows Conversation
69
- details, event details, or turn context. Only one shows at a time. Each uses
70
- `DetailsPanel`: a 400px aside at 1280px and above, and a `BottomSheet` below.
71
-
72
- - Event details and turn context show in place of Conversation details.
73
- - When you close them, Conversation details show again if they are open. The
74
- details stay open by choice or by the 1440px default.
75
- - A link that opens an event sets details to closed. When you close that
76
- event, no panel shows.
77
- - The details control closes event details and turn context, then shows or
78
- hides Conversation details.
79
- - Turn context belongs to the Conversation view. A view change closes it.
80
- Select the same context control again to close it.
81
- - Closing a panel returns focus to the row or control that opened it.
82
-
83
- `ConversationDetails` owns one set of tabs and content. It supplies its title and
84
- actions to `DetailsPanel`, which uses a 400px aside at 1280px and above. The chat
85
- and aside scroll separately.
86
- Smaller windows use `BottomSheet` with a visible title and close control. The sheet and
87
- side drawers use the shadcn Base UI `Sheet` and share the app's document
88
- scroll lock. A resize changes the frame and keeps the selected tab. Tabs stay
89
- fixed above the scrolling content in both layouts. The sheet title and close control sit above the tabs. Use the close
90
- control, tap the backdrop, or press Escape to dismiss it. Base UI owns the
91
- transition lifecycle.
92
- A pending exit cannot close a new panel or a different Conversation.
93
-
94
- ## Event log
95
-
96
- `ChatLayout` keeps the Conversation header outside the transcript scroll region.
97
- `ConversationEventLog` fills the pane without a card border or page inset. Its
98
- search toolbar and column labels stay together at the top while rows scroll.
99
- `Transcript` shares the search value with its owner, or keeps it locally when
100
- used alone. Event log search stays visible; the transcript search toggle is for
101
- the Conversation view.
102
-
103
- Rows keep time, event type, and summary in columns when the pane is at least
104
- 512px wide. Smaller panes put the summary below time and type. Model and usage
105
- metadata share one optional line. Missing model metadata is omitted from rows.
106
- Selected rows have a background and an accent edge. Up and Down select the
107
- previous or next visible row while a row has focus. Selection stops at the
108
- ends of the filtered list. The selected row stays clear of the fixed toolbar.
109
- `eventLogRows` and `filterEventLogRows` own the row text and search match.
110
- The page uses them too, so its step controls follow the same rows.
111
-
112
- The timezone toggle uses `useEventTime` to share a UTC/local preference through
113
- `@uidotdev/usehooks` and browser local storage. It applies to event row clocks
114
- and detail timestamps. Local mode follows the browser timezone. The button uses
115
- a generic zone name because a log can span standard and daylight-saving time.
116
- Detail timestamps use a readable date to the second and the zone name for each
117
- event date, in both modes. The header keeps them on one line beside the
118
- actions. Their tooltip adds milliseconds, and the `time` element keeps the
119
- original ISO value. The mode, not the timezone identifier, persists across
120
- visits and tabs. This hook belongs to the client-rendered event viewer. Raw
121
- JSON retains the original ISO values.
122
-
123
- ## Event details
124
-
125
- The URL owns the selected event in the main Conversation. `ConversationPage`
126
- places `ConversationEventDetails` beside the log in the page panel. Selecting
127
- another row changes the event and resets its scroll position and disclosures.
128
- Closing returns focus to that row. Switching Conversations removes the panel.
129
-
130
- The previous and next controls select the adjacent visible row. They form one
131
- `ui/button-group` named "Event navigation". They follow
132
- the event search, like Up and Down. They are necessary below 1280px, where the
133
- sheet covers the log. At the ends of the list, the control is disabled but
134
- keeps focus. The sheet title is the event type. The header shows the event
135
- type, then the sequence number and timestamp. The body groups fields in the
136
- same `DetailsCard` that Conversation details use. Groups inside a card, such
137
- as tool input and output, use a divider, not another card.
138
-
139
- A linked event can be older than the loaded pages. While the event loads, the
140
- panel stays open. Only its header shows placeholders, so it does not compete
141
- with the log. While the transcript loads, `ConversationEventLogLoading` shows
142
- the log toolbar, columns, and row skeletons in the event log view.
143
-
144
- Standalone transcripts, such as a subagent drawer, keep selection in
145
- `ConversationEventLog`. Their event details use a nested drawer on desktop and
146
- `BottomSheet` on smaller screens. The nested drawer stacks in front of the
147
- subagent drawer. The subagent drawer shows its event log without a body inset,
148
- so rows meet the drawer edges and the toolbar sticks to the top. This keeps the main log free of modal behavior
149
- while a nested transcript retains its parent drawer's focus rules. These event
150
- details have the same previous and next controls.
151
-
152
- `SubagentTranscriptDrawer` uses the wide `Drawer` at all widths. Below 768px
153
- the drawer fills the screen, so the long transcript gets the most room. Its
154
- header holds search, view, and copy actions. `BottomSheet` has no place for
155
- them.
156
-
157
- `ForkConversationButton` opens a `ui/dialog` form, not a drawer. The dialog
158
- stays above event details in a docked panel or a sheet. Cancel returns focus
159
- to the fork button. A pending fork blocks dismissal until the request ends.
@@ -1,47 +0,0 @@
1
- import { memo } from "react";
2
- import type { ActorIdentity } from "@sentry/junior/api/schema";
3
-
4
- import { HighlightedCode } from "../code";
5
- import { parseMarkdownBlocks, transcriptRoleKind } from "../format";
6
- import { TranscriptMarkdown } from "./TranscriptMarkdown";
7
- import { MentionPeopleProvider } from "./transcriptMentions";
8
-
9
- /**
10
- * Render transcript prose as Markdown with explicit fenced code blocks.
11
- * `mentions` are the people that the API resolved for this text.
12
- */
13
- export const TranscriptText = memo(function TranscriptText(props: {
14
- mentions: readonly ActorIdentity[];
15
- role?: string;
16
- text: string;
17
- }) {
18
- const roleKind = transcriptRoleKind(props.role ?? "");
19
- const blocks = parseMarkdownBlocks(props.text);
20
-
21
- return (
22
- <MentionPeopleProvider mentions={props.mentions}>
23
- <div className="grid min-w-0 gap-2">
24
- {blocks.map((block, index) => {
25
- if (block.language === "markdown" && !block.fenced) {
26
- return (
27
- <TranscriptMarkdown
28
- compact={roleKind === "assistant" || roleKind === "user"}
29
- key={index}
30
- text={block.code}
31
- />
32
- );
33
- }
34
-
35
- return (
36
- <div
37
- className="min-w-0 overflow-hidden rounded-md border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5"
38
- key={index}
39
- >
40
- <HighlightedCode code={block.code} language={block.language} />
41
- </div>
42
- );
43
- })}
44
- </div>
45
- </MentionPeopleProvider>
46
- );
47
- });
@@ -1,39 +0,0 @@
1
- import { formatUserInputAnswer } from "@sentry/junior/api/schema";
2
-
3
- import { UserInputForm } from "../components/UserInputForm";
4
- import { TranscriptText } from "./TranscriptText";
5
- import type { OpenUserInputRequest } from "./userInputRequest";
6
-
7
- /**
8
- * Show Junior's open request above the composer. The answer goes as the
9
- * reader's next message. Typing a reply also answers it.
10
- */
11
- export function UserInputRequestCard(props: {
12
- onAnswer(answer: string): Promise<void>;
13
- request: OpenUserInputRequest;
14
- }) {
15
- return (
16
- <section
17
- aria-label="Junior asks"
18
- className="mb-2 rounded-lg border border-cyan-300/20 bg-cyan-300/[0.07] px-3 py-2.5 font-sans text-sm text-dashboard-text"
19
- >
20
- <div className="max-h-48 overflow-y-auto">
21
- <TranscriptText
22
- mentions={[]}
23
- role="assistant"
24
- text={props.request.question}
25
- />
26
- </div>
27
- {/* Above the composer, a long form scrolls so the transcript stays visible. */}
28
- <div className="max-h-96 overflow-y-auto">
29
- <UserInputForm
30
- onSubmit={(answer) =>
31
- props.onAnswer(formatUserInputAnswer(props.request, answer))
32
- }
33
- request={props.request}
34
- requestId={props.request.toolCallId}
35
- />
36
- </div>
37
- </section>
38
- );
39
- }
@@ -1,16 +0,0 @@
1
- // Slack writes links as `<https://example.com>` or `<https://example.com|label>`.
2
- const SLACK_LINK_RE = /<((?:https?|mailto):[^\s|<>]+)(?:\|([^<>]*))?>/g;
3
-
4
- /** Reduce message Markdown to compact plain text for conversation previews. */
5
- export function formatConversationActivityPreview(text: string): string {
6
- return text
7
- .replace(SLACK_LINK_RE, (_, url: string, label?: string) =>
8
- label?.trim() ? label.trim() : url,
9
- )
10
- .replace(/\[([^\]]+)\]\([^\s)]+(?:\s+"[^"]*")?\)/g, "$1")
11
- .replace(/(^|\s)([*_~`]{1,3})(?=\S)/g, "$1")
12
- .replace(/([*_~`]{1,3})(?=\s|$|[.,!?;:])/g, "")
13
- .replace(/^\s*(?:#{1,6}|[-+*]|\d+\.)\s+/gm, "")
14
- .replace(/\s+/g, " ")
15
- .trim();
16
- }
@@ -1,134 +0,0 @@
1
- import { createContext, useContext, type ReactNode } from "react";
2
- import { Link } from "react-router";
3
- import type { ActorIdentity } from "@sentry/junior/api/schema";
4
-
5
- import { peoplePath } from "../format";
6
- import { cn } from "../styles";
7
- import { TRANSCRIPT_ANCHOR_CLASS } from "./transcriptMarkdownLinks";
8
- import { HighlightText } from "./transcriptSearch";
9
-
10
- type TranscriptMention = {
11
- end: number;
12
- id: string;
13
- label?: string;
14
- sigil: "@" | "#";
15
- start: number;
16
- };
17
-
18
- // Matches the user and channel tokens that core keeps in message text.
19
- const SLACK_MENTION_RE = /<([@#])([A-Z0-9]+)(?:\|([^<>]*))?>/g;
20
-
21
- const TranscriptMentionPeople = createContext<
22
- ReadonlyMap<string, ActorIdentity>
23
- >(new Map());
24
-
25
- /**
26
- * Share the people that the API resolved for one block of display text.
27
- * Use `TranscriptText` or `MentionText`, which require the API mentions.
28
- */
29
- export function MentionPeopleProvider(props: {
30
- children: ReactNode;
31
- mentions: readonly ActorIdentity[];
32
- }) {
33
- const people = new Map<string, ActorIdentity>();
34
- for (const person of props.mentions) {
35
- if (person.slackUserId) people.set(person.slackUserId, person);
36
- }
37
- return (
38
- <TranscriptMentionPeople.Provider value={people}>
39
- {props.children}
40
- </TranscriptMentionPeople.Provider>
41
- );
42
- }
43
-
44
- /** Render one line of display text with the people the API resolved for it. */
45
- export function MentionText(props: {
46
- /**
47
- * Link people to their page. Set false inside a button, which cannot hold
48
- * a link.
49
- */
50
- links?: boolean;
51
- mentions: readonly ActorIdentity[];
52
- text: string;
53
- }) {
54
- return (
55
- <MentionPeopleProvider mentions={props.mentions}>
56
- {renderMentionText(props.text, "mention-text", props.links)}
57
- </MentionPeopleProvider>
58
- );
59
- }
60
-
61
- /**
62
- * Render text with Slack `<@U123>` and `<#C123|name>` tokens as readable
63
- * mentions. It reads people from the nearest `MentionPeopleProvider`.
64
- */
65
- export function renderMentionText(
66
- text: string,
67
- keyBase: string,
68
- links = true,
69
- ): ReactNode[] {
70
- const nodes: ReactNode[] = [];
71
- let cursor = 0;
72
- for (const mention of findMentions(text)) {
73
- if (mention.start > cursor) {
74
- nodes.push(
75
- <HighlightText
76
- key={`${keyBase}-m-${cursor}`}
77
- text={text.slice(cursor, mention.start)}
78
- />,
79
- );
80
- }
81
- nodes.push(
82
- <MentionView
83
- key={`${keyBase}-m-${mention.start}`}
84
- links={links}
85
- mention={mention}
86
- />,
87
- );
88
- cursor = mention.end;
89
- }
90
- if (cursor < text.length) {
91
- nodes.push(
92
- <HighlightText
93
- key={`${keyBase}-m-${cursor}`}
94
- text={text.slice(cursor)}
95
- />,
96
- );
97
- }
98
- return nodes;
99
- }
100
-
101
- function findMentions(text: string): TranscriptMention[] {
102
- return [...text.matchAll(SLACK_MENTION_RE)].map((match) => ({
103
- end: match.index + match[0].length,
104
- id: match[2]!,
105
- label: match[3]?.trim() || undefined,
106
- sigil: match[1] as "@" | "#",
107
- start: match.index,
108
- }));
109
- }
110
-
111
- function MentionView(props: { links: boolean; mention: TranscriptMention }) {
112
- const people = useContext(TranscriptMentionPeople);
113
- const { id, label, sigil } = props.mention;
114
- const person = sigil === "@" ? people.get(id) : undefined;
115
- const name = person?.fullName ?? person?.slackUserName ?? label ?? id;
116
- const text = <HighlightText text={`${sigil}${name.replace(/^[@#]/, "")}`} />;
117
- if (props.links && person?.email) {
118
- return (
119
- <Link
120
- // Previews sit in cards that disable pointer events on their text.
121
- className={cn("pointer-events-auto", TRANSCRIPT_ANCHOR_CLASS)}
122
- title={id}
123
- to={peoplePath(person.email)}
124
- >
125
- {text}
126
- </Link>
127
- );
128
- }
129
- return (
130
- <span className="font-medium text-dashboard-accent-text" title={id}>
131
- {text}
132
- </span>
133
- );
134
- }
@@ -1,56 +0,0 @@
1
- import {
2
- userInputRequestSchema,
3
- type ConversationReportEvent,
4
- type UserInputRequest,
5
- } from "@sentry/junior/api/schema";
6
-
7
- /** Model-facing name of Junior's tool that asks a person for input. */
8
- const REQUEST_USER_INPUT_TOOL_NAME = "requestUserInput";
9
-
10
- /** A request from Junior that still waits for an answer. */
11
- export type OpenUserInputRequest = UserInputRequest & { toolCallId: string };
12
-
13
- /**
14
- * Find the latest `requestUserInput` call that no user message answers yet.
15
- * Any later user message is the answer, whether it is a submitted form or
16
- * typed text.
17
- *
18
- * Reporting splits one call over events: the assistant event has the input
19
- * and status `running`, and the later result event has status `completed`
20
- * without the input. Keep the input by tool call id until the result.
21
- */
22
- export function openUserInputRequest(
23
- events: readonly ConversationReportEvent[] | undefined,
24
- ): OpenUserInputRequest | undefined {
25
- const inputs = new Map<string, unknown>();
26
- const completed = new Set<string>();
27
- let open: OpenUserInputRequest | undefined;
28
- for (const event of events ?? []) {
29
- const data = event.data;
30
- if (data.type === "message" && data.role === "user") {
31
- open = undefined;
32
- continue;
33
- }
34
- if (data.type !== "tool_calls") continue;
35
- for (const call of data.calls) {
36
- if (call.name !== REQUEST_USER_INPUT_TOOL_NAME) continue;
37
- if (call.input !== undefined) inputs.set(call.toolCallId, call.input);
38
- if (call.status === "error" && open?.toolCallId === call.toolCallId) {
39
- open = undefined;
40
- }
41
- // Open a request once, when its result first arrives. A repeated
42
- // result must not reopen a request that a user message answered.
43
- if (call.status !== "completed" || completed.has(call.toolCallId)) {
44
- continue;
45
- }
46
- completed.add(call.toolCallId);
47
- const request = userInputRequestSchema.safeParse(
48
- inputs.get(call.toolCallId),
49
- );
50
- if (request.success) {
51
- open = { ...request.data, toolCallId: call.toolCallId };
52
- }
53
- }
54
- }
55
- return open;
56
- }
@@ -1,97 +0,0 @@
1
- # Automation editor
2
-
3
- Core owns validation, edit access, creator-only rules, revision checks,
4
- Schedule previews, and the Event catalog. Preview requests cannot save or run
5
- work. Completed scheduled Automations remain read-only.
6
-
7
- Creators and public Destination readers can edit. For a public reader, the
8
- editor shows outcomes as read-only and blocks turning on creator credential
9
- use. It warns that an instruction or event selector change switches to system
10
- credentials. Pause and resume stay creator-only.
11
-
12
- The editor sends only changed fields. Keep unsupported triggers and stored
13
- Destinations intact unless the user replaces them. Outcomes keep their order.
14
- New messages can target only the current Destination or the creator.
15
-
16
- Query refreshes must not replace an unsaved draft. After a stale save, the user
17
- can keep their changed fields or load the latest saved version. Unchanged fields
18
- come from that version; core checks its revision again on save.
19
-
20
- The data router blocks navigation with unsaved changes. Browser unload uses the
21
- native prompt. Save and Cancel return to the Automation page and keep its query
22
- string.
23
-
24
- Version history lists saved versions, newest first, and marks the active
25
- version. "Make active" uses the native confirm dialog, like delete. It then
26
- reads the latest revision and asks core to save that version again as a new
27
- version. Core applies the same edit rules, so the page
28
- shows its error when a public reader cannot make a version active.
29
-
30
- Browser behavior lives in `e2e/automation-editor.spec.ts`. API and persistence
31
- coverage lives in core's `tests/integration/api/automation-edits.test.ts`.
32
-
33
- ## Automation page
34
-
35
- Each Automation has one page at `/automations/:id`. Overview, Runs, and
36
- Versions are tabs. They use `SecondaryNavigation`, like the repository page.
37
- `/automations/:id/edit` is edit mode of the Overview tab. The old
38
- `/executions` route redirects to `/runs` and keeps the query string. List rows
39
- link to the page. There is no details slideout.
40
-
41
- The page keeps the list query string. "All automations" returns to the same
42
- list page and filters. The back link drops params that belong to one tab:
43
- `result`, `run`, and `version`. One `range` param drives the Overview numbers
44
- and the Runs chart.
45
-
46
- The header shows the name, the state, the trigger, the Destination, and the
47
- creator. `AutomationStateChip` is a live region, so Pause and Resume results
48
- are announced. `AutomationActions` owns Edit, Pause or Resume, pause help, and
49
- Delete. Lifecycle actions re-read the revision before they apply. Public
50
- readers can edit settings. Pause, Resume, and Delete stay creator-only, so
51
- other viewers do not see them. The About card names the creator-only actions.
52
- Completed Automations have no Edit action, and `/edit` returns to Overview.
53
-
54
- Overview shows `AutomationAttention` first when something needs action: a
55
- block, an unavailable trigger, or a failed last run. The notice has one main
56
- action, such as Change trigger or Open last run. While it shows, the header
57
- Edit button becomes a secondary button. The saved settings follow
58
- in the same `AutomationFormSection` rows as the editor, so edit mode changes
59
- them in place. The side column shows the next trigger, the last result, runs,
60
- failures, and spend for the range, and About. Spend is `costUsd` from the
61
- Automation summary. On small screens, status comes first and About moves below
62
- the settings.
63
-
64
- `AutomationRecentRuns` uses the existing execution-history query and cache. It
65
- shows up to three newest terminal runs, links to their Conversations when one
66
- was recorded, and displays recorded runtime and estimated cost. It omits run
67
- titles that repeat the Automation name. Its loading,
68
- empty, and retry states do not replace Automation settings. It does not infer
69
- queued or running work from terminal records.
70
-
71
- In edit mode, "Your changes" lists each changed field with the saved value, the
72
- new value, and its own Undo. It is beside the form on wide screens and below
73
- the form on small screens when there are changes. Undo remounts the fields, so fields with local state show the saved value again.
74
- Changed sections get an accent mark. Save and Cancel return to Overview.
75
-
76
- The Runs tab filters loaded runs by result through the `result` param. Rows
77
- show the version that ran and the recorded error. A row opens the run details
78
- panel. The run list scrolls on its own beside it.
79
-
80
- The Versions tab lists versions and compares the selected one with the version
81
- before it. The `version` param selects a version; run details link to it.
82
- Changed fields show the previous value. Instruction changes use a line diff.
83
-
84
- `AutomationDeleteDialog` names the Automation before deletion. It keeps errors
85
- and retry inside the dialog and blocks dismissal while the request is pending.
86
- Cancel returns focus to Delete. Successful deletion returns to the list.
87
- Success toasts confirm Save, Pause, Resume, and Delete. Their state belongs to
88
- the mounted dashboard's Toaster provider.
89
-
90
- Browser journeys for the page live in `e2e/automations.spec.ts`. Use mock
91
- reporting to review scheduled and event Automations in both themes at desktop
92
- and mobile widths. Keep layout checks in manual QA.
93
-
94
- Filters use a shadcn Popover at 768px and above and the shared BottomSheet below
95
- that width. Both edit the same URL filters immediately. Active filter chips
96
- remove one filter without clearing search, scope, or other filters. The mobile
97
- sheet fits its controls instead of taking the full screen height.
@@ -1,23 +0,0 @@
1
- # System pages
2
-
3
- ## Workspaces
4
-
5
- `/system/workspaces` lists the Workspace recipes. The URL selects one recipe:
6
- `/system/workspaces/:id` keeps the list and opens `WorkspaceDetails` in the
7
- shared `DetailsPanel`. The panel is 400px wide at 1280px and above. Smaller
8
- screens use the bottom sheet. Repository pages link to these details.
9
-
10
- The panel shows the list item at once. The detail read adds the current
11
- snapshot, because the list report does not include snapshots. Snapshot loading
12
- and errors stay in the Snapshot row. Usage shows switches for the last 30 days.
13
- Workspace switch counts are daily only.
14
-
15
- Edit and Delete are in the panel header. List rows have no actions. Edit opens
16
- the full-page editor at `/system/workspaces/:id/edit`. Save and Cancel return to
17
- the details. A new Workspace opens its details after create.
18
-
19
- Close and Escape return focus to the selected row. `WorkspaceDeleteDialog`
20
- names the recipe, keeps errors inside the dialog, and blocks dismissal while the
21
- request runs. A successful delete closes the details and shows a toast.
22
-
23
- Browser coverage lives in `e2e/system.spec.ts`.