@kenkaiiii/gg-editor 0.6.2 → 0.6.4

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 (114) hide show
  1. package/README.md +5 -1
  2. package/dist/core/doctor-runner.js +45 -11
  3. package/dist/core/doctor-runner.js.map +1 -1
  4. package/dist/core/hosts/lazy.d.ts +5 -5
  5. package/dist/core/hosts/lazy.d.ts.map +1 -1
  6. package/dist/core/hosts/lazy.js +16 -0
  7. package/dist/core/hosts/lazy.js.map +1 -1
  8. package/dist/core/hosts/premiere/adapter.d.ts +4 -1
  9. package/dist/core/hosts/premiere/adapter.d.ts.map +1 -1
  10. package/dist/core/hosts/premiere/adapter.js +6 -5
  11. package/dist/core/hosts/premiere/adapter.js.map +1 -1
  12. package/dist/core/hosts/premiere/bridge-source.d.ts.map +1 -1
  13. package/dist/core/hosts/premiere/bridge-source.js +45 -0
  14. package/dist/core/hosts/premiere/bridge-source.js.map +1 -1
  15. package/dist/core/hosts/resolve/adapter.d.ts +6 -2
  16. package/dist/core/hosts/resolve/adapter.d.ts.map +1 -1
  17. package/dist/core/hosts/resolve/adapter.js +13 -5
  18. package/dist/core/hosts/resolve/adapter.js.map +1 -1
  19. package/dist/core/hosts/resolve/bridge-source.d.ts.map +1 -1
  20. package/dist/core/hosts/resolve/bridge-source.js +376 -2
  21. package/dist/core/hosts/resolve/bridge-source.js.map +1 -1
  22. package/dist/core/hosts/resolve/bridge.d.ts +22 -4
  23. package/dist/core/hosts/resolve/bridge.d.ts.map +1 -1
  24. package/dist/core/hosts/resolve/bridge.integration.test.js +338 -0
  25. package/dist/core/hosts/resolve/bridge.integration.test.js.map +1 -1
  26. package/dist/core/hosts/resolve/bridge.js +56 -6
  27. package/dist/core/hosts/resolve/bridge.js.map +1 -1
  28. package/dist/core/hosts/resolve/bridge.test.js +61 -1
  29. package/dist/core/hosts/resolve/bridge.test.js.map +1 -1
  30. package/dist/core/hosts/types.d.ts +81 -0
  31. package/dist/core/hosts/types.d.ts.map +1 -1
  32. package/dist/core/hosts/types.js.map +1 -1
  33. package/dist/core/safe-paths.d.ts +47 -0
  34. package/dist/core/safe-paths.d.ts.map +1 -0
  35. package/dist/core/safe-paths.js +102 -0
  36. package/dist/core/safe-paths.js.map +1 -0
  37. package/dist/core/safe-paths.test.d.ts +2 -0
  38. package/dist/core/safe-paths.test.d.ts.map +1 -0
  39. package/dist/core/safe-paths.test.js +76 -0
  40. package/dist/core/safe-paths.test.js.map +1 -0
  41. package/dist/skills.d.ts.map +1 -1
  42. package/dist/skills.js +125 -0
  43. package/dist/skills.js.map +1 -1
  44. package/dist/system-prompt.d.ts.map +1 -1
  45. package/dist/system-prompt.js +33 -1
  46. package/dist/system-prompt.js.map +1 -1
  47. package/dist/tools/compose-layered.d.ts.map +1 -1
  48. package/dist/tools/compose-layered.js +2 -1
  49. package/dist/tools/compose-layered.js.map +1 -1
  50. package/dist/tools/compose-thumbnail.d.ts.map +1 -1
  51. package/dist/tools/compose-thumbnail.js +10 -2
  52. package/dist/tools/compose-thumbnail.js.map +1 -1
  53. package/dist/tools/concat-videos.d.ts.map +1 -1
  54. package/dist/tools/concat-videos.js +2 -1
  55. package/dist/tools/concat-videos.js.map +1 -1
  56. package/dist/tools/cut-filler-words.d.ts.map +1 -1
  57. package/dist/tools/cut-filler-words.js +2 -1
  58. package/dist/tools/cut-filler-words.js.map +1 -1
  59. package/dist/tools/extract-frame.d.ts.map +1 -1
  60. package/dist/tools/extract-frame.js +9 -2
  61. package/dist/tools/extract-frame.js.map +1 -1
  62. package/dist/tools/fusion-comp.d.ts +49 -0
  63. package/dist/tools/fusion-comp.d.ts.map +1 -0
  64. package/dist/tools/fusion-comp.js +129 -0
  65. package/dist/tools/fusion-comp.js.map +1 -0
  66. package/dist/tools/generate-gif.d.ts.map +1 -1
  67. package/dist/tools/generate-gif.js +12 -2
  68. package/dist/tools/generate-gif.js.map +1 -1
  69. package/dist/tools/host-eval.d.ts +35 -0
  70. package/dist/tools/host-eval.d.ts.map +1 -0
  71. package/dist/tools/host-eval.js +70 -0
  72. package/dist/tools/host-eval.js.map +1 -0
  73. package/dist/tools/host-tools.test.js +42 -0
  74. package/dist/tools/host-tools.test.js.map +1 -1
  75. package/dist/tools/index.d.ts +2 -0
  76. package/dist/tools/index.d.ts.map +1 -1
  77. package/dist/tools/index.js +9 -0
  78. package/dist/tools/index.js.map +1 -1
  79. package/dist/tools/index.test.js +4 -0
  80. package/dist/tools/index.test.js.map +1 -1
  81. package/dist/tools/overlay-watermark.d.ts.map +1 -1
  82. package/dist/tools/overlay-watermark.js +2 -1
  83. package/dist/tools/overlay-watermark.js.map +1 -1
  84. package/dist/tools/reformat-timeline.d.ts.map +1 -1
  85. package/dist/tools/reformat-timeline.js +3 -2
  86. package/dist/tools/reformat-timeline.js.map +1 -1
  87. package/dist/tools/reorder-timeline.d.ts.map +1 -1
  88. package/dist/tools/reorder-timeline.js +3 -2
  89. package/dist/tools/reorder-timeline.js.map +1 -1
  90. package/dist/tools/transcribe.d.ts.map +1 -1
  91. package/dist/tools/transcribe.js +2 -1
  92. package/dist/tools/transcribe.js.map +1 -1
  93. package/dist/tools/write-ass.d.ts.map +1 -1
  94. package/dist/tools/write-ass.js +3 -2
  95. package/dist/tools/write-ass.js.map +1 -1
  96. package/dist/tools/write-edl.d.ts.map +1 -1
  97. package/dist/tools/write-edl.js +3 -2
  98. package/dist/tools/write-edl.js.map +1 -1
  99. package/dist/tools/write-fcpxml.d.ts.map +1 -1
  100. package/dist/tools/write-fcpxml.js +3 -2
  101. package/dist/tools/write-fcpxml.js.map +1 -1
  102. package/dist/tools/write-keyword-captions.d.ts.map +1 -1
  103. package/dist/tools/write-keyword-captions.js +2 -1
  104. package/dist/tools/write-keyword-captions.js.map +1 -1
  105. package/dist/tools/write-lower-third.d.ts.map +1 -1
  106. package/dist/tools/write-lower-third.js +3 -2
  107. package/dist/tools/write-lower-third.js.map +1 -1
  108. package/dist/tools/write-srt.d.ts.map +1 -1
  109. package/dist/tools/write-srt.js +3 -2
  110. package/dist/tools/write-srt.js.map +1 -1
  111. package/dist/tools/write-title-card.d.ts.map +1 -1
  112. package/dist/tools/write-title-card.js +3 -2
  113. package/dist/tools/write-title-card.js.map +1 -1
  114. package/package.json +3 -3
