@agent-native/core 0.90.8 → 0.90.10
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 +7 -28
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +16 -0
- package/corpus/core/docs/content/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +3 -3
- package/corpus/core/docs/content/tracking.mdx +3 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +15 -4
- package/corpus/core/src/cli/deploy-build.ts +50 -0
- package/corpus/core/src/cli/index.ts +9 -4
- package/corpus/core/src/client/analytics.ts +17 -3
- package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +3 -1
- package/corpus/core/src/client/blocks/library/wireframe.tsx +6 -1
- package/corpus/core/src/deploy/build.ts +64 -27
- package/corpus/core/src/file-upload/builder.ts +10 -23
- package/corpus/core/src/file-upload/types.ts +3 -3
- package/corpus/core/src/styles/blocks.css +7 -0
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +9 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +21 -7
- package/corpus/templates/analytics/app/entry.client.tsx +6 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-07-07-analytics-now-recovers-from-stale-route-chunks-after-deploys.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-07-metric-dashboard-cards-now-show-warehouse-backed-numeric-val.md +6 -0
- package/corpus/templates/clips/actions/create-recording.ts +1 -1
- package/corpus/templates/clips/actions/finalize-recording.ts +2 -2
- package/corpus/templates/clips/actions/import-loom-recording.ts +1 -1
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +1 -1
- package/corpus/templates/clips/changelog/2026-07-07-clip-video-uploads-now-compress-in-the-background-while-keeping.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-desktop-settings-and-fn-dictation-are-more-reliable.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-desktop-settings-now-includes-a-manual-update-check-with-res.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +95 -8
- package/corpus/templates/clips/desktop/src/lib/updater.ts +60 -43
- package/corpus/templates/clips/desktop/src/styles.css +5 -0
- package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +93 -23
- package/corpus/templates/dispatch/changelog/2026-07-07-overview-is-now-just-ask-dispatch-and-a-list-of-apps.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +16 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/deploy-build.d.ts +12 -0
- package/dist/cli/deploy-build.d.ts.map +1 -0
- package/dist/cli/deploy-build.js +30 -0
- package/dist/cli/deploy-build.js.map +1 -0
- package/dist/cli/index.js +8 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +14 -3
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.d.ts +1 -0
- package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.js +2 -1
- package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
- package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe.js +6 -1
- package/dist/client/blocks/library/wireframe.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +6 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +54 -25
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/builder.d.ts.map +1 -1
- package/dist/file-upload/builder.js +9 -19
- package/dist/file-upload/builder.js.map +1 -1
- package/dist/file-upload/types.d.ts +3 -3
- package/dist/file-upload/types.d.ts.map +1 -1
- package/dist/file-upload/types.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/styles/blocks.css +7 -0
- package/docs/content/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ar-SA/tracking.mdx +3 -3
- package/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/de-DE/tracking.mdx +3 -3
- package/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/es-ES/tracking.mdx +3 -3
- package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/fr-FR/tracking.mdx +3 -3
- package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/hi-IN/tracking.mdx +3 -3
- package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ja-JP/tracking.mdx +3 -3
- package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ko-KR/tracking.mdx +3 -3
- package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/pt-BR/tracking.mdx +3 -3
- package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/zh-CN/tracking.mdx +3 -3
- package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/zh-TW/tracking.mdx +3 -3
- package/docs/content/tracking.mdx +3 -3
- package/package.json +1 -1
|
@@ -16,8 +16,8 @@ export interface FileUploadInput {
|
|
|
16
16
|
mimeType?: string;
|
|
17
17
|
/** Optional owner email for per-user scoping in fallback storage. */
|
|
18
18
|
ownerEmail?: string;
|
|
19
|
-
/** Builder.io upload hint: return
|
|
20
|
-
|
|
19
|
+
/** Builder.io upload hint: return one stable URL while compression finishes asynchronously. */
|
|
20
|
+
stableUrl?: boolean;
|
|
21
21
|
/** Builder.io upload hint: set false for internal artifacts that should not appear in the asset library. */
|
|
22
22
|
recordAsset?: boolean;
|
|
23
23
|
}
|
|
@@ -81,7 +81,7 @@ export interface FileUploadProvider {
|
|
|
81
81
|
completeSession(
|
|
82
82
|
session: ResumableUploadSession,
|
|
83
83
|
filename: string,
|
|
84
|
-
options?: {
|
|
84
|
+
options?: { stableUrl?: boolean; recordAsset?: boolean },
|
|
85
85
|
): Promise<string>;
|
|
86
86
|
};
|
|
87
87
|
}
|
|
@@ -1007,6 +1007,13 @@
|
|
|
1007
1007
|
min-height: 100%;
|
|
1008
1008
|
}
|
|
1009
1009
|
|
|
1010
|
+
.plan-html-frame[data-frame="hide"]:not([data-render-mode="design"])
|
|
1011
|
+
> .plan-html-frame-content
|
|
1012
|
+
> :first-child {
|
|
1013
|
+
margin: 0 !important;
|
|
1014
|
+
padding: 0 !important;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1010
1017
|
.plan-html-frame[data-prototype-live="true"] {
|
|
1011
1018
|
overflow: auto;
|
|
1012
1019
|
}
|
|
@@ -106,12 +106,17 @@ agent answers about browser recordings in the Analytics template.
|
|
|
106
106
|
|
|
107
107
|
## Capture Defaults
|
|
108
108
|
|
|
109
|
-
- Replay is
|
|
110
|
-
|
|
109
|
+
- Replay is on by default for signed-in hosted users when
|
|
110
|
+
`VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY` or `configureTracking({ key })` is
|
|
111
|
+
present. The default sample rate is 100% of eligible sessions.
|
|
112
|
+
- Replay remains off when no first-party analytics key is configured, and it is
|
|
113
|
+
not auto-enabled on localhost/local dev. Consumers can still enable replay
|
|
114
|
+
directly with
|
|
111
115
|
`configureTracking({ key, endpoint, sessionReplay: { enabled: true } })`.
|
|
116
|
+
- Apps can opt out with `configureTracking({ sessionReplay: false })`.
|
|
112
117
|
- Agent Native templates already call `configureTracking()` in their roots;
|
|
113
|
-
hosted template deployments
|
|
114
|
-
vars on the recorded site.
|
|
118
|
+
hosted template deployments only need the normal Agent Native Analytics
|
|
119
|
+
Vite/Netlify env vars on the recorded site.
|
|
115
120
|
- Inputs are masked by default. Page text is visible unless marked with
|
|
116
121
|
`.an-mask` or `data-an-mask`.
|
|
117
122
|
- Use `.an-block`, `.an-ignore`, `data-an-block`, or `data-an-ignore` for
|
|
@@ -155,6 +155,24 @@ export function formatMetricValue(
|
|
|
155
155
|
: String(raw ?? "-");
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function isNumericLikeValue(value: unknown): boolean {
|
|
159
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
160
|
+
return (
|
|
161
|
+
typeof value === "string" &&
|
|
162
|
+
value.trim() !== "" &&
|
|
163
|
+
Number.isFinite(Number(value))
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function detectMetricValueColumn(
|
|
168
|
+
row: Record<string, unknown>,
|
|
169
|
+
configuredKey?: string,
|
|
170
|
+
): string {
|
|
171
|
+
const cols = Object.keys(row);
|
|
172
|
+
if (configuredKey && cols.includes(configuredKey)) return configuredKey;
|
|
173
|
+
return cols.find((key) => isNumericLikeValue(row[key])) || cols[0] || "";
|
|
174
|
+
}
|
|
175
|
+
|
|
158
176
|
function parsePrometheusSeriesLabel(label: string): {
|
|
159
177
|
metric: string;
|
|
160
178
|
labels: Record<string, string>;
|
|
@@ -804,7 +822,7 @@ function detectKeys(
|
|
|
804
822
|
if (yKeys.length === 0) {
|
|
805
823
|
for (const c of cols) {
|
|
806
824
|
if (c === xKey) continue;
|
|
807
|
-
if (
|
|
825
|
+
if (isNumericLikeValue(sample[c])) yKeys.push(c);
|
|
808
826
|
}
|
|
809
827
|
}
|
|
810
828
|
if (yKeys.length === 0 && cols.length > 1) {
|
|
@@ -1120,14 +1138,10 @@ function MetricRenderer({
|
|
|
1120
1138
|
panel: SqlPanel;
|
|
1121
1139
|
}) {
|
|
1122
1140
|
const row = rows[0];
|
|
1123
|
-
const
|
|
1124
|
-
const valueCol =
|
|
1125
|
-
panel.config?.yKey ||
|
|
1126
|
-
cols.find((c) => typeof row[c] === "number") ||
|
|
1127
|
-
cols[0];
|
|
1141
|
+
const valueCol = detectMetricValueColumn(row, panel.config?.yKey);
|
|
1128
1142
|
|
|
1129
1143
|
let raw: unknown;
|
|
1130
|
-
if (rows.length > 1 &&
|
|
1144
|
+
if (rows.length > 1 && isNumericLikeValue(row[valueCol])) {
|
|
1131
1145
|
raw = rows.reduce((sum, r) => sum + (Number(r[valueCol]) || 0), 0);
|
|
1132
1146
|
} else {
|
|
1133
1147
|
raw = row[valueCol];
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
appBasePath,
|
|
3
|
+
installRouteChunkRecovery,
|
|
4
|
+
} from "@agent-native/core/client";
|
|
2
5
|
import { hydrateRoot } from "react-dom/client";
|
|
3
6
|
import { HydratedRouter } from "react-router/dom";
|
|
4
7
|
|
|
8
|
+
installRouteChunkRecovery();
|
|
9
|
+
|
|
5
10
|
const basePath = appBasePath();
|
|
6
11
|
const pathname = window.location.pathname;
|
|
7
12
|
const routerBasePath =
|
|
@@ -596,7 +596,7 @@ export default defineAction({
|
|
|
596
596
|
typeof resumableSession.meta.filename === "string"
|
|
597
597
|
? resumableSession.meta.filename
|
|
598
598
|
: "",
|
|
599
|
-
{
|
|
599
|
+
{ stableUrl: true, recordAsset: false },
|
|
600
600
|
);
|
|
601
601
|
debugLog("[finalize] resumable upload completed", { id, videoUrl });
|
|
602
602
|
try {
|
|
@@ -966,7 +966,7 @@ export default defineAction({
|
|
|
966
966
|
filename: `${id}.${videoFormat}`,
|
|
967
967
|
mimeType,
|
|
968
968
|
ownerEmail,
|
|
969
|
-
|
|
969
|
+
stableUrl: true,
|
|
970
970
|
recordAsset: false,
|
|
971
971
|
});
|
|
972
972
|
} catch (err) {
|
|
@@ -81,7 +81,14 @@ import {
|
|
|
81
81
|
saveBool,
|
|
82
82
|
saveString,
|
|
83
83
|
} from "./lib/storage";
|
|
84
|
-
import {
|
|
84
|
+
import {
|
|
85
|
+
canCheckForUpdates,
|
|
86
|
+
installAndRestart,
|
|
87
|
+
isUpdatePendingRestart,
|
|
88
|
+
retryUpdateCheck,
|
|
89
|
+
useUpdateStatus,
|
|
90
|
+
type UpdateStatus,
|
|
91
|
+
} from "./lib/updater";
|
|
85
92
|
import { normalizeServerUrl } from "./lib/url";
|
|
86
93
|
import {
|
|
87
94
|
installDesktopVoiceDictation,
|
|
@@ -3521,6 +3528,25 @@ function formatStorageBytes(bytes: number): string {
|
|
|
3521
3528
|
return `${Math.max(1, Math.round(mb))} MB`;
|
|
3522
3529
|
}
|
|
3523
3530
|
|
|
3531
|
+
function desktopUpdateStatusText(status: UpdateStatus): string {
|
|
3532
|
+
switch (status.state) {
|
|
3533
|
+
case "idle":
|
|
3534
|
+
return "Clips checks automatically after launch and every 4 hours.";
|
|
3535
|
+
case "checking":
|
|
3536
|
+
return "Checking for updates...";
|
|
3537
|
+
case "not-available":
|
|
3538
|
+
return "Clips is up to date.";
|
|
3539
|
+
case "available":
|
|
3540
|
+
return `Update ${status.version} found. Downloading now...`;
|
|
3541
|
+
case "downloading":
|
|
3542
|
+
return `Downloading update ${status.version}... ${status.percent}%`;
|
|
3543
|
+
case "downloaded":
|
|
3544
|
+
return `Update ${status.version} is ready. Restart to install it.`;
|
|
3545
|
+
case "error":
|
|
3546
|
+
return `Update check failed: ${status.message}`;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3524
3550
|
function Setup({
|
|
3525
3551
|
initial,
|
|
3526
3552
|
serverUrl,
|
|
@@ -3568,8 +3594,8 @@ function Setup({
|
|
|
3568
3594
|
}) {
|
|
3569
3595
|
const [url, setUrl] = useState(initial ?? DEFAULT_URL);
|
|
3570
3596
|
const [readinessOpen, setReadinessOpen] = useState(false);
|
|
3571
|
-
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
3572
3597
|
const featureConfig = useFeatureConfig();
|
|
3598
|
+
const updateStatus = useUpdateStatus();
|
|
3573
3599
|
const voiceEnabled = featureConfig?.voiceEnabled !== false;
|
|
3574
3600
|
const meetingsEnabled = featureConfig?.meetingsEnabled !== false;
|
|
3575
3601
|
const launchAtLoginEnabled = featureConfig?.launchAtLoginEnabled !== false;
|
|
@@ -3868,10 +3894,6 @@ function Setup({
|
|
|
3868
3894
|
);
|
|
3869
3895
|
}
|
|
3870
3896
|
|
|
3871
|
-
useEffect(() => {
|
|
3872
|
-
inputRef.current?.focus();
|
|
3873
|
-
}, []);
|
|
3874
|
-
|
|
3875
3897
|
useEffect(() => {
|
|
3876
3898
|
let cancelled = false;
|
|
3877
3899
|
const refresh = () => {
|
|
@@ -4134,6 +4156,32 @@ function Setup({
|
|
|
4134
4156
|
if (providerStatus[byokProvider]) return null;
|
|
4135
4157
|
return `${keyForByokProvider(byokProvider)} is not set — cleanup will fail until configured.`;
|
|
4136
4158
|
})();
|
|
4159
|
+
const updateChecksSupported = canCheckForUpdates();
|
|
4160
|
+
const updateBusy =
|
|
4161
|
+
updateStatus.state === "checking" ||
|
|
4162
|
+
updateStatus.state === "available" ||
|
|
4163
|
+
updateStatus.state === "downloading";
|
|
4164
|
+
const updateReady = updateStatus.state === "downloaded";
|
|
4165
|
+
const updateStatusClass =
|
|
4166
|
+
updateStatus.state === "error"
|
|
4167
|
+
? "setup-warning"
|
|
4168
|
+
: updateStatus.state === "downloaded" ||
|
|
4169
|
+
updateStatus.state === "not-available"
|
|
4170
|
+
? "setup-success"
|
|
4171
|
+
: "setup-hint";
|
|
4172
|
+
const updateCheckLabel = !updateChecksSupported
|
|
4173
|
+
? "Release builds only"
|
|
4174
|
+
: updateStatus.state === "checking"
|
|
4175
|
+
? "Checking..."
|
|
4176
|
+
: updateStatus.state === "downloading"
|
|
4177
|
+
? `Downloading ${updateStatus.percent}%`
|
|
4178
|
+
: "Check now";
|
|
4179
|
+
|
|
4180
|
+
function checkForDesktopUpdate() {
|
|
4181
|
+
retryUpdateCheck().catch((err) => {
|
|
4182
|
+
console.error("[clips-updater] manual check failed:", err);
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4137
4185
|
|
|
4138
4186
|
return (
|
|
4139
4187
|
<form className="setup" onSubmit={handleSubmit}>
|
|
@@ -4161,13 +4209,12 @@ function Setup({
|
|
|
4161
4209
|
/>
|
|
4162
4210
|
<input
|
|
4163
4211
|
id="clips-url"
|
|
4164
|
-
ref={inputRef}
|
|
4165
4212
|
type="url"
|
|
4166
4213
|
value={url}
|
|
4167
4214
|
onChange={(e) => setUrl(e.target.value)}
|
|
4168
4215
|
placeholder="http://localhost:8080"
|
|
4169
4216
|
/>
|
|
4170
|
-
<button className="
|
|
4217
|
+
<button className="secondary setup-connect-button" type="submit">
|
|
4171
4218
|
Connect
|
|
4172
4219
|
</button>
|
|
4173
4220
|
</div>
|
|
@@ -4214,6 +4261,46 @@ function Setup({
|
|
|
4214
4261
|
</div>
|
|
4215
4262
|
</div>
|
|
4216
4263
|
|
|
4264
|
+
<div className="setup-section">
|
|
4265
|
+
<SettingLabel
|
|
4266
|
+
label="Desktop updates"
|
|
4267
|
+
hint="Check the signed Clips desktop release channel and download any available update."
|
|
4268
|
+
/>
|
|
4269
|
+
<div className="setup-button-row">
|
|
4270
|
+
<button
|
|
4271
|
+
type="button"
|
|
4272
|
+
className="secondary"
|
|
4273
|
+
onClick={checkForDesktopUpdate}
|
|
4274
|
+
disabled={!updateChecksSupported || updateBusy || updateReady}
|
|
4275
|
+
>
|
|
4276
|
+
<IconRefresh
|
|
4277
|
+
size={15}
|
|
4278
|
+
stroke={1.9}
|
|
4279
|
+
className={updateBusy ? "update-spinner" : undefined}
|
|
4280
|
+
/>
|
|
4281
|
+
{updateCheckLabel}
|
|
4282
|
+
</button>
|
|
4283
|
+
{updateReady ? (
|
|
4284
|
+
<button
|
|
4285
|
+
type="button"
|
|
4286
|
+
className="secondary"
|
|
4287
|
+
onClick={() => {
|
|
4288
|
+
installAndRestart().catch((err) => {
|
|
4289
|
+
console.error("[clips-updater] relaunch failed:", err);
|
|
4290
|
+
});
|
|
4291
|
+
}}
|
|
4292
|
+
>
|
|
4293
|
+
Restart to install
|
|
4294
|
+
</button>
|
|
4295
|
+
) : null}
|
|
4296
|
+
</div>
|
|
4297
|
+
<p className={updateStatusClass}>
|
|
4298
|
+
{updateChecksSupported
|
|
4299
|
+
? desktopUpdateStatusText(updateStatus)
|
|
4300
|
+
: "Update checks are available in signed release builds."}
|
|
4301
|
+
</p>
|
|
4302
|
+
</div>
|
|
4303
|
+
|
|
4217
4304
|
<div className="setup-section-heading">Permissions</div>
|
|
4218
4305
|
|
|
4219
4306
|
<div className="setup-section">
|
|
@@ -26,6 +26,13 @@ let cachedStatus: UpdateStatus = { state: "idle" };
|
|
|
26
26
|
let pendingUpdate: Update | null = null;
|
|
27
27
|
const listeners = new Set<StatusListener>();
|
|
28
28
|
let started = false;
|
|
29
|
+
let checkInFlight: Promise<void> | null = null;
|
|
30
|
+
|
|
31
|
+
function canRunUpdateChecks() {
|
|
32
|
+
// Dev and local release builds are for testing the current checkout. Do not
|
|
33
|
+
// replace them with the published auto-update channel.
|
|
34
|
+
return !import.meta.env.DEV && !__CLIPS_DESKTOP_LOCAL_BUILD__;
|
|
35
|
+
}
|
|
29
36
|
|
|
30
37
|
function setStatus(next: UpdateStatus) {
|
|
31
38
|
cachedStatus = next;
|
|
@@ -33,55 +40,60 @@ function setStatus(next: UpdateStatus) {
|
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
async function runCheck() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
setStatus({ state: "
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const notes = update.body ?? undefined;
|
|
46
|
-
setStatus({ state: "available", version, notes });
|
|
47
|
-
|
|
48
|
-
// Download ONLY — do not install here. On macOS `install` swaps the .app
|
|
49
|
-
// bundle on disk while this process keeps running, which invalidates the
|
|
50
|
-
// running process's Screen Recording (TCC) grant and breaks capture until
|
|
51
|
-
// relaunch. We defer the swap to `installAndRestart()` so a downloaded-but-
|
|
52
|
-
// not-yet-installed update leaves the running app fully functional; the
|
|
53
|
-
// user records normally until they choose to restart.
|
|
54
|
-
let total = 0;
|
|
55
|
-
let downloaded = 0;
|
|
56
|
-
await update.download((event) => {
|
|
57
|
-
if (event.event === "Started") {
|
|
58
|
-
total = event.data.contentLength ?? 0;
|
|
59
|
-
downloaded = 0;
|
|
60
|
-
setStatus({ state: "downloading", version, notes, percent: 0 });
|
|
61
|
-
} else if (event.event === "Progress") {
|
|
62
|
-
downloaded += event.data.chunkLength;
|
|
63
|
-
const percent =
|
|
64
|
-
total > 0 ? Math.min(100, Math.round((downloaded / total) * 100)) : 0;
|
|
65
|
-
setStatus({ state: "downloading", version, notes, percent });
|
|
66
|
-
} else if (event.event === "Finished") {
|
|
67
|
-
setStatus({ state: "downloaded", version, notes });
|
|
43
|
+
if (checkInFlight) return checkInFlight;
|
|
44
|
+
|
|
45
|
+
checkInFlight = (async () => {
|
|
46
|
+
try {
|
|
47
|
+
setStatus({ state: "checking" });
|
|
48
|
+
const update = await check();
|
|
49
|
+
if (!update) {
|
|
50
|
+
setStatus({ state: "not-available" });
|
|
51
|
+
return;
|
|
68
52
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
53
|
+
pendingUpdate = update;
|
|
54
|
+
const version = update.version;
|
|
55
|
+
const notes = update.body ?? undefined;
|
|
56
|
+
setStatus({ state: "available", version, notes });
|
|
57
|
+
|
|
58
|
+
// Download ONLY — do not install here. On macOS `install` swaps the .app
|
|
59
|
+
// bundle on disk while this process keeps running, which invalidates the
|
|
60
|
+
// running process's Screen Recording (TCC) grant and breaks capture until
|
|
61
|
+
// relaunch. We defer the swap to `installAndRestart()` so a downloaded-but-
|
|
62
|
+
// not-yet-installed update leaves the running app fully functional; the
|
|
63
|
+
// user records normally until they choose to restart.
|
|
64
|
+
let total = 0;
|
|
65
|
+
let downloaded = 0;
|
|
66
|
+
await update.download((event) => {
|
|
67
|
+
if (event.event === "Started") {
|
|
68
|
+
total = event.data.contentLength ?? 0;
|
|
69
|
+
downloaded = 0;
|
|
70
|
+
setStatus({ state: "downloading", version, notes, percent: 0 });
|
|
71
|
+
} else if (event.event === "Progress") {
|
|
72
|
+
downloaded += event.data.chunkLength;
|
|
73
|
+
const percent =
|
|
74
|
+
total > 0
|
|
75
|
+
? Math.min(100, Math.round((downloaded / total) * 100))
|
|
76
|
+
: 0;
|
|
77
|
+
setStatus({ state: "downloading", version, notes, percent });
|
|
78
|
+
} else if (event.event === "Finished") {
|
|
79
|
+
setStatus({ state: "downloaded", version, notes });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
} catch (err) {
|
|
83
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
84
|
+
setStatus({ state: "error", message });
|
|
85
|
+
} finally {
|
|
86
|
+
checkInFlight = null;
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
|
|
90
|
+
return checkInFlight;
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
function startUpdateLoop() {
|
|
77
94
|
if (started) return;
|
|
78
95
|
started = true;
|
|
79
|
-
|
|
80
|
-
if (import.meta.env.DEV) return;
|
|
81
|
-
// Local release builds are for testing the current checkout. Do not replace
|
|
82
|
-
// them with the published auto-update channel just because package.json has
|
|
83
|
-
// a lower development version.
|
|
84
|
-
if (__CLIPS_DESKTOP_LOCAL_BUILD__) return;
|
|
96
|
+
if (!canRunUpdateChecks()) return;
|
|
85
97
|
// Check 3s after launch (let the popover finish first paint), then every
|
|
86
98
|
// 4 hours. Matches the cadence used by the Electron app.
|
|
87
99
|
setTimeout(runCheck, 3000);
|
|
@@ -125,11 +137,16 @@ export function isUpdatePendingRestart(): boolean {
|
|
|
125
137
|
return cachedStatus.state === "downloaded";
|
|
126
138
|
}
|
|
127
139
|
|
|
140
|
+
export function canCheckForUpdates(): boolean {
|
|
141
|
+
return canRunUpdateChecks();
|
|
142
|
+
}
|
|
143
|
+
|
|
128
144
|
/**
|
|
129
145
|
* Manual retry entry point. Used by the UpdateBanner's error state to let
|
|
130
146
|
* users re-attempt after a signature-verification / download / network
|
|
131
147
|
* failure. Runs a full check + download pass, same as the periodic loop.
|
|
132
148
|
*/
|
|
133
149
|
export function retryUpdateCheck(): Promise<void> {
|
|
150
|
+
if (!canRunUpdateChecks()) return Promise.resolve();
|
|
134
151
|
return runCheck();
|
|
135
152
|
}
|