@jini-ai/chat 0.5.5 → 0.5.7

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 (51) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/core/durable-projection.d.ts +3 -1
  3. package/dist/core/durable-projection.js +40 -7
  4. package/dist/core/index.d.ts +1 -0
  5. package/dist/core/index.js +1 -0
  6. package/dist/core/run-events/entry.d.ts +12 -1
  7. package/dist/core/run-events/entry.js +17 -6
  8. package/dist/core/tool-call-detail.d.ts +36 -0
  9. package/dist/core/tool-call-detail.js +49 -0
  10. package/dist/core/turn-usage.d.ts +11 -0
  11. package/dist/core/turn-usage.js +17 -0
  12. package/dist/react/chat-pane.d.ts +2 -2
  13. package/dist/react/chat-pane.js +1 -1
  14. package/dist/react/components/Composer.d.ts +2 -0
  15. package/dist/react/components/Composer.js +5 -2
  16. package/dist/react/components/Markdown.js +6 -6
  17. package/dist/react/components/MessageList.d.ts +7 -12
  18. package/dist/react/components/MessageList.js +20 -2
  19. package/dist/react/components/MessageRow.d.ts +6 -1
  20. package/dist/react/components/MessageRow.js +10 -4
  21. package/dist/react/components/MidRunUserMessage.d.ts +27 -0
  22. package/dist/react/components/MidRunUserMessage.js +39 -0
  23. package/dist/react/components/ToolCard.js +33 -22
  24. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  25. package/dist/react/features/chat-pane/components/ChatPane.js +11 -3
  26. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +44 -0
  27. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +69 -0
  28. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +3 -1
  29. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +69 -11
  30. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.d.ts +25 -0
  31. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +70 -0
  32. package/dist/react/features/chat-pane/index.d.ts +2 -1
  33. package/dist/react/features/chat-pane/index.js +1 -0
  34. package/dist/react/features/chat-pane/model-options.d.ts +3 -1
  35. package/dist/react/features/chat-pane/model-options.js +6 -2
  36. package/dist/react/features/chat-pane/rules.d.ts +13 -0
  37. package/dist/react/features/chat-pane/rules.js +14 -0
  38. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  39. package/dist/react/features/chat-pane/styles.js +7 -0
  40. package/dist/react/features/chat-pane/types.d.ts +23 -0
  41. package/dist/react/hooks/composer-draft-cache.d.ts +15 -0
  42. package/dist/react/hooks/composer-draft-cache.js +20 -0
  43. package/dist/react/hooks/useComposer.d.ts +14 -0
  44. package/dist/react/hooks/useComposer.js +78 -6
  45. package/dist/react/index.d.ts +4 -2
  46. package/dist/react/index.js +3 -1
  47. package/dist/server/durable-runs/recover.js +19 -11
  48. package/dist/server/run-finalizer/entry.js +2 -1
  49. package/dist/store/runtime/durable-run-store.js +5 -2
  50. package/dist/store/runtime/run-ledger.js +5 -2
  51. package/package.json +9 -9
@@ -29,20 +29,89 @@
29
29
  */
30
30
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
31
31
  import { useComposerHistory } from './useComposerHistory.hooks.js';
32
- import { readCachedAttachments, readCachedDraft, writeCachedAttachments, writeCachedDraft, } from './composer-draft-cache.js';
32
+ import { readCachedAttachments, readCachedDraft, rememberCachedDraft, writeCachedAttachments, writeCachedDraft, } from './composer-draft-cache.js';
33
33
  import { cacheAttachmentPreviewSource } from './attachment-preview-cache.js';
34
34
  const EMPTY_MENTION = { open: false, query: '', results: [] };
