local-operator-ui 0.34.56 → 0.35.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/out/main/index.js +604 -46
- package/out/preload/index.js +50 -1
- package/out/renderer/assets/{_basePickBy-BZpmSLU-.js → _basePickBy-C724Y6y1.js} +1 -1
- package/out/renderer/assets/{_baseUniq-5Fk4AsiY.js → _baseUniq-CTHBzlaK.js} +1 -1
- package/out/renderer/assets/{agent-details-page-BEoctGwH.js → agent-details-page-C6VzLw1m.js} +1 -1
- package/out/renderer/assets/{agent-hub-page-DVtEYZne.js → agent-hub-page-C4o6Aqru.js} +1 -1
- package/out/renderer/assets/{agents-page-CqlFD0Ps.js → agents-page-FP1_RvOY.js} +3 -3
- package/out/renderer/assets/{arc-Cgq0RekE.js → arc-Cv2GJOa_.js} +1 -1
- package/out/renderer/assets/{architectureDiagram-IEHRJDOE-SKpw5bNL.js → architectureDiagram-IEHRJDOE-BZyhfzWz.js} +1 -1
- package/out/renderer/assets/{blockDiagram-JOT3LUYC-jQv0ffHr.js → blockDiagram-JOT3LUYC-DtHvlbei.js} +1 -1
- package/out/renderer/assets/{book-open--60aKTv6.js → book-open-C65LfbfB.js} +1 -1
- package/out/renderer/assets/browser-page-Cgwj8_cP.js +1 -0
- package/out/renderer/assets/{browser-webauthn-prompt-PeVRs-qh.js → browser-webauthn-prompt-CjbHvbI7.js} +1 -1
- package/out/renderer/assets/{c4Diagram-VJAJSXHY-BlRyRK9C.js → c4Diagram-VJAJSXHY-DKV5t3tX.js} +1 -1
- package/out/renderer/assets/channel-Bzt6SfZF.js +1 -0
- package/out/renderer/assets/{chunk-4BMEZGHF-BBVAaHlr.js → chunk-4BMEZGHF-D1J0hVNB.js} +1 -1
- package/out/renderer/assets/{chunk-A2AXSNBT-sJR5i1wN.js → chunk-A2AXSNBT-msLg_v_T.js} +1 -1
- package/out/renderer/assets/{chunk-AEK57VVT-C8IWMgPw.js → chunk-AEK57VVT-Dk25anb1.js} +1 -1
- package/out/renderer/assets/{chunk-D6G4REZN-DTfUS03C.js → chunk-D6G4REZN-DhfHQLiP.js} +1 -1
- package/out/renderer/assets/{chunk-RZ5BOZE2-DiGk8o7V.js → chunk-RZ5BOZE2-D83jc5YN.js} +1 -1
- package/out/renderer/assets/{chunk-XZIHB7SX-Mamouuqj.js → chunk-XZIHB7SX-DCYwl-3m.js} +1 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-BAnolaTk.js +1 -0
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-BAnolaTk.js +1 -0
- package/out/renderer/assets/clone-QOUemT9c.js +1 -0
- package/out/renderer/assets/{console-mirror-Ch4jKiRK.js → console-mirror-M7tcIGks.js} +1 -1
- package/out/renderer/assets/{consoleCapture-BYlfSgpd.js → consoleCapture-DN8X9Ugb.js} +1 -1
- package/out/renderer/assets/{conversation-input-sync-Cs9wr7F7.js → conversation-input-sync-CO6CiDf8.js} +264 -259
- package/out/renderer/assets/{dagre-OKDRZEBW-D5TY-UFL.js → dagre-OKDRZEBW-D8jHPlQ1.js} +1 -1
- package/out/renderer/assets/{diagram-SSKATNLV-BTiHJkCz.js → diagram-SSKATNLV-DBWQdBIr.js} +1 -1
- package/out/renderer/assets/{diagram-VNBRO52H-CBxLprav.js → diagram-VNBRO52H-DT_32LVX.js} +1 -1
- package/out/renderer/assets/{erDiagram-Q7BY3M3F-9aF5V3AY.js → erDiagram-Q7BY3M3F-B-GEywmS.js} +1 -1
- package/out/renderer/assets/{error-boundary-B5YLbOOu.js → error-boundary-Ba0RLAEb.js} +2 -2
- package/out/renderer/assets/{flowDiagram-4HSFHLVR-BXlN8K06.js → flowDiagram-4HSFHLVR-DWqU7r-A.js} +1 -1
- package/out/renderer/assets/{ganttDiagram-APWFNJXF-1FcF-Cau.js → ganttDiagram-APWFNJXF-DGenG4u6.js} +1 -1
- package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-CjCM20j_.js → gitGraphDiagram-7IBYFJ6S-BPA8urey.js} +1 -1
- package/out/renderer/assets/{graph-CGuSrplO.js → graph-GUG88wOs.js} +1 -1
- package/out/renderer/assets/index-BadTTC0t.js +57 -0
- package/out/renderer/assets/{index-CalJ1cZq.js → index-DYdfRls6.js} +1 -1
- package/out/renderer/assets/{index-rnB6EiBX.js → index-DZH3HC4h.js} +216 -201
- package/out/renderer/assets/index-DstdCAdq.css +1 -0
- package/out/renderer/assets/{index-5brl9P13.js → index-FR873jzR.js} +1 -1
- package/out/renderer/assets/{infoDiagram-PH2N3AL5-Bqs15-nE.js → infoDiagram-PH2N3AL5-BcZyO1LQ.js} +1 -1
- package/out/renderer/assets/{installer-DZXz4Bgj.js → installer-DArqAYep.js} +1 -1
- package/out/renderer/assets/{journeyDiagram-U35MCT3I-BkSqHNi1.js → journeyDiagram-U35MCT3I-Mo89YhbY.js} +1 -1
- package/out/renderer/assets/{kanban-definition-NDS4AKOZ-CGnCYoin.js → kanban-definition-NDS4AKOZ-ZnBrWmw4.js} +1 -1
- package/out/renderer/assets/{layout-BM7AzCJ1.js → layout-PAHQYOvY.js} +1 -1
- package/out/renderer/assets/{legacy-agents-page-B8f2tJOf.js → legacy-agents-page-Czc5Wbjr.js} +1 -1
- package/out/renderer/assets/{mermaid.core-C14g_ZP7.js → mermaid.core-C9gG5Wli.js} +5 -5
- package/out/renderer/assets/{mesh-page-DCCummN5.js → mesh-page-BxR50_4c.js} +1 -1
- package/out/renderer/assets/{mindmap-definition-ALO5MXBD-D7Z-SM4X.js → mindmap-definition-ALO5MXBD-CCb-zon0.js} +1 -1
- package/out/renderer/assets/{mini-CcJI0pQs.js → mini-DqIb7MPh.js} +1 -1
- package/out/renderer/assets/{notebook-pen-Dj0km82T.js → notebook-pen-D8RjZqVC.js} +1 -1
- package/out/renderer/assets/{org-access-D0eAQUWQ.js → org-access-BK8MV-Og.js} +1 -1
- package/out/renderer/assets/{org-surface-gate-Bgznt94a.js → org-surface-gate-BG7zY2lU.js} +1 -1
- package/out/renderer/assets/{page-header-B3mzM2nC.js → page-header-Ch4j7oI6.js} +1 -1
- package/out/renderer/assets/{pieDiagram-IB7DONF6-3pVvYdop.js → pieDiagram-IB7DONF6-BXTvjCLw.js} +1 -1
- package/out/renderer/assets/{projects-page-T2Yw-kRf.js → projects-page-si3RlRLo.js} +1 -1
- package/out/renderer/assets/{publication-validation-BIJPIOf1.js → publication-validation-CxIO7FGl.js} +1 -1
- package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-Dttg4cdR.js → quadrantDiagram-7GDLP6J5-BMU76de4.js} +1 -1
- package/out/renderer/assets/{radar-MK3ICKWK-Bd7wnpF5.js → radar-MK3ICKWK-C1pTiS7q.js} +1 -1
- package/out/renderer/assets/{radient-auth-buttons-MPoHRsQX.js → radient-auth-buttons-DrefDDYq.js} +1 -1
- package/out/renderer/assets/{requirementDiagram-KVF5MWMF-Jiaz4ehK.js → requirementDiagram-KVF5MWMF-DsVqu3pR.js} +1 -1
- package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-C4tMl9ww.js → sankeyDiagram-QLVOVGJD-CprOe8fX.js} +1 -1
- package/out/renderer/assets/{schedules-page-CR7bLqQt.js → schedules-page-CzOtqfGC.js} +1 -1
- package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-DncET3lE.js → sequenceDiagram-X6HHIX6F-DZ5TPMR1.js} +1 -1
- package/out/renderer/assets/settings-page-7uhG4VZK.js +287 -0
- package/out/renderer/assets/{stateDiagram-DGXRK772-BI1ziKRV.js → stateDiagram-DGXRK772-_sPBuqdd.js} +1 -1
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-B-4nbbOf.js +1 -0
- package/out/renderer/assets/{timeline-definition-BDJGKUSR-DxR2rlJV.js → timeline-definition-BDJGKUSR-DWABwm6j.js} +1 -1
- package/out/renderer/assets/ui-preferences-store-wu8ubztI.js +1 -0
- package/out/renderer/assets/{use-download-agent-mutation-C6TvAwyM.js → use-download-agent-mutation-ZIDdnF_f.js} +1 -1
- package/out/renderer/assets/{use-memberships-query-AlVCuF9E.js → use-memberships-query-BZiimzLs.js} +1 -1
- package/out/renderer/assets/{use-session-snapshot-XM0bTxHC.js → use-session-snapshot-BiWZ99PX.js} +1 -1
- package/out/renderer/assets/{xychartDiagram-VJFVF3MP-CJW_dOUR.js → xychartDiagram-VJFVF3MP-BBSAaYhx.js} +1 -1
- package/out/renderer/console-capture.html +5 -5
- package/out/renderer/index.html +7 -7
- package/out/renderer/installer.html +4 -4
- package/out/renderer/mini.html +7 -7
- package/package.json +2 -2
- package/out/renderer/assets/browser-page-jc3r193W.js +0 -1
- package/out/renderer/assets/channel-BpN7da5J.js +0 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-IcRW-YOl.js +0 -1
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-IcRW-YOl.js +0 -1
- package/out/renderer/assets/clone-Dri7yO5y.js +0 -1
- package/out/renderer/assets/index-BUuvY2SU.css +0 -1
- package/out/renderer/assets/index-DbzmFwlT.js +0 -57
- package/out/renderer/assets/settings-page-B_D-wGm4.js +0 -292
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-Bqeb-TkR.js +0 -1
- package/out/renderer/assets/ui-preferences-store-D1IJShox.js +0 -1
package/out/main/index.js
CHANGED
|
@@ -185,6 +185,26 @@ const profileName = zod.z.string().min(1).max(128).refine(
|
|
|
185
185
|
)
|
|
186
186
|
);
|
|
187
187
|
const target = zod.z.object({ kind: zod.z.enum(["agent", "team"]), name: profileName }).strict();
|
|
188
|
+
const importSourceName = zod.z.enum(["claude", "codex", "omp"]);
|
|
189
|
+
const importSessionIds = zod.z.record(
|
|
190
|
+
zod.z.string(),
|
|
191
|
+
zod.z.array(zod.z.string().min(1).max(512)).max(5e3)
|
|
192
|
+
);
|
|
193
|
+
const importRequestFields = {
|
|
194
|
+
sources: zod.z.array(importSourceName).max(3).optional(),
|
|
195
|
+
sessionIds: importSessionIds.optional(),
|
|
196
|
+
/*
|
|
197
|
+
* Kept on the wire for the CLI/TUI and future callers, but the Settings
|
|
198
|
+
* surface never sets it: no-default-all is enforced in the UI by requiring
|
|
199
|
+
* an explicit per-session selection before preview and again before import.
|
|
200
|
+
*/
|
|
201
|
+
all: zod.z.boolean().optional(),
|
|
202
|
+
includeSubagents: zod.z.boolean().optional(),
|
|
203
|
+
includeClearedSegments: zod.z.boolean().optional(),
|
|
204
|
+
includeMemory: zod.z.boolean().optional(),
|
|
205
|
+
includeInstructions: zod.z.boolean().optional(),
|
|
206
|
+
maxSessions: zod.z.number().int().min(1).max(5e3).optional()
|
|
207
|
+
};
|
|
188
208
|
const modelSelection = zod.z.object({
|
|
189
209
|
provider: zod.z.string().min(1).max(128),
|
|
190
210
|
model_id: zod.z.string().min(1).max(512),
|
|
@@ -419,6 +439,27 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
|
|
|
419
439
|
zod.z.object({ op: zod.z.literal("runtimes.list") }).strict(),
|
|
420
440
|
zod.z.object({ op: zod.z.literal("profiles.list") }).strict(),
|
|
421
441
|
zod.z.object({ op: zod.z.literal("profiles.get"), name: profileName }).strict(),
|
|
442
|
+
/*
|
|
443
|
+
* The harness import plane. Reads are fixed-shape GETs; preview and start
|
|
444
|
+
* carry the same explicit request fields, and neither is allowed to infer a
|
|
445
|
+
* selection: the UI paints an empty selection and makes the user choose.
|
|
446
|
+
*/
|
|
447
|
+
zod.z.object({ op: zod.z.literal("imports.sources") }).strict(),
|
|
448
|
+
zod.z.object({
|
|
449
|
+
op: zod.z.literal("imports.sessions"),
|
|
450
|
+
source: importSourceName,
|
|
451
|
+
includeSubagents: zod.z.boolean().optional()
|
|
452
|
+
}).strict(),
|
|
453
|
+
zod.z.object({ op: zod.z.literal("imports.preview"), ...importRequestFields }).strict(),
|
|
454
|
+
zod.z.object({ op: zod.z.literal("imports.start"), ...importRequestFields }).strict(),
|
|
455
|
+
zod.z.object({
|
|
456
|
+
op: zod.z.literal("imports.status"),
|
|
457
|
+
jobId: zod.z.string().min(1).max(128)
|
|
458
|
+
}).strict(),
|
|
459
|
+
zod.z.object({
|
|
460
|
+
op: zod.z.literal("imports.cancel"),
|
|
461
|
+
jobId: zod.z.string().min(1).max(128)
|
|
462
|
+
}).strict(),
|
|
422
463
|
/*
|
|
423
464
|
* Definition CAS (proposed additive wire, b-wire-receipt-definition-cas-and-adopt).
|
|
424
465
|
* `expectedDefinitionRevision` is an opaque echo of the `definition_revision` the
|
|
@@ -2780,6 +2821,10 @@ zod.z.object({
|
|
|
2780
2821
|
const DESKTOP_MESSAGE_BYTE_BUDGET = 88e4;
|
|
2781
2822
|
const DESKTOP_CONTROL_BYTE_BUDGET = 262144;
|
|
2782
2823
|
const DESKTOP_SYSTEM_PROMPT_BYTE_BUDGET = 11e5;
|
|
2824
|
+
const IMPORT_OPS = /* @__PURE__ */ new Set([
|
|
2825
|
+
"imports.preview",
|
|
2826
|
+
"imports.start"
|
|
2827
|
+
]);
|
|
2783
2828
|
const MESSAGE_OPS = /* @__PURE__ */ new Set([
|
|
2784
2829
|
"sessions.message",
|
|
2785
2830
|
"sessions.command",
|
|
@@ -2792,7 +2837,7 @@ const MESSAGE_OPS = /* @__PURE__ */ new Set([
|
|
|
2792
2837
|
function desktopRequestByteBudget(op) {
|
|
2793
2838
|
if (op === "legacy.agent.systemPrompt.update")
|
|
2794
2839
|
return DESKTOP_SYSTEM_PROMPT_BYTE_BUDGET;
|
|
2795
|
-
return MESSAGE_OPS.has(op) ? DESKTOP_MESSAGE_BYTE_BUDGET : DESKTOP_CONTROL_BYTE_BUDGET;
|
|
2840
|
+
return MESSAGE_OPS.has(op) || IMPORT_OPS.has(op) ? DESKTOP_MESSAGE_BYTE_BUDGET : DESKTOP_CONTROL_BYTE_BUDGET;
|
|
2796
2841
|
}
|
|
2797
2842
|
const DESKTOP_CONTROL_DEADLINE_MS = 2e4;
|
|
2798
2843
|
const DESKTOP_LONG_READ_DEADLINE_MS = 9e4;
|
|
@@ -3054,6 +3099,18 @@ function assertPairedPublicationTarget(request) {
|
|
|
3054
3099
|
);
|
|
3055
3100
|
}
|
|
3056
3101
|
}
|
|
3102
|
+
function harnessImportBody(request) {
|
|
3103
|
+
return {
|
|
3104
|
+
sources: request.sources ?? null,
|
|
3105
|
+
session_ids: request.sessionIds ?? {},
|
|
3106
|
+
all: request.all ?? false,
|
|
3107
|
+
include_subagents: request.includeSubagents ?? false,
|
|
3108
|
+
include_cleared_segments: request.includeClearedSegments ?? false,
|
|
3109
|
+
include_memory: request.includeMemory ?? true,
|
|
3110
|
+
include_instructions: request.includeInstructions ?? true,
|
|
3111
|
+
...request.maxSessions === void 0 ? {} : { max_sessions: request.maxSessions }
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3057
3114
|
function desktopEndpoint(request) {
|
|
3058
3115
|
switch (request.op) {
|
|
3059
3116
|
case "capabilities":
|
|
@@ -3149,6 +3206,38 @@ function desktopEndpoint(request) {
|
|
|
3149
3206
|
method: "POST",
|
|
3150
3207
|
body: { request_id: request.requestId, name: request.name }
|
|
3151
3208
|
};
|
|
3209
|
+
case "imports.sources":
|
|
3210
|
+
return { path: "/v1/desktop/imports/sources", method: "GET" };
|
|
3211
|
+
case "imports.sessions": {
|
|
3212
|
+
const query = new URLSearchParams({ source: request.source });
|
|
3213
|
+
if (request.includeSubagents) query.set("include_subagents", "true");
|
|
3214
|
+
return {
|
|
3215
|
+
path: `/v1/desktop/imports/sessions?${query.toString()}`,
|
|
3216
|
+
method: "GET"
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
case "imports.preview":
|
|
3220
|
+
return {
|
|
3221
|
+
path: "/v1/desktop/imports/preview",
|
|
3222
|
+
method: "POST",
|
|
3223
|
+
body: harnessImportBody(request)
|
|
3224
|
+
};
|
|
3225
|
+
case "imports.start":
|
|
3226
|
+
return {
|
|
3227
|
+
path: "/v1/desktop/imports/jobs",
|
|
3228
|
+
method: "POST",
|
|
3229
|
+
body: harnessImportBody(request)
|
|
3230
|
+
};
|
|
3231
|
+
case "imports.status":
|
|
3232
|
+
return {
|
|
3233
|
+
path: `/v1/desktop/imports/jobs/${encodeURIComponent(request.jobId)}`,
|
|
3234
|
+
method: "GET"
|
|
3235
|
+
};
|
|
3236
|
+
case "imports.cancel":
|
|
3237
|
+
return {
|
|
3238
|
+
path: `/v1/desktop/imports/jobs/${encodeURIComponent(request.jobId)}`,
|
|
3239
|
+
method: "DELETE"
|
|
3240
|
+
};
|
|
3152
3241
|
case "profiles.create":
|
|
3153
3242
|
return {
|
|
3154
3243
|
path: "/v1/desktop/profiles",
|
|
@@ -4659,27 +4748,42 @@ function resolveWindowChromeForLaunch(input) {
|
|
|
4659
4748
|
const GLASS_COVERAGES = ["sidebars", "full"];
|
|
4660
4749
|
const GLASS_MAC_MATERIALS = ["window", "menu", "popover"];
|
|
4661
4750
|
const GLASS_WIN_MATERIALS = ["mica", "acrylic", "tabbed"];
|
|
4751
|
+
const DEFAULT_GLASS_INTENSITY = 80;
|
|
4662
4752
|
const DEFAULT_GLASS_SETTINGS = {
|
|
4663
4753
|
enabled: false,
|
|
4664
4754
|
coverage: "full",
|
|
4665
|
-
intensity:
|
|
4755
|
+
intensity: DEFAULT_GLASS_INTENSITY,
|
|
4666
4756
|
material: "window"
|
|
4667
4757
|
};
|
|
4668
4758
|
const WIN_BACKGROUND_MATERIAL_MIN_BUILD = 22621;
|
|
4669
4759
|
const parseVersion = (version) => version.split(".").map((part) => Number.parseInt(part, 10)).map((n) => Number.isFinite(n) ? n : 0);
|
|
4760
|
+
const gpuFeatureUsable = (status2) => typeof status2 === "string" && status2.startsWith("enabled");
|
|
4761
|
+
function glassEnvironmentObjection(environment) {
|
|
4762
|
+
if (!environment) return null;
|
|
4763
|
+
if (environment.accelerationDisabledByLaunch || environment.hardwareAcceleration === false)
|
|
4764
|
+
return "hardware-acceleration-disabled";
|
|
4765
|
+
if (environment.gpu && (!gpuFeatureUsable(environment.gpu.compositing) || !gpuFeatureUsable(environment.gpu.rasterization)))
|
|
4766
|
+
return "gpu-unavailable";
|
|
4767
|
+
if (environment.backdropFilter === false)
|
|
4768
|
+
return "backdrop-filter-unavailable";
|
|
4769
|
+
return null;
|
|
4770
|
+
}
|
|
4670
4771
|
function glassCapabilityFor(input) {
|
|
4671
|
-
const none = (reason) => ({
|
|
4772
|
+
const none = (reason, defaultMaterial = DEFAULT_GLASS_SETTINGS.material) => ({
|
|
4672
4773
|
supported: false,
|
|
4673
4774
|
mechanism: "none",
|
|
4674
4775
|
reason,
|
|
4675
4776
|
materials: [],
|
|
4676
|
-
defaultMaterial
|
|
4777
|
+
defaultMaterial,
|
|
4677
4778
|
liquid: false,
|
|
4779
|
+
defaultEnabled: false,
|
|
4678
4780
|
liveToggle: false
|
|
4679
4781
|
});
|
|
4680
4782
|
const version = parseVersion(input.osVersion);
|
|
4681
4783
|
if (input.platform === "darwin") {
|
|
4682
4784
|
if (input.reducedTransparency) return none("reduced-transparency");
|
|
4785
|
+
const objection = glassEnvironmentObjection(input.environment);
|
|
4786
|
+
if (objection) return none(objection);
|
|
4683
4787
|
return {
|
|
4684
4788
|
supported: true,
|
|
4685
4789
|
mechanism: "vibrancy",
|
|
@@ -4687,26 +4791,26 @@ function glassCapabilityFor(input) {
|
|
|
4687
4791
|
materials: GLASS_MAC_MATERIALS,
|
|
4688
4792
|
defaultMaterial: "window",
|
|
4689
4793
|
liquid: (version[0] ?? 0) >= 26,
|
|
4794
|
+
defaultEnabled: true,
|
|
4690
4795
|
liveToggle: true
|
|
4691
4796
|
};
|
|
4692
4797
|
}
|
|
4693
4798
|
if (input.platform === "win32") {
|
|
4694
4799
|
if ((version[2] ?? 0) < WIN_BACKGROUND_MATERIAL_MIN_BUILD)
|
|
4695
|
-
return none("windows-too-old");
|
|
4696
|
-
|
|
4697
|
-
return {
|
|
4698
|
-
supported: true,
|
|
4699
|
-
mechanism: "background-material",
|
|
4700
|
-
reason: "ok",
|
|
4701
|
-
materials: GLASS_WIN_MATERIALS,
|
|
4702
|
-
defaultMaterial: "mica",
|
|
4703
|
-
liquid: false,
|
|
4704
|
-
liveToggle: false
|
|
4705
|
-
};
|
|
4800
|
+
return none("windows-too-old", "mica");
|
|
4801
|
+
return none("windows-unverified", "mica");
|
|
4706
4802
|
}
|
|
4707
|
-
if (input.platform === "linux") return none("linux-
|
|
4803
|
+
if (input.platform === "linux") return none("linux-compositor-dependent");
|
|
4708
4804
|
return none("unsupported-platform");
|
|
4709
4805
|
}
|
|
4806
|
+
function defaultGlassSettings(capability) {
|
|
4807
|
+
return {
|
|
4808
|
+
enabled: capability.defaultEnabled,
|
|
4809
|
+
coverage: "full",
|
|
4810
|
+
intensity: DEFAULT_GLASS_INTENSITY,
|
|
4811
|
+
material: capability.defaultMaterial
|
|
4812
|
+
};
|
|
4813
|
+
}
|
|
4710
4814
|
const isCoverage = (value) => GLASS_COVERAGES.includes(value);
|
|
4711
4815
|
const migrateCoverage = (value) => value === "chrome" ? "sidebars" : value;
|
|
4712
4816
|
function sanitizeGlassSettings(raw, capability, base = DEFAULT_GLASS_SETTINGS) {
|
|
@@ -4726,6 +4830,40 @@ function sanitizeGlassSettings(raw, capability, base = DEFAULT_GLASS_SETTINGS) {
|
|
|
4726
4830
|
material
|
|
4727
4831
|
};
|
|
4728
4832
|
}
|
|
4833
|
+
const GLASS_FILE_VERSION = 2;
|
|
4834
|
+
const GLASS_SETTINGS_FIELDS = [
|
|
4835
|
+
"enabled",
|
|
4836
|
+
"coverage",
|
|
4837
|
+
"intensity",
|
|
4838
|
+
"material"
|
|
4839
|
+
];
|
|
4840
|
+
function resolveStoredGlass(raw, capability) {
|
|
4841
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
4842
|
+
const record = raw;
|
|
4843
|
+
if (record.settings === null || typeof record.settings !== "object")
|
|
4844
|
+
return null;
|
|
4845
|
+
const defaults = defaultGlassSettings(capability);
|
|
4846
|
+
const stored = sanitizeGlassSettings(
|
|
4847
|
+
record.settings,
|
|
4848
|
+
capability,
|
|
4849
|
+
DEFAULT_GLASS_SETTINGS
|
|
4850
|
+
);
|
|
4851
|
+
const legacy = record.version !== GLASS_FILE_VERSION || !Array.isArray(record.explicit);
|
|
4852
|
+
const explicit = legacy ? [...GLASS_SETTINGS_FIELDS] : GLASS_SETTINGS_FIELDS.filter(
|
|
4853
|
+
(field) => record.explicit.includes(field)
|
|
4854
|
+
);
|
|
4855
|
+
const settings = { ...defaults };
|
|
4856
|
+
for (const field of explicit)
|
|
4857
|
+
settings[field] = stored[field];
|
|
4858
|
+
return {
|
|
4859
|
+
settings,
|
|
4860
|
+
explicit,
|
|
4861
|
+
scheme: record.scheme === "light" ? "light" : "dark"
|
|
4862
|
+
};
|
|
4863
|
+
}
|
|
4864
|
+
function glassGateLine(capability, settings, platform) {
|
|
4865
|
+
return `[window-glass] launch gate platform=${platform} supported=${capability.supported} default=${capability.defaultEnabled} enabled=${settings.enabled} intensity=${settings.intensity} reason=${capability.reason} mechanism=${capability.mechanism}`;
|
|
4866
|
+
}
|
|
4729
4867
|
const glassIsActive = (settings, capability) => settings.enabled && capability.supported;
|
|
4730
4868
|
const GLASS_TRANSPARENT = "#00000000";
|
|
4731
4869
|
function glassWindowOptions(settings, capability, platform) {
|
|
@@ -4763,6 +4901,7 @@ const WINDOW_GLASS_GET_CHANNEL = "window-glass:get";
|
|
|
4763
4901
|
const WINDOW_GLASS_SET_CHANNEL = "window-glass:set";
|
|
4764
4902
|
const WINDOW_GLASS_SCHEME_CHANNEL = "window-glass:scheme";
|
|
4765
4903
|
const WINDOW_GLASS_STATE_CHANNEL = "window-glass:state";
|
|
4904
|
+
const WINDOW_GLASS_RENDERER_CHANNEL = "window-glass:renderer";
|
|
4766
4905
|
const WINDOW_GLASS_FILE = "window-glass.json";
|
|
4767
4906
|
function windowGlassArgument(state2, scheme) {
|
|
4768
4907
|
if (!state2.active) return `${WINDOW_GLASS_ARG}=off`;
|
|
@@ -8012,6 +8151,19 @@ const mediaRequestSchema = zod.z.discriminatedUnion("op", [
|
|
|
8012
8151
|
childId: zod.z.string().regex(/^[a-f0-9]{12}$/),
|
|
8013
8152
|
digest: zod.z.string().regex(/^[a-f0-9]{32}$/)
|
|
8014
8153
|
}).strict(),
|
|
8154
|
+
// A generated sound's PRE-RENDER peaks sidecar (`<digest>.peaks.json`, 240
|
|
8155
|
+
// bars). It rides this relay because it is content IN the session store,
|
|
8156
|
+
// served beside the audio it describes by the same attachment route family -
|
|
8157
|
+
// the renderer asks the store one question ("what did this session keep under
|
|
8158
|
+
// this digest?") rather than learning the store's layout - and because the
|
|
8159
|
+
// route needs the bearer, which this relay attaches and the JSON transport's
|
|
8160
|
+
// callers would have to re-plumb. Same shape constraints as the attachment op
|
|
8161
|
+
// above for the same reason: main builds the path.
|
|
8162
|
+
zod.z.object({
|
|
8163
|
+
op: zod.z.literal("sessions.attachment.waveform"),
|
|
8164
|
+
sessionId: zod.z.string().regex(/^[a-f0-9]{12}$/),
|
|
8165
|
+
digest: zod.z.string().regex(/^[a-f0-9]{32}$/)
|
|
8166
|
+
}).strict(),
|
|
8015
8167
|
// One supplement component's assembled HTML document (turn-supplements
|
|
8016
8168
|
// memo §2.7), as `{ html }` JSON. Both identifiers are shape-constrained
|
|
8017
8169
|
// here for the same reason the attachment ops' are: the path is built in
|
|
@@ -8046,6 +8198,11 @@ function endpoint(request) {
|
|
|
8046
8198
|
path: `/v1/desktop/sessions/${request.sessionId}/children/${request.childId}/attachments/${request.digest}`,
|
|
8047
8199
|
method: "GET"
|
|
8048
8200
|
};
|
|
8201
|
+
case "sessions.attachment.waveform":
|
|
8202
|
+
return {
|
|
8203
|
+
path: `/v1/desktop/sessions/${request.sessionId}/attachments/${request.digest}/waveform`,
|
|
8204
|
+
method: "GET"
|
|
8205
|
+
};
|
|
8049
8206
|
case "sessions.supplementDocument":
|
|
8050
8207
|
return {
|
|
8051
8208
|
path: `/v1/desktop/sessions/${request.sessionId}/supplements/${request.digest}/document`,
|
|
@@ -8090,7 +8247,7 @@ async function requestDesktopMediaOutcome(input, bytes, backendUrl, token) {
|
|
|
8090
8247
|
const target2 = endpoint(request);
|
|
8091
8248
|
let body;
|
|
8092
8249
|
let contentType;
|
|
8093
|
-
if (request.op === "agent.export" || request.op === "sessions.attachment" || request.op === "subagents.attachment" || request.op === "sessions.supplementDocument") {
|
|
8250
|
+
if (request.op === "agent.export" || request.op === "sessions.attachment" || request.op === "subagents.attachment" || request.op === "sessions.attachment.waveform" || request.op === "sessions.supplementDocument") {
|
|
8094
8251
|
body = void 0;
|
|
8095
8252
|
} else if (request.op === "speech.create" || request.op === "speech.agent") {
|
|
8096
8253
|
body = JSON.stringify(request.request);
|
|
@@ -32318,6 +32475,8 @@ const enUiBrowser = {
|
|
|
32318
32475
|
"ui.browser.browser_tab_strip.close": 'Close "',
|
|
32319
32476
|
"ui.browser.browser_tab_strip.copy_url": "Copy URL",
|
|
32320
32477
|
"ui.browser.browser_tab_strip.close_tab": "Close tab",
|
|
32478
|
+
"ui.browser.browser_tab_strip.mute_tab": "Mute tab",
|
|
32479
|
+
"ui.browser.browser_tab_strip.unmute_tab": "Unmute tab",
|
|
32321
32480
|
"ui.browser.browser_tab_strip.close_tab_shortcut": "Close tab ({shortcut})",
|
|
32322
32481
|
"ui.browser.browser_tab_strip.close_named_tab": "Close {title}",
|
|
32323
32482
|
"ui.browser.browser_tab_strip.all_tabs_unshown": "All tabs — {count, plural, other {{count, number, ::group-off} not shown}}",
|
|
@@ -32330,6 +32489,7 @@ const enUiBrowser = {
|
|
|
32330
32489
|
"ui.browser.browser_url_bar.forward_shortcut": "Forward ({shortcut})",
|
|
32331
32490
|
"ui.browser.browser_url_bar.find_in_page": "Find in page",
|
|
32332
32491
|
"ui.browser.browser_url_bar.find_in_page_shortcut": "Find in page ({shortcut})",
|
|
32492
|
+
"ui.browser.browser_url_bar.print_page": "Print page",
|
|
32333
32493
|
"ui.browser.browser_url_bar.forward": "Forward",
|
|
32334
32494
|
"ui.browser.browser_url_bar.reset_page_zoom_shortcut": "Reset page zoom ({shortcut})",
|
|
32335
32495
|
"ui.browser.browser_url_bar.page_zoom_reset": "Page zoom {zoom, number, ::group-off}%, reset to 100%",
|
|
@@ -32396,7 +32556,7 @@ const enUiCanvas = {
|
|
|
32396
32556
|
"ui.canvas.files.documents": "Documents",
|
|
32397
32557
|
"ui.canvas.files.spreadsheets": "Spreadsheets",
|
|
32398
32558
|
"ui.canvas.files.other": "Other",
|
|
32399
|
-
"ui.canvas.files.audio": "
|
|
32559
|
+
"ui.canvas.files.audio": "Sounds",
|
|
32400
32560
|
"ui.canvas.files.code": "Code",
|
|
32401
32561
|
"ui.canvas.files.presentations": "Presentations",
|
|
32402
32562
|
"ui.canvas.files.archives": "Archives",
|
|
@@ -34320,12 +34480,12 @@ const enUiSettingsAppearance = {
|
|
|
34320
34480
|
};
|
|
34321
34481
|
const enUiSettingsAppearanceWindowGlass = {
|
|
34322
34482
|
"ui.settings.appearance.window_glass.label": "Translucent window",
|
|
34323
|
-
"ui.settings.appearance.window_glass.description": "Lets your desktop show through the app window, blurred by the system.
|
|
34483
|
+
"ui.settings.appearance.window_glass.description": "Lets your desktop show through the app window, blurred by the system. On by default on macOS where the system supports it, and off elsewhere. Text stays readable on every theme: the slider stops before any theme's text gets hard to read.",
|
|
34324
34484
|
"ui.settings.appearance.window_glass.restart_needed": "Restart Local Operator to apply this change. The window keeps its current look until then.",
|
|
34325
|
-
"ui.settings.appearance.window_glass.unsupported_reduced_transparency": "Translucent window is unavailable because Reduce transparency is on in your system accessibility settings. Turn it off there
|
|
34485
|
+
"ui.settings.appearance.window_glass.unsupported_reduced_transparency": "Translucent window is unavailable because Reduce transparency is on in your system accessibility settings. Turn it off there to enable this control.",
|
|
34326
34486
|
"ui.settings.appearance.window_glass.unsupported_windows_too_old": "Translucent window needs Windows 11 version 22H2 or later.",
|
|
34327
|
-
"ui.settings.appearance.window_glass.unsupported_linux": "Translucent window is not available on Linux: there is no
|
|
34328
|
-
"ui.settings.appearance.window_glass.unsupported_other": "Translucent window is not available on this
|
|
34487
|
+
"ui.settings.appearance.window_glass.unsupported_linux": "Translucent window is not available on Linux: there is no native window blur an app can request. Desktop translucency there depends on the compositor (X11 or Wayland) and can fall back to software blur, which would show the desktop's text through the app's.",
|
|
34488
|
+
"ui.settings.appearance.window_glass.unsupported_other": "Translucent window is not available on this platform: only macOS is verified to composite a blurred window, and the Windows 11 material is not verified on a real machine yet.",
|
|
34329
34489
|
"ui.settings.appearance.window_glass.coverage_label": "Glass applies to",
|
|
34330
34490
|
"ui.settings.appearance.window_glass.coverage_description": "Which parts of the window go translucent. Sidebars only leaves the conversation and pages solid. Full app also covers them and the side panels; web pages, documents and images shown inside stay solid.",
|
|
34331
34491
|
"ui.settings.appearance.window_glass.coverage_sidebars": "Sidebars only",
|
|
@@ -34342,7 +34502,11 @@ const enUiSettingsAppearanceWindowGlass = {
|
|
|
34342
34502
|
"ui.settings.appearance.window_glass.material_tabbed": "Tabbed",
|
|
34343
34503
|
"ui.settings.appearance.window_glass.intensity_start": "Frosted",
|
|
34344
34504
|
"ui.settings.appearance.window_glass.intensity_end": "See-through",
|
|
34345
|
-
"ui.settings.appearance.window_glass.intensity_value": "{value}% toward see-through"
|
|
34505
|
+
"ui.settings.appearance.window_glass.intensity_value": "{value}% toward see-through",
|
|
34506
|
+
"ui.settings.appearance.window_glass.unsupported_windows_unverified": "Translucent window is not offered on Windows yet: Mica and Acrylic are implemented but have not been tested on a real Windows machine, so the control stays off until that testing lands.",
|
|
34507
|
+
"ui.settings.appearance.window_glass.unsupported_gpu": "Translucent window is unavailable because the GPU cannot composite it right now: compositing or rasterisation is disabled or running in software. A driver update or leaving a remote session may bring the control back without a restart.",
|
|
34508
|
+
"ui.settings.appearance.window_glass.unsupported_acceleration": "Translucent window is unavailable because hardware acceleration is off for this launch (--disable-gpu or a crash fallback). Relaunch with acceleration on to use it.",
|
|
34509
|
+
"ui.settings.appearance.window_glass.unsupported_backdrop_filter": "Translucent window is unavailable because this renderer cannot draw a backdrop blur."
|
|
34346
34510
|
};
|
|
34347
34511
|
const enUiSettingsConfiguration = {
|
|
34348
34512
|
"ui.settings.configuration.backend_setting_row.read_only": "Read-only",
|
|
@@ -34438,6 +34602,82 @@ const enUiSettingsConfiguration = {
|
|
|
34438
34602
|
"ui.settings.configuration.settings_filter_bar.advanced_shown": "{count, plural, one {{count, number, ::group-off} advanced setting shown — Expand all to see it} other {{count, number, ::group-off} advanced settings shown — Expand all to see them}}",
|
|
34439
34603
|
"ui.settings.configuration.settings_filter_bar.core_and_advanced": "{coreCount, plural, one {{coreCount, number, ::group-off} core setting} other {{coreCount, number, ::group-off} core settings}}, {advancedCount, plural, one {{advancedCount, number, ::group-off} advanced} other {{advancedCount, number, ::group-off} advanced}}"
|
|
34440
34604
|
};
|
|
34605
|
+
const enUiSettingsImports = {
|
|
34606
|
+
"ui.settings.imports.title": "Import conversations",
|
|
34607
|
+
"ui.settings.imports.description": "Copy conversations and curated memory from Claude Code, Codex or omp into Local Operator. The source stores are read-only.",
|
|
34608
|
+
"ui.settings.imports.source_heading": "Sources",
|
|
34609
|
+
"ui.settings.imports.source_not_found": "Not found",
|
|
34610
|
+
"ui.settings.imports.source_sessions": "{count, plural, one {# session} other {# sessions}}",
|
|
34611
|
+
"ui.settings.imports.loading_sources": "Checking for source stores...",
|
|
34612
|
+
"ui.settings.imports.loading_sessions": "Reading {source} sessions...",
|
|
34613
|
+
"ui.settings.imports.loading_preview": "Building the preview...",
|
|
34614
|
+
"ui.settings.imports.empty_sources": "No Claude Code, Codex or omp stores were detected on this machine.",
|
|
34615
|
+
"ui.settings.imports.empty_sessions": "No sessions found for {source}.",
|
|
34616
|
+
"ui.settings.imports.empty_filter": "No sessions match this search.",
|
|
34617
|
+
"ui.settings.imports.error_sources": "Could not read the source stores. {reason}",
|
|
34618
|
+
"ui.settings.imports.error_sessions": "Could not read {source} sessions. {reason}",
|
|
34619
|
+
"ui.settings.imports.error_preview": "Could not build the preview. {reason}",
|
|
34620
|
+
"ui.settings.imports.error_start": "Could not start the import. {reason}",
|
|
34621
|
+
"ui.settings.imports.error_job": "Could not read import progress. {reason}",
|
|
34622
|
+
"ui.settings.imports.error_cancel": "Could not cancel the import. {reason}",
|
|
34623
|
+
"ui.settings.imports.retry": "Retry",
|
|
34624
|
+
"ui.settings.imports.refresh": "Refresh",
|
|
34625
|
+
"ui.settings.imports.search_placeholder": "Search conversations",
|
|
34626
|
+
"ui.settings.imports.select_all": "Select all",
|
|
34627
|
+
"ui.settings.imports.clear_all": "Clear selection",
|
|
34628
|
+
"ui.settings.imports.selected": "{count, plural, one {# selected} other {# selected}}",
|
|
34629
|
+
"ui.settings.imports.selection_limit": "Only {max, number} sessions can be selected at once. Select fewer sessions or import them in batches.",
|
|
34630
|
+
"ui.settings.imports.session_meta": "{messages, plural, one {# message} other {# messages}} · {compactions, plural, one {# compaction} other {# compactions}}",
|
|
34631
|
+
"ui.settings.imports.session_updated": "Updated {date}",
|
|
34632
|
+
"ui.settings.imports.session_index_only": "Index only — no conversation body",
|
|
34633
|
+
"ui.settings.imports.preview_heading": "Preview",
|
|
34634
|
+
"ui.settings.imports.preview_summary": "{sessions, plural, one {# session} other {# sessions}} · {messages, plural, one {# message} other {# messages}} · {compactions, plural, one {# compaction} other {# compactions}}",
|
|
34635
|
+
"ui.settings.imports.preview_memory": "{records, plural, one {# memory record} other {# memory records}} · ~{tokens} tokens",
|
|
34636
|
+
"ui.settings.imports.preview_confirm": "Review the selection above, then confirm the copy.",
|
|
34637
|
+
"ui.settings.imports.preview_required": "Select at least one session and build a preview first.",
|
|
34638
|
+
"ui.settings.imports.confirm_label": "I understand these conversations will be copied into Local Operator.",
|
|
34639
|
+
"ui.settings.imports.import_button": "{count, plural, one {Import # session} other {Import # sessions}}",
|
|
34640
|
+
"ui.settings.imports.importing": "Importing",
|
|
34641
|
+
"ui.settings.imports.progress_heading": "Importing conversations",
|
|
34642
|
+
"ui.settings.imports.progress_job": "Job {job} · {state}",
|
|
34643
|
+
"ui.settings.imports.progress_pending": "{count, plural, one {# session pending} other {# sessions pending}}",
|
|
34644
|
+
"ui.settings.imports.cancel": "Cancel import",
|
|
34645
|
+
"ui.settings.imports.cancelling": "Cancelling...",
|
|
34646
|
+
"ui.settings.imports.result_heading": "Import result",
|
|
34647
|
+
"ui.settings.imports.result_summary": "{state} · {sessions, plural, one {# session} other {# sessions}} · {messages, plural, one {# message} other {# messages}}",
|
|
34648
|
+
"ui.settings.imports.result_summary_cancelled": "{state} · {sessions, number} of {total, plural, one {# session} other {# sessions}} · {messages, plural, one {# message} other {# messages}}",
|
|
34649
|
+
"ui.settings.imports.result_target": "New session {target}",
|
|
34650
|
+
"ui.settings.imports.outcome_imported": "imported",
|
|
34651
|
+
"ui.settings.imports.outcome_resumed": "resumed",
|
|
34652
|
+
"ui.settings.imports.outcome_skipped": "already imported",
|
|
34653
|
+
"ui.settings.imports.outcome_metadata": "index only — nothing to import",
|
|
34654
|
+
"ui.settings.imports.outcome_failed": "failed",
|
|
34655
|
+
"ui.settings.imports.outcome_cancelled": "cancelled",
|
|
34656
|
+
"ui.settings.imports.warnings_heading": "{count, plural, one {# warning} other {# warnings}}",
|
|
34657
|
+
"ui.settings.imports.review_instructions": "Instructions saved for review",
|
|
34658
|
+
"ui.settings.imports.review_memory": "Unattached memory saved for review",
|
|
34659
|
+
"ui.settings.imports.copy_path": "Copy path",
|
|
34660
|
+
"ui.settings.imports.copied": "Copied",
|
|
34661
|
+
"ui.settings.imports.rerun_safe": "Re-running is safe: unchanged sources are skipped, grown sources resume, rewritten sources get a new copy.",
|
|
34662
|
+
"ui.settings.imports.import_another": "Import more conversations",
|
|
34663
|
+
"ui.settings.imports.metadata_only_explain": "The source index has an entry but no conversation body; nothing can be imported. The harness may have removed the rollout.",
|
|
34664
|
+
"ui.settings.imports.state_queued": "queued",
|
|
34665
|
+
"ui.settings.imports.state_running": "running",
|
|
34666
|
+
"ui.settings.imports.state_done": "done",
|
|
34667
|
+
"ui.settings.imports.state_failed": "failed",
|
|
34668
|
+
"ui.settings.imports.state_cancelled": "cancelled",
|
|
34669
|
+
"ui.settings.imports.phase_queued": "queued",
|
|
34670
|
+
"ui.settings.imports.phase_selecting": "selecting",
|
|
34671
|
+
"ui.settings.imports.phase_importing": "importing",
|
|
34672
|
+
"ui.settings.imports.phase_memory": "reading memory",
|
|
34673
|
+
"ui.settings.imports.phase_done": "finishing",
|
|
34674
|
+
"ui.settings.imports.phase_cancelled": "cancelled",
|
|
34675
|
+
"ui.settings.imports.session_pending": "Pending",
|
|
34676
|
+
"ui.settings.imports.session_importing": "Importing",
|
|
34677
|
+
"ui.settings.imports.availability_full": "full",
|
|
34678
|
+
"ui.settings.imports.availability_metadata_only": "index only",
|
|
34679
|
+
"ui.settings.imports.availability_unsupported": "unsupported"
|
|
34680
|
+
};
|
|
34441
34681
|
const enUiSettingsIntegrations = {
|
|
34442
34682
|
"ui.settings.integrations.add_integration_form.add_integration": "Add integration",
|
|
34443
34683
|
"ui.settings.integrations.add_integration_form.name": "Name",
|
|
@@ -36318,6 +36558,7 @@ const bundledEnglish = {
|
|
|
36318
36558
|
...enUiSettingsAppearance,
|
|
36319
36559
|
...enUiSettingsAppearanceWindowGlass,
|
|
36320
36560
|
...enUiSettingsConfiguration,
|
|
36561
|
+
...enUiSettingsImports,
|
|
36321
36562
|
...enUiSettingsIntegrations,
|
|
36322
36563
|
...enUiSettings,
|
|
36323
36564
|
...enUiSettingsModelDefaultEffort,
|
|
@@ -39040,6 +39281,7 @@ const PAGE_ZOOM_STEPS = [
|
|
|
39040
39281
|
4,
|
|
39041
39282
|
5
|
|
39042
39283
|
];
|
|
39284
|
+
const PRINT_CANCELLED = /cancel/i;
|
|
39043
39285
|
class PageControls {
|
|
39044
39286
|
constructor(changed, resultTimeoutMs = 5e3) {
|
|
39045
39287
|
this.changed = changed;
|
|
@@ -39174,6 +39416,36 @@ class PageControls {
|
|
|
39174
39416
|
const factor = command === "reset" ? 1 : command === "in" ? PAGE_ZOOM_STEPS.find((step) => step > current + 1e-3) ?? 5 : [...PAGE_ZOOM_STEPS].reverse().find((step) => step < current - 1e-3) ?? 0.25;
|
|
39175
39417
|
active.view.webContents.setZoomFactor?.(factor);
|
|
39176
39418
|
}
|
|
39419
|
+
/** Print the presented page (LOH-13 slice C).
|
|
39420
|
+
*
|
|
39421
|
+
* HUMAN-ONLY, like everything in this class: it travels the sender-
|
|
39422
|
+
* authorized page-control IPC and the tab's serial lane, never the agent
|
|
39423
|
+
* protocol — an agent that needs a page as a document already has the
|
|
39424
|
+
* PDF/screenshot actions, and silently sending a page to a physical
|
|
39425
|
+
* printer is not a capability a tool call should hold.
|
|
39426
|
+
*
|
|
39427
|
+
* `silent: false` is the load-bearing option: Electron's silent mode
|
|
39428
|
+
* prints to the default printer with default settings and NO dialog,
|
|
39429
|
+
* which for a human press would be a side effect they never confirmed.
|
|
39430
|
+
* The dialog is the consent. A failure the dialog itself reports
|
|
39431
|
+
* (cancelled is the person changing their mind, not a failure) only
|
|
39432
|
+
* surfaces through `notice` when Electron names a real reason. */
|
|
39433
|
+
print(active) {
|
|
39434
|
+
if (!active.presented) return;
|
|
39435
|
+
const wc = active.view.webContents;
|
|
39436
|
+
if (wc.isDestroyed() || !wc.print) {
|
|
39437
|
+
this.notice = "Printing is not available for this page";
|
|
39438
|
+
this.noticeTabId = active.tabId;
|
|
39439
|
+
return;
|
|
39440
|
+
}
|
|
39441
|
+
this.notice = null;
|
|
39442
|
+
wc.print({ silent: false, printBackground: false }, (success, reason) => {
|
|
39443
|
+
if (success || !reason || PRINT_CANCELLED.test(reason)) return;
|
|
39444
|
+
this.notice = `Printing failed: ${reason}`;
|
|
39445
|
+
this.noticeTabId = active.tabId;
|
|
39446
|
+
this.changed();
|
|
39447
|
+
});
|
|
39448
|
+
}
|
|
39177
39449
|
/** Call BEFORE a capability transfer. Isolated per-WebContents zoom means
|
|
39178
39450
|
* resetting this tab cannot alter another tab at the same origin. */
|
|
39179
39451
|
prepareAgent(record) {
|
|
@@ -39183,6 +39455,71 @@ class PageControls {
|
|
|
39183
39455
|
if (changed) this.changed();
|
|
39184
39456
|
}
|
|
39185
39457
|
}
|
|
39458
|
+
class TabAudioTracker {
|
|
39459
|
+
constructor(changed) {
|
|
39460
|
+
this.changed = changed;
|
|
39461
|
+
}
|
|
39462
|
+
states = /* @__PURE__ */ new Map();
|
|
39463
|
+
listeners = /* @__PURE__ */ new Map();
|
|
39464
|
+
/** Start tracking one tab (idempotent) and return its current state. The
|
|
39465
|
+
* muted flag is re-read live on every call: `setAudioMuted` is also
|
|
39466
|
+
* reachable from the page's own media controls in principle, and a stale
|
|
39467
|
+
* cached flag would render a button that claims the opposite of the
|
|
39468
|
+
* page's actual state. */
|
|
39469
|
+
stateFor(tab) {
|
|
39470
|
+
this.track(tab);
|
|
39471
|
+
const state2 = this.states.get(tab.tabId) ?? {
|
|
39472
|
+
audible: false,
|
|
39473
|
+
muted: false
|
|
39474
|
+
};
|
|
39475
|
+
const wc = tab.view.webContents;
|
|
39476
|
+
if (!wc.isDestroyed()) state2.muted = wc.isAudioMuted?.() ?? state2.muted;
|
|
39477
|
+
return { ...state2 };
|
|
39478
|
+
}
|
|
39479
|
+
/** Mute or unmute one tab. Returns the resulting state, or null when the
|
|
39480
|
+
* webContents is already gone — the caller (a human press) then reports
|
|
39481
|
+
* the tab as closed rather than claiming a mute that never landed. */
|
|
39482
|
+
setMuted(tab, muted) {
|
|
39483
|
+
const wc = tab.view.webContents;
|
|
39484
|
+
if (wc.isDestroyed()) return null;
|
|
39485
|
+
this.track(tab);
|
|
39486
|
+
wc.setAudioMuted?.(muted);
|
|
39487
|
+
const state2 = this.stateFor(tab);
|
|
39488
|
+
state2.muted = wc.isAudioMuted?.() ?? muted;
|
|
39489
|
+
this.states.set(tab.tabId, state2);
|
|
39490
|
+
this.changed();
|
|
39491
|
+
return { ...state2 };
|
|
39492
|
+
}
|
|
39493
|
+
/** Drop tracking for every tab not in `liveTabIds`. Called from the
|
|
39494
|
+
* projection, which is the one place main already enumerates the live
|
|
39495
|
+
* set on every change. */
|
|
39496
|
+
prune(liveTabIds) {
|
|
39497
|
+
for (const [tabId, entry] of this.listeners) {
|
|
39498
|
+
if (liveTabIds.has(tabId)) continue;
|
|
39499
|
+
this.listeners.delete(tabId);
|
|
39500
|
+
this.states.delete(tabId);
|
|
39501
|
+
if (!entry.wc.isDestroyed())
|
|
39502
|
+
entry.wc.removeListener("audio-state-changed", entry.listener);
|
|
39503
|
+
}
|
|
39504
|
+
}
|
|
39505
|
+
track(tab) {
|
|
39506
|
+
if (this.listeners.has(tab.tabId)) return;
|
|
39507
|
+
const wc = tab.view.webContents;
|
|
39508
|
+
this.states.set(tab.tabId, {
|
|
39509
|
+
audible: wc.isDestroyed() ? false : wc.isCurrentlyAudible?.() ?? false,
|
|
39510
|
+
muted: wc.isDestroyed() ? false : wc.isAudioMuted?.() ?? false
|
|
39511
|
+
});
|
|
39512
|
+
const listener = (event) => {
|
|
39513
|
+
const current = this.states.get(tab.tabId);
|
|
39514
|
+
if (!current) return;
|
|
39515
|
+
current.audible = event?.audible ?? false;
|
|
39516
|
+
this.states.set(tab.tabId, current);
|
|
39517
|
+
this.changed();
|
|
39518
|
+
};
|
|
39519
|
+
this.listeners.set(tab.tabId, { wc, listener });
|
|
39520
|
+
if (!wc.isDestroyed()) wc.on("audio-state-changed", listener);
|
|
39521
|
+
}
|
|
39522
|
+
}
|
|
39186
39523
|
const DUPLICATABLE_WEB_URL = /^https?:\/\//i;
|
|
39187
39524
|
const DOCUMENT_SCOPED = /* @__PURE__ */ new Set([
|
|
39188
39525
|
"read",
|
|
@@ -39272,6 +39609,23 @@ class BrowserHost {
|
|
|
39272
39609
|
chromeTabOrder = new ChromeTabOrder();
|
|
39273
39610
|
registry;
|
|
39274
39611
|
pageControls;
|
|
39612
|
+
/** Per-tab audio for the strip's mute control (LOH-13 slice C). Main owns
|
|
39613
|
+
* it for the reason `tabLoading` is read here: a background tab's sound
|
|
39614
|
+
* is legible only through this projection.
|
|
39615
|
+
*
|
|
39616
|
+
* ASSIGNED IN THE CONSTRUCTOR, NOT INITIALISED HERE. The shipping main
|
|
39617
|
+
* bundle is compiled to V8 bytecode by electron-vite, and its `vite:bytecode`
|
|
39618
|
+
* transform refuses an arrow function inside a class PROPERTY: "Unable to
|
|
39619
|
+
* transform arrow inside class property". This initialiser was that arrow,
|
|
39620
|
+
* and it failed the v0.35.0 platform builds identically on every OS, so no
|
|
39621
|
+
* update assets shipped. A constructor assignment is inside the transform's
|
|
39622
|
+
* supported shape, keeps the field `readonly`, and preserves the callback's
|
|
39623
|
+
* laziness (the tracker only stores it). Do not move it back to a field
|
|
39624
|
+
* initialiser. Keep arrows out of every non-static class-field initialiser,
|
|
39625
|
+
* not only the direct form: a callback passed to a constructor
|
|
39626
|
+
* (`new X(() => ...)`) or sitting inside an object/array literal value is
|
|
39627
|
+
* refused by the same rule (2026-10-18). */
|
|
39628
|
+
tabAudio;
|
|
39275
39629
|
cdp;
|
|
39276
39630
|
approvals;
|
|
39277
39631
|
ownership;
|
|
@@ -39303,6 +39657,7 @@ class BrowserHost {
|
|
|
39303
39657
|
this.downloads = options.downloads;
|
|
39304
39658
|
this.log = options.log;
|
|
39305
39659
|
this.onChanged = options.onChanged;
|
|
39660
|
+
this.tabAudio = new TabAudioTracker(() => this.onChanged());
|
|
39306
39661
|
this.facts = options.facts;
|
|
39307
39662
|
this.forgetSiteData = options.forgetSiteData;
|
|
39308
39663
|
}
|
|
@@ -39581,6 +39936,9 @@ class BrowserHost {
|
|
|
39581
39936
|
const activeRecord = this.registry.activeTab;
|
|
39582
39937
|
const active = activeRecord && !activeRecord.view.webContents.isDestroyed() ? activeRecord : null;
|
|
39583
39938
|
const navigation = active?.view.webContents.navigationHistory;
|
|
39939
|
+
this.tabAudio.prune(
|
|
39940
|
+
new Set(this.registry.snapshot().map((entry) => entry.tabId))
|
|
39941
|
+
);
|
|
39584
39942
|
return {
|
|
39585
39943
|
pageControls: this.pageControls.state(active),
|
|
39586
39944
|
tabs: this.chromeTabOrder.project(this.registry.snapshot()).map((entry, displayOrder) => ({
|
|
@@ -39616,7 +39974,20 @@ class BrowserHost {
|
|
|
39616
39974
|
// was the whole of what the user saw of an agent's work. Read from the
|
|
39617
39975
|
// record rather than the snapshot: `snapshot()` is the diagnostic
|
|
39618
39976
|
// projection shared with `status`/`tabs`, and this field is chrome.
|
|
39619
|
-
loading: this.tabLoading(entry.tabId)
|
|
39977
|
+
loading: this.tabLoading(entry.tabId),
|
|
39978
|
+
// PER TAB, projected with `loading` and for the same reason: a
|
|
39979
|
+
// background tab making sound has no other surface. `audible`
|
|
39980
|
+
// gates the mute control's visibility; `muted` is its state.
|
|
39981
|
+
// Read from the record (guarded like `tabLoading`) because the
|
|
39982
|
+
// snapshot carries no webContents.
|
|
39983
|
+
audible: (() => {
|
|
39984
|
+
const record = this.registry.get(entry.tabId);
|
|
39985
|
+
return record && !record.view.webContents.isDestroyed() ? this.tabAudio.stateFor(record).audible : false;
|
|
39986
|
+
})(),
|
|
39987
|
+
muted: (() => {
|
|
39988
|
+
const record = this.registry.get(entry.tabId);
|
|
39989
|
+
return record && !record.view.webContents.isDestroyed() ? this.tabAudio.stateFor(record).muted : false;
|
|
39990
|
+
})()
|
|
39620
39991
|
})),
|
|
39621
39992
|
activeTabId: activeRecord?.tabId ?? null,
|
|
39622
39993
|
// The download surface's facts (§16.4): what is in flight, where it went and
|
|
@@ -39718,8 +40089,23 @@ class BrowserHost {
|
|
|
39718
40089
|
error: { code: "tab_closed", message: t("ui.browser.chrome.tab_gone") }
|
|
39719
40090
|
};
|
|
39720
40091
|
let url = "";
|
|
39721
|
-
let destinationId = action === "
|
|
40092
|
+
let destinationId = action === "duplicate" ? null : tabId;
|
|
39722
40093
|
try {
|
|
40094
|
+
if (action === "mute" || action === "unmute") {
|
|
40095
|
+
const result = this.tabAudio.setMuted(record, action === "mute");
|
|
40096
|
+
if (!result)
|
|
40097
|
+
return {
|
|
40098
|
+
status: "failed",
|
|
40099
|
+
action,
|
|
40100
|
+
tabId: null,
|
|
40101
|
+
error: {
|
|
40102
|
+
code: "tab_closed",
|
|
40103
|
+
message: t("ui.browser.chrome.tab_gone")
|
|
40104
|
+
}
|
|
40105
|
+
};
|
|
40106
|
+
this.onChanged();
|
|
40107
|
+
return { status: "completed", action, tabId };
|
|
40108
|
+
}
|
|
39723
40109
|
url = record.view.webContents.getURL();
|
|
39724
40110
|
if (action === "duplicate" && url !== "about:blank" && !DUPLICATABLE_WEB_URL.test(url))
|
|
39725
40111
|
return {
|
|
@@ -39788,6 +40174,7 @@ class BrowserHost {
|
|
|
39788
40174
|
if (!active || active.tabId !== tabId || !active.presented || active.view.webContents.isDestroyed())
|
|
39789
40175
|
throw new Error("The browser tab is no longer presented.");
|
|
39790
40176
|
if (action === "open") this.pageControls.open(active);
|
|
40177
|
+
else if (action === "print") this.pageControls.print(active);
|
|
39791
40178
|
else if (action === "close") this.pageControls.clear();
|
|
39792
40179
|
else if (action === "search")
|
|
39793
40180
|
this.pageControls.search(active, query, forward, next);
|
|
@@ -40527,7 +40914,8 @@ class DownloadArmer {
|
|
|
40527
40914
|
deadlineTimer: null,
|
|
40528
40915
|
capTimer: null,
|
|
40529
40916
|
finish: null,
|
|
40530
|
-
settled: false
|
|
40917
|
+
settled: false,
|
|
40918
|
+
human: false
|
|
40531
40919
|
};
|
|
40532
40920
|
this.captures.set(tabId, capture);
|
|
40533
40921
|
const deadline2 = setTimeout(() => this.settle(capture), timeoutMs);
|
|
@@ -40582,9 +40970,15 @@ class DownloadArmer {
|
|
|
40582
40970
|
*/
|
|
40583
40971
|
decide(item, webContentsId) {
|
|
40584
40972
|
const tabId = this.options.tabForWebContents(webContentsId);
|
|
40585
|
-
|
|
40973
|
+
let capture = tabId === null ? void 0 : this.captures.get(tabId);
|
|
40586
40974
|
if (!capture || capture.settled) {
|
|
40587
|
-
|
|
40975
|
+
capture = (tabId !== null ? this.humanCapture(tabId) : null) ?? void 0;
|
|
40976
|
+
if (!capture) {
|
|
40977
|
+
return {
|
|
40978
|
+
cancel: true,
|
|
40979
|
+
reason: "no download call is armed on this tab"
|
|
40980
|
+
};
|
|
40981
|
+
}
|
|
40588
40982
|
}
|
|
40589
40983
|
const raw = item.getFilename();
|
|
40590
40984
|
const clean = safeName(raw);
|
|
@@ -40706,6 +41100,58 @@ class DownloadArmer {
|
|
|
40706
41100
|
const dirs = this.notes.filter((note) => note.dir !== "").map((n) => n.dir);
|
|
40707
41101
|
return [...this.captures.values()].at(-1)?.dir ?? dirs.at(-1) ?? null;
|
|
40708
41102
|
}
|
|
41103
|
+
/** Build (or reuse) the human capture for one user-owned tab.
|
|
41104
|
+
*
|
|
41105
|
+
* Returns null — leaving the caller to cancel — when this host cannot
|
|
41106
|
+
* honestly call the download human-initiated: the tab is agent-owned or
|
|
41107
|
+
* handed to an agent (`ownerKindOf` reads the registry's CURRENT owner,
|
|
41108
|
+
* which hand-over flips), the armer was built without a human directory,
|
|
41109
|
+
* or that directory cannot be made. A settled human capture is replaced
|
|
41110
|
+
* rather than reused, so its file count bounds one burst (up to the
|
|
41111
|
+
* quiet window after the last write) and never the session.
|
|
41112
|
+
*
|
|
41113
|
+
* The directory is NOT chmodded, unlike `arm`: it is the person's own
|
|
41114
|
+
* downloads folder, which predates this app and whose mode is theirs.
|
|
41115
|
+
* The runtime-cap sampler IS started, for the reason `CAP_SAMPLE_MS`
|
|
41116
|
+
* states: an unknown-size human download has the same disk to fill as
|
|
41117
|
+
* an agent's. */
|
|
41118
|
+
humanCapture(tabId) {
|
|
41119
|
+
if (this.ownerKindOf(tabId) !== "user") return null;
|
|
41120
|
+
const dir = this.options.humanDownloadDir?.() ?? null;
|
|
41121
|
+
if (!dir) return null;
|
|
41122
|
+
try {
|
|
41123
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
41124
|
+
} catch {
|
|
41125
|
+
return null;
|
|
41126
|
+
}
|
|
41127
|
+
const capture = {
|
|
41128
|
+
tabId,
|
|
41129
|
+
dir,
|
|
41130
|
+
timeoutMs: 0,
|
|
41131
|
+
startedAt: (this.options.now ?? Date.now)(),
|
|
41132
|
+
files: [],
|
|
41133
|
+
refusals: [],
|
|
41134
|
+
pending: 0,
|
|
41135
|
+
started: false,
|
|
41136
|
+
live: [],
|
|
41137
|
+
handedOut: /* @__PURE__ */ new Set(),
|
|
41138
|
+
active: null,
|
|
41139
|
+
lastProgressAt: 0,
|
|
41140
|
+
quietTimer: null,
|
|
41141
|
+
deadlineTimer: null,
|
|
41142
|
+
capTimer: null,
|
|
41143
|
+
finish: null,
|
|
41144
|
+
settled: false,
|
|
41145
|
+
human: true
|
|
41146
|
+
};
|
|
41147
|
+
capture.capTimer = setInterval(
|
|
41148
|
+
() => this.sampleCaps(capture),
|
|
41149
|
+
CAP_SAMPLE_MS
|
|
41150
|
+
);
|
|
41151
|
+
capture.capTimer.unref?.();
|
|
41152
|
+
this.captures.set(tabId, capture);
|
|
41153
|
+
return capture;
|
|
41154
|
+
}
|
|
40709
41155
|
/** One upload's own line (review round 1, U3).
|
|
40710
41156
|
*
|
|
40711
41157
|
* WHY AN UPLOAD HAS A NOTE AT ALL, when §16.4 deliberately has no upload
|
|
@@ -40777,7 +41223,7 @@ class DownloadArmer {
|
|
|
40777
41223
|
if (entry.weCancelled) {
|
|
40778
41224
|
if (state2 !== "completed") discardPartial(savePath, this.options.log);
|
|
40779
41225
|
} else if (state2 === "completed") {
|
|
40780
|
-
restrictMode(savePath, this.options.log);
|
|
41226
|
+
if (!capture.human) restrictMode(savePath, this.options.log);
|
|
40781
41227
|
capture.files.push(fileFact(item, savePath));
|
|
40782
41228
|
this.note(capture, name, "saved", 1, "", null);
|
|
40783
41229
|
} else {
|
|
@@ -40933,6 +41379,9 @@ class DownloadArmer {
|
|
|
40933
41379
|
capture.settled = true;
|
|
40934
41380
|
capture.active = null;
|
|
40935
41381
|
this.clearTimers(capture);
|
|
41382
|
+
if (this.captures.get(capture.tabId) === capture) {
|
|
41383
|
+
this.captures.delete(capture.tabId);
|
|
41384
|
+
}
|
|
40936
41385
|
capture.finish?.();
|
|
40937
41386
|
this.options.onActivity?.();
|
|
40938
41387
|
}
|
|
@@ -40963,6 +41412,7 @@ class DownloadArmer {
|
|
|
40963
41412
|
capture.refusals.push(reason);
|
|
40964
41413
|
this.note(capture, clean, "refused", 1, reason, refusal);
|
|
40965
41414
|
this.options.log(`[browser] ${reason}`);
|
|
41415
|
+
this.options.onActivity?.();
|
|
40966
41416
|
return { cancel: true, reason };
|
|
40967
41417
|
}
|
|
40968
41418
|
note(capture, name, outcome, count, reason, refusal) {
|
|
@@ -41215,7 +41665,7 @@ function registerBrowserIpc(options) {
|
|
|
41215
41665
|
"browser-tab-action",
|
|
41216
41666
|
(event, tabId, action) => {
|
|
41217
41667
|
const host = authorize(event);
|
|
41218
|
-
if (action !== "duplicate" && action !== "reload")
|
|
41668
|
+
if (action !== "duplicate" && action !== "reload" && action !== "mute" && action !== "unmute")
|
|
41219
41669
|
throw new Error(t("ui.browser.chrome.invalid_action"));
|
|
41220
41670
|
return host.actOnChromeTab(numberOrThrow(tabId, "tabId"), action);
|
|
41221
41671
|
}
|
|
@@ -41243,7 +41693,9 @@ function registerBrowserIpc(options) {
|
|
|
41243
41693
|
"browser-page-control",
|
|
41244
41694
|
(event, tabId, action, query, forward, next) => {
|
|
41245
41695
|
const host = authorize(event);
|
|
41246
|
-
if (typeof action !== "string" || !["open", "close", "search", "in", "out", "reset"].includes(
|
|
41696
|
+
if (typeof action !== "string" || !["open", "close", "search", "print", "in", "out", "reset"].includes(
|
|
41697
|
+
action
|
|
41698
|
+
))
|
|
41247
41699
|
throw new Error("Invalid page control action.");
|
|
41248
41700
|
if (typeof query !== "string" || query.length > 4096 || typeof forward !== "boolean" || typeof next !== "boolean")
|
|
41249
41701
|
throw new Error("Invalid page search input.");
|
|
@@ -43623,6 +44075,17 @@ async function initializeBrowserHost(options, bindings) {
|
|
|
43623
44075
|
// exists is a marker pointing at nothing. `null` when the lookup cannot answer
|
|
43624
44076
|
// (a tab already gone), which the row renders the old way rather than guessing.
|
|
43625
44077
|
ownerKindFor: (tabId) => registryForDownloads?.get(tabId)?.owner ?? null,
|
|
44078
|
+
// The human download destination (LOH-13 slice C): the person's
|
|
44079
|
+
// own downloads folder, read from the app at decision time. Main
|
|
44080
|
+
// composes it here for the same reason the reveal channel takes
|
|
44081
|
+
// no path: no renderer or page string ever becomes a directory.
|
|
44082
|
+
humanDownloadDir: () => electron.app.getPath("downloads"),
|
|
44083
|
+
// NO `humanDeadlineMs` WIRING (review round 2, MAJOR): the wall-clock
|
|
44084
|
+
// budget this used to pass settled — and cancelled, discarding the
|
|
44085
|
+
// partial — a legitimate in-flight human download at 120 s. A human
|
|
44086
|
+
// capture now ends only on a guard, the quiet window, or the tab.
|
|
44087
|
+
// `COMMAND_TIMEOUTS_S.download` remains the AGENT call's own budget,
|
|
44088
|
+
// passed by its caller and unchanged.
|
|
43626
44089
|
log: log2,
|
|
43627
44090
|
onActivity: () => {
|
|
43628
44091
|
if (options.window.isDestroyed()) return;
|
|
@@ -45640,7 +46103,13 @@ function miniGlassPlan(input) {
|
|
|
45640
46103
|
function miniDocumentState(state2, launchedGlassOn) {
|
|
45641
46104
|
return {
|
|
45642
46105
|
...state2,
|
|
45643
|
-
|
|
46106
|
+
/*
|
|
46107
|
+
* Same rule as the main window's `stateFor`: a restart is only pending for a
|
|
46108
|
+
* SUPPORTED change that cannot be applied live. Without the `supported` term a
|
|
46109
|
+
* capability drop (`mechanism: "none"`, also `liveToggle: false`) reads as a
|
|
46110
|
+
* restart-pending change in the popout as well.
|
|
46111
|
+
*/
|
|
46112
|
+
restartRequired: state2.capability.supported && !state2.capability.liveToggle && state2.active !== launchedGlassOn
|
|
45644
46113
|
};
|
|
45645
46114
|
}
|
|
45646
46115
|
function createMiniView(options) {
|
|
@@ -53901,21 +54370,46 @@ function attachWindowChrome(options) {
|
|
|
53901
54370
|
function windowGlassFilePath(userDataDir) {
|
|
53902
54371
|
return path$1.join(userDataDir, WINDOW_GLASS_FILE);
|
|
53903
54372
|
}
|
|
54373
|
+
let gpuInfoReported = false;
|
|
54374
|
+
electron.app.on("gpu-info-update", () => {
|
|
54375
|
+
gpuInfoReported = true;
|
|
54376
|
+
});
|
|
54377
|
+
function readGlassEnvironmentFacts() {
|
|
54378
|
+
const facts = {};
|
|
54379
|
+
try {
|
|
54380
|
+
facts.accelerationDisabledByLaunch = electron.app.commandLine.hasSwitch("disable-gpu");
|
|
54381
|
+
} catch {
|
|
54382
|
+
facts.accelerationDisabledByLaunch = false;
|
|
54383
|
+
}
|
|
54384
|
+
try {
|
|
54385
|
+
facts.hardwareAcceleration = electron.app.isHardwareAccelerationEnabled();
|
|
54386
|
+
} catch {
|
|
54387
|
+
facts.hardwareAcceleration = null;
|
|
54388
|
+
}
|
|
54389
|
+
try {
|
|
54390
|
+
const status2 = gpuInfoReported ? electron.app.getGPUFeatureStatus() : null;
|
|
54391
|
+
facts.gpu = status2 ? {
|
|
54392
|
+
compositing: status2.gpu_compositing,
|
|
54393
|
+
rasterization: status2.rasterization
|
|
54394
|
+
} : null;
|
|
54395
|
+
} catch {
|
|
54396
|
+
facts.gpu = null;
|
|
54397
|
+
}
|
|
54398
|
+
return facts;
|
|
54399
|
+
}
|
|
53904
54400
|
function readPersistedWindowGlass(userDataDir, capability) {
|
|
53905
54401
|
const raw = readJson(
|
|
53906
54402
|
windowGlassFilePath(userDataDir)
|
|
53907
54403
|
);
|
|
53908
|
-
|
|
53909
|
-
return {
|
|
53910
|
-
settings: sanitizeGlassSettings(raw.settings, capability),
|
|
53911
|
-
scheme: raw.scheme === "light" ? "light" : "dark"
|
|
53912
|
-
};
|
|
54404
|
+
return resolveStoredGlass(raw, capability);
|
|
53913
54405
|
}
|
|
53914
54406
|
function resolveLaunchWindowGlass(input) {
|
|
54407
|
+
const environment = input.environment ?? readGlassEnvironmentFacts();
|
|
53915
54408
|
const capability = glassCapabilityFor({
|
|
53916
54409
|
platform: input.platform ?? process.platform,
|
|
53917
54410
|
osVersion: input.osVersion ?? process.getSystemVersion(),
|
|
53918
|
-
reducedTransparency: input.reducedTransparency ?? electron.nativeTheme.prefersReducedTransparency
|
|
54411
|
+
reducedTransparency: input.reducedTransparency ?? electron.nativeTheme.prefersReducedTransparency,
|
|
54412
|
+
environment
|
|
53919
54413
|
});
|
|
53920
54414
|
let persisted = null;
|
|
53921
54415
|
try {
|
|
@@ -53923,13 +54417,16 @@ function resolveLaunchWindowGlass(input) {
|
|
|
53923
54417
|
} catch {
|
|
53924
54418
|
persisted = null;
|
|
53925
54419
|
}
|
|
53926
|
-
const settings = persisted?.settings ??
|
|
54420
|
+
const settings = persisted?.settings ?? defaultGlassSettings(capability);
|
|
54421
|
+
const explicit = persisted?.explicit ?? [];
|
|
53927
54422
|
const scheme = persisted?.scheme ?? "dark";
|
|
53928
54423
|
return {
|
|
53929
54424
|
capability,
|
|
53930
54425
|
settings,
|
|
54426
|
+
explicit,
|
|
53931
54427
|
scheme,
|
|
53932
|
-
state: stateFor(settings, capability)
|
|
54428
|
+
state: stateFor(settings, capability),
|
|
54429
|
+
environment
|
|
53933
54430
|
};
|
|
53934
54431
|
}
|
|
53935
54432
|
const stateFor = (settings, capability, launchedActive = glassIsActive(settings, capability)) => {
|
|
@@ -53938,7 +54435,16 @@ const stateFor = (settings, capability, launchedActive = glassIsActive(settings,
|
|
|
53938
54435
|
capability,
|
|
53939
54436
|
settings,
|
|
53940
54437
|
active,
|
|
53941
|
-
|
|
54438
|
+
/*
|
|
54439
|
+
* A RESTART IS ONLY PENDING WHEN THE EFFECT IS AVAILABLE AND NOT LIVE.
|
|
54440
|
+
* `liveToggle: false` is also true of `mechanism: "none"`, so a capability
|
|
54441
|
+
* DROP (GPU software fallback, backdrop-filter report, Reduce transparency)
|
|
54442
|
+
* satisfied the old expression and told the user to restart while the window
|
|
54443
|
+
* had already fallen back to opaque. The unsupported state has its own
|
|
54444
|
+
* cause line; this notice is only for a supported change that needs a new
|
|
54445
|
+
* window (QA Q-1 / design D2).
|
|
54446
|
+
*/
|
|
54447
|
+
restartRequired: capability.supported && !capability.liveToggle && active !== launchedActive
|
|
53942
54448
|
};
|
|
53943
54449
|
};
|
|
53944
54450
|
function attachWindowGlass(options) {
|
|
@@ -53946,6 +54452,8 @@ function attachWindowGlass(options) {
|
|
|
53946
54452
|
let capability = options.initial.capability;
|
|
53947
54453
|
let settings = options.initial.settings;
|
|
53948
54454
|
let scheme = options.initial.scheme;
|
|
54455
|
+
const explicit = new Set(options.initial.explicit);
|
|
54456
|
+
let environment = { ...options.initial.environment };
|
|
53949
54457
|
const launchedActive = glassIsActive(settings, capability);
|
|
53950
54458
|
let pinnedTheme = false;
|
|
53951
54459
|
let appliedMechanism = launchedActive ? capability.mechanism : "none";
|
|
@@ -53989,7 +54497,17 @@ function attachWindowGlass(options) {
|
|
|
53989
54497
|
}
|
|
53990
54498
|
};
|
|
53991
54499
|
const persist = () => {
|
|
53992
|
-
if (!writeJsonAtomic(windowGlassFilePath(userDataDir), {
|
|
54500
|
+
if (!writeJsonAtomic(windowGlassFilePath(userDataDir), {
|
|
54501
|
+
/*
|
|
54502
|
+
* Versioned and per-field: the next launch fills only what this list does
|
|
54503
|
+
* not name from its own defaults. Writing the full `settings` object alone
|
|
54504
|
+
* is what made an untouched OFF indistinguishable from a chosen one.
|
|
54505
|
+
*/
|
|
54506
|
+
version: GLASS_FILE_VERSION,
|
|
54507
|
+
settings,
|
|
54508
|
+
explicit: GLASS_SETTINGS_FIELDS.filter((field) => explicit.has(field)),
|
|
54509
|
+
scheme
|
|
54510
|
+
}))
|
|
53993
54511
|
log2(
|
|
53994
54512
|
"[window-glass] the setting could not be saved - it applies to this run and will not survive a restart"
|
|
53995
54513
|
);
|
|
@@ -54010,6 +54528,11 @@ function attachWindowGlass(options) {
|
|
|
54010
54528
|
(event, patch) => {
|
|
54011
54529
|
if (!fromOwnWindow(event)) return null;
|
|
54012
54530
|
settings = sanitizeGlassSettings(patch, capability, settings);
|
|
54531
|
+
if (patch !== null && typeof patch === "object") {
|
|
54532
|
+
for (const field of Object.keys(patch))
|
|
54533
|
+
if (GLASS_SETTINGS_FIELDS.includes(field))
|
|
54534
|
+
explicit.add(field);
|
|
54535
|
+
}
|
|
54013
54536
|
applyToWindow();
|
|
54014
54537
|
persist();
|
|
54015
54538
|
push2();
|
|
@@ -54028,23 +54551,47 @@ function attachWindowGlass(options) {
|
|
|
54028
54551
|
persist();
|
|
54029
54552
|
return true;
|
|
54030
54553
|
});
|
|
54031
|
-
|
|
54554
|
+
electron.ipcMain.handle(WINDOW_GLASS_RENDERER_CHANNEL, (event, report) => {
|
|
54555
|
+
if (!fromOwnWindow(event)) return null;
|
|
54556
|
+
if (report !== null && typeof report === "object") {
|
|
54557
|
+
const value = report.backdropFilter;
|
|
54558
|
+
if (typeof value === "boolean")
|
|
54559
|
+
environment = { ...environment, backdropFilter: value };
|
|
54560
|
+
}
|
|
54561
|
+
reevaluate();
|
|
54562
|
+
return current();
|
|
54563
|
+
});
|
|
54564
|
+
function reevaluate() {
|
|
54565
|
+
environment = {
|
|
54566
|
+
...readGlassEnvironmentFacts(),
|
|
54567
|
+
...environment.backdropFilter === void 0 ? {} : { backdropFilter: environment.backdropFilter }
|
|
54568
|
+
};
|
|
54032
54569
|
const next = glassCapabilityFor({
|
|
54033
54570
|
platform: process.platform,
|
|
54034
54571
|
osVersion: process.getSystemVersion(),
|
|
54035
|
-
reducedTransparency: electron.nativeTheme.prefersReducedTransparency
|
|
54572
|
+
reducedTransparency: electron.nativeTheme.prefersReducedTransparency,
|
|
54573
|
+
environment
|
|
54036
54574
|
});
|
|
54037
|
-
if (next.supported === capability.supported && next.reason === capability.reason)
|
|
54575
|
+
if (next.supported === capability.supported && next.reason === capability.reason && next.mechanism === capability.mechanism)
|
|
54038
54576
|
return;
|
|
54577
|
+
const wasSupported = capability.supported;
|
|
54039
54578
|
capability = next;
|
|
54040
54579
|
settings = sanitizeGlassSettings(settings, capability, settings);
|
|
54580
|
+
if (next.supported && !wasSupported && !explicit.has("enabled"))
|
|
54581
|
+
settings.enabled = defaultGlassSettings(next).enabled;
|
|
54041
54582
|
applyToWindow();
|
|
54042
54583
|
push2();
|
|
54043
54584
|
log2(
|
|
54044
54585
|
`[window-glass] capability changed supported=${next.supported} reason=${next.reason}`
|
|
54045
54586
|
);
|
|
54046
|
-
}
|
|
54587
|
+
}
|
|
54588
|
+
const onNativeThemeUpdated = () => reevaluate();
|
|
54047
54589
|
electron.nativeTheme.on("updated", onNativeThemeUpdated);
|
|
54590
|
+
electron.app.on("gpu-info-update", reevaluate);
|
|
54591
|
+
const onChildProcessGone = (_event, details) => {
|
|
54592
|
+
if (details?.type === "GPU") setTimeout(reevaluate, 0);
|
|
54593
|
+
};
|
|
54594
|
+
electron.app.on("child-process-gone", onChildProcessGone);
|
|
54048
54595
|
if (glassIsActive(settings, capability)) {
|
|
54049
54596
|
electron.nativeTheme.themeSource = scheme;
|
|
54050
54597
|
pinnedTheme = true;
|
|
@@ -54055,7 +54602,10 @@ function attachWindowGlass(options) {
|
|
|
54055
54602
|
electron.ipcMain.removeHandler(WINDOW_GLASS_GET_CHANNEL);
|
|
54056
54603
|
electron.ipcMain.removeHandler(WINDOW_GLASS_SET_CHANNEL);
|
|
54057
54604
|
electron.ipcMain.removeHandler(WINDOW_GLASS_SCHEME_CHANNEL);
|
|
54605
|
+
electron.ipcMain.removeHandler(WINDOW_GLASS_RENDERER_CHANNEL);
|
|
54058
54606
|
electron.nativeTheme.removeListener("updated", onNativeThemeUpdated);
|
|
54607
|
+
electron.app.removeListener("gpu-info-update", reevaluate);
|
|
54608
|
+
electron.app.removeListener("child-process-gone", onChildProcessGone);
|
|
54059
54609
|
if (pinnedTheme) electron.nativeTheme.themeSource = "system";
|
|
54060
54610
|
}
|
|
54061
54611
|
};
|
|
@@ -54808,6 +55358,7 @@ const authPopupWindowOptions = {
|
|
|
54808
55358
|
disableDialogs: true
|
|
54809
55359
|
}
|
|
54810
55360
|
};
|
|
55361
|
+
let glassGateLogged = false;
|
|
54811
55362
|
function createWindow(initialSession = null, openCatalogue = false, request = ownLaunchRequest()) {
|
|
54812
55363
|
const chrome = resolveLaunchWindowChrome({
|
|
54813
55364
|
argv: process.argv,
|
|
@@ -54822,6 +55373,13 @@ function createWindow(initialSession = null, openCatalogue = false, request = ow
|
|
|
54822
55373
|
const glass = resolveLaunchWindowGlass({
|
|
54823
55374
|
userDataDir: electron.app.getPath("userData")
|
|
54824
55375
|
});
|
|
55376
|
+
if (!glassGateLogged) {
|
|
55377
|
+
glassGateLogged = true;
|
|
55378
|
+
logger.info(
|
|
55379
|
+
glassGateLine(glass.capability, glass.settings, process.platform),
|
|
55380
|
+
LogFileType.BACKEND
|
|
55381
|
+
);
|
|
55382
|
+
}
|
|
54825
55383
|
const mainWindow2 = new electron.BrowserWindow({
|
|
54826
55384
|
/*
|
|
54827
55385
|
* The frame, per platform, from a pure function so the matrix is exercised on
|