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.
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/PKG-INFO +4 -3
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/pyproject.toml +4 -3
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/__init__.py +1 -2
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/host_actions.py +16 -28
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/prompt.py +12 -33
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/session.py +8 -3
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/types.py +5 -6
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/PKG-INFO +4 -3
- optio_opencode-0.1.5/src/optio_opencode.egg-info/requires.txt +8 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_local.py +3 -3
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_prompt.py +11 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_hooks.py +4 -4
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_local.py +2 -1
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_resume.py +2 -1
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_smart_install.py +8 -8
- optio_opencode-0.1.3/src/optio_opencode.egg-info/requires.txt +0 -7
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/README.md +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/setup.cfg +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode/snapshots.py +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/SOURCES.txt +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/dependency_links.txt +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/src/optio_opencode.egg-info/top_level.txt +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_primitives_local.py +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_primitives_remote.py +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_remote_resume.py +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_host_resume.py +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_sanity.py +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_blob_hooks.py +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_session_remote.py +0 -0
- {optio_opencode-0.1.3 → optio_opencode-0.1.5}/tests/test_snapshots.py +0 -0
- {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
|
+
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.
|
|
24
|
-
Requires-Dist: optio-host<0.
|
|
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.
|
|
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.
|
|
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.
|
|
32
|
+
"optio-host>=0.2,<0.3",
|
|
33
|
+
"optio-agents>=0.1,<0.2",
|
|
33
34
|
"asyncssh>=2.14",
|
|
34
35
|
]
|
|
35
36
|
|
|
@@ -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,42 +7,14 @@ addressed. The consumer's own task description is then appended verbatim.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
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"{
|
|
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 ``
|
|
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
|
|
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
|
|
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
|
|
4
|
-
``
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
+
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.
|
|
24
|
-
Requires-Dist: optio-host<0.
|
|
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"
|
|
@@ -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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
259
|
-
from
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
442
|
+
from optio_agents import HookContext
|
|
443
443
|
from optio_opencode import host_actions
|
|
444
444
|
|
|
445
445
|
fake_home = tmp_path / "home"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{optio_opencode-0.1.3 → 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
|