@jini-ai/chat 0.6.7 → 0.6.10
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/CHANGELOG.md +125 -1
- package/dist/core/assistant-client/transport.js +3 -1
- package/dist/react/components/MessageRow.d.ts +1 -1
- package/dist/react/components/MessageRow.js +29 -17
- package/dist/react/components/ToolCallGroup.d.ts +4 -3
- package/dist/react/components/ToolCallGroup.js +7 -5
- package/dist/react/features/chat-pane/types.d.ts +2 -1
- package/dist/react/tool-call-display.d.ts +12 -6
- package/dist/react/tool-call-display.js +42 -15
- package/package.json +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,127 @@
|
|
|
1
|
+
## 0.6.10
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- f9d9894: Hide recovered tool failures when tool calls are hidden. Keep unrecovered failures in one compact, localized disclosure per reply, preserving their existing details.
|
|
6
|
+
- 8ff5653: Make the chat-pane transcript readable from outside the component — a must-have for an AI (or an
|
|
7
|
+
e2e harness) verifying that commands it gave the pane (create a form, add a user, change a
|
|
8
|
+
permission) actually happened.
|
|
9
|
+
|
|
10
|
+
Before this, `<ChatPane>` fully encapsulated its conversation state: `useConversation()`'s
|
|
11
|
+
`messages` never left the component, `ChatPaneProps` had no callback that reported the live message
|
|
12
|
+
list, and the rendered transcript (`MessageList`/`MessageRow`/`ToolCard`) carried zero
|
|
13
|
+
`data-agent-element` tags — unlike every other interactive surface in this app's reference pages.
|
|
14
|
+
|
|
15
|
+
- `ChatPaneProps.onMessagesChange?: (messages: ChatMessage[]) => void` — fires with the full message
|
|
16
|
+
array on every change, mirroring the existing `onActivityChange` pattern. Each `ChatMessage`
|
|
17
|
+
already carries its `events` array (`tool_use`/`tool_result`), so a subscriber gets complete,
|
|
18
|
+
structured tool-call data for free — no DOM parsing needed.
|
|
19
|
+
- `data-agent-element="chat-transcript"` (role `list`) on the message-list container, and
|
|
20
|
+
`data-agent-element="chat-message-<id>"` (role `region`) on every message row, so `page.*` DOM
|
|
21
|
+
tools can enumerate the transcript the same way they read everything else in this app.
|
|
22
|
+
- `ToolCard` now wraps whatever it renders (any of its dozen built-in card variants, or a host's own
|
|
23
|
+
custom-registered renderer) in one `data-agent-element="tool-call-<use.id>"` (role `region`)
|
|
24
|
+
element — added at the single dispatch point every branch already funnels through, so no
|
|
25
|
+
individual card variant's own markup changed. The collapsed-by-default accordion detail (args/
|
|
26
|
+
result JSON) sits inside this tagged element, not gated behind expanding it: `textContent` (what
|
|
27
|
+
the `page.*` DOM driver actually reads) is layout-agnostic, so the full detail is readable whether
|
|
28
|
+
or not anything has been clicked open.
|
|
29
|
+
|
|
30
|
+
- 3fe4d14: Add compatibility regression fixtures for legacy inline image results, media-only saved events and delegated echo notes; runtime behavior is unchanged.
|
|
31
|
+
- 8ff5653: Add `createDaemonAttachmentUploader`, so composer drag-and-drop is one line instead of 160.
|
|
32
|
+
|
|
33
|
+
`ChatPane` has always gated its drop target and file picker on `uploadAttachments` being supplied — but
|
|
34
|
+
supplying it meant writing the client half yourself: a bounded-concurrency worker pool that preserves
|
|
35
|
+
input order, per-turn quota accounting, abort and timeout plumbing, and cleanup of files that already
|
|
36
|
+
landed when a later one fails. Every host would write the same ~160 lines, and the ordering and
|
|
37
|
+
cleanup details are exactly the ones that get skipped.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<ChatPane
|
|
41
|
+
transport={transport}
|
|
42
|
+
uploadAttachments={createDaemonAttachmentUploader(daemonUrl)}
|
|
43
|
+
/>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
That talks to `@jini-ai/http-kit`'s `POST`/`DELETE /api/attachments`. Pass `''` when a dev-server proxy
|
|
47
|
+
already forwards `/api` from the page's own origin. `maxAttachmentBytes`, `maxAttachmentCount`,
|
|
48
|
+
`maxBatchBytes`, `timeoutMs`, and `concurrency` are all configurable and all default to the daemon side's
|
|
49
|
+
own limits, so the client's early rejection matches what the daemon would have said anyway.
|
|
50
|
+
|
|
51
|
+
Client-side quotas are a courtesy, never the boundary — telling a user their 400 MB video is too large
|
|
52
|
+
before the browser streams it, not after. The daemon re-derives every one of them.
|
|
53
|
+
|
|
54
|
+
Two behavioral notes for anyone porting hand-rolled code onto this:
|
|
55
|
+
|
|
56
|
+
- **The request always sends `content-type: application/octet-stream`**, not `file.type`. The daemon
|
|
57
|
+
sniffs the real kind from the bytes and ignores the header, so forwarding the browser's guess buys
|
|
58
|
+
nothing — and breaks real uploads: a dropped `.json` file arrives as `application/json`, which an
|
|
59
|
+
app-wide `express.json()` on the daemon then claims, draining the request stream before the upload
|
|
60
|
+
route can read a byte.
|
|
61
|
+
- **Batch accounting is per uploader instance**, not module-global, so two panes pointed at two daemons
|
|
62
|
+
cannot consume each other's per-turn quota. Reservations are rolled back on failure, so a retry of a
|
|
63
|
+
failed turn is not refused for quota it never actually used.
|
|
64
|
+
|
|
65
|
+
- 72ea04d: Folded `@jini-ai/renderers-react` into `@jini-ai/ui` as an independently-importable, tree-shakeable
|
|
66
|
+
`./renderers` subpath, and retired the package.
|
|
67
|
+
|
|
68
|
+
Real (non-comment) imports of `@jini-ai/renderers-react` anywhere in the repo were exactly 3 files,
|
|
69
|
+
2 symbols, all inside `@jini-ai/ui`'s own `features/html-viewer` — `openSandboxedPreviewInNewTab`
|
|
70
|
+
and `useSandboxBridge`. `@jini-ai/ui` was already the package's only real consumer, so a standalone
|
|
71
|
+
package bought nothing, the same reasoning that folded `@jini-ai/ui-core` into `./core` and
|
|
72
|
+
`@jini-ai/composio` into `@jini-ai/admin`. The whole `src/**` tree (registry, renderers, srcDoc
|
|
73
|
+
host, annotation-canvas, preview-modal-shell, i18n) moves to `packages/ui/src/renderers/` as one
|
|
74
|
+
`git mv`, unchanged in shape; `html-viewer`'s two consumers switch from the package-qualified
|
|
75
|
+
import to a relative one, since they're now in the same package.
|
|
76
|
+
|
|
77
|
+
**The `@jini-ai/renderers-react → @jini-ai/chat` dependency was dropped first, before the move,
|
|
78
|
+
not after.** `registry.ts` imported `ArtifactManifest`/`inferLegacyManifest` from `@jini-ai/chat`,
|
|
79
|
+
and `types.ts` re-exported five manifest types from there. `@jini-ai/chat` already depends on
|
|
80
|
+
`@jini-ai/ui`, so folding renderers-react into `ui` unchanged would have created a real `ui ↔ chat`
|
|
81
|
+
package cycle. The five manifest types (`ArtifactManifest`, `ArtifactKind`, `ArtifactRendererId`,
|
|
82
|
+
`ArtifactExportKind`, `ArtifactStatus`) are now defined locally in `types.ts`, copied verbatim from
|
|
83
|
+
`@jini-ai/chat`'s `src/core/util/types.ts` — a small duplicated-type cost, accepted because a
|
|
84
|
+
renderer package must not depend on a chat package.
|
|
85
|
+
|
|
86
|
+
**`resolveArtifactManifest` no longer infers a legacy manifest from a file's name/extension** — a
|
|
87
|
+
real, disclosed API behavior change. It now returns `file.manifest ?? null`. Manifest inference
|
|
88
|
+
(guessing kind/renderer from `.md`/`.html`/`.svg`/`.tsx`, etc.) is a caller concern that depends on
|
|
89
|
+
a product's own naming conventions, not something a generic renderer registry should do on a
|
|
90
|
+
caller's behalf; a caller that still wants that inference should resolve a manifest for the file
|
|
91
|
+
itself (see `@jini-ai/chat`'s `inferLegacyManifest`) before handing it to this registry. Verified
|
|
92
|
+
zero external callers of `RendererRegistry`/`resolveArtifactManifest`/`createDefaultRendererRegistry`
|
|
93
|
+
anywhere in Jini or Tovu before making this change — `@jini-ai/chat`'s own `RendererRegistry` is an
|
|
94
|
+
unrelated, locally-defined class in `artifact-types.ts`, and `@jini-ai/artifacts`' own
|
|
95
|
+
`resolveArtifactManifest` is an unrelated 3-argument function; both are same-name coincidences, not
|
|
96
|
+
real dependents. The four bundled renderers (`HtmlRenderer`, `MarkdownRenderer`, `SvgRenderer`,
|
|
97
|
+
`ReactComponentRenderer`) gate `canRender` on a resolvable manifest before falling back to any
|
|
98
|
+
`file.kind`/extension heuristic of their own, so this ripples into every test that exercised
|
|
99
|
+
default-registry routing via a bare file name with no explicit `manifest` — all updated to attach
|
|
100
|
+
one, with the extension-inference-specific assertions flipped to assert the removed behavior no
|
|
101
|
+
longer fires. No test was weakened or deleted: every changed assertion now tests the new,
|
|
102
|
+
documented contract, or restores a manifest to keep testing what it always meant to test.
|
|
103
|
+
|
|
104
|
+
**`@jini-ai/renderers-react` is retired, not versioned forward.** It was published (0.1.2), and
|
|
105
|
+
`packages/renderers-react` is deleted from this workspace. Deleting the source does not unpublish
|
|
106
|
+
anything, so whoever runs the next release should follow up with:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
npm deprecate @jini-ai/renderers-react "Merged into @jini-ai/ui; import from '@jini-ai/ui/renderers' instead."
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`@jini-ai/chat`'s dead `@jini-ai/renderers-react` dependency (declared, never imported — its own
|
|
113
|
+
`react/artifact-types.ts` defines a local placeholder registry instead, per its
|
|
114
|
+
`TODO(renderers-react)` header, left untouched here as out of scope) is removed from its
|
|
115
|
+
`package.json`, hence the accompanying patch bump.
|
|
116
|
+
|
|
117
|
+
- 0c247a8: Fix visitor chat streaming announcements, resumed focus, frame batching, persistence and lazy mount/retry handling. Document the additive A2uiSurfaceCard registry prop for host validation and rendering.
|
|
118
|
+
- e16a907: Clear floating-point noise in displayed costs, reconcile resumed counter resets and revisited sessions, and expose reported output tokens to assistive technology.
|
|
119
|
+
- ea8d883: Release modal semantics when focus leaves chat, announce bounded rendered reply text, and retry opening without reloading mounted assets.
|
|
120
|
+
- Updated dependencies [22f0619]
|
|
121
|
+
- Updated dependencies [82ed2d3]
|
|
122
|
+
- Updated dependencies [5a3de86]
|
|
123
|
+
- @jini-ai/core@0.6.10
|
|
124
|
+
|
|
1
125
|
## 0.6.7 — 2026-10-22
|
|
2
126
|
|
|
3
127
|
- 2026-10-22: Memory fixes (durable run store reads its own row, ledger parses events once), shared settings segmented control, Slack token rule restored, coverage.
|
|
@@ -118,7 +242,6 @@
|
|
|
118
242
|
- Correct renderer priority and opt-in stylesheet documentation; retain the layout rationale with neutral provenance.
|
|
119
243
|
- BREAKING: replace the local finalizer Clock with core Clock.nowMs(); core/React helpers and ext-event slot-key callbacks take required objects.
|
|
120
244
|
|
|
121
|
-
|
|
122
245
|
- BREAKING: ChatStore methods use required argument objects; paging options move to the second bag. ChatHistoryMaintenance and ChatStoreFactory follow the same convention.
|
|
123
246
|
- BREAKING: SQLite/Postgres/PGlite factories use core `Clock` through `{ clock }` rather than `{ now }`; system-time defaults and SQL behavior are preserved.
|
|
124
247
|
|
|
@@ -208,5 +331,6 @@
|
|
|
208
331
|
- @jini-ai/agentic@0.1.1
|
|
209
332
|
|
|
210
333
|
### C2 storage ownership
|
|
334
|
+
|
|
211
335
|
- Added `./store/legacy` and `./store/legacy/sqlite`; preserved sync local project chat CRUD and schema.
|
|
212
336
|
- Retained the original owner-isolation suite on the concern adapter after draining the old package.
|
|
@@ -103,7 +103,9 @@ export function createAssistantTransportClient({ ports }, _options = {}) {
|
|
|
103
103
|
* `events_json` is the sole durable record of why a run died (exit code, signal, the CLI's own
|
|
104
104
|
* stderr) once the daemon's in-memory event log is gone, and the pane renders it on reload — so
|
|
105
105
|
* dropping the row trades a history bug for a forensics hole. The skip belongs here, on the read
|
|
106
|
-
* side.
|
|
106
|
+
* side. The transcript's hidden-tool mode likewise filters only recovered tool failures and keeps
|
|
107
|
+
* unrecovered failures behind a compact disclosure; it never deletes this durable evidence.
|
|
108
|
+
* See {@link historyForTranscript} and `assistant-chats.ts`'s `persistableMessages`.
|
|
107
109
|
*/
|
|
108
110
|
function isAnsweredAssistantTurn(message) {
|
|
109
111
|
return message.runStatus === undefined || message.runStatus === "succeeded";
|
|
@@ -63,7 +63,7 @@ export interface MessageRowProps {
|
|
|
63
63
|
* included (a host's "Done line" display choice, 2026-10-11). */
|
|
64
64
|
showUsageLine?: boolean;
|
|
65
65
|
/** How tool calls show (`../tool-call-display.ts`): every card (`'show'`, the default), one
|
|
66
|
-
* fold-up line per turn (`'collapsed'`), or
|
|
66
|
+
* fold-up line per turn (`'collapsed'`), or compact unrecovered failures (`'hidden'`). */
|
|
67
67
|
toolCallDisplay?: ToolCallDisplay;
|
|
68
68
|
/** Show the model's reasoning (`thinking` events) as a fold-up block where it happened. Off by
|
|
69
69
|
* default — the transcript never showed it before this option. Needs the interleaved layout; a
|
|
@@ -54,7 +54,7 @@ import { usageLayout, usageLineMeetsActions, useMessageUsage, useUsageSummary }
|
|
|
54
54
|
* chunk stopped arriving.
|
|
55
55
|
*/
|
|
56
56
|
import React, { memo, useMemo } from 'react';
|
|
57
|
-
import { isAwaitingAnswer, isTerminalRunStatus, splitOnQuestionForms, stripArtifact } from '../../core/index.js';
|
|
57
|
+
import { assistantContentFromEvents, isAwaitingAnswer, isTerminalRunStatus, splitOnQuestionForms, stripArtifact } from '../../core/index.js';
|
|
58
58
|
import { useToolTimeline } from '../hooks/useToolTimeline.js';
|
|
59
59
|
import { useExtEventGroups } from '../hooks/useExtEventGroups.js';
|
|
60
60
|
import { useCopyToClipboard } from '../hooks/useCopyToClipboard.js';
|
|
@@ -118,16 +118,25 @@ function UsageSummary({ usage, showTotal }) {
|
|
|
118
118
|
return null;
|
|
119
119
|
return (_jsxs("div", { className: "jini-message-usage", title: title, role: "note", "aria-label": ariaLabel, children: [_jsx("span", { className: "jini-message-usage-dot", "aria-hidden": true, children: "\u25CF" }), t('Done'), " \u00B7 ", text] }));
|
|
120
120
|
}
|
|
121
|
-
function messageBlocks({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
121
|
+
function messageBlocks({ message, rows, registry, content = message.content }, { toolCallDisplay = 'show', includeThinking = false } = {}) {
|
|
122
|
+
const blocks = interleaveMessageBlocks({ events: message.events, content, rows }, { slotOf: (ev) => registry.slot({ name: ev.name, data: ev.data }), includeThinking });
|
|
123
|
+
return blocks ? applyToolCallDisplay({ blocks, mode: toolCallDisplay }, { runFailed: message.runStatus === 'failed' }) : null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The flat layout's tool groups. Even when host text prevents interleaving, event ordering can still
|
|
127
|
+
* prove tool recovery and reply boundaries, so hidden mode reconstructs the blocks from the events —
|
|
128
|
+
* only for this display decision; the host's text is never replaced. Otherwise one flat list.
|
|
129
|
+
* @complexity O(n) in events + rows.
|
|
130
|
+
*/
|
|
131
|
+
function flatToolGroups({ message, rows, registry, displayBlocks }, { toolCallDisplay }) {
|
|
132
|
+
if (toolCallDisplay === 'hidden') {
|
|
133
|
+
const recovery = displayBlocks
|
|
134
|
+
?? messageBlocks({ message, rows, registry, content: assistantContentFromEvents({ events: message.events ?? [] }) }, { toolCallDisplay });
|
|
135
|
+
if (recovery)
|
|
136
|
+
return recovery.flatMap((block) => (block.kind === 'tools' ? [{ rows: block.rows, key: block.key }] : []));
|
|
137
|
+
}
|
|
138
|
+
const flat = toolRowsForDisplay({ rows, mode: toolCallDisplay }, { runFailed: message.runStatus === 'failed' });
|
|
139
|
+
return flat.length > 0 ? [{ rows: flat, key: 'tools' }] : [];
|
|
131
140
|
}
|
|
132
141
|
/** The model's reasoning, folded under a "Thinking" summary so it never crowds the answer. */
|
|
133
142
|
function ThinkingBlock({ text }) {
|
|
@@ -191,18 +200,21 @@ function AssistantMessageRow({ message, usageSummary, showUsageTotal, showUsageL
|
|
|
191
200
|
const answeredCards = useAnsweredCards({ slotOf: registry.slot, events: message.events ?? [], groups: extGroups, runStreaming, runStatus: message.runStatus });
|
|
192
201
|
const terminalNotice = terminalMessageNotice({ message }, {});
|
|
193
202
|
const visibleContent = stripArtifact({ content: message.content });
|
|
194
|
-
const
|
|
203
|
+
const displayBlocks = messageBlocks({ message, rows: timeline.rows, registry }, { toolCallDisplay, includeThinking: showThinking });
|
|
204
|
+
// Artifact stripping trims/replaces text, invalidating the event offsets for rendering. Keep the
|
|
205
|
+
// flat layout there; displayBlocks still supplies the recovery decision from the original events.
|
|
206
|
+
const blocks = visibleContent === message.content ? displayBlocks : null;
|
|
195
207
|
const pending = isPendingWithNoContent(message, visibleContent, timeline.rows.length);
|
|
196
208
|
const runInProgress = isRunInProgress(message.runStatus);
|
|
197
209
|
const awaitingAnswer = messageAwaitingAnswer({ runStreaming, runInProgress, events: message.events }, { isAnsweredCard: answeredCards.isAnsweredCard });
|
|
198
210
|
const usage = usageLayout({ usage: messageUsage, interleaved: blocks !== null, runInProgress }, { runStreaming });
|
|
199
211
|
const footerUsage = showUsageLine ? usage.footer : undefined;
|
|
200
212
|
const renderToolCard = (row) => (_jsx(ToolCard, { use: row.use, result: row.result, runStreaming: runStreaming, runSucceeded: runSucceeded, ...(projectFileNames !== undefined ? { projectFileNames } : {}), ...(onRequestOpenFile !== undefined ? { onRequestOpenFile } : {}) }, row.id));
|
|
201
|
-
//
|
|
202
|
-
const renderTools = (rows, key) => (toolCallDisplay
|
|
203
|
-
? _jsx(ToolCallGroup, { rows: rows, renderRow: renderToolCard }, key)
|
|
213
|
+
// Hidden mode reuses the same disclosure and card renderer for unrecovered failures.
|
|
214
|
+
const renderTools = (rows, key) => (toolCallDisplay !== 'show'
|
|
215
|
+
? _jsx(ToolCallGroup, { rows: rows, renderRow: renderToolCard, failureOnly: toolCallDisplay === 'hidden' }, key)
|
|
204
216
|
: _jsx("div", { className: "jini-message-tools", children: rows.map(renderToolCard) }, key));
|
|
205
|
-
const
|
|
217
|
+
const flatTools = flatToolGroups({ message, rows: timeline.rows, registry, displayBlocks }, { toolCallDisplay });
|
|
206
218
|
const renderExtGroup = (group) => {
|
|
207
219
|
const renderer = registry.get({ name: group.name });
|
|
208
220
|
if (!renderer && group.name === MID_RUN_USER_MESSAGE_EXT_EVENT_NAME)
|
|
@@ -254,7 +266,7 @@ function AssistantMessageRow({ message, usageSummary, showUsageTotal, showUsageL
|
|
|
254
266
|
? 'jini-message jini-message-assistant jini-message-assistant--usage-actions'
|
|
255
267
|
: 'jini-message jini-message-assistant', "data-message-id": message.id, "data-run-status": message.runStatus, "data-agent-element": `chat-message-${message.id}`, "data-agent-role": "region", "data-agent-label": "A reply from the assistant", children: [message.agentName ? _jsx("div", { className: "jini-message-agent", children: message.agentName }) : null, blocks
|
|
256
268
|
? blocks.map(renderBlock)
|
|
257
|
-
: (_jsxs(_Fragment, { children: [renderTextSegments(visibleContent, 'text'),
|
|
269
|
+
: (_jsxs(_Fragment, { children: [renderTextSegments(visibleContent, 'text'), flatTools.map((group) => renderTools(group.rows, group.key)), extGroups.length > 0 ? _jsx("div", { className: "jini-message-ext-events", children: extGroups.map(renderExtGroup) }) : null] })), _jsx(AssistantMessageFooter, { message, usageEvent: footerUsage, showUsageTotal, terminalNotice, pending, runInProgress, runStreaming, visibleContent, isAnsweredCard: answeredCards.isAnsweredCard })] }));
|
|
258
270
|
}
|
|
259
271
|
/**
|
|
260
272
|
* The live "what is it doing" line under a running reply (see `core/run-activity.ts`). Replaces the
|
|
@@ -2,11 +2,12 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import type { ToolDisplayRow } from '../tool-call-display.js';
|
|
3
3
|
/**
|
|
4
4
|
* One turn's tool cards folded into a single line ("Used 3 tools ▸") that opens on click — the
|
|
5
|
-
* `toolCallDisplay: 'collapsed'` layout
|
|
6
|
-
* `useToolCallGroup
|
|
5
|
+
* `toolCallDisplay: 'collapsed'` layout, or the compact failure notice in 'hidden'. The disclosure
|
|
6
|
+
* state lives in `useToolCallGroup`; copy uses the host's translator.
|
|
7
7
|
*/
|
|
8
|
-
export declare function ToolCallGroup<Row extends ToolDisplayRow>({ rows, renderRow }: {
|
|
8
|
+
export declare function ToolCallGroup<Row extends ToolDisplayRow>({ rows, renderRow, failureOnly }: {
|
|
9
9
|
rows: readonly Row[];
|
|
10
|
+
failureOnly?: boolean;
|
|
10
11
|
renderRow: (row: Row) => ReactNode;
|
|
11
12
|
}): import("react").JSX.Element;
|
|
12
13
|
//# sourceMappingURL=ToolCallGroup.d.ts.map
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useToolCallGroup } from './ToolCallGroup.hooks.js';
|
|
3
|
+
import { useT } from '../hooks/context.js';
|
|
3
4
|
import { Icon } from './Icon.js';
|
|
4
5
|
/**
|
|
5
6
|
* One turn's tool cards folded into a single line ("Used 3 tools ▸") that opens on click — the
|
|
6
|
-
* `toolCallDisplay: 'collapsed'` layout
|
|
7
|
-
* `useToolCallGroup
|
|
7
|
+
* `toolCallDisplay: 'collapsed'` layout, or the compact failure notice in 'hidden'. The disclosure
|
|
8
|
+
* state lives in `useToolCallGroup`; copy uses the host's translator.
|
|
8
9
|
*/
|
|
9
|
-
export function ToolCallGroup({ rows, renderRow }) {
|
|
10
|
+
export function ToolCallGroup({ rows, renderRow, failureOnly = false }) {
|
|
10
11
|
const group = useToolCallGroup({ rows });
|
|
11
|
-
|
|
12
|
+
const t = useT();
|
|
13
|
+
return (_jsxs("div", { className: "jini-tool-group", "data-failed": group.failed ? 'true' : undefined, children: [failureOnly ? _jsxs("span", { className: "jini-tool-group__label", children: [t('A step failed'), " "] }) : null, _jsxs("button", { type: "button", className: "jini-tool-group__toggle", "aria-expanded": group.open, onClick: group.toggle, children: [_jsx("span", { className: `jini-tool-group__label${group.running ? ' shimmer-text' : ''}`, children: failureOnly ? t('Show') : group.label }), _jsx(Icon, { name: group.open ? 'chevron-down' : 'chevron-right', size: 12 })] }), group.open ? _jsx("div", { className: "jini-message-tools", children: rows.map(renderRow) }) : null] }));
|
|
12
14
|
}
|
|
13
15
|
//# sourceMappingURL=ToolCallGroup.js.map
|
|
@@ -312,7 +312,8 @@ export interface ChatPaneProps extends UserTextRedactionOptions {
|
|
|
312
312
|
/**
|
|
313
313
|
* Transcript display choices (a host's chat settings, 2026-10-11), passed straight to
|
|
314
314
|
* `MessageList`: how tool calls show (`'show'` default, `'collapsed'` one line per turn, `'hidden'`
|
|
315
|
-
*
|
|
315
|
+
* hides recovered calls and folds unrecovered failures — see `tool-call-display.ts`), whether the
|
|
316
|
+
* "Done · time · cost" lines show
|
|
316
317
|
* (default `true`), and whether the model's reasoning shows (default `false`).
|
|
317
318
|
*/
|
|
318
319
|
toolCallDisplay?: ToolCallDisplay;
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* - `'show'` — every tool card, where it happened. The layout every host had before this.
|
|
7
7
|
* - `'collapsed'` — each turn's tool cards fold into ONE line ("Used 3 tools") that expands on
|
|
8
8
|
* click; while a tool runs the line names it instead ("Running Bash…").
|
|
9
|
-
* - `'hidden'` —
|
|
10
|
-
*
|
|
9
|
+
* - `'hidden'` — recovered failures disappear with successful calls. Unrecovered failures stay
|
|
10
|
+
* behind one compact disclosure per reply: work that stopped must never vanish silently.
|
|
11
11
|
*
|
|
12
12
|
* Pure rules over `interleaveMessageBlocks`' output (or the flat row list when that refused), kept
|
|
13
13
|
* out of `MessageRow.tsx` so every mode is testable without rendering.
|
|
@@ -25,7 +25,7 @@ export interface ToolDisplayRow {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Whether a row
|
|
28
|
+
* Whether a row failed: the tool returned an error, or the run
|
|
29
29
|
* ended without the tool ever answering (`deriveToolStatus` maps both to `'error'`).
|
|
30
30
|
* @complexity O(1).
|
|
31
31
|
*/
|
|
@@ -41,7 +41,8 @@ export declare function isFailedToolRow({ row }: {
|
|
|
41
41
|
* one line per reply. Keeping the first block's key keeps the line's open/closed state across
|
|
42
42
|
* streaming updates, since later blocks append after it.
|
|
43
43
|
*
|
|
44
|
-
* `'hidden'` keeps
|
|
44
|
+
* `'hidden'` keeps unrecovered failures in one block per reply. Later assistant text or a successful
|
|
45
|
+
* tool proves recovery; running tools and reasoning do not. An errored final reply keeps its failures.
|
|
45
46
|
*
|
|
46
47
|
* @param input.blocks - `interleaveMessageBlocks` output.
|
|
47
48
|
* @param input.mode - The display mode.
|
|
@@ -51,15 +52,20 @@ export declare function isFailedToolRow({ row }: {
|
|
|
51
52
|
export declare function applyToolCallDisplay<Row extends ToolDisplayRow>({ blocks, mode }: {
|
|
52
53
|
blocks: readonly MessageBlock<Row>[];
|
|
53
54
|
mode: ToolCallDisplay;
|
|
55
|
+
}, { runFailed }?: {
|
|
56
|
+
runFailed?: boolean;
|
|
54
57
|
}): readonly MessageBlock<Row>[];
|
|
55
58
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
59
|
+
* Row-level recovery scan, also used by the flat fallback. Without trustworthy text ordering,
|
|
60
|
+
* only later successful rows prove recovery. Other modes keep every row.
|
|
58
61
|
* @complexity O(n) in rows.
|
|
59
62
|
*/
|
|
60
63
|
export declare function toolRowsForDisplay<Row extends ToolDisplayRow>({ rows, mode }: {
|
|
61
64
|
rows: readonly Row[];
|
|
62
65
|
mode: ToolCallDisplay;
|
|
66
|
+
}, { recovered, runFailed }?: {
|
|
67
|
+
recovered?: boolean;
|
|
68
|
+
runFailed?: boolean;
|
|
63
69
|
}): readonly Row[];
|
|
64
70
|
/** What a collapsed tool line says: the tool running now, or how many ran (and how many failed). */
|
|
65
71
|
export type ToolGroupSummary = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { humanizeToolName } from '../core/index.js';
|
|
2
2
|
/**
|
|
3
|
-
* Whether a row
|
|
3
|
+
* Whether a row failed: the tool returned an error, or the run
|
|
4
4
|
* ended without the tool ever answering (`deriveToolStatus` maps both to `'error'`).
|
|
5
5
|
* @complexity O(1).
|
|
6
6
|
*/
|
|
@@ -20,31 +20,48 @@ function isRunningToolRow(row) {
|
|
|
20
20
|
* one line per reply. Keeping the first block's key keeps the line's open/closed state across
|
|
21
21
|
* streaming updates, since later blocks append after it.
|
|
22
22
|
*
|
|
23
|
-
* `'hidden'` keeps
|
|
23
|
+
* `'hidden'` keeps unrecovered failures in one block per reply. Later assistant text or a successful
|
|
24
|
+
* tool proves recovery; running tools and reasoning do not. An errored final reply keeps its failures.
|
|
24
25
|
*
|
|
25
26
|
* @param input.blocks - `interleaveMessageBlocks` output.
|
|
26
27
|
* @param input.mode - The display mode.
|
|
27
28
|
* @returns New blocks; the input is never mutated. `'show'` returns `blocks` itself.
|
|
28
29
|
* @complexity O(n) in blocks + rows.
|
|
29
30
|
*/
|
|
30
|
-
export function applyToolCallDisplay({ blocks, mode }) {
|
|
31
|
+
export function applyToolCallDisplay({ blocks, mode }, { runFailed = false } = {}) {
|
|
31
32
|
if (mode === 'show')
|
|
32
33
|
return blocks;
|
|
33
|
-
return mode === 'hidden' ? hideToolCalls({ blocks }) : collapseToolCalls({ blocks });
|
|
34
|
+
return mode === 'hidden' ? hideToolCalls({ blocks, runFailed }) : collapseToolCalls({ blocks });
|
|
34
35
|
}
|
|
35
|
-
/**
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Scan backwards so only later content in the SAME reply can prove recovery. Failures stay visible
|
|
38
|
+
* while streaming until that evidence arrives. This is a display filter, never a deletion from the
|
|
39
|
+
* durable events: see assistant-client/transport.ts's forensics rationale.
|
|
40
|
+
* @complexity O(n) in blocks + rows, excluding the shared grouping pass.
|
|
41
|
+
*/
|
|
42
|
+
function hideToolCalls({ blocks, runFailed }) {
|
|
37
43
|
const out = [];
|
|
38
|
-
|
|
44
|
+
let recovered = false;
|
|
45
|
+
let failedReply = runFailed;
|
|
46
|
+
for (let i = blocks.length - 1; i >= 0; i -= 1) {
|
|
47
|
+
const block = blocks[i];
|
|
48
|
+
if (block.kind === 'reply-end') {
|
|
49
|
+
recovered = false;
|
|
50
|
+
failedReply = false;
|
|
51
|
+
}
|
|
52
|
+
if (block.kind === 'text' && block.text.trim())
|
|
53
|
+
recovered = true;
|
|
39
54
|
if (block.kind !== 'tools') {
|
|
40
55
|
out.push(block);
|
|
41
56
|
continue;
|
|
42
57
|
}
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
out.push({
|
|
58
|
+
const rows = toolRowsForDisplay({ rows: block.rows, mode: 'hidden' }, { recovered, runFailed: failedReply });
|
|
59
|
+
if (rows.length > 0)
|
|
60
|
+
out.push({ ...block, rows });
|
|
61
|
+
if (block.rows.some((row) => row.status === 'complete'))
|
|
62
|
+
recovered = true;
|
|
46
63
|
}
|
|
47
|
-
return out;
|
|
64
|
+
return collapseToolCalls({ blocks: out.reverse() });
|
|
48
65
|
}
|
|
49
66
|
/** `'collapsed'`: merges every `tools` block of a turn into the turn's first one (see above). */
|
|
50
67
|
function collapseToolCalls({ blocks }) {
|
|
@@ -65,12 +82,22 @@ function collapseToolCalls({ blocks }) {
|
|
|
65
82
|
return out;
|
|
66
83
|
}
|
|
67
84
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
85
|
+
* Row-level recovery scan, also used by the flat fallback. Without trustworthy text ordering,
|
|
86
|
+
* only later successful rows prove recovery. Other modes keep every row.
|
|
70
87
|
* @complexity O(n) in rows.
|
|
71
88
|
*/
|
|
72
|
-
export function toolRowsForDisplay({ rows, mode }) {
|
|
73
|
-
|
|
89
|
+
export function toolRowsForDisplay({ rows, mode }, { recovered = false, runFailed = false } = {}) {
|
|
90
|
+
if (mode !== 'hidden')
|
|
91
|
+
return rows;
|
|
92
|
+
const failed = [];
|
|
93
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
94
|
+
const row = rows[i];
|
|
95
|
+
if (isFailedToolRow({ row }) && (runFailed || !recovered))
|
|
96
|
+
failed.push(row);
|
|
97
|
+
if (row.status === 'complete')
|
|
98
|
+
recovered = true;
|
|
99
|
+
}
|
|
100
|
+
return failed.reverse();
|
|
74
101
|
}
|
|
75
102
|
/**
|
|
76
103
|
* Summarises one turn's tool rows. The LAST running row wins, so a line over several in-flight calls
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/chat",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"description": "Jini's chat/artifact frontend, consolidated into one package with two subpaths: framework-free chat vocabulary and pure parsers in `/core`, headless hooks + presentational components + slots in `/react`. Import only the layer you need.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -196,12 +196,12 @@
|
|
|
196
196
|
"registry": "https://registry.npmjs.org"
|
|
197
197
|
},
|
|
198
198
|
"dependencies": {
|
|
199
|
-
"@jini-ai/core": "^0.
|
|
199
|
+
"@jini-ai/core": "^0.6.10"
|
|
200
200
|
},
|
|
201
201
|
"peerDependencies": {
|
|
202
202
|
"react": "^18.3.0 || ^19.0.0",
|
|
203
203
|
"react-dom": "^18.3.0 || ^19.0.0",
|
|
204
|
-
"@jini-ai/db": "^0.
|
|
204
|
+
"@jini-ai/db": "^0.6.10",
|
|
205
205
|
"kysely": "^0.29.6",
|
|
206
206
|
"better-sqlite3": "^13.0.0",
|
|
207
207
|
"@electric-sql/pglite": "0.5.8",
|
|
@@ -213,11 +213,11 @@
|
|
|
213
213
|
"@radix-ui/react-radio-group": "^1.4.7",
|
|
214
214
|
"@radix-ui/react-select": "^2.3.7",
|
|
215
215
|
"@radix-ui/react-slot": "^1.3.3",
|
|
216
|
-
"@jini-ai/agent-runtime": "^0.
|
|
217
|
-
"@jini-ai/agentic": "^0.
|
|
218
|
-
"@jini-ai/protocol": "^0.
|
|
219
|
-
"@jini-ai/ui": "^0.
|
|
220
|
-
"@jini-ai/diagnostics": "^0.
|
|
216
|
+
"@jini-ai/agent-runtime": "^0.6.10",
|
|
217
|
+
"@jini-ai/agentic": "^0.6.10",
|
|
218
|
+
"@jini-ai/protocol": "^0.6.10",
|
|
219
|
+
"@jini-ai/ui": "^0.6.10",
|
|
220
|
+
"@jini-ai/diagnostics": "^0.6.10"
|
|
221
221
|
},
|
|
222
222
|
"peerDependenciesMeta": {
|
|
223
223
|
"react": {
|
|
@@ -303,13 +303,13 @@
|
|
|
303
303
|
"pg": "8.23.0",
|
|
304
304
|
"@ag-ui/core": "0.0.58",
|
|
305
305
|
"fast-check": "4.3.0",
|
|
306
|
-
"@jini-ai/agentic": "^0.
|
|
307
|
-
"@jini-ai/
|
|
308
|
-
"@jini-ai/
|
|
309
|
-
"@jini-ai/
|
|
310
|
-
"@jini-ai/
|
|
311
|
-
"@jini-ai/diagnostics": "^0.
|
|
312
|
-
"@jini-ai/
|
|
306
|
+
"@jini-ai/agentic": "^0.6.10",
|
|
307
|
+
"@jini-ai/db": "0.6.10",
|
|
308
|
+
"@jini-ai/agent-runtime": "0.6.10",
|
|
309
|
+
"@jini-ai/ui": "^0.6.10",
|
|
310
|
+
"@jini-ai/core": "^0.6.10",
|
|
311
|
+
"@jini-ai/diagnostics": "^0.6.10",
|
|
312
|
+
"@jini-ai/protocol": "^0.6.10"
|
|
313
313
|
},
|
|
314
314
|
"typesVersions": {
|
|
315
315
|
"*": {
|