@jini-ai/chat 0.6.7 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +135 -1
  2. package/dist/core/accepted-input.d.ts +2 -0
  3. package/dist/core/accepted-input.js +34 -3
  4. package/dist/core/assistant-client/durable-run-subscription.d.ts +4 -2
  5. package/dist/core/assistant-client/durable-run-subscription.js +36 -7
  6. package/dist/core/assistant-client/transport.js +3 -1
  7. package/dist/core/persistence/title.d.ts +8 -16
  8. package/dist/core/persistence/title.js +20 -63
  9. package/dist/core/question-answer.js +2 -2
  10. package/dist/core/run-events/entry.d.ts +1 -1
  11. package/dist/core/run-events/entry.js +2 -1
  12. package/dist/core/turn-usage.js +19 -2
  13. package/dist/react/components/Markdown.js +2 -2
  14. package/dist/react/components/McpUiSurfaceCard.js +10 -7
  15. package/dist/react/components/MessageList.hooks.js +5 -1
  16. package/dist/react/components/MessageRow.d.ts +1 -1
  17. package/dist/react/components/MessageRow.js +41 -24
  18. package/dist/react/components/MessageRow.usage.d.ts +9 -1
  19. package/dist/react/components/MessageRow.usage.js +22 -5
  20. package/dist/react/components/ToolCallGroup.d.ts +4 -3
  21. package/dist/react/components/ToolCallGroup.hooks.d.ts +3 -1
  22. package/dist/react/components/ToolCallGroup.hooks.js +2 -1
  23. package/dist/react/components/ToolCallGroup.js +7 -5
  24. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +3 -3
  25. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -5
  26. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +69 -17
  27. package/dist/react/features/chat-pane/types.d.ts +9 -3
  28. package/dist/react/tool-call-display.d.ts +12 -6
  29. package/dist/react/tool-call-display.js +42 -15
  30. package/dist/server/durable-runs/recover.js +59 -23
  31. package/dist/store/sql/store.d.ts +2 -2
  32. package/dist/store/sql/store.js +11 -3
  33. package/package.json +15 -15
