local-operator-ui 0.34.44 → 0.34.46

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 +1051 -66
  2. package/out/preload/index.js +305 -18
  3. package/out/renderer/assets/{_basePickBy-C-GwcPo5.js → _basePickBy-hPi3fEQr.js} +1 -1
  4. package/out/renderer/assets/{_baseUniq-whkmDW6w.js → _baseUniq-BH6SqqsG.js} +1 -1
  5. package/out/renderer/assets/{agent-details-page-DpCpkTfN.js → agent-details-page-D6Sf3QUC.js} +2 -2
  6. package/out/renderer/assets/agent-hub-page-geh3R5Uy.js +11 -0
  7. package/out/renderer/assets/agents-page-CFjl9C1o.js +7 -0
  8. package/out/renderer/assets/arc-hK5DqZTp.js +1 -0
  9. package/out/renderer/assets/architectureDiagram-IEHRJDOE-Od2Y7WJh.js +36 -0
  10. package/out/renderer/assets/{blockDiagram-JOT3LUYC-Dp4G8uLX.js → blockDiagram-JOT3LUYC-Ctc7-wAT.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-KrEaCKVz.js +1 -0
  13. package/out/renderer/assets/browser-webauthn-prompt-CKqDqDU5.js +1 -0
  14. package/out/renderer/assets/{c4Diagram-VJAJSXHY-CnX5ELGv.js → c4Diagram-VJAJSXHY-KjhfyGml.js} +1 -1
  15. package/out/renderer/assets/channel-Bs-Xt2ih.js +1 -0
  16. package/out/renderer/assets/{chunk-4BMEZGHF-DYgqjjvg.js → chunk-4BMEZGHF-DUs-A7ex.js} +1 -1
  17. package/out/renderer/assets/{chunk-A2AXSNBT-3DoU7E6Y.js → chunk-A2AXSNBT-LbRRk1xd.js} +1 -1
  18. package/out/renderer/assets/{chunk-AEK57VVT-OX8UcnIV.js → chunk-AEK57VVT-Ddc0Dk4L.js} +1 -1
  19. package/out/renderer/assets/{chunk-D6G4REZN-Dbt1r37J.js → chunk-D6G4REZN-Cj8PtGTg.js} +1 -1
  20. package/out/renderer/assets/{chunk-RZ5BOZE2-CdLKa5S8.js → chunk-RZ5BOZE2-KXGjmJKN.js} +1 -1
  21. package/out/renderer/assets/{chunk-XZIHB7SX-B_CjCOio.js → chunk-XZIHB7SX-B8dmznR7.js} +1 -1
  22. package/out/renderer/assets/classDiagram-GIVACNV2-BrkzW__m.js +1 -0
  23. package/out/renderer/assets/classDiagram-v2-COTLJTTW-BrkzW__m.js +1 -0
  24. package/out/renderer/assets/clone-CnlBeott.js +1 -0
  25. package/out/renderer/assets/{console-mirror-D1YYJxrt.js → console-mirror-cPzOwzR9.js} +14 -14
  26. package/out/renderer/assets/consoleCapture-ComgYGzq.js +1 -0
  27. package/out/renderer/assets/{conversation-input-sync-BbbcfWqh.js → conversation-input-sync-B5HfvhyR.js} +259 -261
  28. package/out/renderer/assets/dagre-OKDRZEBW-BaeP_npI.js +4 -0
  29. package/out/renderer/assets/diagram-SSKATNLV-BDwSJBgs.js +43 -0
  30. package/out/renderer/assets/diagram-VNBRO52H-Ce2_qnEd.js +24 -0
  31. package/out/renderer/assets/{erDiagram-Q7BY3M3F-Cs4yDA7g.js → erDiagram-Q7BY3M3F-DUpyMDtK.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-Twa-e-xh.js} +1 -1
  34. package/out/renderer/assets/ganttDiagram-APWFNJXF-6IcdZzTN.js +257 -0
  35. package/out/renderer/assets/gitGraphDiagram-7IBYFJ6S-BHwjqSB2.js +65 -0
  36. package/out/renderer/assets/{graph-D3WT1lJS.js → graph-CjSBt2SL.js} +1 -1
  37. package/out/renderer/assets/index-BDqI882o.js +7 -0
  38. package/out/renderer/assets/index-C7mMgpwT.css +1 -0
  39. package/out/renderer/assets/index-fKRRIb8a.js +1 -0
  40. package/out/renderer/assets/index-o-e57W52.js +804 -0
  41. package/out/renderer/assets/infoDiagram-PH2N3AL5-DusuxGWy.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-ouPHBWbW.js} +1 -1
  44. package/out/renderer/assets/{kanban-definition-NDS4AKOZ-DpiJ7P3i.js → kanban-definition-NDS4AKOZ-BpfzQ_cF.js} +1 -1
  45. package/out/renderer/assets/{layout-BFA2wwh_.js → layout-DWM2xDwq.js} +1 -1
  46. package/out/renderer/assets/legacy-agents-page-DMWtT-nK.js +33 -0
  47. package/out/renderer/assets/{mermaid.core-C7iGmYcY.js → mermaid.core-C2AhDEdD.js} +5 -5
  48. package/out/renderer/assets/mesh-page-DI_FXBTf.js +1 -0
  49. package/out/renderer/assets/messages-Bf6oXWsi.js +3 -0
  50. package/out/renderer/assets/mindmap-definition-ALO5MXBD-CWFoHd9r.js +95 -0
  51. package/out/renderer/assets/mini-BVxX6iDv.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-Cj7MYYx3.js +1 -0
  54. package/out/renderer/assets/org-surface-gate-B61_V5sH.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-C4p49jAq.js +30 -0
  57. package/out/renderer/assets/projects-page-D0OgNUWf.js +45 -0
  58. package/out/renderer/assets/publication-validation-DLHcjuJy.js +1 -0
  59. package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-u3qKyomE.js → quadrantDiagram-7GDLP6J5-By3GJSI3.js} +1 -1
  60. package/out/renderer/assets/{radar-MK3ICKWK-DTXBXOd-.js → radar-MK3ICKWK-n8dqb_zt.js} +1 -1
  61. package/out/renderer/assets/radient-auth-buttons-p_Axd8pX.js +1 -0
  62. package/out/renderer/assets/{requirementDiagram-KVF5MWMF-CIyOJzHO.js → requirementDiagram-KVF5MWMF-CQ7JllrS.js} +1 -1
  63. package/out/renderer/assets/sankeyDiagram-QLVOVGJD-DNyPn5zx.js +10 -0
  64. package/out/renderer/assets/{schedules-page-DHwzeRvF.js → schedules-page-R7SXiFmi.js} +1 -1
  65. package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-DCj3Ke6W.js → sequenceDiagram-X6HHIX6F-CUHeYZkr.js} +1 -1
  66. package/out/renderer/assets/{settings-page-Br0K0YLW.js → settings-page-BZ_HPCkT.js} +60 -60
  67. package/out/renderer/assets/stateDiagram-DGXRK772-CL_twVvQ.js +1 -0
  68. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-ruz5Lapk.js +1 -0
  69. package/out/renderer/assets/{timeline-definition-BDJGKUSR-C4cCAtD2.js → timeline-definition-BDJGKUSR-BCcBGH9R.js} +1 -1
  70. package/out/renderer/assets/{use-download-agent-mutation-CRmN4Z4O.js → use-download-agent-mutation-B87MG4sW.js} +4 -4
  71. package/out/renderer/assets/use-memberships-query-DCEm9yOI.js +1 -0
  72. package/out/renderer/assets/{use-session-snapshot-DZAAvDEa.js → use-session-snapshot-CChKOZqb.js} +1 -1
  73. package/out/renderer/assets/{xychartDiagram-VJFVF3MP-DG9g8Bz_.js → xychartDiagram-VJFVF3MP-CvHda-UX.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()
@@ -1866,6 +1993,39 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
1866
1993
  accountId: zod.z.number().int().positive(),
1867
1994
  confirmed: zod.z.literal(true)
1868
1995
  }).strict(),
1996
+ /*
1997
+ * PHONE ACCESS SETUP (`features.tunnel_setup`, local-operator
1998
+ * `docs/DESKTOP_API.md` "Explicit phone setup"). Three ops over ONE route,
1999
+ * split by authority so a caller cannot mutate by accident:
2000
+ *
2001
+ * - `tunnel.setup.read`: GET, local metadata only. No upstream read, no token
2002
+ * refresh, never `verified` and never a quote - the cheap poll that rebuilds
2003
+ * the block after a window close or a lost response.
2004
+ * - `tunnel.setup.plan`: POST `{action:"plan"}`, a FRESH account/quote/
2005
+ * preparation observation that changes nothing.
2006
+ * - `tunnel.setup.run`: POST `{action:"run"}`, the only op that can write. The
2007
+ * backend REFUSES it without an explicit owner (422), so `credentialId` is
2008
+ * required here rather than defaulted: an account is never picked for the
2009
+ * user. `acceptMonthlyPrice` is a STRING, the exact fresh amount the user
2010
+ * saw, because the backend compares it to a fresh quote and a number would
2011
+ * round-trip through float formatting.
2012
+ *
2013
+ * ONLY the capability gates these. An older backend 404s the route, so the
2014
+ * renderer asks `features.tunnel_setup` first and sends nothing without it.
2015
+ */
2016
+ zod.z.object({
2017
+ op: zod.z.literal("tunnel.setup.read"),
2018
+ credentialId: zod.z.number().int().positive().optional()
2019
+ }).strict(),
2020
+ zod.z.object({
2021
+ op: zod.z.literal("tunnel.setup.plan"),
2022
+ credentialId: zod.z.number().int().positive().optional()
2023
+ }).strict(),
2024
+ zod.z.object({
2025
+ op: zod.z.literal("tunnel.setup.run"),
2026
+ credentialId: zod.z.number().int().positive(),
2027
+ acceptMonthlyPrice: zod.z.string().min(1).max(64).optional()
2028
+ }).strict(),
1869
2029
  zod.z.object({ op: zod.z.literal("auth.start"), provider: id$1 }).strict(),
