@theoremjs/react 0.3.0 → 0.3.1
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/README.md +728 -202
- package/client/composer-hints.js +1 -1
- package/client/decision-images.d.ts +76 -0
- package/client/decision-images.js +139 -0
- package/client/decision-transport.d.ts +26 -4
- package/client/decision-transport.js +79 -6
- package/client/embedding-input.d.ts +84 -0
- package/client/embedding-input.js +194 -0
- package/client/embedding-space.d.ts +82 -0
- package/client/embedding-space.js +389 -0
- package/client/embedding-transport.d.ts +40 -0
- package/client/embedding-transport.js +71 -0
- package/client/failure.d.ts +7 -10
- package/client/failure.js +9 -9
- package/client/host-call.d.ts +1 -1
- package/client/host-call.js +7 -2
- package/client/host-transport.d.ts +2 -0
- package/client/host-transport.js +3 -2
- package/client/index.d.ts +19 -11
- package/client/index.js +14 -10
- package/client/ink-waveform.d.ts +1 -1
- package/client/ink-waveform.js +1 -2
- package/client/live/apply-live-turn-tool-event.js +1 -1
- package/client/live/live-captions.d.ts +30 -0
- package/client/live/live-captions.js +105 -1
- package/client/live/live-mic-forward.d.ts +1 -0
- package/client/live/live-mic-forward.js +11 -2
- package/client/live/live-page-tool.d.ts +34 -0
- package/client/live/live-page-tool.js +11 -0
- package/client/live/live-state.d.ts +4 -2
- package/client/live/live-state.js +2 -0
- package/client/live/live-video.js +1 -1
- package/client/live/run-live-tool-call.d.ts +7 -2
- package/client/live/run-live-tool-call.js +48 -10
- package/client/live/use-live-caption-log.d.ts +17 -0
- package/client/live/use-live-caption-log.js +51 -0
- package/client/live-client.d.ts +52 -4
- package/client/live-client.js +250 -59
- package/client/live-messages.d.ts +51 -13
- package/client/live-messages.js +42 -9
- package/client/page-dispatch.d.ts +15 -0
- package/client/page-dispatch.js +63 -0
- package/client/run-commit.d.ts +16 -43
- package/client/run-commit.js +26 -101
- package/client/run-session.d.ts +40 -41
- package/client/run-session.js +104 -112
- package/client/schema-fields.js +27 -6
- package/client/shaped-data.js +37 -8
- package/client/source-citations.js +1 -1
- package/client/space-geometry.d.ts +39 -0
- package/client/space-geometry.js +183 -0
- package/client/state-fields.js +1 -1
- package/client/tool-result.js +30 -12
- package/client/tool-resume.d.ts +18 -1
- package/client/tool-resume.js +4 -1
- package/client/trace-feed.d.ts +1 -1
- package/client/trace-story.d.ts +2 -2
- package/client/trace-story.js +43 -19
- package/client/trace-view.d.ts +2 -2
- package/client/trace-view.js +56 -16
- package/client/transcript-groups.d.ts +28 -2
- package/client/transcript-groups.js +63 -13
- package/client/transcript-schema.d.ts +10 -0
- package/client/transcript-schema.js +120 -0
- package/client/transport.d.ts +114 -26
- package/client/transport.js +106 -32
- package/client/turn-client.d.ts +17 -8
- package/client/turn-client.js +26 -31
- package/client/voice-recorder.d.ts +1 -1
- package/client/voice-recorder.js +2 -5
- package/client/wire-line.js +5 -5
- package/client/worklets/mic-capture.js +9 -9
- package/components/InkWaveform.d.ts +12 -1
- package/components/InkWaveform.js +65 -42
- package/components/live/use-live-runner-controls.d.ts +3 -4
- package/components/live/use-live-runner-controls.js +6 -10
- package/components/live/use-live-runner-model.d.ts +10 -6
- package/components/live/use-live-runner-model.js +29 -14
- package/components/live/use-live-runner-ui.d.ts +10 -8
- package/components/live/use-live-runner-ui.js +19 -11
- package/components/live/use-live-session-client.d.ts +4 -4
- package/components/live/use-live-session-client.js +11 -8
- package/components/use-composer-voice.d.ts +4 -4
- package/components/use-composer-voice.js +8 -8
- package/components/use-editor-selection.js +4 -1
- package/components/use-voice-playback.d.ts +2 -1
- package/components/use-voice-playback.js +4 -4
- package/hooks/use-described.d.ts +5 -1
- package/hooks/use-described.js +11 -9
- package/hooks/use-embedding-index.d.ts +27 -0
- package/hooks/use-embedding-index.js +43 -0
- package/hooks/use-one-call.d.ts +31 -0
- package/hooks/use-one-call.js +76 -0
- package/hooks/use-page-tool-warnings.d.ts +6 -0
- package/hooks/use-page-tool-warnings.js +25 -0
- package/hooks/use-second-ticker.d.ts +2 -0
- package/hooks/use-second-ticker.js +13 -0
- package/hooks/use-theorem-chat-actions.d.ts +25 -21
- package/hooks/use-theorem-chat-actions.js +67 -45
- package/hooks/use-theorem-chat-state.d.ts +6 -6
- package/hooks/use-theorem-chat-state.js +13 -9
- package/hooks/use-theorem-chat.d.ts +48 -14
- package/hooks/use-theorem-chat.js +329 -141
- package/hooks/use-theorem-decision.d.ts +5 -10
- package/hooks/use-theorem-decision.js +4 -67
- package/hooks/use-theorem-embedding.d.ts +18 -0
- package/hooks/use-theorem-embedding.js +12 -0
- package/hooks/use-theorem-host.d.ts +5 -4
- package/hooks/use-theorem-host.js +26 -8
- package/hooks/use-theorem-interface.d.ts +6 -3
- package/hooks/use-theorem-interface.js +12 -22
- package/index.d.ts +6 -3
- package/index.js +5 -2
- package/live.d.ts +1 -0
- package/live.js +1 -1
- package/package.json +6 -5
- package/server/checkpoint-ownership.d.ts +6 -0
- package/server/checkpoint-ownership.js +42 -0
- package/server/conversation-store.d.ts +92 -0
- package/server/conversation-store.js +131 -0
- package/server/conversation.d.ts +193 -0
- package/server/conversation.js +552 -0
- package/server/decision-handler.d.ts +12 -4
- package/server/decision-handler.js +29 -38
- package/server/embedding-handler.d.ts +33 -0
- package/server/embedding-handler.js +45 -0
- package/server/handler.d.ts +58 -20
- package/server/handler.js +441 -238
- package/server/mod.d.ts +15 -6
- package/server/mod.js +13 -5
- package/server/page-dispatch.d.ts +22 -0
- package/server/page-dispatch.js +127 -0
- package/server/request-body.d.ts +2 -0
- package/server/request-body.js +42 -0
- package/server/request-check.d.ts +6 -1
- package/server/request-check.js +27 -12
- package/server/session-authority.d.ts +8 -0
- package/server/session-authority.js +78 -0
- package/server/session-boundary.d.ts +26 -0
- package/server/session-boundary.js +64 -0
- package/server/session-identity.d.ts +16 -0
- package/server/session-identity.js +41 -0
- package/server/session-limits.d.ts +12 -0
- package/server/session-limits.js +59 -0
- package/server/session-store.d.ts +69 -20
- package/server/session-store.js +220 -5
- package/server/single-action.d.ts +21 -0
- package/server/single-action.js +30 -0
- package/server/steer-inbox.js +1 -1
- package/server/tool-operations.d.ts +6 -0
- package/server/tool-operations.js +132 -0
- package/server/turn-input.d.ts +10 -0
- package/server/turn-input.js +29 -0
- package/styles/ink-controls.css +78 -78
- package/ui/CardWall.d.ts +10 -0
- package/ui/CardWall.js +61 -0
- package/ui/ChatComposerBar.d.ts +28 -5
- package/ui/ChatComposerBar.js +85 -30
- package/ui/ChatTranscript.d.ts +14 -1
- package/ui/ChatTranscript.js +110 -57
- package/ui/ConsoleFrame.d.ts +61 -4
- package/ui/ConsoleFrame.js +109 -9
- package/ui/DecisionAnswers.d.ts +40 -2
- package/ui/DecisionAnswers.js +166 -35
- package/ui/DecisionImages.d.ts +16 -0
- package/ui/DecisionImages.js +46 -0
- package/ui/EmbeddingDocuments.d.ts +78 -0
- package/ui/EmbeddingDocuments.js +278 -0
- package/ui/EmbeddingQuery.d.ts +27 -0
- package/ui/EmbeddingQuery.js +95 -0
- package/ui/EmbeddingResults.d.ts +41 -0
- package/ui/EmbeddingResults.js +165 -0
- package/ui/EmbeddingSpace.d.ts +26 -0
- package/ui/EmbeddingSpace.js +106 -0
- package/ui/LiveCaptionsPanel.d.ts +22 -0
- package/ui/LiveCaptionsPanel.js +27 -0
- package/ui/LiveRunner.d.ts +6 -4
- package/ui/LiveRunner.js +42 -55
- package/ui/PaneState.d.ts +33 -0
- package/ui/PaneState.js +50 -0
- package/ui/SchemaFields.d.ts +1 -1
- package/ui/SchemaFields.js +54 -26
- package/ui/ShapedData.js +73 -28
- package/ui/SidePanel.d.ts +74 -5
- package/ui/SidePanel.js +82 -9
- package/ui/StateFields.d.ts +10 -2
- package/ui/StateFields.js +56 -13
- package/ui/TheoremChat.d.ts +65 -14
- package/ui/TheoremChat.js +66 -78
- package/ui/TheoremDecision.d.ts +12 -20
- package/ui/TheoremDecision.js +78 -35
- package/ui/TheoremEmbedding.d.ts +27 -0
- package/ui/TheoremEmbedding.js +122 -0
- package/ui/TheoremHost.d.ts +22 -5
- package/ui/TheoremHost.js +127 -37
- package/ui/ToolGateCard.d.ts +5 -1
- package/ui/ToolGateCard.js +30 -23
- package/ui/ToolResult.js +12 -10
- package/ui/ToolResultCharts.js +29 -12
- package/ui/TraceCharts.d.ts +2 -2
- package/ui/TraceCharts.js +48 -17
- package/ui/TraceGuardrails.d.ts +3 -2
- package/ui/TraceGuardrails.js +24 -6
- package/ui/TraceInspector.d.ts +11 -1
- package/ui/TraceInspector.js +45 -28
- package/ui/TraceInspectorPanel.d.ts +31 -9
- package/ui/TraceInspectorPanel.js +77 -39
- package/ui/TraceOverview.d.ts +7 -3
- package/ui/TraceOverview.js +44 -14
- package/ui/TraceSpanDetail.js +24 -22
- package/ui/TraceStory.js +9 -8
- package/ui/TraceValues.js +19 -9
- package/ui/TraceWaterfall.js +12 -9
- package/ui/VoiceNote.js +4 -4
- package/ui/approval-heading.d.ts +4 -0
- package/ui/approval-heading.js +9 -0
- package/ui/arrive.d.ts +18 -0
- package/ui/arrive.js +46 -0
- package/ui/built/theme.css +86 -68
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem.d.ts +3 -3
- package/ui/built/theorem.js +34 -5
- package/ui/built/theorem.variants.d.ts +8 -2
- package/ui/code-language.d.ts +12 -0
- package/ui/code-language.js +50 -0
- package/ui/disclosure-motion.js +53 -9
- package/ui/embedding-space-gl.d.ts +37 -0
- package/ui/embedding-space-gl.js +663 -0
- package/ui/embedding-space-view.d.ts +8 -0
- package/ui/embedding-space-view.js +8 -0
- package/ui/glide.d.ts +7 -0
- package/ui/glide.js +44 -0
- package/ui/icons.js +2 -1
- package/ui/index.d.ts +20 -9
- package/ui/index.js +20 -10
- package/ui/labels-provider.js +5 -2
- package/ui/labels.d.ts +374 -85
- package/ui/labels.js +1028 -188
- package/ui/markdown-plugins.d.ts +9 -0
- package/ui/markdown-plugins.js +13 -0
- package/ui/mcp-icon.d.ts +6 -0
- package/ui/mcp-icon.js +8 -0
- package/ui/row-keys.d.ts +19 -0
- package/ui/row-keys.js +33 -0
- package/ui/theme.d.ts +1 -1
- package/ui/theme.js +2 -2
- package/ui/theorem-theme.js +32 -10
- package/ui/trace-palette.d.ts +2 -16
- package/ui/trace-palette.js +2 -16
- package/ui/trace-patterns.d.ts +11 -3
- package/ui/trace-patterns.js +16 -3
- package/ui/transcript-copy-text.js +1 -0
- package/ui/wall-columns.d.ts +8 -0
- package/ui/wall-columns.js +19 -0
- package/client/walk-away.d.ts +0 -34
- package/client/walk-away.js +0 -48
- package/server/walk-away.d.ts +0 -22
- package/server/walk-away.js +0 -41
|
@@ -1,10 +1,62 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { clientFailure } from "../client/failure.js";
|
|
1
|
+
import { branchInterfaceTurnSession, consumeNextComposerQueue, convertSteersToFrontQueued, defaultInterfaceEffort, orderComposerPendingMessages, promoteComposerPendingKind, removeComposerPendingMessage, removeLandedSteers, } from '@theoremjs/agents/interface';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { clientFailure, turnFailure } from "../client/failure.js";
|
|
4
4
|
import { followGenerationDefaults } from "../client/generation-selection.js";
|
|
5
|
-
import {
|
|
5
|
+
import { withPageToolDispatch } from "../client/page-dispatch.js";
|
|
6
|
+
import { sessionAfter, stampWorked } from "../client/run-commit.js";
|
|
7
|
+
import { keepsConversation } from "../client/run-session.js";
|
|
8
|
+
import { foldAssistantTurn } from "../client/turn-client.js";
|
|
9
|
+
import { usePageToolWarnings } from "./use-page-tool-warnings.js";
|
|
6
10
|
import { useTheoremChatActions } from "./use-theorem-chat-actions.js";
|
|
7
11
|
import { useTheoremChatState, } from "./use-theorem-chat-state.js";
|
|
12
|
+
/**
|
|
13
|
+
* The slots the profile declares and the value of each: the page's where it names one, else what
|
|
14
|
+
* the visitor chose, else the first allowed value.
|
|
15
|
+
*/
|
|
16
|
+
function useSlotChoices(iface, fixed, onSlotsChange) {
|
|
17
|
+
const [picked, setPicked] = useState({});
|
|
18
|
+
const declared = iface?.inputs.slots;
|
|
19
|
+
const values = useMemo(() => {
|
|
20
|
+
const out = { ...fixed };
|
|
21
|
+
for (const [name, allowed] of Object.entries(declared ?? {})) {
|
|
22
|
+
const value = fixed?.[name] ?? picked[name] ?? allowed[0];
|
|
23
|
+
if (value !== undefined)
|
|
24
|
+
out[name] = value;
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}, [declared, fixed, picked]);
|
|
28
|
+
const choose = useCallback((name, value) => {
|
|
29
|
+
setPicked((prev) => ({ ...prev, [name]: value }));
|
|
30
|
+
onSlotsChange?.({ ...values, [name]: value });
|
|
31
|
+
}, [values, onSlotsChange]);
|
|
32
|
+
const isChosen = declared !== undefined && Object.keys(declared).length > 0;
|
|
33
|
+
return {
|
|
34
|
+
values: isChosen || fixed ? values : undefined,
|
|
35
|
+
choices: isChosen ? { declared, values, onChange: choose } : undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Settles legacy page gates as unanswered. Browser callbacks require an active dispatch grant.
|
|
40
|
+
*/
|
|
41
|
+
function usePageAnswers(gated, pageTools, answer) {
|
|
42
|
+
const answered = useRef(null);
|
|
43
|
+
const latest = useRef({ pageTools, answer });
|
|
44
|
+
latest.current = { pageTools, answer };
|
|
45
|
+
const waiting = gated?.gateKind === 'page' ? gated : null;
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!waiting?.operationId || answered.current === waiting.operationId)
|
|
48
|
+
return;
|
|
49
|
+
const operationId = waiting.operationId;
|
|
50
|
+
answered.current = operationId;
|
|
51
|
+
// invariant: A restored page gate cannot authorize callback execution. Only active dispatch grants do.
|
|
52
|
+
void latest.current.answer({ action: 'page', page: { unanswered: true } });
|
|
53
|
+
}, [waiting]);
|
|
54
|
+
}
|
|
55
|
+
/** The page's side of a chat: answers the calls that wait on it, and warns of a tool it cannot answer. */
|
|
56
|
+
function usePageTools(iface, waitingOn, pageTools, answer) {
|
|
57
|
+
usePageAnswers(waitingOn, pageTools, answer);
|
|
58
|
+
usePageToolWarnings(iface && 'tools' in iface ? iface.tools.page : undefined, pageTools);
|
|
59
|
+
}
|
|
8
60
|
/**
|
|
9
61
|
* Seed the session with the profile's default model / effort once the interface loads, and keep
|
|
10
62
|
* it valid as the interface changes: a pick still on the old defaults follows the new ones, and a
|
|
@@ -22,43 +74,52 @@ function useDefaultGeneration(iface, session, setSession) {
|
|
|
22
74
|
setSession((prev) => ({ ...prev, selectedModel: next.model, selectedEffort: next.effort }));
|
|
23
75
|
}, [session.selectedEffort, session.selectedModel, setSession, iface]);
|
|
24
76
|
}
|
|
77
|
+
function useConversationRef(initial, onChange) {
|
|
78
|
+
const ref = useRef(initial);
|
|
79
|
+
const onChangeRef = useRef(onChange);
|
|
80
|
+
onChangeRef.current = onChange;
|
|
81
|
+
const adopt = useCallback((next) => {
|
|
82
|
+
if (ref.current === next)
|
|
83
|
+
return;
|
|
84
|
+
ref.current = next;
|
|
85
|
+
onChangeRef.current?.(next);
|
|
86
|
+
}, []);
|
|
87
|
+
return useMemo(() => ({ ref, adopt }), [adopt]);
|
|
88
|
+
}
|
|
89
|
+
/** Draws the conversation as the host shows it: its transcript, and the reply that waits on gates. */
|
|
90
|
+
function drawConversation(state, view, session, events) {
|
|
91
|
+
state.setBlocks(view.transcript);
|
|
92
|
+
state.setStreamBlocks(view.paused?.blocks ?? []);
|
|
93
|
+
// why: A reply at rest leaves the run's own events (tokens, an ask); a paused one, the host's.
|
|
94
|
+
state.setSession(sessionAfter(session, view.paused?.events ?? events));
|
|
95
|
+
state.setChatStarted(view.transcript.length > 0 || view.paused !== undefined);
|
|
96
|
+
}
|
|
25
97
|
/** The streamed blocks, ending on the turn's error unless the stream already carried it. */
|
|
26
98
|
function withTurnError(blocks, error) {
|
|
27
99
|
if (blocks.some((block) => block.kind === 'error'))
|
|
28
100
|
return blocks;
|
|
29
101
|
return [...blocks, { id: crypto.randomUUID(), kind: 'error', message: error }];
|
|
30
102
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const { error, errorKind, errorInternal } = result;
|
|
40
|
-
if (open) {
|
|
41
|
-
state.setFailure({ error, errorKind, ...(errorInternal ? { errorInternal } : {}) });
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
const failed = withTurnError(streamed, error);
|
|
45
|
-
state.setBlocks((prev) => [...prev, ...failed]);
|
|
46
|
-
state.setDelivery(null);
|
|
47
|
-
}
|
|
48
|
-
if (result.issues)
|
|
49
|
-
state.setIssues(result.issues);
|
|
103
|
+
/** A failure the composer shows: none when the person stopped the run. */
|
|
104
|
+
function showComposerFailure(state, failure) {
|
|
105
|
+
if (!failure || failure.aborted)
|
|
106
|
+
return;
|
|
107
|
+
const { error, errorKind, errorInternal } = failure;
|
|
108
|
+
state.setFailure({ error, errorKind, ...(errorInternal ? { errorInternal } : {}) });
|
|
109
|
+
if (failure.issues)
|
|
110
|
+
state.setIssues(failure.issues);
|
|
50
111
|
}
|
|
51
112
|
/**
|
|
52
113
|
* The run begins: busy, and streaming unless its message goes live when it
|
|
53
114
|
* posts (onUserBlocks), so the previous reply never renders as streaming in
|
|
54
115
|
* between. Returns the delivery before it, which a new message replaces.
|
|
55
116
|
*/
|
|
56
|
-
function beginRun(state,
|
|
117
|
+
function beginRun(state, message) {
|
|
57
118
|
state.setFailure(null);
|
|
58
119
|
const priorDelivery = state.deliveryRef.current;
|
|
59
120
|
state.busyRef.current = true;
|
|
60
121
|
state.setBusy(true);
|
|
61
|
-
if (!
|
|
122
|
+
if (!message)
|
|
62
123
|
state.setStreaming(true);
|
|
63
124
|
state.allowQueueDrainRef.current = false;
|
|
64
125
|
return priorDelivery;
|
|
@@ -72,108 +133,181 @@ function endRun(state) {
|
|
|
72
133
|
state.abortRef.current = null;
|
|
73
134
|
state.turnIdRef.current = null;
|
|
74
135
|
}
|
|
136
|
+
/** A message that never reached the conversation leaves the transcript and goes back in the composer. */
|
|
137
|
+
async function takeBackMessage(state, start, restore) {
|
|
138
|
+
state.setBlocks(start.blocks);
|
|
139
|
+
state.setStreamBlocks(start.streamBlocks);
|
|
140
|
+
state.setChatStarted(start.blocks.length > 0 || start.streamBlocks.length > 0);
|
|
141
|
+
state.setDelivery(null);
|
|
142
|
+
await restore();
|
|
143
|
+
}
|
|
75
144
|
/**
|
|
76
|
-
* A
|
|
77
|
-
*
|
|
78
|
-
*
|
|
145
|
+
* A host that keeps the conversation is the truth: the chat draws it again
|
|
146
|
+
* from its ref, even after a message that never posted (a walk can settle
|
|
147
|
+
* calls before it fails). A failure shows in the composer unless the host's
|
|
148
|
+
* transcript closed on it. Returns whether the reply waits on gates.
|
|
79
149
|
*/
|
|
80
|
-
function
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
150
|
+
async function settleHosted(state, transport, conversation, outcome, start) {
|
|
151
|
+
const { failure } = outcome;
|
|
152
|
+
if (failure)
|
|
153
|
+
state.setDelivery(null);
|
|
154
|
+
if (conversation === undefined) {
|
|
155
|
+
state.setStreamBlocks(start.streamBlocks);
|
|
156
|
+
showComposerFailure(state, failure);
|
|
157
|
+
return state.sessionRef.current.gatedTool !== null;
|
|
158
|
+
}
|
|
159
|
+
const view = await transport.conversation({ conversation });
|
|
160
|
+
drawConversation(state, view, outcome.session, outcome.events);
|
|
161
|
+
if (!view.transcript.slice(start.blocks.length).some((b) => b.kind === 'error')) {
|
|
162
|
+
showComposerFailure(state, failure);
|
|
163
|
+
}
|
|
164
|
+
return view.paused !== undefined;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A stateless chat keeps the reply as the run streamed it: in the transcript
|
|
168
|
+
* once it ends (closed by its failure, or by the person's stop), or beside it
|
|
169
|
+
* while it waits on a gate. Returns whether it waits.
|
|
170
|
+
*/
|
|
171
|
+
function settleLocal(state, iface, outcome, worked) {
|
|
172
|
+
const session = sessionAfter(outcome.session, outcome.events);
|
|
173
|
+
const { failure } = outcome;
|
|
174
|
+
const reply = stampWorked(foldAssistantTurn(iface, outcome.events), worked);
|
|
175
|
+
state.setSession(session);
|
|
176
|
+
if (session.gatedTool !== null) {
|
|
177
|
+
state.setStreamBlocks(reply);
|
|
178
|
+
showComposerFailure(state, failure);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
state.setStreamBlocks([]);
|
|
182
|
+
if (!failure) {
|
|
183
|
+
state.setBlocks((prev) => [...prev, ...reply]);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
state.setDelivery(null);
|
|
187
|
+
const closing = failure.aborted
|
|
188
|
+
? [{ id: crypto.randomUUID(), kind: 'turn-done', stop: { kind: 'cancelled' } }]
|
|
189
|
+
: withTurnError(reply, failure.error);
|
|
190
|
+
state.setBlocks((prev) => [...prev, ...closing]);
|
|
191
|
+
if (failure.issues)
|
|
192
|
+
state.setIssues(failure.issues);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Draws the conversation the chat resumes, once the interface says the host
|
|
197
|
+
* keeps one. A run that starts first draws it itself.
|
|
198
|
+
*/
|
|
199
|
+
function useResumedConversation(iface, transport, state, conversation, shown) {
|
|
200
|
+
const loaded = useRef(false);
|
|
201
|
+
const shownRef = useRef(shown);
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (!iface || loaded.current)
|
|
204
|
+
return;
|
|
205
|
+
loaded.current = true;
|
|
206
|
+
const ref = conversation.ref.current;
|
|
207
|
+
if (ref === undefined || !keepsConversation(iface))
|
|
208
|
+
return;
|
|
209
|
+
if (shownRef.current) {
|
|
210
|
+
drawConversation(state, shownRef.current, state.sessionRef.current, []);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
void (async () => {
|
|
214
|
+
const before = state.blocksRef.current;
|
|
215
|
+
try {
|
|
216
|
+
const view = await transport.conversation({ conversation: ref });
|
|
217
|
+
// why: A message sent while it loaded posted onto the host's conversation; its run draws it.
|
|
218
|
+
if (state.blocksRef.current !== before || state.busyRef.current)
|
|
219
|
+
return;
|
|
220
|
+
drawConversation(state, view, state.sessionRef.current, []);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
showComposerFailure(state, turnFailure(err, iface.labels));
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
226
|
+
}, [conversation, iface, state, transport]);
|
|
91
227
|
}
|
|
92
228
|
/**
|
|
93
229
|
* Runs one turn's stream into the transcript: live partials while it streams,
|
|
94
|
-
* then the
|
|
230
|
+
* then what the run left (or its failure) and the pending queue's next step.
|
|
95
231
|
*/
|
|
96
|
-
function useRunTurnStream(iface, state) {
|
|
97
|
-
//
|
|
232
|
+
function useRunTurnStream(iface, transport, state, conversation) {
|
|
233
|
+
// why: A stateless reply's work across its runs: a gate splits a reply into runs, and the wait between doesn't count.
|
|
98
234
|
const replyWorkedMs = useRef(0);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
state.setPendingMessages(orderComposerPendingMessages(
|
|
235
|
+
// why: Steers the reply never took wait at the front of the queue.
|
|
236
|
+
const onRunEnded = useCallback((drain) => {
|
|
237
|
+
state.setPendingMessages((prev) => orderComposerPendingMessages(convertSteersToFrontQueued(prev)));
|
|
102
238
|
state.allowQueueDrainRef.current = drain;
|
|
103
|
-
return converted;
|
|
104
239
|
}, [state]);
|
|
105
240
|
return useCallback(async (run, options = {}) => {
|
|
106
241
|
if (!iface || state.busyRef.current)
|
|
107
242
|
return;
|
|
108
|
-
const
|
|
109
|
-
const priorDelivery = beginRun(state,
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
243
|
+
const { message } = options;
|
|
244
|
+
const priorDelivery = beginRun(state, message !== undefined);
|
|
245
|
+
const start = {
|
|
246
|
+
blocks: state.blocksRef.current,
|
|
247
|
+
streamBlocks: state.streamBlocksRef.current,
|
|
248
|
+
};
|
|
249
|
+
// why: A run that answers a gate continues its reply; any other starts one.
|
|
250
|
+
if (!state.sessionRef.current.gatedTool)
|
|
114
251
|
replyWorkedMs.current = 0;
|
|
115
252
|
const runStartedAt = Date.now();
|
|
116
253
|
const work = (async () => {
|
|
117
|
-
let latestStream = [];
|
|
118
254
|
const view = {
|
|
119
|
-
blocks: (partial) =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
// The reply goes on; the composer names what it left out.
|
|
124
|
-
skipped: (error) => state.setFailure(clientFailure(error, iface.lexicon)),
|
|
255
|
+
blocks: (partial) => state.scheduleStreamBlocks(partial),
|
|
256
|
+
// why: The reply goes on; the composer names what it left out.
|
|
257
|
+
skipped: (error) => state.setFailure(clientFailure(error, iface.labels)),
|
|
125
258
|
};
|
|
126
|
-
const
|
|
259
|
+
const outcome = await run(view);
|
|
127
260
|
const endedAt = Date.now();
|
|
128
261
|
replyWorkedMs.current += endedAt - runStartedAt;
|
|
129
262
|
endRun(state);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
onRunEnded(state.pendingRef.current, false);
|
|
135
|
-
return;
|
|
263
|
+
const unposted = message !== undefined && !outcome.posted;
|
|
264
|
+
// why: A message that posts itself (onUserBlocks) gets a fresh delivery.
|
|
265
|
+
if (unposted && state.deliveryRef.current !== priorDelivery) {
|
|
266
|
+
await takeBackMessage(state, start, message.restore);
|
|
136
267
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return;
|
|
150
|
-
onRunEnded(state.pendingRef.current, true);
|
|
268
|
+
let waits;
|
|
269
|
+
if (keepsConversation(iface)) {
|
|
270
|
+
waits = await settleHosted(state, transport, conversation.ref.current, outcome, start);
|
|
271
|
+
}
|
|
272
|
+
else if (unposted) {
|
|
273
|
+
showComposerFailure(state, outcome.failure);
|
|
274
|
+
waits = state.sessionRef.current.gatedTool !== null;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
waits = settleLocal(state, iface, outcome, { workedMs: replyWorkedMs.current, endedAt });
|
|
278
|
+
}
|
|
279
|
+
onRunEnded(!outcome.failure && !waits);
|
|
151
280
|
})();
|
|
152
281
|
state.runPromiseRef.current = work;
|
|
153
282
|
try {
|
|
154
283
|
await work;
|
|
155
284
|
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
// why: The run left no view to draw; the composer says so, and the chat stays as it stood.
|
|
287
|
+
showComposerFailure(state, turnFailure(err, iface.labels));
|
|
288
|
+
onRunEnded(false);
|
|
289
|
+
}
|
|
156
290
|
finally {
|
|
157
291
|
if (state.runPromiseRef.current === work)
|
|
158
292
|
state.runPromiseRef.current = null;
|
|
159
293
|
}
|
|
160
|
-
}, [iface, onRunEnded, state]);
|
|
294
|
+
}, [conversation, iface, onRunEnded, state, transport]);
|
|
161
295
|
}
|
|
162
296
|
/** Start the next queued message once the run goes idle, when the run that ended allows it. */
|
|
163
297
|
function useQueueDrain(phase, state, startTurnFromDraft) {
|
|
164
298
|
const drainQueue = useCallback(async () => {
|
|
165
|
-
if (state.drainLockRef.current ||
|
|
166
|
-
state.busyRef.current ||
|
|
167
|
-
state.sessionRef.current.gatedTool) {
|
|
299
|
+
if (state.drainLockRef.current || state.busyRef.current || state.sessionRef.current.gatedTool) {
|
|
168
300
|
return;
|
|
169
301
|
}
|
|
170
|
-
const { message
|
|
302
|
+
const { message } = consumeNextComposerQueue(state.pendingRef.current);
|
|
171
303
|
if (!message)
|
|
172
304
|
return;
|
|
173
305
|
state.drainLockRef.current = true;
|
|
174
|
-
state.setPendingMessages(remaining);
|
|
175
306
|
try {
|
|
176
|
-
|
|
307
|
+
// why: The message leaves the queue once it posts; one that never starts stays queued.
|
|
308
|
+
await startTurnFromDraft(message.draft, {
|
|
309
|
+
onPosted: () => state.setPendingMessages((prev) => removeComposerPendingMessage(prev, message.id)),
|
|
310
|
+
});
|
|
177
311
|
}
|
|
178
312
|
finally {
|
|
179
313
|
state.drainLockRef.current = false;
|
|
@@ -196,48 +330,91 @@ const SERVER_EVENTS = new Set(['stage', 'guardrail', 'session', 'error', 'done']
|
|
|
196
330
|
/** The reply itself (its thinking and tool calls come before it). */
|
|
197
331
|
const REPLY_EVENTS = new Set(['text', 'structured', 'media']);
|
|
198
332
|
/**
|
|
199
|
-
* How far
|
|
200
|
-
* one from the model (thinking, a tool call) means
|
|
201
|
-
* reply means it was read.
|
|
333
|
+
* How far a line shows the message got: the host saving it, or any event,
|
|
334
|
+
* means the server took it; one from the model (thinking, a tool call) means
|
|
335
|
+
* the model has it, and the reply means it was read. A save without the
|
|
336
|
+
* message (a walk settling calls) says nothing of it.
|
|
202
337
|
*/
|
|
203
|
-
function deliveryOf(
|
|
204
|
-
if (
|
|
338
|
+
function deliveryOf(line) {
|
|
339
|
+
if (line.type === 'conversation')
|
|
340
|
+
return line.posted ? 'sent' : undefined;
|
|
341
|
+
if (REPLY_EVENTS.has(line.type))
|
|
205
342
|
return 'read';
|
|
206
|
-
return SERVER_EVENTS.has(
|
|
343
|
+
return SERVER_EVENTS.has(line.type) ? 'sent' : 'delivered';
|
|
344
|
+
}
|
|
345
|
+
/** Moves the message's delivery on to what `line` shows it reached, never back. */
|
|
346
|
+
function advanceDelivery(deliveryRef, setDelivery, line) {
|
|
347
|
+
const delivery = deliveryRef.current;
|
|
348
|
+
const reached = deliveryOf(line);
|
|
349
|
+
if (!delivery || !reached)
|
|
350
|
+
return;
|
|
351
|
+
if (DELIVERY_ORDER.indexOf(reached) > DELIVERY_ORDER.indexOf(delivery.status)) {
|
|
352
|
+
setDelivery({ status: reached });
|
|
353
|
+
}
|
|
207
354
|
}
|
|
208
355
|
/**
|
|
209
356
|
* The transport, with each turn's events also clearing the steers they report
|
|
210
357
|
* as landed (so the run's end requeues only the steers the agent never saw),
|
|
211
|
-
* and moving the posted message's delivery forward.
|
|
358
|
+
* and moving the posted message's delivery forward. The chat adopts each
|
|
359
|
+
* conversation ref as it arrives, so a page that reloads mid-reply keeps the
|
|
360
|
+
* posted message. Each turn carries the page's slots and context as they are
|
|
361
|
+
* when it is sent.
|
|
212
362
|
*/
|
|
213
|
-
function useTappedTransport(transport, state) {
|
|
363
|
+
function useTappedTransport(transport, state, conversation, values, onTurnEvent) {
|
|
364
|
+
const valuesRef = useRef(values);
|
|
365
|
+
valuesRef.current = values;
|
|
366
|
+
const onTurnEventRef = useRef(onTurnEvent);
|
|
367
|
+
onTurnEventRef.current = onTurnEvent;
|
|
214
368
|
return useMemo(() => {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
369
|
+
const withValues = (request) => {
|
|
370
|
+
const { slots, context } = valuesRef.current;
|
|
371
|
+
if (slots === undefined && context === undefined)
|
|
372
|
+
return request;
|
|
373
|
+
return {
|
|
374
|
+
...request,
|
|
375
|
+
input: {
|
|
376
|
+
...request.input,
|
|
377
|
+
...(slots === undefined ? {} : { slots }),
|
|
378
|
+
...(context === undefined ? {} : { context }),
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
const tap = (onLine) => (line) => {
|
|
383
|
+
if (line.type === 'stage' && line.injected?.length) {
|
|
384
|
+
state.pendingRef.current = removeLandedSteers(state.pendingRef.current, line);
|
|
385
|
+
state.setPendingMessages((prev) => removeLandedSteers(prev, line));
|
|
224
386
|
}
|
|
225
|
-
|
|
387
|
+
advanceDelivery(state.deliveryRef, state.setDelivery, line);
|
|
388
|
+
if (line.type === 'conversation')
|
|
389
|
+
conversation.adopt(line.ref);
|
|
390
|
+
else
|
|
391
|
+
onTurnEventRef.current?.(line);
|
|
392
|
+
onLine(line);
|
|
226
393
|
};
|
|
227
394
|
return {
|
|
228
395
|
...transport,
|
|
229
|
-
turn: (request,
|
|
230
|
-
invoke: (request,
|
|
396
|
+
turn: (request, onLine, signal) => transport.turn(withValues(request), tap(onLine), signal),
|
|
397
|
+
invoke: (request, onLine, signal) => transport.invoke(request, tap(onLine), signal),
|
|
231
398
|
};
|
|
232
|
-
}, [
|
|
399
|
+
}, [
|
|
400
|
+
transport,
|
|
401
|
+
conversation,
|
|
402
|
+
state.pendingRef,
|
|
403
|
+
state.setPendingMessages,
|
|
404
|
+
state.deliveryRef,
|
|
405
|
+
state.setDelivery,
|
|
406
|
+
]);
|
|
233
407
|
}
|
|
234
408
|
/**
|
|
235
409
|
* Headless chat model: transcript, streaming, composer drafts, pending
|
|
236
410
|
* queue / steer / stash, tool gates. Render it with `@theoremjs/react/ui` or
|
|
237
411
|
* your own components.
|
|
238
412
|
*/
|
|
239
|
-
export function useTheoremChat({ transport, iface,
|
|
240
|
-
const
|
|
413
|
+
export function useTheoremChat({ transport, iface, conversation: initialConversation, conversationView, initialText, onConversationChange, slots, onSlotsChange, context, pageTools, onTurnEvent, }) {
|
|
414
|
+
const slotState = useSlotChoices(iface, slots, onSlotsChange);
|
|
415
|
+
const state = useTheoremChatState(initialText);
|
|
416
|
+
const conversation = useConversationRef(initialConversation, onConversationChange);
|
|
417
|
+
useResumedConversation(iface, transport, state, conversation, conversationView);
|
|
241
418
|
useDefaultGeneration(iface, state.session, state.setSession);
|
|
242
419
|
const gated = state.session.gatedTool !== null;
|
|
243
420
|
const phase = state.busy ? 'streaming' : gated ? 'gated' : 'idle';
|
|
@@ -249,53 +426,63 @@ export function useTheoremChat({ transport, iface, initial, onChange }) {
|
|
|
249
426
|
...(effort ? { selectedEffort: effort } : { selectedEffort: undefined }),
|
|
250
427
|
}));
|
|
251
428
|
}, [iface, state]);
|
|
252
|
-
const runTurnStream = useRunTurnStream(iface, state);
|
|
253
|
-
const
|
|
429
|
+
const runTurnStream = useRunTurnStream(iface, transport, state, conversation);
|
|
430
|
+
const pageTransport = useMemo(() => withPageToolDispatch(transport, pageTools), [transport, pageTools]);
|
|
431
|
+
const steerTransport = useTappedTransport(pageTransport, state, conversation, { slots: slotState.values, context }, onTurnEvent);
|
|
254
432
|
const actions = useTheoremChatActions({
|
|
255
433
|
...state,
|
|
256
434
|
iface,
|
|
257
435
|
transport: steerTransport,
|
|
436
|
+
conversationRef: conversation.ref,
|
|
258
437
|
phase,
|
|
259
438
|
gated,
|
|
260
439
|
runTurnStream,
|
|
261
440
|
});
|
|
262
441
|
useQueueDrain(phase, state, actions.startTurnFromDraft);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
useEffect(() => {
|
|
267
|
-
if (state.busy || gated)
|
|
268
|
-
return;
|
|
269
|
-
const last = restedOn.current;
|
|
270
|
-
if (last && last.blocks === state.blocks && last.session === state.session)
|
|
271
|
-
return;
|
|
272
|
-
const snapshot = { blocks: state.blocks, session: state.session };
|
|
273
|
-
restedOn.current = snapshot;
|
|
274
|
-
onChangeRef.current?.(snapshot);
|
|
275
|
-
}, [state.busy, gated, state.blocks, state.session]);
|
|
276
|
-
/** Sends `text` as the composer would, and resolves once the reply is done; `null` when it can't go now. */
|
|
277
|
-
const sendText = useCallback(async (text) => {
|
|
442
|
+
usePageTools(iface, phase === 'gated' ? state.session.gatedTool : null, pageTools, actions.resumeGatedTool);
|
|
443
|
+
/** Sends `text` and optional files as the composer would, and resolves once the reply is done; `null` when it can't go now. */
|
|
444
|
+
const sendText = useCallback(async (text, files = []) => {
|
|
278
445
|
if (!iface || state.busyRef.current || state.sessionRef.current.gatedTool !== null)
|
|
279
446
|
return null;
|
|
280
447
|
const before = state.blocksRef.current.length;
|
|
281
|
-
await actions.startTurnFromFields({ text, files: [], voice: [] });
|
|
448
|
+
await actions.startTurnFromFields({ text, files: [...files], voice: [] });
|
|
282
449
|
return { blocks: state.blocksRef.current.slice(before) };
|
|
283
450
|
}, [actions, iface, state]);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
state.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
451
|
+
/**
|
|
452
|
+
* Cut the conversation after transcript block `index`. A host that keeps
|
|
453
|
+
* the conversation cuts it (walking away from a reply that waits on gates)
|
|
454
|
+
* and the chat draws what it returns; a stateless chat cuts its own.
|
|
455
|
+
*/
|
|
456
|
+
const handleBranch = useCallback(async (index) => {
|
|
457
|
+
if (!iface || state.busyRef.current)
|
|
458
|
+
return;
|
|
459
|
+
const ref = conversation.ref.current;
|
|
460
|
+
if (!keepsConversation(iface) || ref === undefined) {
|
|
461
|
+
const kept = [...state.blocksRef.current, ...state.streamBlocksRef.current].slice(0, index + 1);
|
|
462
|
+
state.setBlocks(kept);
|
|
463
|
+
state.setStreamBlocks([]);
|
|
464
|
+
state.setChatStarted(kept.length > 0);
|
|
465
|
+
// why: The kept transcript's last message isn't the one whose delivery was tracked.
|
|
466
|
+
state.setDelivery(null);
|
|
467
|
+
state.setSession((prev) => branchInterfaceTurnSession(prev, kept));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
state.setFailure(null);
|
|
471
|
+
try {
|
|
472
|
+
// why: The paused reply is not in the host's transcript; a cut inside it keeps the transcript whole.
|
|
473
|
+
const at = Math.min(index, state.blocksRef.current.length - 1);
|
|
474
|
+
const rewound = await transport.rewind({ conversation: ref, at });
|
|
475
|
+
conversation.adopt(rewound.conversation);
|
|
476
|
+
drawConversation(state, rewound.view, state.sessionRef.current, []);
|
|
477
|
+
state.setDelivery(null);
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
showComposerFailure(state, turnFailure(err, iface.labels));
|
|
481
|
+
}
|
|
482
|
+
}, [conversation, iface, state, transport]);
|
|
296
483
|
const handlePendingQueue = useCallback((id) => {
|
|
297
484
|
const message = state.pendingRef.current.find((m) => m.id === id);
|
|
298
|
-
if (
|
|
485
|
+
if (message?.kind !== 'stash')
|
|
299
486
|
return;
|
|
300
487
|
if (phase === 'idle') {
|
|
301
488
|
state.allowQueueDrainRef.current = true;
|
|
@@ -335,6 +522,7 @@ export function useTheoremChat({ transport, iface, initial, onChange }) {
|
|
|
335
522
|
handleSendNow: actions.handleSendNow,
|
|
336
523
|
handleToolDecision: actions.handleToolDecision,
|
|
337
524
|
handleGenerationChange,
|
|
525
|
+
slotChoices: slotState.choices,
|
|
338
526
|
sendText,
|
|
339
527
|
};
|
|
340
528
|
}
|
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
import type { DecisionJson, DecisionResult } from '@theoremjs/agents';
|
|
1
|
+
import type { DecisionAsset, DecisionJson, DecisionResult } from '@theoremjs/agents';
|
|
2
2
|
import type { DecisionInterface, DecisionTransport } from '../client/decision-transport.ts';
|
|
3
|
-
import {
|
|
3
|
+
import type { ClientFailure } from '../client/failure.ts';
|
|
4
|
+
import { type OneCallState } from './use-one-call.ts';
|
|
4
5
|
export type TheoremDecisionState = {
|
|
5
6
|
/** The profile as the page sees it; null until the host describes it. */
|
|
6
7
|
iface: DecisionInterface | null;
|
|
7
8
|
/** Why the host could not describe the profile. */
|
|
8
9
|
describeFailure: ClientFailure | null;
|
|
9
|
-
|
|
10
|
-
/** The last answers; kept while the next decision runs and when it fails. */
|
|
11
|
-
result: DecisionResult | null;
|
|
12
|
-
/** How long the last decision took, round trip, in milliseconds. */
|
|
13
|
-
elapsedMs: number | null;
|
|
14
|
-
failure: ClientFailure | null;
|
|
15
|
-
};
|
|
10
|
+
} & OneCallState<'deciding', DecisionResult>;
|
|
16
11
|
/**
|
|
17
12
|
* One decision profile: describe it once, then `decide(state)` as often as
|
|
18
13
|
* needed. A new decision aborts the one still running.
|
|
19
14
|
*/
|
|
20
15
|
export declare function useTheoremDecision(transport: DecisionTransport): TheoremDecisionState & {
|
|
21
|
-
decide: (state: Exclude<DecisionJson, null>) => Promise<void>;
|
|
16
|
+
decide: (state: Exclude<DecisionJson, null>, assets?: Record<string, DecisionAsset>) => Promise<void>;
|
|
22
17
|
cancel: () => void;
|
|
23
18
|
};
|