optio-opencode 0.1.3__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.
Files changed (31) hide show
  1. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/PKG-INFO +4 -3
  2. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/pyproject.toml +4 -3
  3. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/__init__.py +1 -2
  4. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/host_actions.py +16 -28
  5. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/prompt.py +12 -33
  6. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/session.py +8 -3
  7. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/types.py +5 -6
  8. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/PKG-INFO +4 -3
  9. optio_opencode-0.1.5/src/optio_opencode.egg-info/requires.txt +8 -0
  10. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_local.py +3 -3
  11. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_prompt.py +11 -0
  12. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_hooks.py +4 -4
  13. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_local.py +2 -1
  14. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_resume.py +2 -1
  15. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_smart_install.py +8 -8
  16. optio_opencode-0.1.3/src/optio_opencode.egg-info/requires.txt +0 -7
  17. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/README.md +0 -0
  18. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/setup.cfg +0 -0
  19. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/snapshots.py +0 -0
  20. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/SOURCES.txt +0 -0
  21. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/dependency_links.txt +0 -0
  22. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/top_level.txt +0 -0
  23. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_primitives_local.py +0 -0
  24. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_primitives_remote.py +0 -0
  25. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_remote_resume.py +0 -0
  26. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_resume.py +0 -0
  27. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_sanity.py +0 -0
  28. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_blob_hooks.py +0 -0
  29. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_remote.py +0 -0
  30. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_snapshots.py +0 -0
  31. {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optio-opencode
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Run opencode web as an optio task; local subprocess or remote via SSH.
5
5
  Author-email: Kristof Csillag <kristof.csillag@deai-labs.com>
6
6
  License-Expression: Apache-2.0
@@ -20,8 +20,9 @@ Classifier: Topic :: Software Development :: Code Generators
20
20
  Classifier: Framework :: AsyncIO
21
21
  Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
- Requires-Dist: optio-core<0.2,>=0.1
24
- Requires-Dist: optio-host<0.2,>=0.1
23
+ Requires-Dist: optio-core<0.3,>=0.2
24
+ Requires-Dist: optio-host<0.3,>=0.2
25
+ Requires-Dist: optio-agents<0.2,>=0.1
25
26
  Requires-Dist: asyncssh>=2.14
26
27
  Provides-Extra: dev
27
28
  Requires-Dist: pytest>=8.0; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "optio-opencode"
7
- version = "0.1.3"
7
+ version = "0.1.5"
8
8
  description = "Run opencode web as an optio task; local subprocess or remote via SSH."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -26,10 +26,11 @@ classifiers = [
26
26
  "Framework :: AsyncIO",
27
27
  ]
28
28
  dependencies = [
29
- "optio-core>=0.1,<0.2",
29
+ "optio-core>=0.2,<0.3",
30
30
  # 0.1.1 introduces the bind_addr kwarg on Host.establish_tunnel —
31
31
  # required for OPTIO_WIDGET_TUNNEL_BIND to work.
32
- "optio-host>=0.1,<0.2",
32
+ "optio-host>=0.2,<0.3",
33
+ "optio-agents>=0.1,<0.2",
33
34
  "asyncssh>=2.14",
34
35
  ]
35
36
 
@@ -2,9 +2,8 @@
2
2
 
3
3
  import logging as _logging
4
4
 
5
+ from optio_agents import HookContext, HookContextProtocol
5
6
  from optio_host import (
6
- HookContext,
7
- HookContextProtocol,
8
7
  HostCommandError,
9
8
  RunResult,
10
9
  SSHConfig,
@@ -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
- Lays down no-op browser-opener stubs (xdg-open, gio, open,
340
- sensible-browser) under ``<workdir>/bin`` and prepends that
341
- directory to PATH so opencode's automatic browser-launch is
342
- suppressed.
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
- # Browser-suppression bin shadow.
358
- for noop in ("xdg-open", "gio", "open", "sensible-browser"):
359
- await host.write_text(f"bin/{noop}", "#!/bin/sh\nexit 0\n")
360
- chmod_result = await host.run_command(f"chmod +x {host.workdir}/bin/*")
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 we set in `env` below. With
373
- # the prefix gone, the noop xdg-open / sensible-browser / gio / open
374
- # shadows below stop hiding the real ones and opencode succeeds at
375
- # opening a real browser window. opencode_executable is an absolute
376
- # path (resolved by ensure_opencode_installed), so login-shell PATH
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,42 +7,14 @@ addressed. The consumer's own task description is then appended verbatim.
7
7
  """
8
8
 
9
9
 
10
- BASE_PROMPT_PRE = """# Coordination protocol with the host (optio-opencode)
10
+ from optio_agents.protocol import build_log_channel_prompt
11
+
12
+
13
+ _OPENCODE_INTRO = """# Coordination protocol with the host (optio-opencode)
11
14
 
12
15
  You are running inside a coordination harness. Follow these conventions
13
16
  throughout the session.
14
17
 
15
- ## Log channel
16
-
17
- Append one line per entry to `./optio.log` in this directory. Each line
18
- must start with one of:
19
-
20
- - `STATUS:` — progress update for the human. Optional leading percent,
21
- e.g. `STATUS: 50% counting my fingers`.
22
- - `DELIVERABLE:` — absolute or workdir-relative path to a file you've
23
- just produced, e.g. `DELIVERABLE: ./deliverables/summary.md`.
24
- - `DONE` — you have finished the task. May be followed by an optional
25
- summary on the same line: `DONE: wrote the report`.
26
- - `ERROR` — you cannot continue. May be followed by an optional
27
- message: `ERROR: provider auth failed`.
28
-
29
- **Every entry must end with a newline character (`\\n`).** The host
30
- reads `optio.log` with a line-oriented tailer that only emits a line
31
- once it sees `\\n`; an entry written without a trailing newline (e.g.
32
- via `printf 'DONE'`) will be buffered indefinitely and never reach the
33
- host. Use `echo`, `>>` redirection of a heredoc, or any other mechanism
34
- that guarantees a trailing newline. If unsure, double-check with
35
- `tail -c 1 ./optio.log` — the result must be a newline.
36
-
37
- After writing `DONE` or `ERROR`, the session will terminate. Do not
38
- write further lines.
39
-
40
- ## Deliverables
41
-
42
- Place files you want to hand back to the host under `./deliverables/`.
43
- For each file, write a `DELIVERABLE:` log line *after* the file exists
44
- and its contents are final. The host fetches files by reading these
45
- log lines.
46
18
  """
47
19
 
48
20
 
@@ -145,6 +117,7 @@ def compose_agents_md(
145
117
  consumer_instructions: str,
146
118
  *,
147
119
  workdir_exclude: list[str] | None,
120
+ documentation: str | None = None,
148
121
  supports_resume: bool = True,
149
122
  ) -> str:
150
123
  """Build the full AGENTS.md body.
@@ -155,12 +128,18 @@ def compose_agents_md(
155
128
  callers must pass it explicitly to prevent silent desync between
156
129
  archive.py defaults and the prompt's claims about what's preserved.
157
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.
158
134
  supports_resume: when False, the resume-detection section is omitted
159
135
  from the prompt. Default True.
160
136
  """
137
+ if documentation is None:
138
+ documentation = build_log_channel_prompt("suppress")
139
+ base_prompt_pre = _OPENCODE_INTRO + documentation
161
140
  body = consumer_instructions.rstrip()
162
141
  if supports_resume:
163
142
  resume_block = _render_resume_section(workdir_exclude) + "\n"
164
143
  else:
165
144
  resume_block = ""
166
- return f"{BASE_PROMPT_PRE}\n{resume_block}{BASE_PROMPT_POST}\n{body}\n"
145
+ return f"{base_prompt_pre}\n{resume_block}{BASE_PROMPT_POST}\n{body}\n"
@@ -7,7 +7,7 @@ Section 4 of the design spec. The public entry point is the factory
7
7
 
8
8
  Most of the per-session work is generic log/deliverables protocol
9
9
  plumbing (parse ``optio.log``, fetch deliverables, watch for cancel) and
10
- lives in ``optio_host.protocol.run_log_protocol_session``. This module
10
+ lives in ``optio_agents.protocol.run_log_protocol_session``. This module
11
11
  keeps only the opencode-specific work — write AGENTS.md / opencode.json,
12
12
  install/launch the opencode binary, set up tunnel and widget, and the
13
13
  resume/snapshot brackets around the protocol session.
@@ -29,12 +29,13 @@ from typing import AsyncIterator, Callable
29
29
  from optio_core.context import ProcessContext
30
30
  from optio_core.models import BasicAuth, TaskInstance
31
31
 
32
- from optio_host.context import HookContext
32
+ from optio_agents import HookContext
33
33
  from optio_host.host import Host, LocalHost, ProcessHandle, RemoteHost
34
34
  from optio_host.paths import task_dir
35
- from optio_host.protocol.session import _SessionFailed, run_log_protocol_session
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
@@ -1,16 +1,15 @@
1
1
  """Public data types for optio-opencode consumers.
2
2
 
3
- The generic ``DeliverableCallback`` / ``HookCallback`` types and
4
- ``SSHConfig`` are owned by ``optio-host`` (since they describe the
5
- log/deliverables protocol and SSH config in general). This module
6
- re-exports them so existing ``from optio_opencode.types import ...``
7
- imports keep working unchanged.
3
+ The generic ``DeliverableCallback`` / ``HookCallback`` types are owned by
4
+ ``optio-agents`` (they describe the log/deliverables protocol); ``SSHConfig``
5
+ is owned by ``optio-host``. This module re-exports them so existing
6
+ ``from optio_opencode.types import ...`` imports keep working unchanged.
8
7
  """
9
8
 
10
9
  from dataclasses import dataclass, field
11
10
  from typing import Any, Callable
12
11
 
13
- from optio_host.protocol.session import DeliverableCallback, HookCallback
12
+ from optio_agents.protocol.session import DeliverableCallback, HookCallback
14
13
  from optio_host.types import SSHConfig
15
14
 
16
15
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optio-opencode
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Run opencode web as an optio task; local subprocess or remote via SSH.
5
5
  Author-email: Kristof Csillag <kristof.csillag@deai-labs.com>
6
6
  License-Expression: Apache-2.0
@@ -20,8 +20,9 @@ Classifier: Topic :: Software Development :: Code Generators
20
20
  Classifier: Framework :: AsyncIO
21
21
  Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
- Requires-Dist: optio-core<0.2,>=0.1
24
- Requires-Dist: optio-host<0.2,>=0.1
23
+ Requires-Dist: optio-core<0.3,>=0.2
24
+ Requires-Dist: optio-host<0.3,>=0.2
25
+ Requires-Dist: optio-agents<0.2,>=0.1
25
26
  Requires-Dist: asyncssh>=2.14
26
27
  Provides-Extra: dev
27
28
  Requires-Dist: pytest>=8.0; extra == "dev"
@@ -0,0 +1,8 @@
1
+ optio-core<0.3,>=0.2
2
+ optio-host<0.3,>=0.2
3
+ optio-agents<0.2,>=0.1
4
+ asyncssh>=2.14
5
+
6
+ [dev]
7
+ pytest>=8.0
8
+ pytest-asyncio>=0.23
@@ -24,7 +24,7 @@ async def test_setup_workdir_creates_workdir(local_host):
24
24
  assert os.path.isdir(local_host.workdir)
25
25
  # As of the optio-host split, setup_workdir mkdirs the workdir only.
26
26
  # The protocol-specific deliverables/ + optio.log are owned by the
27
- # protocol session driver in optio_host.protocol.session.
27
+ # protocol session driver in optio_agents.protocol.session.
28
28
 
29
29
 
30
30
  @pytest.mark.asyncio
@@ -136,7 +136,7 @@ async def test_tail_file_yields_appended_lines(local_host):
136
136
 
137
137
 
138
138
  async def test_fetch_deliverable_text(local_host):
139
- from optio_host.protocol.session import fetch_deliverable_text
139
+ from optio_agents.protocol.session import fetch_deliverable_text
140
140
  await local_host.setup_workdir()
141
141
  os.makedirs(os.path.join(local_host.workdir, "deliverables"), exist_ok=True)
142
142
  target = os.path.join(local_host.workdir, "deliverables", "a.txt")
@@ -146,7 +146,7 @@ async def test_fetch_deliverable_text(local_host):
146
146
 
147
147
 
148
148
  async def test_fetch_deliverable_non_utf8_raises(local_host):
149
- from optio_host.protocol.session import fetch_deliverable_text
149
+ from optio_agents.protocol.session import fetch_deliverable_text
150
150
  await local_host.setup_workdir()
151
151
  os.makedirs(os.path.join(local_host.workdir, "deliverables"), exist_ok=True)
152
152
  target = os.path.join(local_host.workdir, "deliverables", "b.bin")
@@ -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
@@ -97,7 +97,7 @@ class _RecordingFakeHost:
97
97
  return b""
98
98
 
99
99
  async def run_command(self, *a, **kw):
100
- from optio_host.context import RunResult
100
+ from optio_host.host import RunResult
101
101
  self.timeline.append(f"run_command:{a[0]}")
102
102
  return RunResult(stdout="", stderr="", exit_code=0)
103
103
 
@@ -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
 
@@ -255,8 +255,8 @@ async def test_on_deliverable_receives_hook_ctx_and_can_use_host_primitives(tmp_
255
255
  )
256
256
  # We don't run a full session here; we directly invoke
257
257
  # _deliverable_fetch_loop with a constructed HookContext.
258
- from optio_host.protocol.session import _deliverable_fetch_loop
259
- from optio_host.context import HookContext
258
+ from optio_agents.protocol.session import _deliverable_fetch_loop
259
+ from optio_agents import HookContext
260
260
 
261
261
  queue = asyncio.Queue()
262
262
  await queue.put(("/wd/deliverables/x.txt", "x.txt"))
@@ -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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import pytest
4
4
 
5
- from optio_host.context import RunResult
5
+ from optio_host.host import RunResult
6
6
 
7
7
 
8
8
  class _FakeHost:
@@ -170,7 +170,7 @@ class _ExecutingFakeCtx:
170
170
 
171
171
 
172
172
  async def test_install_opencode_from_zip_happy_path(zip_server, tmp_path):
173
- from optio_host.context import HookContext
173
+ from optio_agents import HookContext
174
174
  from optio_host.host import LocalHost
175
175
  from optio_opencode.host_actions import _install_opencode_from_zip
176
176
 
@@ -212,7 +212,7 @@ async def test_install_opencode_from_zip_happy_path(zip_server, tmp_path):
212
212
 
213
213
  async def test_install_opencode_from_zip_cleans_up_tempdir(zip_server, tmp_path):
214
214
  """The temp dir created on the host should be removed after install."""
215
- from optio_host.context import HookContext
215
+ from optio_agents import HookContext
216
216
  from optio_host.host import LocalHost
217
217
  from optio_opencode.host_actions import _install_opencode_from_zip
218
218
 
@@ -264,7 +264,7 @@ async def test_install_opencode_from_zip_cleans_up_tempdir(zip_server, tmp_path)
264
264
 
265
265
  async def test_ensure_opencode_installed_returns_existing_path_when_ok(monkeypatch):
266
266
  """When smart-install says 'ok', resolve the on-PATH path and return it."""
267
- from optio_host.context import HookContext
267
+ from optio_agents import HookContext
268
268
  from optio_opencode import host_actions
269
269
 
270
270
  async def stub_check(host, *, install_dir=None):
@@ -295,7 +295,7 @@ async def test_ensure_opencode_installed_returns_existing_path_when_ok(monkeypat
295
295
 
296
296
 
297
297
  async def test_ensure_opencode_installed_raises_when_install_disabled(monkeypatch):
298
- from optio_host.context import HookContext
298
+ from optio_agents import HookContext
299
299
  from optio_opencode import host_actions
300
300
 
301
301
  async def stub_check(host, *, install_dir=None):
@@ -324,7 +324,7 @@ async def test_ensure_opencode_installed_installs_when_download_required(
324
324
  monkeypatch, zip_server, tmp_path,
325
325
  ):
326
326
  """End-to-end with a real LocalHost + fake zip server: install path."""
327
- from optio_host.context import HookContext
327
+ from optio_agents import HookContext
328
328
  from optio_host.host import LocalHost
329
329
  from optio_opencode import host_actions
330
330
 
@@ -394,7 +394,7 @@ async def test_ensure_opencode_installed_respects_custom_install_dir(
394
394
  ):
395
395
  """End-to-end: an explicit ``install_dir`` flows through to the
396
396
  install target and to the post-ok ``command -v`` PATH augmentation."""
397
- from optio_host.context import HookContext
397
+ from optio_agents import HookContext
398
398
  from optio_host.host import LocalHost
399
399
  from optio_opencode import host_actions
400
400
 
@@ -439,7 +439,7 @@ async def test_ensure_opencode_installed_default_install_dir_is_home_local_bin(
439
439
  ):
440
440
  """When ``install_dir`` is omitted, the default is
441
441
  ``<host_home>/.local/bin``."""
442
- from optio_host.context import HookContext
442
+ from optio_agents import HookContext
443
443
  from optio_opencode import host_actions
444
444
 
445
445
  fake_home = tmp_path / "home"
@@ -1,7 +0,0 @@
1
- optio-core<0.2,>=0.1
2
- optio-host<0.2,>=0.1
3
- asyncssh>=2.14
4
-
5
- [dev]
6
- pytest>=8.0
7
- pytest-asyncio>=0.23
File without changes
File without changes