@@ -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'` — no tool cards at all, EXCEPT a failed one: an error must never vanish from the
10
- * transcript just because the reader asked for a quieter view.
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 is a failure the reader must always see: the tool returned an error, or the run
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 only failed rows and drops `tools` blocks left empty.
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
- * The flat layout's counterpart (used when `interleaveMessageBlocks` refused): `'hidden'` keeps only
57
- * failed rows; the other modes keep every row (`'collapsed'` folds them into one line at render).
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 is a failure the reader must always see: the tool returned an error, or the run
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 only failed rows and drops `tools` blocks left empty.
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
- /** `'hidden'`: keeps only failed rows and drops `tools` blocks left empty. */
36
- function hideToolCalls({ blocks }) {
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
- for (const block of blocks) {
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 failed = block.rows.filter((row) => isFailedToolRow({ row }));
44
- if (failed.length > 0)
45
- out.push({ kind: 'tools', rows: failed, key: block.key });
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
- * The flat layout's counterpart (used when `interleaveMessageBlocks` refused): `'hidden'` keeps only
69
- * failed rows; the other modes keep every row (`'collapsed'` folds them into one line at render).
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
- return mode === 'hidden' ? rows.filter((row) => isFailedToolRow({ row })) : rows;
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
@@ -17,22 +17,29 @@ function lastIndexWhere({ events, match }, _optional = {}) {
17
17
  return index;
18
18
  return -1;
19
19
  }
20
- /** One saved event's contribution to the executor diagnostics. */
21
- function diagnosticLines({ event }, _optional = {}) {
22
- if (event.kind === "status")
23
- return [event.label, event.detail ?? ""];
24
- if (event.kind !== "raw")
25
- return [];
20
+ /** One complete stdout line's contribution to executor diagnostics. */
21
+ function diagnosticLine({ line }, _optional = {}) {
26
22
  try {
27
- const result = JSON.parse(event.line);
23
+ const result = JSON.parse(line);
28
24
  // Successful CLI summaries contain usage metadata and answer prose, not failure evidence.
29
25
  if (result?.type === "result" && result.is_error === false)
30
26
  return [];
27
+ // A subagent report can discuss billing/auth in completed work; it is not executor evidence.
28
+ if (result?.type === "system" && result.subtype === "task_notification")
29
+ return [];
31
30
  }
32
31
  catch {
33
32
  // Non-JSON stdout can still contain the executor's failure reason.
34
33
  }
35
- return [event.line];
34
+ return [line];
35
+ }
36
+ /** A saved raw event may hold several JSONL lines; filter each before failure classification. */
37
+ function diagnosticLines({ event }, _optional = {}) {
38
+ if (event.kind === "status")
39
+ return [event.label, event.detail ?? ""];
40
+ if (event.kind !== "raw")
41
+ return [];
42
+ return event.line.split(/\r?\n/).flatMap(line => diagnosticLine({ line }));
36
43
  }
37
44
  /** Retryability is independent of session resumability. Inspect executor diagnostics, never
38
45
  * answer text or tool results: those can quote unrelated failures from completed work. Old
@@ -118,8 +125,8 @@ function savedWork({ run }, _optional = {}) {
118
125
  return saved;
119
126
  }
120
127
  /** A dead attempt continues now; a fresh queued one waits (see the caller's comment). */
121
- function continuesNow({ run, probe, trigger, now }, _optional = {}) {
122
- return probe === "dead" && (trigger === "attempt-failed" || now - run.attemptStartedAt >= 10_000);
128
+ function continuesNow({ run, trigger, now }, _optional = {}) {
129
+ return trigger === "attempt-failed" || now - run.attemptStartedAt >= 10_000;
123
130
  }
124
131
  /** Recovery coordinator behind explicit host ports. One CAS owns both the attempt and its conversation slot.
125
132
  * Each recovery decision logs one `logger.info` line (default console) of run metadata. */
@@ -193,37 +200,66 @@ export function createDurableRecovery(ports, { logger = console } = {}) {
193
200
  if (exclusion(run))
194
201
  return { decision: await finalize(run, STOPPED_NOTICE, { canceled: true }) };
195
202
  const probe = await probeAttempt({ run, trigger, liveRunId });
203
+ // HTTP/storage awaits can cross cancellation, settlement, or an attempt-generation change.
204
+ // Re-read before attaching or applying diagnostics to the probed attempt.
205
+ const current = await reload(run);
206
+ if (typeof current === "string")
207
+ return { probe, decision: current };
196
208
  if (probe === "live")
197
- return { probe, decision: await reattach(run) };
209
+ return { probe, decision: await reattach(current) };
210
+ if (probe === "uncertain") {
211
+ // Transport uncertainty is not proof of death and spends no restart/time budget. The
212
+ // existing watcher waits and probes again; even an expired budget cannot kill a live child.
213
+ await reattach(current);
214
+ return { probe, decision: "waiting" };
215
+ }
216
+ return decideDead(current, trigger);
217
+ }
218
+ async function decideDead(run, trigger) {
219
+ const probe = "dead";
198
220
  // A pending human answer takes precedence over executor exit diagnostics.
199
- if (probe === "dead" && awaitingAnswer({ run }))
200
- return { probe, decision: await resolveMissing(run, probe, trigger) };
221
+ if (awaitingAnswer({ run }))
222
+ return { probe, decision: await resolveMissing(run, trigger) };
201
223
  // Diagnostics decide only the fate of an attempt that is no longer running. Read before the
202
224
  // probe, raw stdout quoting a tool description ("OAuth-authenticated") canceled a live turn
203
225
  // as "Not logged in" (luvira, 2026-10-08).
204
226
  const diagnostics = attemptDiagnostics({ run });
205
227
  const failure = attemptFailure({ diagnostics });
206
- const decision = failure === "retryable" ? await resolveMissing(run, probe, trigger) : await finalizeFailure({ run, failure, diagnostics });
228
+ const decision = failure === "retryable" ? await resolveMissing(run, trigger) : await finalizeFailure({ run, failure, diagnostics });
207
229
  return { probe, failure, decision };
208
230
  }
209
- async function resolveMissing(run, probe, trigger) {
231
+ async function resolveMissing(run, trigger) {
210
232
  await ports.store.recoveryClock({ runId: run.runId, now: ports.now(), active: true }, {});
211
- const timed = await ports.store.load({ messageId: run.messageId }, {}) ?? run;
233
+ const timed = await reload(run);
234
+ return typeof timed === "string" ? timed : resolveDead(timed, trigger);
235
+ }
236
+ /** The saved row after an await, or the outcome that ends this decision. O(1) store reads. */
237
+ async function reload(run) {
238
+ const current = await ports.store.load({ messageId: run.messageId }, {});
239
+ if (!current)
240
+ return "gone";
212
241
  // A probe only speaks about the generation it inspected. Never apply its dead result to
213
242
  // a newer attempt committed by another caller while this one was awaiting storage/HTTP.
214
- if (timed.runId !== run.runId)
243
+ if (current.runId !== run.runId)
215
244
  return "superseded";
245
+ if (current.message.runStatus !== "queued" && current.message.runStatus !== "running")
246
+ return "terminal";
247
+ if (exclusion(current))
248
+ return finalize(current, STOPPED_NOTICE, { canceled: true });
249
+ return current;
250
+ }
251
+ async function resolveDead(run, trigger) {
216
252
  // A dead process cannot resume its question. End this turn with the existing card intact,
217
253
  // rather than minting an attempt that runs preflight and asks the same question again.
218
- if (probe === "dead" && awaitingAnswer({ run: timed }))
219
- return finalize(timed, AWAITING_ANSWER_NOTICE);
220
- const notice = budgetNotice(timed, ports.now());
254
+ if (awaitingAnswer({ run }))
255
+ return finalize(run, AWAITING_ANSWER_NOTICE);
256
+ const notice = budgetNotice(run, ports.now());
221
257
  if (notice)
222
- return finalize(run, notice, { detail: notice === EXHAUSTED_NOTICE ? exhaustedDetail({ run: timed }) : undefined });
258
+ return finalize(run, notice, { detail: notice === EXHAUSTED_NOTICE ? exhaustedDetail({ run }) : undefined });
223
259
  // A queued attempt may be between durable acceptance and the daemon's start transaction.
224
260
  // Give that idempotent launch time to appear before a 404 can advance its generation.
225
- if (continuesNow({ run, probe, trigger, now: ports.now() }))
226
- return continueDead(timed);
261
+ if (continuesNow({ run, trigger, now: ports.now() }))
262
+ return continueDead(run);
227
263
  ports.attach(run, {});
228
264
  return "waiting";
229
265
  }
@@ -28,8 +28,8 @@ export declare function toMessage(row: MessageRow): ChatMessage;
28
28
  * @param scope the isolation predicate, resolved from the host's authentication. For a guest,
29
29
  * `ownerId` must already be a hash of the session key (`tenant-scope.ts`).
30
30
  * @param optional Optional core Clock for deterministic host/tests; defaults to system wall time.
31
- * @complexity each method is one to four indexed statements; `list` is O(owned conversations),
32
- * `messages` O(messages in the conversation).
31
+ * @complexity each method is one to four indexed statements; conversation summaries scan their
32
+ * messages and embedded events in SQL; `messages` is O(messages in the conversation).
33
33
  */
34
34
  export declare function createSqlChatStore<DB extends ChatDatabase>({ kernel, scope: inputScope }: {
35
35
  kernel: StorageKernel<DB>;
@@ -13,6 +13,7 @@ import { sql } from 'kysely';
13
13
  import { createSystemClock } from '@jini-ai/core/primitives';
14
14
  import { isUniqueViolation } from '@jini-ai/db/kernel';
15
15
  import { redactUserMessage } from '../../core/user-text-redaction.js';
16
+ import { MID_RUN_USER_MESSAGE_EVENT_NAME } from '../../core/turn-usage.js';
16
17
  import { ChatStoreError } from '../errors.js';
17
18
  import { copyScope, atStoreBoundary, runChatQuery } from './boundary.js';
18
19
  import { pageLimit, decodeCursor, encodeCursor } from './cursor.js';
@@ -113,8 +114,8 @@ function toHistoryMessage(row, { includeToolEvents }) {
113
114
  * @param scope the isolation predicate, resolved from the host's authentication. For a guest,
114
115
  * `ownerId` must already be a hash of the session key (`tenant-scope.ts`).
115
116
  * @param optional Optional core Clock for deterministic host/tests; defaults to system wall time.
116
- * @complexity each method is one to four indexed statements; `list` is O(owned conversations),
117
- * `messages` O(messages in the conversation).
117
+ * @complexity each method is one to four indexed statements; conversation summaries scan their
118
+ * messages and embedded events in SQL; `messages` is O(messages in the conversation).
118
119
  */
119
120
  export function createSqlChatStore({ kernel, scope: inputScope }, { clock = createSystemClock(), ...redactionOptions } = {}) {
120
121
  const scope = copyScope({ scope: inputScope });
@@ -122,6 +123,13 @@ export function createSqlChatStore({ kernel, scope: inputScope }, { clock = crea
122
123
  // The single subset boundary: DB must contain both required table definitions. Extra host tables
123
124
  // stay on the exact borrowed kernel; its transaction context is never projected or recreated.
124
125
  const run = (body) => runChatQuery({ kernel: kernel, body: body });
126
+ // Count embedded user messages in SQL: list/get still return summaries, never whole transcripts.
127
+ // Writers store `JSON.stringify` output (no whitespace, as appendedEventsJson also relies on), so
128
+ // each mid-run event holds this exact text once; text inside a JSON string has escaped quotes and
129
+ // cannot match, and invalid JSON stays readable (it counts what it holds, never throws). Plain
130
+ // length/replace run on SQLite and PostgreSQL 14+, unlike `IS JSON` (PostgreSQL 16+).
131
+ const needle = `"name":"${MID_RUN_USER_MESSAGE_EVENT_NAME}"`;
132
+ const midRunCount = sql `((length(coalesce(m.events_json, '')) - length(replace(coalesce(m.events_json, ''), ${sql.lit(needle)}, ''))) / ${sql.lit(needle.length)})`;
125
133
  /** This scope's conversations, projected as {@link ConversationRow}s with their message count. */
126
134
  function ownedConversations(db) {
127
135
  return db
@@ -135,7 +143,7 @@ export function createSqlChatStore({ kernel, scope: inputScope }, { clock = crea
135
143
  "c.expires_at",
136
144
  eb
137
145
  .selectFrom("ai_chat_messages as m")
138
- .select((sb) => sb.fn.countAll().as("n"))
146
+ .select(sql `coalesce(sum(1 + case when m.role = 'assistant' then ${midRunCount} else 0 end), 0)`.as("n"))
139
147
  .whereRef("m.conversation_id", "=", "c.id")
140
148
  .as("message_count"),
141
149
  ])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/chat",
3
- "version": "0.6.7",
3
+ "version": "0.6.11",
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.4.9"
199
+ "@jini-ai/core": "^0.6.11"
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.3.2",
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.4.5",
217
- "@jini-ai/agentic": "^0.4.4",
218
- "@jini-ai/protocol": "^0.4.4",
219
- "@jini-ai/ui": "^0.5.0",
220
- "@jini-ai/diagnostics": "^0.5.2"
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.4.5",
307
- "@jini-ai/agent-runtime": "0.4.12",
308
- "@jini-ai/protocol": "^0.4.5",
309
- "@jini-ai/core": "^0.4.9",
310
- "@jini-ai/db": "0.3.6",
311
- "@jini-ai/diagnostics": "^0.5.4",
312
- "@jini-ai/ui": "^0.5.6"
306
+ "@jini-ai/db": "0.6.11",
307
+ "@jini-ai/agent-runtime": "0.6.11",
308
+ "@jini-ai/protocol": "^0.6.11",
309
+ "@jini-ai/core": "^0.6.11",
310
+ "@jini-ai/diagnostics": "^0.6.11",
311
+ "@jini-ai/ui": "^0.6.11",
312
+ "@jini-ai/agentic": "^0.6.11"
313
313
  },
314
314
  "typesVersions": {
315
315
  "*": {