feat(artifact-presenter): open text files in Orca
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: artifact-presenter
|
||||
description: Present local artifacts for review: use Browser Harness for interactive web artifacts, especially HTML, and mcat for non-web files, Markdown, and static source.
|
||||
description: Present local artifacts for review: use Browser Harness for interactive web artifacts, Orca for editor-friendly text in Orca-managed sessions, and mcat for other non-web files.
|
||||
---
|
||||
|
||||
# Artifact presenter
|
||||
@@ -26,7 +26,7 @@ Keep ordinary explanations, code diffs, logs, multi-file comparisons, and user-r
|
||||
Choose the presentation path:
|
||||
|
||||
- **Interactive web artifacts, especially `.html`:** use the Browser Harness/Browser Use-backed workflow.
|
||||
- **Non-web files, Markdown, and static source:** use mcat in the reusable Herdr pane.
|
||||
- **Non-web artifacts:** run this skill's presenter helper. It opens editor-friendly UTF-8 text directly in Orca when the harness is running in an Orca-managed terminal; otherwise it uses mcat in the reusable Herdr pane. Binary and media files stay on the mcat path.
|
||||
|
||||
Completion criterion: select exactly one existing or generated artifact path before presentation.
|
||||
|
||||
@@ -46,7 +46,7 @@ Load and follow the [`browser-harness`](../browser-harness/SKILL.md) skill. Open
|
||||
|
||||
Completion criterion: the active browser target reports the local artifact URL and page title.
|
||||
|
||||
## Present non-web artifacts with mcat
|
||||
## Present non-web artifacts
|
||||
|
||||
Run the helper using its absolute path resolved from this skill directory:
|
||||
|
||||
@@ -54,9 +54,11 @@ Run the helper using its absolute path resolved from this skill directory:
|
||||
python3 <skill-directory>/scripts/present.py <artifact-path>
|
||||
```
|
||||
|
||||
The helper reuses the pane labeled `artifact presenter` in the current Herdr workspace. It serializes pane discovery, creation, and replacement per workspace, with an additional per-pane lock during viewer handoff. When that pane is already displaying an artifact, it closes the active `mcat` viewer and waits for the pane shell before launching the replacement through a shell-neutral helper command with encoded path arguments. It confirms the helper started the viewer rather than trusting terminal input delivery alone. It leaves unrelated foreground programs untouched and creates a new viewer pane instead. When no reusable pane exists, it creates a right split without taking focus. It launches `mcat <artifact-path>` so mcat can infer and display documents, source, archives, PDFs, images, and videos. It falls back to artifact-only delivery when Herdr or mcat is unavailable.
|
||||
The helper detects an Orca-managed harness from Orca's terminal environment markers (`TERM_PROGRAM=Orca` or scoped `ORCA_*` workspace, worktree, pane, or terminal IDs). For editor-friendly UTF-8 text, it resolves the session's version-appropriate CLI and runs `orca file open <artifact-path> --json`. Orca only opens paths inside its selected worktree, so a rejected path falls through to the normal non-web fallback.
|
||||
|
||||
Completion criterion: capture whether the helper opened, reused, or skipped the viewer pane.
|
||||
The fallback reuses the pane labeled `artifact presenter` in the current Herdr workspace. It serializes pane discovery, creation, and replacement per workspace, with an additional per-pane lock during viewer handoff. When that pane is already displaying an artifact, it closes the active `mcat` viewer and waits for the pane shell before launching the replacement through a shell-neutral helper command with encoded path arguments. It confirms the helper started the viewer rather than trusting terminal input delivery alone. It leaves unrelated foreground programs untouched and creates a new viewer pane instead. When no reusable pane exists, it creates a right split without taking focus. It launches `mcat <artifact-path>` so mcat can infer and display documents, source, archives, PDFs, images, and videos. It falls back to artifact-only delivery when neither Orca nor the Herdr/mcat path can present the file.
|
||||
|
||||
Completion criterion: capture whether Orca opened the file, the helper opened or reused an mcat pane, or presentation fell back to the artifact path only.
|
||||
|
||||
## Convert only when requested
|
||||
|
||||
@@ -77,6 +79,6 @@ Return:
|
||||
- a one- to three-sentence result summary;
|
||||
- the artifact path;
|
||||
- for interactive web artifacts, that Browser Harness presented the artifact and verified its URL/title;
|
||||
- for other artifacts, whether the reusable mcat pane was opened or reused, or why presentation fell back to the file only.
|
||||
- for other artifacts, whether Orca opened the file, the reusable mcat pane was opened or reused, or why presentation fell back to the file only.
|
||||
|
||||
Do not repeat the complete artifact in chat. Honor a later request to paste any section inline.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Open one review artifact in a reusable right-side Herdr mcat pane."""
|
||||
"""Present one review artifact through Orca or a reusable Herdr mcat pane."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -22,6 +22,13 @@ from typing import Callable, Mapping, Sequence
|
||||
|
||||
PANE_LABEL = "artifact presenter"
|
||||
VIEWER_PROCESS_NAME = "mcat"
|
||||
TEXT_SAMPLE_BYTES = 8192
|
||||
ORCA_SESSION_ENVIRONMENT_KEYS = (
|
||||
"ORCA_WORKSPACE_ID",
|
||||
"ORCA_WORKTREE_ID",
|
||||
"ORCA_PANE_KEY",
|
||||
"ORCA_TERMINAL_HANDLE",
|
||||
)
|
||||
PANE_READY_ATTEMPTS = 40
|
||||
PANE_READY_DELAY_SECONDS = 0.05
|
||||
PANE_SHELL_STABLE_CHECKS = 2
|
||||
@@ -57,9 +64,66 @@ class PresentationResult:
|
||||
def message(self) -> str:
|
||||
if self.status == "artifact-only":
|
||||
return f"artifact-only: {self.reason}; {self.artifact}"
|
||||
if self.status == "orca-opened":
|
||||
return f"orca-opened: {self.artifact}"
|
||||
return f"{self.status}: pane {self.pane_id}; {self.artifact}"
|
||||
|
||||
|
||||
def is_orca_managed_session(env: Mapping[str, str]) -> bool:
|
||||
"""Return whether this process belongs to an Orca-managed terminal session."""
|
||||
if env.get("TERM_PROGRAM", "").casefold() == "orca":
|
||||
return True
|
||||
return any(env.get(key) for key in ORCA_SESSION_ENVIRONMENT_KEYS)
|
||||
|
||||
|
||||
def is_editor_friendly_text(artifact: Path) -> bool:
|
||||
"""Recognize UTF-8 text without maintaining a source-extension allowlist."""
|
||||
with artifact.open("rb") as handle:
|
||||
sample = handle.read(TEXT_SAMPLE_BYTES)
|
||||
if b"\0" in sample:
|
||||
return False
|
||||
try:
|
||||
sample.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def orca_executable(
|
||||
env: Mapping[str, str],
|
||||
which: FindExecutable,
|
||||
) -> str | None:
|
||||
configured = env.get("ORCA_CLI_COMMAND")
|
||||
if configured:
|
||||
return which(configured)
|
||||
if env.get("ORCA_DEV_REPO_ROOT"):
|
||||
return which("orca-dev")
|
||||
return which("orca")
|
||||
|
||||
|
||||
def open_in_orca(
|
||||
orca: str,
|
||||
artifact: Path,
|
||||
run: RunCommand,
|
||||
) -> PresentationResult:
|
||||
completed = run(
|
||||
[orca, "file", "open", str(artifact), "--json"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
response = json.loads(completed.stdout)
|
||||
result = response.get("result") if isinstance(response, dict) else None
|
||||
if (
|
||||
not isinstance(response, dict)
|
||||
or response.get("ok") is not True
|
||||
or not isinstance(result, dict)
|
||||
or result.get("opened") is not True
|
||||
):
|
||||
raise ValueError("Orca did not confirm that it opened the file")
|
||||
return PresentationResult("orca-opened", artifact)
|
||||
|
||||
|
||||
def run_json(
|
||||
executable: str,
|
||||
args: Sequence[str],
|
||||
@@ -345,6 +409,14 @@ def present_document(
|
||||
if not artifact.is_file():
|
||||
raise ValueError(f"artifact is not a file: {artifact}")
|
||||
|
||||
if is_orca_managed_session(env) and is_editor_friendly_text(artifact):
|
||||
orca = orca_executable(env, which)
|
||||
if orca:
|
||||
try:
|
||||
return open_in_orca(orca, artifact, run)
|
||||
except (json.JSONDecodeError, OSError, subprocess.SubprocessError, ValueError):
|
||||
pass
|
||||
|
||||
herdr = which("herdr")
|
||||
mcat = which("mcat")
|
||||
workspace_id = env.get("HERDR_WORKSPACE_ID")
|
||||
|
||||
@@ -120,6 +120,22 @@ class FakeRunner:
|
||||
return subprocess.CompletedProcess(argv, 0, json.dumps(output), "")
|
||||
|
||||
|
||||
class OrcaAwareRunner(FakeRunner):
|
||||
def __init__(self, *args, fail_orca_open: bool = False, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fail_orca_open = fail_orca_open
|
||||
|
||||
def __call__(self, command, **kwargs):
|
||||
argv = [str(value) for value in command]
|
||||
if argv[1:3] == ["file", "open"]:
|
||||
self.calls.append(argv)
|
||||
if self.fail_orca_open:
|
||||
raise subprocess.CalledProcessError(1, argv)
|
||||
output = {"ok": True, "result": {"opened": True}}
|
||||
return subprocess.CompletedProcess(argv, 0, json.dumps(output), "")
|
||||
return super().__call__(command, **kwargs)
|
||||
|
||||
|
||||
class ArtifactPresenterTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.tempdir = tempfile.TemporaryDirectory()
|
||||
@@ -132,6 +148,7 @@ class ArtifactPresenterTests(unittest.TestCase):
|
||||
self.executables = {
|
||||
"herdr": "/usr/bin/herdr",
|
||||
"mcat": "/usr/bin/mcat",
|
||||
"orca": "/usr/bin/orca",
|
||||
}
|
||||
|
||||
def tearDown(self) -> None:
|
||||
@@ -381,21 +398,71 @@ class ArtifactPresenterTests(unittest.TestCase):
|
||||
)
|
||||
self.assertFalse(any(call[1:3] == ["pane", "split"] for call in runner.calls))
|
||||
|
||||
def test_presents_non_markdown_file_directly_with_mcat(self) -> None:
|
||||
video = self.artifact.with_name("review.mp4")
|
||||
video.write_bytes(b"video fixture")
|
||||
runner = FakeRunner(
|
||||
def test_opens_editor_friendly_file_in_orca_managed_session(self) -> None:
|
||||
source = self.artifact.with_name("module.ts")
|
||||
source.write_text("export const answer = 42;\n", encoding="utf-8")
|
||||
runner = OrcaAwareRunner()
|
||||
|
||||
result = MODULE.present_document(
|
||||
source,
|
||||
env={**self.env, "ORCA_WORKSPACE_ID": "workspace"},
|
||||
which=self.which,
|
||||
run=runner,
|
||||
)
|
||||
|
||||
self.assertEqual(result.status, "orca-opened")
|
||||
self.assertEqual(
|
||||
runner.calls,
|
||||
[["/usr/bin/orca", "file", "open", str(source.resolve()), "--json"]],
|
||||
)
|
||||
|
||||
def test_uses_configured_orca_cli_command(self) -> None:
|
||||
self.executables["orca-wsl"] = "/opt/orca/orca-wsl"
|
||||
runner = OrcaAwareRunner()
|
||||
|
||||
result = MODULE.present_document(
|
||||
self.artifact,
|
||||
env={"TERM_PROGRAM": "Orca", "ORCA_CLI_COMMAND": "orca-wsl"},
|
||||
which=self.which,
|
||||
run=runner,
|
||||
)
|
||||
|
||||
self.assertEqual(result.status, "orca-opened")
|
||||
self.assertEqual(runner.calls[0][0], "/opt/orca/orca-wsl")
|
||||
|
||||
def test_falls_back_to_mcat_when_orca_cannot_open_text_file(self) -> None:
|
||||
runner = OrcaAwareRunner(
|
||||
[{"pane_id": "workspace:p-viewer", "label": MODULE.PANE_LABEL}],
|
||||
fail_orca_open=True,
|
||||
)
|
||||
|
||||
result = MODULE.present_document(
|
||||
video,
|
||||
env=self.env,
|
||||
self.artifact,
|
||||
env={**self.env, "ORCA_TERMINAL_HANDLE": "term-1"},
|
||||
which=self.which,
|
||||
run=runner,
|
||||
)
|
||||
|
||||
self.assertEqual(result.status, "reused")
|
||||
self.assertEqual(runner.calls[0][1:3], ["file", "open"])
|
||||
self.mcat_run_call(runner, "workspace:p-viewer")
|
||||
|
||||
def test_presents_binary_file_directly_with_mcat_in_orca_session(self) -> None:
|
||||
video = self.artifact.with_name("review.mp4")
|
||||
video.write_bytes(b"video\0fixture")
|
||||
runner = OrcaAwareRunner(
|
||||
[{"pane_id": "workspace:p-viewer", "label": MODULE.PANE_LABEL}],
|
||||
)
|
||||
|
||||
result = MODULE.present_document(
|
||||
video,
|
||||
env={**self.env, "TERM_PROGRAM": "Orca"},
|
||||
which=self.which,
|
||||
run=runner,
|
||||
)
|
||||
|
||||
self.assertEqual(result.status, "reused")
|
||||
self.assertFalse(any(call[1:3] == ["file", "open"] for call in runner.calls))
|
||||
self.mcat_run_call(runner, "workspace:p-viewer", video)
|
||||
|
||||
def test_encodes_artifact_paths_before_terminal_delivery(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user