local-operator-ui 0.34.44 → 0.34.45

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.
Files changed (113) hide show
  1. package/out/main/index.js +948 -66
  2. package/out/preload/index.js +234 -18
  3. package/out/renderer/assets/{_basePickBy-C-GwcPo5.js → _basePickBy-AOIZJtmP.js} +1 -1
  4. package/out/renderer/assets/{_baseUniq-whkmDW6w.js → _baseUniq-6rYoQPQm.js} +1 -1
  5. package/out/renderer/assets/{agent-details-page-DpCpkTfN.js → agent-details-page-B9MNIwUW.js} +2 -2
  6. package/out/renderer/assets/agent-hub-page-Bj3VsEfD.js +11 -0
  7. package/out/renderer/assets/agents-page-y_fPy8cB.js +7 -0
  8. package/out/renderer/assets/arc-DEIVtaqP.js +1 -0
  9. package/out/renderer/assets/architectureDiagram-IEHRJDOE-C6lgf3ft.js +36 -0
  10. package/out/renderer/assets/{blockDiagram-JOT3LUYC-Dp4G8uLX.js → blockDiagram-JOT3LUYC-rUXOYgBS.js} +6 -6
  11. package/out/renderer/assets/{book-open-C9onOH1C.js → book-open-BWUyDXVa.js} +1 -1
  12. package/out/renderer/assets/browser-page-BENwHsVf.js +1 -0
  13. package/out/renderer/assets/browser-webauthn-prompt-BlawzvV4.js +1 -0
  14. package/out/renderer/assets/{c4Diagram-VJAJSXHY-CnX5ELGv.js → c4Diagram-VJAJSXHY-B5DZCCX3.js} +1 -1
  15. package/out/renderer/assets/channel-vM9rusk9.js +1 -0
  16. package/out/renderer/assets/{chunk-4BMEZGHF-DYgqjjvg.js → chunk-4BMEZGHF-LYO-H6gm.js} +1 -1
  17. package/out/renderer/assets/{chunk-A2AXSNBT-3DoU7E6Y.js → chunk-A2AXSNBT-C-eXpCGB.js} +1 -1
  18. package/out/renderer/assets/{chunk-AEK57VVT-OX8UcnIV.js → chunk-AEK57VVT-CkjgDW9B.js} +1 -1
  19. package/out/renderer/assets/{chunk-D6G4REZN-Dbt1r37J.js → chunk-D6G4REZN-hAERljt9.js} +1 -1
  20. package/out/renderer/assets/{chunk-RZ5BOZE2-CdLKa5S8.js → chunk-RZ5BOZE2-D1aP9fPZ.js} +1 -1
  21. package/out/renderer/assets/{chunk-XZIHB7SX-B_CjCOio.js → chunk-XZIHB7SX-Chv37Bqx.js} +1 -1
  22. package/out/renderer/assets/classDiagram-GIVACNV2-BCqIuRq4.js +1 -0
  23. package/out/renderer/assets/classDiagram-v2-COTLJTTW-BCqIuRq4.js +1 -0
  24. package/out/renderer/assets/clone-Bj8HwBLn.js +1 -0
  25. package/out/renderer/assets/{console-mirror-D1YYJxrt.js → console-mirror-DF_IQ1r-.js} +14 -14
  26. package/out/renderer/assets/consoleCapture-CAbvxCQS.js +1 -0
  27. package/out/renderer/assets/{conversation-input-sync-BbbcfWqh.js → conversation-input-sync-BdhFw-JY.js} +259 -261
  28. package/out/renderer/assets/dagre-OKDRZEBW-ClDfyejg.js +4 -0
  29. package/out/renderer/assets/diagram-SSKATNLV-BECmfn9C.js +43 -0
  30. package/out/renderer/assets/diagram-VNBRO52H-82BMsx_Z.js +24 -0
  31. package/out/renderer/assets/{erDiagram-Q7BY3M3F-Cs4yDA7g.js → erDiagram-Q7BY3M3F-BiCriqsZ.js} +1 -1
  32. package/out/renderer/assets/{error-boundary-YxH9ow-2.js → error-boundary-B4K6A3dS.js} +2 -2
  33. package/out/renderer/assets/{flowDiagram-4HSFHLVR-BofJ9_8f.js → flowDiagram-4HSFHLVR-D4PQMO6X.js} +1 -1
  34. package/out/renderer/assets/ganttDiagram-APWFNJXF-Rts_QpVD.js +257 -0
  35. package/out/renderer/assets/gitGraphDiagram-7IBYFJ6S-B1rxvfyh.js +65 -0
  36. package/out/renderer/assets/{graph-D3WT1lJS.js → graph-DxQCKx-L.js} +1 -1
  37. package/out/renderer/assets/index-BJ-AJLyd.js +804 -0
  38. package/out/renderer/assets/index-BhsV-ipc.js +1 -0
  39. package/out/renderer/assets/index-C7mMgpwT.css +1 -0
  40. package/out/renderer/assets/index-XFXAg7sM.js +7 -0
  41. package/out/renderer/assets/infoDiagram-PH2N3AL5-CDvWuX2O.js +2 -0
  42. package/out/renderer/assets/{installer-BzSW3GXd.js → installer-B4Bd2pBF.js} +1 -1
  43. package/out/renderer/assets/{journeyDiagram-U35MCT3I-DVmD515J.js → journeyDiagram-U35MCT3I-CnefKX-i.js} +1 -1
  44. package/out/renderer/assets/{kanban-definition-NDS4AKOZ-DpiJ7P3i.js → kanban-definition-NDS4AKOZ-Kd7aNO4_.js} +1 -1
  45. package/out/renderer/assets/{layout-BFA2wwh_.js → layout-CMQ0zdhs.js} +1 -1
  46. package/out/renderer/assets/legacy-agents-page-PBvD5xd9.js +33 -0
  47. package/out/renderer/assets/{mermaid.core-C7iGmYcY.js → mermaid.core-x5Fva3t5.js} +5 -5
  48. package/out/renderer/assets/mesh-page-IOGN512j.js +1 -0
  49. package/out/renderer/assets/messages-D3mGACg6.js +3 -0
  50. package/out/renderer/assets/mindmap-definition-ALO5MXBD-DJwQ3wYh.js +95 -0
  51. package/out/renderer/assets/mini-DHQCkYmb.js +1 -0
  52. package/out/renderer/assets/{notebook-pen-rCh1XFso.js → notebook-pen-CKFoYavJ.js} +1 -1
  53. package/out/renderer/assets/org-access-_jPzS_wW.js +1 -0
  54. package/out/renderer/assets/org-surface-gate-D29XdDTt.js +1 -0
  55. package/out/renderer/assets/{page-header-DWR3DkBt.js → page-header-D90TvAM5.js} +1 -1
  56. package/out/renderer/assets/pieDiagram-IB7DONF6-CW-ezwOn.js +30 -0
  57. package/out/renderer/assets/projects-page-DGLblpIY.js +45 -0
  58. package/out/renderer/assets/publication-validation-ZquLmMLD.js +1 -0
  59. package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-u3qKyomE.js → quadrantDiagram-7GDLP6J5-cgwnEsXh.js} +1 -1
  60. package/out/renderer/assets/{radar-MK3ICKWK-DTXBXOd-.js → radar-MK3ICKWK-DvOkOeQ6.js} +1 -1
  61. package/out/renderer/assets/radient-auth-buttons-y3JK-yD8.js +1 -0
  62. package/out/renderer/assets/{requirementDiagram-KVF5MWMF-CIyOJzHO.js → requirementDiagram-KVF5MWMF-DOMWjtoO.js} +1 -1
  63. package/out/renderer/assets/sankeyDiagram-QLVOVGJD-CE3E7VO5.js +10 -0
  64. package/out/renderer/assets/{schedules-page-DHwzeRvF.js → schedules-page-ETT8R0wn.js} +1 -1
  65. package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-DCj3Ke6W.js → sequenceDiagram-X6HHIX6F-C4EGcl7U.js} +1 -1
  66. package/out/renderer/assets/{settings-page-Br0K0YLW.js → settings-page-BZDF6UIV.js} +1 -1
  67. package/out/renderer/assets/stateDiagram-DGXRK772-wBbKaUQh.js +1 -0
  68. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-Bh0k2Dz3.js +1 -0
  69. package/out/renderer/assets/{timeline-definition-BDJGKUSR-C4cCAtD2.js → timeline-definition-BDJGKUSR-CgTCOxCY.js} +1 -1
  70. package/out/renderer/assets/{use-download-agent-mutation-CRmN4Z4O.js → use-download-agent-mutation-DkI3E5yA.js} +4 -4
  71. package/out/renderer/assets/use-memberships-query-Hv-eprVl.js +1 -0
  72. package/out/renderer/assets/{use-session-snapshot-DZAAvDEa.js → use-session-snapshot-CfEF5TPK.js} +1 -1
  73. package/out/renderer/assets/{xychartDiagram-VJFVF3MP-DG9g8Bz_.js → xychartDiagram-VJFVF3MP-G2SxM5sQ.js} +1 -1
  74. package/out/renderer/console-capture.html +5 -5
  75. package/out/renderer/index.html +7 -7
  76. package/out/renderer/installer.html +4 -4
  77. package/out/renderer/mini.html +7 -7
  78. package/package.json +2 -2
  79. package/out/renderer/assets/agent-hub-page-CUIy_Kx2.js +0 -11
  80. package/out/renderer/assets/agents-page-2HbuAEbx.js +0 -7
  81. package/out/renderer/assets/arc-DDs-mKBJ.js +0 -1
  82. package/out/renderer/assets/architectureDiagram-IEHRJDOE-CPUUDGvU.js +0 -36
  83. package/out/renderer/assets/browser-page-DYIlDoNt.js +0 -1
  84. package/out/renderer/assets/browser-webauthn-prompt-BCVHqXb0.js +0 -1
  85. package/out/renderer/assets/channel-DMF6TPjP.js +0 -1
  86. package/out/renderer/assets/classDiagram-GIVACNV2-B0EbOHV0.js +0 -1
  87. package/out/renderer/assets/classDiagram-v2-COTLJTTW-B0EbOHV0.js +0 -1
  88. package/out/renderer/assets/clone-D1pjaXDr.js +0 -1
  89. package/out/renderer/assets/consoleCapture-B-w56zDU.js +0 -1
  90. package/out/renderer/assets/dagre-OKDRZEBW-Di-XleTu.js +0 -4
  91. package/out/renderer/assets/diagram-SSKATNLV-HX5Tirdq.js +0 -43
  92. package/out/renderer/assets/diagram-VNBRO52H-BkGCdAFk.js +0 -24
  93. package/out/renderer/assets/ganttDiagram-APWFNJXF-B8-HoTDC.js +0 -257
  94. package/out/renderer/assets/gitGraphDiagram-7IBYFJ6S-B72gxYgp.js +0 -65
  95. package/out/renderer/assets/index-B7VSZn2a.css +0 -1
  96. package/out/renderer/assets/index-Bo-U0L08.js +0 -804
  97. package/out/renderer/assets/index-BwwNZ8BB.js +0 -1
  98. package/out/renderer/assets/index-StPsU3lP.js +0 -7
  99. package/out/renderer/assets/infoDiagram-PH2N3AL5-ChbJv-y5.js +0 -2
  100. package/out/renderer/assets/legacy-agents-page-C_w_SC0A.js +0 -33
  101. package/out/renderer/assets/mesh-page-DMSyd0cL.js +0 -1
  102. package/out/renderer/assets/mindmap-definition-ALO5MXBD-FgYmOkcR.js +0 -95
  103. package/out/renderer/assets/mini-B9yZoGaU.js +0 -1
  104. package/out/renderer/assets/org-surface-gate-CDIyXoCz.js +0 -1
  105. package/out/renderer/assets/pieDiagram-IB7DONF6-Ahxca-lE.js +0 -30
  106. package/out/renderer/assets/projects-page-BclfcCc4.js +0 -50
  107. package/out/renderer/assets/radient-auth-buttons-D6EcW_pf.js +0 -1
  108. package/out/renderer/assets/sankeyDiagram-QLVOVGJD-DPL4XBFG.js +0 -10
  109. package/out/renderer/assets/stateDiagram-DGXRK772-C_ILyKXn.js +0 -1
  110. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-DgnGm7tu.js +0 -1
  111. package/out/renderer/assets/ui-preferences-store-lljzccwU.js +0 -1
  112. package/out/renderer/assets/use-memberships-query-DIOIcGCQ.js +0 -1
  113. /package/out/renderer/assets/{index-CzjIMcYW.js → index-B-uibf5k.js} +0 -0
