@jini-ai/chat 0.6.10 → 0.6.12
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 +17 -0
- package/dist/core/accepted-input.d.ts +2 -0
- package/dist/core/accepted-input.js +34 -3
- package/dist/core/assistant-client/durable-run-subscription.d.ts +4 -2
- package/dist/core/assistant-client/durable-run-subscription.js +36 -7
- package/dist/core/persistence/title.d.ts +8 -16
- package/dist/core/persistence/title.js +20 -63
- package/dist/core/question-answer.js +2 -2
- package/dist/core/run-activity.js +4 -0
- package/dist/core/run-events/entry.d.ts +1 -1
- package/dist/core/run-events/entry.js +2 -1
- package/dist/core/turn-usage.js +19 -2
- package/dist/react/components/Markdown.js +2 -2
- package/dist/react/components/McpUiSurfaceCard.js +10 -7
- package/dist/react/components/MessageList.hooks.js +5 -1
- package/dist/react/components/MessageRow.js +13 -8
- package/dist/react/components/MessageRow.usage.d.ts +9 -1
- package/dist/react/components/MessageRow.usage.js +22 -5
- package/dist/react/components/MidRunUserMessage.d.ts +1 -1
- package/dist/react/components/MidRunUserMessage.js +8 -5
- package/dist/react/components/ToolCallGroup.hooks.d.ts +3 -1
- package/dist/react/components/ToolCallGroup.hooks.js +2 -1
- package/dist/react/components/ToolCallGroup.js +1 -1
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +3 -3
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -5
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +88 -27
- package/dist/react/features/chat-pane/rules.d.ts +5 -2
- package/dist/react/features/chat-pane/rules.js +8 -2
- package/dist/react/features/chat-pane/types.d.ts +7 -2
- package/dist/server/durable-runs/recover.js +59 -23
- package/dist/store/sql/store.d.ts +2 -2
- package/dist/store/sql/store.js +11 -3
- package/package.json +9 -9
|
@@ -17,22 +17,29 @@ function lastIndexWhere({ events, match }, _optional = {}) {
|
|
|
17
17
|
return index;
|
|
18
18
|
return -1;
|
|
19
19
|
}
|
|
20
|
-
/** One
|
|
21
|
-
function
|
|
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(
|
|
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 [
|
|
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,
|
|
122
|
-
return
|
|
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(
|
|
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 (
|
|
200
|
-
return { probe, decision: await resolveMissing(run,
|
|
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,
|
|
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,
|
|
231
|
+
async function resolveMissing(run, trigger) {
|
|
210
232
|
await ports.store.recoveryClock({ runId: run.runId, now: ports.now(), active: true }, {});
|
|
211
|
-
const timed = await
|
|
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 (
|
|
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 (
|
|
219
|
-
return finalize(
|
|
220
|
-
const notice = budgetNotice(
|
|
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
|
|
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,
|
|
226
|
-
return continueDead(
|
|
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;
|
|
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>;
|
package/dist/store/sql/store.js
CHANGED
|
@@ -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;
|
|
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((
|
|
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.
|
|
3
|
+
"version": "0.6.12",
|
|
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,7 +196,7 @@
|
|
|
196
196
|
"registry": "https://registry.npmjs.org"
|
|
197
197
|
},
|
|
198
198
|
"dependencies": {
|
|
199
|
-
"@jini-ai/core": "^0.6.
|
|
199
|
+
"@jini-ai/core": "^0.6.12"
|
|
200
200
|
},
|
|
201
201
|
"peerDependencies": {
|
|
202
202
|
"react": "^18.3.0 || ^19.0.0",
|
|
@@ -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/
|
|
307
|
-
"@jini-ai/
|
|
308
|
-
"@jini-ai/agent-runtime": "0.6.
|
|
309
|
-
"@jini-ai/
|
|
310
|
-
"@jini-ai/
|
|
311
|
-
"@jini-ai/
|
|
312
|
-
"@jini-ai/protocol": "^0.6.
|
|
306
|
+
"@jini-ai/db": "0.6.12",
|
|
307
|
+
"@jini-ai/agentic": "^0.6.12",
|
|
308
|
+
"@jini-ai/agent-runtime": "0.6.12",
|
|
309
|
+
"@jini-ai/diagnostics": "^0.6.12",
|
|
310
|
+
"@jini-ai/ui": "^0.6.12",
|
|
311
|
+
"@jini-ai/core": "^0.6.12",
|
|
312
|
+
"@jini-ai/protocol": "^0.6.12"
|
|
313
313
|
},
|
|
314
314
|
"typesVersions": {
|
|
315
315
|
"*": {
|