1870
2030
  zod.z.object({ op: zod.z.literal("auth.status"), id: id$1 }).strict(),
1871
2031
  zod.z.object({ op: zod.z.literal("auth.input"), id: id$1, promptId: id$1, value: secret }).strict(),
@@ -2239,6 +2399,71 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
2239
2399
  ]);
2240
2400
  const desktopRequestSchema = desktopRequestUnion.superRefine(
2241
2401
  (request, ctx) => {
2402
+ if (request.op === "hub.skillPull" || request.op === "hub.skillPublish") {
2403
+ if (request.visibility === "public" && request.tenantId !== null || request.visibility === "org" && !request.tenantId) {
2404
+ ctx.addIssue({
2405
+ code: zod.z.ZodIssueCode.custom,
2406
+ message: "A skill destination requires explicit paired scope",
2407
+ path: ["tenantId"]
2408
+ });
2409
+ return;
2410
+ }
2411
+ if (request.op === "hub.skillPublish" && (request.dryRun ? request.previewReceipt !== void 0 || request.confirmPublish !== void 0 : !request.previewReceipt || request.confirmPublish !== true)) {
2412
+ ctx.addIssue({
2413
+ code: zod.z.ZodIssueCode.custom,
2414
+ message: "An initial skill publication requires its reviewed draft receipt",
2415
+ path: ["previewReceipt"]
2416
+ });
2417
+ return;
2418
+ }
2419
+ }
2420
+ if (request.op === "hub.check" || request.op === "hub.apply" || request.op === "hub.retry") {
2421
+ const hasLink = request.linkId !== void 0;
2422
+ const hasLegacy = request.kind !== void 0 || request.name !== void 0;
2423
+ if (hasLink && hasLegacy || request.op !== "hub.check" && !hasLink && (!request.kind || !request.name) || request.op === "hub.retry" && hasLink && !request.direction || request.direction === "push" && !hasLink) {
2424
+ ctx.addIssue({
2425
+ code: zod.z.ZodIssueCode.custom,
2426
+ message: "A hub action requires one unambiguous selector and an explicit retry direction",
2427
+ path: ["linkId"]
2428
+ });
2429
+ return;
2430
+ }
2431
+ }
2432
+ if (request.op === "hub.apply" && (request.previewReceipt !== void 0 || request.confirmApply !== void 0) && (!request.linkId || request.dryRun || !request.previewReceipt || request.confirmApply !== true)) {
2433
+ ctx.addIssue({
2434
+ code: zod.z.ZodIssueCode.custom,
2435
+ message: "A viewed incoming application requires its scoped preview receipt and confirmation",
2436
+ path: ["previewReceipt"]
2437
+ });
2438
+ return;
2439
+ }
2440
+ if (request.op === "hub.apply" && request.resolveSource === true) {
2441
+ const scopePaired = request.visibility === "public" && request.tenantId === null || request.visibility === "org" && typeof request.tenantId === "string" && request.tenantId.length > 0;
2442
+ if (!request.hubSource || !scopePaired || request.acknowledgeUnknownBaseline !== true || request.linkId !== void 0 || request.previewReceipt !== void 0 || request.confirmApply !== void 0 || request.prefer !== void 0) {
2443
+ ctx.addIssue({
2444
+ code: zod.z.ZodIssueCode.custom,
2445
+ message: "Confirming a source requires its exact stated destination and the unknown-baseline acknowledgement, with no link, preview or preference",
2446
+ path: ["hubSource"]
2447
+ });
2448
+ return;
2449
+ }
2450
+ }
2451
+ if (request.op === "hub.push" && (request.dryRun ? request.previewReceipt !== void 0 || request.confirmPublish !== void 0 : !request.previewReceipt || request.confirmPublish !== true)) {
2452
+ ctx.addIssue({
2453
+ code: zod.z.ZodIssueCode.custom,
2454
+ message: "A confirmed publication requires its reviewed preview receipt",
2455
+ path: ["previewReceipt"]
2456
+ });
2457
+ return;
2458
+ }
2459
+ if (request.op === "hub.policy" && request.subscribe === void 0 && request.autoPush === void 0) {
2460
+ ctx.addIssue({
2461
+ code: zod.z.ZodIssueCode.custom,
2462
+ message: "A hub policy change must name subscribe or autoPush",
2463
+ path: ["subscribe"]
2464
+ });
2465
+ return;
2466
+ }
2242
2467
  if (request.op === "sessions.answer") {
2243
2468
  if (request.askId !== void 0) {
2244
2469
  if (!request.askId)
@@ -2314,7 +2539,15 @@ const desktopRequestSchema = desktopRequestUnion.superRefine(
2314
2539
  }
2315
2540
  return;
2316
2541
  }
2317
- if (request.op !== "agent.publish" && request.op !== "agent.republish") {
2542
+ if (request.op !== "agent.publish" && request.op !== "agent.republish" && request.op !== "team.publish") {
2543
+ return;
2544
+ }
2545
+ if ((request.subscribe !== void 0 || request.autoPush !== void 0 || request.confirmPublicEgress !== void 0) && !request.requestId) {
2546
+ ctx.addIssue({
2547
+ code: zod.z.ZodIssueCode.custom,
2548
+ message: "Initial link policy needs a durable publication request ID",
2549
+ path: ["requestId"]
2550
+ });
2318
2551
  return;
2319
2552
  }
2320
2553
  if (Boolean(request.visibility === "org") !== Boolean(request.tenantId)) {
@@ -2373,6 +2606,44 @@ function desktopRefusalCodeForStatus(path2, status2) {
2373
2606
  if (status2 === 503) return DESKTOP_REFUSAL_CODE.planeClosed;
2374
2607
  return void 0;
2375
2608
  }
2609
+ zod.z.object({
2610
+ step: zod.z.string(),
2611
+ blocked_on: zod.z.string(),
2612
+ configured: zod.z.boolean(),
2613
+ ready: zod.z.boolean(),
2614
+ verified: zod.z.boolean(),
2615
+ url: zod.z.string().nullable(),
2616
+ credential_id: zod.z.number().int().positive().nullable(),
2617
+ identity_label: zod.z.string().nullable(),
2618
+ credential_choices: zod.z.array(
2619
+ zod.z.object({
2620
+ id: zod.z.number().int().positive(),
2621
+ identity_label: zod.z.string().nullable().optional()
2622
+ }).passthrough()
2623
+ ).default([]),
2624
+ quote: zod.z.object({
2625
+ monthly_price_usd: zod.z.string().nullable(),
2626
+ positive_balance: zod.z.boolean(),
2627
+ eligible: zod.z.boolean(),
2628
+ account_valid: zod.z.boolean()
2629
+ }).passthrough().nullable(),
2630
+ receipts: zod.z.array(zod.z.object({ step: zod.z.string(), ok: zod.z.boolean(), text: zod.z.string() })).default([]),
2631
+ remedy: zod.z.object({
2632
+ kind: zod.z.string(),
2633
+ text: zod.z.string(),
2634
+ command: zod.z.string().nullable().optional(),
2635
+ url: zod.z.string().nullable().optional()
2636
+ }).nullable(),
2637
+ source: zod.z.string(),
2638
+ in_progress: zod.z.boolean().default(false),
2639
+ progress: zod.z.array(zod.z.string()).default([]),
2640
+ active_credential_id: zod.z.number().int().positive().nullable().default(null),
2641
+ // Backend facts, separate from receipts: an emitted receipt does not prove
2642
+ // an ambiguous activation succeeded or was rolled back.
2643
+ billing_outcome: zod.z.string().optional(),
2644
+ reservation_outcome: zod.z.string().optional(),
2645
+ failure_stage: zod.z.string().optional()
2646
+ }).passthrough();
2376
2647
  const DESKTOP_MESSAGE_BYTE_BUDGET = 88e4;
2377
2648
  const DESKTOP_CONTROL_BYTE_BUDGET = 262144;
2378
2649
  const DESKTOP_SYSTEM_PROMPT_BYTE_BUDGET = 11e5;
@@ -2430,12 +2701,17 @@ function moveClientBoundMs(shape) {
2430
2701
  const PRESENCE_GESTURE_DEADLINE_MS = 18e4;
2431
2702
  const APPROVAL_APPROVE_DEADLINE_MS = PRESENCE_GESTURE_DEADLINE_MS + 15e3;
2432
2703
  const HUB_ITEM_MERGE_S = 120;
2704
+ const PHONE_SETUP_PLAN_DEADLINE_MS = 135e3;
2705
+ const PHONE_SETUP_RUN_DEADLINE_MS = 93e4;
2433
2706
  const HUB_ITEM_WRITE_DEADLINE_MS = (HUB_ITEM_MERGE_S + 30 + MOVE_CLIENT_MARGIN_S) * 1e3;
2434
2707
  const HUB_CHECK_DEADLINE_MS = 6e4;
2435
2708
  const HUB_APPLY_ALL_DEADLINE_MS = 5 * HUB_ITEM_WRITE_DEADLINE_MS;
2436
2709
  const HUB_WRITE_OPS = /* @__PURE__ */ new Set([
2437
2710
  "hub.apply",
2438
2711
  "hub.retry",
2712
+ "hub.push",
2713
+ "hub.skillPublish",
2714
+ "hub.skillPull",
2439
2715
  "hub.check",
2440
2716
  "hub.applyAll"
2441
2717
  ]);
@@ -2443,6 +2719,9 @@ function hubWriteDeadlineMs(op) {
2443
2719
  switch (op) {
2444
2720
  case "hub.apply":
2445
2721
  case "hub.retry":
2722
+ case "hub.push":
2723
+ case "hub.skillPublish":
2724
+ case "hub.skillPull":
2446
2725
  return HUB_ITEM_WRITE_DEADLINE_MS;
2447
2726
  case "hub.check":
2448
2727
  return HUB_CHECK_DEADLINE_MS;
@@ -2458,6 +2737,8 @@ function desktopRequestDeadlineMs(request) {
2458
2737
  }
2459
2738
  const op = typeof request === "string" ? request : request.op;
2460
2739
  if (op === "approvals.approve") return APPROVAL_APPROVE_DEADLINE_MS;
2740
+ if (op === "tunnel.setup.run") return PHONE_SETUP_RUN_DEADLINE_MS;
2741
+ if (op === "tunnel.setup.plan") return PHONE_SETUP_PLAN_DEADLINE_MS;
2461
2742
  const hub = hubWriteDeadlineMs(op);
2462
2743
  if (hub !== null) return hub;
2463
2744
  return LONG_READ_OPS.has(op) ? DESKTOP_LONG_READ_DEADLINE_MS : DESKTOP_CONTROL_DEADLINE_MS;
@@ -2475,6 +2756,7 @@ const READ_ONLY_OPS = /* @__PURE__ */ new Set([
2475
2756
  "config.get",
2476
2757
  "credentials.list",
2477
2758
  "hub.updates",
2759
+ "hub.links",
2478
2760
  "info.get",
2479
2761
  "instructions.get",
2480
2762
  "legacy.agent.get",
@@ -2511,6 +2793,7 @@ const READ_ONLY_OPS = /* @__PURE__ */ new Set([
2511
2793
  "sessions.find",
2512
2794
  "sessions.get",
2513
2795
  "sessions.history",
2796
+ "sessions.files",
2514
2797
  "sessions.list",
2515
2798
  "sessions.preview",
2516
2799
  "sessions.report",
@@ -2520,6 +2803,10 @@ const READ_ONLY_OPS = /* @__PURE__ */ new Set([
2520
2803
  "subagents.transcript",
2521
2804
  "teams.get",
2522
2805
  "teams.list",
2806
+ // The two phone-setup observations change nothing on the server; `tunnel.setup.run`
2807
+ // is deliberately absent so a run that ran out of time gets the cautious sentence.
2808
+ "tunnel.setup.plan",
2809
+ "tunnel.setup.read",
2523
2810
  "usage.get"
2524
2811
  ]);
2525
2812
  const PANEL_READ_OPS = /* @__PURE__ */ new Set([
@@ -2617,6 +2904,14 @@ function wakeTiming(request) {
2617
2904
  ...request.limit !== void 0 ? { limit: request.limit } : {}
2618
2905
  };
2619
2906
  }
2907
+ function publicationPolicyBody(request) {
2908
+ return {
2909
+ ...request.requestId ? { request_id: request.requestId } : {},
2910
+ ...request.subscribe !== void 0 ? { subscribe: request.subscribe } : {},
2911
+ ...request.autoPush !== void 0 ? { auto_push: request.autoPush } : {},
2912
+ ...request.confirmPublicEgress !== void 0 ? { confirm_public_egress: request.confirmPublicEgress } : {}
2913
+ };
2914
+ }
2620
2915
  function assertPairedPublicationTarget(request) {
2621
2916
  if (Boolean(request.visibility === "org") !== Boolean(request.tenantId)) {
2622
2917
  throw new Error(
@@ -2733,16 +3028,81 @@ function desktopEndpoint(request) {
2733
3028
  return {
2734
3029
  path: `/v1/desktop/profiles/${encodeURIComponent(request.name)}`,
2735
3030
  method: "PATCH",
2736
- body: { request_id: request.requestId, ...request.fields }
3031
+ body: {
3032
+ request_id: request.requestId,
3033
+ ...request.fields,
3034
+ /*
3035
+ * The opaque echo travels ALONGSIDE the fields, never inside them: the
3036
+ * edit body is `exclude_unset`-merged on the backend, so a revision
3037
+ * nested in `fields` could be mistaken for an editable property. Sent
3038
+ * only when present, so absence stays the legacy unconditional write.
3039
+ */
3040
+ ...request.expectedDefinitionRevision !== void 0 && request.expectedDefinitionRevision !== null ? {
3041
+ expected_definition_revision: request.expectedDefinitionRevision
3042
+ } : {}
3043
+ }
2737
3044
  };
2738
3045
  case "hub.updates":
2739
3046
  return { path: "/v1/desktop/hub/updates", method: "GET" };
3047
+ case "hub.skillPull":
3048
+ return {
3049
+ path: "/v1/desktop/hub/skills/pull",
3050
+ method: "POST",
3051
+ body: {
3052
+ request_id: request.requestId,
3053
+ hub_id: request.hubId,
3054
+ hub_source: request.hubSource,
3055
+ visibility: request.visibility,
3056
+ tenant_id: request.tenantId,
3057
+ local_alias: request.localAlias,
3058
+ ...request.subscribe !== void 0 ? { subscribe: request.subscribe } : {},
3059
+ ...request.autoPush !== void 0 ? { auto_push: request.autoPush } : {},
3060
+ ...request.confirmPublicEgress !== void 0 ? { confirm_public_egress: request.confirmPublicEgress } : {}
3061
+ }
3062
+ };
3063
+ case "hub.skillPublish":
3064
+ return {
3065
+ path: "/v1/desktop/hub/skills/publish",
3066
+ method: "POST",
3067
+ body: {
3068
+ request_id: request.requestId,
3069
+ dry_run: request.dryRun,
3070
+ source_skill: request.sourceSkill,
3071
+ local_alias: request.localAlias,
3072
+ visibility: request.visibility,
3073
+ tenant_id: request.tenantId,
3074
+ subscribe: request.subscribe,
3075
+ auto_push: request.autoPush,
3076
+ confirm_public_egress: request.confirmPublicEgress,
3077
+ ...request.previewReceipt ? { preview_receipt: request.previewReceipt } : {},
3078
+ ...request.confirmPublish ? { confirm_publish: true } : {}
3079
+ }
3080
+ };
3081
+ case "hub.links":
3082
+ return {
3083
+ path: `/v1/desktop/hub/links${request.kind ? `?kind=${request.kind}` : ""}`,
3084
+ method: "GET"
3085
+ };
3086
+ case "hub.policy":
3087
+ return {
3088
+ path: `/v1/desktop/hub/links/${encodeURIComponent(request.linkId)}`,
3089
+ method: "PATCH",
3090
+ body: {
3091
+ request_id: request.requestId,
3092
+ expected_policy_revision: request.expectedPolicyRevision,
3093
+ ...request.subscribe !== void 0 ? { subscribe: request.subscribe } : {},
3094
+ ...request.autoPush !== void 0 ? { auto_push: request.autoPush } : {},
3095
+ ...request.confirmPublicEgress !== void 0 ? { confirm_public_egress: request.confirmPublicEgress } : {}
3096
+ }
3097
+ };
2740
3098
  case "hub.check":
2741
3099
  return {
2742
3100
  path: "/v1/desktop/hub/updates/check",
2743
3101
  method: "POST",
2744
3102
  body: {
2745
3103
  request_id: request.requestId,
3104
+ ...request.linkId ? { link_id: request.linkId } : {},
3105
+ ...request.direction ? { direction: request.direction } : {},
2746
3106
  ...request.kind ? { kind: request.kind } : {},
2747
3107
  ...request.name ? { name: request.name } : {}
2748
3108
  }
@@ -2753,13 +3113,30 @@ function desktopEndpoint(request) {
2753
3113
  method: "POST",
2754
3114
  body: {
2755
3115
  request_id: request.requestId,
2756
- kind: request.kind,
2757
- name: request.name,
3116
+ ...request.previewReceipt ? { preview_receipt: request.previewReceipt } : {},
3117
+ ...request.confirmApply ? { confirm_apply: true } : {},
3118
+ ...request.linkId ? { link_id: request.linkId } : {},
3119
+ ...request.direction ? { direction: request.direction } : {},
3120
+ ...request.kind ? { kind: request.kind } : {},
3121
+ ...request.name ? { name: request.name } : {},
2758
3122
  ...request.prefer ? { prefer: request.prefer } : {},
2759
3123
  ...request.acknowledgeUnknownBaseline !== void 0 ? {
2760
3124
  acknowledge_unknown_baseline: request.acknowledgeUnknownBaseline
2761
3125
  } : {},
2762
- ...request.dryRun !== void 0 ? { dry_run: request.dryRun } : {}
3126
+ ...request.dryRun !== void 0 ? { dry_run: request.dryRun } : {},
3127
+ /*
3128
+ * The source-confirmation branch travels on the SAME apply route with an
3129
+ * explicit flag, never a new endpoint. `resolve_source:true` plus the
3130
+ * stated scope triple is the whole act; the service materializes the
3131
+ * real identity/link and preserves any known scope (refusing a conflict
3132
+ * with `origin-conflict`), so nothing here infers Public or an org.
3133
+ */
3134
+ ...request.resolveSource === true ? {
3135
+ resolve_source: true,
3136
+ hub_source: request.hubSource,
3137
+ visibility: request.visibility,
3138
+ tenant_id: request.tenantId ?? null
3139
+ } : {}
2763
3140
  }
2764
3141
  };
2765
3142
  case "hub.applyAll":
@@ -2777,8 +3154,23 @@ function desktopEndpoint(request) {
2777
3154
  method: "POST",
2778
3155
  body: {
2779
3156
  request_id: request.requestId,
2780
- kind: request.kind,
2781
- name: request.name
3157
+ ...request.linkId ? { link_id: request.linkId } : {},
3158
+ ...request.direction ? { direction: request.direction } : {},
3159
+ ...request.kind ? { kind: request.kind } : {},
3160
+ ...request.name ? { name: request.name } : {}
3161
+ }
3162
+ };
3163
+ case "hub.push":
3164
+ return {
3165
+ path: "/v1/desktop/hub/updates/push",
3166
+ method: "POST",
3167
+ body: {
3168
+ request_id: request.requestId,
3169
+ link_id: request.linkId,
3170
+ direction: request.direction,
3171
+ dry_run: request.dryRun,
3172
+ ...request.previewReceipt ? { preview_receipt: request.previewReceipt } : {},
3173
+ ...request.confirmPublish ? { confirm_publish: true } : {}
2782
3174
  }
2783
3175
  };
2784
3176
  case "teams.list":
@@ -2798,7 +3190,14 @@ function desktopEndpoint(request) {
2798
3190
  return {
2799
3191
  path: `/v1/desktop/teams/${encodeURIComponent(request.name)}`,
2800
3192
  method: "PATCH",
2801
- body: { request_id: request.requestId, ...request.fields }
3193
+ body: {
3194
+ request_id: request.requestId,
3195
+ ...request.fields,
3196
+ /* Opaque echo beside the fields; absent/null = legacy unconditional. */
3197
+ ...request.expectedDefinitionRevision !== void 0 && request.expectedDefinitionRevision !== null ? {
3198
+ expected_definition_revision: request.expectedDefinitionRevision
3199
+ } : {}
3200
+ }
2802
3201
  };
2803
3202
  case "sessions.list": {
2804
3203
  const params = new URLSearchParams();
@@ -2927,6 +3326,31 @@ function desktopEndpoint(request) {
2927
3326
  method: "GET"
2928
3327
  };
2929
3328
  }
3329
+ case "sessions.files": {
3330
+ const query = new URLSearchParams({
3331
+ limit: String(request.limit ?? 200)
3332
+ });
3333
+ for (const key of [
3334
+ "scope",
3335
+ "kind",
3336
+ "q",
3337
+ "sort",
3338
+ "order",
3339
+ "cursor"
3340
+ ]) {
3341
+ const value = request[key];
3342
+ if (value !== void 0) query.set(key, value);
3343
+ }
3344
+ return {
3345
+ path: `/v1/desktop/sessions/${encodeURIComponent(request.sessionId)}/files?${query}`,
3346
+ method: "GET"
3347
+ };
3348
+ }
3349
+ case "sessions.files.materialise":
3350
+ return {
3351
+ path: `/v1/desktop/sessions/${encodeURIComponent(request.sessionId)}/files/${encodeURIComponent(request.fileId)}/materialise`,
3352
+ method: "POST"
3353
+ };
2930
3354
  case "sessions.history": {
2931
3355
  const query = new URLSearchParams({
2932
3356
  limit: String(request.limit ?? 100)
@@ -3241,7 +3665,10 @@ function desktopEndpoint(request) {
3241
3665
  // there is no override, because the route's input model forbids extras
3242
3666
  // and still wants a JSON OBJECT — an omitted body makes a legal call
3243
3667
  // answer 422.
3244
- body: { document: request.document ?? {} }
3668
+ body: {
3669
+ document: request.document ?? {},
3670
+ ...publicationPolicyBody(request)
3671
+ }
3245
3672
  };
3246
3673
  }
3247
3674
  case "agent.republish": {
@@ -3257,10 +3684,25 @@ function desktopEndpoint(request) {
3257
3684
  method: "PUT",
3258
3685
  body: {
3259
3686
  hub_agent_id: request.hubAgentId,
3260
- document: request.document ?? {}
3687
+ document: request.document ?? {},
3688
+ ...publicationPolicyBody(request),
3689
+ ...request.expectedRemoteRevision ? { expected_remote_revision: request.expectedRemoteRevision } : {}
3261
3690
  }
3262
3691
  };
3263
3692
  }
3693
+ case "team.publish": {
3694
+ assertPairedPublicationTarget(request);
3695
+ const query = new URLSearchParams();
3696
+ if (request.visibility === "org" && request.tenantId) {
3697
+ query.set("visibility", "org");
3698
+ query.set("tenant_id", request.tenantId);
3699
+ }
3700
+ return {
3701
+ path: `/v1/teams/${request.teamId}/publish${query.size ? `?${query}` : ""}`,
3702
+ method: "POST",
3703
+ body: publicationPolicyBody(request)
3704
+ };
3705
+ }
3264
3706
  case "team.pull": {
3265
3707
  const query = new URLSearchParams();
3266
3708
  if (request.tenantId) query.set("tenant_id", request.tenantId);
@@ -3660,6 +4102,30 @@ function desktopEndpoint(request) {
3660
4102
  path: `/v1/auth/accounts/${request.accountId}`,
3661
4103
  method: "DELETE"
3662
4104
  };
4105
+ case "tunnel.setup.read":
4106
+ return {
4107
+ path: request.credentialId === void 0 ? "/v1/desktop/tunnel/setup" : `/v1/desktop/tunnel/setup?credential_id=${request.credentialId}`,
4108
+ method: "GET"
4109
+ };
4110
+ case "tunnel.setup.plan":
4111
+ return {
4112
+ path: "/v1/desktop/tunnel/setup",
4113
+ method: "POST",
4114
+ body: {
4115
+ action: "plan",
4116
+ ...request.credentialId !== void 0 ? { credential_id: request.credentialId } : {}
4117
+ }
4118
+ };
4119
+ case "tunnel.setup.run":
4120
+ return {
4121
+ path: "/v1/desktop/tunnel/setup",
4122
+ method: "POST",
4123
+ body: {
4124
+ action: "run",
4125
+ credential_id: request.credentialId,
4126
+ ...request.acceptMonthlyPrice !== void 0 ? { accept_monthly_price: request.acceptMonthlyPrice } : {}
4127
+ }
4128
+ };
3663
4129
  case "auth.start":
3664
4130
  return {
3665
4131
  path: "/v1/auth/login",
@@ -13252,6 +13718,11 @@ function describeWindowLaunch(plan, platform = process.platform) {
13252
13718
  const suffix = extras.length === 0 ? "" : `, ${extras.join(", ")}`;
13253
13719
  return `window mode ${plan.mode}: ${plan.width}x${plan.height}, ${behaviour}, page throttling ${plan.backgroundThrottling ? "on" : "off"}${suffix}${assumption}`;
13254
13720
  }
13721
+ function focusBrowserChordTarget(window, target2, show) {
13722
+ if (show !== "focus" || window.isDestroyed() || !window.isFocused() || target2.isDestroyed())
13723
+ return;
13724
+ target2.focus();
13725
+ }
13255
13726
  const MODE_OF_SHOW = {
13256
13727
  focus: "normal",
13257
13728
  inactive: "inactive",
@@ -14766,6 +15237,38 @@ ${result.stdout}`,
14766
15237
  });
14767
15238
  }
14768
15239
  }
15240
+ const DIGIT_KEY$1 = /^[1-9]$/;
15241
+ const DIGIT_CODE = /^Digit([1-9])$/;
15242
+ function browserChordIntent(input, platform) {
15243
+ if (input.type !== "keyDown" || input.alt || input.isComposing) return null;
15244
+ const key = input.key.toLowerCase();
15245
+ let intent = null;
15246
+ if (key === "tab" && input.control && !input.meta) {
15247
+ intent = input.shift ? "prev-tab" : "next-tab";
15248
+ } else {
15249
+ const primary = platform === "darwin" ? input.meta && !input.control : input.control && !input.meta;
15250
+ if (!primary) return null;
15251
+ if (key === "+" || key === "=") intent = "zoom-in";
15252
+ else if (key === "-" && !input.shift) intent = "zoom-out";
15253
+ else if (key === "0" && !input.shift) intent = "zoom-reset";
15254
+ else if (platform === "darwin" && input.shift && ["[", "]", "{", "}"].includes(key)) {
15255
+ intent = key === "[" || key === "{" ? "prev-tab" : "next-tab";
15256
+ } else if (key === "r") intent = input.shift ? "hard-reload" : "reload";
15257
+ else if (key === "t") intent = input.shift ? "reopen-tab" : "new-tab";
15258
+ else if (!input.shift) {
15259
+ if (key === "f") intent = "find-page";
15260
+ else if (key === "l") intent = "focus-address";
15261
+ else if (key === "w") intent = "close-tab";
15262
+ else if (key === "[") intent = "back";
15263
+ else if (key === "]") intent = "forward";
15264
+ else {
15265
+ const digit = DIGIT_KEY$1.test(key) ? key : DIGIT_CODE.exec(input.code ?? "")?.[1];
15266
+ if (digit) intent = `tab-${digit}`;
15267
+ }
15268
+ }
15269
+ }
15270
+ return input.isAutoRepeat && intent !== "next-tab" && intent !== "prev-tab" ? null : intent;
15271
+ }
14769
15272
  const CONSOLE_METHODS = [
14770
15273
  "console_list",
14771
15274
  "console_resources",
@@ -27844,6 +28347,53 @@ class ApprovalStore {
27844
28347
  );
27845
28348
  }
27846
28349
  }
28350
+ function runBrowserMenuAction(intent, target2, owner, route) {
28351
+ if (!target2) return;
28352
+ if (target2 === owner && route(intent)) return;
28353
+ if (intent === "reload") target2.webContents?.reload();
28354
+ else if (intent === "hard-reload") target2.webContents?.reloadIgnoringCache();
28355
+ else target2.close();
28356
+ }
28357
+ function createBrowserChordRouter(options) {
28358
+ function dispatch(intent, origin) {
28359
+ if (intent === "reload" || intent === "hard-reload")
28360
+ options.reload(intent === "hard-reload");
28361
+ else if (intent === "back" || intent === "forward") options.history(intent);
28362
+ else if (intent.startsWith("zoom-") && options.zoom) {
28363
+ options.zoom(
28364
+ intent === "zoom-in" ? "in" : intent === "zoom-out" ? "out" : "reset"
28365
+ );
28366
+ } else {
28367
+ options.focusChrome();
28368
+ options.send({ intent, origin });
28369
+ }
28370
+ }
28371
+ return {
28372
+ input(event, input, origin, owns) {
28373
+ const intent = owns ? browserChordIntent(input, options.platform) : null;
28374
+ if (!intent) {
28375
+ const rejected = owns && input.type === "keyDown" && (input.isAutoRepeat || input.isComposing) && browserChordIntent(
28376
+ { ...input, isAutoRepeat: false, isComposing: false },
28377
+ options.platform
28378
+ );
28379
+ if (rejected) {
28380
+ event.preventDefault();
28381
+ return true;
28382
+ }
28383
+ return false;
28384
+ }
28385
+ event.preventDefault();
28386
+ dispatch(intent, origin);
28387
+ return true;
28388
+ },
28389
+ menu(intent) {
28390
+ const page = options.ownsPage();
28391
+ if (!page && !options.ownsChrome()) return false;
28392
+ dispatch(intent, page ? "page" : "chrome");
28393
+ return true;
28394
+ }
28395
+ };
28396
+ }
27847
28397
  const MAX_ENTRIES = 200;
27848
28398
  const MAX_BYTES = 256 * 1024;
27849
28399
  const MAX_ARG_CHARS = 2e3;
@@ -30881,6 +31431,168 @@ function factOf(path2) {
30881
31431
  sha256: ""
30882
31432
  };
30883
31433
  }
31434
+ const PAGE_ZOOM_STEPS = [
31435
+ 0.25,
31436
+ 0.33,
31437
+ 0.5,
31438
+ 0.67,
31439
+ 0.75,
31440
+ 0.8,
31441
+ 0.9,
31442
+ 1,
31443
+ 1.1,
31444
+ 1.25,
31445
+ 1.5,
31446
+ 1.75,
31447
+ 2,
31448
+ 2.5,
31449
+ 3,
31450
+ 4,
31451
+ 5
31452
+ ];
31453
+ class PageControls {
31454
+ constructor(changed, resultTimeoutMs = 5e3) {
31455
+ this.changed = changed;
31456
+ this.resultTimeoutMs = resultTimeoutMs;
31457
+ }
31458
+ find = null;
31459
+ record = null;
31460
+ epoch = -1;
31461
+ requestId = null;
31462
+ listener = null;
31463
+ notice = null;
31464
+ noticeTabId = null;
31465
+ timer = null;
31466
+ stopTimer() {
31467
+ if (this.timer) clearTimeout(this.timer);
31468
+ this.timer = null;
31469
+ }
31470
+ reconcile(active) {
31471
+ if (this.record && (active !== this.record || !active.presented || active.documentEpoch !== this.epoch || active.view.webContents.isDestroyed()))
31472
+ this.clear();
31473
+ }
31474
+ state(active) {
31475
+ this.reconcile(active);
31476
+ if (active?.tabId !== this.noticeTabId || !active?.presented)
31477
+ this.notice = null;
31478
+ return {
31479
+ find: this.find ? { ...this.find } : null,
31480
+ zoom: active?.view.webContents.getZoomFactor?.() ?? 1,
31481
+ zoomAllowed: active?.owner === "user" && !active.handedTo,
31482
+ notice: this.notice
31483
+ };
31484
+ }
31485
+ clear() {
31486
+ this.stopTimer();
31487
+ const old = this.record;
31488
+ const listener = this.listener;
31489
+ this.record = null;
31490
+ this.find = null;
31491
+ this.requestId = null;
31492
+ this.listener = null;
31493
+ if (old && listener)
31494
+ old.view.webContents.removeListener("found-in-page", listener);
31495
+ if (old && !old.view.webContents.isDestroyed())
31496
+ old.view.webContents.stopFindInPage?.("clearSelection");
31497
+ }
31498
+ open(active) {
31499
+ this.reconcile(active);
31500
+ if (!active.presented) return;
31501
+ if (this.record === active) return;
31502
+ this.clear();
31503
+ this.record = active;
31504
+ this.epoch = active.documentEpoch;
31505
+ this.find = {
31506
+ tabId: active.tabId,
31507
+ query: "",
31508
+ ordinal: 0,
31509
+ matches: 0,
31510
+ pending: false,
31511
+ unreported: false
31512
+ };
31513
+ this.listener = (_event, result) => {
31514
+ if (this.record !== active || !active.presented || active.documentEpoch !== this.epoch || result.requestId !== this.requestId || !this.find)
31515
+ return;
31516
+ if (result.finalUpdate !== false) this.stopTimer();
31517
+ this.find = {
31518
+ ...this.find,
31519
+ unreported: false,
31520
+ ordinal: result.activeMatchOrdinal,
31521
+ matches: result.matches,
31522
+ pending: result.finalUpdate === false
31523
+ };
31524
+ this.changed();
31525
+ };
31526
+ active.view.webContents.on("found-in-page", this.listener);
31527
+ }
31528
+ search(active, query, forward, next) {
31529
+ this.open(active);
31530
+ if (!this.find || this.record !== active) return;
31531
+ this.stopTimer();
31532
+ const same = this.find.query === query;
31533
+ const continuing = same && next && this.requestId !== null && !this.find.unreported;
31534
+ this.requestId = null;
31535
+ this.find = {
31536
+ ...this.find,
31537
+ query,
31538
+ pending: query.length > 0,
31539
+ unreported: false,
31540
+ ...same ? {} : { ordinal: 0, matches: 0 }
31541
+ };
31542
+ if (!query) {
31543
+ active.view.webContents.stopFindInPage?.("clearSelection");
31544
+ return;
31545
+ }
31546
+ this.requestId = active.view.webContents.findInPage?.(query, {
31547
+ forward,
31548
+ // Electron's misleading name means NEW SESSION (44.3 native maps
31549
+ // it to options->new_session), not "advance". Initial/changed/retry
31550
+ // must be true; an existing query's next/previous must be false.
31551
+ findNext: !continuing
31552
+ }) ?? null;
31553
+ const request = this.requestId;
31554
+ this.timer = setTimeout(() => {
31555
+ this.timer = null;
31556
+ if (this.record !== active || !active.presented || active.documentEpoch !== this.epoch || this.requestId !== request || !this.find)
31557
+ return;
31558
+ this.requestId = null;
31559
+ if (active.view.webContents.isDestroyed()) {
31560
+ this.clear();
31561
+ this.changed();
31562
+ return;
31563
+ }
31564
+ active.view.webContents.stopFindInPage?.("clearSelection");
31565
+ this.find = {
31566
+ ...this.find,
31567
+ pending: false,
31568
+ unreported: true,
31569
+ ordinal: 0,
31570
+ matches: 0
31571
+ };
31572
+ this.changed();
31573
+ }, this.resultTimeoutMs);
31574
+ this.timer.unref?.();
31575
+ }
31576
+ zoom(active, command) {
31577
+ if (active.owner !== "user" || active.handedTo) {
31578
+ this.notice = "Zoom is off for tabs an agent is driving";
31579
+ this.noticeTabId = active.tabId;
31580
+ return;
31581
+ }
31582
+ this.notice = null;
31583
+ const current = active.view.webContents.getZoomFactor?.() ?? 1;
31584
+ 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;
31585
+ active.view.webContents.setZoomFactor?.(factor);
31586
+ }
31587
+ /** Call BEFORE a capability transfer. Isolated per-WebContents zoom means
31588
+ * resetting this tab cannot alter another tab at the same origin. */
31589
+ prepareAgent(record) {
31590
+ const changed = this.record === record || (record.view.webContents.getZoomFactor?.() ?? 1) !== 1;
31591
+ if (this.record === record) this.clear();
31592
+ record.view.webContents.setZoomFactor?.(1);
31593
+ if (changed) this.changed();
31594
+ }
31595
+ }
30884
31596
  const DOCUMENT_SCOPED = /* @__PURE__ */ new Set([
30885
31597
  "read",
30886
31598
  // The structured reads (`styles`, `hit_test`, `ancestors`) describe the
@@ -30967,6 +31679,7 @@ function sessionRequesterOf(requester) {
30967
31679
  }
30968
31680
  class BrowserHost {
30969
31681
  registry;
31682
+ pageControls;
30970
31683
  cdp;
30971
31684
  approvals;
30972
31685
  ownership;
@@ -30991,6 +31704,7 @@ class BrowserHost {
30991
31704
  hydration = Promise.resolve();
30992
31705
  constructor(options) {
30993
31706
  this.registry = options.registry;
31707
+ this.pageControls = new PageControls(() => this.onChanged());
30994
31708
  this.cdp = options.cdp;
30995
31709
  this.approvals = options.approvals;
30996
31710
  this.ownership = options.ownership;
@@ -31014,6 +31728,10 @@ class BrowserHost {
31014
31728
  return run();
31015
31729
  }
31016
31730
  async authorizedPerform(method, params, requestId2) {
31731
+ if (typeof params.tab === "string" && TAB_SCOPED.has(method)) {
31732
+ const record2 = this.registry.requireSurface(params.tab);
31733
+ this.pageControls.prepareAgent(record2);
31734
+ }
31017
31735
  if (!DOCUMENT_SCOPED.has(method))
31018
31736
  return this.perform(method, params, requestId2);
31019
31737
  const token = String(params.tab ?? "");
@@ -31272,6 +31990,7 @@ class BrowserHost {
31272
31990
  const active = activeRecord && !activeRecord.view.webContents.isDestroyed() ? activeRecord : null;
31273
31991
  const navigation = active?.view.webContents.navigationHistory;
31274
31992
  return {
31993
+ pageControls: this.pageControls.state(active),
31275
31994
  tabs: this.registry.snapshot().map((entry) => ({
31276
31995
  tabId: entry.tabId,
31277
31996
  title: this.titleForChrome(entry.title) || "New tab",
@@ -31389,16 +32108,94 @@ class BrowserHost {
31389
32108
  * one field.
31390
32109
  */
31391
32110
  async newTab(sessionId2) {
32111
+ await this.createUserTab(sessionId2 ?? null);
32112
+ return this.chromeState();
32113
+ }
32114
+ /** `newTab`'s body, returning the record so `reopenClosed` can navigate the tab
32115
+ * it created rather than whichever tab is active by the time the await ends. */
32116
+ async createUserTab(sessionId2) {
31392
32117
  const record = this.registry.create({
31393
32118
  owner: "user",
31394
- sessionId: sessionId2 ?? null
32119
+ sessionId: sessionId2
31395
32120
  });
31396
32121
  await record.view.webContents.loadURL("about:blank");
31397
32122
  await this.cdp.attach(record.view.webContents);
31398
32123
  this.onChanged();
32124
+ return record;
32125
+ }
32126
+ /** Main owns this short-lived UI focus fact; it conveys no page capability. */
32127
+ chordFocus = false;
32128
+ onChordFocusChanged;
32129
+ /** An explicit tab id fences queued chrome intents: a switch between key
32130
+ * delivery and IPC must never search/zoom the newly active tab instead. */
32131
+ async controlPage(tabId, action, query = "", forward = true, next = false) {
32132
+ return this.registry.lane(tabId, async () => {
32133
+ const active = this.registry.activeTab;
32134
+ if (!active || active.tabId !== tabId || !active.presented || active.view.webContents.isDestroyed())
32135
+ throw new Error("The browser tab is no longer presented.");
32136
+ if (action === "open") this.pageControls.open(active);
32137
+ else if (action === "close") this.pageControls.clear();
32138
+ else if (action === "search")
32139
+ this.pageControls.search(active, query, forward, next);
32140
+ else this.pageControls.zoom(active, action);
32141
+ this.onChanged();
32142
+ return this.chromeState();
32143
+ });
32144
+ }
32145
+ setChordFocus(focused) {
32146
+ this.chordFocus = focused;
32147
+ this.onChordFocusChanged?.(focused);
32148
+ }
32149
+ /** Only user URLs survive a close, never a capability, page state or nonce.
32150
+ * Conversation attribution is retained only to find the appropriate scope. */
32151
+ closedUserTabs = [];
32152
+ rememberClosed(tabId) {
32153
+ const record = this.registry.get(tabId);
32154
+ if (!record || record.owner !== "user" || record.handedTo !== null) return;
32155
+ const url = record.view.webContents.getURL();
32156
+ try {
32157
+ if (!permittedScheme(new URL(url))) return;
32158
+ } catch {
32159
+ return;
32160
+ }
32161
+ this.closedUserTabs.push({ url, sessionId: record.homeSessionId });
32162
+ if (this.closedUserTabs.length > 10) this.closedUserTabs.shift();
32163
+ }
32164
+ clearChordState() {
32165
+ this.pageControls.clear();
32166
+ this.setChordFocus(false);
32167
+ this.closedUserTabs = [];
32168
+ }
32169
+ /**
32170
+ * Forget remembered closes. Called when the user clears browsing data or forgets
32171
+ * a site, because a closed tab's URL can carry a one-time token or name a page
32172
+ * the user just asked this app to erase: Cmd+Shift+T must not resurrect it
32173
+ * (review R3). `origin` filters to one site; omitted forgets every entry. Clearing
32174
+ * only cookies or only the cache leaves the stack alone: neither erases what the
32175
+ * user visited, and a logged-out reopen simply lands on the site's own login.
32176
+ */
32177
+ forgetClosedTabs(origin) {
32178
+ this.closedUserTabs = origin ? this.closedUserTabs.filter((entry) => {
32179
+ try {
32180
+ return new URL(entry.url).origin !== origin;
32181
+ } catch {
32182
+ return false;
32183
+ }
32184
+ }) : [];
32185
+ }
32186
+ async reopenClosed(sessionId2) {
32187
+ const index = this.closedUserTabs.findLastIndex(
32188
+ (entry2) => sessionId2 === null || entry2.sessionId === sessionId2
32189
+ );
32190
+ if (index < 0) return this.chromeState();
32191
+ const [entry] = this.closedUserTabs.splice(index, 1);
32192
+ const record = await this.createUserTab(sessionId2 ?? entry.sessionId);
32193
+ await record.view.webContents.loadURL(entry.url);
32194
+ this.onChanged();
31399
32195
  return this.chromeState();
31400
32196
  }
31401
32197
  closeTab(tabId) {
32198
+ this.rememberClosed(tabId);
31402
32199
  this.dropReceipt(tabId);
31403
32200
  this.loadFailures.delete(tabId);
31404
32201
  this.registry.destroy(tabId);
@@ -31425,6 +32222,7 @@ class BrowserHost {
31425
32222
  closeTabs(intent) {
31426
32223
  const tabIds = intent.mode === "ids" ? intent.tabIds : this.registry.list().filter((record) => record.sessionId === intent.sessionId).map((record) => record.tabId);
31427
32224
  for (const tabId of tabIds) {
32225
+ this.rememberClosed(tabId);
31428
32226
  this.dropReceipt(tabId);
31429
32227
  this.loadFailures.delete(tabId);
31430
32228
  }
@@ -31689,8 +32487,10 @@ class BrowserHost {
31689
32487
  this.onChanged();
31690
32488
  return this.chromeState();
31691
32489
  }
31692
- reloadActive() {
31693
- this.registry.activeTab?.view.webContents.reload();
32490
+ reloadActive(hard = false) {
32491
+ const contents = this.registry.activeTab?.view.webContents;
32492
+ if (hard && contents?.reloadIgnoringCache) contents.reloadIgnoringCache();
32493
+ else contents?.reload();
31694
32494
  return this.chromeState();
31695
32495
  }
31696
32496
  stopActive() {
@@ -31721,6 +32521,8 @@ class BrowserHost {
31721
32521
  * sees the nonce: it travels to the session through `tabs`, which is what keeps
31722
32522
  * this the one authority transfer rather than an exposure (design 6.3, 11.7). */
31723
32523
  handOver(tabId, sessionId2) {
32524
+ const record = this.registry.get(tabId);
32525
+ if (record) this.pageControls.prepareAgent(record);
31724
32526
  this.dropReceipt(tabId);
31725
32527
  this.registry.handOver(tabId, sessionId2);
31726
32528
  this.onChanged();
@@ -31784,6 +32586,7 @@ class BrowserHost {
31784
32586
  const origin = safeHttpUrl(rawOrigin).origin;
31785
32587
  const removed = this.approvals.revokeOrigin(origin);
31786
32588
  await this.forgetSiteData(origin);
32589
+ this.forgetClosedTabs(origin);
31787
32590
  this.log(`[browser] forgot ${origin}: ${removed} approval(s) revoked`);
31788
32591
  this.onChanged();
31789
32592
  return { origin, removed, ...this.chromeState() };
@@ -32624,6 +33427,10 @@ async function startWithRetries(options) {
32624
33427
  }
32625
33428
  const BROWSER_IPC_CHANNELS = [
32626
33429
  "browser-state",
33430
+ "browser-set-chord-focus",
33431
+ "browser-page-control",
33432
+ "browser-reopen-tab",
33433
+ "browser-focus-page",
32627
33434
  "browser-new-tab",
32628
33435
  "browser-close-tab",
32629
33436
  "browser-close-tabs",
@@ -32713,7 +33520,43 @@ function registerBrowserIpc(options) {
32713
33520
  }
32714
33521
  return host.navigateActive(url.trim());
32715
33522
  });
32716
- electron.ipcMain.handle("browser-reload", (event) => authorize(event).reloadActive());
33523
+ electron.ipcMain.handle(
33524
+ "browser-page-control",
33525
+ (event, tabId, action, query, forward, next) => {
33526
+ const host = authorize(event);
33527
+ if (typeof action !== "string" || !["open", "close", "search", "in", "out", "reset"].includes(action))
33528
+ throw new Error("Invalid page control action.");
33529
+ if (typeof query !== "string" || query.length > 4096 || typeof forward !== "boolean" || typeof next !== "boolean")
33530
+ throw new Error("Invalid page search input.");
33531
+ return host.controlPage(
33532
+ numberOrThrow(tabId, "tabId"),
33533
+ action,
33534
+ query,
33535
+ forward,
33536
+ next
33537
+ );
33538
+ }
33539
+ );
33540
+ electron.ipcMain.handle("browser-set-chord-focus", (event, focused) => {
33541
+ const host = authorize(event);
33542
+ if (typeof focused !== "boolean")
33543
+ throw new Error("Browser focus must be a boolean.");
33544
+ host.setChordFocus(focused);
33545
+ });
33546
+ electron.ipcMain.handle(
33547
+ "browser-reopen-tab",
33548
+ (event, sessionId2) => authorize(event).reopenClosed(optionalConversationOrThrow(sessionId2))
33549
+ );
33550
+ electron.ipcMain.handle("browser-focus-page", (event) => {
33551
+ authorize(event);
33552
+ options.focusPage?.();
33553
+ });
33554
+ electron.ipcMain.handle("browser-reload", (event, hard) => {
33555
+ const host = authorize(event);
33556
+ if (hard !== void 0 && typeof hard !== "boolean")
33557
+ throw new Error("Hard reload must be a boolean.");
33558
+ return host.reloadActive(hard === true);
33559
+ });
32717
33560
  electron.ipcMain.handle("browser-stop", (event) => authorize(event).stopActive());
32718
33561
  electron.ipcMain.handle("browser-history", (event, direction) => {
32719
33562
  const host = authorize(event);
@@ -32798,11 +33641,12 @@ function registerBrowserIpc(options) {
32798
33641
  return host.forgetSite(origin.trim());
32799
33642
  });
32800
33643
  electron.ipcMain.handle("browser-clear-data", async (event, what) => {
32801
- authorize(event);
33644
+ const host = authorize(event);
32802
33645
  if (what !== "cookies" && what !== "cache" && what !== "everything") {
32803
33646
  throw new Error("Unsupported clear-data request.");
32804
33647
  }
32805
33648
  await options.clearData(what);
33649
+ if (what === "everything") host.forgetClosedTabs();
32806
33650
  options.log(`[browser] cleared browsing data: ${what}`);
32807
33651
  return { cleared: what };
32808
33652
  });
@@ -34595,6 +35439,22 @@ function refusesNavigation(rawUrl) {
34595
35439
  }
34596
35440
  }
34597
35441
  let activeHost = null;
35442
+ function routeBrowserZoomInput(event, input) {
35443
+ const candidate = browserChordIntent(
35444
+ { ...input, isAutoRepeat: false, isComposing: false },
35445
+ process.platform
35446
+ );
35447
+ if (!candidate?.startsWith("zoom-") || !activeHost) return false;
35448
+ return activeHost.chords.input(
35449
+ event,
35450
+ input,
35451
+ "chrome",
35452
+ activeHost.host.chordFocus
35453
+ );
35454
+ }
35455
+ function routeBrowserMenu(intent) {
35456
+ return activeHost?.chords.menu(intent) ?? false;
35457
+ }
34598
35458
  let stopping = null;
34599
35459
  function browserHostStopPending() {
34600
35460
  return activeHost !== null || stopping !== null || starting !== null;
@@ -34662,6 +35522,12 @@ function startBrowserHost(options) {
34662
35522
  expectedUrl: options.expectedUrl,
34663
35523
  host: () => bindings.host?.() ?? null,
34664
35524
  webauthn: () => bindings.webauthn?.() ?? null,
35525
+ focusPage: () => {
35526
+ const record = bindings.host?.()?.registry.activeTab;
35527
+ const target2 = record?.presented ? electron.webContents.fromId(record.view.webContents.id) : null;
35528
+ if (target2)
35529
+ focusBrowserChordTarget(options.window, target2, options.windowShow);
35530
+ },
34665
35531
  availability: () => availability,
34666
35532
  clearData: async (what) => {
34667
35533
  if (!bindings.clearData)
@@ -34741,6 +35607,8 @@ async function initializeBrowserHost(options, bindings) {
34741
35607
  // No `preload`: a driven page has no bridge to anything.
34742
35608
  }
34743
35609
  });
35610
+ view.webContents.setZoomMode("isolated");
35611
+ view.webContents.setZoomFactor(1);
34744
35612
  window.contentView.addChildView(view);
34745
35613
  view.setBounds(BACKGROUND_VIEWPORT);
34746
35614
  view.setVisible(false);
@@ -34818,10 +35686,10 @@ async function initializeBrowserHost(options, bindings) {
34818
35686
  contents.on("will-navigate", onWillNavigate);
34819
35687
  contents.on("will-redirect", onWillNavigate);
34820
35688
  contents.on("before-input-event", (event, input) => {
34821
- const direction = paneNavigationDirection(input);
34822
- if (!direction) return;
34823
- event.preventDefault();
34824
- host.historyActive(direction);
35689
+ const record = registry.get(tabId);
35690
+ const owns = record === registry.activeTab && record?.presented === true;
35691
+ const handled = chords.input(event, input, "page", owns);
35692
+ contents.setIgnoreMenuShortcuts(handled);
34825
35693
  });
34826
35694
  const notifyChrome = () => {
34827
35695
  options.window.webContents.send("browser-state-changed");
@@ -35125,6 +35993,75 @@ async function initializeBrowserHost(options, bindings) {
35125
35993
  facts: () => facts,
35126
35994
  forgetSiteData: (origin) => clearOriginData(browserSession, origin)
35127
35995
  });
35996
+ const chords = createBrowserChordRouter({
35997
+ platform: process.platform,
35998
+ ownsChrome: () => host.chordFocus,
35999
+ ownsPage: () => {
36000
+ const record = registry.activeTab;
36001
+ return Boolean(
36002
+ record?.presented && electron.webContents.getFocusedWebContents()?.id === record.view.webContents.id
36003
+ );
36004
+ },
36005
+ reload: (hard) => {
36006
+ host.reloadActive(hard);
36007
+ },
36008
+ zoom: (command) => {
36009
+ const tabId = registry.activeTab?.tabId;
36010
+ if (tabId !== void 0)
36011
+ void host.controlPage(tabId, command).catch(
36012
+ (error) => log2(
36013
+ `Page zoom refused: ${error instanceof Error ? error.message : "unavailable"}`
36014
+ )
36015
+ );
36016
+ },
36017
+ history: (direction) => {
36018
+ host.historyActive(direction);
36019
+ },
36020
+ send: (message) => options.window.webContents.send("browser-chord", message),
36021
+ focusChrome: () => focusBrowserChordTarget(
36022
+ options.window,
36023
+ options.window.webContents,
36024
+ options.windowShow
36025
+ )
36026
+ });
36027
+ const onChromeInput = (event, input) => {
36028
+ if (event.defaultPrevented) return;
36029
+ const handled = chords.input(event, input, "chrome", host.chordFocus);
36030
+ options.window.webContents.setIgnoreMenuShortcuts(handled);
36031
+ };
36032
+ const resetChordFocus = () => {
36033
+ host.setChordFocus(false);
36034
+ };
36035
+ host.onChordFocusChanged = (focused) => {
36036
+ if (!focused && !options.window.webContents.isDestroyed())
36037
+ options.window.webContents.setIgnoreMenuShortcuts(false);
36038
+ };
36039
+ options.window.webContents.on("before-input-event", onChromeInput);
36040
+ options.window.on("blur", resetChordFocus);
36041
+ options.window.on("closed", resetChordFocus);
36042
+ const resetOnNavigation = (details) => {
36043
+ if (details.isMainFrame && !details.isSameDocument) resetChordFocus();
36044
+ };
36045
+ options.window.webContents.on("did-start-navigation", resetOnNavigation);
36046
+ options.window.webContents.on("render-process-gone", resetChordFocus);
36047
+ const detachChords = () => {
36048
+ host.clearChordState();
36049
+ options.window.webContents.removeListener(
36050
+ "before-input-event",
36051
+ onChromeInput
36052
+ );
36053
+ options.window.removeListener("blur", resetChordFocus);
36054
+ options.window.removeListener("closed", resetChordFocus);
36055
+ options.window.webContents.removeListener(
36056
+ "did-start-navigation",
36057
+ resetOnNavigation
36058
+ );
36059
+ options.window.webContents.removeListener(
36060
+ "render-process-gone",
36061
+ resetChordFocus
36062
+ );
36063
+ };
36064
+ rollback.add(detachChords);
35128
36065
  const recorded = readSession(sessionStore.filePath, log2);
35129
36066
  host.restoreTabs(recorded);
35130
36067
  if (recorded.length) {
@@ -35240,12 +36177,14 @@ async function initializeBrowserHost(options, bindings) {
35240
36177
  `[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
36178
  );
35242
36179
  const handle2 = {
36180
+ chords,
35243
36181
  host,
35244
36182
  port: server.port,
35245
36183
  profileDir,
35246
36184
  agentTabs: () => registry.agentTabCount(),
35247
36185
  sessionCookies: () => restoreReport,
35248
36186
  stop: async () => {
36187
+ detachChords();
35249
36188
  const atStop = captureTabs(
35250
36189
  registry.list(),
35251
36190
  registry.activeTab?.tabId ?? null,
@@ -45492,25 +46431,6 @@ function configureAboutPanel() {
45492
46431
  }
45493
46432
  function createApplicationMenu() {
45494
46433
  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
46434
  const template = [
45515
46435
  {
45516
46436
  label: "File",
@@ -45534,29 +46454,62 @@ function createApplicationMenu() {
45534
46454
  label: "View",
45535
46455
  submenu: [
45536
46456
  {
45537
- role: "reload",
45538
- accelerator: "CmdOrCtrl+R"
46457
+ id: "app-reload",
46458
+ label: "Reload",
46459
+ accelerator: "CmdOrCtrl+R",
46460
+ click: (_item, target2) => runBrowserMenuAction(
46461
+ "reload",
46462
+ target2,
46463
+ mainWindow,
46464
+ routeBrowserMenu
46465
+ )
45539
46466
  },
45540
46467
  {
45541
- role: "forceReload",
45542
- accelerator: "CmdOrCtrl+Shift+R"
46468
+ id: "app-force-reload",
46469
+ label: "Force Reload",
46470
+ accelerator: "CmdOrCtrl+Shift+R",
46471
+ click: (_item, target2) => runBrowserMenuAction(
46472
+ "hard-reload",
46473
+ target2,
46474
+ mainWindow,
46475
+ routeBrowserMenu
46476
+ )
45543
46477
  },
45544
46478
  { type: "separator" },
45545
46479
  {
46480
+ id: "app-actual-size",
45546
46481
  label: "Actual Size",
45547
- accelerator: "CmdOrCtrl+O",
45548
- click: actualSizeHandler
46482
+ accelerator: "CmdOrCtrl+0",
46483
+ click: (_item, target2) => {
46484
+ if (!(target2 instanceof electron.BrowserWindow)) return;
46485
+ if (target2 === mainWindow && routeBrowserMenu("zoom-reset")) return;
46486
+ target2.webContents.setZoomFactor(1);
46487
+ }
45549
46488
  },
45550
46489
  {
46490
+ id: "app-zoom-in",
45551
46491
  label: "Zoom In",
45552
46492
  accelerator: "CmdOrCtrl+Plus",
45553
46493
  // On macOS, this often requires Shift as well (Cmd+Shift+=)
45554
- click: zoomInHandler
46494
+ click: (_item, target2) => {
46495
+ if (!(target2 instanceof electron.BrowserWindow)) return;
46496
+ if (target2 === mainWindow && routeBrowserMenu("zoom-in")) return;
46497
+ target2.webContents.setZoomFactor(
46498
+ target2.webContents.getZoomFactor() + 0.1
46499
+ );
46500
+ }
45555
46501
  },
45556
46502
  {
46503
+ id: "app-zoom-out",
45557
46504
  label: "Zoom Out",
45558
46505
  accelerator: "CmdOrCtrl+-",
45559
- click: zoomOutHandler
46506
+ click: (_item, target2) => {
46507
+ if (!(target2 instanceof electron.BrowserWindow)) return;
46508
+ if (target2 === mainWindow && routeBrowserMenu("zoom-out")) return;
46509
+ target2.webContents.setZoomFactor(
46510
+ target2.webContents.getZoomFactor() - 0.1
46511
+ );
46512
+ }
45560
46513
  },
45561
46514
  { type: "separator" },
45562
46515
  { role: "togglefullscreen" }
@@ -45582,10 +46535,40 @@ function createApplicationMenu() {
45582
46535
  * `window-all-closed` path the red button takes, which is the one
45583
46536
  * this change made safe for an install in flight.
45584
46537
  */
45585
- { role: "close" },
46538
+ {
46539
+ id: "app-close",
46540
+ // One honest label for both owners: the SAME accelerator closes a
46541
+ // browser TAB while the browser has focus and the WINDOW elsewhere,
46542
+ // and a menu label cannot follow focus that lives in a page view
46543
+ // (UX r1 U4). The roles it replaced were "Close Window"/"Close".
46544
+ label: "Close Tab or Window",
46545
+ accelerator: "CmdOrCtrl+W",
46546
+ click: (_item, target2) => runBrowserMenuAction(
46547
+ "close-tab",
46548
+ target2,
46549
+ mainWindow,
46550
+ routeBrowserMenu
46551
+ )
46552
+ },
45586
46553
  { type: "separator" },
45587
46554
  { role: "window" }
45588
- ] : [{ role: "close" }]
46555
+ ] : [
46556
+ {
46557
+ id: "app-close",
46558
+ // One honest label for both owners: the SAME accelerator closes a
46559
+ // browser TAB while the browser has focus and the WINDOW elsewhere,
46560
+ // and a menu label cannot follow focus that lives in a page view
46561
+ // (UX r1 U4). The roles it replaced were "Close Window"/"Close".
46562
+ label: "Close Tab or Window",
46563
+ accelerator: "CmdOrCtrl+W",
46564
+ click: (_item, target2) => runBrowserMenuAction(
46565
+ "close-tab",
46566
+ target2,
46567
+ mainWindow,
46568
+ routeBrowserMenu
46569
+ )
46570
+ }
46571
+ ]
45589
46572
  ]
45590
46573
  },
45591
46574
  {
@@ -46966,14 +47949,16 @@ electron.app.whenReady().then(async () => {
46966
47949
  if (mainWindow) {
46967
47950
  mainWindow.webContents.on("before-input-event", (event, input) => {
46968
47951
  const isCmdOrCtrl = input.control || input.meta;
46969
- if (isCmdOrCtrl) {
47952
+ if (event.defaultPrevented) return;
47953
+ if (routeBrowserZoomInput(event, input)) return;
47954
+ if (isCmdOrCtrl && input.type === "keyDown" && !input.alt && !input.isComposing) {
46970
47955
  if (input.key === "+" || input.key === "=") {
46971
47956
  zoomInFromEvent();
46972
47957
  event.preventDefault();
46973
47958
  } else if (input.key === "-") {
46974
47959
  zoomOutFromEvent();
46975
47960
  event.preventDefault();
46976
- } else if (input.key === "O") {
47961
+ } else if (input.key === "0") {
46977
47962
  actualSizeFromEvent();
46978
47963
  event.preventDefault();
46979
47964
  }