optio-opencode 0.1.4__tar.gz → 0.1.5__tar.gz
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.
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/PKG-INFO +1 -1
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/pyproject.toml +1 -1
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode/host_actions.py +16 -28
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode/prompt.py +9 -5
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode/session.py +5 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/PKG-INFO +1 -1
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_prompt.py +11 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_session_hooks.py +1 -1
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_session_local.py +2 -1
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_session_resume.py +2 -1
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/README.md +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/setup.cfg +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode/__init__.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode/snapshots.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode/types.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/SOURCES.txt +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/dependency_links.txt +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/requires.txt +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/top_level.txt +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_host_local.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_host_primitives_local.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_host_primitives_remote.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_host_remote_resume.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_host_resume.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_sanity.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_session_blob_hooks.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_session_remote.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_smart_install.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_snapshots.py +0 -0
- {optio_opencode-0.1.4 → optio_opencode-0.1.5}/tests/test_types.py +0 -0
|
@@ -329,6 +329,7 @@ async def launch_opencode(
|
|
|
329
329
|
ready_timeout_s: float = 30.0,
|
|
330
330
|
opencode_executable: str = "opencode",
|
|
331
331
|
hostname: str = "127.0.0.1",
|
|
332
|
+
extra_env: dict[str, str] | None = None,
|
|
332
333
|
) -> tuple[ProcessHandle, int]:
|
|
333
334
|
"""Launch ``opencode web`` on ``host``; wait for the listening URL.
|
|
334
335
|
|
|
@@ -336,10 +337,10 @@ async def launch_opencode(
|
|
|
336
337
|
and references it via ``$(cat ...)`` in the launch command so the
|
|
337
338
|
literal value never appears on the remote process's argv.
|
|
338
339
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
Browser-open suppression is handled by the optio-agents protocol
|
|
341
|
+
driver (``get_protocol(browser="suppress")``), which installs no-op
|
|
342
|
+
opener stubs under ``<workdir>/bin`` and returns the ``BROWSER`` /
|
|
343
|
+
``PATH`` env additions; the caller passes those in via ``extra_env``.
|
|
343
344
|
|
|
344
345
|
``hostname`` is passed to ``opencode web --hostname=`` so callers
|
|
345
346
|
can bind to a non-loopback interface when consumers reach the server
|
|
@@ -354,48 +355,35 @@ async def launch_opencode(
|
|
|
354
355
|
await host.write_text(pw_file, password)
|
|
355
356
|
await host.run_command(f"chmod 600 {host.workdir}/{pw_file}")
|
|
356
357
|
|
|
357
|
-
#
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if chmod_result.exit_code != 0:
|
|
362
|
-
# Non-fatal: the noop scripts may fail to be executable, but worst
|
|
363
|
-
# case opencode tries to open a browser and we just live with it.
|
|
364
|
-
pass
|
|
365
|
-
|
|
366
|
-
# Build cmd: read password from file via $(cat), set BROWSER=true,
|
|
367
|
-
# cd to workdir so opencode picks up opencode.json.
|
|
358
|
+
# Build cmd: read password from file via $(cat), cd to workdir so
|
|
359
|
+
# opencode picks up opencode.json. Browser suppression (the BROWSER
|
|
360
|
+
# env + the <workdir>/bin PATH prepend that shadows the openers) comes
|
|
361
|
+
# from the protocol driver's suppress shims, passed in via extra_env.
|
|
368
362
|
#
|
|
369
363
|
# NOTE: do NOT wrap in `bash -lc` / `bash -l`. A login shell sources
|
|
370
364
|
# the user's profile (~/.profile, ~/.bash_profile, /etc/profile),
|
|
371
365
|
# which on most Linux installs rewrites PATH from scratch and
|
|
372
|
-
# therefore wipes the workdir/bin prefix
|
|
373
|
-
# the prefix gone, the
|
|
374
|
-
#
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
# lookup is not needed to find the binary. Let LocalHost / RemoteHost
|
|
378
|
-
# launch_subprocess do the shell wrapping; we just need the env-var
|
|
379
|
-
# prefix and $(cat ...) substitution, which any POSIX sh handles.
|
|
366
|
+
# therefore wipes the workdir/bin prefix carried in `env` below. With
|
|
367
|
+
# the prefix gone, the suppress stubs stop hiding the real openers and
|
|
368
|
+
# opencode succeeds at opening a real browser window. opencode_executable
|
|
369
|
+
# is an absolute path (resolved by ensure_opencode_installed), so
|
|
370
|
+
# login-shell PATH lookup is not needed to find the binary.
|
|
380
371
|
cmd = (
|
|
381
372
|
f"exec env "
|
|
382
373
|
f"OPENCODE_SERVER_PASSWORD=\"$(cat {shlex.quote(host.workdir + '/' + pw_file)})\" "
|
|
383
|
-
f"BROWSER=true "
|
|
384
374
|
f"{opencode_executable} web --port=0 --hostname={shlex.quote(hostname)}"
|
|
385
375
|
)
|
|
386
376
|
|
|
387
|
-
# Prepend the noop-browsers bin dir to PATH via env on launch_subprocess.
|
|
388
|
-
workdir_bin = f"{host.workdir}/bin"
|
|
389
|
-
extra_path = workdir_bin + ":" + os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin")
|
|
390
377
|
# OPENCODE_DB must point at the same per-task db file used by the
|
|
391
378
|
# subsequent export/import CLI calls. Without this, the server falls
|
|
392
379
|
# back to opencode's global default db while export/import target the
|
|
393
380
|
# taskdir-local file — causing snapshot capture to "Session not found"
|
|
394
381
|
# against an empty file. Convention: opencode.db is a sibling of the
|
|
395
382
|
# workdir under taskdir (session.py: opencode_db = f"{taskdir}/opencode.db").
|
|
383
|
+
# The browser-suppression env (PATH prepend + BROWSER) comes from extra_env.
|
|
396
384
|
env = {
|
|
397
|
-
"PATH": extra_path,
|
|
398
385
|
"OPENCODE_DB": f"{host.taskdir}/opencode.db",
|
|
386
|
+
**(extra_env or {}),
|
|
399
387
|
}
|
|
400
388
|
|
|
401
389
|
handle = await host.launch_subprocess(cmd, env=env, cwd=host.workdir)
|
|
@@ -7,7 +7,7 @@ addressed. The consumer's own task description is then appended verbatim.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
from optio_agents.protocol
|
|
10
|
+
from optio_agents.protocol import build_log_channel_prompt
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
_OPENCODE_INTRO = """# Coordination protocol with the host (optio-opencode)
|
|
@@ -18,9 +18,6 @@ throughout the session.
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
BASE_PROMPT_PRE = _OPENCODE_INTRO + LOG_CHANNEL_PROMPT
|
|
22
|
-
|
|
23
|
-
|
|
24
21
|
BASE_PROMPT_POST = """## Task
|
|
25
22
|
|
|
26
23
|
Here comes the description of your actual task to complete. Throughout
|
|
@@ -120,6 +117,7 @@ def compose_agents_md(
|
|
|
120
117
|
consumer_instructions: str,
|
|
121
118
|
*,
|
|
122
119
|
workdir_exclude: list[str] | None,
|
|
120
|
+
documentation: str | None = None,
|
|
123
121
|
supports_resume: bool = True,
|
|
124
122
|
) -> str:
|
|
125
123
|
"""Build the full AGENTS.md body.
|
|
@@ -130,12 +128,18 @@ def compose_agents_md(
|
|
|
130
128
|
callers must pass it explicitly to prevent silent desync between
|
|
131
129
|
archive.py defaults and the prompt's claims about what's preserved.
|
|
132
130
|
Pass None to render with the framework defaults.
|
|
131
|
+
documentation: the keyword-protocol block; the session passes
|
|
132
|
+
``get_protocol(browser="suppress").documentation``. Defaults (for
|
|
133
|
+
unit tests / standalone callers) to opencode's ``suppress`` docs.
|
|
133
134
|
supports_resume: when False, the resume-detection section is omitted
|
|
134
135
|
from the prompt. Default True.
|
|
135
136
|
"""
|
|
137
|
+
if documentation is None:
|
|
138
|
+
documentation = build_log_channel_prompt("suppress")
|
|
139
|
+
base_prompt_pre = _OPENCODE_INTRO + documentation
|
|
136
140
|
body = consumer_instructions.rstrip()
|
|
137
141
|
if supports_resume:
|
|
138
142
|
resume_block = _render_resume_section(workdir_exclude) + "\n"
|
|
139
143
|
else:
|
|
140
144
|
resume_block = ""
|
|
141
|
-
return f"{
|
|
145
|
+
return f"{base_prompt_pre}\n{resume_block}{BASE_PROMPT_POST}\n{body}\n"
|
|
@@ -35,6 +35,7 @@ from optio_host.paths import task_dir
|
|
|
35
35
|
from optio_agents.protocol.session import _SessionFailed, run_log_protocol_session
|
|
36
36
|
from optio_opencode import host_actions
|
|
37
37
|
from optio_opencode.prompt import compose_agents_md
|
|
38
|
+
from optio_agents import get_protocol
|
|
38
39
|
from optio_opencode.snapshots import (
|
|
39
40
|
insert_snapshot,
|
|
40
41
|
load_latest_snapshot,
|
|
@@ -71,6 +72,7 @@ async def run_opencode_session(ctx: ProcessContext, config: OpencodeTaskConfig)
|
|
|
71
72
|
"""Execute function body for one optio-opencode task instance."""
|
|
72
73
|
# --- per-task filesystem layout ---------------------------------------
|
|
73
74
|
host: Host = _build_host(config, ctx.process_id)
|
|
75
|
+
protocol = get_protocol(browser="suppress")
|
|
74
76
|
taskdir = task_dir(
|
|
75
77
|
ssh=config.ssh, process_id=ctx.process_id, consumer_name="optio-opencode",
|
|
76
78
|
)
|
|
@@ -172,6 +174,7 @@ async def run_opencode_session(ctx: ProcessContext, config: OpencodeTaskConfig)
|
|
|
172
174
|
"AGENTS.md",
|
|
173
175
|
compose_agents_md(
|
|
174
176
|
config.consumer_instructions,
|
|
177
|
+
documentation=protocol.documentation,
|
|
175
178
|
workdir_exclude=config.workdir_exclude,
|
|
176
179
|
supports_resume=config.supports_resume,
|
|
177
180
|
),
|
|
@@ -241,6 +244,7 @@ async def run_opencode_session(ctx: ProcessContext, config: OpencodeTaskConfig)
|
|
|
241
244
|
ready_timeout_s=READY_TIMEOUT_S,
|
|
242
245
|
opencode_executable=opencode_exec,
|
|
243
246
|
hostname=opencode_hostname,
|
|
247
|
+
extra_env=hook_ctx.browser_launch_env,
|
|
244
248
|
)
|
|
245
249
|
launched_handle = handle
|
|
246
250
|
|
|
@@ -308,6 +312,7 @@ async def run_opencode_session(ctx: ProcessContext, config: OpencodeTaskConfig)
|
|
|
308
312
|
body=_opencode_body,
|
|
309
313
|
on_deliverable=config.on_deliverable,
|
|
310
314
|
after_execute=config.after_execute,
|
|
315
|
+
protocol=protocol,
|
|
311
316
|
)
|
|
312
317
|
except _SessionFailed as fail:
|
|
313
318
|
session_error = fail
|
|
@@ -97,3 +97,14 @@ def test_compose_agents_md_empty_excludes_renders_no_paths_excluded_copy():
|
|
|
97
97
|
# The 'inside an excluded subdirectory' clause should be absent (it's
|
|
98
98
|
# only relevant when there are exclusions to live inside).
|
|
99
99
|
assert "inside an excluded subdirectory" not in out
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_opencode_docs_omit_browser_keyword():
|
|
103
|
+
"""opencode suppresses browser-opens, so it must NOT advertise BROWSER:."""
|
|
104
|
+
out = _compose()
|
|
105
|
+
assert "BROWSER:" not in out
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_opencode_docs_include_suppress_note():
|
|
109
|
+
out = _compose()
|
|
110
|
+
assert "impossible to launch a browser" in out
|
|
@@ -118,7 +118,7 @@ def _patch_host_actions(monkeypatch, host):
|
|
|
118
118
|
async def _version(_host, *, opencode_executable="opencode"):
|
|
119
119
|
return None
|
|
120
120
|
|
|
121
|
-
async def _launch(_host, _password, *, ready_timeout_s=30.0, opencode_executable="opencode", hostname="127.0.0.1"):
|
|
121
|
+
async def _launch(_host, _password, *, ready_timeout_s=30.0, opencode_executable="opencode", hostname="127.0.0.1", extra_env=None):
|
|
122
122
|
host.timeline.append("launch_opencode")
|
|
123
123
|
raise RuntimeError("test never gets past launch")
|
|
124
124
|
|
|
@@ -105,7 +105,7 @@ def _supply_scenario(monkeypatch):
|
|
|
105
105
|
orig_launch = host_actions.launch_opencode
|
|
106
106
|
scenario_holder: dict = {"name": "happy"}
|
|
107
107
|
|
|
108
|
-
async def _launch(host, password, *, ready_timeout_s=30.0, opencode_executable="opencode", hostname="127.0.0.1"):
|
|
108
|
+
async def _launch(host, password, *, ready_timeout_s=30.0, opencode_executable="opencode", hostname="127.0.0.1", extra_env=None):
|
|
109
109
|
del opencode_executable # we substitute fully
|
|
110
110
|
return await orig_launch(
|
|
111
111
|
host, password,
|
|
@@ -115,6 +115,7 @@ def _supply_scenario(monkeypatch):
|
|
|
115
115
|
f"--scenario {scenario_holder['name']}"
|
|
116
116
|
),
|
|
117
117
|
hostname=hostname,
|
|
118
|
+
extra_env=extra_env,
|
|
118
119
|
)
|
|
119
120
|
monkeypatch.setattr(host_actions, "launch_opencode", _launch)
|
|
120
121
|
|
|
@@ -53,7 +53,7 @@ def _supply_scenario(monkeypatch):
|
|
|
53
53
|
orig_launch = host_actions.launch_opencode
|
|
54
54
|
holder = {"name": "happy"}
|
|
55
55
|
|
|
56
|
-
async def _launch(host, password, *, ready_timeout_s=30.0, opencode_executable="opencode", hostname="127.0.0.1"):
|
|
56
|
+
async def _launch(host, password, *, ready_timeout_s=30.0, opencode_executable="opencode", hostname="127.0.0.1", extra_env=None):
|
|
57
57
|
del opencode_executable
|
|
58
58
|
return await orig_launch(
|
|
59
59
|
host, password,
|
|
@@ -62,6 +62,7 @@ def _supply_scenario(monkeypatch):
|
|
|
62
62
|
f"{sys.executable} {FAKE_OPENCODE} --scenario {holder['name']}"
|
|
63
63
|
),
|
|
64
64
|
hostname=hostname,
|
|
65
|
+
extra_env=extra_env,
|
|
65
66
|
)
|
|
66
67
|
monkeypatch.setattr(host_actions, "launch_opencode", _launch)
|
|
67
68
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{optio_opencode-0.1.4 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|