@@ -355,6 +355,23 @@ def _fmt3(triple):
355
355
  return "%g %g %g" % (float(triple[0]), float(triple[1]), float(triple[2]))
356
356
 
357
357
 
358
+ def _ensure_color_page():
359
+ """Best-effort switch to the Color page when the current page isn't already
360
+ color. Returns True if we issued an OpenPage call, False otherwise. Used by
361
+ SetCDL / CopyGrades retry logic — those API calls silently no-op on the
362
+ wrong page even though the values land in the data model.
363
+ """
364
+ try:
365
+ r = get_resolve()
366
+ cur = r.GetCurrentPage() if hasattr(r, "GetCurrentPage") else None
367
+ if cur == "color":
368
+ return False
369
+ r.OpenPage("color")
370
+ return True
371
+ except Exception:
372
+ return False
373
+
374
+
358
375
  def m_set_primary_correction(params):
359
376
  clip_id = str(params["clipId"])
360
377
  node_index = int(params.get("nodeIndex", 1) or 1)
@@ -371,10 +388,16 @@ def m_set_primary_correction(params):
371
388
  if params.get("saturation") is not None:
372
389
  cdl["Saturation"] = "%g" % float(params["saturation"])
373
390
  ok = item.SetCDL(cdl)
391
+ if not ok:
392
+ # Common cause: agent set CDL while user was on another page. Auto-
393
+ # switch to Color and retry once. If it fails again, the user/clip is
394
+ # genuinely the problem — surface the original message.
395
+ if _ensure_color_page():
396
+ ok = item.SetCDL(cdl)
374
397
  if not ok:
