@hachej/boring-ui-kit 0.4.22 → 0.4.24
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.
- package/README.md +4 -0
- package/dist/chat-view.d.ts +278 -0
- package/dist/chat-view.js +393 -0
- package/package.json +6 -2
- package/r/agent-target.json +4 -4
- package/r/app-shell.json +9 -9
- package/r/board-viewer.json +5 -5
- package/r/button.json +1 -1
- package/r/diagram-viewer.json +4 -4
- package/r/dock.json +1 -1
- package/r/documents.json +15 -14
- package/r/feedback-preview.json +1 -1
- package/r/feedback.json +6 -6
- package/r/file-tree.json +5 -5
- package/r/highlights.json +3 -3
- package/r/html-viewer.json +3 -3
- package/r/looks-viewer.json +1 -1
- package/r/maquette-viewer.json +5 -5
- package/r/markdown-editor.json +3 -3
- package/r/mockup-viewer.json +1 -1
- package/r/pi-ambient.json +10 -10
- package/r/pi-app-kinds.json +27 -0
- package/r/pi-app.json +16 -25
- package/r/pi-artifacts.json +1 -1
- package/r/pi-chat-approval.json +1 -1
- package/r/pi-chat-attachments.json +1 -1
- package/r/pi-chat-markdown.json +1 -1
- package/r/pi-chat-mentions.json +1 -1
- package/r/pi-chat-pickers.json +1 -1
- package/r/pi-chat-plugins.json +11 -11
- package/r/pi-chat-question.json +1 -1
- package/r/pi-chat-slash.json +1 -1
- package/r/pi-chat-subagent.json +1 -1
- package/r/pi-chat-voice.json +2 -2
- package/r/pi-chat.json +11 -11
- package/r/pi-workspace.json +4 -4
- package/r/placed-agent.json +4 -4
- package/r/provider-setup.json +2 -2
- package/r/record-viewer.json +1 -1
- package/r/registry.json +186 -169
- package/r/requests-board.json +1 -1
- package/r/shared-chat.json +7 -7
- package/r/task-list-viewer.json +2 -2
- package/r/version-history.json +3 -3
- package/r/viewer-code.json +2 -2
- package/r/viewer-frame.json +5 -5
- package/r/viewer-html.json +4 -4
- package/r/viewer-image.json +2 -2
- package/r/viewer-markdown.json +5 -5
- package/r/viewer-pdf.json +3 -3
- package/r/viewers.json +6 -6
- package/r/windows.json +1 -1
package/README.md
CHANGED
|
@@ -142,6 +142,10 @@ A submit the host refused before admission (status 402 `submission-refused`, for
|
|
|
142
142
|
|
|
143
143
|
`test/packages/ui-native-chat.test.mjs` uses a real native Harness and a fictional local streaming provider.
|
|
144
144
|
|
|
145
|
+
### Chat view rules
|
|
146
|
+
|
|
147
|
+
`@hachej/boring-ui-kit/chat-view` holds the chat's presentation rules once, so the registry items (`pi-chat`, `pi-ambient`) keep only their markup. It imports Pi types only: no React, no files or agent code. `derive(view, options)` turns a native `ConversationView` into display rows (cards, activity steps, questions, approvals, artifacts) and the run state; its card content type is generic, and the items fix it to React nodes. `waits(part)`, `waitingPart(rows)` and `waitingForAnswer(rows)` are the one rule for "waiting for the person": a committed question with no result, or an approval with no result and no decision (a decided approval is not waiting). `preparing(...)` is when the natural presentation's single status shows. `pendingNotice` and `finishNotice` give the words of the run toasts and system notifications from one table, `defaultNoticeText`, which a surface can replace to localize them. Evidence: `test/packages/ui-chat-view.test.mjs`.
|
|
148
|
+
|
|
145
149
|
### Native conversation history
|
|
146
150
|
|
|
147
151
|
`loadEarlier()` reads one native page through `Conversation.entries`. `snapshot.history` distinguishes disabled, idle, loading, ready and error states. Each request contains at most 40 native entries. The controller accumulates pages in display order for the person's current browsing session and retains an opaque native continuation cursor. It clears that cache on history invalidation or `clearHistory()`; loading never restores records to model context. The first request captures the greatest observed active entry ID as an inclusive upper bound; later writes do not move that bound. Parent-owned fork entries remain valid native history.
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional chat view rules: what a native Pi conversation view means for a chat surface. Pure functions over Pi types; no React,
|
|
3
|
+
* files, agent or controller runtime. Registry items (pi-chat, pi-ambient, ...) draw these rows and words, they do not re-derive them.
|
|
4
|
+
*
|
|
5
|
+
* `Content` is whatever the surface renders (React nodes in the registry); this module never reads it.
|
|
6
|
+
*/
|
|
7
|
+
import type { ConversationView, EntryRecord, InboxItem, JsonObject } from '@earendil-works/pi-durable';
|
|
8
|
+
import type { JsonValue } from '@earendil-works/chord';
|
|
9
|
+
import type { AssistantMessage, ImageContent, ToolCall, ToolResultMessage, UserMessage } from '@earendil-works/pi-ai';
|
|
10
|
+
import type { ChatSnapshot } from './native-chat.js';
|
|
11
|
+
/** The tool that asks the person a question (`ask_user` of `@hachej/boring-agent/ask-user`). Its calls are question parts. */
|
|
12
|
+
export declare const ASK_USER_TOOL = "ask_user";
|
|
13
|
+
/** Key of `{ summary, decision? }` in a gated call's details: `APPROVAL_DETAILS` of `@hachej/boring-agent/approval` (a contract test keeps them equal). */
|
|
14
|
+
export declare const APPROVAL_DETAILS = "boring.approval";
|
|
15
|
+
/** The decision `requireApproval` records for a gated call. */
|
|
16
|
+
export type ApprovalDecision = 'approved' | 'denied' | 'cancelled';
|
|
17
|
+
/** The host adds file content for `@path` mentions as parts that start like this; the message already shows the mention itself. */
|
|
18
|
+
export declare const isFileBlock: (text: string) => boolean;
|
|
19
|
+
/** A marked app context stays in the native message; presentation collapses only a valid leading target block to a chip. */
|
|
20
|
+
export declare function targetContent(text: string): {
|
|
21
|
+
readonly target: JsonObject;
|
|
22
|
+
readonly speech: string;
|
|
23
|
+
readonly label: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
/** A submission waiting in the native inbox (steer or follow-up); `queuedMessages` reads them from the view, the queue renders them. */
|
|
26
|
+
export type QueuedMessage = Extract<InboxItem, {
|
|
27
|
+
readonly mode: 'steer' | 'followUp';
|
|
28
|
+
}>;
|
|
29
|
+
export type ToolStatus = 'running' | 'completed' | 'failed' | 'unfinished';
|
|
30
|
+
export interface ToolEntry<Content = unknown> {
|
|
31
|
+
readonly key: string;
|
|
32
|
+
readonly call: ToolCall;
|
|
33
|
+
readonly result: ToolResultMessage | undefined;
|
|
34
|
+
readonly status: ToolStatus;
|
|
35
|
+
readonly custom?: Content;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* An artifact a tool call made (or a running `present` call is making), as the chat places it. `descriptor` is the artifacts item's own
|
|
39
|
+
* value: the chat hands it back to the host's `open` and `isOpen` and never reads it.
|
|
40
|
+
*/
|
|
41
|
+
export interface ChatArtifact {
|
|
42
|
+
readonly state: 'presenting' | 'ready';
|
|
43
|
+
/** One card per id in a reply: the last version, at the place of its last step. */
|
|
44
|
+
readonly id: string | undefined;
|
|
45
|
+
readonly title: string | undefined;
|
|
46
|
+
/** The workspace path, for the conversation's file list. */
|
|
47
|
+
readonly path: string | undefined;
|
|
48
|
+
/** The tool's own result carries it, so its card replaces the step; a host-detected one is shown in addition to the step. */
|
|
49
|
+
readonly own: boolean;
|
|
50
|
+
readonly descriptor: unknown;
|
|
51
|
+
}
|
|
52
|
+
/** Finds an artifact descriptor a finished call's result does not carry itself (the host's `detect`). */
|
|
53
|
+
export type ArtifactDetect = (call: ToolCall, result: ToolResultMessage) => unknown;
|
|
54
|
+
/** The artifact a call made, or that a running call (`running`) is making; undefined for any other call. */
|
|
55
|
+
export type ArtifactRecognise = (call: ToolCall, result: ToolResultMessage | undefined, running: boolean, detect: ArtifactDetect | undefined) => ChatArtifact | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Draws a tool call that ran a sub-agent, from the details its running slot published (`details`) or that it finished with.
|
|
58
|
+
* Undefined: the call is not a sub-agent call and stays an ordinary tool step.
|
|
59
|
+
*/
|
|
60
|
+
export type SubagentRenderer<Content = unknown> = (call: ToolCall, details: unknown, result: ToolResultMessage | undefined) => Content | undefined;
|
|
61
|
+
/** `required` pins the card above the transcript. `inline` keeps an entry/tool card in place in expert mode too;
|
|
62
|
+
* inline entries remain part of the answer. Other custom tool cards show only in developer mode or on failure. */
|
|
63
|
+
export interface ChatCard<Content = unknown> {
|
|
64
|
+
readonly content: Content;
|
|
65
|
+
readonly required?: boolean;
|
|
66
|
+
readonly inline?: boolean; /** Host stage wording for the single natural status; null clears it. */
|
|
67
|
+
readonly status?: Content;
|
|
68
|
+
}
|
|
69
|
+
export type Mode = 'expert' | 'developer';
|
|
70
|
+
export type StreamPart = {
|
|
71
|
+
readonly type: 'text';
|
|
72
|
+
readonly text: string;
|
|
73
|
+
} | {
|
|
74
|
+
readonly type: 'thinking';
|
|
75
|
+
readonly thinking: string;
|
|
76
|
+
} | ToolCall;
|
|
77
|
+
export type QuestionPart = {
|
|
78
|
+
readonly kind: 'question';
|
|
79
|
+
readonly key: string;
|
|
80
|
+
readonly call: ToolCall;
|
|
81
|
+
readonly result: ToolResultMessage | undefined;
|
|
82
|
+
readonly live: boolean;
|
|
83
|
+
readonly questionId: string | undefined;
|
|
84
|
+
};
|
|
85
|
+
export type ApprovalPart<Content = unknown> = {
|
|
86
|
+
readonly kind: 'approval';
|
|
87
|
+
readonly key: string;
|
|
88
|
+
readonly call: ToolCall;
|
|
89
|
+
readonly result: ToolResultMessage | undefined;
|
|
90
|
+
readonly live: boolean; /** The bound question ID the answer must use (as for `question`). */
|
|
91
|
+
readonly questionId: string | undefined; /** The one-line summary `requireApproval` published for this call. */
|
|
92
|
+
readonly summary: string; /** The decision it recorded, once made. */
|
|
93
|
+
readonly decision?: ApprovalDecision; /** The call's own step, shown in the activity block once it has a result. */
|
|
94
|
+
readonly entry?: ToolEntry<Content>;
|
|
95
|
+
};
|
|
96
|
+
export type Part<Content = unknown> = {
|
|
97
|
+
readonly kind: 'card';
|
|
98
|
+
readonly key: string;
|
|
99
|
+
readonly content: Content;
|
|
100
|
+
} | {
|
|
101
|
+
readonly kind: 'text';
|
|
102
|
+
readonly key: string;
|
|
103
|
+
readonly text: string;
|
|
104
|
+
} | {
|
|
105
|
+
readonly kind: 'thinking';
|
|
106
|
+
readonly key: string;
|
|
107
|
+
readonly text: string;
|
|
108
|
+
readonly streaming: boolean;
|
|
109
|
+
} | QuestionPart | ApprovalPart<Content> | {
|
|
110
|
+
readonly kind: 'subagent';
|
|
111
|
+
readonly key: string;
|
|
112
|
+
readonly call: ToolCall;
|
|
113
|
+
readonly result: ToolResultMessage | undefined; /** The sub-agent card, from the details the call published (live) or finished with. */
|
|
114
|
+
readonly content: Content; /** The call's own step, shown in the activity block once it has a result. */
|
|
115
|
+
readonly entry?: ToolEntry<Content>;
|
|
116
|
+
} | {
|
|
117
|
+
readonly kind: 'artifact';
|
|
118
|
+
readonly key: string;
|
|
119
|
+
readonly call: ToolCall;
|
|
120
|
+
readonly artifact: ChatArtifact; /** Set when the host's `detect` found the descriptor: the call stays a step of the activity block too. */
|
|
121
|
+
readonly entry?: ToolEntry<Content>;
|
|
122
|
+
} | {
|
|
123
|
+
readonly kind: 'tool';
|
|
124
|
+
readonly key: string;
|
|
125
|
+
readonly entry: ToolEntry<Content>;
|
|
126
|
+
};
|
|
127
|
+
export type Row<Content = unknown> = ({
|
|
128
|
+
readonly key: string;
|
|
129
|
+
readonly type: 'user';
|
|
130
|
+
readonly message: UserMessage; /** `messageStyle="messaging"`: first of a run (see `markLeads`). */
|
|
131
|
+
readonly lead?: boolean;
|
|
132
|
+
} | {
|
|
133
|
+
readonly key: string;
|
|
134
|
+
readonly type: 'assistant';
|
|
135
|
+
readonly lead?: boolean;
|
|
136
|
+
readonly parts: readonly Part<Content>[]; /** Native result images remain visible when their tool chrome is omitted. */
|
|
137
|
+
readonly images?: readonly ImageContent[];
|
|
138
|
+
readonly streaming: boolean; /** The turn is still running (the last row while a run is active). */
|
|
139
|
+
readonly active?: boolean;
|
|
140
|
+
readonly stopReason?: AssistantMessage['stopReason'];
|
|
141
|
+
readonly errorMessage?: string;
|
|
142
|
+
readonly text: string; /** When the reply was written (the last round of a merged turn), in ms. Absent while it streams. */
|
|
143
|
+
readonly timestamp?: number; /** The model that wrote the last round of the reply. */
|
|
144
|
+
readonly model?: {
|
|
145
|
+
readonly provider: string;
|
|
146
|
+
readonly modelId: string;
|
|
147
|
+
}; /** Sum of the merged rounds' native `durationMs`, when recorded. */
|
|
148
|
+
readonly durationMs?: number; /** The native entry of its last committed round: where a fork of the conversation after this reply starts. */
|
|
149
|
+
readonly entryId?: string;
|
|
150
|
+
} | {
|
|
151
|
+
readonly key: string;
|
|
152
|
+
readonly type: 'orphan-result';
|
|
153
|
+
readonly message: ToolResultMessage;
|
|
154
|
+
} | {
|
|
155
|
+
readonly key: string;
|
|
156
|
+
readonly type: 'system';
|
|
157
|
+
readonly text: string;
|
|
158
|
+
} | {
|
|
159
|
+
readonly key: string;
|
|
160
|
+
readonly type: 'event';
|
|
161
|
+
readonly label: string;
|
|
162
|
+
} | {
|
|
163
|
+
readonly key: string;
|
|
164
|
+
readonly type: 'card';
|
|
165
|
+
readonly card: ChatCard<Content>;
|
|
166
|
+
}) & {
|
|
167
|
+
readonly historical?: boolean;
|
|
168
|
+
};
|
|
169
|
+
export declare function object(value: JsonValue | undefined): JsonObject | undefined;
|
|
170
|
+
export declare function streamParts(value: JsonValue | undefined): StreamPart[];
|
|
171
|
+
export declare function queuedMessages(view: ConversationView | undefined): QueuedMessage[];
|
|
172
|
+
export interface Derived<Content = unknown> {
|
|
173
|
+
readonly rows: readonly Row<Content>[];
|
|
174
|
+
/** Required entry and tool cards: pinned above the transcript. */
|
|
175
|
+
readonly pinned: readonly {
|
|
176
|
+
readonly key: string;
|
|
177
|
+
readonly content: Content;
|
|
178
|
+
}[];
|
|
179
|
+
readonly run: JsonObject | undefined;
|
|
180
|
+
readonly retry: JsonObject | undefined;
|
|
181
|
+
readonly compacting: boolean;
|
|
182
|
+
readonly stageStatus?: Content;
|
|
183
|
+
}
|
|
184
|
+
export interface DeriveOptions<Content = unknown> {
|
|
185
|
+
readonly mode: Mode;
|
|
186
|
+
/** Complete assistant lines to omit. With live text, the unfinished last line waits for its newline or settlement. */
|
|
187
|
+
readonly hideLine?: ((line: string) => boolean) | undefined;
|
|
188
|
+
readonly renderEntry: ((entry: EntryRecord) => ChatCard<Content> | undefined) | undefined;
|
|
189
|
+
readonly renderTool: ((call: ToolCall, result: ToolResultMessage | undefined) => ChatCard<Content> | undefined) | undefined;
|
|
190
|
+
readonly groupTool: ((call: ToolCall, result: ToolResultMessage) => boolean) | undefined;
|
|
191
|
+
/** Turn artifact tool results into cards (`recognise`; `detect` finds descriptors the tool result does not carry itself). */
|
|
192
|
+
readonly artifacts?: {
|
|
193
|
+
readonly recognise: ArtifactRecognise;
|
|
194
|
+
readonly detect?: ArtifactDetect | undefined;
|
|
195
|
+
} | undefined;
|
|
196
|
+
/** Draws sub-agent calls. Without it they are ordinary tool steps. */
|
|
197
|
+
readonly subagent?: SubagentRenderer<Content> | undefined;
|
|
198
|
+
/** `natural` shows only what a non-developer reads: their messages, the assistant's prose and the question, approval and artifact cards. Tool steps, reasoning, system rows and raw error text are left out of the rows (the entries stay as they are). */
|
|
199
|
+
readonly presentation?: 'natural' | undefined;
|
|
200
|
+
}
|
|
201
|
+
/** The display rows of a native conversation view, plus its run, retry, compaction and pinned cards. */
|
|
202
|
+
export declare function derive<Content = unknown>(view: ConversationView | undefined, options: DeriveOptions<Content>): Derived<Content>;
|
|
203
|
+
/** What a reply reports about the model round that wrote it, read from Pi's own message (each field only when it is there). */
|
|
204
|
+
export declare function replyMeta(message: AssistantMessage): {
|
|
205
|
+
readonly timestamp?: number;
|
|
206
|
+
readonly model?: {
|
|
207
|
+
readonly provider: string;
|
|
208
|
+
readonly modelId: string;
|
|
209
|
+
};
|
|
210
|
+
readonly durationMs?: number;
|
|
211
|
+
};
|
|
212
|
+
/** One step of an activity block: a default tool call or a stretch of reasoning. */
|
|
213
|
+
export type Step<Content = unknown> = {
|
|
214
|
+
readonly kind: 'tool';
|
|
215
|
+
readonly key: string;
|
|
216
|
+
readonly entry: ToolEntry<Content>;
|
|
217
|
+
} | {
|
|
218
|
+
readonly kind: 'thinking';
|
|
219
|
+
readonly key: string;
|
|
220
|
+
readonly text: string;
|
|
221
|
+
readonly streaming: boolean;
|
|
222
|
+
};
|
|
223
|
+
/** An assistant turn is text parts and activity blocks: one block per uninterrupted run of steps. */
|
|
224
|
+
export type Segment<Content = unknown> = {
|
|
225
|
+
readonly kind: 'part';
|
|
226
|
+
readonly part: Part<Content>;
|
|
227
|
+
} | {
|
|
228
|
+
readonly kind: 'activity';
|
|
229
|
+
readonly key: string;
|
|
230
|
+
readonly steps: readonly Step<Content>[];
|
|
231
|
+
};
|
|
232
|
+
export declare function segments<Content>(parts: readonly Part<Content>[], groupTool: DeriveOptions['groupTool']): Segment<Content>[];
|
|
233
|
+
/**
|
|
234
|
+
* `messageStyle="messaging"`: flags the first user or assistant row of each run (a change of side, or more than 15 minutes since the
|
|
235
|
+
* last message) so only it gets a tail. A row keeps its identity while its flag does not change, so memoized rows stay still.
|
|
236
|
+
*/
|
|
237
|
+
export declare function markLeads<Content>(rows: readonly Row<Content>[]): readonly Row<Content>[];
|
|
238
|
+
/**
|
|
239
|
+
* The newest question or approval that waits for the person: committed (not still being generated), with no result yet and, for an
|
|
240
|
+
* approval, no decision recorded. An approval whose decision is made is not waiting: the run carries on (or stops) by itself.
|
|
241
|
+
*/
|
|
242
|
+
export declare function waitingPart<Content>(rows: readonly Row<Content>[]): QuestionPart | ApprovalPart<Content> | undefined;
|
|
243
|
+
/** Whether this one part waits for the person (the rule of `waitingPart`). */
|
|
244
|
+
export declare function waits<Content>(part: Part<Content>): part is QuestionPart | ApprovalPart<Content>;
|
|
245
|
+
/** Whether the conversation waits for the person to answer a question or decide an approval (`waitingPart`). */
|
|
246
|
+
export declare const waitingForAnswer: (rows: readonly Row<unknown>[]) => boolean;
|
|
247
|
+
/** `presentation="natural"`: whether the one "preparing" status shows. Any work (a run, a retry, a compaction) counts, except a confirmed stop with no run left; a question waiting for the person does not. */
|
|
248
|
+
export declare function preparing(input: {
|
|
249
|
+
readonly natural: boolean;
|
|
250
|
+
readonly waitingForAnswer: boolean;
|
|
251
|
+
readonly run: unknown;
|
|
252
|
+
readonly retry: unknown;
|
|
253
|
+
readonly compacting: boolean;
|
|
254
|
+
readonly stop: ChatSnapshot['stop'];
|
|
255
|
+
}): boolean;
|
|
256
|
+
/** The words of run notices (toasts and system notifications). One table, so a surface localizes them in one place. */
|
|
257
|
+
export declare const defaultNoticeText: {
|
|
258
|
+
/** What a waiting approval asks. */
|
|
259
|
+
approve: (summary: string) => string;
|
|
260
|
+
/** A failed run that reported no error message. */
|
|
261
|
+
runFailed: string;
|
|
262
|
+
/** A finished run whose reply has no text. */
|
|
263
|
+
finished: string;
|
|
264
|
+
};
|
|
265
|
+
export type NoticeText = typeof defaultNoticeText;
|
|
266
|
+
/** One line from a Markdown reply: its first non-empty line without Markdown marks, cut with an ellipsis. */
|
|
267
|
+
export declare function summaryOf(text: string, limit?: number): string;
|
|
268
|
+
/**
|
|
269
|
+
* What a notice says about the question or approval waiting for the person (`waitingPart`), if one waits. `question` reads the
|
|
270
|
+
* question text from an `ask_user` call (the question item owns that grammar).
|
|
271
|
+
*/
|
|
272
|
+
export declare function pendingNotice(rows: readonly Row<unknown>[], question: (call: ToolCall) => string, text?: NoticeText): string | undefined;
|
|
273
|
+
/** What a notice says about a run that just ended, from the conversation's rows; `undefined` when the person stopped it. */
|
|
274
|
+
export declare function finishNotice(rows: readonly Row<unknown>[], text?: NoticeText): {
|
|
275
|
+
readonly kind: 'done' | 'error';
|
|
276
|
+
readonly summary: string;
|
|
277
|
+
} | undefined;
|
|
278
|
+
//# sourceMappingURL=chat-view.d.ts.map
|