local-operator-ui 0.30.35 → 0.31.0
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 +823 -9
- package/out/preload/index.js +77 -0
- package/out/renderer/assets/{_basePickBy-DBMIejg7.js → _basePickBy-C4iSwCk_.js} +1 -1
- package/out/renderer/assets/{_baseUniq-BcqrDipQ.js → _baseUniq-CoR3b7Pb.js} +1 -1
- package/out/renderer/assets/{agent-details-page-CKouhYcx.js → agent-details-page-BrVmQlq3.js} +2 -2
- package/out/renderer/assets/agent-hub-page-DhQgRhtE.js +6 -0
- package/out/renderer/assets/agents-page-amh_zIfd.js +2 -0
- package/out/renderer/assets/{arc-C7db1al4.js → arc-BgzcPy1_.js} +1 -1
- package/out/renderer/assets/architectureDiagram-IEHRJDOE-DBQelo90.js +36 -0
- package/out/renderer/assets/{blockDiagram-JOT3LUYC-DFXKZ0Pd.js → blockDiagram-JOT3LUYC-B6Tj9M-m.js} +1 -1
- package/out/renderer/assets/book-open-BAndGvXv.js +6 -0
- package/out/renderer/assets/brand-mark-Bv0Hu-Cz.png +0 -0
- package/out/renderer/assets/browser-page-DXM0wVmK.js +1 -0
- package/out/renderer/assets/browser-webauthn-prompt-DRUBwhbd.js +1 -0
- package/out/renderer/assets/{c4Diagram-VJAJSXHY-DREcsmeA.js → c4Diagram-VJAJSXHY-DZffC4DW.js} +1 -1
- package/out/renderer/assets/channel-YJZLLu8D.js +1 -0
- package/out/renderer/assets/{chunk-4BMEZGHF-1AW6sHDl.js → chunk-4BMEZGHF-CzJZJ_Kr.js} +1 -1
- package/out/renderer/assets/{chunk-A2AXSNBT-BHYmovGQ.js → chunk-A2AXSNBT-FDhQL1W7.js} +1 -1
- package/out/renderer/assets/{chunk-AEK57VVT-VSZdczYO.js → chunk-AEK57VVT-BSVY08cz.js} +1 -1
- package/out/renderer/assets/{chunk-D6G4REZN-CHy8bDy2.js → chunk-D6G4REZN-3NnvkvEu.js} +1 -1
- package/out/renderer/assets/{chunk-RZ5BOZE2-D7gxwymY.js → chunk-RZ5BOZE2-B6vH5nHy.js} +1 -1
- package/out/renderer/assets/{chunk-XZIHB7SX-Bu9gT-3-.js → chunk-XZIHB7SX-DbDfwswt.js} +1 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-BA8uIyub.js +1 -0
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-BA8uIyub.js +1 -0
- package/out/renderer/assets/clone-8Z28G76s.js +1 -0
- package/out/renderer/assets/compact-pagination-DacmIceV.js +1 -0
- package/out/renderer/assets/{console-mirror-Bjpk3DAz.js → console-mirror-CzhHBJ89.js} +14 -14
- package/out/renderer/assets/{consoleCapture-B-P-8vPw.js → consoleCapture-Ff67EfR-.js} +1 -1
- package/out/renderer/assets/dagre-OKDRZEBW-DcDJ_xq8.js +4 -0
- package/out/renderer/assets/diagram-SSKATNLV-B_9mY_Gx.js +43 -0
- package/out/renderer/assets/diagram-VNBRO52H-N-VUNAsp.js +24 -0
- package/out/renderer/assets/en-US-BkS8Ixxl.js +1 -0
- package/out/renderer/assets/{erDiagram-Q7BY3M3F-Dzwd2fLB.js → erDiagram-Q7BY3M3F-CoBFM5wz.js} +1 -1
- package/out/renderer/assets/error-boundary-OyyPDgmi.js +126 -0
- package/out/renderer/assets/{flowDiagram-4HSFHLVR-DpqxYeov.js → flowDiagram-4HSFHLVR-U31R127Z.js} +1 -1
- package/out/renderer/assets/{ganttDiagram-APWFNJXF-CJqzp0mX.js → ganttDiagram-APWFNJXF-B6HIP3LX.js} +3 -3
- package/out/renderer/assets/gitGraphDiagram-7IBYFJ6S-CLTQlpsI.js +65 -0
- package/out/renderer/assets/{graph-BKZN53d3.js → graph-CPZjs5mz.js} +1 -1
- package/out/renderer/assets/index-CE03vul4.js +7 -0
- package/out/renderer/assets/index-CSPo12AE.css +1 -0
- package/out/renderer/assets/{index-C6pxQu_W.js → index-CXCGQLAf.js} +404 -453
- package/out/renderer/assets/index-DapmdGGl.css +1 -0
- package/out/renderer/assets/index-Dgpr-4wJ.js +1 -0
- package/out/renderer/assets/infoDiagram-PH2N3AL5-Bn8r1GBu.js +2 -0
- package/out/renderer/assets/installer-D8YScrG7.js +1 -0
- package/out/renderer/assets/{journeyDiagram-U35MCT3I-BG7areA5.js → journeyDiagram-U35MCT3I-B90sRmxY.js} +1 -1
- package/out/renderer/assets/{kanban-definition-NDS4AKOZ-BgMtdkNP.js → kanban-definition-NDS4AKOZ-CdQk4JVL.js} +1 -1
- package/out/renderer/assets/{layout-Bw_CgLbc.js → layout-DKBN0F9A.js} +1 -1
- package/out/renderer/assets/{legacy-agents-page-NU8mTf6U.js → legacy-agents-page-L5nl_vP8.js} +4 -4
- package/out/renderer/assets/{mermaid.core-Dz6HoxZc.js → mermaid.core-D22o0-Xw.js} +19 -19
- package/out/renderer/assets/mindmap-definition-ALO5MXBD-lMzKnedt.js +95 -0
- package/out/renderer/assets/{page-header-CYtvpazS.js → page-header-BosNuEEY.js} +1 -1
- package/out/renderer/assets/{parseISO-BIBIiSY3.js → parseISO-CG_r30eq.js} +2 -2
- package/out/renderer/assets/{pieDiagram-IB7DONF6-DR4ycBv-.js → pieDiagram-IB7DONF6-BPCgHz97.js} +1 -1
- package/out/renderer/assets/publication-validation-B1Ho4y5X.js +1 -0
- package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-DeXlOkVg.js → quadrantDiagram-7GDLP6J5-BN7q6nRl.js} +1 -1
- package/out/renderer/assets/{radar-MK3ICKWK-Dr73Vn9F.js → radar-MK3ICKWK-DVteWlkm.js} +1 -1
- package/out/renderer/assets/radient-auth-buttons-DsuZI5F-.js +6 -0
- package/out/renderer/assets/{requirementDiagram-KVF5MWMF-DHeR1FW9.js → requirementDiagram-KVF5MWMF-B19ClXnf.js} +1 -1
- package/out/renderer/assets/sankeyDiagram-QLVOVGJD-gohXveM_.js +10 -0
- package/out/renderer/assets/{schedules-page-C_-aK6li.js → schedules-page-BJYsnsA_.js} +8 -8
- package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-BBJ035AA.js → sequenceDiagram-X6HHIX6F-DAxdPXaF.js} +1 -1
- package/out/renderer/assets/settings-page-Cn4iJPhy.js +307 -0
- package/out/renderer/assets/stateDiagram-DGXRK772-CmAoNGXc.js +1 -0
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-DH3xoVHZ.js +1 -0
- package/out/renderer/assets/{timeline-definition-BDJGKUSR-BbDHYeOO.js → timeline-definition-BDJGKUSR-Cxg2i86G.js} +1 -1
- package/out/renderer/assets/use-download-agent-mutation-B3fB2cnC.js +61 -0
- package/out/renderer/assets/{xychartDiagram-VJFVF3MP-B4F8fbBE.js → xychartDiagram-VJFVF3MP-Bm7vUCym.js} +1 -1
- package/out/renderer/console-capture.html +4 -4
- package/out/renderer/index.html +6 -6
- package/out/renderer/installer.html +4 -4
- package/package.json +3 -3
- package/out/renderer/assets/agent-hub-page-CVdr01hB.js +0 -6
- package/out/renderer/assets/agents-page-HLrle3JE.js +0 -2
- package/out/renderer/assets/architectureDiagram-IEHRJDOE-CIccpocP.js +0 -36
- package/out/renderer/assets/browser-page-DA_7uhAS.js +0 -1
- package/out/renderer/assets/browser-webauthn-prompt-DIhdYBWb.js +0 -1
- package/out/renderer/assets/channel-Bpdhm4cc.js +0 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-Dy2svcuX.js +0 -1
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-Dy2svcuX.js +0 -1
- package/out/renderer/assets/clone-DnsDN_54.js +0 -1
- package/out/renderer/assets/compact-pagination-BRR4OtYY.js +0 -1
- package/out/renderer/assets/dagre-OKDRZEBW-CeR0cM5J.js +0 -4
- package/out/renderer/assets/diagram-SSKATNLV-Dhu6_Dgz.js +0 -43
- package/out/renderer/assets/diagram-VNBRO52H-R0bWQ_Bk.js +0 -24
- package/out/renderer/assets/gitGraphDiagram-7IBYFJ6S-PSAaXIbu.js +0 -65
- package/out/renderer/assets/icon-Cf2gAQ3W.js +0 -126
- package/out/renderer/assets/index-BIOY3qsR.css +0 -1
- package/out/renderer/assets/index-BV47Bsxl.js +0 -7
- package/out/renderer/assets/index-ByGvbPtr.css +0 -1
- package/out/renderer/assets/index-CeIHQLkR.js +0 -1
- package/out/renderer/assets/infoDiagram-PH2N3AL5-0WYVxV0b.js +0 -2
- package/out/renderer/assets/installer-DIgdF65j.js +0 -1
- package/out/renderer/assets/mindmap-definition-ALO5MXBD-C9GjbpP_.js +0 -95
- package/out/renderer/assets/radient-auth-buttons--2wV70ya.js +0 -6
- package/out/renderer/assets/sankeyDiagram-QLVOVGJD-BwC3hgaf.js +0 -10
- package/out/renderer/assets/settings-page-BxyC2IfK.js +0 -307
- package/out/renderer/assets/stateDiagram-DGXRK772-DAU2jfYz.js +0 -1
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-DJY1Hfi_.js +0 -1
- package/out/renderer/assets/use-agent-like-mutation-asZxgbHQ.js +0 -1
- /package/out/renderer/assets/{index-7OErfuvQ.js → index-Brz4xf_N.js} +0 -0
package/out/main/index.js
CHANGED
|
@@ -17,7 +17,7 @@ const promises = require("node:fs/promises");
|
|
|
17
17
|
const node_net = require("node:net");
|
|
18
18
|
const headless = require("@xterm/headless");
|
|
19
19
|
const node_module = require("node:module");
|
|
20
|
-
const
|
|
20
|
+
const http = require("node:http");
|
|
21
21
|
const https = require("node:https");
|
|
22
22
|
const electronUpdater = require("electron-updater");
|
|
23
23
|
function _interopNamespaceDefault(e) {
|
|
@@ -37,6 +37,7 @@ function _interopNamespaceDefault(e) {
|
|
|
37
37
|
return Object.freeze(n);
|
|
38
38
|
}
|
|
39
39
|
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path$1);
|
|
40
|
+
const http__namespace = /* @__PURE__ */ _interopNamespaceDefault(http);
|
|
40
41
|
const https__namespace = /* @__PURE__ */ _interopNamespaceDefault(https);
|
|
41
42
|
const icon = path.join(__dirname, "../../resources/icon.png");
|
|
42
43
|
const BACKEND_STATUS_CHANNEL = "backend-status";
|
|
@@ -389,7 +390,18 @@ const desktopRequestSchema = zod.z.discriminatedUnion("op", [
|
|
|
389
390
|
* actionable is strictly additive, and a `null` here would be a
|
|
390
391
|
* different request for every caller that never asked.
|
|
391
392
|
*/
|
|
392
|
-
model: modelSelection.optional()
|
|
393
|
+
model: modelSelection.optional(),
|
|
394
|
+
/*
|
|
395
|
+
* The id a `sessions.draft` mint handed the pane, when it has one: the
|
|
396
|
+
* create then adopts that id (and the runtime already engaged for it)
|
|
397
|
+
* instead of minting a fresh session id. OMITTED when the pane never
|
|
398
|
+
* minted — an older backend, a draft the user sent before the first
|
|
399
|
+
* keystroke's mint answered, or a set of fields that changed since the
|
|
400
|
+
* mint (see the store's drop rule) — so the body is byte-for-byte the
|
|
401
|
+
* request this op sent before the draft could be warmed, and a backend
|
|
402
|
+
* that cannot resolve the id mints fresh rather than failing the send.
|
|
403
|
+
*/
|
|
404
|
+
draftId: sessionId.optional()
|
|
393
405
|
}).strict(),
|
|
394
406
|
/*
|
|
395
407
|
* What a NEW conversation's readings would be, without creating anything.
|
|
@@ -423,6 +435,49 @@ const desktopRequestSchema = zod.z.discriminatedUnion("op", [
|
|
|
423
435
|
first turn will actually get. */
|
|
424
436
|
model: modelSelection.optional()
|
|
425
437
|
}).strict(),
|
|
438
|
+
/*
|
|
439
|
+
* Mint the id a NEW chat's runtime is warmed and then born on, from the
|
|
440
|
+
* pane's first keystroke.
|
|
441
|
+
*
|
|
442
|
+
* WHY THE OP EXISTS. A draft pane has no session to address, so there is
|
|
443
|
+
* nothing to warm: `sessions.warm` needs an id, and the multi-second engage
|
|
444
|
+
* the first send pays is exactly what the draft cannot pre-empt without one.
|
|
445
|
+
* The mint allocates that id and registers it with the daemon (fast, no
|
|
446
|
+
* engage), and the id then unlocks the same three doors a session pane uses
|
|
447
|
+
* — `events`, `watch` (the lease) and `warm` — through a deliberately narrow
|
|
448
|
+
* allow-list on the backend. `sessions.create` adopts the id on send, so the
|
|
449
|
+
* conversation the user lands in IS the one that was warmed; a daemon that
|
|
450
|
+
* has never seen the id (restart, expiry, eviction) mints fresh and the send
|
|
451
|
+
* works exactly as it did before this op existed.
|
|
452
|
+
*
|
|
453
|
+
* THE MINT ENGAGES NOTHING, and that is the lifetime design rather than an
|
|
454
|
+
* omission: the pane's own subscription and watch lease hold the bridge that
|
|
455
|
+
* keeps a warm alive, exactly as they do for a session, so abandoning the
|
|
456
|
+
* pane cancels an in-flight warm through the same `_detach` and a runtime
|
|
457
|
+
* nobody holds is reaped by the residency drain. There is no second, warmer-
|
|
458
|
+
* owned lifetime to get wrong.
|
|
459
|
+
*
|
|
460
|
+
* `requestId` IS A RECEIPT KEY, unlike `sessions.preview`'s token: a mint is
|
|
461
|
+
* fired once per pane, and a retry — a fast second keystroke, a lost
|
|
462
|
+
* response — must replay the SAME id, because two ids for one pane would
|
|
463
|
+
* warm two runtimes and leave a registry entry nobody can ever consume.
|
|
464
|
+
*
|
|
465
|
+
* The body is `sessions.create`'s first half, deliberately: same `cwd` bounds,
|
|
466
|
+
* same optional `target`, same optional `model` and the same 422 for an
|
|
467
|
+
* unresolvable profile. The pane is asking the question it will ask for real
|
|
468
|
+
* on the first send, so both derive from one selection and cannot disagree.
|
|
469
|
+
*
|
|
470
|
+
* Deliberately NOT a `MESSAGE_OPS` member (see `desktopRequestByteBudget`):
|
|
471
|
+
* a path and two optional short ids are not prose, so the mint costs the
|
|
472
|
+
* control budget — which is what lets a KEYSTROKE issue it.
|
|
473
|
+
*/
|
|
474
|
+
zod.z.object({
|
|
475
|
+
op: zod.z.literal("sessions.draft"),
|
|
476
|
+
requestId,
|
|
477
|
+
cwd: zod.z.string().min(1).max(4096),
|
|
478
|
+
target: target.optional(),
|
|
479
|
+
model: modelSelection.optional()
|
|
480
|
+
}).strict(),
|
|
426
481
|
zod.z.object({ op: zod.z.literal("sessions.get"), sessionId }).strict(),
|
|
427
482
|
zod.z.object({
|
|
428
483
|
op: zod.z.literal("sessions.history"),
|
|
@@ -859,6 +914,29 @@ const desktopRequestSchema = zod.z.discriminatedUnion("op", [
|
|
|
859
914
|
untilMs: zod.z.number().int().nonnegative().optional(),
|
|
860
915
|
days: zod.z.number().int().min(1).max(366).optional()
|
|
861
916
|
}).strict(),
|
|
917
|
+
/*
|
|
918
|
+
* The per-model rate table's own read, and a SEPARATE op on purpose.
|
|
919
|
+
*
|
|
920
|
+
* Its rows come from a grouped scan of the RAW LEDGER rather than from the
|
|
921
|
+
* rollup `analytics.get` reads, which is what makes it cover the operator's
|
|
922
|
+
* whole existing history rather than only the days since the rollup shipped
|
|
923
|
+
* — and is also what makes it cost seconds on a large ledger. Riding
|
|
924
|
+
* `analytics.get` would add that scan to every analytics panel load,
|
|
925
|
+
* including the ones that never scroll to the table, so it is fetched on its
|
|
926
|
+
* own and its wait is bounded and stated on its own section.
|
|
927
|
+
*
|
|
928
|
+
* Same arguments as `analytics.get`, same `.strict()` door, same window
|
|
929
|
+
* bounds: the two ops are windowed by the same `since_ms`/`until_ms`, which
|
|
930
|
+
* is the one property that lets a reader hold a row here against the
|
|
931
|
+
* headline Total above it.
|
|
932
|
+
*/
|
|
933
|
+
zod.z.object({
|
|
934
|
+
op: zod.z.literal("analytics.models"),
|
|
935
|
+
sessionId: sessionId.optional(),
|
|
936
|
+
sinceMs: zod.z.number().int().nonnegative().optional(),
|
|
937
|
+
untilMs: zod.z.number().int().nonnegative().optional(),
|
|
938
|
+
days: zod.z.number().int().min(1).max(366).optional()
|
|
939
|
+
}).strict(),
|
|
862
940
|
/*
|
|
863
941
|
* The two diagnostics reads. They ride their own capability key
|
|
864
942
|
* (`diagnostics`) rather than the catalogue one, because `/analytics` and
|
|
@@ -1291,6 +1369,15 @@ const DESKTOP_CONTROL_DEADLINE_MS = 2e4;
|
|
|
1291
1369
|
const DESKTOP_LONG_READ_DEADLINE_MS = 9e4;
|
|
1292
1370
|
const LEDGER_READ_OPS = /* @__PURE__ */ new Set([
|
|
1293
1371
|
"analytics.get",
|
|
1372
|
+
/*
|
|
1373
|
+
* The worst case of the same shape, and the reason the set is spelled by
|
|
1374
|
+
* shape rather than by measured cost: `/analytics/models` is a grouped scan
|
|
1375
|
+
* of the raw ledger with no covering index, measured in SECONDS on a 1.95 M
|
|
1376
|
+
* row ledger where `analytics.get` measures 12-40 s cold. On the control
|
|
1377
|
+
* budget it would be abandoned mid-read on nearly every open, so it is here
|
|
1378
|
+
* beside the op it is a slower sibling of.
|
|
1379
|
+
*/
|
|
1380
|
+
"analytics.models",
|
|
1294
1381
|
"sessions.report"
|
|
1295
1382
|
]);
|
|
1296
1383
|
const PROVIDER_READ_OPS = /* @__PURE__ */ new Set(["usage.get"]);
|
|
@@ -1307,6 +1394,7 @@ const READ_ONLY_OPS = /* @__PURE__ */ new Set([
|
|
|
1307
1394
|
"capabilities",
|
|
1308
1395
|
"accounts.list",
|
|
1309
1396
|
"analytics.get",
|
|
1397
|
+
"analytics.models",
|
|
1310
1398
|
"commands.entities",
|
|
1311
1399
|
"commands.list",
|
|
1312
1400
|
"config.get",
|
|
@@ -1346,6 +1434,7 @@ const READ_ONLY_OPS = /* @__PURE__ */ new Set([
|
|
|
1346
1434
|
]);
|
|
1347
1435
|
const PANEL_READ_OPS = /* @__PURE__ */ new Set([
|
|
1348
1436
|
"analytics.get",
|
|
1437
|
+
"analytics.models",
|
|
1349
1438
|
"usage.get",
|
|
1350
1439
|
"sessions.report",
|
|
1351
1440
|
"info.get",
|
|
@@ -1493,7 +1582,10 @@ function desktopEndpoint(request) {
|
|
|
1493
1582
|
request_id: request.requestId,
|
|
1494
1583
|
cwd: request.cwd,
|
|
1495
1584
|
...request.target ? { target: request.target } : {},
|
|
1496
|
-
...request.model ? { model: request.model } : {}
|
|
1585
|
+
...request.model ? { model: request.model } : {},
|
|
1586
|
+
// Omitted, not nulled, when the pane has no minted id: see the field's
|
|
1587
|
+
// own note for the byte-identity promise this keeps.
|
|
1588
|
+
...request.draftId ? { draft_id: request.draftId } : {}
|
|
1497
1589
|
}
|
|
1498
1590
|
};
|
|
1499
1591
|
case "sessions.preview":
|
|
@@ -1507,6 +1599,17 @@ function desktopEndpoint(request) {
|
|
|
1507
1599
|
...request.model ? { model: request.model } : {}
|
|
1508
1600
|
}
|
|
1509
1601
|
};
|
|
1602
|
+
case "sessions.draft":
|
|
1603
|
+
return {
|
|
1604
|
+
path: "/v1/desktop/sessions/draft",
|
|
1605
|
+
method: "POST",
|
|
1606
|
+
body: {
|
|
1607
|
+
request_id: request.requestId,
|
|
1608
|
+
cwd: request.cwd,
|
|
1609
|
+
...request.target ? { target: request.target } : {},
|
|
1610
|
+
...request.model ? { model: request.model } : {}
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1510
1613
|
case "sessions.get":
|
|
1511
1614
|
return {
|
|
1512
1615
|
path: `/v1/desktop/sessions/${request.sessionId}`,
|
|
@@ -1902,6 +2005,25 @@ function desktopEndpoint(request) {
|
|
|
1902
2005
|
query.set("until_ms", String(request.untilMs));
|
|
1903
2006
|
return { path: `/v1/desktop/analytics?${query}`, method: "GET" };
|
|
1904
2007
|
}
|
|
2008
|
+
/*
|
|
2009
|
+
* `/analytics/models`, NOT `/analytics?models=1`.
|
|
2010
|
+
*
|
|
2011
|
+
* The path segment is what keeps the two reads separable at the
|
|
2012
|
+
* transport: `desktopRequestDeadlineMs` sizes a wait from the op, and an
|
|
2013
|
+
* op that sometimes means "the cheap rollup" and sometimes "a 1.95 M-row
|
|
2014
|
+
* ledger scan" cannot have one honest budget. The two spellings of the
|
|
2015
|
+
* query string below are deliberately identical to `analytics.get`'s, so
|
|
2016
|
+
* the server resolves one window from either.
|
|
2017
|
+
*/
|
|
2018
|
+
case "analytics.models": {
|
|
2019
|
+
const query = new URLSearchParams({ days: String(request.days ?? 30) });
|
|
2020
|
+
if (request.sessionId) query.set("session_id", request.sessionId);
|
|
2021
|
+
if (request.sinceMs !== void 0)
|
|
2022
|
+
query.set("since_ms", String(request.sinceMs));
|
|
2023
|
+
if (request.untilMs !== void 0)
|
|
2024
|
+
query.set("until_ms", String(request.untilMs));
|
|
2025
|
+
return { path: `/v1/desktop/analytics/models?${query}`, method: "GET" };
|
|
2026
|
+
}
|
|
1905
2027
|
case "info.get":
|
|
1906
2028
|
return { path: "/v1/desktop/info", method: "GET" };
|
|
1907
2029
|
case "sessions.report": {
|
|
@@ -2136,6 +2258,115 @@ function readLaunchTarget(argv) {
|
|
|
2136
2258
|
if (argv.includes(OPEN_CATALOGUE_FLAG)) return { kind: "catalogue" };
|
|
2137
2259
|
return { kind: "restore" };
|
|
2138
2260
|
}
|
|
2261
|
+
const WINDOW_CHROME_MODES = ["integrated", "native"];
|
|
2262
|
+
const WINDOW_CHROME_FLAG = "--window-chrome";
|
|
2263
|
+
const WINDOW_CHROME_ENV = "LOCAL_OPERATOR_UI_WINDOW_CHROME";
|
|
2264
|
+
const WINDOW_CHROME_ARG = "--lo-window-chrome";
|
|
2265
|
+
const WINDOW_CHROME_HEIGHT = 40;
|
|
2266
|
+
const WINDOW_CHROME_TRANSPARENT = "#00000000";
|
|
2267
|
+
const DEFAULT_WINDOW_CHROME_COLORS = {
|
|
2268
|
+
ground: "#22201c",
|
|
2269
|
+
symbol: "#c2bcaf",
|
|
2270
|
+
symbolInactive: "#a6a091"
|
|
2271
|
+
};
|
|
2272
|
+
function chromePlatformFor(platform) {
|
|
2273
|
+
if (platform === "darwin") return "mac";
|
|
2274
|
+
if (platform === "win32") return "win";
|
|
2275
|
+
return "linux";
|
|
2276
|
+
}
|
|
2277
|
+
function parseWindowChromeMode(value) {
|
|
2278
|
+
if (value === void 0) return null;
|
|
2279
|
+
const normalized = value.trim().toLowerCase();
|
|
2280
|
+
return WINDOW_CHROME_MODES.includes(normalized) ? normalized : null;
|
|
2281
|
+
}
|
|
2282
|
+
function readWindowChromeFlag(argv, name = WINDOW_CHROME_FLAG) {
|
|
2283
|
+
let found = false;
|
|
2284
|
+
let valueless = false;
|
|
2285
|
+
let value;
|
|
2286
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2287
|
+
const argument = argv[index];
|
|
2288
|
+
if (argument === name) {
|
|
2289
|
+
const next = argv[index + 1];
|
|
2290
|
+
found = true;
|
|
2291
|
+
if (next === void 0 || next.startsWith("--")) {
|
|
2292
|
+
valueless = true;
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
value = next;
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
if (argument.startsWith(`${name}=`)) {
|
|
2299
|
+
found = true;
|
|
2300
|
+
value = argument.slice(name.length + 1);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return { found, value, valueless };
|
|
2304
|
+
}
|
|
2305
|
+
function resolveWindowChrome(input) {
|
|
2306
|
+
const problems = [];
|
|
2307
|
+
const argv = input.argv ?? [];
|
|
2308
|
+
const env = input.env ?? {};
|
|
2309
|
+
const persisted = input.persisted ?? null;
|
|
2310
|
+
const flag = readWindowChromeFlag(argv);
|
|
2311
|
+
if (flag.found) {
|
|
2312
|
+
const parsed = parseWindowChromeMode(flag.value);
|
|
2313
|
+
if (parsed !== null) return { mode: parsed, source: "argument", problems };
|
|
2314
|
+
if (flag.valueless) {
|
|
2315
|
+
problems.push(
|
|
2316
|
+
`${WINDOW_CHROME_FLAG} was given with no value and names nothing`
|
|
2317
|
+
);
|
|
2318
|
+
} else {
|
|
2319
|
+
problems.push(
|
|
2320
|
+
`${WINDOW_CHROME_FLAG}=${flag.value} is not one of ${WINDOW_CHROME_MODES.join(", ")}`
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
const fromEnv = env[WINDOW_CHROME_ENV];
|
|
2325
|
+
if (fromEnv !== void 0 && fromEnv.trim() !== "") {
|
|
2326
|
+
const parsed = parseWindowChromeMode(fromEnv);
|
|
2327
|
+
if (parsed !== null)
|
|
2328
|
+
return { mode: parsed, source: "environment", problems };
|
|
2329
|
+
problems.push(
|
|
2330
|
+
`${WINDOW_CHROME_ENV}=${fromEnv} is not one of ${WINDOW_CHROME_MODES.join(", ")}`
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
if (persisted !== null)
|
|
2334
|
+
return { mode: persisted, source: "persisted", problems };
|
|
2335
|
+
return { mode: "integrated", source: "default", problems };
|
|
2336
|
+
}
|
|
2337
|
+
function windowChromeArgument(facts) {
|
|
2338
|
+
return `${WINDOW_CHROME_ARG}=${facts.platform}:${facts.mode}`;
|
|
2339
|
+
}
|
|
2340
|
+
const WINDOW_CHROME_REPORT_CHANNEL = "window-chrome:report";
|
|
2341
|
+
const WINDOW_CHROME_STATE_CHANNEL = "window-chrome:state";
|
|
2342
|
+
const WINDOW_CHROME_MENU_CHANNEL = "window-chrome:popup-app-menu";
|
|
2343
|
+
const WINDOW_CHROME_FILE = "window-chrome.json";
|
|
2344
|
+
function windowChromeFactsFor(platform, mode) {
|
|
2345
|
+
return { platform: chromePlatformFor(platform), mode };
|
|
2346
|
+
}
|
|
2347
|
+
function windowChromeArgumentFor(platform, mode) {
|
|
2348
|
+
return windowChromeArgument(windowChromeFactsFor(platform, mode));
|
|
2349
|
+
}
|
|
2350
|
+
function resolveWindowChromeForLaunch(input) {
|
|
2351
|
+
const resolved = resolveWindowChrome({
|
|
2352
|
+
argv: input.argv,
|
|
2353
|
+
env: input.env,
|
|
2354
|
+
persisted: input.persisted?.mode ?? null
|
|
2355
|
+
});
|
|
2356
|
+
const problems = [...resolved.problems];
|
|
2357
|
+
const macRequestedNative = input.platform === "darwin" && resolved.mode === "native";
|
|
2358
|
+
if (macRequestedNative) {
|
|
2359
|
+
problems.push(
|
|
2360
|
+
"macOS has no native-frame fallback (the frame options are #462's shipped design and the renderer's 32px lane depends on them), so --window-chrome=native is ignored here: the app runs integrated"
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
return {
|
|
2364
|
+
mode: macRequestedNative ? "integrated" : resolved.mode,
|
|
2365
|
+
colors: input.persisted?.colors ?? DEFAULT_WINDOW_CHROME_COLORS,
|
|
2366
|
+
source: input.persisted?.colors ? `${resolved.source}+persisted-colors` : resolved.source,
|
|
2367
|
+
problems
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2139
2370
|
const SERVE_RUN_DIRNAME = "run/serve";
|
|
2140
2371
|
const REAPED_DIRNAME = "reaped";
|
|
2141
2372
|
const REAPED_MAX_FILES = 200;
|
|
@@ -5376,9 +5607,9 @@ const SILENCE_TIMEOUT_MS = 45e3;
|
|
|
5376
5607
|
const WATCHDOG_TICK_MS = 1e3;
|
|
5377
5608
|
const MAX_FRAME_BYTES$1 = 8 * 1024 * 1024;
|
|
5378
5609
|
const LINE_ENDING = /\r\n|\n|\r/;
|
|
5379
|
-
const BLANK_LINE = /(?:\r\n|\n|\r)(?:\r\n|\n|\r)/;
|
|
5610
|
+
const BLANK_LINE$1 = /(?:\r\n|\n|\r)(?:\r\n|\n|\r)/;
|
|
5380
5611
|
function nextRecord(buffer) {
|
|
5381
|
-
const blank = BLANK_LINE.exec(buffer);
|
|
5612
|
+
const blank = BLANK_LINE$1.exec(buffer);
|
|
5382
5613
|
if (blank === null) return null;
|
|
5383
5614
|
const data = [];
|
|
5384
5615
|
for (const line of buffer.slice(0, blank.index).split(LINE_ENDING)) {
|
|
@@ -7350,8 +7581,9 @@ function runBounded(command, args, env, bounds) {
|
|
|
7350
7581
|
);
|
|
7351
7582
|
});
|
|
7352
7583
|
}
|
|
7584
|
+
const NEWLINES = /\r?\n/;
|
|
7353
7585
|
const errorTail = (text, lines = 3) => {
|
|
7354
|
-
const kept = text.split(
|
|
7586
|
+
const kept = text.split(NEWLINES).map((line) => line.trim()).filter(Boolean);
|
|
7355
7587
|
if (kept.length === 0) return "";
|
|
7356
7588
|
const tail = kept.slice(-lines).join(" | ");
|
|
7357
7589
|
return kept.length > lines ? `… ${tail}` : tail;
|
|
@@ -21025,7 +21257,7 @@ function validateRequest(raw) {
|
|
|
21025
21257
|
};
|
|
21026
21258
|
}
|
|
21027
21259
|
async function startRpcServer(options) {
|
|
21028
|
-
const server =
|
|
21260
|
+
const server = http.createServer((req, res) => {
|
|
21029
21261
|
void handle(req, res, options);
|
|
21030
21262
|
});
|
|
21031
21263
|
const bound = await new Promise(
|
|
@@ -23868,6 +24100,41 @@ function describeTelemetryLaunch(decision) {
|
|
|
23868
24100
|
const detail = [decision.offReason, ...decision.problems].filter((part) => part !== null && part !== void 0).join("; ");
|
|
23869
24101
|
return `[telemetry] off: ${detail}`;
|
|
23870
24102
|
}
|
|
24103
|
+
function titlebarOptions(platform, chrome) {
|
|
24104
|
+
if (platform === "darwin") {
|
|
24105
|
+
return { titleBarStyle: "hidden" };
|
|
24106
|
+
}
|
|
24107
|
+
if (platform !== "win32" && platform !== "linux") {
|
|
24108
|
+
return {};
|
|
24109
|
+
}
|
|
24110
|
+
if (chrome.mode === "native") return {};
|
|
24111
|
+
const chromePlatform = chromePlatformFor(platform);
|
|
24112
|
+
return {
|
|
24113
|
+
titleBarStyle: "hidden",
|
|
24114
|
+
titleBarOverlay: {
|
|
24115
|
+
color: chromePlatform === "win" ? (
|
|
24116
|
+
/* Transparent: the app's own ground shows through. See the docblock. */
|
|
24117
|
+
WINDOW_CHROME_TRANSPARENT
|
|
24118
|
+
) : (
|
|
24119
|
+
/* Opaque reported ground: Linux forces alpha to ff, so a transparent
|
|
24120
|
+
value would paint the glyphs on black. The renderer reports the
|
|
24121
|
+
resolved ground of the element owning the controls' corner and main
|
|
24122
|
+
re-applies it with `setTitleBarOverlay`; this is the value for the
|
|
24123
|
+
first frame, before any report has arrived, which is why it is the
|
|
24124
|
+
palette's `canvas` rather than a guessed pane ground. */
|
|
24125
|
+
chrome.colors.ground
|
|
24126
|
+
),
|
|
24127
|
+
symbolColor: chrome.colors.symbol,
|
|
24128
|
+
/*
|
|
24129
|
+
* 40 is the app's own toolbar step, so the caption buttons are exactly as
|
|
24130
|
+
* tall as the row they end: the chat header, every pane toolbar and the
|
|
24131
|
+
* sidebar's brand row are all `h-10`. It sits inside Microsoft's 32-48
|
|
24132
|
+
* range. `height` is an integer; a fractional one is rejected.
|
|
24133
|
+
*/
|
|
24134
|
+
height: WINDOW_CHROME_HEIGHT
|
|
24135
|
+
}
|
|
24136
|
+
};
|
|
24137
|
+
}
|
|
23871
24138
|
const CHROMIUM_TRANSPORT_CODES = [
|
|
23872
24139
|
"ERR_INTERNET_DISCONNECTED",
|
|
23873
24140
|
"ERR_NETWORK_CHANGED",
|
|
@@ -24365,6 +24632,273 @@ function dropStream(stream) {
|
|
|
24365
24632
|
const destroy = stream?.destroy;
|
|
24366
24633
|
if (typeof destroy === "function") destroy.call(stream);
|
|
24367
24634
|
}
|
|
24635
|
+
const SERVER_RELEASE_REPO = "damianvtran/local-operator";
|
|
24636
|
+
const GITHUB_API_ORIGIN = "https://api.github.com";
|
|
24637
|
+
const RELEASE_NOTES_CACHE_FILE = "server-release-notes.json";
|
|
24638
|
+
const SUMMARY_LIMIT = 400;
|
|
24639
|
+
const RELEASE_NOTES_TIMEOUT_MS = 4e3;
|
|
24640
|
+
const CACHE_LIMIT = 12;
|
|
24641
|
+
const MAX_RESPONSE_BYTES = 512 * 1024;
|
|
24642
|
+
const TAG_PREFIX = /^v/i;
|
|
24643
|
+
function releaseTag(version) {
|
|
24644
|
+
const trimmed = String(version ?? "").trim();
|
|
24645
|
+
if (!trimmed) return "";
|
|
24646
|
+
return TAG_PREFIX.test(trimmed) ? trimmed : `v${trimmed}`;
|
|
24647
|
+
}
|
|
24648
|
+
function releasePageUrl(version) {
|
|
24649
|
+
return `https://github.com/${SERVER_RELEASE_REPO}/releases/tag/${releaseTag(version)}`;
|
|
24650
|
+
}
|
|
24651
|
+
function releaseNotesCachePath(userDataDir) {
|
|
24652
|
+
return path$1.join(userDataDir, RELEASE_NOTES_CACHE_FILE);
|
|
24653
|
+
}
|
|
24654
|
+
function normaliseCache(parsed) {
|
|
24655
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
24656
|
+
const cache = {};
|
|
24657
|
+
for (const [version, entry] of Object.entries(
|
|
24658
|
+
parsed
|
|
24659
|
+
)) {
|
|
24660
|
+
if (!entry || typeof entry !== "object") continue;
|
|
24661
|
+
const { version: named, summary, url } = entry;
|
|
24662
|
+
if (typeof summary !== "string" || !summary.trim()) continue;
|
|
24663
|
+
if (typeof url !== "string" || !url.trim()) continue;
|
|
24664
|
+
cache[version] = {
|
|
24665
|
+
version: typeof named === "string" && named ? named : version,
|
|
24666
|
+
summary,
|
|
24667
|
+
url
|
|
24668
|
+
};
|
|
24669
|
+
}
|
|
24670
|
+
return cache;
|
|
24671
|
+
}
|
|
24672
|
+
function readReleaseNotesCache(userDataDir) {
|
|
24673
|
+
try {
|
|
24674
|
+
return normaliseCache(
|
|
24675
|
+
JSON.parse(fs.readFileSync(releaseNotesCachePath(userDataDir), "utf8"))
|
|
24676
|
+
);
|
|
24677
|
+
} catch {
|
|
24678
|
+
return {};
|
|
24679
|
+
}
|
|
24680
|
+
}
|
|
24681
|
+
function writeReleaseNotesCache(userDataDir, cache) {
|
|
24682
|
+
try {
|
|
24683
|
+
fs.mkdirSync(userDataDir, { recursive: true });
|
|
24684
|
+
fs.writeFileSync(
|
|
24685
|
+
releaseNotesCachePath(userDataDir),
|
|
24686
|
+
`${JSON.stringify(cache, null, 2)}
|
|
24687
|
+
`,
|
|
24688
|
+
"utf8"
|
|
24689
|
+
);
|
|
24690
|
+
} catch {
|
|
24691
|
+
}
|
|
24692
|
+
}
|
|
24693
|
+
function rememberReleaseNotes(cache, notes) {
|
|
24694
|
+
const next = { ...cache, [notes.version]: notes };
|
|
24695
|
+
const versions = Object.keys(next);
|
|
24696
|
+
if (versions.length <= CACHE_LIMIT) return next;
|
|
24697
|
+
const trimmed = {};
|
|
24698
|
+
for (const version of versions.slice(versions.length - CACHE_LIMIT)) {
|
|
24699
|
+
trimmed[version] = next[version];
|
|
24700
|
+
}
|
|
24701
|
+
return trimmed;
|
|
24702
|
+
}
|
|
24703
|
+
function cachedReleaseNotes(cache, version) {
|
|
24704
|
+
return cache[String(version ?? "").trim()] ?? null;
|
|
24705
|
+
}
|
|
24706
|
+
function summariseReleaseBody(body, limit = SUMMARY_LIMIT) {
|
|
24707
|
+
if (typeof body !== "string") return null;
|
|
24708
|
+
const blocks = body.replace(CRLF, "\n").split(BLANK_LINE);
|
|
24709
|
+
for (const block of blocks) {
|
|
24710
|
+
const text = flattenBlock(block);
|
|
24711
|
+
if (text && !isMachineRow(text)) return truncateSummary(text, limit);
|
|
24712
|
+
}
|
|
24713
|
+
return null;
|
|
24714
|
+
}
|
|
24715
|
+
function isMachineRow(text) {
|
|
24716
|
+
return PR_HEADLINE.test(text) || MERGE_CITATION.test(text);
|
|
24717
|
+
}
|
|
24718
|
+
const CRLF = /\r\n?/g;
|
|
24719
|
+
const BLANK_LINE = /\n{2,}/;
|
|
24720
|
+
const HEADING_LINE = /^#{1,6}\s/;
|
|
24721
|
+
const RULE_LINE = /^(?:[-*_]\s*){3,}$/;
|
|
24722
|
+
const COMMENT_OPEN = /^<!--/;
|
|
24723
|
+
const COMMENT_CLOSE = /-->$/;
|
|
24724
|
+
const BLOCKQUOTE_MARKER = /^>\s?/;
|
|
24725
|
+
const LIST_MARKER = /^(?:[-*+]|\d+[.)])\s+/;
|
|
24726
|
+
const IMAGE = /!\[([^\]]*)\]\([^)]*\)/g;
|
|
24727
|
+
const LINK = /\[([^\]]*)\]\([^)]*\)/g;
|
|
24728
|
+
const CODE_SPAN = /`([^`]*)`/g;
|
|
24729
|
+
const CODE_PLACEHOLDER = /\uE000(\d+)\uE001/g;
|
|
24730
|
+
const MASK_ALPHABET = /[\uE000\uE001]/g;
|
|
24731
|
+
const EMPHASIS = /(?<!\*)\*{3}([\s\S]+?)(?<!\*)\*{3}(?!\*)|(?<!\*)\*{2}([\s\S]+?)(?<!\*)\*{2}(?!\*)|(?<!\*)\*(?!\*)([\s\S]+?)(?<!\*)\*(?!\*)/g;
|
|
24732
|
+
const WHITESPACE_RUN = /\s+/g;
|
|
24733
|
+
const PR_HEADLINE = /^#\d+\s+[—–-]\s/;
|
|
24734
|
+
const MERGE_CITATION = /\(merge [0-9a-f]{7,40}\)$/i;
|
|
24735
|
+
function flattenBlock(block) {
|
|
24736
|
+
const lines = block.split("\n").flatMap((line) => {
|
|
24737
|
+
const trimmed = line.trim();
|
|
24738
|
+
if (!trimmed) return [];
|
|
24739
|
+
if (HEADING_LINE.test(trimmed)) return [];
|
|
24740
|
+
if (RULE_LINE.test(trimmed)) return [];
|
|
24741
|
+
if (COMMENT_OPEN.test(trimmed) || COMMENT_CLOSE.test(trimmed)) return [];
|
|
24742
|
+
return [trimmed.replace(BLOCKQUOTE_MARKER, "").replace(LIST_MARKER, "")];
|
|
24743
|
+
});
|
|
24744
|
+
if (lines.length === 0) return "";
|
|
24745
|
+
return collapseWhitespace(flattenInline(lines.join(" ")));
|
|
24746
|
+
}
|
|
24747
|
+
function flattenInline(text) {
|
|
24748
|
+
const code = [];
|
|
24749
|
+
const masked = text.replace(MASK_ALPHABET, " ").replace(IMAGE, "$1").replace(LINK, "$1").replace(CODE_SPAN, (_span, inner) => {
|
|
24750
|
+
code.push(inner);
|
|
24751
|
+
return `${code.length - 1}`;
|
|
24752
|
+
});
|
|
24753
|
+
const unwrapped = masked.replace(EMPHASIS, unwrapEmphasis).replace(EMPHASIS, unwrapEmphasis);
|
|
24754
|
+
return unwrapped.replace(CODE_PLACEHOLDER, (placeholder, index) => {
|
|
24755
|
+
return code[Number(index)] ?? placeholder;
|
|
24756
|
+
});
|
|
24757
|
+
}
|
|
24758
|
+
function unwrapEmphasis(_match, triple, strong, italic) {
|
|
24759
|
+
return triple ?? strong ?? italic ?? "";
|
|
24760
|
+
}
|
|
24761
|
+
function collapseWhitespace(text) {
|
|
24762
|
+
return text.replace(WHITESPACE_RUN, " ").trim();
|
|
24763
|
+
}
|
|
24764
|
+
function truncateSummary(text, limit) {
|
|
24765
|
+
if (text.length <= limit) return text;
|
|
24766
|
+
const cut = text.slice(0, Math.max(0, limit - 3));
|
|
24767
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
24768
|
+
const words = lastSpace > limit / 2 ? cut.slice(0, lastSpace) : cut;
|
|
24769
|
+
const trimmed = words.trimEnd();
|
|
24770
|
+
const dropped = DanglingWord.test(trimmed) ? trimmed.slice(0, trimmed.lastIndexOf(" ")).trimEnd() : trimmed;
|
|
24771
|
+
const balanced = stripUnpairedEmphasis(dropped);
|
|
24772
|
+
const sentence = balanced.replace(TRAILING_STOP, "");
|
|
24773
|
+
return `${sentence}...`;
|
|
24774
|
+
}
|
|
24775
|
+
const DanglingWord = /(?:^|\s)(?:a|an|as|at|by|in|of|on|or|to)$/i;
|
|
24776
|
+
const TRAILING_STOP = /[.!?]$/;
|
|
24777
|
+
function stripUnpairedEmphasis(text) {
|
|
24778
|
+
const leading = text.match(/^\*{1,3}/);
|
|
24779
|
+
if (leading && !text.slice(leading[0].length).includes("*")) {
|
|
24780
|
+
return text.slice(leading[0].length).trimStart();
|
|
24781
|
+
}
|
|
24782
|
+
const trailing = text.match(/\*{1,3}$/);
|
|
24783
|
+
if (trailing && !text.slice(0, -trailing[0].length).includes("*")) {
|
|
24784
|
+
return text.slice(0, -trailing[0].length).trimEnd();
|
|
24785
|
+
}
|
|
24786
|
+
return text;
|
|
24787
|
+
}
|
|
24788
|
+
function parseReleaseResponse(payload, version) {
|
|
24789
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
24790
|
+
return null;
|
|
24791
|
+
}
|
|
24792
|
+
const release = payload;
|
|
24793
|
+
const summary = summariseReleaseBody(release.body);
|
|
24794
|
+
if (!summary) return null;
|
|
24795
|
+
const url = typeof release.html_url === "string" && release.html_url.trim() ? release.html_url.trim() : releasePageUrl(version);
|
|
24796
|
+
return { version: String(version).trim(), summary, url };
|
|
24797
|
+
}
|
|
24798
|
+
async function fetchServerReleaseNotes(version, options) {
|
|
24799
|
+
const named = String(version ?? "").trim();
|
|
24800
|
+
const tag = releaseTag(named);
|
|
24801
|
+
if (!tag) return { status: "absent", reason: "the offer named no version" };
|
|
24802
|
+
const cache = readReleaseNotesCache(options.userDataDir);
|
|
24803
|
+
const hit = cachedReleaseNotes(cache, named);
|
|
24804
|
+
if (hit) return { status: "found", notes: hit, cached: true };
|
|
24805
|
+
const origin = options.apiOrigin ?? GITHUB_API_ORIGIN;
|
|
24806
|
+
const url = `${origin}/repos/${SERVER_RELEASE_REPO}/releases/tags/${tag}`;
|
|
24807
|
+
let answer;
|
|
24808
|
+
try {
|
|
24809
|
+
answer = await getJson(url, options.timeoutMs ?? RELEASE_NOTES_TIMEOUT_MS);
|
|
24810
|
+
} catch (error) {
|
|
24811
|
+
return {
|
|
24812
|
+
status: "absent",
|
|
24813
|
+
reason: `the lookup for ${tag} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
24814
|
+
};
|
|
24815
|
+
}
|
|
24816
|
+
if (answer.status !== 200) {
|
|
24817
|
+
return {
|
|
24818
|
+
status: "absent",
|
|
24819
|
+
reason: `GitHub answered ${answer.status} for ${tag}`
|
|
24820
|
+
};
|
|
24821
|
+
}
|
|
24822
|
+
let payload;
|
|
24823
|
+
try {
|
|
24824
|
+
payload = JSON.parse(answer.body);
|
|
24825
|
+
} catch {
|
|
24826
|
+
return {
|
|
24827
|
+
status: "absent",
|
|
24828
|
+
reason: `GitHub's answer for ${tag} was not JSON`
|
|
24829
|
+
};
|
|
24830
|
+
}
|
|
24831
|
+
const notes = parseReleaseResponse(payload, named);
|
|
24832
|
+
if (!notes) {
|
|
24833
|
+
return { status: "absent", reason: `the ${tag} release carries no notes` };
|
|
24834
|
+
}
|
|
24835
|
+
writeReleaseNotesCache(
|
|
24836
|
+
options.userDataDir,
|
|
24837
|
+
rememberReleaseNotes(cache, notes)
|
|
24838
|
+
);
|
|
24839
|
+
return { status: "found", notes, cached: false };
|
|
24840
|
+
}
|
|
24841
|
+
function getJson(url, timeoutMs) {
|
|
24842
|
+
return new Promise((resolve, reject) => {
|
|
24843
|
+
let settled = false;
|
|
24844
|
+
const settle2 = (finish) => {
|
|
24845
|
+
if (settled) return;
|
|
24846
|
+
settled = true;
|
|
24847
|
+
finish();
|
|
24848
|
+
};
|
|
24849
|
+
const fail = (error) => settle2(
|
|
24850
|
+
() => reject(error instanceof Error ? error : new Error(String(error)))
|
|
24851
|
+
);
|
|
24852
|
+
let parsed;
|
|
24853
|
+
try {
|
|
24854
|
+
parsed = new URL(url);
|
|
24855
|
+
} catch (error) {
|
|
24856
|
+
fail(error);
|
|
24857
|
+
return;
|
|
24858
|
+
}
|
|
24859
|
+
const transport = parsed.protocol === "http:" ? http__namespace : https__namespace;
|
|
24860
|
+
const request = transport.get(
|
|
24861
|
+
parsed,
|
|
24862
|
+
{
|
|
24863
|
+
timeout: timeoutMs,
|
|
24864
|
+
headers: {
|
|
24865
|
+
// GitHub refuses a request with no user agent, and the versioned
|
|
24866
|
+
// media type is what pins the payload shape this module parses.
|
|
24867
|
+
"user-agent": "local-operator-ui",
|
|
24868
|
+
accept: "application/vnd.github+json"
|
|
24869
|
+
}
|
|
24870
|
+
},
|
|
24871
|
+
(response) => {
|
|
24872
|
+
const chunks = [];
|
|
24873
|
+
let length = 0;
|
|
24874
|
+
response.on("data", (chunk) => {
|
|
24875
|
+
length += chunk.length;
|
|
24876
|
+
if (length > MAX_RESPONSE_BYTES) {
|
|
24877
|
+
request.destroy();
|
|
24878
|
+
fail(new Error("the answer was larger than this lookup accepts"));
|
|
24879
|
+
return;
|
|
24880
|
+
}
|
|
24881
|
+
chunks.push(chunk);
|
|
24882
|
+
});
|
|
24883
|
+
response.on(
|
|
24884
|
+
"end",
|
|
24885
|
+
() => settle2(
|
|
24886
|
+
() => resolve({
|
|
24887
|
+
status: response.statusCode ?? 0,
|
|
24888
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
24889
|
+
})
|
|
24890
|
+
)
|
|
24891
|
+
);
|
|
24892
|
+
response.on("error", fail);
|
|
24893
|
+
}
|
|
24894
|
+
);
|
|
24895
|
+
request.setTimeout(timeoutMs, () => {
|
|
24896
|
+
request.destroy();
|
|
24897
|
+
fail(new Error(`no answer within ${timeoutMs}ms`));
|
|
24898
|
+
});
|
|
24899
|
+
request.on("error", fail);
|
|
24900
|
+
});
|
|
24901
|
+
}
|
|
24368
24902
|
function serverUpdateFailureRemedy(input) {
|
|
24369
24903
|
if (input.rebuildRoute && /REFUSING to release a stale ref/i.test(input.diagnosis)) {
|
|
24370
24904
|
return "This checkout is behind its remote, which is what `lop-update` refused to build from: bring the checkout up to date, and the next press here will build it.";
|
|
@@ -27473,6 +28007,51 @@ ${heal.removed.join("\n")}`,
|
|
|
27473
28007
|
});
|
|
27474
28008
|
});
|
|
27475
28009
|
}
|
|
28010
|
+
/**
|
|
28011
|
+
* What changed in the server version this offer names, or null.
|
|
28012
|
+
*
|
|
28013
|
+
* The read is off the CRITICAL PATH in the sense that matters: it cannot fail
|
|
28014
|
+
* the offer. Every way it can come back empty - no release for the tag, an
|
|
28015
|
+
* unreachable host, an answer that is not JSON, a body with no prose in it -
|
|
28016
|
+
* is a `null` here and a line in this log, and the panel renders without the
|
|
28017
|
+
* paragraph rather than not at all. That is the shape the app's own channel
|
|
28018
|
+
* has always had (`updateInfo.releaseNotes` is optional and the card is drawn
|
|
28019
|
+
* either way), and it is the one a panel asking a reader to move has to keep:
|
|
28020
|
+
* the notes are the reason to move, never the condition for being told.
|
|
28021
|
+
*
|
|
28022
|
+
* AWAITED RATHER THAN SENT LATER, deliberately. The alternative is a second
|
|
28023
|
+
* renderer event carrying the notes after the offer is already on screen,
|
|
28024
|
+
* which buys a panel that grows under the reader's eyes and a state machine to
|
|
28025
|
+
* review in exchange for saving the one bounded round trip this makes per
|
|
28026
|
+
* version (every later check reads the cache). The bound is
|
|
28027
|
+
* `RELEASE_NOTES_TIMEOUT_MS`, and it is smaller than the wait the version read
|
|
28028
|
+
* above it can already impose with no bound at all.
|
|
28029
|
+
*/
|
|
28030
|
+
async readServerReleaseNotes(version) {
|
|
28031
|
+
try {
|
|
28032
|
+
const lookup = await fetchServerReleaseNotes(version, {
|
|
28033
|
+
userDataDir: electron.app.getPath("userData")
|
|
28034
|
+
});
|
|
28035
|
+
if (lookup.status === "found") {
|
|
28036
|
+
logger.info(
|
|
28037
|
+
`Release notes for ${version} read from ${lookup.cached ? "the cache" : "GitHub"} (${lookup.notes.summary.length} chars)`,
|
|
28038
|
+
LogFileType.UPDATE_SERVICE
|
|
28039
|
+
);
|
|
28040
|
+
return lookup.notes;
|
|
28041
|
+
}
|
|
28042
|
+
logger.info(
|
|
28043
|
+
`No release notes for ${version}: ${lookup.reason}`,
|
|
28044
|
+
LogFileType.UPDATE_SERVICE
|
|
28045
|
+
);
|
|
28046
|
+
return null;
|
|
28047
|
+
} catch (error) {
|
|
28048
|
+
logger.warn(
|
|
28049
|
+
`Could not read release notes for ${version}: ${error}`,
|
|
28050
|
+
LogFileType.UPDATE_SERVICE
|
|
28051
|
+
);
|
|
28052
|
+
return null;
|
|
28053
|
+
}
|
|
28054
|
+
}
|
|
27476
28055
|
/**
|
|
27477
28056
|
* Check for backend updates using health API and PyPI
|
|
27478
28057
|
* @param silent - Whether to suppress notifications on no update
|
|
@@ -27924,9 +28503,11 @@ ${heal.removed.join("\n")}`,
|
|
|
27924
28503
|
`New backend version available: ${latestVersion} (installed: ${installedVersion})`,
|
|
27925
28504
|
LogFileType.UPDATE_SERVICE
|
|
27926
28505
|
);
|
|
28506
|
+
const releaseNotes = await this.readServerReleaseNotes(latestVersion);
|
|
27927
28507
|
const updateInfo = {
|
|
27928
28508
|
currentVersion: installedVersion,
|
|
27929
28509
|
latestVersion,
|
|
28510
|
+
releaseNotes,
|
|
27930
28511
|
/*
|
|
27931
28512
|
* THE PROCESS'S OWN READING, not `/health`'s (review round 2, T1): the
|
|
27932
28513
|
* panel's sentence is "the server you are using is running X until it
|
|
@@ -30162,6 +30743,193 @@ class ViewerRecordPublisher {
|
|
|
30162
30743
|
}
|
|
30163
30744
|
}
|
|
30164
30745
|
}
|
|
30746
|
+
function windowChromeFilePath(userDataDir) {
|
|
30747
|
+
return path$1.join(userDataDir, WINDOW_CHROME_FILE);
|
|
30748
|
+
}
|
|
30749
|
+
function readPersistedWindowChrome(userDataDir) {
|
|
30750
|
+
const raw = readJson(
|
|
30751
|
+
windowChromeFilePath(userDataDir)
|
|
30752
|
+
);
|
|
30753
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
30754
|
+
const mode = raw.mode === "native" || raw.mode === "integrated" ? raw.mode : null;
|
|
30755
|
+
if (mode === null) return null;
|
|
30756
|
+
const colors = parseChromeColors(raw.colors);
|
|
30757
|
+
return {
|
|
30758
|
+
mode,
|
|
30759
|
+
...colors ? { colors } : {},
|
|
30760
|
+
...typeof raw.themeId === "string" ? { themeId: raw.themeId } : {}
|
|
30761
|
+
};
|
|
30762
|
+
}
|
|
30763
|
+
function writePersistedWindowChrome(userDataDir, value) {
|
|
30764
|
+
return writeJsonAtomic(windowChromeFilePath(userDataDir), value);
|
|
30765
|
+
}
|
|
30766
|
+
const HEX_COLOR = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
30767
|
+
const FUNCTIONAL_COLOR = /^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(?:,\s*(?:0|1|0?\.\d+)\s*)?\)$/;
|
|
30768
|
+
function isValidChromeColor(value) {
|
|
30769
|
+
if (typeof value !== "string") return false;
|
|
30770
|
+
const trimmed = value.trim();
|
|
30771
|
+
return HEX_COLOR.test(trimmed) || FUNCTIONAL_COLOR.test(trimmed);
|
|
30772
|
+
}
|
|
30773
|
+
function mergeChromeColors(current, reported) {
|
|
30774
|
+
if (reported === void 0 || reported === null) return current;
|
|
30775
|
+
if (typeof reported !== "object") return null;
|
|
30776
|
+
const record = reported;
|
|
30777
|
+
const next = { ...current };
|
|
30778
|
+
let sawOne = false;
|
|
30779
|
+
for (const key of [
|
|
30780
|
+
"ground",
|
|
30781
|
+
"symbol",
|
|
30782
|
+
"symbolInactive"
|
|
30783
|
+
]) {
|
|
30784
|
+
if (!(key in record)) continue;
|
|
30785
|
+
const value = record[key];
|
|
30786
|
+
if (!isValidChromeColor(value)) return null;
|
|
30787
|
+
sawOne = true;
|
|
30788
|
+
next[key] = value.trim();
|
|
30789
|
+
}
|
|
30790
|
+
return sawOne ? next : null;
|
|
30791
|
+
}
|
|
30792
|
+
function parseChromeColors(value) {
|
|
30793
|
+
if (value === null || typeof value !== "object") return null;
|
|
30794
|
+
const record = value;
|
|
30795
|
+
const ground = record.ground;
|
|
30796
|
+
const symbol = record.symbol;
|
|
30797
|
+
const symbolInactive = record.symbolInactive;
|
|
30798
|
+
if (!isValidChromeColor(ground) || !isValidChromeColor(symbol) || !isValidChromeColor(symbolInactive)) {
|
|
30799
|
+
return null;
|
|
30800
|
+
}
|
|
30801
|
+
return { ground, symbol, symbolInactive };
|
|
30802
|
+
}
|
|
30803
|
+
function resolveLaunchWindowChrome(input) {
|
|
30804
|
+
let persisted = null;
|
|
30805
|
+
try {
|
|
30806
|
+
persisted = readPersistedWindowChrome(input.userDataDir);
|
|
30807
|
+
} catch {
|
|
30808
|
+
persisted = null;
|
|
30809
|
+
}
|
|
30810
|
+
return resolveWindowChromeForLaunch({
|
|
30811
|
+
argv: input.argv,
|
|
30812
|
+
env: input.env,
|
|
30813
|
+
persisted,
|
|
30814
|
+
platform: input.platform
|
|
30815
|
+
});
|
|
30816
|
+
}
|
|
30817
|
+
function attachWindowChrome(options) {
|
|
30818
|
+
const { window, mode, userDataDir, headless: headless2, log } = options;
|
|
30819
|
+
let colors = options.colors;
|
|
30820
|
+
let cornerGround;
|
|
30821
|
+
let themeId;
|
|
30822
|
+
const platform = chromePlatformFor(process.platform);
|
|
30823
|
+
const hasOverlay = platform !== "mac" && mode === "integrated";
|
|
30824
|
+
const applyOverlay = (focused) => {
|
|
30825
|
+
if (!hasOverlay) return;
|
|
30826
|
+
const focusedNow = focused && !window.isDestroyed();
|
|
30827
|
+
try {
|
|
30828
|
+
window.setTitleBarOverlay({
|
|
30829
|
+
...platform === "linux" ? { color: cornerGround ?? colors.ground } : {},
|
|
30830
|
+
symbolColor: focusedNow ? colors.symbol : colors.symbolInactive
|
|
30831
|
+
});
|
|
30832
|
+
} catch (error) {
|
|
30833
|
+
log(
|
|
30834
|
+
`[window-chrome] setTitleBarOverlay refused (${String(error)}) — the window keeps its previous caption colours`
|
|
30835
|
+
);
|
|
30836
|
+
}
|
|
30837
|
+
};
|
|
30838
|
+
const currentState = () => ({
|
|
30839
|
+
fullScreen: window.isDestroyed() ? false : window.isFullScreen(),
|
|
30840
|
+
maximized: window.isDestroyed() ? false : window.isMaximized(),
|
|
30841
|
+
focused: window.isDestroyed() ? false : window.isFocused()
|
|
30842
|
+
});
|
|
30843
|
+
const pushState = () => {
|
|
30844
|
+
if (window.isDestroyed()) return;
|
|
30845
|
+
window.webContents.send(WINDOW_CHROME_STATE_CHANNEL, currentState());
|
|
30846
|
+
};
|
|
30847
|
+
const persist = () => {
|
|
30848
|
+
writePersistedWindowChrome(userDataDir, {
|
|
30849
|
+
mode,
|
|
30850
|
+
colors,
|
|
30851
|
+
...themeId ? { themeId } : {}
|
|
30852
|
+
});
|
|
30853
|
+
};
|
|
30854
|
+
const onReport = (event, report) => {
|
|
30855
|
+
if (event.sender !== window.webContents) return false;
|
|
30856
|
+
const next = mergeChromeColors(colors, report?.colors);
|
|
30857
|
+
if (next === null) {
|
|
30858
|
+
log(
|
|
30859
|
+
"[window-chrome] refused a report whose colours did not parse — the caption colours are unchanged"
|
|
30860
|
+
);
|
|
30861
|
+
return false;
|
|
30862
|
+
}
|
|
30863
|
+
colors = next;
|
|
30864
|
+
const nextCorner = isValidChromeColor(report?.cornerGround) ? report.cornerGround.trim() : void 0;
|
|
30865
|
+
cornerGround = nextCorner;
|
|
30866
|
+
if (typeof report?.themeId === "string") themeId = report.themeId;
|
|
30867
|
+
window.setBackgroundColor(colors.ground);
|
|
30868
|
+
applyOverlay(!window.isDestroyed() && window.isFocused());
|
|
30869
|
+
persist();
|
|
30870
|
+
log(
|
|
30871
|
+
`[window-chrome] applied themeId=${themeId ?? "(none)"} ground=${colors.ground} symbol=${colors.symbol}${cornerGround ? ` cornerGround=${cornerGround}` : ""}`
|
|
30872
|
+
);
|
|
30873
|
+
return true;
|
|
30874
|
+
};
|
|
30875
|
+
const onPopupAppMenu = (event) => {
|
|
30876
|
+
if (event.sender !== window.webContents) return false;
|
|
30877
|
+
if (headless2) {
|
|
30878
|
+
log(
|
|
30879
|
+
"[window-chrome] app menu popup refused: this launch is headless, so there is no window to hang it from"
|
|
30880
|
+
);
|
|
30881
|
+
return false;
|
|
30882
|
+
}
|
|
30883
|
+
if (window.isDestroyed()) return false;
|
|
30884
|
+
const menu = electron.Menu.getApplicationMenu();
|
|
30885
|
+
if (menu === null) {
|
|
30886
|
+
log("[window-chrome] app menu popup refused: no application menu is set");
|
|
30887
|
+
return false;
|
|
30888
|
+
}
|
|
30889
|
+
const [x, y] = window.getPosition();
|
|
30890
|
+
menu.popup({ window, x, y: y + WINDOW_CHROME_HEIGHT });
|
|
30891
|
+
return true;
|
|
30892
|
+
};
|
|
30893
|
+
electron.ipcMain.handle(WINDOW_CHROME_REPORT_CHANNEL, onReport);
|
|
30894
|
+
electron.ipcMain.handle(WINDOW_CHROME_MENU_CHANNEL, onPopupAppMenu);
|
|
30895
|
+
const onFullScreen = () => {
|
|
30896
|
+
pushState();
|
|
30897
|
+
};
|
|
30898
|
+
const onFocus = () => {
|
|
30899
|
+
applyOverlay(true);
|
|
30900
|
+
pushState();
|
|
30901
|
+
};
|
|
30902
|
+
const onBlur = () => {
|
|
30903
|
+
applyOverlay(false);
|
|
30904
|
+
pushState();
|
|
30905
|
+
};
|
|
30906
|
+
const onResizeState = () => {
|
|
30907
|
+
pushState();
|
|
30908
|
+
};
|
|
30909
|
+
window.on("enter-full-screen", onFullScreen);
|
|
30910
|
+
window.on("leave-full-screen", onFullScreen);
|
|
30911
|
+
window.on("maximize", onResizeState);
|
|
30912
|
+
window.on("unmaximize", onResizeState);
|
|
30913
|
+
window.on("focus", onFocus);
|
|
30914
|
+
window.on("blur", onBlur);
|
|
30915
|
+
window.setBackgroundColor(colors.ground);
|
|
30916
|
+
const sync = () => {
|
|
30917
|
+
applyOverlay(!window.isDestroyed() && window.isFocused());
|
|
30918
|
+
window.setBackgroundColor(colors.ground);
|
|
30919
|
+
pushState();
|
|
30920
|
+
};
|
|
30921
|
+
const dispose = () => {
|
|
30922
|
+
electron.ipcMain.removeHandler(WINDOW_CHROME_REPORT_CHANNEL);
|
|
30923
|
+
electron.ipcMain.removeHandler(WINDOW_CHROME_MENU_CHANNEL);
|
|
30924
|
+
window.removeListener("enter-full-screen", onFullScreen);
|
|
30925
|
+
window.removeListener("leave-full-screen", onFullScreen);
|
|
30926
|
+
window.removeListener("maximize", onResizeState);
|
|
30927
|
+
window.removeListener("unmaximize", onResizeState);
|
|
30928
|
+
window.removeListener("focus", onFocus);
|
|
30929
|
+
window.removeListener("blur", onBlur);
|
|
30930
|
+
};
|
|
30931
|
+
return { sync, dispose };
|
|
30932
|
+
}
|
|
30165
30933
|
const BASE64_FILE_EXTENSIONS = ["csv", "tsv", "xls", "xlsx", "ods"];
|
|
30166
30934
|
const resolveUserPath = (filePath, cwd) => resolveUserPath$1(filePath, cwd, electron.app.getPath("home"));
|
|
30167
30935
|
const windowLaunch = resolveWindowLaunchPlan({
|
|
@@ -30430,7 +31198,36 @@ const ownLaunchRequest = () => ({
|
|
|
30430
31198
|
trigger: "initial-present"
|
|
30431
31199
|
});
|
|
30432
31200
|
function createWindow(initialSession = null, openCatalogue = false, request = ownLaunchRequest()) {
|
|
31201
|
+
const chrome = resolveLaunchWindowChrome({
|
|
31202
|
+
argv: process.argv,
|
|
31203
|
+
env: launchEnv,
|
|
31204
|
+
userDataDir: electron.app.getPath("userData"),
|
|
31205
|
+
platform: process.platform
|
|
31206
|
+
});
|
|
31207
|
+
for (const problem of chrome.problems) {
|
|
31208
|
+
logger.warn(`[window-chrome] ${problem}`, LogFileType.BACKEND);
|
|
31209
|
+
console.log(`[window-chrome] ${problem}`);
|
|
31210
|
+
}
|
|
30433
31211
|
const mainWindow2 = new electron.BrowserWindow({
|
|
31212
|
+
/*
|
|
31213
|
+
* The frame, per platform, from a pure function so the matrix is exercised on
|
|
31214
|
+
* any host (`scripts/titlebar-options.test.mjs`). macOS hides its title bar
|
|
31215
|
+
* and keeps the native traffic lights, which is why the renderer reserves their
|
|
31216
|
+
* 32px lane at the top of the shell (`chat-layout.tsx`) - the lane exists
|
|
31217
|
+
* because this line does, and shipping one without the other is either 32px of
|
|
31218
|
+
* blank space under a native title bar or lights drawn over the app's own row.
|
|
31219
|
+
*/
|
|
31220
|
+
...titlebarOptions(process.platform, {
|
|
31221
|
+
mode: chrome.mode,
|
|
31222
|
+
colors: chrome.colors
|
|
31223
|
+
}),
|
|
31224
|
+
/*
|
|
31225
|
+
* The ground the OS paints before the renderer's first frame. Without it
|
|
31226
|
+
* Electron's default is `#FFF`, so every launch on a dark palette opened with a
|
|
31227
|
+
* white flash - the same defect `INSTALL_WINDOW_CANVAS` fixed for the installer
|
|
31228
|
+
* window, and a hard-coded palette value that a test keeps honest.
|
|
31229
|
+
*/
|
|
31230
|
+
backgroundColor: chrome.colors.ground,
|
|
30434
31231
|
width: windowLaunch.width,
|
|
30435
31232
|
height: windowLaunch.height,
|
|
30436
31233
|
// The layout is verified down to 800x600 and not below: the app rail,
|
|
@@ -30501,9 +31298,18 @@ function createWindow(initialSession = null, openCatalogue = false, request = ow
|
|
|
30501
31298
|
* silent. `rendererArgumentFlags` below concatenates them, and is `{}` when
|
|
30502
31299
|
* neither applies, so "adds no option at all" stays literally true.
|
|
30503
31300
|
*/
|
|
30504
|
-
...rendererArgumentFlags(initialSession, openCatalogue)
|
|
31301
|
+
...rendererArgumentFlags(initialSession, openCatalogue, chrome.mode)
|
|
30505
31302
|
}
|
|
30506
31303
|
});
|
|
31304
|
+
const chromeController = attachWindowChrome({
|
|
31305
|
+
window: mainWindow2,
|
|
31306
|
+
mode: chrome.mode,
|
|
31307
|
+
colors: chrome.colors,
|
|
31308
|
+
userDataDir: electron.app.getPath("userData"),
|
|
31309
|
+
headless: windowLaunch.mode === "headless",
|
|
31310
|
+
log: (line) => logger.info(line, LogFileType.BACKEND)
|
|
31311
|
+
});
|
|
31312
|
+
mainWindow2.on("closed", () => chromeController.dispose());
|
|
30507
31313
|
if (initialSession)
|
|
30508
31314
|
holdPresentUntilConversation(mainWindow2, initialSession, request);
|
|
30509
31315
|
mainWindow2.webContents.on("console-message", (_event, level, message) => {
|
|
@@ -30651,8 +31457,16 @@ function launchArgumentFlags(initialSession, openCatalogue) {
|
|
|
30651
31457
|
if (openCatalogue) return { additionalArguments: [OPEN_CATALOGUE_FLAG] };
|
|
30652
31458
|
return {};
|
|
30653
31459
|
}
|
|
30654
|
-
function rendererArgumentFlags(initialSession, openCatalogue) {
|
|
31460
|
+
function rendererArgumentFlags(initialSession, openCatalogue, chromeMode) {
|
|
30655
31461
|
const flags = [
|
|
31462
|
+
/*
|
|
31463
|
+
* The chrome facts, and they are ALWAYS written - unlike the conversation or
|
|
31464
|
+
* the dev driver's arming entry, which appear only when they apply. The
|
|
31465
|
+
* renderer chooses between two incompatible layouts on this value, so a reader
|
|
31466
|
+
* that had to infer it from an absent entry would be guessing; see
|
|
31467
|
+
* `readWindowChromeArgument` for why the guess fails towards `native`.
|
|
31468
|
+
*/
|
|
31469
|
+
windowChromeArgumentFor(process.platform, chromeMode),
|
|
30656
31470
|
...launchArgumentFlags(initialSession, openCatalogue).additionalArguments ?? [],
|
|
30657
31471
|
...devDriverWebPreferences.additionalArguments ?? [],
|
|
30658
31472
|
...telemetryWebPreferences.additionalArguments ?? []
|