375
398
  raise RuntimeError(
376
399
  f"SetCDL returned False for node {node_index}. "
377
- f"fix: open the Color page (open_page('color')) and retry."
400
+ f"fix: ensure the clip exists and the project's color page is reachable."
378
401
  )
379
402
  return {"clipId": clip_id, "nodeIndex": node_index}
380
403
 
@@ -389,9 +412,12 @@ def m_copy_grade(params):
389
412
  if not hasattr(src, "CopyGrades"):
390
413
  raise RuntimeError("this Resolve version's TimelineItem has no CopyGrades")
391
414
  ok = src.CopyGrades(targets)
415
+ if not ok:
416
+ if _ensure_color_page():
417
+ ok = src.CopyGrades(targets)
392
418
  if not ok:
393
419
  raise RuntimeError(
394
- "CopyGrades returned False. fix: open the Color page (open_page('color')) and retry."
420
+ "CopyGrades returned False. fix: ensure source + targets are valid timeline items."
395
421
  )
396
422
  return {"sourceClipId": source_id, "copied": len(targets)}
397
423
 
@@ -592,6 +618,352 @@ def m_import_subtitles(params):
592
618
  return {"imported": True, "attached": True}
593
619
 
594
620
 
621
+ # ── Fusion ──────────────────────────────────────────────────
622
+ #
623
+ # Fusion compositions live either at the timeline-clip level (every
624
+ # TimelineItem can carry one or more 'Fusion comps') OR at the project level
625
+ # via the standalone Fusion page. The agent operates on whichever the user
626
+ # is currently focused on — we resolve the comp once per call:
627
+ #
628
+ # - clipId given: item.GetFusionCompByIndex(1) on that timeline item.
629
+ # - no clipId: fusion.GetCurrentComp() on the active Fusion page.
630
+ #
631
+ # We deliberately keep the surface narrow (8 actions, the Fusion-page tools
632
+ # every short-form workflow needs) instead of mirroring the entire Fusion
633
+ # Python API. Power users have m_execute_code for the rest.
634
+
635
+
636
+ def _resolve_fusion_comp(clip_id):
637
+ if clip_id:
638
+ item = _find_timeline_item(clip_id)
639
+ if not hasattr(item, "GetFusionCompCount") or not hasattr(item, "GetFusionCompByIndex"):
640
+ raise RuntimeError("this Resolve version's TimelineItem has no Fusion comp accessors")
641
+ count = int(item.GetFusionCompCount() or 0)
642
+ if count < 1:
643
+ # Try to add one. Modern Resolve exposes AddFusionComp; older
644
+ # builds need the user to do it manually on the Fusion page.
645
+ if hasattr(item, "AddFusionComp"):
646
+ comp = item.AddFusionComp()
647
+ if comp is None:
648
+ raise RuntimeError(f"AddFusionComp returned None for clip {clip_id}")
649
+ return comp
650
+ raise RuntimeError(
651
+ f"clip {clip_id} has no Fusion comp; create one in Resolve (right-click → New Fusion Composition) first."
652
+ )
653
+ comp = item.GetFusionCompByIndex(1)
654
+ if comp is None:
655
+ raise RuntimeError(f"GetFusionCompByIndex(1) returned None for clip {clip_id}")
656
+ return comp
657
+ # No clip — use the active Fusion page comp.
658
+ r = get_resolve()
659
+ try:
660
+ fusion = r.Fusion()
661
+ except Exception as e:
662
+ raise RuntimeError(f"Resolve.Fusion() unavailable: {e}")
663
+ if fusion is None:
664
+ raise RuntimeError("Fusion is not available in this Resolve build")
665
+ comp = fusion.GetCurrentComp() if hasattr(fusion, "GetCurrentComp") else None
666
+ if comp is None:
667
+ raise RuntimeError(
668
+ "No active Fusion comp. fix: switch to the Fusion page (open_page('fusion')) on a clip with a comp, or pass clipId."
669
+ )
670
+ return comp
671
+
672
+
673
+ def _fusion_node_summary(node):
674
+ # Best-effort, never throws — the caller is reading a list of nodes.
675
+ try:
676
+ name = node.GetAttrs("TOOLS_Name") if hasattr(node, "GetAttrs") else None
677
+ except Exception:
678
+ name = None
679
+ try:
680
+ toolid = node.GetAttrs("TOOLS_RegID") if hasattr(node, "GetAttrs") else None
681
+ except Exception:
682
+ toolid = None
683
+ if not name and hasattr(node, "Name"):
684
+ try:
685
+ name = node.Name
686
+ except Exception:
687
+ pass
688
+ if not toolid and hasattr(node, "ID"):
689
+ try:
690
+ toolid = node.ID
691
+ except Exception:
692
+ pass
693
+ return {"name": name, "toolId": toolid}
694
+
695
+
696
+ def m_fusion_comp(params):
697
+ action = str(params.get("action") or "")
698
+ if not action:
699
+ raise RuntimeError("fusion_comp requires an 'action' field")
700
+ clip_id = params.get("clipId")
701
+ comp = _resolve_fusion_comp(str(clip_id) if clip_id else None)
702
+
703
+ if action == "list_nodes":
704
+ nodes = []
705
+ try:
706
+ tool_list = comp.GetToolList(False) or {}
707
+ except Exception as e:
708
+ raise RuntimeError(f"GetToolList failed: {e}")
709
+ # GetToolList returns a 1-indexed dict on some builds; iterate values.
710
+ try:
711
+ iterator = tool_list.values() if hasattr(tool_list, "values") else list(tool_list)
712
+ except Exception:
713
+ iterator = []
714
+ for node in iterator:
715
+ nodes.append(_fusion_node_summary(node))
716
+ return {"nodes": nodes, "count": len(nodes)}
717
+
718
+ if action == "add_node":
719
+ tool_id = str(params.get("toolId") or "")
720
+ if not tool_id:
721
+ raise RuntimeError("add_node requires 'toolId' (e.g. 'TextPlus', 'Background', 'Merge')")
722
+ if not hasattr(comp, "AddTool"):
723
+ raise RuntimeError("this Resolve build's Comp has no AddTool")
724
+ node = comp.AddTool(tool_id)
725
+ if node is None:
726
+ raise RuntimeError(f"AddTool('{tool_id}') returned None; verify the tool ID exists")
727
+ wanted_name = params.get("name")
728
+ if wanted_name and hasattr(node, "SetAttrs"):
729
+ try:
730
+ node.SetAttrs({"TOOLS_Name": str(wanted_name)})
731
+ except Exception:
732
+ pass
733
+ return _fusion_node_summary(node)
734
+
735
+ if action == "delete_node":
736
+ name = str(params.get("name") or "")
737
+ if not name:
738
+ raise RuntimeError("delete_node requires 'name'")
739
+ node = comp.FindTool(name) if hasattr(comp, "FindTool") else None
740
+ if node is None:
741
+ raise RuntimeError(f"node not found: {name}")
742
+ if not hasattr(node, "Delete"):
743
+ raise RuntimeError("this Resolve build's Tool has no Delete")
744
+ node.Delete()
745
+ return {"deleted": name}
746
+
747
+ if action == "connect":
748
+ from_name = str(params.get("fromNode") or "")
749
+ to_name = str(params.get("toNode") or "")
750
+ from_output = str(params.get("fromOutput") or "Output")
751
+ to_input = str(params.get("toInput") or "Input")
752
+ if not from_name or not to_name:
753
+ raise RuntimeError("connect requires 'fromNode' and 'toNode'")
754
+ a = comp.FindTool(from_name)
755
+ b = comp.FindTool(to_name)
756
+ if a is None or b is None:
757
+ raise RuntimeError(f"node not found (from={from_name}, to={to_name})")
758
+ try:
759
+ out = a.FindMainOutput(1) if from_output == "Output" else getattr(a, from_output, None)
760
+ except Exception:
761
+ out = None
762
+ if out is None:
763
+ raise RuntimeError(f"output '{from_output}' not found on {from_name}")
764
+ try:
765
+ inp = getattr(b, to_input, None)
766
+ if inp is None and hasattr(b, "FindMainInput"):
767
+ inp = b.FindMainInput(1)
768
+ except Exception:
769
+ inp = None
770
+ if inp is None:
771
+ raise RuntimeError(f"input '{to_input}' not found on {to_name}")
772
+ try:
773
+ inp.ConnectTo(out)
774
+ except Exception as e:
775
+ raise RuntimeError(f"ConnectTo failed: {e}")
776
+ return {"from": from_name, "to": to_name}
777
+
778
+ if action == "set_input":
779
+ name = str(params.get("node") or "")
780
+ input_name = str(params.get("input") or "")
781
+ if not name or not input_name:
782
+ raise RuntimeError("set_input requires 'node' and 'input'")
783
+ if "value" not in params:
784
+ raise RuntimeError("set_input requires 'value'")
785
+ node = comp.FindTool(name)
786
+ if node is None:
787
+ raise RuntimeError(f"node not found: {name}")
788
+ try:
789
+ attr = getattr(node, input_name)
790
+ except Exception:
791
+ raise RuntimeError(f"input '{input_name}' not found on {name}")
792
+ try:
793
+ # Fusion inputs accept assignment directly: node.Input = value.
794
+ setattr(node, input_name, params["value"])
795
+ except Exception as e:
796
+ raise RuntimeError(f"set_input failed: {e}")
797
+ return {"node": name, "input": input_name}
798
+
799
+ if action == "get_input":
800
+ name = str(params.get("node") or "")
801
+ input_name = str(params.get("input") or "")
802
+ if not name or not input_name:
803
+ raise RuntimeError("get_input requires 'node' and 'input'")
804
+ node = comp.FindTool(name)
805
+ if node is None:
806
+ raise RuntimeError(f"node not found: {name}")
807
+ try:
808
+ attr = getattr(node, input_name)
809
+ except Exception:
810
+ raise RuntimeError(f"input '{input_name}' not found on {name}")
811
+ value = None
812
+ try:
813
+ # Fusion inputs are callable for current-value access on some builds.
814
+ value = attr() if callable(attr) else attr
815
+ except Exception:
816
+ value = attr
817
+ try:
818
+ json.dumps(value)
819
+ except Exception:
820
+ value = repr(value)
821
+ return {"node": name, "input": input_name, "value": value}
822
+
823
+ if action == "set_keyframe":
824
+ name = str(params.get("node") or "")
825
+ input_name = str(params.get("input") or "")
826
+ frame = params.get("frame")
827
+ if frame is None or not name or not input_name or "value" not in params:
828
+ raise RuntimeError("set_keyframe requires 'node', 'input', 'frame', 'value'")
829
+ node = comp.FindTool(name)
830
+ if node is None:
831
+ raise RuntimeError(f"node not found: {name}")
832
+ try:
833
+ attr = getattr(node, input_name)
834
+ except Exception:
835
+ raise RuntimeError(f"input '{input_name}' not found on {name}")
836
+ try:
837
+ # Comp.SetKeyFrames is the canonical path; older builds use
838
+ # input.SetKeyFrames or .SetExpression. Try the modern one first.
839
+ ok = comp.SetKeyFrames({attr: {int(frame): params["value"]}})
840
+ except Exception:
841
+ ok = False
842
+ if not ok and hasattr(attr, "SetKeyFrames"):
843
+ try:
844
+ attr.SetKeyFrames({int(frame): params["value"]})
845
+ ok = True
846
+ except Exception:
847
+ ok = False
848
+ if not ok:
849
+ raise RuntimeError("could not set keyframe via Comp.SetKeyFrames or Input.SetKeyFrames")
850
+ return {"node": name, "input": input_name, "frame": int(frame)}
851
+
852
+ if action == "set_render_range":
853
+ start = params.get("start")
854
+ end = params.get("end")
855
+ if start is None or end is None:
856
+ raise RuntimeError("set_render_range requires 'start' and 'end'")
857
+ try:
858
+ comp.SetAttrs({"COMPN_RenderStart": int(start), "COMPN_RenderEnd": int(end)})
859
+ except Exception as e:
860
+ raise RuntimeError(f"SetAttrs failed: {e}")
861
+ return {"start": int(start), "end": int(end)}
862
+
863
+ raise RuntimeError(
864
+ f"unknown fusion_comp action: {action}; valid: list_nodes, add_node, delete_node, connect, set_input, get_input, set_keyframe, set_render_range"
865
+ )
866
+
867
+
868
+ def m_execute_code(params):
869
+ """Escape hatch: run arbitrary user code with the Resolve API pre-bound.
870
+
871
+ Pre-bound globals available to the snippet:
872
+ resolve, project, projectManager, mediaPool, mediaStorage, timeline,
873
+ fusion (lazy — None if Fusion comp is unavailable), dvr (the
874
+ DaVinciResolveScript module itself).
875
+
876
+ Result delivery (in priority order):
877
+ 1. Call set_result(value) — explicit, supports any JSON-serialisable value.
878
+ 2. Assign to a top-level 'result' variable.
879
+ 3. Otherwise: stdout text only.
880
+
881
+ stdout is captured and returned regardless. The snippet runs in the same
882
+ process as every other bridge call, so a crash here CAN take the bridge
883
+ down. We trap exceptions but a segfault in fusionscript.so cannot be caught.
884
+ """
885
+ code = params.get("code")
886
+ if not isinstance(code, str) or not code.strip():
887
+ raise RuntimeError("execute_code requires a non-empty 'code' string.")
888
+
889
+ import io
890
+ import contextlib
891
+
892
+ resolve = get_resolve()
893
+ project_manager = resolve.GetProjectManager()
894
+ project = project_manager.GetCurrentProject() if project_manager else None
895
+ media_pool = project.GetMediaPool() if project else None
896
+ media_storage = resolve.GetMediaStorage()
897
+ try:
898
+ timeline = project.GetCurrentTimeline() if project else None
899
+ except Exception:
900
+ timeline = None
901
+ try:
902
+ fusion = resolve.Fusion()
903
+ except Exception:
904
+ fusion = None
905
+
906
+ # JSON-serialisability check that won't crash on Resolve API objects (which
907
+ # are PyRemoteObject and not JSON-able). Anything non-serialisable is
908
+ # coerced to its repr so the agent at least sees what came back.
909
+ def _safe(value):
910
+ try:
911
+ json.dumps(value)
912
+ return value
913
+ except Exception:
914
+ return repr(value)
915
+
916
+ holder = {"result": None, "set": False}
917
+
918
+ def set_result(value):
919
+ holder["result"] = _safe(value)
920
+ holder["set"] = True
921
+
922
+ g = {
923
+ "__builtins__": __builtins__,
924
+ "resolve": resolve,
925
+ "project": project,
926
+ "projectManager": project_manager,
927
+ "mediaPool": media_pool,
928
+ "mediaStorage": media_storage,
929
+ "timeline": timeline,
930
+ "fusion": fusion,
931
+ "dvr": dvr,
932
+ "set_result": set_result,
933
+ "json": json,
934
+ }
935
+ l = {}
936
+
937
+ buf = io.StringIO()
938
+ with contextlib.redirect_stdout(buf):
939
+ try:
940
+ exec(code, g, l)
941
+ except Exception as ex:
942
+ # Re-raise with the captured stdout attached so the agent gets both
943
+ # the print() output that ran before the crash AND the error.
944
+ stdout = buf.getvalue()
945
+ tail = ("\nstdout: " + stdout[-1500:]) if stdout else ""
946
+ raise RuntimeError(f"{ex.__class__.__name__}: {ex}{tail}") from ex
947
+
948
+ stdout = buf.getvalue()
949
+
950
+ # Result resolution: explicit set_result wins; else top-level 'result'.
951
+ if holder["set"]:
952
+ result_value = holder["result"]
953
+ elif "result" in l:
954
+ result_value = _safe(l["result"])
955
+ else:
956
+ result_value = None
957
+
958
+ out = {"result": result_value}
959
+ if stdout:
960
+ # Cap stdout to keep tool output token-economical.
961
+ if len(stdout) > 4000:
962
+ stdout = stdout[:2000] + "\n…[truncated]…\n" + stdout[-1500:]
963
+ out["stdout"] = stdout
964
+ return out
965
+
966
+
595
967
  METHODS = {
596
968
  "ping": m_ping,
597
969
  "get_timeline": m_get_timeline,
@@ -616,6 +988,8 @@ METHODS = {
616
988
  "add_track": m_add_track,
617
989
  "set_clip_volume": m_set_clip_volume,
618
990
  "import_subtitles": m_import_subtitles,
991
+ "fusion_comp": m_fusion_comp,
992
+ "execute_code": m_execute_code,
619
993
  }
620
994
 
621
995
 
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-source.js","sourceRoot":"","sources":["../../../../src/core/hosts/resolve/bridge-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmoBlC,CAAC"}
1
+ {"version":3,"file":"bridge-source.js","sourceRoot":"","sources":["../../../../src/core/hosts/resolve/bridge-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAy/BlC,CAAC"}
@@ -8,9 +8,22 @@ export declare class ResolveBridge {
8
8
  private readyPromise?;
9
9
  private nextId;
10
10
  private bridgePath?;
11
+ /**
12
+ * The child process is dead when:
13
+ * - we never spawned it,
14
+ * - it exited naturally (exitCode !== null),
15
+ * - or it was signalled (signalCode !== null).
16
+ *
17
+ * Node's ChildProcess exposes both fields directly so we don't keep a
18
+ * parallel `dead` flag in sync — the standard idiom across real codebases
19
+ * (mastra, openclaw, AFFiNE, midscene, paseo, takt, rivet) reads them
20
+ * straight off the child.
21
+ */
22
+ private isChildDead;
11
23
  /**
12
24
  * Start the bridge. Idempotent — subsequent calls return the same readiness
13
- * promise.
25
+ * promise. After the bridge dies (Resolve quit, Python crash), the next
26
+ * call respawns from scratch.
14
27
  */
15
28
  ensureStarted(): Promise<void>;
16
29
  private handshakeDone;
@@ -25,9 +38,15 @@ export declare class ResolveBridge {
25
38
  kill(): void;
26
39
  get scriptPath(): string | undefined;
27
40
  }
28
- interface PythonCmd {
41
+ export interface PythonCmd {
29
42
  cmd: string;
30
43
  args: string[];
44
+ /**
45
+ * `sys.prefix` of the interpreter, when probing succeeded. Used on Windows
46
+ * to set PYTHONHOME — multi-Python systems need it set or Resolve 20.3 can
47
+ * crash on bridge startup (samuelgursky/davinci-resolve-mcp #26).
48
+ */
49
+ prefix?: string;
31
50
  }
32
51
  /**
33
52
  * Probe for a working Python 3 interpreter. Order matters:
@@ -46,6 +65,5 @@ export declare function findPython(): PythonCmd | undefined;
46
65
  * Honours pre-set env vars (so power users with custom installs aren't
47
66
  * overridden).
48
67
  */
49
- export declare function resolveEnv(): NodeJS.ProcessEnv;
50
- export {};
68
+ export declare function resolveEnv(py?: PythonCmd): NodeJS.ProcessEnv;
51
69
  //# sourceMappingURL=bridge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../../src/core/hosts/resolve/bridge.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAC,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,UAAU,CAAC,CAAS;IAE5B;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAsF9B,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,UAAU;IAoClB;;;OAGG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAClC,OAAO,CAAC,CAAC,CAAC;IA8Cb,IAAI,IAAI,IAAI;IAYZ,IAAI,UAAU,IAAI,MAAM,GAAG,SAAS,CAEnC;CACF;AAID,UAAU,SAAS;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,SAAS,GAAG,SAAS,CAkBlD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,UAAU,CAwB9C"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../../src/core/hosts/resolve/bridge.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAC,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,UAAU,CAAC,CAAS;IAE5B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,WAAW;IAKnB;;;;OAIG;IACH,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAgG9B,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,UAAU;IAoClB;;;OAGG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAClC,OAAO,CAAC,CAAC,CAAC;IAiDb,IAAI,IAAI,IAAI;IAeZ,IAAI,UAAU,IAAI,MAAM,GAAG,SAAS,CAEnC;CACF;AAID,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,SAAS,GAAG,SAAS,CA4BlD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAAE,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC,UAAU,CAkC5D"}