@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.
- package/CHANGELOG.md +4 -0
- package/dist/core/durable-projection.d.ts +3 -1
- package/dist/core/durable-projection.js +40 -7
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/run-events/entry.d.ts +12 -1
- package/dist/core/run-events/entry.js +17 -6
- package/dist/core/tool-call-detail.d.ts +36 -0
- package/dist/core/tool-call-detail.js +49 -0
- package/dist/core/turn-usage.d.ts +11 -0
- package/dist/core/turn-usage.js +17 -0
- package/dist/react/chat-pane.d.ts +2 -2
- package/dist/react/chat-pane.js +1 -1
- package/dist/react/components/Composer.d.ts +2 -0
- package/dist/react/components/Composer.js +5 -2
- package/dist/react/components/Markdown.js +6 -6
- package/dist/react/components/MessageList.d.ts +7 -12
- package/dist/react/components/MessageList.js +20 -2
- package/dist/react/components/MessageRow.d.ts +6 -1
- package/dist/react/components/MessageRow.js +10 -4
- package/dist/react/components/MidRunUserMessage.d.ts +27 -0
- package/dist/react/components/MidRunUserMessage.js +39 -0
- package/dist/react/components/ToolCard.js +33 -22
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +11 -3
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +44 -0
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +69 -0
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +69 -11
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.d.ts +25 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +70 -0
- 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/model-options.d.ts +3 -1
- package/dist/react/features/chat-pane/model-options.js +6 -2
- package/dist/react/features/chat-pane/rules.d.ts +13 -0
- package/dist/react/features/chat-pane/rules.js +14 -0
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +7 -0
- package/dist/react/features/chat-pane/types.d.ts +23 -0
- package/dist/react/hooks/composer-draft-cache.d.ts +15 -0
- package/dist/react/hooks/composer-draft-cache.js +20 -0
- package/dist/react/hooks/useComposer.d.ts +14 -0
- package/dist/react/hooks/useComposer.js +78 -6
- package/dist/react/index.d.ts +4 -2
- package/dist/react/index.js +3 -1
- package/dist/server/durable-runs/recover.js +19 -11
- package/dist/server/run-finalizer/entry.js +2 -1
- package/dist/store/runtime/durable-run-store.js +5 -2
- package/dist/store/runtime/run-ledger.js +5 -2
- 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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/react/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
197
|
+
"@jini-ai/agent-runtime": "^0.4.4",
|
|
198
198
|
"@jini-ai/agentic": "^0.4.2",
|
|
199
|
-
"@jini-ai/protocol": "^0.4.
|
|
200
|
-
"@jini-ai/ui": "^0.4.
|
|
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/
|
|
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/
|
|
293
|
-
"@jini-ai/
|
|
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
|
"*": {
|