@jini-ai/chat 0.5.1 → 0.5.3
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 +24 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/run-activity.d.ts +12 -20
- package/dist/core/run-activity.js +59 -7
- package/dist/core/surface-expiry.d.ts +42 -0
- package/dist/core/surface-expiry.js +42 -0
- package/dist/core/tool-events.d.ts +34 -0
- package/dist/core/tool-events.js +61 -0
- package/dist/core/tools.d.ts +1 -1
- package/dist/core/tools.js +1 -1
- package/dist/react/chat-pane.d.ts +2 -2
- package/dist/react/chat-pane.js +1 -1
- package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +19 -2
- package/dist/react/components/AttachmentPreviewModal.hooks.js +53 -38
- package/dist/react/components/Markdown.js +37 -6
- package/dist/react/components/McpUiSurfaceCard.d.ts +7 -1
- package/dist/react/components/McpUiSurfaceCard.js +23 -38
- package/dist/react/components/MessageList.d.ts +11 -0
- package/dist/react/components/MessageList.hooks.d.ts +19 -0
- package/dist/react/components/MessageList.hooks.js +82 -0
- package/dist/react/components/MessageList.js +3 -71
- package/dist/react/components/MessageList.rules.d.ts +43 -0
- package/dist/react/components/MessageList.rules.js +38 -0
- package/dist/react/components/MessageRow.js +3 -1
- package/dist/react/components/ToolCard.js +10 -7
- package/dist/react/components/mcp-ui-surface-state.d.ts +27 -0
- package/dist/react/components/mcp-ui-surface-state.js +33 -0
- package/dist/react/ext-event-renderer-registry.d.ts +7 -0
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +6 -5
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +54 -0
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +46 -0
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +17 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +153 -7
- package/dist/react/features/chat-pane/index.d.ts +2 -1
- package/dist/react/features/chat-pane/index.js +1 -0
- package/dist/react/features/chat-pane/rules.d.ts +59 -1
- package/dist/react/features/chat-pane/rules.js +74 -0
- package/dist/react/features/chat-pane/types.d.ts +34 -0
- package/dist/react/hooks/composer-draft-cache.d.ts +28 -0
- package/dist/react/hooks/composer-draft-cache.js +56 -6
- package/dist/react/hooks/last-conversation-store.d.ts +58 -0
- package/dist/react/hooks/last-conversation-store.js +112 -0
- package/dist/react/hooks/useSurfaceExpiry.d.ts +20 -0
- package/dist/react/hooks/useSurfaceExpiry.js +35 -0
- package/dist/react/hooks/useToolTimeline.js +6 -3
- package/dist/react/index.d.ts +10 -2
- package/dist/react/index.js +7 -1
- package/package.json +7 -7
|
@@ -105,6 +105,80 @@ export function describeChatPaneSendBlocker(blocker) {
|
|
|
105
105
|
export function isChatPaneQueueableBlocker({ blocker }) {
|
|
106
106
|
return blocker === 'streaming';
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether the run in flight is holding a question open for the human: the newest message is the
|
|
110
|
+
* assistant's, and it carries an interactive surface (`kind: 'ext'`, e.g. an MCP-UI card) that
|
|
111
|
+
* arrived inside a still-open call to the host's question tool (`toolName`). That is the one moment
|
|
112
|
+
* text typed into the composer is an ANSWER rather than a next turn — the tool is parked waiting on
|
|
113
|
+
* it, and queueing the text behind the run would only deliver it after the question had expired, as
|
|
114
|
+
* a new paid run.
|
|
115
|
+
*
|
|
116
|
+
* Only the question tool counts. Other tools park on cards too — a delete confirm, an approval —
|
|
117
|
+
* but they take no typed answer: the server routes typed text only to the question tool, so treating
|
|
118
|
+
* those as waiting would refuse the text with "no longer waiting" and drop it instead of queueing.
|
|
119
|
+
* A call is the question tool when ANY `tool_use` for its id names it, since the same call's
|
|
120
|
+
* `tool_use` can arrive more than once (the CLI re-emits it). A vendor's wrapper row
|
|
121
|
+
* (`execute_delegated_tool`) and the daemon's canonical row do NOT share an id — the wrapper carries
|
|
122
|
+
* the CLI's `toolu_…` id and the canonical row a fresh UUID from `@jini-ai/mcp`. The canonical row
|
|
123
|
+
* opens after its wrapper, so it is the newest open call when the surface arrives, and that is
|
|
124
|
+
* what attributes the surface to the question tool.
|
|
125
|
+
*
|
|
126
|
+
* A surface is attributed to the newest call still open when it arrived — the same rule
|
|
127
|
+
* `useExtEventGroups` uses to give a card its `call` — so an older call returning does not settle
|
|
128
|
+
* it. Once its call returns (answered, expired, abandoned), typing is a next turn again.
|
|
129
|
+
*
|
|
130
|
+
* @complexity O(e·c): e events in the newest message, c calls open at once (one or two in practice).
|
|
131
|
+
*/
|
|
132
|
+
export function isAwaitingTypedAnswer({ messages, toolName }) {
|
|
133
|
+
return findAwaitedTypedAnswerId({ messages, toolName }) !== null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The tool-use id of the question {@link isAwaitingTypedAnswer} sees waiting, or `null` — the newest
|
|
137
|
+
* one when several are open. The id lets the pane tell one question from the next: a draft typed
|
|
138
|
+
* for a question stays tied to THAT question after it closes (see `useChatPane`'s held answer).
|
|
139
|
+
*
|
|
140
|
+
* @complexity O(e·c), as {@link isAwaitingTypedAnswer}.
|
|
141
|
+
*/
|
|
142
|
+
export function findAwaitedTypedAnswerId({ messages, toolName }) {
|
|
143
|
+
const newest = messages.at(-1);
|
|
144
|
+
if (newest?.role !== 'assistant')
|
|
145
|
+
return null;
|
|
146
|
+
// Open call id -> whether it is the question tool. Insertion-ordered, so the last entry is the
|
|
147
|
+
// newest call still open; a repeat id keeps its slot and only ORs in its name.
|
|
148
|
+
const openCalls = new Map();
|
|
149
|
+
const awaited = new Set();
|
|
150
|
+
for (const event of newest.events ?? []) {
|
|
151
|
+
if (event.kind === 'tool_use')
|
|
152
|
+
openCalls.set(event.id, openCalls.get(event.id) === true || event.name === toolName);
|
|
153
|
+
if (event.kind === 'tool_result') {
|
|
154
|
+
openCalls.delete(event.toolUseId);
|
|
155
|
+
awaited.delete(event.toolUseId);
|
|
156
|
+
}
|
|
157
|
+
const owner = event.kind === 'ext' ? [...openCalls].at(-1) : undefined;
|
|
158
|
+
if (owner?.[1] === true)
|
|
159
|
+
awaited.add(owner[0]);
|
|
160
|
+
}
|
|
161
|
+
return [...awaited].at(-1) ?? null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Whether a composer send is an answer to a waiting question rather than a next turn: a run is
|
|
165
|
+
* streaming ({@link isChatPaneQueueableBlocker}), the turn has no attachments (a typed answer
|
|
166
|
+
* carries text only), and that run is holding the `toolName` question open
|
|
167
|
+
* ({@link isAwaitingTypedAnswer}).
|
|
168
|
+
*
|
|
169
|
+
* @complexity O(e·c), from {@link isAwaitingTypedAnswer}.
|
|
170
|
+
*/
|
|
171
|
+
export function isTypedAnswerTurn({ blocker, attachmentCount, messages, toolName }) {
|
|
172
|
+
return isChatPaneQueueableBlocker({ blocker }) && attachmentCount === 0 && isAwaitingTypedAnswer({ messages, toolName });
|
|
173
|
+
}
|
|
174
|
+
const TYPED_ANSWER_NOTICES = {
|
|
175
|
+
'not-pending': 'That question is no longer waiting for an answer, so your message was not sent.',
|
|
176
|
+
failed: 'Your answer could not be delivered. Try sending it again.',
|
|
177
|
+
};
|
|
178
|
+
/** The English copy (and i18n key) for why a typed answer was not sent. */
|
|
179
|
+
export function describeTypedAnswerNotice({ notice }) {
|
|
180
|
+
return TYPED_ANSWER_NOTICES[notice];
|
|
181
|
+
}
|
|
108
182
|
export function orderChatPaneAgents({ agents }) {
|
|
109
183
|
return [...agents].sort((left, right) => {
|
|
110
184
|
const availability = Number(left.available === false) - Number(right.available === false);
|
|
@@ -120,6 +120,28 @@ export interface ChatPaneComposerHandle {
|
|
|
120
120
|
*/
|
|
121
121
|
insertText: (text: string) => void;
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* What happened to text typed while the running agent waits on a question (see
|
|
125
|
+
* {@link ChatPaneProps.deliverTypedAnswer}): `'delivered'` — the question took it as its answer;
|
|
126
|
+
* `'not-pending'` — nothing is waiting any more (already answered, expired, or never open);
|
|
127
|
+
* `'failed'` — the host could not tell (network error, unexpected status).
|
|
128
|
+
*/
|
|
129
|
+
export type TypedAnswerDelivery = 'delivered' | 'not-pending' | 'failed';
|
|
130
|
+
/** Why the pane did not send a typed answer — shown to the human until the next send or reset. */
|
|
131
|
+
export type TypedAnswerNotice = Exclude<TypedAnswerDelivery, 'delivered'>;
|
|
132
|
+
/**
|
|
133
|
+
* Delivers text typed into the composer to the question the running agent is waiting on.
|
|
134
|
+
*
|
|
135
|
+
* Carries `toolName`, the host's question tool id, so the pane routes typed text here only while a
|
|
136
|
+
* card raised by THAT tool is open — any other pending card (a confirm, an approval) still queues the
|
|
137
|
+
* text behind the run. One value, so the tool the pane waits on and the tool the answer is posted to
|
|
138
|
+
* cannot drift apart; `createTypedAnswerPoster` sets it.
|
|
139
|
+
*/
|
|
140
|
+
export type DeliverTypedAnswer = ((input: {
|
|
141
|
+
text: string;
|
|
142
|
+
}) => Promise<TypedAnswerDelivery>) & {
|
|
143
|
+
readonly toolName: string;
|
|
144
|
+
};
|
|
123
145
|
export interface ChatPaneProps {
|
|
124
146
|
transport: ChatTransport;
|
|
125
147
|
agents?: readonly ChatPaneAgent[];
|
|
@@ -170,6 +192,18 @@ export interface ChatPaneProps {
|
|
|
170
192
|
* before the pane exists has nothing to call.
|
|
171
193
|
*/
|
|
172
194
|
composerHandle?: RefObject<ChatPaneComposerHandle | null>;
|
|
195
|
+
/**
|
|
196
|
+
* Called instead of queueing when the human types while the running agent is holding a question
|
|
197
|
+
* open in the transcript (an interactive surface whose call to `deliverTypedAnswer.toolName` has
|
|
198
|
+
* not returned — `isAwaitingTypedAnswer`) and the turn carries no attachments. `'delivered'` clears the draft
|
|
199
|
+
* (only if unchanged since it went out); any other outcome, or a throw, leaves the draft in place with a
|
|
200
|
+
* notice and NEVER queues it — a queued answer would go out after the run ends as a new, paid run
|
|
201
|
+
* answering nothing. A draft typed while the question waited stays held after it closes, until the
|
|
202
|
+
* human clears it or picks "Send as a new message" (`useChatPane`'s `sendAsNewMessage`).
|
|
203
|
+
*
|
|
204
|
+
* Omit it and typing while a run streams queues exactly as before.
|
|
205
|
+
*/
|
|
206
|
+
deliverTypedAnswer?: DeliverTypedAnswer;
|
|
173
207
|
placeholder?: string;
|
|
174
208
|
/**
|
|
175
209
|
* Rotating composer suggestions, cycled every few seconds in place of one fixed `placeholder` —
|
|
@@ -115,6 +115,34 @@ export declare function clearCachedDraft({ conversationId }: {
|
|
|
115
115
|
export declare function readCachedAttachments({ conversationId }: {
|
|
116
116
|
conversationId: string | null | undefined;
|
|
117
117
|
}): readonly ChatAttachment[] | null;
|
|
118
|
+
/**
|
|
119
|
+
* The upload batch `conversationId`'s cached attachments were uploaded under, so a pane that
|
|
120
|
+
* restores those attachments uploads the next file into the SAME batch.
|
|
121
|
+
*
|
|
122
|
+
* Why this exists: the daemon grants the agent exactly one batch directory per turn and refuses a
|
|
123
|
+
* turn whose attachments span two ("Attachments must belong to one batch"). A pane that restored an
|
|
124
|
+
* attachment after a remount, reload, or conversation switch used to upload the next file under a
|
|
125
|
+
* brand-new batch id, so every such turn failed before the agent started (2026-10-06).
|
|
126
|
+
*
|
|
127
|
+
* @returns The batch id, or `null` when there are no cached attachments to stay consistent with
|
|
128
|
+
* (none staged, attachment persistence off because the host supplied no validator, or an entry
|
|
129
|
+
* written before batch ids were recorded) — the caller then starts a fresh batch.
|
|
130
|
+
* @complexity Time/space: O(1) on an in-memory hit, O(n) in the stored count otherwise.
|
|
131
|
+
*/
|
|
132
|
+
export declare function readCachedAttachmentBatchId({ conversationId }: {
|
|
133
|
+
conversationId: string | null | undefined;
|
|
134
|
+
}): string | null;
|
|
135
|
+
/**
|
|
136
|
+
* Records the upload batch of an attachment about to be staged in `conversationId`'s composer.
|
|
137
|
+
* Memory only: the stored envelope picks it up on the {@link writeCachedAttachments} that persists
|
|
138
|
+
* that attachment, so it is never written ahead of the references it describes.
|
|
139
|
+
*
|
|
140
|
+
* @complexity Time/space: O(1) amortized.
|
|
141
|
+
*/
|
|
142
|
+
export declare function writeCachedAttachmentBatchId({ conversationId, batchId }: {
|
|
143
|
+
conversationId: string | null | undefined;
|
|
144
|
+
batchId: string;
|
|
145
|
+
}): void;
|
|
118
146
|
/**
|
|
119
147
|
* Stores `attachments` for `conversationId` in both tiers.
|
|
120
148
|
*
|
|
@@ -62,6 +62,10 @@ export const COMPOSER_DRAFT_STORAGE_PREFIX = 'jini.chat.composer-draft.v1.';
|
|
|
62
62
|
export const COMPOSER_ATTACHMENTS_STORAGE_PREFIX = 'jini.chat.composer-attachments.v1.';
|
|
63
63
|
const drafts = new Map();
|
|
64
64
|
const stagedAttachments = new Map();
|
|
65
|
+
// Kept beside `stagedAttachments` rather than inside it so `writeCachedAttachments` keeps its
|
|
66
|
+
// attachments-only signature: `useComposer` persists the list and never sees a batch id, while
|
|
67
|
+
// `useChatPane` (the one caller that uploads in batches) records the batch here.
|
|
68
|
+
const stagedBatchIds = new Map();
|
|
65
69
|
/**
|
|
66
70
|
* The origin's `localStorage`, or `null` when it cannot be used.
|
|
67
71
|
*
|
|
@@ -282,10 +286,11 @@ function isChatAttachment(candidate) {
|
|
|
282
286
|
return true;
|
|
283
287
|
}
|
|
284
288
|
/**
|
|
285
|
-
* Reads the stored attachment references for `conversationId`.
|
|
289
|
+
* Reads the stored attachment references (and their upload batch) for `conversationId`.
|
|
286
290
|
*
|
|
287
291
|
* Elements that do not validate are dropped individually rather than failing the whole entry — one
|
|
288
|
-
* malformed row should cost the operator that row, not every attachment on the turn.
|
|
292
|
+
* malformed row should cost the operator that row, not every attachment on the turn. A missing or
|
|
293
|
+
* malformed batch id costs only the batch (`batchId: null`), never the references.
|
|
289
294
|
*
|
|
290
295
|
* @returns The stored references, or `null` when there are none, storage is unavailable, or nothing
|
|
291
296
|
* in the entry validated.
|
|
@@ -302,6 +307,7 @@ function readStoredAttachments(conversationId) {
|
|
|
302
307
|
return null;
|
|
303
308
|
const parsed = JSON.parse(raw);
|
|
304
309
|
const list = parsed?.a;
|
|
310
|
+
const batchId = parsed?.b;
|
|
305
311
|
if (!Array.isArray(list)) {
|
|
306
312
|
store.removeItem(key);
|
|
307
313
|
return null;
|
|
@@ -311,7 +317,7 @@ function readStoredAttachments(conversationId) {
|
|
|
311
317
|
store.removeItem(key);
|
|
312
318
|
return null;
|
|
313
319
|
}
|
|
314
|
-
return valid;
|
|
320
|
+
return { attachments: valid, batchId: typeof batchId === 'string' && batchId !== '' ? batchId : null };
|
|
315
321
|
}
|
|
316
322
|
catch {
|
|
317
323
|
try {
|
|
@@ -345,8 +351,48 @@ export function readCachedAttachments({ conversationId }) {
|
|
|
345
351
|
const stored = readStoredAttachments(conversationId);
|
|
346
352
|
if (stored === null)
|
|
347
353
|
return null;
|
|
348
|
-
stagedAttachments.set(conversationId, stored);
|
|
349
|
-
|
|
354
|
+
stagedAttachments.set(conversationId, stored.attachments);
|
|
355
|
+
if (stored.batchId !== null)
|
|
356
|
+
stagedBatchIds.set(conversationId, stored.batchId);
|
|
357
|
+
return stored.attachments;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* The upload batch `conversationId`'s cached attachments were uploaded under, so a pane that
|
|
361
|
+
* restores those attachments uploads the next file into the SAME batch.
|
|
362
|
+
*
|
|
363
|
+
* Why this exists: the daemon grants the agent exactly one batch directory per turn and refuses a
|
|
364
|
+
* turn whose attachments span two ("Attachments must belong to one batch"). A pane that restored an
|
|
365
|
+
* attachment after a remount, reload, or conversation switch used to upload the next file under a
|
|
366
|
+
* brand-new batch id, so every such turn failed before the agent started (2026-10-06).
|
|
367
|
+
*
|
|
368
|
+
* @returns The batch id, or `null` when there are no cached attachments to stay consistent with
|
|
369
|
+
* (none staged, attachment persistence off because the host supplied no validator, or an entry
|
|
370
|
+
* written before batch ids were recorded) — the caller then starts a fresh batch.
|
|
371
|
+
* @complexity Time/space: O(1) on an in-memory hit, O(n) in the stored count otherwise.
|
|
372
|
+
*/
|
|
373
|
+
export function readCachedAttachmentBatchId({ conversationId }) {
|
|
374
|
+
if (!conversationId)
|
|
375
|
+
return null;
|
|
376
|
+
if (readCachedAttachments({ conversationId: conversationId }) === null)
|
|
377
|
+
return null;
|
|
378
|
+
return stagedBatchIds.get(conversationId) ?? null;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Records the upload batch of an attachment about to be staged in `conversationId`'s composer.
|
|
382
|
+
* Memory only: the stored envelope picks it up on the {@link writeCachedAttachments} that persists
|
|
383
|
+
* that attachment, so it is never written ahead of the references it describes.
|
|
384
|
+
*
|
|
385
|
+
* @complexity Time/space: O(1) amortized.
|
|
386
|
+
*/
|
|
387
|
+
export function writeCachedAttachmentBatchId({ conversationId, batchId }) {
|
|
388
|
+
if (!conversationId)
|
|
389
|
+
return;
|
|
390
|
+
if (!stagedBatchIds.has(conversationId) && stagedBatchIds.size >= MAX_CACHED_CONVERSATION_DRAFTS) {
|
|
391
|
+
const oldest = stagedBatchIds.keys().next().value;
|
|
392
|
+
if (oldest !== undefined)
|
|
393
|
+
stagedBatchIds.delete(oldest);
|
|
394
|
+
}
|
|
395
|
+
stagedBatchIds.set(conversationId, batchId);
|
|
350
396
|
}
|
|
351
397
|
/**
|
|
352
398
|
* Stores `attachments` for `conversationId` in both tiers.
|
|
@@ -362,6 +408,8 @@ export function writeCachedAttachments({ conversationId, attachments }) {
|
|
|
362
408
|
const store = storage();
|
|
363
409
|
if (attachments.length === 0) {
|
|
364
410
|
stagedAttachments.delete(conversationId);
|
|
411
|
+
// A sent or cleared turn's batch is spent; the next upload starts a fresh one.
|
|
412
|
+
stagedBatchIds.delete(conversationId);
|
|
365
413
|
try {
|
|
366
414
|
store?.removeItem(key);
|
|
367
415
|
}
|
|
@@ -381,7 +429,8 @@ export function writeCachedAttachments({ conversationId, attachments }) {
|
|
|
381
429
|
try {
|
|
382
430
|
if (store.getItem(key) === null)
|
|
383
431
|
pruneStored(COMPOSER_ATTACHMENTS_STORAGE_PREFIX, MAX_CACHED_CONVERSATION_DRAFTS - 1);
|
|
384
|
-
const
|
|
432
|
+
const batchId = stagedBatchIds.get(conversationId);
|
|
433
|
+
const envelope = { v: 1, t: Date.now(), a: attachments, ...(batchId === undefined ? {} : { b: batchId }) };
|
|
385
434
|
store.setItem(key, JSON.stringify(envelope));
|
|
386
435
|
}
|
|
387
436
|
catch {
|
|
@@ -399,6 +448,7 @@ export function writeCachedAttachments({ conversationId, attachments }) {
|
|
|
399
448
|
export function __resetComposerDraftCacheForTests(options) {
|
|
400
449
|
drafts.clear();
|
|
401
450
|
stagedAttachments.clear();
|
|
451
|
+
stagedBatchIds.clear();
|
|
402
452
|
if (options?.keepStorage === true)
|
|
403
453
|
return;
|
|
404
454
|
pruneStored(COMPOSER_DRAFT_STORAGE_PREFIX, 0);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module last-conversation-store
|
|
3
|
+
*
|
|
4
|
+
* Remembers which conversation an operator last had open, so a host's chat surface can land back
|
|
5
|
+
* on it after a page reload, a closed tab, or a navigation away — instead of an empty new chat.
|
|
6
|
+
*
|
|
7
|
+
* Conversation SELECTION is host-owned (`ChatPane` is uncontrolled and takes a `conversationId`;
|
|
8
|
+
* `ConversationList` takes `activeConversationId` as a prop), so this module does not decide when
|
|
9
|
+
* to restore. It is only the durable slot: the host reads it once at mount, checks the id still
|
|
10
|
+
* exists for this caller, and writes it whenever its active conversation changes.
|
|
11
|
+
*
|
|
12
|
+
* Same storage discipline as `composer-draft-cache.ts`: a versioned key, every access wrapped, and a
|
|
13
|
+
* foreign or corrupt entry treated as "nothing remembered" and deleted. A private window, blocked
|
|
14
|
+
* site data or a quota rejection degrades this to remembering nothing — never a throw at the host.
|
|
15
|
+
*
|
|
16
|
+
* Unlike the draft cache this is NOT a module singleton. The partition here is the caller's
|
|
17
|
+
* identity (`scope`), which only the host knows — two users on one browser must never land in each
|
|
18
|
+
* other's chat — so a host constructs one store per scope and passes it down.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Key prefix for the stored entry. Versioned (`.v1.`) so a future change to the stored shape can be
|
|
22
|
+
* introduced without interpreting, or mis-interpreting, entries written by this one.
|
|
23
|
+
*/
|
|
24
|
+
export declare const LAST_CONVERSATION_STORAGE_PREFIX = "jini.chat.last-conversation.v1.";
|
|
25
|
+
/** The subset of Web Storage this module uses — `localStorage` satisfies it; tests pass a fake. */
|
|
26
|
+
export interface LastConversationStorage {
|
|
27
|
+
getItem(key: string): string | null;
|
|
28
|
+
setItem(key: string, value: string): void;
|
|
29
|
+
removeItem(key: string): void;
|
|
30
|
+
}
|
|
31
|
+
/** The host-facing port. Every method is total: none throws, whatever storage does. */
|
|
32
|
+
export interface LastConversationStore {
|
|
33
|
+
/** The remembered conversation id, or `null` when there is none or storage is unusable. */
|
|
34
|
+
read(): string | null;
|
|
35
|
+
/** Remembers `conversationId` as the one to land on next time, replacing any earlier one. */
|
|
36
|
+
write(conversationId: string): void;
|
|
37
|
+
/** Forgets the remembered id — for a conversation that was deleted or is no longer reachable. */
|
|
38
|
+
clear(): void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Creates the last-conversation slot for one caller identity.
|
|
42
|
+
*
|
|
43
|
+
* @param required.scope Who this memory belongs to — e.g. `${workspaceId}:${userId}`. Part of the
|
|
44
|
+
* key, so a different scope on the same browser reads nothing.
|
|
45
|
+
* @param optional.storage Defaults to the origin's `localStorage`, resolved on every call (not once)
|
|
46
|
+
* so a store created during SSR still works after hydration. `null` disables persistence.
|
|
47
|
+
* @returns A {@link LastConversationStore}; see its methods for the never-throws contract.
|
|
48
|
+
* @example
|
|
49
|
+
* const store = createLastConversationStore({ scope: `${workspaceId}:${user.id}` });
|
|
50
|
+
* const id = store.read(); // land here if it still exists, else store.clear()
|
|
51
|
+
* @complexity Time/space: O(1) per call, plus O(n) in the entry's length for the JSON parse.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createLastConversationStore({ scope }: {
|
|
54
|
+
scope: string;
|
|
55
|
+
}, { storage }?: {
|
|
56
|
+
storage?: LastConversationStorage | null;
|
|
57
|
+
}): LastConversationStore;
|
|
58
|
+
//# sourceMappingURL=last-conversation-store.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module last-conversation-store
|
|
3
|
+
*
|
|
4
|
+
* Remembers which conversation an operator last had open, so a host's chat surface can land back
|
|
5
|
+
* on it after a page reload, a closed tab, or a navigation away — instead of an empty new chat.
|
|
6
|
+
*
|
|
7
|
+
* Conversation SELECTION is host-owned (`ChatPane` is uncontrolled and takes a `conversationId`;
|
|
8
|
+
* `ConversationList` takes `activeConversationId` as a prop), so this module does not decide when
|
|
9
|
+
* to restore. It is only the durable slot: the host reads it once at mount, checks the id still
|
|
10
|
+
* exists for this caller, and writes it whenever its active conversation changes.
|
|
11
|
+
*
|
|
12
|
+
* Same storage discipline as `composer-draft-cache.ts`: a versioned key, every access wrapped, and a
|
|
13
|
+
* foreign or corrupt entry treated as "nothing remembered" and deleted. A private window, blocked
|
|
14
|
+
* site data or a quota rejection degrades this to remembering nothing — never a throw at the host.
|
|
15
|
+
*
|
|
16
|
+
* Unlike the draft cache this is NOT a module singleton. The partition here is the caller's
|
|
17
|
+
* identity (`scope`), which only the host knows — two users on one browser must never land in each
|
|
18
|
+
* other's chat — so a host constructs one store per scope and passes it down.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Key prefix for the stored entry. Versioned (`.v1.`) so a future change to the stored shape can be
|
|
22
|
+
* introduced without interpreting, or mis-interpreting, entries written by this one.
|
|
23
|
+
*/
|
|
24
|
+
export const LAST_CONVERSATION_STORAGE_PREFIX = 'jini.chat.last-conversation.v1.';
|
|
25
|
+
/**
|
|
26
|
+
* The origin's `localStorage`, or `null` when it cannot be used — a non-browser runtime, or a
|
|
27
|
+
* browser that throws on the property access itself (Safari and Chrome both do with site data
|
|
28
|
+
* blocked), which is why this is a try/catch rather than a `typeof` check alone.
|
|
29
|
+
* @complexity Time/space: O(1).
|
|
30
|
+
*/
|
|
31
|
+
function browserStorage() {
|
|
32
|
+
try {
|
|
33
|
+
return typeof localStorage === 'undefined' ? null : localStorage;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Validates one parsed entry as this module's own envelope.
|
|
41
|
+
* @complexity Time/space: O(1).
|
|
42
|
+
*/
|
|
43
|
+
function storedId(parsed) {
|
|
44
|
+
const entry = parsed;
|
|
45
|
+
if (entry?.v !== 1 || typeof entry.id !== 'string' || entry.id === '')
|
|
46
|
+
return null;
|
|
47
|
+
return entry.id;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates the last-conversation slot for one caller identity.
|
|
51
|
+
*
|
|
52
|
+
* @param required.scope Who this memory belongs to — e.g. `${workspaceId}:${userId}`. Part of the
|
|
53
|
+
* key, so a different scope on the same browser reads nothing.
|
|
54
|
+
* @param optional.storage Defaults to the origin's `localStorage`, resolved on every call (not once)
|
|
55
|
+
* so a store created during SSR still works after hydration. `null` disables persistence.
|
|
56
|
+
* @returns A {@link LastConversationStore}; see its methods for the never-throws contract.
|
|
57
|
+
* @example
|
|
58
|
+
* const store = createLastConversationStore({ scope: `${workspaceId}:${user.id}` });
|
|
59
|
+
* const id = store.read(); // land here if it still exists, else store.clear()
|
|
60
|
+
* @complexity Time/space: O(1) per call, plus O(n) in the entry's length for the JSON parse.
|
|
61
|
+
*/
|
|
62
|
+
export function createLastConversationStore({ scope }, { storage } = {}) {
|
|
63
|
+
const key = `${LAST_CONVERSATION_STORAGE_PREFIX}${encodeURIComponent(scope)}`;
|
|
64
|
+
const resolve = () => (storage === undefined ? browserStorage() : storage);
|
|
65
|
+
const remove = (store) => {
|
|
66
|
+
try {
|
|
67
|
+
store.removeItem(key);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* storage is unusable; nothing further this module can do */
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
read() {
|
|
75
|
+
const store = resolve();
|
|
76
|
+
if (!store)
|
|
77
|
+
return null;
|
|
78
|
+
try {
|
|
79
|
+
const raw = store.getItem(key);
|
|
80
|
+
if (raw === null)
|
|
81
|
+
return null;
|
|
82
|
+
const id = storedId(JSON.parse(raw));
|
|
83
|
+
if (id === null)
|
|
84
|
+
remove(store);
|
|
85
|
+
return id;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// A parse failure or a throwing read: a corrupt entry must not survive to be read again.
|
|
89
|
+
remove(store);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
write(conversationId) {
|
|
94
|
+
const store = resolve();
|
|
95
|
+
if (!store)
|
|
96
|
+
return;
|
|
97
|
+
try {
|
|
98
|
+
const envelope = { v: 1, id: conversationId };
|
|
99
|
+
store.setItem(key, JSON.stringify(envelope));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* blocked, full, or unusable storage — remembering nothing is the documented degraded mode */
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
clear() {
|
|
106
|
+
const store = resolve();
|
|
107
|
+
if (store)
|
|
108
|
+
remove(store);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=last-conversation-store.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SurfaceExpiry } from '../../core/index.js';
|
|
2
|
+
/** The clock port: the current time, and a repeating ticker that returns its own stop function. */
|
|
3
|
+
export interface SurfaceExpiryClock {
|
|
4
|
+
nowMs(): number;
|
|
5
|
+
every(intervalMs: number, tick: () => void): () => void;
|
|
6
|
+
}
|
|
7
|
+
/** The wall clock and `setInterval`; the default when a caller injects none. */
|
|
8
|
+
export declare const SYSTEM_SURFACE_EXPIRY_CLOCK: SurfaceExpiryClock;
|
|
9
|
+
/**
|
|
10
|
+
* @param input.expiresAtMs - The card's deadline (epoch ms), or `undefined` when it has none.
|
|
11
|
+
* @param options.clock - Time source and ticker. Defaults to {@link SYSTEM_SURFACE_EXPIRY_CLOCK}.
|
|
12
|
+
* @returns The deadline's state at the last tick, or `undefined` when there is no deadline.
|
|
13
|
+
* @complexity O(1) per render; one ticker while live.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useSurfaceExpiry({ expiresAtMs }: {
|
|
16
|
+
expiresAtMs: number | undefined;
|
|
17
|
+
}, { clock }?: {
|
|
18
|
+
clock?: SurfaceExpiryClock;
|
|
19
|
+
}): SurfaceExpiry | undefined;
|
|
20
|
+
//# sourceMappingURL=useSurfaceExpiry.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module useSurfaceExpiry
|
|
3
|
+
*
|
|
4
|
+
* The live countdown for a pending MCP-UI card: `describeSurfaceExpiry` re-read once a second on an
|
|
5
|
+
* injected clock. Ticks only while the card is answerable and has a deadline; an expired card or one
|
|
6
|
+
* without a deadline runs no timer.
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect, useState } from 'react';
|
|
9
|
+
import { describeSurfaceExpiry, SURFACE_EXPIRY_TICK_MS } from '../../core/index.js';
|
|
10
|
+
/** The wall clock and `setInterval`; the default when a caller injects none. */
|
|
11
|
+
export const SYSTEM_SURFACE_EXPIRY_CLOCK = {
|
|
12
|
+
nowMs: () => Date.now(),
|
|
13
|
+
every: (intervalMs, tick) => {
|
|
14
|
+
const timer = setInterval(tick, intervalMs);
|
|
15
|
+
return () => clearInterval(timer);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* @param input.expiresAtMs - The card's deadline (epoch ms), or `undefined` when it has none.
|
|
20
|
+
* @param options.clock - Time source and ticker. Defaults to {@link SYSTEM_SURFACE_EXPIRY_CLOCK}.
|
|
21
|
+
* @returns The deadline's state at the last tick, or `undefined` when there is no deadline.
|
|
22
|
+
* @complexity O(1) per render; one ticker while live.
|
|
23
|
+
*/
|
|
24
|
+
export function useSurfaceExpiry({ expiresAtMs }, { clock = SYSTEM_SURFACE_EXPIRY_CLOCK } = {}) {
|
|
25
|
+
const [nowMs, setNowMs] = useState(() => clock.nowMs());
|
|
26
|
+
const expiry = expiresAtMs === undefined ? undefined : describeSurfaceExpiry({ expiresAtMs, nowMs });
|
|
27
|
+
const live = expiry?.expired === false;
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!live)
|
|
30
|
+
return undefined;
|
|
31
|
+
return clock.every(SURFACE_EXPIRY_TICK_MS, () => setNowMs(clock.nowMs()));
|
|
32
|
+
}, [live, clock]);
|
|
33
|
+
return expiry;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=useSurfaceExpiry.js.map
|
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Per-tool lifecycle rows for a message's events: pairs each `tool_use` with
|
|
5
5
|
* its `tool_result` (deduped by id via `@jini-ai/chat/core`'s
|
|
6
|
-
* `dedupeToolUsesById
|
|
6
|
+
* `dedupeToolUsesById`, delegated wrapper calls folded onto their canonical
|
|
7
|
+
* rows via `foldDelegatedWrapperCalls`), derives a four-state status via
|
|
7
8
|
* `deriveToolStatus`, and tracks per-row expand/collapse UI state. Pure over
|
|
8
9
|
* `AgentEvent[]` — zero I/O. Per
|
|
9
10
|
* `ADS-memory/reports/jini-port/recon/r4b-webui-design.md` §4.
|
|
10
11
|
*/
|
|
11
12
|
import { useCallback, useMemo, useState } from 'react';
|
|
12
|
-
import { dedupeToolUsesById, deriveToolStatus } from '../../core/index.js';
|
|
13
|
+
import { dedupeToolUsesById, deriveToolStatus, foldDelegatedWrapperCalls } from '../../core/index.js';
|
|
13
14
|
export function useToolTimeline({ events }, options = {}) {
|
|
14
15
|
const { runStreaming = false, runSucceeded = false, defaultExpanded = false } = options;
|
|
15
16
|
const [overrides, setOverrides] = useState({});
|
|
16
|
-
|
|
17
|
+
// A delegated call's wrapper row and the daemon's canonical row have different ids, so dedupe
|
|
18
|
+
// alone leaves both; the fold keeps the canonical one (see `foldDelegatedWrapperCalls`).
|
|
19
|
+
const deduped = useMemo(() => foldDelegatedWrapperCalls({ events: dedupeToolUsesById({ events: events }) }), [events]);
|
|
17
20
|
const rows = useMemo(() => {
|
|
18
21
|
const out = [];
|
|
19
22
|
for (const ev of deduped) {
|
package/dist/react/index.d.ts
CHANGED
|
@@ -36,8 +36,8 @@ export type { ExtEventRenderProps, ExtEventRenderer, ExtEventRendererOptions, Ex
|
|
|
36
36
|
* This alias has no removal date scheduled yet; see `chat-pane.ts` for the canonical surface and
|
|
37
37
|
* the module doc there for why the split exists.
|
|
38
38
|
*/
|
|
39
|
-
export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
|
|
40
|
-
export type { AgentRuntimePickerProps, ByokRuntimeSummary, ChatPaneActivity, ChatPaneAgent, ChatPaneAgentBridgeAccess, ChatPaneAgentControlOptions, ChatPaneAgentOption, ChatPaneAgentSelection, ChatPaneAgentToolAction, ChatPaneAgentToolDef, ChatPaneAgentToolInputSchema, ChatPaneAgentToolRisk, ChatPaneAttachmentUploadOptions, ChatPaneComposerHandle, ChatPaneProps, ChatPaneRunContext, ChatPaneRunContextInput, ChatPaneRuntimeAccess, ChatPaneVariant, ChatPaneWorkingDirectoryAccess, CreateDaemonAttachmentUploaderOptions, CreateMcpUiToolCallerOptions, McpUiToolCallRequest, RuntimePickerPlacement, UseChatPaneAgentControlOptions, UseChatPaneOptions, UseChatPaneResult, UseChatPaneRuntimeInventoryOptions, UseChatPaneRuntimeInventoryResult, UseChatPaneWorkingDirectoryOptions, UseChatPaneWorkingDirectoryResult, } from './chat-pane.js';
|
|
39
|
+
export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
|
|
40
|
+
export type { AgentRuntimePickerProps, ByokRuntimeSummary, ChatPaneActivity, ChatPaneAgent, ChatPaneAgentBridgeAccess, ChatPaneAgentControlOptions, ChatPaneAgentOption, ChatPaneAgentSelection, ChatPaneAgentToolAction, ChatPaneAgentToolDef, ChatPaneAgentToolInputSchema, ChatPaneAgentToolRisk, ChatPaneAttachmentUploadOptions, ChatPaneComposerHandle, ChatPaneProps, ChatPaneRunContext, ChatPaneRunContextInput, ChatPaneRuntimeAccess, ChatPaneVariant, ChatPaneWorkingDirectoryAccess, CreateDaemonAttachmentUploaderOptions, CreateMcpUiToolCallerOptions, CreateTypedAnswerPosterOptions, DeliverTypedAnswer, McpUiToolCallRequest, RuntimePickerPlacement, TypedAnswerDelivery, TypedAnswerNotice, UseChatPaneAgentControlOptions, UseChatPaneOptions, UseChatPaneResult, UseChatPaneRuntimeInventoryOptions, UseChatPaneRuntimeInventoryResult, UseChatPaneWorkingDirectoryOptions, UseChatPaneWorkingDirectoryResult, } from './chat-pane.js';
|
|
41
41
|
export { useRunStream, } from './hooks/useRunStream.js';
|
|
42
42
|
export type { RunStreamState, RunStreamStatus, StartRunOptions, UseRunStreamResult, } from './hooks/useRunStream.js';
|
|
43
43
|
export { useConversation, } from './hooks/useConversation.js';
|
|
@@ -48,6 +48,8 @@ export { useToolTimeline, } from './hooks/useToolTimeline.js';
|
|
|
48
48
|
export type { ToolTimelineRow, UseToolTimelineOptions, UseToolTimelineResult, } from './hooks/useToolTimeline.js';
|
|
49
49
|
export { useExtEventGroups, } from './hooks/useExtEventGroups.js';
|
|
50
50
|
export type { ExtEventGroup, } from './hooks/useExtEventGroups.js';
|
|
51
|
+
export { SYSTEM_SURFACE_EXPIRY_CLOCK, useSurfaceExpiry, } from './hooks/useSurfaceExpiry.js';
|
|
52
|
+
export type { SurfaceExpiryClock, } from './hooks/useSurfaceExpiry.js';
|
|
51
53
|
export { usePinnedTodos, } from './hooks/usePinnedTodos.js';
|
|
52
54
|
export type { UsePinnedTodosResult, } from './hooks/usePinnedTodos.js';
|
|
53
55
|
export { parseSubmittedAnswers, useQuestionForms, } from './hooks/useQuestionForms.js';
|
|
@@ -110,6 +112,12 @@ export type { TodoCardProps } from './components/TodoCard.js';
|
|
|
110
112
|
*/
|
|
111
113
|
export { ConversationList } from './components/ConversationList.js';
|
|
112
114
|
export type { ConversationListProps, ConversationListItem } from './components/ConversationList.js';
|
|
115
|
+
/**
|
|
116
|
+
* The conversation an operator last had open, per caller identity — so a host's switcher lands
|
|
117
|
+
* back on it after a reload instead of an empty new chat. See the module doc for the contract.
|
|
118
|
+
*/
|
|
119
|
+
export { createLastConversationStore, LAST_CONVERSATION_STORAGE_PREFIX } from './hooks/last-conversation-store.js';
|
|
120
|
+
export type { LastConversationStorage, LastConversationStore } from './hooks/last-conversation-store.js';
|
|
113
121
|
export { ToolCard } from './components/ToolCard.js';
|
|
114
122
|
export type { ToolCardProps } from './components/ToolCard.js';
|
|
115
123
|
export { A2uiSurfaceCard } from './components/A2uiSurfaceCard.js';
|
package/dist/react/index.js
CHANGED
|
@@ -26,12 +26,13 @@ export { clearExtEventRenderers, extEventSlot, getExtEventRenderer, registerExtE
|
|
|
26
26
|
* This alias has no removal date scheduled yet; see `chat-pane.ts` for the canonical surface and
|
|
27
27
|
* the module doc there for why the split exists.
|
|
28
28
|
*/
|
|
29
|
-
export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
|
|
29
|
+
export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
|
|
30
30
|
export { useRunStream, } from './hooks/useRunStream.js';
|
|
31
31
|
export { useConversation, } from './hooks/useConversation.js';
|
|
32
32
|
export { useComposer, } from './hooks/useComposer.js';
|
|
33
33
|
export { useToolTimeline, } from './hooks/useToolTimeline.js';
|
|
34
34
|
export { useExtEventGroups, } from './hooks/useExtEventGroups.js';
|
|
35
|
+
export { SYSTEM_SURFACE_EXPIRY_CLOCK, useSurfaceExpiry, } from './hooks/useSurfaceExpiry.js';
|
|
35
36
|
export { usePinnedTodos, } from './hooks/usePinnedTodos.js';
|
|
36
37
|
export { parseSubmittedAnswers, useQuestionForms, } from './hooks/useQuestionForms.js';
|
|
37
38
|
export { useArtifactStream, } from './hooks/useArtifactStream.js';
|
|
@@ -85,6 +86,11 @@ export { TodoCard } from './components/TodoCard.js';
|
|
|
85
86
|
* `leadingAccessory` slot; it needs no changes to `ChatPane` itself.
|
|
86
87
|
*/
|
|
87
88
|
export { ConversationList } from './components/ConversationList.js';
|
|
89
|
+
/**
|
|
90
|
+
* The conversation an operator last had open, per caller identity — so a host's switcher lands
|
|
91
|
+
* back on it after a reload instead of an empty new chat. See the module doc for the contract.
|
|
92
|
+
*/
|
|
93
|
+
export { createLastConversationStore, LAST_CONVERSATION_STORAGE_PREFIX } from './hooks/last-conversation-store.js';
|
|
88
94
|
export { ToolCard } from './components/ToolCard.js';
|
|
89
95
|
export { A2uiSurfaceCard } from './components/A2uiSurfaceCard.js';
|
|
90
96
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/chat",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
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": {
|
|
@@ -145,15 +145,15 @@
|
|
|
145
145
|
"registry": "https://registry.npmjs.org"
|
|
146
146
|
},
|
|
147
147
|
"dependencies": {
|
|
148
|
-
"@jini-ai/agentic": "^0.4.
|
|
148
|
+
"@jini-ai/agentic": "^0.4.2",
|
|
149
|
+
"@jini-ai/ui": "^0.4.4",
|
|
149
150
|
"@jini-ai/protocol": "^0.4.0",
|
|
150
|
-
"@jini-ai/core": "^0.4.
|
|
151
|
-
"@jini-ai/ui": "^0.4.2"
|
|
151
|
+
"@jini-ai/core": "^0.4.1"
|
|
152
152
|
},
|
|
153
153
|
"peerDependencies": {
|
|
154
154
|
"react": "^18.3.0 || ^19.0.0",
|
|
155
155
|
"react-dom": "^18.3.0 || ^19.0.0",
|
|
156
|
-
"@jini-ai/db": "^0.2.0",
|
|
156
|
+
"@jini-ai/db": "^0.2.0 || ^0.3.0",
|
|
157
157
|
"kysely": "^0.29.6",
|
|
158
158
|
"better-sqlite3": "^13.0.0",
|
|
159
159
|
"@electric-sql/pglite": "0.5.8",
|
|
@@ -238,8 +238,8 @@
|
|
|
238
238
|
"@electric-sql/pglite": "0.5.8",
|
|
239
239
|
"pg": "8.23.0",
|
|
240
240
|
"@ag-ui/core": "0.0.58",
|
|
241
|
-
"@jini-ai/
|
|
242
|
-
"@jini-ai/
|
|
241
|
+
"@jini-ai/db": "0.3.0",
|
|
242
|
+
"@jini-ai/agent-runtime": "0.4.0"
|
|
243
243
|
},
|
|
244
244
|
"typesVersions": {
|
|
245
245
|
"*": {
|