35
+ /**
36
+ * How long typing must pause before the draft reaches `localStorage` and the host's persistence
37
+ * port. Those writes used to run synchronously on every keystroke (owner report 2026-10-08: typing
38
+ * lagged in a long conversation). The in-memory cache tier still updates per keystroke, and every
39
+ * exit path (send, conversation switch, unmount, page hide, blur) writes or discards immediately,
40
+ * so the delay can only cost a draft on a hard crash inside this window.
41
+ */
42
+ export const DRAFT_PERSIST_DEBOUNCE_MS = 800;
43
+ /**
44
+ * One pending durable draft write at a time, trailing-edge debounced. Flushes on unmount and when
45
+ * the page is hidden (`pagehide`, or `visibilitychange` to hidden — the last event a mobile browser
46
+ * reliably delivers before discarding the tab).
47
+ * @complexity Time/space: O(1) per call.
48
+ */
49
+ function useDeferredDraftWrite() {
50
+ const pendingRef = useRef(null);
51
+ const cancel = useCallback(() => {
52
+ const pending = pendingRef.current;
53
+ if (!pending)
54
+ return;
55
+ pendingRef.current = null;
56
+ clearTimeout(pending.timer);
57
+ }, []);
58
+ const flush = useCallback(() => {
59
+ const pending = pendingRef.current;
60
+ if (!pending)
61
+ return;
62
+ pendingRef.current = null;
63
+ clearTimeout(pending.timer);
64
+ pending.write();
65
+ }, []);
66
+ const schedule = useCallback((write) => {
67
+ if (pendingRef.current)
68
+ clearTimeout(pendingRef.current.timer);
69
+ const timer = setTimeout(() => {
70
+ pendingRef.current = null;
71
+ write();
72
+ }, DRAFT_PERSIST_DEBOUNCE_MS);
73
+ pendingRef.current = { timer, write };
74
+ }, []);
75
+ useEffect(() => {
76
+ if (typeof window === 'undefined')
77
+ return flush;
78
+ const onVisibilityChange = () => {
79
+ if (document.visibilityState === 'hidden')
80
+ flush();
81
+ };
82
+ window.addEventListener('pagehide', flush);
83
+ document.addEventListener('visibilitychange', onVisibilityChange);
84
+ return () => {
85
+ window.removeEventListener('pagehide', flush);
86
+ document.removeEventListener('visibilitychange', onVisibilityChange);
87
+ flush();
88
+ };
89
+ }, [flush]);
90
+ return useMemo(() => ({ schedule, flush, cancel }), [schedule, flush, cancel]);
91
+ }
35
92
  export function useComposer(options = {}) {
36
93
  const { project, composerSlots, persistence, conversationId, validateAttachments } = options;
37
94
  const [draft, setDraftState] = useState(() => options.initialDraft ?? persistence?.read() ?? readCachedDraft({ conversationId: conversationId }) ?? '');
38
95
  const [attachments, setAttachments] = useState([]);
39
96
  const [agent, setAgent] = useState(options.initialAgent);
40
97
  const [mention, setMention] = useState(EMPTY_MENTION);
98
+ const deferredWrite = useDeferredDraftWrite();
41
99
  const commitDraft = useCallback((next) => {
42
100
  setDraftState(next);
43
- persistence?.write(next);
44
- writeCachedDraft({ conversationId: conversationId, draft: next });
45
- }, [persistence, conversationId]);
101
+ rememberCachedDraft({ conversationId: conversationId, draft: next });
102
+ const write = () => {
103
+ persistence?.write(next);
104
+ writeCachedDraft({ conversationId: conversationId, draft: next });
105
+ };
106
+ // A cleared draft (send, reset) is written at once and drops any pending write, so a stale
107
+ // timer can never put the sent text back.
108
+ if (next.trim() === '') {
109
+ deferredWrite.cancel();
110
+ write();
111
+ return;
112
+ }
113
+ deferredWrite.schedule(write);
114
+ }, [persistence, conversationId, deferredWrite]);
46
115
  const history = useComposerHistory({ setRecalledDraft: setDraftState }, {
47
116
  ...(options.historyMessages === undefined ? {} : { messages: options.historyMessages }),
48
117
  ...(options.historyScope === undefined ? {} : { scope: options.historyScope }),
@@ -68,6 +137,8 @@ export function useComposer(options = {}) {
68
137
  if (conversationId === previousConversationIdRef.current)
69
138
  return;
70
139
  previousConversationIdRef.current = conversationId;
140
+ // The pending write closed over the conversation being left, so this lands under its id.
141
+ deferredWrite.flush();
71
142
  const restored = readCachedDraft({ conversationId: conversationId });
72
143
  // An id arriving where there was none is NOT a switch between two conversations — it is the one
73
144
  // the operator is already looking at finally getting a key (a new chat created server-side
@@ -85,7 +156,7 @@ export function useComposer(options = {}) {
85
156
  setDraftState(restored ?? '');
86
157
  setAttachments([]);
87
158
  setMention(EMPTY_MENTION);
88
- }, [conversationId]);
159
+ }, [conversationId, deferredWrite]);
89
160
  // Held in a ref so an inline arrow from the host does not re-run the restore below on every
