@beanx/cathygo-web-core 0.1.1 → 0.1.2
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 +30 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +141 -9
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @beanx/cathygo-web-core
|
|
2
|
+
|
|
3
|
+
Shared CathyGO Web UI and state package.
|
|
4
|
+
|
|
5
|
+
Current active upgrade plan:
|
|
6
|
+
|
|
7
|
+
- [`../../docs/session-runtime-vnext-upgrade-plan.md`](../../docs/session-runtime-vnext-upgrade-plan.md)
|
|
8
|
+
|
|
9
|
+
## vNext Role
|
|
10
|
+
|
|
11
|
+
This package owns the selected-session view model for:
|
|
12
|
+
|
|
13
|
+
- single-session transcript rendering
|
|
14
|
+
- session event reduction
|
|
15
|
+
- optimistic user input reconciliation by `client_input_id`
|
|
16
|
+
- session-scoped stop state
|
|
17
|
+
- replay-safe hydration after refresh or reconnect
|
|
18
|
+
- selectors used by local Web and `beanx-home`
|
|
19
|
+
|
|
20
|
+
Host applications still own account routing, agent selection, transport setup,
|
|
21
|
+
selected session id, lightweight session lists, and product-specific shell UI.
|
|
22
|
+
They should not own a second session execution runtime.
|
|
23
|
+
|
|
24
|
+
## First vNext Deliverables
|
|
25
|
+
|
|
26
|
+
- Keep the current single-session `ChatState` reducer exported.
|
|
27
|
+
- Add `SessionViewState`, session event reducer, and current-session selectors.
|
|
28
|
+
- Add reducer tests for interrupt, replay, stop, and optimistic reconciliation.
|
|
29
|
+
- Prove the implementation in `cathygo-agent/web` before publishing
|
|
30
|
+
`@beanx/cathygo-web-core@0.2.0`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { Dispatch, RefObject } from 'react';
|
|
3
|
-
import { AgentActivity, LearningTurnEvent, ConversationSummary, GatewayModelStatus } from '@beanx/cathygo-protocol';
|
|
3
|
+
import { AgentActivity, SessionRuntimeSnapshot, LearningTurnEvent, ConversationSummary, GatewayModelStatus } from '@beanx/cathygo-protocol';
|
|
4
4
|
|
|
5
5
|
type AgentIdentityStatus = {
|
|
6
6
|
agent_id?: string | null;
|
|
@@ -96,6 +96,7 @@ type ChatState = {
|
|
|
96
96
|
messages: ChatMessage$1[];
|
|
97
97
|
activitiesByTurnId: Record<string, AgentActivity[]>;
|
|
98
98
|
progressByTurnId: Record<string, AgentRunProgress>;
|
|
99
|
+
runtime?: SessionRuntimeSnapshot;
|
|
99
100
|
status: string;
|
|
100
101
|
contextMessageCount?: number;
|
|
101
102
|
error?: string;
|
|
@@ -131,6 +132,7 @@ type ChatAction = {
|
|
|
131
132
|
sessionId: string;
|
|
132
133
|
messages: ChatMessage$1[];
|
|
133
134
|
activities?: AgentActivity[];
|
|
135
|
+
runtime?: SessionRuntimeSnapshot;
|
|
134
136
|
} | {
|
|
135
137
|
type: 'session.load.failed';
|
|
136
138
|
sessionId: string;
|
|
@@ -142,9 +144,12 @@ type ChatAction = {
|
|
|
142
144
|
content: string;
|
|
143
145
|
parts?: ChatMessagePart[];
|
|
144
146
|
} | {
|
|
145
|
-
type: '
|
|
147
|
+
type: 'session.input.accepted';
|
|
146
148
|
sessionId: string;
|
|
147
149
|
turnId: string;
|
|
150
|
+
clientInputId?: string;
|
|
151
|
+
clientTurnId?: string;
|
|
152
|
+
idempotentReplay?: boolean;
|
|
148
153
|
} | {
|
|
149
154
|
type: 'send.failed';
|
|
150
155
|
id: string;
|
|
@@ -204,7 +209,7 @@ type UseCathyGOChatResult = {
|
|
|
204
209
|
dispatchChat: Dispatch<ChatAction>;
|
|
205
210
|
resetChat: () => void;
|
|
206
211
|
createSession: (sessionId: string) => void;
|
|
207
|
-
loadSession: (sessionId: string, messages: ChatMessage$1[], activities?: ChatState['activitiesByTurnId'][string]) => void;
|
|
212
|
+
loadSession: (sessionId: string, messages: ChatMessage$1[], activities?: ChatState['activitiesByTurnId'][string], runtime?: SessionRuntimeSnapshot) => void;
|
|
208
213
|
applyTurnEvent: (event: LearningTurnEvent) => void;
|
|
209
214
|
};
|
|
210
215
|
declare function useCathyGOChat(initialState?: ChatState): UseCathyGOChatResult;
|
package/dist/index.js
CHANGED
|
@@ -1668,7 +1668,7 @@ function ChatView({
|
|
|
1668
1668
|
onSettingsOpen
|
|
1669
1669
|
}) {
|
|
1670
1670
|
const loadingSession = chat.status === "Loading";
|
|
1671
|
-
const busy = Boolean(
|
|
1671
|
+
const busy = Boolean(externalBusy) || loadingSession;
|
|
1672
1672
|
const ready = Boolean(chat.sessionId) && !loadingSession;
|
|
1673
1673
|
const usingMock = model?.provider === "mock";
|
|
1674
1674
|
const modelLabel = model ? model.model || model.display_name || model.provider : "loading model status";
|
|
@@ -1715,7 +1715,7 @@ function ChatView({
|
|
|
1715
1715
|
activitiesByTurnId: chat.activitiesByTurnId,
|
|
1716
1716
|
progressByTurnId: chat.progressByTurnId,
|
|
1717
1717
|
messages: chat.messages,
|
|
1718
|
-
onSuggest: ready && !busy ? onSuggest : void 0
|
|
1718
|
+
onSuggest: ready && !busy && !chat.activeTurnId ? onSuggest : void 0
|
|
1719
1719
|
}
|
|
1720
1720
|
),
|
|
1721
1721
|
!loadingSession && chat.status === "Error" && chat.error ? /* @__PURE__ */ jsx13("p", { className: "chat-session-error", children: chat.error }) : null,
|
|
@@ -1875,14 +1875,17 @@ function reduceChat(state, action) {
|
|
|
1875
1875
|
};
|
|
1876
1876
|
}
|
|
1877
1877
|
if (action.type === "session.loaded") {
|
|
1878
|
+
const runtime = action.runtime;
|
|
1879
|
+
const activeTurnId = optionalString(runtime?.active_turn_id);
|
|
1878
1880
|
return {
|
|
1879
1881
|
...state,
|
|
1880
1882
|
sessionId: action.sessionId,
|
|
1881
|
-
activeTurnId
|
|
1883
|
+
activeTurnId,
|
|
1882
1884
|
messages: action.messages,
|
|
1883
1885
|
activitiesByTurnId: groupActivitiesByTurn(action.activities ?? []),
|
|
1884
|
-
progressByTurnId: {},
|
|
1885
|
-
|
|
1886
|
+
progressByTurnId: activeTurnId ? setProgress({}, runtimeProgressFromSnapshot(runtime, activeTurnId)) : {},
|
|
1887
|
+
runtime,
|
|
1888
|
+
status: statusFromRuntime(runtime),
|
|
1886
1889
|
error: void 0,
|
|
1887
1890
|
errorCode: void 0
|
|
1888
1891
|
};
|
|
@@ -1929,11 +1932,19 @@ function reduceChat(state, action) {
|
|
|
1929
1932
|
errorCode: action.code
|
|
1930
1933
|
};
|
|
1931
1934
|
}
|
|
1932
|
-
if (action.type === "
|
|
1935
|
+
if (action.type === "session.input.accepted") {
|
|
1933
1936
|
return {
|
|
1934
1937
|
...state,
|
|
1935
1938
|
sessionId: action.sessionId,
|
|
1936
1939
|
activeTurnId: action.turnId,
|
|
1940
|
+
runtime: {
|
|
1941
|
+
...state.runtime ?? emptyRuntimeSnapshot(),
|
|
1942
|
+
status: "running",
|
|
1943
|
+
active_turn_id: action.turnId,
|
|
1944
|
+
active_turn_ids: [action.turnId],
|
|
1945
|
+
active_client_input_id: action.clientInputId ?? null,
|
|
1946
|
+
active_client_turn_id: action.clientTurnId ?? null
|
|
1947
|
+
},
|
|
1937
1948
|
progressByTurnId: setProgress(state.progressByTurnId, {
|
|
1938
1949
|
turnId: action.turnId,
|
|
1939
1950
|
phase: "queued",
|
|
@@ -1953,8 +1964,50 @@ function reduceChat(state, action) {
|
|
|
1953
1964
|
...state,
|
|
1954
1965
|
eventCount: state.eventCount + 1,
|
|
1955
1966
|
sessionId: payload.session_id ?? state.sessionId,
|
|
1956
|
-
activeTurnId: payload.turn_id ?? state.activeTurnId
|
|
1967
|
+
activeTurnId: payload.turn_id ?? state.activeTurnId,
|
|
1968
|
+
runtime: runtimeFromEvent(state.runtime, event)
|
|
1957
1969
|
};
|
|
1970
|
+
if (event.event === "session.created") {
|
|
1971
|
+
return {
|
|
1972
|
+
...next,
|
|
1973
|
+
status: "Ready",
|
|
1974
|
+
error: void 0,
|
|
1975
|
+
errorCode: void 0
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
if (event.event === "session.deleted") {
|
|
1979
|
+
return {
|
|
1980
|
+
...next,
|
|
1981
|
+
activeTurnId: void 0,
|
|
1982
|
+
progressByTurnId: {},
|
|
1983
|
+
status: "Archived"
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
if (event.event === "session.input.accepted") {
|
|
1987
|
+
const turnId = optionalString(payload.turn_id);
|
|
1988
|
+
if (!turnId) return next;
|
|
1989
|
+
return {
|
|
1990
|
+
...next,
|
|
1991
|
+
activeTurnId: turnId,
|
|
1992
|
+
progressByTurnId: setProgress(next.progressByTurnId, {
|
|
1993
|
+
turnId,
|
|
1994
|
+
phase: "queued",
|
|
1995
|
+
status: "running",
|
|
1996
|
+
summary: "\u6B63\u5728\u63D0\u4EA4\u95EE\u9898",
|
|
1997
|
+
detail: "\u7B49\u5F85 CathyGO \u5F00\u59CB\u5904\u7406",
|
|
1998
|
+
startedAt: nowIso()
|
|
1999
|
+
}),
|
|
2000
|
+
status: "Thinking",
|
|
2001
|
+
error: void 0,
|
|
2002
|
+
errorCode: void 0
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
if (event.event === "session.stop.requested") {
|
|
2006
|
+
return {
|
|
2007
|
+
...next,
|
|
2008
|
+
status: "Stopping"
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
1958
2011
|
if (event.event === "agent.progress.delta") {
|
|
1959
2012
|
return {
|
|
1960
2013
|
...next,
|
|
@@ -2029,6 +2082,7 @@ function reduceChat(state, action) {
|
|
|
2029
2082
|
...next,
|
|
2030
2083
|
activeTurnId: void 0,
|
|
2031
2084
|
progressByTurnId: removeProgress(next.progressByTurnId, payload.turn_id),
|
|
2085
|
+
runtime: markRuntimeIdle(next.runtime),
|
|
2032
2086
|
status: "Ready"
|
|
2033
2087
|
};
|
|
2034
2088
|
}
|
|
@@ -2037,6 +2091,7 @@ function reduceChat(state, action) {
|
|
|
2037
2091
|
...next,
|
|
2038
2092
|
activeTurnId: void 0,
|
|
2039
2093
|
progressByTurnId: removeProgress(next.progressByTurnId, payload.turn_id),
|
|
2094
|
+
runtime: markRuntimeIdle(next.runtime),
|
|
2040
2095
|
status: "Ready"
|
|
2041
2096
|
};
|
|
2042
2097
|
}
|
|
@@ -2046,6 +2101,7 @@ function reduceChat(state, action) {
|
|
|
2046
2101
|
...next,
|
|
2047
2102
|
activeTurnId: void 0,
|
|
2048
2103
|
progressByTurnId: removeProgress(next.progressByTurnId, payload.turn_id),
|
|
2104
|
+
runtime: markRuntimeIdle(next.runtime),
|
|
2049
2105
|
status: "Error",
|
|
2050
2106
|
error: error?.message ?? "CathyGO turn failed",
|
|
2051
2107
|
errorCode: error?.code
|
|
@@ -2068,6 +2124,10 @@ function appendAssistantDelta(messages, messageId, delta, turnId) {
|
|
|
2068
2124
|
}
|
|
2069
2125
|
];
|
|
2070
2126
|
}
|
|
2127
|
+
const existing = messages[index];
|
|
2128
|
+
if (existing?.role === "assistant" && existing.status === "done" && existing.content) {
|
|
2129
|
+
return messages;
|
|
2130
|
+
}
|
|
2071
2131
|
return messages.map(
|
|
2072
2132
|
(message, itemIndex) => itemIndex === index ? {
|
|
2073
2133
|
...message,
|
|
@@ -2217,6 +2277,78 @@ function progressFromActivity(progressByTurnId, activity, turnActivities, messag
|
|
|
2217
2277
|
}
|
|
2218
2278
|
return progressByTurnId;
|
|
2219
2279
|
}
|
|
2280
|
+
function runtimeProgressFromSnapshot(runtime, turnId) {
|
|
2281
|
+
return {
|
|
2282
|
+
turnId,
|
|
2283
|
+
phase: runtime?.status === "stopping" ? "model" : "queued",
|
|
2284
|
+
status: "running",
|
|
2285
|
+
summary: runtime?.status === "stopping" ? "\u6B63\u5728\u505C\u6B62" : "\u6B63\u5728\u6062\u590D\u4F1A\u8BDD\u72B6\u6001",
|
|
2286
|
+
detail: runtime?.active_client_input_id ? `client_input_id=${runtime.active_client_input_id}` : void 0,
|
|
2287
|
+
startedAt: nowIso()
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
function runtimeFromEvent(current, event) {
|
|
2291
|
+
const payload = event.payload;
|
|
2292
|
+
const turnId = optionalString(payload.turn_id);
|
|
2293
|
+
const turnIds = eventTurnIds(payload, turnId);
|
|
2294
|
+
const base = {
|
|
2295
|
+
...current ?? emptyRuntimeSnapshot(),
|
|
2296
|
+
last_event_seq: Math.max(current?.last_event_seq ?? 0, event.seq)
|
|
2297
|
+
};
|
|
2298
|
+
if (event.event === "session.input.accepted" || event.event === "turn.started") {
|
|
2299
|
+
return {
|
|
2300
|
+
...base,
|
|
2301
|
+
status: "running",
|
|
2302
|
+
active_turn_id: turnId ?? base.active_turn_id ?? null,
|
|
2303
|
+
active_turn_ids: turnIds.length > 0 ? turnIds : base.active_turn_ids ?? [],
|
|
2304
|
+
active_client_input_id: optionalString(payload.client_input_id) ?? base.active_client_input_id ?? null,
|
|
2305
|
+
active_client_turn_id: optionalString(payload.client_turn_id) ?? base.active_client_turn_id ?? null
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
if (event.event === "session.stop.requested") {
|
|
2309
|
+
return {
|
|
2310
|
+
...base,
|
|
2311
|
+
status: "stopping",
|
|
2312
|
+
active_turn_id: turnId ?? base.active_turn_id ?? null,
|
|
2313
|
+
active_turn_ids: turnIds.length > 0 ? turnIds : base.active_turn_ids ?? []
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
if (event.event === "turn.completed" || event.event === "turn.cancelled" || event.event === "turn.failed" || event.event === "session.deleted") {
|
|
2317
|
+
return markRuntimeIdle(base);
|
|
2318
|
+
}
|
|
2319
|
+
return base;
|
|
2320
|
+
}
|
|
2321
|
+
function eventTurnIds(payload, turnId) {
|
|
2322
|
+
if (Array.isArray(payload.turn_ids)) {
|
|
2323
|
+
return payload.turn_ids.map((value) => optionalString(value)).filter((value) => Boolean(value));
|
|
2324
|
+
}
|
|
2325
|
+
return turnId ? [turnId] : [];
|
|
2326
|
+
}
|
|
2327
|
+
function emptyRuntimeSnapshot() {
|
|
2328
|
+
return {
|
|
2329
|
+
status: "idle",
|
|
2330
|
+
active_turn_id: null,
|
|
2331
|
+
active_turn_ids: [],
|
|
2332
|
+
active_client_input_id: null,
|
|
2333
|
+
active_client_turn_id: null,
|
|
2334
|
+
last_event_seq: 0
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
function markRuntimeIdle(runtime) {
|
|
2338
|
+
return {
|
|
2339
|
+
...runtime ?? emptyRuntimeSnapshot(),
|
|
2340
|
+
status: "idle",
|
|
2341
|
+
active_turn_id: null,
|
|
2342
|
+
active_turn_ids: [],
|
|
2343
|
+
active_client_input_id: null,
|
|
2344
|
+
active_client_turn_id: null
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
function statusFromRuntime(runtime) {
|
|
2348
|
+
if (runtime?.status === "running") return "Thinking";
|
|
2349
|
+
if (runtime?.status === "stopping") return "Stopping";
|
|
2350
|
+
return "Ready";
|
|
2351
|
+
}
|
|
2220
2352
|
function setProgress(progressByTurnId, progress) {
|
|
2221
2353
|
return {
|
|
2222
2354
|
...progressByTurnId,
|
|
@@ -2314,8 +2446,8 @@ function useCathyGOChat(initialState = initialChatState) {
|
|
|
2314
2446
|
dispatchChat({ type: "session.created", sessionId });
|
|
2315
2447
|
}, []);
|
|
2316
2448
|
const loadSession = useCallback(
|
|
2317
|
-
(sessionId, messages, activities) => {
|
|
2318
|
-
dispatchChat({ type: "session.loaded", sessionId, messages, activities });
|
|
2449
|
+
(sessionId, messages, activities, runtime) => {
|
|
2450
|
+
dispatchChat({ type: "session.loaded", sessionId, messages, activities, runtime });
|
|
2319
2451
|
},
|
|
2320
2452
|
[]
|
|
2321
2453
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beanx/cathygo-web-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./styles.css": "./dist/styles.css"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@beanx/cathygo-protocol": "0.1.
|
|
19
|
+
"@beanx/cathygo-protocol": "0.1.1",
|
|
20
20
|
"@streamdown/math": "^1.0.2",
|
|
21
21
|
"katex": "^0.16.47",
|
|
22
22
|
"streamdown": "^2.5.0"
|