fix: improve opencode trial resume controls

This commit is contained in:
lda
2026-06-30 18:30:16 +07:00 Verified
parent 04b1cf30f9
commit 5e9b76eb94
5 changed files with 310 additions and 17 deletions
+3 -2
View File
@@ -12,8 +12,9 @@ Use this command prefix:
{{server_context}}
Your writable trial workspace is `{{workspace_path}}`. Write attempt files only
inside it. End with the challenge's requested YAML self-report. The self-report
will be checked against observed tool calls and manually audited.
inside it. End with the challenge's requested YAML self-report inline in your
final answer. A run without an inline self-report is invalid. The inline
self-report will be checked against observed tool calls and manually audited.
Do not read files under other `workspaces/*` trial directories. Prior trial
workspaces may contain complete answers. If you do read another trial workspace,
+87 -1
View File
@@ -3,7 +3,9 @@ from __future__ import annotations
import json
import subprocess
from pathlib import Path
from typing import Any
from typing import Any, Literal
PromptMode = Literal["auto", "continue", "final-report"]
FINAL_REPORT_PROMPT = (
"Your workflow attempt is over. Do not continue coding. Return only the "
@@ -62,6 +64,15 @@ def resume_prompt_for_result(result: dict[str, object]) -> str:
return CONTINUE_PROMPT
def resume_prompt_for_mode(result: dict[str, object], mode: PromptMode) -> str:
"""Return the operator-selected resume prompt, falling back to auto detection."""
if mode == "continue":
return CONTINUE_PROMPT
if mode == "final-report":
return FINAL_REPORT_PROMPT
return resume_prompt_for_result(result)
def build_resume_command(
*,
session_id: str,
@@ -77,6 +88,81 @@ def build_resume_command(
return command
def resume_command_from_result(
result: dict[str, Any],
*,
session_id: str | None = None,
attach_url: str | None = None,
model: str | None = None,
variant: str | None = None,
prompt_mode: PromptMode = "auto",
) -> list[str]:
"""Build a resume command from new metadata or recover it from old raw results."""
opencode = result.get("opencode")
if isinstance(opencode, dict):
command = opencode.get("resume_command")
has_override = (
any(value is not None for value in (session_id, attach_url, model, variant))
or prompt_mode != "auto"
)
if (
not has_override
and isinstance(command, list)
and all(isinstance(part, str) for part in command)
):
return command
stdout = result.get("stdout")
stdout_text = stdout if isinstance(stdout, str) else ""
if isinstance(opencode, dict):
metadata_session_id = opencode.get("session_id")
metadata_model = opencode.get("model")
metadata_variant = opencode.get("variant")
metadata_attach_url = opencode.get("attach_url")
else:
metadata_session_id = None
metadata_model = None
metadata_variant = None
metadata_attach_url = None
recovered_session_id = (
session_id
or (metadata_session_id if isinstance(metadata_session_id, str) else None)
or extract_session_id(stdout_text)
)
if not recovered_session_id:
raise ValueError("result has no opencode session id; pass --session")
result_model = result.get("model")
result_variant = result.get("variant")
resolved_model = (
model
or (metadata_model if isinstance(metadata_model, str) else None)
or (result_model if isinstance(result_model, str) else None)
)
resolved_variant = (
variant
or (metadata_variant if isinstance(metadata_variant, str) else None)
or (result_variant if isinstance(result_variant, str) else None)
)
resolved_attach_url = attach_url or (
metadata_attach_url if isinstance(metadata_attach_url, str) else None
)
if not resolved_model:
raise ValueError("result has no opencode model; pass --model")
if not resolved_variant:
raise ValueError("result has no opencode variant; pass --variant")
return build_resume_command(
session_id=recovered_session_id,
attach_url=resolved_attach_url,
model=resolved_model,
variant=resolved_variant,
prompt=resume_prompt_for_mode(result, prompt_mode),
)
def display_resume_command(command: list[str]) -> str:
"""Render an argv list for copy-paste display without changing execution."""
return subprocess.list2cmdline(command)
+66 -14
View File
@@ -11,11 +11,18 @@ from pathlib import Path
from typing import Any
try:
from .opencode_resume import display_resume_command, resume_result_path
from .opencode_resume import (
PromptMode,
display_resume_command,
resume_command_from_result,
resume_result_path,
)
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from examples.agent_challenges.opencode_resume import (
PromptMode,
display_resume_command,
resume_command_from_result,
resume_result_path,
)
@@ -30,16 +37,23 @@ def _utf8_subprocess_env() -> dict[str, str]:
return env
def _load_command(result: dict[str, Any]) -> list[str]:
opencode = result.get("opencode")
if not isinstance(opencode, dict):
raise ValueError("result has no opencode metadata")
command = opencode.get("resume_command")
if not isinstance(command, list) or not all(
isinstance(part, str) for part in command
):
raise ValueError("result has no resume_command; session id may be missing")
return command
def _load_command(
result: dict[str, Any],
*,
session_id: str | None = None,
attach_url: str | None = None,
model: str | None = None,
variant: str | None = None,
prompt_mode: PromptMode = "auto",
) -> list[str]:
return resume_command_from_result(
result,
session_id=session_id,
attach_url=attach_url,
model=model,
variant=variant,
prompt_mode=prompt_mode,
)
def _display_command(command: list[str]) -> str:
@@ -49,12 +63,24 @@ def _display_command(command: list[str]) -> str:
def resume_from_result(
result_path: Path,
*,
session_id: str | None = None,
attach_url: str | None = None,
model: str | None = None,
variant: str | None = None,
prompt_mode: PromptMode = "auto",
run_fn: RunFn = subprocess.run,
) -> Path:
result = json.loads(result_path.read_text(encoding="utf-8"))
if not isinstance(result, dict):
raise ValueError("result file must contain a JSON object")
command = _load_command(result)
command = _load_command(
result,
session_id=session_id,
attach_url=attach_url,
model=model,
variant=variant,
prompt_mode=prompt_mode,
)
workspace_path = result.get("workspace_path")
cwd = str(workspace_path) if isinstance(workspace_path, str) else None
started = time.monotonic()
@@ -89,6 +115,16 @@ def main(argv: list[str] | None = None) -> int:
description="Print or run an OpenCode trial resume command."
)
parser.add_argument("--from-result", type=Path, required=True)
parser.add_argument("--session")
parser.add_argument("--attach", dest="attach_url")
parser.add_argument("--model")
parser.add_argument("--variant")
parser.add_argument(
"--prompt-mode",
choices=("auto", "continue", "final-report"),
default="auto",
help="Choose the resume prompt instead of relying on auto detection.",
)
parser.add_argument("--print-command", action="store_true")
parser.add_argument("--run", action="store_true")
args = parser.parse_args(argv)
@@ -97,9 +133,25 @@ def main(argv: list[str] | None = None) -> int:
result = json.loads(args.from_result.read_text(encoding="utf-8"))
if not isinstance(result, dict):
raise ValueError("result file must contain a JSON object")
command = _load_command(result)
command = _load_command(
result,
session_id=args.session,
attach_url=args.attach_url,
model=args.model,
variant=args.variant,
prompt_mode=args.prompt_mode,
)
if args.run:
print(resume_from_result(args.from_result).as_posix())
print(
resume_from_result(
args.from_result,
session_id=args.session,
attach_url=args.attach_url,
model=args.model,
variant=args.variant,
prompt_mode=args.prompt_mode,
).as_posix()
)
else:
print(_display_command(command))
except ValueError as exc: