@sero-ai/app-runtime 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sero-ai/app-runtime",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Hooks for Sero federated app modules — useAppState, useAppInfo, useAgentPrompt, useAppTools",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -20,10 +20,11 @@
20
20
  "access": "public"
21
21
  },
22
22
  "peerDependencies": {
23
- "react": ">=19.0.0"
23
+ "@sero-ai/common": ">=0.3.1",
24
+ "react": "^19.2.4"
24
25
  },
25
26
  "devDependencies": {
26
- "@sero/common": "^0.1.0",
27
+ "@sero-ai/common": "workspace:*",
27
28
  "@types/react": "^19.2.13",
28
29
  "typescript": "^5.9.3"
29
30
  }
package/src/index.ts CHANGED
@@ -15,7 +15,7 @@ export { useAvailableModels, type UseAvailableModelsResult } from './use-availab
15
15
  export { useTheme, type UseThemeResult } from './use-theme';
16
16
  export { getSeroApi } from './sero-bridge';
17
17
  export type { AppModelInfo, AppModelGroup } from './sero-bridge';
18
- export type { AppToolContentBlock, AppToolImageContent, AppToolResult, AppToolTextContent } from '@sero/common';
18
+ export type { AppToolContentBlock, AppToolImageContent, AppToolResult, AppToolTextContent } from '@sero-ai/common';
19
19
  export { registerWidget, getRuntimeWidgets, onWidgetRegistryChange } from './widget-registry';
20
20
  export type { RuntimeWidget } from './widget-registry';
21
21
  export { useWidgetRegistration } from './use-widget-registration';
@@ -15,7 +15,7 @@ import type {
15
15
  SharedModelInfo,
16
16
  WebAppActionResult,
17
17
  WebAppRequest,
18
- } from '@sero/common';
18
+ } from '@sero-ai/common';
19
19
 
20
20
  export interface SeroWindowAppStateBridge {
21
21
  read<TData = unknown>(filePath: string): Promise<TData>;
@@ -16,6 +16,33 @@ import { getSeroApi, type SeroWindowAppStateBridge } from './sero-bridge';
16
16
  * @param defaultState — returned while the file is being read (or if missing)
17
17
  * @returns [state, updateState] — updateState accepts an updater function
18
18
  */
19
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
20
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
21
+ }
22
+
23
+ function normalizeStateValue(defaultValue: unknown, currentValue: unknown): unknown {
24
+ if (Array.isArray(defaultValue)) {
25
+ return Array.isArray(currentValue) ? currentValue : defaultValue;
26
+ }
27
+
28
+ if (isPlainObject(defaultValue)) {
29
+ if (!isPlainObject(currentValue)) return defaultValue;
30
+ const merged: Record<string, unknown> = { ...currentValue };
31
+ for (const [key, childDefault] of Object.entries(defaultValue)) {
32
+ merged[key] = normalizeStateValue(childDefault, currentValue[key]);
33
+ }
34
+ return merged;
35
+ }
36
+
37
+ if (currentValue === undefined) return defaultValue;
38
+ if (defaultValue === null) return currentValue ?? null;
39
+ return typeof currentValue === typeof defaultValue ? currentValue : defaultValue;
40
+ }
41
+
42
+ function applyDefaultState<T>(defaultState: T, current: unknown): T {
43
+ return normalizeStateValue(defaultState, current) as T;
44
+ }
45
+
19
46
  export function useAppState<T>(defaultState: T): [T, (updater: (prev: T) => T) => void] {
20
47
  const ctx = useContext(AppContext);
21
48
  if (!ctx) {
@@ -43,7 +70,7 @@ export function useAppState<T>(defaultState: T): [T, (updater: (prev: T) => T) =
43
70
  try {
44
71
  const current = await api.read<T | null>(stateFilePath);
45
72
  if (writeId !== latestWriteIdRef.current) return;
46
- applyState(current ?? fallbackState);
73
+ applyState(current == null ? fallbackState : applyDefaultState(fallbackState, current));
47
74
  } catch {
48
75
  if (writeId !== latestWriteIdRef.current) return;
49
76
  applyState(fallbackState);
@@ -65,12 +92,12 @@ export function useAppState<T>(defaultState: T): [T, (updater: (prev: T) => T) =
65
92
 
66
93
  const unsubscribe = api.appState.onChange<T | null>((filePath, data) => {
67
94
  if (filePath !== stateFilePath || data == null) return;
68
- applyIfActive(data);
95
+ applyIfActive(applyDefaultState(defaultStateRef.current, data));
69
96
  });
70
97
 
71
98
  void api.appState.watch<T | null>(stateFilePath).then((current) => {
72
99
  if (current == null) return;
73
- applyIfActive(current);
100
+ applyIfActive(applyDefaultState(defaultStateRef.current, current));
74
101
  });
75
102
 
76
103
  return () => {
@@ -1,4 +1,4 @@
1
- import type { AppToolResult } from '@sero/common';
1
+ import type { AppToolResult } from '@sero-ai/common';
2
2
  import { useCallback, useContext, useMemo } from 'react';
3
3
 
4
4
  import { AppContext } from './context';