@nextclaw/ui 0.12.19 → 0.12.20-beta.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/CHANGELOG.md +39 -0
- package/dist/assets/api-BcqDx0tm.js +15 -0
- package/dist/assets/app-manager-provider-DVYBjif-.js +1 -0
- package/dist/assets/app-navigation.config-CMoWvFEI.js +1 -0
- package/dist/assets/{book-open-CVEuA0y5.js → book-open-DgLqYpNY.js} +1 -1
- package/dist/assets/{channels-list-page-BqhqaBf1.js → channels-list-page-CsoI4OJm.js} +2 -2
- package/dist/assets/{chat-D4KecKjB.js → chat-CA3aRmhx.js} +13 -12
- package/dist/assets/chat-page-gdSN6Pr6.js +1 -0
- package/dist/assets/chunk-JZWAC4HX-u4uYphxM.js +3 -0
- package/dist/assets/{config-split-page-BGjVACdO.js → config-split-page-BMRGuCJQ.js} +1 -1
- package/dist/assets/{createLucideIcon-PPrXCGK8.js → createLucideIcon-BZkY6emz.js} +1 -1
- package/dist/assets/desktop-update-config-CD6-2PfI.js +1 -0
- package/dist/assets/{dialog-CTCX7oLf.js → dialog-csshWetU.js} +1 -1
- package/dist/assets/{dist-FL5e8mMi.js → dist-Bl94Ahwx.js} +1 -1
- package/dist/assets/{doc-browser-C02neCIE.js → doc-browser-BUlCkZo2.js} +1 -1
- package/dist/assets/doc-browser-CzCV73NJ.js +1 -0
- package/dist/assets/doc-browser-Doh2541x.js +1 -0
- package/dist/assets/{doc-browser-context-C-WPOji4.js → doc-browser-context-DfLHAWbG.js} +1 -1
- package/dist/assets/{es2015-BNy4R8AC.js → es2015-JCM5-KtW.js} +1 -1
- package/dist/assets/{external-link-BNtqJE01.js → external-link-Sw3ah_JD.js} +1 -1
- package/dist/assets/{folder-QyJHVUNz.js → folder-D7-VTnkz.js} +1 -1
- package/dist/assets/{hash-BGYUE-zr.js → hash-zajSTDXZ.js} +1 -1
- package/dist/assets/i18n-C5Mibli1.js +1 -0
- package/dist/assets/index-BTDFuKka.js +2 -0
- package/dist/assets/index-CUmk8xFK.css +1 -0
- package/dist/assets/{key-round-DenCfA2w.js → key-round-CnI1mc9F.js} +1 -1
- package/dist/assets/loader-circle-B5i8oMMY.js +1 -0
- package/dist/assets/{logo-badge-CKAxvQFc.js → logo-badge-BQgKnVtz.js} +1 -1
- package/dist/assets/{logos-CqXnaJIm.js → logos-CqVm0q0W.js} +1 -1
- package/dist/assets/marketplace-page-DJGDpTAo.js +1 -0
- package/dist/assets/{marketplace-page-XnDa2ulT.js → marketplace-page-DxlxHCFm.js} +2 -2
- package/dist/assets/mcp-marketplace-page-5UjYRWOR.js +40 -0
- package/dist/assets/mcp-marketplace-page-C1XaHZZO.js +1 -0
- package/dist/assets/message-square-D6Z4NwpG.js +1 -0
- package/dist/assets/{model-config-ByeL6Toe.js → model-config-PccJ9XyH.js} +1 -1
- package/dist/assets/{notice-card-D00-02yg.js → notice-card-CCgk6FvF.js} +1 -1
- package/dist/assets/play-D8WJLnJe.js +1 -0
- package/dist/assets/plus-Di0KAkiO.js +1 -0
- package/dist/assets/{popover-AmJkxio3.js → popover-YAsxDBhY.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-CfFJsJp-.js → provider-scoped-model-input-CzpF7cug.js} +1 -1
- package/dist/assets/{providers-list-HMQzW2WV.js → providers-list-8qDMER8o.js} +1 -1
- package/dist/assets/{refresh-ccw-B-dhb3yS.js → refresh-ccw-Bii4w8aB.js} +1 -1
- package/dist/assets/refresh-cw-BxojR62w.js +1 -0
- package/dist/assets/remote-D4TtLPAp.js +1 -0
- package/dist/assets/{rotate-cw-BWqAG3Fv.js → rotate-cw-1Xqa7LZ8.js} +1 -1
- package/dist/assets/runtime-config-page-D-4c5H5z.js +1 -0
- package/dist/assets/{save-DpdkGieJ.js → save--BVI5wZX.js} +1 -1
- package/dist/assets/search-config-D3a65l3r.js +1 -0
- package/dist/assets/{search-CQUdr7j_.js → search-vChioOoe.js} +1 -1
- package/dist/assets/{secrets-config-YCsGd1am.js → secrets-config-CoMlR_7i.js} +2 -2
- package/dist/assets/{select-DVUtSFHZ.js → select-DIZrwsKU.js} +1 -1
- package/dist/assets/{sessions-config-page-BKN-XdKr.js → sessions-config-page-Cc0TJStn.js} +2 -2
- package/dist/assets/{setting-row-Cb5-lFs-.js → setting-row-DiQyrE81.js} +1 -1
- package/dist/assets/{settings-DgtZZlnF.js → settings-CiRChctQ.js} +1 -1
- package/dist/assets/skeleton-CFQRIUzt.js +1 -0
- package/dist/assets/{sparkles-DNSCyDhL.js → sparkles-D1ZKWdm4.js} +1 -1
- package/dist/assets/{status-dot-X_j51OfA.js → status-dot-Dv_hiUVa.js} +1 -1
- package/dist/assets/{tabs-custom-CcWmekaF.js → tabs-custom-CsACkVji.js} +1 -1
- package/dist/assets/{tag-chip-fdbK2wE6.js → tag-chip-C3wDBe_-.js} +1 -1
- package/dist/assets/theme-provider-aOmrJ9J6.js +1 -0
- package/dist/assets/{tooltip-BkZCQcKw.js → tooltip-Dq5Xehpk.js} +1 -1
- package/dist/assets/{trash-2-CqciSCsg.js → trash-2-rY9ZteZX.js} +1 -1
- package/dist/assets/use-config-BQJjq1mP.js +1 -0
- package/dist/assets/{use-confirm-dialog-DSrb9205.js → use-confirm-dialog-DBoV5n5P.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-DmowtyTI.js → use-infinite-scroll-loader-JAicqVC5.js} +1 -1
- package/dist/assets/{use-viewport-layout-CaALCA51.js → use-viewport-layout-BX3XqzJ4.js} +1 -1
- package/dist/assets/x-DpTzXQcX.js +1 -0
- package/dist/index.html +40 -39
- package/package.json +9 -6
- package/src/app/hooks/use-realtime-query-bridge.ts +5 -5
- package/src/app/index.tsx +7 -1
- package/src/features/channels/components/config/channel-form.tsx +3 -3
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +1 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -0
- package/src/features/chat/components/conversation/chat-input-bar.container.tsx +9 -4
- package/src/features/chat/components/conversation/chat-message-list.container.test.tsx +64 -6
- package/src/features/chat/components/conversation/chat-message-list.container.tsx +185 -17
- package/src/features/chat/components/session/session-context-icon.tsx +1 -4
- package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +3 -1
- package/src/features/chat/hooks/use-ncp-chat-page-data.ts +7 -6
- package/src/features/chat/hooks/use-ncp-session-conversation.test.tsx +74 -2
- package/src/features/chat/hooks/use-ncp-session-conversation.ts +32 -10
- package/src/features/chat/hooks/use-selected-session-context-window-indicator.ts +20 -0
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +25 -0
- package/src/features/chat/managers/ncp-chat-input.manager.ts +5 -1
- package/src/features/chat/pages/ncp-chat-page.test.ts +22 -8
- package/src/features/chat/pages/ncp-chat-page.tsx +15 -11
- package/src/features/chat/stores/chat-thread.store.ts +8 -2
- package/src/features/chat/utils/chat-context-window-indicator.utils.ts +50 -0
- package/src/features/chat/utils/chat-runtime.utils.ts +1 -1
- package/src/features/chat/utils/chat-session-preference-governance.utils.test.tsx +114 -0
- package/src/features/chat/utils/chat-session-preference-governance.utils.ts +30 -36
- package/src/features/chat/utils/ncp-chat-runtime-availability.utils.test.ts +165 -0
- package/src/features/chat/utils/ncp-chat-runtime-availability.utils.ts +50 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +27 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +6 -4
- package/src/features/chat/utils/ncp-session-context-metadata.utils.ts +121 -0
- package/src/features/chat/utils/session-context.utils.ts +1 -2
- package/src/features/system-status/components/config/runtime-config-editor.tsx +6 -0
- package/src/features/system-status/components/config/runtime-settings-card.tsx +12 -0
- package/src/features/system-status/components/desktop-update-config.test.tsx +17 -7
- package/src/features/system-status/components/desktop-update-config.tsx +75 -30
- package/src/features/system-status/hooks/use-system-status.ts +0 -11
- package/src/features/system-status/index.ts +4 -1
- package/src/features/system-status/managers/runtime-update.manager.ts +330 -0
- package/src/features/system-status/managers/system-status.manager.test.ts +0 -25
- package/src/features/system-status/managers/system-status.manager.ts +1 -30
- package/src/features/system-status/stores/runtime-update.store.ts +24 -0
- package/src/features/system-status/types/system-status.types.ts +0 -2
- package/src/features/system-status/utils/runtime-config-agent.utils.ts +6 -1
- package/src/features/system-status/utils/system-status.utils.test.ts +1 -85
- package/src/features/system-status/utils/system-status.utils.ts +1 -23
- package/src/platforms/desktop/managers/desktop-update.manager.ts +6 -0
- package/src/platforms/desktop/types/desktop-update.types.ts +21 -19
- package/src/shared/components/common/brand-header.test.tsx +142 -0
- package/src/shared/components/common/brand-header.tsx +93 -0
- package/src/shared/components/cron-config.tsx +1 -1
- package/src/shared/components/doc-browser/doc-browser-context.test.tsx +1 -1
- package/src/shared/components/doc-browser/doc-browser.tsx +1 -1
- package/src/shared/components/search-config.tsx +3 -3
- package/src/shared/lib/api/README.md +3 -0
- package/src/shared/lib/api/index.ts +13 -11
- package/src/shared/lib/api/ncp-session.test.ts +17 -18
- package/src/shared/lib/api/ncp-session.types.ts +92 -0
- package/src/shared/lib/api/raw-client.utils.ts +3 -126
- package/src/shared/lib/api/services/agents.service.ts +18 -0
- package/src/shared/lib/api/services/channel-auth.service.ts +21 -0
- package/src/shared/lib/api/{client.ts → services/client.service.ts} +45 -1
- package/src/shared/lib/api/services/config.service.ts +171 -0
- package/src/shared/lib/api/services/marketplace.service.ts +66 -0
- package/src/shared/lib/api/services/mcp-marketplace.service.ts +70 -0
- package/src/shared/lib/api/services/ncp-attachments.service.ts +14 -0
- package/src/shared/lib/api/services/ncp-session.service.ts +39 -0
- package/src/shared/lib/api/services/remote.service.ts +50 -0
- package/src/shared/lib/api/services/runtime-control.service.ts +18 -0
- package/src/shared/lib/api/services/runtime-update.service.ts +26 -0
- package/src/shared/lib/api/services/server-path.service.ts +16 -0
- package/src/shared/lib/api/types.ts +9 -74
- package/src/shared/lib/i18n/{chat.ts → chat-labels.utils.ts} +13 -1
- package/src/shared/lib/i18n/desktop-update-labels.utils.ts +65 -0
- package/src/shared/lib/i18n/index.ts +4 -5
- package/src/shared/lib/i18n/runtime/i18n-language-owner.ts +5 -5
- package/src/shared/lib/transport/index.ts +1 -0
- package/src/shared/lib/transport/local-transport.service.ts +24 -4
- package/src/shared/lib/transport/remote-transport.service.ts +2 -2
- package/src/shared/lib/transport/request-raw-api-response.utils.ts +133 -0
- package/src/shared/lib/transport/transport.types.ts +8 -2
- package/src/shared/lib/ui-document-title/index.ts +1 -1
- package/tsconfig.json +1 -0
- package/dist/assets/api-BurjmW4A.js +0 -15
- package/dist/assets/app-manager-provider-DhxUmyTv.js +0 -1
- package/dist/assets/app-navigation.config-Bpd16Pem.js +0 -1
- package/dist/assets/chat-page-Cc7n80lW.js +0 -1
- package/dist/assets/chunk-JZWAC4HX-24FLdHl7.js +0 -3
- package/dist/assets/desktop-update-config-fMLlSStv.js +0 -1
- package/dist/assets/doc-browser-COj7x090.js +0 -1
- package/dist/assets/doc-browser-fyn7eDTp.js +0 -1
- package/dist/assets/i18n-CM4y8Mw9.js +0 -1
- package/dist/assets/index-CtVSzMPM.js +0 -2
- package/dist/assets/index-N3hjuljD.css +0 -1
- package/dist/assets/loader-circle-R23uEPkM.js +0 -1
- package/dist/assets/marketplace-page-mF-M5mku.js +0 -1
- package/dist/assets/mcp-marketplace-page-BArKWcRZ.js +0 -40
- package/dist/assets/mcp-marketplace-page-DBUcIIHJ.js +0 -1
- package/dist/assets/message-square-Dm34zD6k.js +0 -1
- package/dist/assets/play-ul4L6MWm.js +0 -1
- package/dist/assets/plus-D14303DH.js +0 -1
- package/dist/assets/remote-B4ELSd3u.js +0 -1
- package/dist/assets/runtime-config-page-N4FP6H0M.js +0 -1
- package/dist/assets/search-config-B62TY-z2.js +0 -1
- package/dist/assets/skeleton-BCPi52jT.js +0 -1
- package/dist/assets/theme-provider-WTWq_jYq.js +0 -1
- package/dist/assets/use-config-CyvhbRhf.js +0 -1
- package/dist/assets/x-tYcSDsrY.js +0 -1
- package/src/shared/lib/api/agents.ts +0 -34
- package/src/shared/lib/api/channel-auth.ts +0 -35
- package/src/shared/lib/api/config.ts +0 -362
- package/src/shared/lib/api/marketplace.ts +0 -156
- package/src/shared/lib/api/mcp-marketplace.ts +0 -138
- package/src/shared/lib/api/ncp-attachments.ts +0 -41
- package/src/shared/lib/api/ncp-session.ts +0 -78
- package/src/shared/lib/api/remote.ts +0 -86
- package/src/shared/lib/api/runtime-control.ts +0 -34
- package/src/shared/lib/api/server-path.ts +0 -46
- /package/dist/assets/{config-hints-CPNzbMEp.js → config-hints-MogHYQ8G.js} +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import type { UpdatePreferences, UpdateSnapshot } from '@nextclaw/kernel';
|
|
2
|
+
import { applyRuntimeUpdate, checkRuntimeUpdate, downloadRuntimeUpdate, fetchRuntimeUpdate, updateRuntimeUpdateChannel, updateRuntimeUpdatePreferences } from '@/shared/lib/api';
|
|
3
|
+
import type { NextClawDesktopBridge } from '@/platforms/desktop';
|
|
4
|
+
import { t } from '@/shared/lib/i18n';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { useRuntimeUpdateStore, type RuntimeUpdateBusyAction } from '@/features/system-status/stores/runtime-update.store';
|
|
7
|
+
|
|
8
|
+
type RuntimeUpdateSourceKind = 'desktop-bridge' | 'runtime-host';
|
|
9
|
+
|
|
10
|
+
interface RuntimeUpdateSourceBase {
|
|
11
|
+
getState: () => Promise<UpdateSnapshot>;
|
|
12
|
+
checkForUpdates: () => Promise<UpdateSnapshot>;
|
|
13
|
+
downloadUpdate: () => Promise<UpdateSnapshot>;
|
|
14
|
+
applyDownloadedUpdate: () => Promise<UpdateSnapshot>;
|
|
15
|
+
updatePreferences: (preferences: Partial<UpdatePreferences>) => Promise<UpdateSnapshot>;
|
|
16
|
+
updateChannel: (channel: UpdateSnapshot['channel']) => Promise<UpdateSnapshot>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DesktopBridgeRuntimeUpdateSourceContract extends RuntimeUpdateSourceBase {
|
|
20
|
+
kind: 'desktop-bridge';
|
|
21
|
+
subscribe: (listener: (snapshot: UpdateSnapshot) => void) => () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface HostRuntimeUpdateSourceContract extends RuntimeUpdateSourceBase {
|
|
25
|
+
kind: 'runtime-host';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const RUNTIME_HOST_POLL_INTERVAL_MS = 1000;
|
|
29
|
+
|
|
30
|
+
type RuntimeUpdateSource = DesktopBridgeRuntimeUpdateSourceContract | HostRuntimeUpdateSourceContract;
|
|
31
|
+
|
|
32
|
+
class DesktopBridgeRuntimeUpdateSource implements DesktopBridgeRuntimeUpdateSourceContract {
|
|
33
|
+
readonly kind = 'desktop-bridge' as const;
|
|
34
|
+
|
|
35
|
+
constructor(private readonly desktopApi: NextClawDesktopBridge) {}
|
|
36
|
+
|
|
37
|
+
subscribe = (listener: (snapshot: UpdateSnapshot) => void) => {
|
|
38
|
+
return this.desktopApi.onUpdateStateChanged(listener);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
getState = async (): Promise<UpdateSnapshot> => {
|
|
42
|
+
return await this.desktopApi.getUpdateState();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
checkForUpdates = async (): Promise<UpdateSnapshot> => {
|
|
46
|
+
return await this.desktopApi.checkForUpdates();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
downloadUpdate = async (): Promise<UpdateSnapshot> => {
|
|
50
|
+
return await this.desktopApi.downloadUpdate();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
applyDownloadedUpdate = async (): Promise<UpdateSnapshot> => {
|
|
54
|
+
return await this.desktopApi.applyDownloadedUpdate();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
updatePreferences = async (preferences: Partial<UpdatePreferences>): Promise<UpdateSnapshot> => {
|
|
58
|
+
return await this.desktopApi.updatePreferences(preferences);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
updateChannel = async (channel: UpdateSnapshot['channel']): Promise<UpdateSnapshot> => {
|
|
62
|
+
return await this.desktopApi.updateChannel(channel);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class HostRuntimeUpdateSource implements HostRuntimeUpdateSourceContract {
|
|
67
|
+
readonly kind = 'runtime-host' as const;
|
|
68
|
+
|
|
69
|
+
getState = async (): Promise<UpdateSnapshot> => {
|
|
70
|
+
return await fetchRuntimeUpdate();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
checkForUpdates = async (): Promise<UpdateSnapshot> => {
|
|
74
|
+
return await checkRuntimeUpdate();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
downloadUpdate = async (): Promise<UpdateSnapshot> => {
|
|
78
|
+
return await downloadRuntimeUpdate();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
applyDownloadedUpdate = async (): Promise<UpdateSnapshot> => {
|
|
82
|
+
return await applyRuntimeUpdate();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
updatePreferences = async (preferences: Partial<UpdatePreferences>): Promise<UpdateSnapshot> => {
|
|
86
|
+
return await updateRuntimeUpdatePreferences(preferences);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
updateChannel = async (channel: UpdateSnapshot['channel']): Promise<UpdateSnapshot> => {
|
|
90
|
+
return await updateRuntimeUpdateChannel(channel);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class RuntimeUpdateManager {
|
|
95
|
+
private unsubscribe: (() => void) | null = null;
|
|
96
|
+
private pollingTimer: number | null = null;
|
|
97
|
+
private subscriptionCount = 0;
|
|
98
|
+
private source: RuntimeUpdateSource | null = null;
|
|
99
|
+
|
|
100
|
+
start = async () => {
|
|
101
|
+
this.subscriptionCount += 1;
|
|
102
|
+
const source = this.resolveSource();
|
|
103
|
+
this.source = source;
|
|
104
|
+
if (!source) {
|
|
105
|
+
useRuntimeUpdateStore.setState({
|
|
106
|
+
supported: false,
|
|
107
|
+
initialized: true,
|
|
108
|
+
snapshot: null
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (source.kind === 'desktop-bridge' && !this.unsubscribe) {
|
|
114
|
+
this.unsubscribe = source.subscribe((snapshot) => {
|
|
115
|
+
useRuntimeUpdateStore.setState({
|
|
116
|
+
supported: true,
|
|
117
|
+
initialized: true,
|
|
118
|
+
snapshot
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (source.kind === 'runtime-host') {
|
|
124
|
+
this.ensurePolling();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
useRuntimeUpdateStore.setState({
|
|
128
|
+
supported: true,
|
|
129
|
+
initialized: false
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const snapshot = await source.getState();
|
|
134
|
+
useRuntimeUpdateStore.setState({
|
|
135
|
+
supported: true,
|
|
136
|
+
initialized: true,
|
|
137
|
+
snapshot
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (source.kind === 'runtime-host' && this.isUnsupportedError(error)) {
|
|
141
|
+
useRuntimeUpdateStore.setState({
|
|
142
|
+
supported: false,
|
|
143
|
+
initialized: true,
|
|
144
|
+
snapshot: null
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
useRuntimeUpdateStore.setState({
|
|
149
|
+
supported: true,
|
|
150
|
+
initialized: true
|
|
151
|
+
});
|
|
152
|
+
toast.error(`${t('runtimeUpdatesLoadFailed')}: ${this.getErrorMessage(error)}`);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
stop = () => {
|
|
157
|
+
this.subscriptionCount = Math.max(0, this.subscriptionCount - 1);
|
|
158
|
+
if (this.subscriptionCount > 0) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.unsubscribe?.();
|
|
162
|
+
this.unsubscribe = null;
|
|
163
|
+
if (this.pollingTimer !== null && typeof window !== 'undefined') {
|
|
164
|
+
window.clearInterval(this.pollingTimer);
|
|
165
|
+
}
|
|
166
|
+
this.pollingTimer = null;
|
|
167
|
+
this.source = null;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
checkForUpdates = async () => {
|
|
171
|
+
let snapshot: UpdateSnapshot;
|
|
172
|
+
try {
|
|
173
|
+
snapshot = await this.runSnapshotCommand('checking', t('runtimeUpdatesCheckFailed'), async (source) => {
|
|
174
|
+
return await source.checkForUpdates();
|
|
175
|
+
});
|
|
176
|
+
} catch {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (snapshot.status === 'up-to-date') {
|
|
181
|
+
toast.success(t('runtimeUpdatesAlreadyLatest'));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (snapshot.status === 'update-available') {
|
|
185
|
+
toast.success(
|
|
186
|
+
t('runtimeUpdatesAvailable').replace('{version}', snapshot.availableVersion ?? t('runtimeUpdatesUnknownVersion'))
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (snapshot.status === 'downloaded') {
|
|
191
|
+
toast.success(t('runtimeUpdatesReadyToApply'));
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
downloadUpdate = async () => {
|
|
196
|
+
try {
|
|
197
|
+
await this.runSnapshotCommand('downloading', t('runtimeUpdatesDownloadFailed'), async (source) => {
|
|
198
|
+
return await source.downloadUpdate();
|
|
199
|
+
});
|
|
200
|
+
} catch {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
applyDownloadedUpdate = async () => {
|
|
206
|
+
try {
|
|
207
|
+
await this.runSnapshotCommand('applying', t('runtimeUpdatesApplyFailed'), async (source) => {
|
|
208
|
+
return await source.applyDownloadedUpdate();
|
|
209
|
+
});
|
|
210
|
+
} catch {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
updatePreferences = async (preferences: Partial<UpdatePreferences>) => {
|
|
216
|
+
try {
|
|
217
|
+
await this.runSnapshotCommand('saving-preferences', t('runtimeUpdatesPreferencesFailed'), async (source) => {
|
|
218
|
+
return await source.updatePreferences(preferences);
|
|
219
|
+
});
|
|
220
|
+
} catch {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
updateChannel = async (channel: UpdateSnapshot['channel']) => {
|
|
226
|
+
const currentChannel = useRuntimeUpdateStore.getState().snapshot?.channel;
|
|
227
|
+
if (currentChannel === channel) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let snapshot: UpdateSnapshot;
|
|
232
|
+
try {
|
|
233
|
+
snapshot = await this.runSnapshotCommand('switching-channel', t('runtimeUpdatesChannelChangeFailed'), async (source) => {
|
|
234
|
+
return await source.updateChannel(channel);
|
|
235
|
+
});
|
|
236
|
+
} catch {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (snapshot.status === 'update-available' && snapshot.availableVersion) {
|
|
241
|
+
toast.success(
|
|
242
|
+
t('runtimeUpdatesChannelChangedWithUpdate')
|
|
243
|
+
.replace('{channel}', this.getChannelLabel(channel))
|
|
244
|
+
.replace('{version}', snapshot.availableVersion)
|
|
245
|
+
);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
toast.success(t('runtimeUpdatesChannelChanged').replace('{channel}', this.getChannelLabel(channel)));
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
private ensurePolling = () => {
|
|
253
|
+
if (this.pollingTimer !== null || typeof window === 'undefined') {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
this.pollingTimer = window.setInterval(() => {
|
|
257
|
+
void this.refreshSnapshot();
|
|
258
|
+
}, RUNTIME_HOST_POLL_INTERVAL_MS);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
private refreshSnapshot = async () => {
|
|
262
|
+
if (!this.source || this.source.kind !== 'runtime-host') {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const snapshot = await this.source.getState();
|
|
267
|
+
useRuntimeUpdateStore.setState({
|
|
268
|
+
supported: true,
|
|
269
|
+
initialized: true,
|
|
270
|
+
snapshot
|
|
271
|
+
});
|
|
272
|
+
} catch {
|
|
273
|
+
// keep the latest successful snapshot visible
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
private runSnapshotCommand = async (
|
|
278
|
+
busyAction: RuntimeUpdateBusyAction,
|
|
279
|
+
fallbackMessage: string,
|
|
280
|
+
job: (source: RuntimeUpdateSource) => Promise<UpdateSnapshot>
|
|
281
|
+
): Promise<UpdateSnapshot> => {
|
|
282
|
+
const source = this.source ?? this.resolveSource();
|
|
283
|
+
if (!source) {
|
|
284
|
+
throw new Error(t('runtimeUpdatesUnavailableDescription'));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.source = source;
|
|
288
|
+
useRuntimeUpdateStore.setState({ busyAction });
|
|
289
|
+
try {
|
|
290
|
+
const snapshot = await job(source);
|
|
291
|
+
useRuntimeUpdateStore.setState({ snapshot });
|
|
292
|
+
return snapshot;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
toast.error(`${fallbackMessage}: ${this.getErrorMessage(error)}`);
|
|
295
|
+
throw error;
|
|
296
|
+
} finally {
|
|
297
|
+
useRuntimeUpdateStore.setState({ busyAction: null });
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
private resolveSource = (): RuntimeUpdateSource | null => {
|
|
302
|
+
const desktopApi = this.getDesktopApi();
|
|
303
|
+
if (desktopApi) {
|
|
304
|
+
return new DesktopBridgeRuntimeUpdateSource(desktopApi);
|
|
305
|
+
}
|
|
306
|
+
return new HostRuntimeUpdateSource();
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
private getDesktopApi = (): NextClawDesktopBridge | null => {
|
|
310
|
+
if (typeof window === 'undefined') {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
return window.nextclawDesktop ?? null;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
private isUnsupportedError = (error: unknown): boolean => {
|
|
317
|
+
const message = this.getErrorMessage(error).toLowerCase();
|
|
318
|
+
return message.includes('404') || message.includes('not found') || message.includes('endpoint not found');
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
private getErrorMessage = (error: unknown): string => {
|
|
322
|
+
return error instanceof Error ? error.message : t('error');
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
private getChannelLabel = (channel: UpdateSnapshot['channel']): string => {
|
|
326
|
+
return channel === 'beta' ? t('desktopUpdatesChannelBeta') : t('desktopUpdatesChannelStable');
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export const runtimeUpdateManager = new RuntimeUpdateManager();
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type { BootstrapStatusView } from '@/shared/lib/api';
|
|
3
3
|
import { appQueryClient } from '@/app-query-client';
|
|
4
|
-
import { t } from '@/shared/lib/i18n';
|
|
5
4
|
import {
|
|
6
5
|
isTransientRuntimeConnectionErrorMessage,
|
|
7
6
|
systemStatusManager,
|
|
@@ -120,30 +119,6 @@ describe('systemStatusManager', () => {
|
|
|
120
119
|
expect(useSystemStatusStore.getState().state.lifecyclePhase).toBe('stalled');
|
|
121
120
|
});
|
|
122
121
|
|
|
123
|
-
it('maps transient chat errors to friendly recovery copy while recovering', () => {
|
|
124
|
-
systemStatusManager.reportBootstrapStatus(readyBootstrapStatus);
|
|
125
|
-
systemStatusManager.handleConnectionInterrupted('Failed to fetch');
|
|
126
|
-
|
|
127
|
-
expect(
|
|
128
|
-
systemStatusManager.getDisplayMessage(
|
|
129
|
-
'NCP fetch failed for POST /api/ncp/agent: Error: Failed to fetch'
|
|
130
|
-
)
|
|
131
|
-
).toBe(t('runtimeControlRecoveringHelp'));
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('suppresses transient transport errors after recovery stalls', async () => {
|
|
135
|
-
systemStatusManager.reportBootstrapStatus(readyBootstrapStatus);
|
|
136
|
-
systemStatusManager.handleConnectionInterrupted('Failed to fetch');
|
|
137
|
-
|
|
138
|
-
await vi.advanceTimersByTimeAsync(30_000);
|
|
139
|
-
|
|
140
|
-
expect(
|
|
141
|
-
systemStatusManager.getDisplayMessage(
|
|
142
|
-
'NCP fetch failed for POST /api/ncp/agent: Error: Failed to fetch'
|
|
143
|
-
)
|
|
144
|
-
).toBeNull();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
122
|
it('restores readiness from stalled once the realtime connection reopens', async () => {
|
|
148
123
|
systemStatusManager.reportBootstrapStatus(readyBootstrapStatus);
|
|
149
124
|
systemStatusManager.handleConnectionInterrupted('websocket error');
|
|
@@ -15,7 +15,6 @@ import type { NextClawDesktopBridge } from '@/platforms/desktop';
|
|
|
15
15
|
import { t } from '@/shared/lib/i18n';
|
|
16
16
|
import {
|
|
17
17
|
buildActiveSystemActionState,
|
|
18
|
-
resolveChatRuntimeMessage,
|
|
19
18
|
toSystemStatusView,
|
|
20
19
|
} from '@/features/system-status/utils/system-status.utils';
|
|
21
20
|
import {
|
|
@@ -245,35 +244,7 @@ export class SystemStatusManager {
|
|
|
245
244
|
}
|
|
246
245
|
};
|
|
247
246
|
|
|
248
|
-
|
|
249
|
-
return toSystemStatusView(this.getState()).isChatBlocked;
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
getDisplayMessage = (message: string | null | undefined): string | null => {
|
|
253
|
-
if (!message?.trim()) {
|
|
254
|
-
return resolveChatRuntimeMessage(this.getState());
|
|
255
|
-
}
|
|
256
|
-
const { phase } = toSystemStatusView(this.getState());
|
|
257
|
-
if (
|
|
258
|
-
phase === 'service-transitioning' &&
|
|
259
|
-
this.getState().activeSystemAction?.message?.trim()
|
|
260
|
-
) {
|
|
261
|
-
return this.getState().activeSystemAction?.message?.trim() ?? null;
|
|
262
|
-
}
|
|
263
|
-
if (
|
|
264
|
-
phase === 'recovering' &&
|
|
265
|
-
isTransientRuntimeConnectionErrorMessage(message)
|
|
266
|
-
) {
|
|
267
|
-
return t('runtimeControlRecoveringHelp');
|
|
268
|
-
}
|
|
269
|
-
if (
|
|
270
|
-
phase === 'stalled' &&
|
|
271
|
-
isTransientRuntimeConnectionErrorMessage(message)
|
|
272
|
-
) {
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
return message;
|
|
276
|
-
};
|
|
247
|
+
getStatusView = () => toSystemStatusView(this.getState());
|
|
277
248
|
|
|
278
249
|
resetForTests = (): void => {
|
|
279
250
|
this.clearRecoveryTimeout();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { UpdateSnapshot } from '@nextclaw/kernel';
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
|
|
4
|
+
export type RuntimeUpdateBusyAction =
|
|
5
|
+
| 'checking'
|
|
6
|
+
| 'downloading'
|
|
7
|
+
| 'applying'
|
|
8
|
+
| 'saving-preferences'
|
|
9
|
+
| 'switching-channel'
|
|
10
|
+
| null;
|
|
11
|
+
|
|
12
|
+
type RuntimeUpdateStoreState = {
|
|
13
|
+
supported: boolean;
|
|
14
|
+
initialized: boolean;
|
|
15
|
+
busyAction: RuntimeUpdateBusyAction;
|
|
16
|
+
snapshot: UpdateSnapshot | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const useRuntimeUpdateStore = create<RuntimeUpdateStoreState>(() => ({
|
|
20
|
+
supported: false,
|
|
21
|
+
initialized: false,
|
|
22
|
+
busyAction: null,
|
|
23
|
+
snapshot: null
|
|
24
|
+
}));
|
|
@@ -46,8 +46,6 @@ export type SystemStatusState = {
|
|
|
46
46
|
export type SystemStatusView = SystemStatusState & {
|
|
47
47
|
phase: SystemStatusPhase;
|
|
48
48
|
connectionStatus: SystemConnectionStatus;
|
|
49
|
-
isChatBlocked: boolean;
|
|
50
|
-
chatMessage: string | null;
|
|
51
49
|
};
|
|
52
50
|
|
|
53
51
|
export type RuntimeStatusTone = 'healthy' | 'attention' | 'inactive';
|
|
@@ -4,7 +4,7 @@ import { t } from '@/shared/lib/i18n';
|
|
|
4
4
|
export type DmScope = 'main' | 'per-peer' | 'per-channel-peer' | 'per-account-channel-peer';
|
|
5
5
|
export type PeerKind = '' | 'direct' | 'group' | 'channel';
|
|
6
6
|
export type RuntimeEntryDraft = RuntimeEntryView & { id: string; configText: string };
|
|
7
|
-
export type RuntimeConfigEditorState = { agents: AgentProfileView[]; bindings: AgentBindingView[]; runtimeEntries: RuntimeEntryDraft[]; dmScope: DmScope; defaultContextTokens: number; defaultEngine: string };
|
|
7
|
+
export type RuntimeConfigEditorState = { companionEnabled: boolean; agents: AgentProfileView[]; bindings: AgentBindingView[]; runtimeEntries: RuntimeEntryDraft[]; dmScope: DmScope; defaultContextTokens: number; defaultEngine: string };
|
|
8
8
|
|
|
9
9
|
const DEFAULT_NARP_STDIO_ENTRY_CONFIG = {
|
|
10
10
|
wireDialect: 'acp',
|
|
@@ -56,6 +56,7 @@ export function parseOptionalInt(value: string): number | undefined {
|
|
|
56
56
|
|
|
57
57
|
export function createRuntimeConfigEditorState(config: ConfigView): RuntimeConfigEditorState {
|
|
58
58
|
return {
|
|
59
|
+
companionEnabled: config.companion?.enabled === true,
|
|
59
60
|
agents: (config.agents.list ?? []).map(hydrateRuntimeAgent),
|
|
60
61
|
bindings: (config.bindings ?? []).map(hydrateRuntimeBinding),
|
|
61
62
|
runtimeEntries: Object.entries(config.agents.runtimes?.entries ?? {}).map(([id, entry]) => ({ id, enabled: entry.enabled !== false, label: entry.label ?? '', type: entry.type, config: entry.config ?? {}, configText: JSON.stringify(entry.config ?? {}, null, 2) })),
|
|
@@ -81,6 +82,7 @@ export function toPersistedRuntimeAgent(agent: AgentProfileView): AgentProfileVi
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
export function createRuntimeConfigUpdatePayload(input: {
|
|
85
|
+
companionEnabled: boolean;
|
|
84
86
|
agents: AgentProfileView[];
|
|
85
87
|
bindings: AgentBindingView[];
|
|
86
88
|
runtimeEntries: RuntimeEntryDraft[];
|
|
@@ -134,6 +136,9 @@ export function createRuntimeConfigUpdatePayload(input: {
|
|
|
134
136
|
return entries;
|
|
135
137
|
}, {});
|
|
136
138
|
return {
|
|
139
|
+
companion: {
|
|
140
|
+
enabled: input.companionEnabled
|
|
141
|
+
},
|
|
137
142
|
agents: {
|
|
138
143
|
defaults: {
|
|
139
144
|
contextTokens: Math.max(1000, input.defaultContextTokens),
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { t } from '@/shared/lib/i18n';
|
|
3
2
|
import {
|
|
4
|
-
resolveChatRuntimeMessage,
|
|
5
3
|
resolveSystemConnectionStatus,
|
|
6
4
|
toSystemStatusView,
|
|
7
5
|
} from './system-status.utils';
|
|
@@ -26,88 +24,8 @@ describe('resolveSystemConnectionStatus', () => {
|
|
|
26
24
|
});
|
|
27
25
|
});
|
|
28
26
|
|
|
29
|
-
describe('resolveChatRuntimeMessage', () => {
|
|
30
|
-
it('uses the startup message during cold start', () => {
|
|
31
|
-
expect(
|
|
32
|
-
resolveChatRuntimeMessage({
|
|
33
|
-
lifecyclePhase: 'cold-starting',
|
|
34
|
-
hasReachedReady: false,
|
|
35
|
-
lastReadyAt: null,
|
|
36
|
-
recoveryStartedAt: null,
|
|
37
|
-
bootstrapStatus: null,
|
|
38
|
-
lastError: null,
|
|
39
|
-
lastTransportError: null,
|
|
40
|
-
runtimeControlView: null,
|
|
41
|
-
runtimeControlError: null,
|
|
42
|
-
activeSystemAction: null,
|
|
43
|
-
lastSystemActionError: null,
|
|
44
|
-
})
|
|
45
|
-
).toBe(t('chatRuntimeInitializing'));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('uses the bootstrap error when startup failed', () => {
|
|
49
|
-
expect(
|
|
50
|
-
resolveChatRuntimeMessage({
|
|
51
|
-
lifecyclePhase: 'startup-failed',
|
|
52
|
-
hasReachedReady: false,
|
|
53
|
-
lastReadyAt: null,
|
|
54
|
-
recoveryStartedAt: null,
|
|
55
|
-
bootstrapStatus: {
|
|
56
|
-
phase: 'error',
|
|
57
|
-
ncpAgent: {
|
|
58
|
-
state: 'error',
|
|
59
|
-
error: 'boom',
|
|
60
|
-
},
|
|
61
|
-
pluginHydration: {
|
|
62
|
-
state: 'pending',
|
|
63
|
-
loadedPluginCount: 0,
|
|
64
|
-
totalPluginCount: 0,
|
|
65
|
-
},
|
|
66
|
-
channels: {
|
|
67
|
-
state: 'pending',
|
|
68
|
-
enabled: [],
|
|
69
|
-
},
|
|
70
|
-
remote: {
|
|
71
|
-
state: 'pending',
|
|
72
|
-
},
|
|
73
|
-
lastError: 'boom',
|
|
74
|
-
},
|
|
75
|
-
lastError: null,
|
|
76
|
-
lastTransportError: null,
|
|
77
|
-
runtimeControlView: null,
|
|
78
|
-
runtimeControlError: null,
|
|
79
|
-
activeSystemAction: null,
|
|
80
|
-
lastSystemActionError: null,
|
|
81
|
-
})
|
|
82
|
-
).toBe('boom');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('prefers the centralized action message while a system action is running', () => {
|
|
86
|
-
expect(
|
|
87
|
-
resolveChatRuntimeMessage({
|
|
88
|
-
lifecyclePhase: 'ready',
|
|
89
|
-
hasReachedReady: true,
|
|
90
|
-
lastReadyAt: Date.now(),
|
|
91
|
-
recoveryStartedAt: null,
|
|
92
|
-
bootstrapStatus: null,
|
|
93
|
-
lastError: null,
|
|
94
|
-
lastTransportError: null,
|
|
95
|
-
runtimeControlView: null,
|
|
96
|
-
runtimeControlError: null,
|
|
97
|
-
activeSystemAction: {
|
|
98
|
-
action: 'restart-service',
|
|
99
|
-
lifecycle: 'recovering',
|
|
100
|
-
serviceState: null,
|
|
101
|
-
message: 'NextClaw 正在恢复连接',
|
|
102
|
-
},
|
|
103
|
-
lastSystemActionError: null,
|
|
104
|
-
})
|
|
105
|
-
).toBe('NextClaw 正在恢复连接');
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
27
|
describe('toSystemStatusView', () => {
|
|
110
|
-
it('
|
|
28
|
+
it('maps stalled to factual connection and lifecycle view fields', () => {
|
|
111
29
|
expect(
|
|
112
30
|
toSystemStatusView({
|
|
113
31
|
lifecyclePhase: 'stalled',
|
|
@@ -123,8 +41,6 @@ describe('toSystemStatusView', () => {
|
|
|
123
41
|
lastSystemActionError: null,
|
|
124
42
|
})
|
|
125
43
|
).toMatchObject({
|
|
126
|
-
isChatBlocked: true,
|
|
127
|
-
chatMessage: null,
|
|
128
44
|
connectionStatus: 'disconnected',
|
|
129
45
|
phase: 'stalled',
|
|
130
46
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { t } from '@/shared/lib/i18n';
|
|
2
1
|
import type {
|
|
3
2
|
RuntimeControlAction,
|
|
4
3
|
RuntimeLifecycleState,
|
|
5
4
|
RuntimeServiceState,
|
|
6
5
|
} from '@/shared/lib/api';
|
|
6
|
+
import { t } from '@/shared/lib/i18n';
|
|
7
7
|
import type {
|
|
8
8
|
RuntimeControlPanelView,
|
|
9
9
|
RuntimeStatusBadgeView,
|
|
@@ -29,26 +29,6 @@ export function resolveSystemConnectionStatus(
|
|
|
29
29
|
return 'connecting';
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function resolveChatRuntimeMessage(
|
|
33
|
-
state: SystemStatusState
|
|
34
|
-
): string | null {
|
|
35
|
-
if (state.activeSystemAction?.message?.trim()) {
|
|
36
|
-
return state.activeSystemAction.message.trim();
|
|
37
|
-
}
|
|
38
|
-
if (state.lifecyclePhase === 'cold-starting') {
|
|
39
|
-
return t('chatRuntimeInitializing');
|
|
40
|
-
}
|
|
41
|
-
if (state.lifecyclePhase === 'startup-failed') {
|
|
42
|
-
return (
|
|
43
|
-
state.bootstrapStatus?.ncpAgent.error?.trim() ||
|
|
44
|
-
state.bootstrapStatus?.lastError?.trim() ||
|
|
45
|
-
state.lastError?.trim() ||
|
|
46
|
-
t('chatRuntimeInitializationFailed')
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
32
|
export function toSystemStatusView(
|
|
53
33
|
state: SystemStatusState
|
|
54
34
|
): SystemStatusView {
|
|
@@ -57,8 +37,6 @@ export function toSystemStatusView(
|
|
|
57
37
|
...state,
|
|
58
38
|
phase,
|
|
59
39
|
connectionStatus: resolveSystemConnectionStatus(phase),
|
|
60
|
-
isChatBlocked: phase !== 'ready',
|
|
61
|
-
chatMessage: resolveChatRuntimeMessage(state),
|
|
62
40
|
};
|
|
63
41
|
}
|
|
64
42
|
|
|
@@ -12,8 +12,10 @@ type DesktopUpdateBusyAction = 'checking' | 'downloading' | 'applying' | 'saving
|
|
|
12
12
|
|
|
13
13
|
export class DesktopUpdateManager {
|
|
14
14
|
private unsubscribe: (() => void) | null = null;
|
|
15
|
+
private subscriptionCount = 0;
|
|
15
16
|
|
|
16
17
|
start = async () => {
|
|
18
|
+
this.subscriptionCount += 1;
|
|
17
19
|
const desktopApi = this.getDesktopApi();
|
|
18
20
|
if (!desktopApi) {
|
|
19
21
|
useDesktopUpdateStore.setState({
|
|
@@ -56,6 +58,10 @@ export class DesktopUpdateManager {
|
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
stop = () => {
|
|
61
|
+
this.subscriptionCount = Math.max(0, this.subscriptionCount - 1);
|
|
62
|
+
if (this.subscriptionCount > 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
59
65
|
this.unsubscribe?.();
|
|
60
66
|
this.unsubscribe = null;
|
|
61
67
|
};
|