@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 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: 'turn.accepted';
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(chat.activeTurnId) || Boolean(externalBusy) || loadingSession;
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: void 0,
1883
+ activeTurnId,
1882
1884
  messages: action.messages,
1883
1885
  activitiesByTurnId: groupActivitiesByTurn(action.activities ?? []),
1884
- progressByTurnId: {},
1885
- status: "Ready",
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 === "turn.accepted") {
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.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.0",
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"