package/out/main/index.js CHANGED
@@ -92,6 +92,7 @@ const mcpCatalogCwdPattern = /^(\/|[A-Za-z]:[\\/])/;
92
92
  const mcpCatalogCwd = zod.z.string().min(1).max(4096).regex(mcpCatalogCwdPattern);
93
93
  const SUBSCRIPTION_ID_PATTERN = /^[a-f0-9]{32}$/;
94
94
  const requestId = zod.z.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
95
+ const definitionRevision = zod.z.string().min(1).max(512).nullish();
95
96
  const meshId = zod.z.string().regex(/^[A-Za-z0-9_-]{1,128}$/);
96
97
  const approvalId = zod.z.string().regex(/^ap_[0-9a-hjkmnp-tv-z]{1,64}$/);
97
98
  const sessionImage = zod.z.object({
@@ -262,6 +263,12 @@ const publicationDescription = zod.z.string().refine((description) => publicatio
262
263
  const publicationTools = zod.z.array(zod.z.string()).refine((tools) => publicationToolsRule(tools) === null, {
263
264
  message: "That tool list cannot be published."
264
265
  });
266
+ const publicationPolicyFields = {
267
+ requestId: requestId.optional(),
268
+ subscribe: zod.z.boolean().optional(),
269
+ autoPush: zod.z.boolean().optional(),
270
+ confirmPublicEgress: zod.z.boolean().optional()
271
+ };
265
272
  const publicationDocument = zod.z.object({
266
273
  name: publicationName,
267
274
  description: publicationDescription,
@@ -325,6 +332,21 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
325
332
  zod.z.object({ op: zod.z.literal("runtimes.list") }).strict(),
326
333
  zod.z.object({ op: zod.z.literal("profiles.list") }).strict(),
327
334
  zod.z.object({ op: zod.z.literal("profiles.get"), name: profileName }).strict(),
335
+ /*
336
+ * Definition CAS (proposed additive wire, b-wire-receipt-definition-cas-and-adopt).
337
+ * `expectedDefinitionRevision` is an opaque echo of the `definition_revision` the
338
+ * editor was opened with; absent/null is the LEGACY unconditional write and is
339
+ * NOT a protected dirty-edit guarantee, so a caller that wants CAS protection
340
+ * must send it. Strict like every field here: a typo must 400, not silently
341
+ * become an unconditional write.
342
+ */
343
+ zod.z.object({
344
+ op: zod.z.literal("profiles.update"),
345
+ name: profileName,
346
+ requestId,
347
+ fields: profileFields,
348
+ expectedDefinitionRevision: definitionRevision.optional()
349
+ }).strict(),
328
350
  zod.z.object({ op: zod.z.literal("profiles.install"), name: profileName, requestId }).strict(),
329
351
  zod.z.object({
330
352
  op: zod.z.literal("profiles.create"),
@@ -335,12 +357,6 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
335
357
  instructions: zod.z.string().min(1).max(8e3)
336
358
  })
337
359
  }).strict(),
338
- zod.z.object({
339
- op: zod.z.literal("profiles.update"),
340
- name: profileName,
341
- requestId,
342
- fields: profileFields
343
- }).strict(),
344
360
  /*
345
361
  * THE HUB AUTO-UPDATE PLANE (backend `server/routes/desktop_hub.py`, gated by
346
362
  * the `hub_updates` capability). `hub.updates` is a STORE READ on the backend
@@ -356,20 +372,83 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
356
372
  * apply-all schema does not offer it.
357
373
  */
358
374
  zod.z.object({ op: zod.z.literal("hub.updates") }).strict(),
375
+ // Initial skill publication uses an actual discovered source and managed
376
+ // alias, never a synthetic link ID or a private-directory clone.
377
+ zod.z.object({
378
+ op: zod.z.literal("hub.skillPull"),
379
+ requestId,
380
+ hubId: id$1,
381
+ hubSource: zod.z.string().min(1).max(2048),
382
+ visibility: zod.z.enum(["public", "org"]),
383
+ tenantId: id$1.nullable(),
384
+ localAlias: profileName,
385
+ subscribe: zod.z.boolean().optional(),
386
+ autoPush: zod.z.boolean().optional(),
387
+ confirmPublicEgress: zod.z.boolean().optional()
388
+ }).strict(),
389
+ zod.z.object({
390
+ op: zod.z.literal("hub.skillPublish"),
391
+ requestId,
392
+ dryRun: zod.z.boolean(),
393
+ sourceSkill: profileName,
394
+ localAlias: profileName,
395
+ visibility: zod.z.enum(["public", "org"]),
396
+ tenantId: id$1.nullable(),
397
+ subscribe: zod.z.boolean(),
398
+ autoPush: zod.z.boolean(),
399
+ confirmPublicEgress: zod.z.boolean(),
400
+ previewReceipt: zod.z.string().min(1).max(4096).optional(),
401
+ confirmPublish: zod.z.literal(true).optional()
402
+ }).strict(),
403
+ // Full inventory is additive: old callers still receive pending agents/teams
404
+ // only. Policy revision is required even for a single independent toggle so
405
+ // an intervening direct config edit cannot be silently overwritten.
406
+ zod.z.object({
407
+ op: zod.z.literal("hub.links"),
408
+ kind: zod.z.enum(["agent", "team", "skill"]).optional()
409
+ }).strict(),
410
+ zod.z.object({
411
+ op: zod.z.literal("hub.policy"),
412
+ requestId,
413
+ linkId: zod.z.string().min(1).max(256),
414
+ expectedPolicyRevision: zod.z.string().min(1).max(256),
415
+ subscribe: zod.z.boolean().optional(),
416
+ autoPush: zod.z.boolean().optional(),
417
+ confirmPublicEgress: zod.z.boolean().optional()
418
+ }).strict(),
359
419
  zod.z.object({
360
420
  op: zod.z.literal("hub.check"),
361
421
  requestId,
422
+ linkId: zod.z.string().min(1).max(256).optional(),
423
+ direction: zod.z.enum(["pull", "push"]).optional(),
362
424
  kind: hubItemKind.optional(),
363
425
  name: profileName.optional()
364
426
  }).strict(),
365
427
  zod.z.object({
366
428
  op: zod.z.literal("hub.apply"),
367
429
  requestId,
368
- kind: hubItemKind,
369
- name: profileName,
430
+ previewReceipt: zod.z.string().min(1).max(4096).optional(),
431
+ confirmApply: zod.z.literal(true).optional(),
432
+ linkId: zod.z.string().min(1).max(256).optional(),
433
+ direction: zod.z.literal("pull").optional(),
434
+ kind: hubItemKind.optional(),
435
+ name: profileName.optional(),
370
436
  prefer: zod.z.enum(["local", "remote"]).optional(),
371
437
  acknowledgeUnknownBaseline: zod.z.boolean().optional(),
372
- dryRun: zod.z.boolean().optional()
438
+ dryRun: zod.z.boolean().optional(),
439
+ /*
440
+ * LEGACY UNKNOWN-ORIGIN SOURCE CONFIRMATION (proposed additive wire,
441
+ * b-wire-receipt §Part 2). An EXPLICIT act that states a destination the
442
+ * caller asserts: it materializes the real baseline identity and scope,
443
+ * it never infers Public or the active organization. `resolve_source`
444
+ * selects the branch; the source/scope triple is REQUIRED with it so a
445
+ * half-stated destination cannot compose a guessed origin. The service
446
+ * refuses a conflicting known scope with `origin-conflict`.
447
+ */
448
+ resolveSource: zod.z.literal(true).optional(),
449
+ hubSource: zod.z.string().min(1).max(2048).optional(),
450
+ visibility: zod.z.enum(["public", "org"]).optional(),
451
+ tenantId: zod.z.string().min(1).max(128).nullable().optional()
373
452
  }).strict(),
374
453
  zod.z.object({
375
454
  op: zod.z.literal("hub.applyAll"),
@@ -379,21 +458,38 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
379
458
  zod.z.object({
380
459
  op: zod.z.literal("hub.retry"),
381
460
  requestId,
382
- kind: hubItemKind,
383
- name: profileName
461
+ linkId: zod.z.string().min(1).max(256).optional(),
462
+ direction: zod.z.enum(["pull", "push"]).optional(),
463
+ kind: hubItemKind.optional(),
464
+ name: profileName.optional()
384
465
  }).strict(),
385
- zod.z.object({ op: zod.z.literal("teams.list") }).strict(),
386
- zod.z.object({ op: zod.z.literal("teams.get"), name: profileName }).strict(),
387
466
  zod.z.object({
388
- op: zod.z.literal("teams.create"),
467
+ op: zod.z.literal("hub.push"),
389
468
  requestId,
390
- fields: teamFields.extend({ name: zod.z.string().min(1).max(64) })
469
+ linkId: zod.z.string().min(1).max(256),
470
+ direction: zod.z.literal("push"),
471
+ dryRun: zod.z.boolean(),
472
+ previewReceipt: zod.z.string().min(1).max(4096).optional(),
473
+ confirmPublish: zod.z.literal(true).optional()
391
474
  }).strict(),
475
+ zod.z.object({ op: zod.z.literal("teams.list") }).strict(),
476
+ zod.z.object({ op: zod.z.literal("teams.get"), name: profileName }).strict(),
477
+ /*
478
+ * Team CAS is the same optional opaque echo as the profile arm: absent/null is
479
+ * the legacy unconditional write, a supplied stale/malformed value is refused
480
+ * 409 `local-concurrent-edit` before any write under the definition lock.
481
+ */
392
482
  zod.z.object({
393
483
  op: zod.z.literal("teams.update"),
394
484
  name: profileName,
395
485
  requestId,
396
- fields: teamFields
486
+ fields: teamFields,
487
+ expectedDefinitionRevision: definitionRevision.optional()
488
+ }).strict(),
489
+ zod.z.object({
490
+ op: zod.z.literal("teams.create"),
491
+ requestId,
492
+ fields: teamFields.extend({ name: zod.z.string().min(1).max(64) })
397
493
  }).strict(),
398
494
  zod.z.object({
399
495
  op: zod.z.literal("sessions.list"),
@@ -702,6 +798,24 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
702
798
  */
703
799
  peerJournalSource: zod.z.boolean().optional()
704
800
  }).strict(),
801
+ // The inventory belongs to the backend, not to a renderer filesystem walk.
802
+ // Keep the cursor opaque: its query/revision binding is the service's decision.
803
+ zod.z.object({
804
+ op: zod.z.literal("sessions.files"),
805
+ sessionId,
806
+ scope: zod.z.enum(["family", "self"]).optional(),
807
+ kind: zod.z.string().max(256).optional(),
808
+ q: zod.z.string().max(256).optional(),
809
+ sort: zod.z.enum(["recent", "name", "kind", "modified", "created", "size"]).optional(),
810
+ order: zod.z.enum(["asc", "desc"]).optional(),
811
+ limit: zod.z.number().int().min(1).max(200).optional(),
812
+ cursor: zod.z.string().max(8192).optional()
813
+ }).strict(),
814
+ zod.z.object({
815
+ op: zod.z.literal("sessions.files.materialise"),
816
+ sessionId,
817
+ fileId: zod.z.string().regex(/^[a-f0-9]{16}$/)
818
+ }).strict(),
705
819
  zod.z.object({
706
820
  op: zod.z.literal("sessions.history"),
707
821
  sessionId,
@@ -1204,6 +1318,7 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
1204
1318
  op: zod.z.literal("agent.publish"),
1205
1319
  agentId: id$1,
1206
1320
  document: publicationDocument.optional(),
1321
+ ...publicationPolicyFields,
1207
1322
  /*
1208
1323
  * The publication TARGET (§4.4/§4.7). Both travel together or neither does,
1209
1324
  * and the rule is a `superRefine` on the whole UNION (below) rather than a
@@ -1235,11 +1350,20 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
1235
1350
  // republish that did not name one would have to guess which public row to
1236
1351
  // overwrite — and guessing here overwrites somebody's listing.
1237
1352
  hubAgentId: id$1,
1353
+ expectedRemoteRevision: zod.z.string().min(1).max(512).optional(),
1238
1354
  document: publicationDocument.optional(),
1355
+ ...publicationPolicyFields,
1239
1356
  /** The publication target, on the same terms as `agent.publish` above. */
1240
1357
  visibility: zod.z.literal("org").optional(),
1241
1358
  tenantId: id$1.optional()
1242
1359
  }).strict(),
1360
+ zod.z.object({
1361
+ op: zod.z.literal("team.publish"),
1362
+ teamId: id$1,
1363
+ ...publicationPolicyFields,
1364
+ visibility: zod.z.literal("org").optional(),
1365
+ tenantId: id$1.optional()
1366
+ }).strict(),
1243
1367
  /*
1244
1368
  * Pulling a published organization team into this machine's local registry
1245
1369
  * (§4.5/§8.4's "list + pull action").
@@ -1837,6 +1961,8 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
1837
1961
  "org_agents.list",
1838
1962
  "org_team.get",
1839
1963
  "org_teams.list",
1964
+ "org_skills.list",
1965
+ "org_skill.get",
1840
1966
  /*
1841
1967
  * The verification-email resend (core PR2,
1842
1968
  * `feat/quota-notice-resend`): it maps to the upstream's JWT-only
@@ -1854,6 +1980,7 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
1854
1980
  agent_id: id$1.optional(),
1855
1981
  comment_id: id$1.optional(),
1856
1982
  team_id: id$1.optional(),
1983
+ skill_id: id$1.optional(),
1857
1984
  query: zod.z.record(zod.z.union([zod.z.string().max(1024), zod.z.number().int()])).optional(),
1858
1985
  payload: zod.z.record(zod.z.unknown()).optional(),
1859
1986
  confirmed: zod.z.boolean().optional()
@@ -2239,6 +2366,71 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
2239
2366
  ]);
2240
2367
  const desktopRequestSchema = desktopRequestUnion.superRefine(
2241
2368
  (request, ctx) => {
2369
+ if (request.op === "hub.skillPull" || request.op === "hub.skillPublish") {
2370
+ if (request.visibility === "public" && request.tenantId !== null || request.visibility === "org" && !request.tenantId) {
2371
+ ctx.addIssue({
2372
+ code: zod.z.ZodIssueCode.custom,
2373
+ message: "A skill destination requires explicit paired scope",
2374
+ path: ["tenantId"]
2375
+ });
2376
+ return;
2377
+ }
2378
+ if (request.op === "hub.skillPublish" && (request.dryRun ? request.previewReceipt !== void 0 || request.confirmPublish !== void 0 : !request.previewReceipt || request.confirmPublish !== true)) {
2379
+ ctx.addIssue({
2380
+ code: zod.z.ZodIssueCode.custom,
2381
+ message: "An initial skill publication requires its reviewed draft receipt",
2382
+ path: ["previewReceipt"]
2383
+ });
2384
+ return;
2385
+ }
2386
+ }
2387
+ if (request.op === "hub.check" || request.op === "hub.apply" || request.op === "hub.retry") {
2388
+ const hasLink = request.linkId !== void 0;
2389
+ const hasLegacy = request.kind !== void 0 || request.name !== void 0;
2390
+ if (hasLink && hasLegacy || request.op !== "hub.check" && !hasLink && (!request.kind || !request.name) || request.op === "hub.retry" && hasLink && !request.direction || request.direction === "push" && !hasLink) {
2391
+ ctx.addIssue({
2392
+ code: zod.z.ZodIssueCode.custom,
2393
+ message: "A hub action requires one unambiguous selector and an explicit retry direction",
2394
+ path: ["linkId"]
2395
+ });
2396
+ return;
2397
+ }
2398
+ }
2399
+ if (request.op === "hub.apply" && (request.previewReceipt !== void 0 || request.confirmApply !== void 0) && (!request.linkId || request.dryRun || !request.previewReceipt || request.confirmApply !== true)) {
2400
+ ctx.addIssue({
2401
+ code: zod.z.ZodIssueCode.custom,
2402
+ message: "A viewed incoming application requires its scoped preview receipt and confirmation",
2403
+ path: ["previewReceipt"]
2404
+ });
2405
+ return;
2406
+ }
2407
+ if (request.op === "hub.apply" && request.resolveSource === true) {
2408
+ const scopePaired = request.visibility === "public" && request.tenantId === null || request.visibility === "org" && typeof request.tenantId === "string" && request.tenantId.length > 0;
2409
+ if (!request.hubSource || !scopePaired || request.acknowledgeUnknownBaseline !== true || request.linkId !== void 0 || request.previewReceipt !== void 0 || request.confirmApply !== void 0 || request.prefer !== void 0) {
2410
+ ctx.addIssue({
2411
+ code: zod.z.ZodIssueCode.custom,
2412
+ message: "Confirming a source requires its exact stated destination and the unknown-baseline acknowledgement, with no link, preview or preference",
2413
+ path: ["hubSource"]
2414
+ });
2415
+ return;
2416
+ }
2417
+ }
2418
+ if (request.op === "hub.push" && (request.dryRun ? request.previewReceipt !== void 0 || request.confirmPublish !== void 0 : !request.previewReceipt || request.confirmPublish !== true)) {
2419
+ ctx.addIssue({
2420
+ code: zod.z.ZodIssueCode.custom,
2421
+ message: "A confirmed publication requires its reviewed preview receipt",
2422
+ path: ["previewReceipt"]
2423
+ });
2424
+ return;
2425
+ }
2426
+ if (request.op === "hub.policy" && request.subscribe === void 0 && request.autoPush === void 0) {
2427
+ ctx.addIssue({
2428
+ code: zod.z.ZodIssueCode.custom,
2429
+ message: "A hub policy change must name subscribe or autoPush",
2430
+ path: ["subscribe"]
2431
+ });
2432
+ return;
2433
+ }
2242
2434
  if (request.op === "sessions.answer") {
2243
2435
  if (request.askId !== void 0) {
2244
2436
  if (!request.askId)
@@ -2314,7 +2506,15 @@ const desktopRequestSchema = desktopRequestUnion.superRefine(
2314
2506
  }
2315
2507
  return;
2316
2508
  }
2317
- if (request.op !== "agent.publish" && request.op !== "agent.republish") {
2509
+ if (request.op !== "agent.publish" && request.op !== "agent.republish" && request.op !== "team.publish") {
2510
+ return;
2511
+ }
2512
+ if ((request.subscribe !== void 0 || request.autoPush !== void 0 || request.confirmPublicEgress !== void 0) && !request.requestId) {
2513
+ ctx.addIssue({
2514
+ code: zod.z.ZodIssueCode.custom,
2515
+ message: "Initial link policy needs a durable publication request ID",
2516
+ path: ["requestId"]
2517
+ });
2318
2518
  return;
2319
2519
  }
2320
2520
  if (Boolean(request.visibility === "org") !== Boolean(request.tenantId)) {
@@ -2436,6 +2636,9 @@ const HUB_APPLY_ALL_DEADLINE_MS = 5 * HUB_ITEM_WRITE_DEADLINE_MS;
2436
2636
  const HUB_WRITE_OPS = /* @__PURE__ */ new Set([
2437
2637
  "hub.apply",
2438
2638
  "hub.retry",
2639
+ "hub.push",
2640
+ "hub.skillPublish",
2641
+ "hub.skillPull",
2439
2642
  "hub.check",
2440
2643
  "hub.applyAll"
2441
2644
  ]);
@@ -2443,6 +2646,9 @@ function hubWriteDeadlineMs(op) {
2443
2646
  switch (op) {
2444
2647
  case "hub.apply":
2445
2648
  case "hub.retry":
2649
+ case "hub.push":
2650
+ case "hub.skillPublish":
2651
+ case "hub.skillPull":
2446
2652
  return HUB_ITEM_WRITE_DEADLINE_MS;
2447
2653
  case "hub.check":
2448
2654
  return HUB_CHECK_DEADLINE_MS;
@@ -2475,6 +2681,7 @@ const READ_ONLY_OPS = /* @__PURE__ */ new Set([
2475
2681
  "config.get",
2476
2682
  "credentials.list",
2477
2683
  "hub.updates",
2684
+ "hub.links",
2478
2685
  "info.get",
2479
2686
  "instructions.get",
2480
2687
  "legacy.agent.get",
@@ -2511,6 +2718,7 @@ const READ_ONLY_OPS = /* @__PURE__ */ new Set([
2511
2718
  "sessions.find",
2512
2719
  "sessions.get",
2513
2720
  "sessions.history",
2721
+ "sessions.files",
2514
2722
  "sessions.list",
2515
2723
  "sessions.preview",
2516
2724
  "sessions.report",
@@ -2617,6 +2825,14 @@ function wakeTiming(request) {
2617
2825
  ...request.limit !== void 0 ? { limit: request.limit } : {}
2618
2826
  };
2619
2827
  }
2828
+ function publicationPolicyBody(request) {
2829
+ return {
2830
+ ...request.requestId ? { request_id: request.requestId } : {},
2831
+ ...request.subscribe !== void 0 ? { subscribe: request.subscribe } : {},
2832
+ ...request.autoPush !== void 0 ? { auto_push: request.autoPush } : {},
2833
+ ...request.confirmPublicEgress !== void 0 ? { confirm_public_egress: request.confirmPublicEgress } : {}
2834
+ };
2835
+ }
2620
2836
  function assertPairedPublicationTarget(request) {
2621
2837
  if (Boolean(request.visibility === "org") !== Boolean(request.tenantId)) {
2622
2838
  throw new Error(
@@ -2733,16 +2949,81 @@ function desktopEndpoint(request) {
2733
2949
  return {
2734
2950
  path: `/v1/desktop/profiles/${encodeURIComponent(request.name)}`,
2735
2951
  method: "PATCH",
2736
- body: { request_id: request.requestId, ...request.fields }
2952
+ body: {
2953
+ request_id: request.requestId,
2954
+ ...request.fields,
2955
+ /*
2956
+ * The opaque echo travels ALONGSIDE the fields, never inside them: the
2957
+ * edit body is `exclude_unset`-merged on the backend, so a revision
2958
+ * nested in `fields` could be mistaken for an editable property. Sent
2959
+ * only when present, so absence stays the legacy unconditional write.
2960
+ */
2961
+ ...request.expectedDefinitionRevision !== void 0 && request.expectedDefinitionRevision !== null ? {
2962
+ expected_definition_revision: request.expectedDefinitionRevision
2963
+ } : {}
2964
+ }
2737
2965
  };
2738
2966
  case "hub.updates":
2739
2967
  return { path: "/v1/desktop/hub/updates", method: "GET" };
2968
+ case "hub.skillPull":
2969
+ return {
2970
+ path: "/v1/desktop/hub/skills/pull",
2971
+ method: "POST",
2972
+ body: {
2973
+ request_id: request.requestId,
2974
+ hub_id: request.hubId,
2975
+ hub_source: request.hubSource,
2976
+ visibility: request.visibility,
2977
+ tenant_id: request.tenantId,
2978
+ local_alias: request.localAlias,
2979
+ ...request.subscribe !== void 0 ? { subscribe: request.subscribe } : {},
2980
+ ...request.autoPush !== void 0 ? { auto_push: request.autoPush } : {},
2981
+ ...request.confirmPublicEgress !== void 0 ? { confirm_public_egress: request.confirmPublicEgress } : {}
2982
+ }
2983
+ };
2984
+ case "hub.skillPublish":
2985
+ return {
2986
+ path: "/v1/desktop/hub/skills/publish",
2987
+ method: "POST",
2988
+ body: {
2989
+ request_id: request.requestId,
2990
+ dry_run: request.dryRun,
2991
+ source_skill: request.sourceSkill,
2992
+ local_alias: request.localAlias,
2993
+ visibility: request.visibility,
2994
+ tenant_id: request.tenantId,
2995
+ subscribe: request.subscribe,
2996
+ auto_push: request.autoPush,
2997
+ confirm_public_egress: request.confirmPublicEgress,
2998
+ ...request.previewReceipt ? { preview_receipt: request.previewReceipt } : {},
2999
+ ...request.confirmPublish ? { confirm_publish: true } : {}
3000
+ }
3001
+ };
3002
+ case "hub.links":
3003
+ return {
3004
+ path: `/v1/desktop/hub/links${request.kind ? `?kind=${request.kind}` : ""}`,
3005
+ method: "GET"
3006
+ };
3007
+ case "hub.policy":
3008
+ return {
3009
+ path: `/v1/desktop/hub/links/${encodeURIComponent(request.linkId)}`,
3010
+ method: "PATCH",
3011
+ body: {
3012
+ request_id: request.requestId,
3013
+ expected_policy_revision: request.expectedPolicyRevision,
3014
+ ...request.subscribe !== void 0 ? { subscribe: request.subscribe } : {},
3015
+ ...request.autoPush !== void 0 ? { auto_push: request.autoPush } : {},
3016
+ ...request.confirmPublicEgress !== void 0 ? { confirm_public_egress: request.confirmPublicEgress } : {}
3017
+ }
3018
+ };
2740
3019
  case "hub.check":
2741
3020
  return {
2742
3021
  path: "/v1/desktop/hub/updates/check",
2743
3022
  method: "POST",
2744
3023
  body: {
2745
3024
  request_id: request.requestId,
3025
+ ...request.linkId ? { link_id: request.linkId } : {},
3026
+ ...request.direction ? { direction: request.direction } : {},
2746
3027
  ...request.kind ? { kind: request.kind } : {},
2747
3028
  ...request.name ? { name: request.name } : {}
2748
3029
  }
@@ -2753,13 +3034,30 @@ function desktopEndpoint(request) {
2753
3034
  method: "POST",
2754
3035
  body: {
2755
3036
  request_id: request.requestId,
2756
- kind: request.kind,
2757
- name: request.name,
3037
+ ...request.previewReceipt ? { preview_receipt: request.previewReceipt } : {},
3038
+ ...request.confirmApply ? { confirm_apply: true } : {},
3039
+ ...request.linkId ? { link_id: request.linkId } : {},
3040
+ ...request.direction ? { direction: request.direction } : {},
3041
+ ...request.kind ? { kind: request.kind } : {},
3042
+ ...request.name ? { name: request.name } : {},
2758
3043
  ...request.prefer ? { prefer: request.prefer } : {},
2759
3044
  ...request.acknowledgeUnknownBaseline !== void 0 ? {
2760
3045
  acknowledge_unknown_baseline: request.acknowledgeUnknownBaseline
2761
3046
  } : {},
2762
- ...request.dryRun !== void 0 ? { dry_run: request.dryRun } : {}
3047
+ ...request.dryRun !== void 0 ? { dry_run: request.dryRun } : {},
3048
+ /*
3049
+ * The source-confirmation branch travels on the SAME apply route with an
3050
+ * explicit flag, never a new endpoint. `resolve_source:true` plus the
3051
+ * stated scope triple is the whole act; the service materializes the
3052
+ * real identity/link and preserves any known scope (refusing a conflict
3053
+ * with `origin-conflict`), so nothing here infers Public or an org.
3054
+ */
3055
+ ...request.resolveSource === true ? {
3056
+ resolve_source: true,
3057
+ hub_source: request.hubSource,
3058
+ visibility: request.visibility,
3059
+ tenant_id: request.tenantId ?? null
3060
+ } : {}
2763
3061
  }
2764
3062
  };
2765
3063
  case "hub.applyAll":
@@ -2777,8 +3075,23 @@ function desktopEndpoint(request) {
2777
3075
  method: "POST",
2778
3076
  body: {
2779
3077
  request_id: request.requestId,
2780
- kind: request.kind,
2781
- name: request.name
3078
+ ...request.linkId ? { link_id: request.linkId } : {},
3079
+ ...request.direction ? { direction: request.direction } : {},
3080
+ ...request.kind ? { kind: request.kind } : {},
3081
+ ...request.name ? { name: request.name } : {}
3082
+ }
3083
+ };
3084
+ case "hub.push":
3085
+ return {
3086
+ path: "/v1/desktop/hub/updates/push",
3087
+ method: "POST",
3088
+ body: {
3089
+ request_id: request.requestId,
3090
+ link_id: request.linkId,
3091
+ direction: request.direction,
3092
+ dry_run: request.dryRun,
3093
+ ...request.previewReceipt ? { preview_receipt: request.previewReceipt } : {},
3094
+ ...request.confirmPublish ? { confirm_publish: true } : {}
2782
3095
  }
2783
3096
  };
2784
3097
  case "teams.list":
@@ -2798,7 +3111,14 @@ function desktopEndpoint(request) {
2798
3111
  return {
2799
3112
  path: `/v1/desktop/teams/${encodeURIComponent(request.name)}`,
2800
3113
  method: "PATCH",
2801
- body: { request_id: request.requestId, ...request.fields }
3114
+ body: {
3115
+ request_id: request.requestId,
3116
+ ...request.fields,
3117
+ /* Opaque echo beside the fields; absent/null = legacy unconditional. */
3118
+ ...request.expectedDefinitionRevision !== void 0 && request.expectedDefinitionRevision !== null ? {
3119
+ expected_definition_revision: request.expectedDefinitionRevision
3120
+ } : {}
3121
+ }
2802
3122
  };
2803
3123
  case "sessions.list": {
2804
3124
  const params = new URLSearchParams();
@@ -2927,6 +3247,31 @@ function desktopEndpoint(request) {
2927
3247
  method: "GET"
2928
3248
  };
2929
3249
  }
3250
+ case "sessions.files": {
3251
+ const query = new URLSearchParams({
3252
+ limit: String(request.limit ?? 200)
3253
+ });
3254
+ for (const key of [
3255
+ "scope",
3256
+ "kind",
3257
+ "q",
3258
+ "sort",
3259
+ "order",
3260
+ "cursor"
3261
+ ]) {
3262
+ const value = request[key];
3263
+ if (value !== void 0) query.set(key, value);
3264
+ }
3265
+ return {
3266
+ path: `/v1/desktop/sessions/${encodeURIComponent(request.sessionId)}/files?${query}`,
3267
+ method: "GET"
3268
+ };
3269
+ }
3270
+ case "sessions.files.materialise":
3271
+ return {
3272
+ path: `/v1/desktop/sessions/${encodeURIComponent(request.sessionId)}/files/${encodeURIComponent(request.fileId)}/materialise`,
3273
+ method: "POST"
3274
+ };
2930
3275
  case "sessions.history": {
2931
3276
  const query = new URLSearchParams({
2932
3277
  limit: String(request.limit ?? 100)
@@ -3241,7 +3586,10 @@ function desktopEndpoint(request) {
3241
3586
  // there is no override, because the route's input model forbids extras
3242
3587
  // and still wants a JSON OBJECT — an omitted body makes a legal call
3243
3588
  // answer 422.
3244
- body: { document: request.document ?? {} }
3589
+ body: {
3590
+ document: request.document ?? {},
3591
+ ...publicationPolicyBody(request)
3592
+ }
3245
3593
  };
3246
3594
  }
3247
3595
  case "agent.republish": {
@@ -3257,10 +3605,25 @@ function desktopEndpoint(request) {
3257
3605
  method: "PUT",
3258
3606
  body: {
3259
3607
  hub_agent_id: request.hubAgentId,
3260
- document: request.document ?? {}
3608
+ document: request.document ?? {},
3609
+ ...publicationPolicyBody(request),
3610
+ ...request.expectedRemoteRevision ? { expected_remote_revision: request.expectedRemoteRevision } : {}
3261
3611
  }
3262
3612
  };
3263
3613
  }
3614
+ case "team.publish": {
3615
+ assertPairedPublicationTarget(request);
3616
+ const query = new URLSearchParams();
3617
+ if (request.visibility === "org" && request.tenantId) {
3618
+ query.set("visibility", "org");
3619
+ query.set("tenant_id", request.tenantId);
3620
+ }
3621
+ return {
3622
+ path: `/v1/teams/${request.teamId}/publish${query.size ? `?${query}` : ""}`,
3623
+ method: "POST",
3624
+ body: publicationPolicyBody(request)
3625
+ };
3626
+ }
3264
3627
  case "team.pull": {
3265
3628
  const query = new URLSearchParams();
3266
3629
  if (request.tenantId) query.set("tenant_id", request.tenantId);
@@ -13252,6 +13615,11 @@ function describeWindowLaunch(plan, platform = process.platform) {
13252
13615
  const suffix = extras.length === 0 ? "" : `, ${extras.join(", ")}`;
13253
13616
  return `window mode ${plan.mode}: ${plan.width}x${plan.height}, ${behaviour}, page throttling ${plan.backgroundThrottling ? "on" : "off"}${suffix}${assumption}`;
13254
13617
  }
13618
+ function focusBrowserChordTarget(window, target2, show) {
13619
+ if (show !== "focus" || window.isDestroyed() || !window.isFocused() || target2.isDestroyed())
13620
+ return;
13621
+ target2.focus();
13622
+ }
13255
13623
  const MODE_OF_SHOW = {
13256
13624
  focus: "normal",
13257
13625
  inactive: "inactive",
@@ -14766,6 +15134,38 @@ ${result.stdout}`,
14766
15134
  });
14767
15135
  }
14768
15136
  }
15137
+ const DIGIT_KEY$1 = /^[1-9]$/;
15138
+ const DIGIT_CODE = /^Digit([1-9])$/;
15139
+ function browserChordIntent(input, platform) {
15140
+ if (input.type !== "keyDown" || input.alt || input.isComposing) return null;
15141
+ const key = input.key.toLowerCase();
15142
+ let intent = null;
15143
+ if (key === "tab" && input.control && !input.meta) {
15144
+ intent = input.shift ? "prev-tab" : "next-tab";
15145
+ } else {
15146
+ const primary = platform === "darwin" ? input.meta && !input.control : input.control && !input.meta;
15147
+ if (!primary) return null;
15148
+ if (key === "+" || key === "=") intent = "zoom-in";
15149
+ else if (key === "-" && !input.shift) intent = "zoom-out";
15150
+ else if (key === "0" && !input.shift) intent = "zoom-reset";
15151
+ else if (platform === "darwin" && input.shift && ["[", "]", "{", "}"].includes(key)) {
15152
+ intent = key === "[" || key === "{" ? "prev-tab" : "next-tab";
15153
+ } else if (key === "r") intent = input.shift ? "hard-reload" : "reload";
15154
+ else if (key === "t") intent = input.shift ? "reopen-tab" : "new-tab";
15155
+ else if (!input.shift) {
15156
+ if (key === "f") intent = "find-page";
15157
+ else if (key === "l") intent = "focus-address";
15158
+ else if (key === "w") intent = "close-tab";
15159
+ else if (key === "[") intent = "back";
15160
+ else if (key === "]") intent = "forward";
15161
+ else {
15162
+ const digit = DIGIT_KEY$1.test(key) ? key : DIGIT_CODE.exec(input.code ?? "")?.[1];
15163
+ if (digit) intent = `tab-${digit}`;
15164
+ }
15165
+ }
15166
+ }
15167
+ return input.isAutoRepeat && intent !== "next-tab" && intent !== "prev-tab" ? null : intent;
15168
+ }
14769
15169
  const CONSOLE_METHODS = [
14770
15170
  "console_list",
14771
15171
  "console_resources",
@@ -27844,6 +28244,53 @@ class ApprovalStore {
27844
28244
  );
27845
28245
  }
27846
28246
  }
28247
+ function runBrowserMenuAction(intent, target2, owner, route) {
28248
+ if (!target2) return;
28249
+ if (target2 === owner && route(intent)) return;
28250
+ if (intent === "reload") target2.webContents?.reload();
28251
+ else if (intent === "hard-reload") target2.webContents?.reloadIgnoringCache();
28252
+ else target2.close();
28253
+ }
28254
+ function createBrowserChordRouter(options) {
28255
+ function dispatch(intent, origin) {
28256
+ if (intent === "reload" || intent === "hard-reload")
28257
+ options.reload(intent === "hard-reload");
28258
+ else if (intent === "back" || intent === "forward") options.history(intent);
28259
+ else if (intent.startsWith("zoom-") && options.zoom) {
28260
+ options.zoom(
28261
+ intent === "zoom-in" ? "in" : intent === "zoom-out" ? "out" : "reset"
28262
+ );
28263
+ } else {
28264
+ options.focusChrome();
28265
+ options.send({ intent, origin });
28266
+ }
28267
+ }
28268
+ return {
28269
+ input(event, input, origin, owns) {
28270
+ const intent = owns ? browserChordIntent(input, options.platform) : null;
28271
+ if (!intent) {
28272
+ const rejected = owns && input.type === "keyDown" && (input.isAutoRepeat || input.isComposing) && browserChordIntent(
28273
+ { ...input, isAutoRepeat: false, isComposing: false },
28274
+ options.platform
28275
+ );
28276
+ if (rejected) {
28277
+ event.preventDefault();
28278
+ return true;
28279
+ }
28280
+ return false;
28281
+ }
28282
+ event.preventDefault();
28283
+ dispatch(intent, origin);
28284
+ return true;
28285
+ },
28286
+ menu(intent) {
28287
+ const page = options.ownsPage();
28288
+ if (!page && !options.ownsChrome()) return false;
28289
+ dispatch(intent, page ? "page" : "chrome");
28290
+ return true;
28291
+ }
28292
+ };
28293
+ }
27847
28294
  const MAX_ENTRIES = 200;
27848
28295
  const MAX_BYTES = 256 * 1024;
27849
28296
  const MAX_ARG_CHARS = 2e3;
@@ -30881,6 +31328,168 @@ function factOf(path2) {
30881
31328
  sha256: ""
30882
31329
  };
30883
31330
  }
31331
+ const PAGE_ZOOM_STEPS = [
31332
+ 0.25,
31333
+ 0.33,
31334
+ 0.5,
31335
+ 0.67,
31336
+ 0.75,
31337
+ 0.8,
31338
+ 0.9,
31339
+ 1,
31340
+ 1.1,
31341
+ 1.25,
31342
+ 1.5,
31343
+ 1.75,
31344
+ 2,
31345
+ 2.5,
31346
+ 3,
31347
+ 4,
31348
+ 5
31349
+ ];
31350
+ class PageControls {
31351
+ constructor(changed, resultTimeoutMs = 5e3) {
31352
+ this.changed = changed;
31353
+ this.resultTimeoutMs = resultTimeoutMs;
31354
+ }
31355
+ find = null;
31356
+ record = null;
31357
+ epoch = -1;
31358
+ requestId = null;
31359
+ listener = null;
31360
+ notice = null;
31361
+ noticeTabId = null;
31362
+ timer = null;
31363
+ stopTimer() {
31364
+ if (this.timer) clearTimeout(this.timer);
31365
+ this.timer = null;
31366
+ }
31367
+ reconcile(active) {
31368
+ if (this.record && (active !== this.record || !active.presented || active.documentEpoch !== this.epoch || active.view.webContents.isDestroyed()))
31369
+ this.clear();
31370
+ }
31371
+ state(active) {
31372
+ this.reconcile(active);
31373
+ if (active?.tabId !== this.noticeTabId || !active?.presented)
31374
+ this.notice = null;
31375
+ return {
31376
+ find: this.find ? { ...this.find } : null,
31377
+ zoom: active?.view.webContents.getZoomFactor?.() ?? 1,
31378
+ zoomAllowed: active?.owner === "user" && !active.handedTo,
31379
+ notice: this.notice
31380
+ };
31381
+ }
31382
+ clear() {
31383
+ this.stopTimer();
31384
+ const old = this.record;
31385
+ const listener = this.listener;
31386
+ this.record = null;
31387
+ this.find = null;
31388
+ this.requestId = null;
31389
+ this.listener = null;
31390
+ if (old && listener)
31391
+ old.view.webContents.removeListener("found-in-page", listener);
31392
+ if (old && !old.view.webContents.isDestroyed())
31393
+ old.view.webContents.stopFindInPage?.("clearSelection");
31394
+ }
31395
+ open(active) {
31396
+ this.reconcile(active);
31397
+ if (!active.presented) return;
31398
+ if (this.record === active) return;
31399
+ this.clear();
31400
+ this.record = active;
31401
+ this.epoch = active.documentEpoch;
31402
+ this.find = {
31403
+ tabId: active.tabId,
31404
+ query: "",
31405
+ ordinal: 0,
31406
+ matches: 0,
31407
+ pending: false,
31408
+ unreported: false
31409
+ };
31410
+ this.listener = (_event, result) => {
31411
+ if (this.record !== active || !active.presented || active.documentEpoch !== this.epoch || result.requestId !== this.requestId || !this.find)
31412
+ return;
31413
+ if (result.finalUpdate !== false) this.stopTimer();
31414
+ this.find = {
31415
+ ...this.find,
31416
+ unreported: false,
31417
+ ordinal: result.activeMatchOrdinal,
31418
+ matches: result.matches,
31419
+ pending: result.finalUpdate === false
31420
+ };
31421
+ this.changed();
31422
+ };
31423
+ active.view.webContents.on("found-in-page", this.listener);
31424
+ }
31425
+ search(active, query, forward, next) {
31426
+ this.open(active);
31427
+ if (!this.find || this.record !== active) return;
31428
+ this.stopTimer();
31429
+ const same = this.find.query === query;
31430
+ const continuing = same && next && this.requestId !== null && !this.find.unreported;
31431
+ this.requestId = null;
31432
+ this.find = {
31433
+ ...this.find,
31434
+ query,
31435
+ pending: query.length > 0,
31436
+ unreported: false,
31437
+ ...same ? {} : { ordinal: 0, matches: 0 }
31438
+ };
31439
+ if (!query) {
31440
+ active.view.webContents.stopFindInPage?.("clearSelection");
31441
+ return;
31442
+ }
31443
+ this.requestId = active.view.webContents.findInPage?.(query, {
31444
+ forward,
31445
+ // Electron's misleading name means NEW SESSION (44.3 native maps
31446
+ // it to options->new_session), not "advance". Initial/changed/retry
31447
+ // must be true; an existing query's next/previous must be false.
31448
+ findNext: !continuing
31449
+ }) ?? null;
31450
+ const request = this.requestId;
31451
+ this.timer = setTimeout(() => {
31452
+ this.timer = null;
31453
+ if (this.record !== active || !active.presented || active.documentEpoch !== this.epoch || this.requestId !== request || !this.find)
31454
+ return;
31455
+ this.requestId = null;
31456
+ if (active.view.webContents.isDestroyed()) {
31457
+ this.clear();
31458
+ this.changed();
31459
+ return;
31460
+ }
31461
+ active.view.webContents.stopFindInPage?.("clearSelection");
31462
+ this.find = {
31463
+ ...this.find,
31464
+ pending: false,
31465
+ unreported: true,
31466
+ ordinal: 0,
31467
+ matches: 0
31468
+ };
31469
+ this.changed();
31470
+ }, this.resultTimeoutMs);
31471
+ this.timer.unref?.();
31472
+ }
31473
+ zoom(active, command) {
31474
+ if (active.owner !== "user" || active.handedTo) {
31475
+ this.notice = "Zoom is off for tabs an agent is driving";
31476
+ this.noticeTabId = active.tabId;
31477
+ return;
31478
+ }
31479
+ this.notice = null;
31480
+ const current = active.view.webContents.getZoomFactor?.() ?? 1;
31481
+ 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;
31482
+ active.view.webContents.setZoomFactor?.(factor);
31483
+ }
31484
+ /** Call BEFORE a capability transfer. Isolated per-WebContents zoom means
31485
+ * resetting this tab cannot alter another tab at the same origin. */
31486
+ prepareAgent(record) {
31487
+ const changed = this.record === record || (record.view.webContents.getZoomFactor?.() ?? 1) !== 1;
31488
+ if (this.record === record) this.clear();
31489
+ record.view.webContents.setZoomFactor?.(1);
31490
+ if (changed) this.changed();
31491
+ }
31492
+ }
30884
31493
  const DOCUMENT_SCOPED = /* @__PURE__ */ new Set([
30885
31494
  "read",
30886
31495
  // The structured reads (`styles`, `hit_test`, `ancestors`) describe the
@@ -30967,6 +31576,7 @@ function sessionRequesterOf(requester) {
30967
31576
  }
30968
31577
  class BrowserHost {
30969
31578
  registry;
31579
+ pageControls;
30970
31580
  cdp;
30971
31581
  approvals;
30972
31582
  ownership;
@@ -30991,6 +31601,7 @@ class BrowserHost {
30991
31601
  hydration = Promise.resolve();
30992
31602
  constructor(options) {
30993
31603
  this.registry = options.registry;
31604
+ this.pageControls = new PageControls(() => this.onChanged());
30994
31605
  this.cdp = options.cdp;
30995
31606
  this.approvals = options.approvals;
30996
31607
  this.ownership = options.ownership;
@@ -31014,6 +31625,10 @@ class BrowserHost {
31014
31625
  return run();
31015
31626
  }
31016
31627
  async authorizedPerform(method, params, requestId2) {
31628
+ if (typeof params.tab === "string" && TAB_SCOPED.has(method)) {
31629
+ const record2 = this.registry.requireSurface(params.tab);
31630
+ this.pageControls.prepareAgent(record2);
31631
+ }
31017
31632
  if (!DOCUMENT_SCOPED.has(method))
31018
31633
  return this.perform(method, params, requestId2);
31019
31634
  const token = String(params.tab ?? "");
@@ -31272,6 +31887,7 @@ class BrowserHost {
31272
31887
  const active = activeRecord && !activeRecord.view.webContents.isDestroyed() ? activeRecord : null;
31273
31888
  const navigation = active?.view.webContents.navigationHistory;
31274
31889
  return {
31890
+ pageControls: this.pageControls.state(active),
31275
31891
  tabs: this.registry.snapshot().map((entry) => ({
31276
31892
  tabId: entry.tabId,
31277
31893
  title: this.titleForChrome(entry.title) || "New tab",
@@ -31389,16 +32005,94 @@ class BrowserHost {
31389
32005
  * one field.
31390
32006
  */
31391
32007
  async newTab(sessionId2) {
32008
+ await this.createUserTab(sessionId2 ?? null);
32009
+ return this.chromeState();
32010
+ }
32011
+ /** `newTab`'s body, returning the record so `reopenClosed` can navigate the tab
32012
+ * it created rather than whichever tab is active by the time the await ends. */
32013
+ async createUserTab(sessionId2) {
31392
32014
  const record = this.registry.create({
31393
32015
  owner: "user",
31394
- sessionId: sessionId2 ?? null
32016
+ sessionId: sessionId2
31395
32017
  });
31396
32018
  await record.view.webContents.loadURL("about:blank");
31397
32019
  await this.cdp.attach(record.view.webContents);
31398
32020
  this.onChanged();
32021
+ return record;
32022
+ }
32023
+ /** Main owns this short-lived UI focus fact; it conveys no page capability. */
32024
+ chordFocus = false;
32025
+ onChordFocusChanged;
32026
+ /** An explicit tab id fences queued chrome intents: a switch between key
32027
+ * delivery and IPC must never search/zoom the newly active tab instead. */
32028
+ async controlPage(tabId, action, query = "", forward = true, next = false) {
32029
+ return this.registry.lane(tabId, async () => {
32030
+ const active = this.registry.activeTab;
32031
+ if (!active || active.tabId !== tabId || !active.presented || active.view.webContents.isDestroyed())
32032
+ throw new Error("The browser tab is no longer presented.");
32033
+ if (action === "open") this.pageControls.open(active);
32034
+ else if (action === "close") this.pageControls.clear();
32035
+ else if (action === "search")
32036
+ this.pageControls.search(active, query, forward, next);
32037
+ else this.pageControls.zoom(active, action);
32038
+ this.onChanged();
32039
+ return this.chromeState();
32040
+ });
32041
+ }
32042
+ setChordFocus(focused) {
32043
+ this.chordFocus = focused;
32044
+ this.onChordFocusChanged?.(focused);
32045
+ }
32046
+ /** Only user URLs survive a close, never a capability, page state or nonce.
32047
+ * Conversation attribution is retained only to find the appropriate scope. */
32048
+ closedUserTabs = [];
32049
+ rememberClosed(tabId) {
32050
+ const record = this.registry.get(tabId);
32051
+ if (!record || record.owner !== "user" || record.handedTo !== null) return;
32052
+ const url = record.view.webContents.getURL();
32053
+ try {
32054
+ if (!permittedScheme(new URL(url))) return;
32055
+ } catch {
32056
+ return;
32057
+ }
32058
+ this.closedUserTabs.push({ url, sessionId: record.homeSessionId });
32059
+ if (this.closedUserTabs.length > 10) this.closedUserTabs.shift();
32060
+ }
32061
+ clearChordState() {
32062
+ this.pageControls.clear();
32063
+ this.setChordFocus(false);
32064
+ this.closedUserTabs = [];
32065
+ }
32066
+ /**
32067
+ * Forget remembered closes. Called when the user clears browsing data or forgets
32068
+ * a site, because a closed tab's URL can carry a one-time token or name a page
32069
+ * the user just asked this app to erase: Cmd+Shift+T must not resurrect it
32070
+ * (review R3). `origin` filters to one site; omitted forgets every entry. Clearing
32071
+ * only cookies or only the cache leaves the stack alone: neither erases what the
32072
+ * user visited, and a logged-out reopen simply lands on the site's own login.
32073
+ */
32074
+ forgetClosedTabs(origin) {
32075
+ this.closedUserTabs = origin ? this.closedUserTabs.filter((entry) => {
32076
+ try {
32077
+ return new URL(entry.url).origin !== origin;
32078
+ } catch {
32079
+ return false;
32080
+ }
32081
+ }) : [];
32082
+ }
32083
+ async reopenClosed(sessionId2) {
32084
+ const index = this.closedUserTabs.findLastIndex(
32085
+ (entry2) => sessionId2 === null || entry2.sessionId === sessionId2
32086
+ );
32087
+ if (index < 0) return this.chromeState();
32088
+ const [entry] = this.closedUserTabs.splice(index, 1);
32089
+ const record = await this.createUserTab(sessionId2 ?? entry.sessionId);
32090
+ await record.view.webContents.loadURL(entry.url);
32091
+ this.onChanged();
31399
32092
  return this.chromeState();
31400
32093
  }
31401
32094
  closeTab(tabId) {
32095
+ this.rememberClosed(tabId);
31402
32096
  this.dropReceipt(tabId);
31403
32097
  this.loadFailures.delete(tabId);
31404
32098
  this.registry.destroy(tabId);
@@ -31425,6 +32119,7 @@ class BrowserHost {
31425
32119
  closeTabs(intent) {
31426
32120
  const tabIds = intent.mode === "ids" ? intent.tabIds : this.registry.list().filter((record) => record.sessionId === intent.sessionId).map((record) => record.tabId);
31427
32121
  for (const tabId of tabIds) {
32122
+ this.rememberClosed(tabId);
31428
32123
  this.dropReceipt(tabId);
31429
32124
  this.loadFailures.delete(tabId);
31430
32125
  }
@@ -31689,8 +32384,10 @@ class BrowserHost {
31689
32384
  this.onChanged();
31690
32385
  return this.chromeState();
31691
32386
  }
31692
- reloadActive() {
31693
- this.registry.activeTab?.view.webContents.reload();
32387
+ reloadActive(hard = false) {
32388
+ const contents = this.registry.activeTab?.view.webContents;
32389
+ if (hard && contents?.reloadIgnoringCache) contents.reloadIgnoringCache();
32390
+ else contents?.reload();
31694
32391
  return this.chromeState();
31695
32392
  }
31696
32393
  stopActive() {
@@ -31721,6 +32418,8 @@ class BrowserHost {
31721
32418
  * sees the nonce: it travels to the session through `tabs`, which is what keeps
31722
32419
  * this the one authority transfer rather than an exposure (design 6.3, 11.7). */
31723
32420
  handOver(tabId, sessionId2) {
32421
+ const record = this.registry.get(tabId);
32422
+ if (record) this.pageControls.prepareAgent(record);
31724
32423
  this.dropReceipt(tabId);
31725
32424
  this.registry.handOver(tabId, sessionId2);
31726
32425
  this.onChanged();
@@ -31784,6 +32483,7 @@ class BrowserHost {
31784
32483
  const origin = safeHttpUrl(rawOrigin).origin;
31785
32484
  const removed = this.approvals.revokeOrigin(origin);
31786
32485
  await this.forgetSiteData(origin);
32486
+ this.forgetClosedTabs(origin);
31787
32487
  this.log(`[browser] forgot ${origin}: ${removed} approval(s) revoked`);
31788
32488
  this.onChanged();
31789
32489
  return { origin, removed, ...this.chromeState() };
@@ -32624,6 +33324,10 @@ async function startWithRetries(options) {
32624
33324
  }
32625
33325
  const BROWSER_IPC_CHANNELS = [
32626
33326
  "browser-state",
33327
+ "browser-set-chord-focus",
33328
+ "browser-page-control",
33329
+ "browser-reopen-tab",
33330
+ "browser-focus-page",
32627
33331
  "browser-new-tab",
32628
33332
  "browser-close-tab",
32629
33333
  "browser-close-tabs",
@@ -32713,7 +33417,43 @@ function registerBrowserIpc(options) {
32713
33417
  }
32714
33418
  return host.navigateActive(url.trim());
32715
33419
  });
32716
- electron.ipcMain.handle("browser-reload", (event) => authorize(event).reloadActive());
33420
+ electron.ipcMain.handle(
33421
+ "browser-page-control",
33422
+ (event, tabId, action, query, forward, next) => {
33423
+ const host = authorize(event);
33424
+ if (typeof action !== "string" || !["open", "close", "search", "in", "out", "reset"].includes(action))
33425
+ throw new Error("Invalid page control action.");
33426
+ if (typeof query !== "string" || query.length > 4096 || typeof forward !== "boolean" || typeof next !== "boolean")
33427
+ throw new Error("Invalid page search input.");
33428
+ return host.controlPage(
33429
+ numberOrThrow(tabId, "tabId"),
33430
+ action,
33431
+ query,
33432
+ forward,
33433
+ next
33434
+ );
33435
+ }
33436
+ );
33437
+ electron.ipcMain.handle("browser-set-chord-focus", (event, focused) => {
33438
+ const host = authorize(event);
33439
+ if (typeof focused !== "boolean")
33440
+ throw new Error("Browser focus must be a boolean.");
33441
+ host.setChordFocus(focused);
33442
+ });
33443
+ electron.ipcMain.handle(
33444
+ "browser-reopen-tab",
33445
+ (event, sessionId2) => authorize(event).reopenClosed(optionalConversationOrThrow(sessionId2))
33446
+ );
33447
+ electron.ipcMain.handle("browser-focus-page", (event) => {
33448
+ authorize(event);
33449
+ options.focusPage?.();
33450
+ });
33451
+ electron.ipcMain.handle("browser-reload", (event, hard) => {
33452
+ const host = authorize(event);
33453
+ if (hard !== void 0 && typeof hard !== "boolean")
33454
+ throw new Error("Hard reload must be a boolean.");
33455
+ return host.reloadActive(hard === true);
33456
+ });
32717
33457
  electron.ipcMain.handle("browser-stop", (event) => authorize(event).stopActive());
32718
33458
  electron.ipcMain.handle("browser-history", (event, direction) => {
32719
33459
  const host = authorize(event);
@@ -32798,11 +33538,12 @@ function registerBrowserIpc(options) {
32798
33538
  return host.forgetSite(origin.trim());
32799
33539
  });
32800
33540
  electron.ipcMain.handle("browser-clear-data", async (event, what) => {
32801
- authorize(event);
33541
+ const host = authorize(event);
32802
33542
  if (what !== "cookies" && what !== "cache" && what !== "everything") {
32803
33543
  throw new Error("Unsupported clear-data request.");
32804
33544
  }
32805
33545
  await options.clearData(what);
33546
+ if (what === "everything") host.forgetClosedTabs();
32806
33547
  options.log(`[browser] cleared browsing data: ${what}`);
32807
33548
  return { cleared: what };
32808
33549
  });
@@ -34595,6 +35336,22 @@ function refusesNavigation(rawUrl) {
34595
35336
  }
34596
35337
  }
34597
35338
  let activeHost = null;
35339
+ function routeBrowserZoomInput(event, input) {
35340
+ const candidate = browserChordIntent(
35341
+ { ...input, isAutoRepeat: false, isComposing: false },
35342
+ process.platform
35343
+ );
35344
+ if (!candidate?.startsWith("zoom-") || !activeHost) return false;
35345
+ return activeHost.chords.input(
35346
+ event,
35347
+ input,
35348
+ "chrome",
35349
+ activeHost.host.chordFocus
35350
+ );
35351
+ }
35352
+ function routeBrowserMenu(intent) {
35353
+ return activeHost?.chords.menu(intent) ?? false;
35354
+ }
34598
35355
  let stopping = null;
34599
35356
  function browserHostStopPending() {
34600
35357
  return activeHost !== null || stopping !== null || starting !== null;
@@ -34662,6 +35419,12 @@ function startBrowserHost(options) {
34662
35419
  expectedUrl: options.expectedUrl,
34663
35420
  host: () => bindings.host?.() ?? null,
34664
35421
  webauthn: () => bindings.webauthn?.() ?? null,
35422
+ focusPage: () => {
35423
+ const record = bindings.host?.()?.registry.activeTab;
35424
+ const target2 = record?.presented ? electron.webContents.fromId(record.view.webContents.id) : null;
35425
+ if (target2)
35426
+ focusBrowserChordTarget(options.window, target2, options.windowShow);
35427
+ },
34665
35428
  availability: () => availability,
34666
35429
  clearData: async (what) => {
34667
35430
  if (!bindings.clearData)
@@ -34741,6 +35504,8 @@ async function initializeBrowserHost(options, bindings) {
34741
35504
  // No `preload`: a driven page has no bridge to anything.
34742
35505
  }
34743
35506
  });
35507
+ view.webContents.setZoomMode("isolated");
35508
+ view.webContents.setZoomFactor(1);
34744
35509
  window.contentView.addChildView(view);
34745
35510
  view.setBounds(BACKGROUND_VIEWPORT);
34746
35511
  view.setVisible(false);
@@ -34818,10 +35583,10 @@ async function initializeBrowserHost(options, bindings) {
34818
35583
  contents.on("will-navigate", onWillNavigate);
34819
35584
  contents.on("will-redirect", onWillNavigate);
34820
35585
  contents.on("before-input-event", (event, input) => {
34821
- const direction = paneNavigationDirection(input);
34822
- if (!direction) return;
34823
- event.preventDefault();
34824
- host.historyActive(direction);
35586
+ const record = registry.get(tabId);
35587
+ const owns = record === registry.activeTab && record?.presented === true;
35588
+ const handled = chords.input(event, input, "page", owns);
35589
+ contents.setIgnoreMenuShortcuts(handled);
34825
35590
  });
34826
35591
  const notifyChrome = () => {
34827
35592
  options.window.webContents.send("browser-state-changed");
@@ -35125,6 +35890,75 @@ async function initializeBrowserHost(options, bindings) {
35125
35890
  facts: () => facts,
35126
35891
  forgetSiteData: (origin) => clearOriginData(browserSession, origin)
35127
35892
  });
35893
+ const chords = createBrowserChordRouter({
35894
+ platform: process.platform,
35895
+ ownsChrome: () => host.chordFocus,
35896
+ ownsPage: () => {
35897
+ const record = registry.activeTab;
35898
+ return Boolean(
35899
+ record?.presented && electron.webContents.getFocusedWebContents()?.id === record.view.webContents.id
35900
+ );
35901
+ },
35902
+ reload: (hard) => {
35903
+ host.reloadActive(hard);
35904
+ },
35905
+ zoom: (command) => {
35906
+ const tabId = registry.activeTab?.tabId;
35907
+ if (tabId !== void 0)
35908
+ void host.controlPage(tabId, command).catch(
35909
+ (error) => log2(
35910
+ `Page zoom refused: ${error instanceof Error ? error.message : "unavailable"}`
35911
+ )
35912
+ );
35913
+ },
35914
+ history: (direction) => {
35915
+ host.historyActive(direction);
35916
+ },
35917
+ send: (message) => options.window.webContents.send("browser-chord", message),
35918
+ focusChrome: () => focusBrowserChordTarget(
35919
+ options.window,
35920
+ options.window.webContents,
35921
+ options.windowShow
35922
+ )
35923
+ });
35924
+ const onChromeInput = (event, input) => {
35925
+ if (event.defaultPrevented) return;
35926
+ const handled = chords.input(event, input, "chrome", host.chordFocus);
35927
+ options.window.webContents.setIgnoreMenuShortcuts(handled);
35928
+ };
35929
+ const resetChordFocus = () => {
35930
+ host.setChordFocus(false);
35931
+ };
35932
+ host.onChordFocusChanged = (focused) => {
35933
+ if (!focused && !options.window.webContents.isDestroyed())
35934
+ options.window.webContents.setIgnoreMenuShortcuts(false);
35935
+ };
35936
+ options.window.webContents.on("before-input-event", onChromeInput);
35937
+ options.window.on("blur", resetChordFocus);
35938
+ options.window.on("closed", resetChordFocus);
35939
+ const resetOnNavigation = (details) => {
35940
+ if (details.isMainFrame && !details.isSameDocument) resetChordFocus();
35941
+ };
35942
+ options.window.webContents.on("did-start-navigation", resetOnNavigation);
35943
+ options.window.webContents.on("render-process-gone", resetChordFocus);
35944
+ const detachChords = () => {
35945
+ host.clearChordState();
35946
+ options.window.webContents.removeListener(
35947
+ "before-input-event",
35948
+ onChromeInput
35949
+ );
35950
+ options.window.removeListener("blur", resetChordFocus);
35951
+ options.window.removeListener("closed", resetChordFocus);
35952
+ options.window.webContents.removeListener(
35953
+ "did-start-navigation",
35954
+ resetOnNavigation
35955
+ );
35956
+ options.window.webContents.removeListener(
35957
+ "render-process-gone",
35958
+ resetChordFocus
35959
+ );
35960
+ };
35961
+ rollback.add(detachChords);
35128
35962
  const recorded = readSession(sessionStore.filePath, log2);
35129
35963
  host.restoreTabs(recorded);
35130
35964
  if (recorded.length) {
@@ -35240,12 +36074,14 @@ async function initializeBrowserHost(options, bindings) {
35240
36074
  `[browser] host on ${server.address}:${server.port} (proto ${facts.proto}), profile ${profileDir || "(in-memory)"}, agent tabs ${registry.agentTabCount()}/${8}, console ${consoleStartup.ok ? `on (${consoleStartup.counts().total} surface(s))` : `off (${consoleStartup.reason})`}`
35241
36075
  );
35242
36076
  const handle2 = {
36077
+ chords,
35243
36078
  host,
35244
36079
  port: server.port,
35245
36080
  profileDir,
35246
36081
  agentTabs: () => registry.agentTabCount(),
35247
36082
  sessionCookies: () => restoreReport,
35248
36083
  stop: async () => {
36084
+ detachChords();
35249
36085
  const atStop = captureTabs(
35250
36086
  registry.list(),
35251
36087
  registry.activeTab?.tabId ?? null,
@@ -45492,25 +46328,6 @@ function configureAboutPanel() {
45492
46328
  }
45493
46329
  function createApplicationMenu() {
45494
46330
  const isDev = Boolean(process.env.ELECTRON_RENDERER_URL);
45495
- const zoomInHandler = () => {
45496
- if (mainWindow) {
45497
- const webContents2 = mainWindow.webContents;
45498
- const currentZoom = webContents2.getZoomFactor();
45499
- webContents2.setZoomFactor(currentZoom + 0.1);
45500
- }
45501
- };
45502
- const zoomOutHandler = () => {
45503
- if (mainWindow) {
45504
- const webContents2 = mainWindow.webContents;
45505
- const currentZoom = webContents2.getZoomFactor();
45506
- webContents2.setZoomFactor(currentZoom - 0.1);
45507
- }
45508
- };
45509
- const actualSizeHandler = () => {
45510
- if (mainWindow) {
45511
- mainWindow.webContents.setZoomFactor(1);
45512
- }
45513
- };
45514
46331
  const template = [
45515
46332
  {
45516
46333
  label: "File",
@@ -45534,29 +46351,62 @@ function createApplicationMenu() {
45534
46351
  label: "View",
45535
46352
  submenu: [
45536
46353
  {
45537
- role: "reload",
45538
- accelerator: "CmdOrCtrl+R"
46354
+ id: "app-reload",
46355
+ label: "Reload",
46356
+ accelerator: "CmdOrCtrl+R",
46357
+ click: (_item, target2) => runBrowserMenuAction(
46358
+ "reload",
46359
+ target2,
46360
+ mainWindow,
46361
+ routeBrowserMenu
46362
+ )
45539
46363
  },
45540
46364
  {
45541
- role: "forceReload",
45542
- accelerator: "CmdOrCtrl+Shift+R"
46365
+ id: "app-force-reload",
46366
+ label: "Force Reload",
46367
+ accelerator: "CmdOrCtrl+Shift+R",
46368
+ click: (_item, target2) => runBrowserMenuAction(
46369
+ "hard-reload",
46370
+ target2,
46371
+ mainWindow,
46372
+ routeBrowserMenu
46373
+ )
45543
46374
  },
45544
46375
  { type: "separator" },
45545
46376
  {
46377
+ id: "app-actual-size",
45546
46378
  label: "Actual Size",
45547
- accelerator: "CmdOrCtrl+O",
45548
- click: actualSizeHandler
46379
+ accelerator: "CmdOrCtrl+0",
46380
+ click: (_item, target2) => {
46381
+ if (!(target2 instanceof electron.BrowserWindow)) return;
46382
+ if (target2 === mainWindow && routeBrowserMenu("zoom-reset")) return;
46383
+ target2.webContents.setZoomFactor(1);
46384
+ }
45549
46385
  },
45550
46386
  {
46387
+ id: "app-zoom-in",
45551
46388
  label: "Zoom In",
45552
46389
  accelerator: "CmdOrCtrl+Plus",
45553
46390
  // On macOS, this often requires Shift as well (Cmd+Shift+=)
45554
- click: zoomInHandler
46391
+ click: (_item, target2) => {
46392
+ if (!(target2 instanceof electron.BrowserWindow)) return;
46393
+ if (target2 === mainWindow && routeBrowserMenu("zoom-in")) return;
46394
+ target2.webContents.setZoomFactor(
46395
+ target2.webContents.getZoomFactor() + 0.1
46396
+ );
46397
+ }
45555
46398
  },
45556
46399
  {
46400
+ id: "app-zoom-out",
45557
46401
  label: "Zoom Out",
45558
46402
  accelerator: "CmdOrCtrl+-",
45559
- click: zoomOutHandler
46403
+ click: (_item, target2) => {
46404
+ if (!(target2 instanceof electron.BrowserWindow)) return;
46405
+ if (target2 === mainWindow && routeBrowserMenu("zoom-out")) return;
46406
+ target2.webContents.setZoomFactor(
46407
+ target2.webContents.getZoomFactor() - 0.1
46408
+ );
46409
+ }
45560
46410
  },
45561
46411
  { type: "separator" },
45562
46412
  { role: "togglefullscreen" }
@@ -45582,10 +46432,40 @@ function createApplicationMenu() {
45582
46432
  * `window-all-closed` path the red button takes, which is the one
45583
46433
  * this change made safe for an install in flight.
45584
46434
  */
45585
- { role: "close" },
46435
+ {
46436
+ id: "app-close",
46437
+ // One honest label for both owners: the SAME accelerator closes a
46438
+ // browser TAB while the browser has focus and the WINDOW elsewhere,
46439
+ // and a menu label cannot follow focus that lives in a page view
46440
+ // (UX r1 U4). The roles it replaced were "Close Window"/"Close".
46441
+ label: "Close Tab or Window",
46442
+ accelerator: "CmdOrCtrl+W",
46443
+ click: (_item, target2) => runBrowserMenuAction(
46444
+ "close-tab",
46445
+ target2,
46446
+ mainWindow,
46447
+ routeBrowserMenu
46448
+ )
46449
+ },
45586
46450
  { type: "separator" },
45587
46451
  { role: "window" }
45588
- ] : [{ role: "close" }]
46452
+ ] : [
46453
+ {
46454
+ id: "app-close",
46455
+ // One honest label for both owners: the SAME accelerator closes a
46456
+ // browser TAB while the browser has focus and the WINDOW elsewhere,
46457
+ // and a menu label cannot follow focus that lives in a page view
46458
+ // (UX r1 U4). The roles it replaced were "Close Window"/"Close".
46459
+ label: "Close Tab or Window",
46460
+ accelerator: "CmdOrCtrl+W",
46461
+ click: (_item, target2) => runBrowserMenuAction(
46462
+ "close-tab",
46463
+ target2,
46464
+ mainWindow,
46465
+ routeBrowserMenu
46466
+ )
46467
+ }
46468
+ ]
45589
46469
  ]
45590
46470
  },
45591
46471
  {
@@ -46966,14 +47846,16 @@ electron.app.whenReady().then(async () => {
46966
47846
  if (mainWindow) {
46967
47847
  mainWindow.webContents.on("before-input-event", (event, input) => {
46968
47848
  const isCmdOrCtrl = input.control || input.meta;
46969
- if (isCmdOrCtrl) {
47849
+ if (event.defaultPrevented) return;
47850
+ if (routeBrowserZoomInput(event, input)) return;
47851
+ if (isCmdOrCtrl && input.type === "keyDown" && !input.alt && !input.isComposing) {
46970
47852
  if (input.key === "+" || input.key === "=") {
46971
47853
  zoomInFromEvent();
46972
47854
  event.preventDefault();
46973
47855
  } else if (input.key === "-") {
46974
47856
  zoomOutFromEvent();
46975
47857
  event.preventDefault();
46976
- } else if (input.key === "O") {
47858
+ } else if (input.key === "0") {
46977
47859
  actualSizeFromEvent();
46978
47860
  event.preventDefault();
46979
47861
  }