@hachej/boring-ui-kit 0.4.23 → 0.4.25
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/bin/boring-ui-blocks.mjs +50 -13
- package/dist/chat-view.d.ts +278 -0
- package/dist/chat-view.js +393 -0
- package/dist/file-tree.js +5 -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 +11 -11
- 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 +3 -2
- 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 +187 -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.
|
package/bin/boring-ui-blocks.mjs
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
//
|
|
5
5
|
// npx boring-ui-blocks check [--design <dir>...] [--manifest <path>] [--cwd <app web dir>] [--json]
|
|
6
6
|
// npx boring-ui-blocks update [--version <v>] [--registry <url>] [--dry-run] [--manifest <path>] [--cwd <dir>]
|
|
7
|
-
// npx boring-ui-blocks add <shadcn add arguments...> [--manifest <path>] [--cwd <dir>]
|
|
7
|
+
// npx boring-ui-blocks add <shadcn add arguments...> [--overwrite] [--manifest <path>] [--cwd <dir>]
|
|
8
8
|
// npx boring-ui-blocks manifest [--manifest <path>] [--cwd <dir>]
|
|
9
9
|
// npx boring-ui-blocks help (also --help: the usage and which items to add; exit 0)
|
|
10
10
|
//
|
|
11
11
|
// `check` writes nothing. `add`, `update` and `manifest` write the shadcn files they install and the manifest, nothing else.
|
|
12
12
|
import { createHash } from 'node:crypto';
|
|
13
|
-
import { existsSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs';
|
|
14
|
-
import { join, relative, resolve, sep } from 'node:path';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
15
15
|
import { spawnSync } from 'node:child_process';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
|
|
@@ -158,12 +158,24 @@ export function checkBlocks(app, components = appPaths(app).components) {
|
|
|
158
158
|
/** The generated shadcn files under the `ui` alias (`.js/.jsx/.ts/.tsx/.css`) and their sha256, by path relative to it. */
|
|
159
159
|
const uiHashes = ui => new Map(files(ui).filter(path => /\.(?:[jt]sx?|css)$/.test(path)).map(path => [posix(relative(ui, path)), { path, hash: sha256(path) }]));
|
|
160
160
|
|
|
161
|
+
/** Records the given generated ui files in the manifest (`add` after a kept file: only what it created). Other entries and keys are kept. */
|
|
162
|
+
export function recordFiles(app, paths, manifest = MANIFEST) {
|
|
163
|
+
const { ui } = appPaths(app);
|
|
164
|
+
const path = resolve(app, manifest);
|
|
165
|
+
const kept = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : { generatedBy: 'boring-ui-blocks', dir: posix(relative(app, ui)) };
|
|
166
|
+
const hashes = { ...kept.files };
|
|
167
|
+
for (const file of paths) hashes[posix(relative(ui, file))] = sha256(file);
|
|
168
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
169
|
+
writeFileSync(path, `${JSON.stringify({ ...kept, files: Object.fromEntries(Object.entries(hashes).sort(([a], [b]) => a.localeCompare(b))) }, null, 2)}\n`);
|
|
170
|
+
}
|
|
171
|
+
|
|
161
172
|
/** (Re)writes the manifest (`manifest`, default SHADCN-GENERATED.json next to components.json): `files` is replaced, other keys are kept. */
|
|
162
173
|
export function writeManifest(app, manifest = MANIFEST) {
|
|
163
174
|
const { ui } = appPaths(app);
|
|
164
175
|
const path = resolve(app, manifest);
|
|
165
176
|
const hashes = Object.fromEntries([...uiHashes(ui)].map(([name, { hash }]) => [name, hash]));
|
|
166
177
|
const kept = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : { generatedBy: 'boring-ui-blocks', dir: posix(relative(app, ui)) };
|
|
178
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
167
179
|
writeFileSync(path, `${JSON.stringify({ ...kept, files: hashes }, null, 2)}\n`);
|
|
168
180
|
return Object.keys(hashes).length;
|
|
169
181
|
}
|
|
@@ -215,18 +227,20 @@ export function check(app, design = [], manifest = MANIFEST) {
|
|
|
215
227
|
return { version, blocks: blocks.map(block => block.name), violations };
|
|
216
228
|
}
|
|
217
229
|
|
|
218
|
-
const USAGE = `usage: boring-ui-blocks check [--design <dir>...] [--manifest <path>] [--cwd <dir>] [--json] | update [--version <v>] [--registry <url>] [--dry-run] [--manifest <path>] [--cwd <dir>] | add <shadcn add args...> [--manifest <path>] [--cwd <dir>] | manifest [--manifest <path>] [--cwd <dir>] | help\n\n` +
|
|
230
|
+
const USAGE = `usage: boring-ui-blocks check [--design <dir>...] [--manifest <path>] [--cwd <dir>] [--json] | update [--version <v>] [--registry <url>] [--dry-run] [--manifest <path>] [--cwd <dir>] | add <shadcn add args...> [--overwrite] [--manifest <path>] [--cwd <dir>] | manifest [--manifest <path>] [--cwd <dir>] | help\n\n` +
|
|
219
231
|
'Blocks: pi-app is the whole agent app with every chat feature and viewer. pi-chat is the core chat only: add pi-chat-plugins for every ' +
|
|
220
232
|
'feature, or one of pi-chat-markdown, pi-chat-slash, pi-chat-mentions, pi-chat-pickers, pi-chat-voice, pi-chat-attachments, ' +
|
|
221
233
|
'pi-chat-question, pi-chat-approval, pi-chat-subagent and pi-artifacts. Viewers are viewer-frame plus viewer-markdown, viewer-html, ' +
|
|
222
234
|
'viewer-image, viewer-pdf and viewer-code, loaded by kind. update reinstalls only installed items: ' +
|
|
223
|
-
'add the new ones an app uses once (registry/README.md, "From one item to small items").'
|
|
224
|
-
|
|
235
|
+
'add the new ones an app uses once (registry/README.md, "From one item to small items"). add never prompts: a file that already exists ' +
|
|
236
|
+
'with other content is kept and listed (exit 1) unless --overwrite replaces it; identical files are left alone.';
|
|
237
|
+
const FLAGS = { check: ['design', 'manifest', 'json'], update: ['version', 'registry', 'dry-run', 'manifest'], manifest: ['manifest'], add: ['manifest', 'overwrite'] };
|
|
225
238
|
|
|
226
239
|
function parse(command, rest) {
|
|
227
240
|
const flags = { design: [], pass: [] };
|
|
228
241
|
for (let i = 0; i < rest.length; i++) {
|
|
229
242
|
const name = rest[i].startsWith('--') ? rest[i].slice(2) : null;
|
|
243
|
+
if (command === 'add' && (name === 'overwrite' || rest[i] === '-o')) { flags.overwrite = true; continue; }
|
|
230
244
|
if (command === 'add' && name !== 'cwd' && name !== 'manifest') { flags.pass.push(rest[i]); continue; }
|
|
231
245
|
if (!name || !FLAGS[command]?.includes(name) && name !== 'cwd') throw new Error(`unknown argument ${rest[i]}`);
|
|
232
246
|
if (name === 'json') flags.json = true;
|
|
@@ -267,13 +281,30 @@ function restoreSpecifiers(before, app, log) {
|
|
|
267
281
|
if (restored.size) log(`kept the version specifiers of ${[...restored].sort().join(', ')} (the CLI had rewritten them)`);
|
|
268
282
|
}
|
|
269
283
|
|
|
270
|
-
|
|
271
|
-
|
|
284
|
+
/** The files the shadcn CLI may write for a block (the components, ui, lib and hooks aliases) and their bytes, before it runs. */
|
|
285
|
+
function snapshotFiles(app) {
|
|
286
|
+
const { aliases = {} } = readJson(join(app, 'components.json')) ?? {};
|
|
287
|
+
const alias = (name, fallback) => join(app, existsSync(join(app, 'src')) ? 'src' : '', (aliases[name] ?? fallback).replace(/^[@~]\//, ''));
|
|
288
|
+
const { components, ui } = appPaths(app);
|
|
289
|
+
return new Map([...new Set([components, ui, alias('lib', '@/lib'), alias('hooks', '@/hooks')])].flatMap(dir => files(dir)).map(path => [path, readFileSync(path)]));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Runs the shadcn CLI without a terminal: stdin is closed and the CLI always gets `--overwrite --yes`, so it never asks. With
|
|
294
|
+
* `overwrite` false a file that existed with other content is put back afterwards and returned in `skipped`; identical files are never touched.
|
|
295
|
+
* `created` is the generated ui files the run newly wrote.
|
|
296
|
+
*/
|
|
297
|
+
function shadcnAdd(app, args, log, overwrite) {
|
|
298
|
+
const argv = ['-y', SHADCN, 'add', ...args.filter(arg => !['--yes', '-y', '--overwrite'].includes(arg)), '--overwrite', '--yes'];
|
|
272
299
|
log(`npx ${argv.join(' ')}`);
|
|
273
|
-
const before = specifiers(app);
|
|
274
|
-
const result = spawnSync('npx', argv, { cwd: app, stdio: 'inherit', shell: process.platform === 'win32' });
|
|
300
|
+
const before = specifiers(app), existing = snapshotFiles(app);
|
|
301
|
+
const result = spawnSync('npx', argv, { cwd: app, stdio: ['ignore', 'inherit', 'inherit'], shell: process.platform === 'win32' });
|
|
275
302
|
restoreSpecifiers(before, app, log);
|
|
276
|
-
|
|
303
|
+
const skipped = overwrite ? [] : [...existing].filter(([path, bytes]) => existsSync(path) && !readFileSync(path).equals(bytes));
|
|
304
|
+
for (const [path, bytes] of skipped) writeFileSync(path, bytes);
|
|
305
|
+
const { ui } = appPaths(app);
|
|
306
|
+
const created = files(ui).filter(path => /\.(?:[jt]sx?|css)$/.test(path) && !existing.has(path));
|
|
307
|
+
return { status: result.status ?? 1, skipped: skipped.map(([path]) => path), created };
|
|
277
308
|
}
|
|
278
309
|
|
|
279
310
|
const line = v => `${v.check} ${v.rule} ${v.file}${v.line ? `:${v.line}:${v.column}` : ''} ${v.detail}`;
|
|
@@ -300,7 +331,13 @@ export function main(argv, cwd = process.cwd(), log = console.log, error = conso
|
|
|
300
331
|
if (command === 'add') {
|
|
301
332
|
appPaths(app);
|
|
302
333
|
if (!flags.pass.length) throw new Error('add needs shadcn add arguments');
|
|
303
|
-
const status = shadcnAdd(app, flags.pass, log);
|
|
334
|
+
const { status, skipped, created } = shadcnAdd(app, flags.pass, log, flags.overwrite);
|
|
335
|
+
if (skipped.length) {
|
|
336
|
+
// The files the run created are the app's new generated files: record them, so `check` does not call them unknown. The kept ones stay unrecorded.
|
|
337
|
+
if (created.length) recordFiles(app, created, flags.manifest ?? MANIFEST);
|
|
338
|
+
error(`kept ${skipped.length} existing file(s) that differ from the registry (pass --overwrite to replace them; only the files add created were recorded in the manifest):\n${skipped.map(path => ` ${posix(relative(app, path))}`).join('\n')}`);
|
|
339
|
+
return status || 1;
|
|
340
|
+
}
|
|
304
341
|
if (status === 0) log(`${flags.manifest ?? MANIFEST}: ${writeManifest(app, flags.manifest)} file(s)`);
|
|
305
342
|
return status;
|
|
306
343
|
}
|
|
@@ -309,7 +346,7 @@ export function main(argv, cwd = process.cwd(), log = console.log, error = conso
|
|
|
309
346
|
const base = (flags.registry ?? itemBase(flags.version ?? installed)).replace(/\/$/, '');
|
|
310
347
|
const items = blocks.map(block => `${base}/${block.name}.json`);
|
|
311
348
|
if (flags.dryRun) { log(`npx -y ${SHADCN} add ${items.join(' ')} --overwrite --yes`); return 0; }
|
|
312
|
-
const status = shadcnAdd(app,
|
|
349
|
+
const { status } = shadcnAdd(app, items, log, true);
|
|
313
350
|
if (status === 0) log(`${flags.manifest ?? MANIFEST}: ${writeManifest(app, flags.manifest)} file(s)`);
|
|
314
351
|
return status;
|
|
315
352
|
} catch (cause) {
|
|
@@ -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
|