90
161
  // render. Reassigned each render so a host that swaps implementations still gets the new one.
91
162
  const validateAttachmentsRef = useRef(validateAttachments);
@@ -221,6 +292,7 @@ export function useComposer(options = {}) {
221
292
  selectMention,
222
293
  canSubmit,
223
294
  reset,
224
- }), [draft, history, setDraft, attachments, addAttachments, addAttachment, removeAttachment, clearAttachments, agent, mention, openMention, closeMention, selectMention, canSubmit, reset]);
295
+ flushDraft: deferredWrite.flush,
296
+ }), [draft, history, setDraft, attachments, addAttachments, addAttachment, removeAttachment, clearAttachments, agent, mention, openMention, closeMention, selectMention, canSubmit, reset, deferredWrite.flush]);
225
297
  }
226
298
  //# sourceMappingURL=useComposer.js.map
@@ -18,8 +18,8 @@ export type { ExtEventRenderProps, ExtEventRenderer, ExtEventRendererOptions, Ex
18
18
  * This alias has no removal date scheduled yet; see `chat-pane.ts` for the canonical surface and
19
19
  * the module doc there for why the split exists.
20
20
  */
21
- export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
22
- 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';
21
+ export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createMidRunMessagePoster, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
22
+ export type { AgentRuntimePickerProps, ByokRuntimeSummary, ChatPaneActivity, ChatPaneAgent, ChatPaneAgentBridgeAccess, ChatPaneAgentControlOptions, ChatPaneAgentOption, ChatPaneAgentSelection, ChatPaneAgentToolAction, ChatPaneAgentToolDef, ChatPaneAgentToolInputSchema, ChatPaneAgentToolRisk, ChatPaneAttachmentUploadOptions, ChatPaneComposerHandle, ChatPaneProps, ChatPaneRunContext, ChatPaneRunContextInput, ChatPaneRuntimeAccess, ChatPaneVariant, ChatPaneWorkingDirectoryAccess, CreateDaemonAttachmentUploaderOptions, CreateMcpUiToolCallerOptions, CreateMidRunMessagePosterOptions, CreateTypedAnswerPosterOptions, DeliverMidRunMessage, DeliverTypedAnswer, McpUiToolCallRequest, MidRunMessageDelivery, RuntimePickerPlacement, TypedAnswerDelivery, TypedAnswerNotice, UseChatPaneAgentControlOptions, UseChatPaneOptions, UseChatPaneResult, UseChatPaneRuntimeInventoryOptions, UseChatPaneRuntimeInventoryResult, UseChatPaneWorkingDirectoryOptions, UseChatPaneWorkingDirectoryResult, } from './chat-pane.js';
23
23
  export { useRunStream, } from './hooks/useRunStream.js';
24
24
  export type { RunStreamState, RunStreamStatus, StartRunOptions, UseRunStreamResult, } from './hooks/useRunStream.js';
25
25
  export { useConversation, } from './hooks/useConversation.js';
@@ -111,6 +111,8 @@ export type { A2uiAgentActionOutcome, A2uiSurfaceCardProps } from './components/
111
111
  */
112
112
  export { McpUiSurfaceCard, mcpUiSurfaceSlotKey, registerMcpUiSurfaceRenderer, MCP_UI_EXT_EVENT_NAME } from './components/McpUiSurfaceCard.js';
113
113
  export type { McpUiSurfaceCardProps } from './components/McpUiSurfaceCard.js';
114
+ /** A message sent mid-run (`ChatPane`'s `deliverMidRunMessage`), drawn as a user bubble inside the running turn. */
115
+ export { MidRunUserMessage, midRunUserMessageSlotKey, registerMidRunUserMessageRenderer, MID_RUN_USER_MESSAGE_EXT_EVENT_NAME } from './components/MidRunUserMessage.js';
114
116
  export { ExtEventErrorBoundary } from './components/ExtEventErrorBoundary.js';
115
117
  export type { ExtEventErrorBoundaryProps } from './components/ExtEventErrorBoundary.js';
116
118
  export { QuestionForm } from './components/QuestionForm.js';
@@ -26,7 +26,7 @@ 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, createTypedAnswerPoster, defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, useChatPane, useChatPaneAgentControl, useChatPaneRuntimeInventory, useChatPaneWorkingDirectory, } from './chat-pane.js';
29
+ export { AgentRuntimePicker, CHAT_PANE_AGENT_TOOLS, ChatPane, createDaemonAttachmentUploader, createMcpUiToolCaller, createMidRunMessagePoster, 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';
@@ -99,6 +99,8 @@ export { A2uiSurfaceCard } from './components/A2uiSurfaceCard.js';
99
99
  * resources instead of A2UI surfaces.
100
100
  */
101
101
  export { McpUiSurfaceCard, mcpUiSurfaceSlotKey, registerMcpUiSurfaceRenderer, MCP_UI_EXT_EVENT_NAME } from './components/McpUiSurfaceCard.js';
102
+ /** A message sent mid-run (`ChatPane`'s `deliverMidRunMessage`), drawn as a user bubble inside the running turn. */
103
+ export { MidRunUserMessage, midRunUserMessageSlotKey, registerMidRunUserMessageRenderer, MID_RUN_USER_MESSAGE_EXT_EVENT_NAME } from './components/MidRunUserMessage.js';
102
104
  export { ExtEventErrorBoundary } from './components/ExtEventErrorBoundary.js';
103
105
  export { QuestionForm } from './components/QuestionForm.js';
104
106
  export { QuestionsPanel } from './components/QuestionsPanel.js';
@@ -6,7 +6,7 @@ export { RECOVERY_LIMIT, RECOVERY_WINDOW_MS, RUN_STALENESS_MS, SAVED_WORK_NOTICE
6
6
  /** Retryability is independent of session resumability. Inspect executor diagnostics, never
7
7
  * answer text or tool results: those can quote unrelated failures from completed work. Old
8
8
  * attempt diagnostics remain visible but cannot decide the fate of their continuation. */
9
- function attemptFailure(run) {
9
+ function attemptDiagnostics({ run }, _optional = {}) {
10
10
  const events = run.message.events ?? [];
11
11
  // Checkpoints merge adjacent text, so a base's event count is not a durable offset. The
12
12
  // coordinator's recovery marker separates attempts and survives that compaction.
@@ -19,7 +19,10 @@ function attemptFailure(run) {
19
19
  break;
20
20
  }
21
21
  }
22
- const diagnostics = events.slice(marker >= 0 ? marker + 1 : run.attemptBase.length).flatMap((event) => event.kind === "status" ? [event.label, event.detail ?? ""] : event.kind === "raw" ? [event.line] : []).join("\n");
22
+ return events.slice(marker >= 0 ? marker + 1 : run.attemptBase.length).flatMap((event) => event.kind === "status" ? [event.label, event.detail ?? ""] : event.kind === "raw" ? [event.line] : []).join("\n");
23
+ }
24
+ function attemptFailure(run) {
25
+ const diagnostics = attemptDiagnostics({ run }, {});
23
26
  if (/inactivity[_ -]?(timeout|watchdog)|watchdog.*(kill|stop)/i.test(diagnostics))
24
27
  return "inactivity-watchdog";
25
28
  const service = classifyAgentServiceFailure({ text: diagnostics });
@@ -34,13 +37,11 @@ function attemptFailure(run) {
34
37
  return "permanent";
35
38
  return "retryable";
36
39
  }
37
- function exclusion(run, failure) {
40
+ function exclusion(run) {
38
41
  if (run.cancelReason)
39
42
  return run.cancelReason;
40
43
  if (!run.principalId || run.engine !== "daemon")
41
44
  return "excluded";
42
- if (failure === "auth-failure" || failure === "inactivity-watchdog")
43
- return failure;
44
45
  return null;
45
46
  }
46
47
  function budgetNotice(run, now) {
@@ -71,12 +72,15 @@ export function createDurableRecovery(ports, _optional = {}) {
71
72
  }
72
73
  async function continueDead(run) {
73
74
  await ports.cancelAttempt(run, {}).catch(() => undefined);
74
- const native = await nativeAllowed(run);
75
+ // A locator captured before a hard cancel can outlive its rollout. Reuse reconstruction,
76
+ // including prior turns/tool outcomes, instead of retrying a known missing native session.
77
+ const missingResume = run.request.agentId === 'codex' && /no rollout found for thread id/.test(attemptDiagnostics({ run }, {}));
78
+ const native = !missingResume && await nativeAllowed(run);
75
79
  const nextRunId = ports.mintRunId();
76
80
  const saved = [...(run.message.events ?? [])];
77
81
  if (!saved.some((event) => event.kind === "text") && run.message.content)
78
82
  saved.unshift({ kind: "text", text: run.message.content });
79
- const events = recoveredRunEvents({ saved }, {});
83
+ const events = recoveredRunEvents({ saved }, { notice: missingResume ? 'silent' : 'visible' });
80
84
  if (!await ports.store.advance({ run, nextRunId, now: ports.now(), events }, {}))
81
85
  return "superseded";
82
86
  const next = await ports.store.load({ messageId: run.messageId }, {});
@@ -98,10 +102,8 @@ export function createDurableRecovery(ports, _optional = {}) {
98
102
  return "continued";
99
103
  }
100
104
  async function decide(run, trigger, liveRunId) {
101
- const failure = attemptFailure(run);
102
- const reason = exclusion(run, failure);
103
- if (reason)
104
- return finalize(run, reason === "auth-failure" ? "Not logged in. Saved work is above." : "Stopped. Saved work is above.", true);
105
+ if (exclusion(run))
106
+ return finalize(run, "Stopped. Saved work is above.", true);
105
107
  // A terminal executor end confirms this attempt is dead even when the daemon retains its
106
108
  // replayable record (HTTP 200). Other triggers must obtain fresh liveness evidence.
107
109
  const probe = trigger === "attempt-failed" ? "dead" : liveRunId === run.runId ? "live" : await ports.probe(run, {});
@@ -110,6 +112,12 @@ export function createDurableRecovery(ports, _optional = {}) {
110
112
  ports.attach(run, {});
111
113
  return "reattached";
112
114
  }
115
+ // Diagnostics decide only the fate of an attempt that is no longer running. Read before the
116
+ // probe, raw stdout quoting a tool description ("OAuth-authenticated") canceled a live turn
117
+ // as "Not logged in" (luvira, 2026-10-08).
118
+ const failure = attemptFailure(run);
119
+ if (failure === "auth-failure" || failure === "inactivity-watchdog")
120
+ return finalize(run, failure === "auth-failure" ? "Not logged in. Saved work is above." : "Stopped. Saved work is above.", true);
113
121
  if (failure === "permanent")
114
122
  return finalize(run, EXHAUSTED_NOTICE);
115
123
  return resolveMissing(run, probe, trigger);
@@ -86,8 +86,9 @@ export function createAssistantRunFinalizer(args, options = {}) {
86
86
  // Each connection replays the complete history, rather than appending duplicate deltas.
87
87
  watch.events = [];
88
88
  watch.failed = false;
89
+ const carry = {};
89
90
  for await (const frame of readSseFrames({ body: response.body })) {
90
- const outcome = translateRunFrame({ kind: frame.event, raw: frame.data, notices: args.notices });
91
+ const outcome = translateRunFrame({ kind: frame.event, raw: frame.data, notices: args.notices }, { carry });
91
92
  watch.events.push(...outcome.events);
92
93
  if (outcome.error)
93
94
  watch.failed = true;
@@ -88,8 +88,11 @@ function continuationEvents({ current, incoming }, _optional = {}) {
88
88
  break;
89
89
  }
90
90
  }
91
- if (marker >= 0)
92
- return [...recoveredRunEvents({ saved }, {}), ...incoming.slice(marker + 1)];
91
+ if (marker >= 0) {
92
+ const event = incoming[marker];
93
+ const notice = event.kind === 'status' && event.label === '' ? 'silent' : 'visible';
94
+ return [...recoveredRunEvents({ saved }, { notice }), ...incoming.slice(marker + 1)];
95
+ }
93
96
  return runContentFromEvents({ events: incoming }).length >= current.message.content.length ? incoming : saved;
94
97
  }
95
98
  /** Same kernel and message lock as checkpoint/settle. Claim changes run_id before any spawn;
@@ -126,15 +126,18 @@ export function createChatRunLedger({ kernel: hostKernel, isDaemonRunId }, _opti
126
126
  async checkpoint(progress, _optional = {}) {
127
127
  return kernel.transaction(async () => {
128
128
  await kernel.lockKey(runLockKey(progress));
129
- const current = await kernel.run((db) => db.selectFrom("ai_chat_messages").select(["content", "events_json"])
129
+ const current = await kernel.run((db) => db.selectFrom("ai_chat_messages").select(["content", "events_json", "run_status"])
130
130
  .where((eb) => eb.and([isRunRow(eb, progress), notTerminal(eb)])).executeTakeFirst());
131
131
  if (!current || progress.content.length < current.content.length)
132
132
  return false;
133
133
  const events = keepToolCheckpoints(readEvents(current.events_json), progress.events);
134
134
  const grown = progress.content.length > current.content.length || hasNewToolProgress(readEvents(current.events_json), events);
135
+ // A checkpoint is the run producing output, so an accepted (`queued`) row is now `running`;
136
+ // nothing else moves it there, and a streaming answer must not read as still waiting.
137
+ const runStatus = current.run_status === "queued" ? "running" : current.run_status;
135
138
  const result = await kernel.run((db) => db
136
139
  .updateTable("ai_chat_messages")
137
- .set({ content: progress.content, events_json: JSON.stringify(events) })
140
+ .set({ content: progress.content, events_json: JSON.stringify(events), run_status: runStatus })
138
141
  .where((eb) => eb.and([isRunRow(eb, progress), notTerminal(eb)]))
139
142
  .executeTakeFirst());
140
143
  if (Number(result.numUpdatedRows) > 0 && grown) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/chat",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
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": {
@@ -194,10 +194,10 @@
194
194
  "@radix-ui/react-select": "^2.3.7",
195
195
  "@radix-ui/react-slot": "^1.3.3",
196
196
  "recharts": "^3.10.1",
197
- "@jini-ai/agent-runtime": "^0.4.1",
197
+ "@jini-ai/agent-runtime": "^0.4.4",
198
198
  "@jini-ai/agentic": "^0.4.2",
199
- "@jini-ai/protocol": "^0.4.1",
200
- "@jini-ai/ui": "^0.4.5",
199
+ "@jini-ai/protocol": "^0.4.3",
200
+ "@jini-ai/ui": "^0.4.7",
201
201
  "@jini-ai/diagnostics": "^0.5.0"
202
202
  },
203
203
  "peerDependenciesMeta": {
@@ -286,13 +286,13 @@
286
286
  "@electric-sql/pglite": "0.5.8",
287
287
  "pg": "8.23.0",
288
288
  "@ag-ui/core": "0.0.58",
289
+ "@jini-ai/ui": "^0.4.7",
289
290
  "@jini-ai/db": "0.3.1",
290
- "@jini-ai/agent-runtime": "0.4.2",
291
+ "@jini-ai/protocol": "^0.4.3",
292
+ "@jini-ai/diagnostics": "^0.5.1",
291
293
  "@jini-ai/agentic": "^0.4.3",
292
- "@jini-ai/ui": "^0.4.5",
293
- "@jini-ai/protocol": "^0.4.2",
294
- "@jini-ai/core": "^0.4.2",
295
- "@jini-ai/diagnostics": "^0.5.1"
294
+ "@jini-ai/agent-runtime": "0.4.4",
295
+ "@jini-ai/core": "^0.4.2"
296
296
  },
297
297
  "typesVersions": {
298
298
  "*": {