optio-codex 0.4.1__tar.gz → 0.4.2__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_codex-0.4.1 → optio_codex-0.4.2}/PKG-INFO +1 -1
- {optio_codex-0.4.1 → optio_codex-0.4.2}/pyproject.toml +1 -1
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/conversation.py +3 -1
- optio_codex-0.4.2/src/optio_codex/prompt.py +48 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/session.py +3 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex.egg-info/PKG-INFO +1 -1
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_config_hooks.py +9 -3
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_prompt.py +10 -0
- optio_codex-0.4.1/src/optio_codex/prompt.py +0 -184
- {optio_codex-0.4.1 → optio_codex-0.4.2}/README.md +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/setup.cfg +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/__init__.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/account.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/conversation_listener.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/cred_watcher.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/fs_allowlist.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/host_actions.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/info.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/models.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/rollout.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/seed_manifest.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/snapshots.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/types.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex/verify.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex.egg-info/SOURCES.txt +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex.egg-info/dependency_links.txt +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex.egg-info/requires.txt +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/src/optio_codex.egg-info/top_level.txt +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_account.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_agent_info.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_await_codex_gone.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_claustrum.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_codex_cache.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_config.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_conversation.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_conversation_controls.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_conversation_listener.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_cred_watcher.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_file_download.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_file_upload.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_fs_allowlist.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_host_actions.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_import.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_input_wiring.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_kill_ttyd_by_socket.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_models.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_real_codex_conversation.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_real_codex_seed_resume.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_real_codex_session.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_rollout.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_sandbox_enforce.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_seed_manifest.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_session_conversation.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_session_lease.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_session_local.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_session_remote.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_session_resume.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_session_sandbox.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_session_seed.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_snapshots.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_teardown_session_tree.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_verify.py +0 -0
- {optio_codex-0.4.1 → optio_codex-0.4.2}/tests/test_workdir_trust.py +0 -0
|
@@ -496,7 +496,9 @@ class CodexConversation:
|
|
|
496
496
|
|
|
497
497
|
# -- Conversation protocol surface --------------------------------------
|
|
498
498
|
|
|
499
|
-
async def send(self, text: str) -> None:
|
|
499
|
+
async def send(self, text: str, *, uuid: str | None = None) -> None:
|
|
500
|
+
"""``uuid`` is an advisory message id (Fix 13a/Fix 23): this backend
|
|
501
|
+
has no message identity of its own, so it is accepted and ignored."""
|
|
500
502
|
if self._closed.is_set():
|
|
501
503
|
raise ConversationClosed(self._close_reason or "conversation closed")
|
|
502
504
|
if self.thread_id is None:
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""AGENTS.md composition for optio-codex: the profile, plus a thin wrapper over
|
|
2
|
+
the shared composer (``optio_agents.prompt.compose_instructions_file``), which
|
|
3
|
+
owns all of the text. The exclude list is resolved with the same function the
|
|
4
|
+
snapshot archive uses, so the section's preservation claims never drift."""
|
|
5
|
+
|
|
6
|
+
from optio_agents.prompt import AgentPromptProfile, compose_instructions_file
|
|
7
|
+
|
|
8
|
+
from optio_codex.snapshots import effective_workdir_exclude
|
|
9
|
+
|
|
10
|
+
__all__ = ["PROFILE", "compose_agents_md"]
|
|
11
|
+
|
|
12
|
+
PROFILE = AgentPromptProfile(
|
|
13
|
+
state_dir="home/.codex/",
|
|
14
|
+
state_dir_contents=(
|
|
15
|
+
"the codex session store (rollout files under `home/.codex/sessions`, "
|
|
16
|
+
"auth, config)"
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def compose_agents_md(
|
|
22
|
+
consumer_instructions: str,
|
|
23
|
+
*,
|
|
24
|
+
workdir_exclude: list[str] | None = None,
|
|
25
|
+
documentation: str | None = None,
|
|
26
|
+
supports_resume: bool = True,
|
|
27
|
+
host_protocol: bool = True,
|
|
28
|
+
omit_task_framing: bool = False,
|
|
29
|
+
fs_isolation_dirs: list[tuple[str, str]] | None = None,
|
|
30
|
+
file_download: bool = False,
|
|
31
|
+
check_resume_log_every_message: bool = True,
|
|
32
|
+
) -> str:
|
|
33
|
+
"""Render <workdir>/AGENTS.md for an optio-codex task.
|
|
34
|
+
|
|
35
|
+
``workdir_exclude=None`` means the codex defaults
|
|
36
|
+
(``snapshots.effective_workdir_exclude``), not the bare framework ones."""
|
|
37
|
+
return compose_instructions_file(
|
|
38
|
+
consumer_instructions,
|
|
39
|
+
profile=PROFILE,
|
|
40
|
+
workdir_exclude=effective_workdir_exclude(workdir_exclude),
|
|
41
|
+
documentation=documentation,
|
|
42
|
+
supports_resume=supports_resume,
|
|
43
|
+
host_protocol=host_protocol,
|
|
44
|
+
omit_task_framing=omit_task_framing,
|
|
45
|
+
fs_isolation_dirs=fs_isolation_dirs,
|
|
46
|
+
file_download=file_download,
|
|
47
|
+
check_resume_log_every_message=check_resume_log_every_message,
|
|
48
|
+
)
|
|
@@ -16,6 +16,7 @@ from optio_core.models import BasicAuth, TaskInstance
|
|
|
16
16
|
|
|
17
17
|
from optio_agents import HookContext, RESUME_NOTICE, SYSTEM_MESSAGE_PREFIX, get_protocol
|
|
18
18
|
from optio_agents import seeds as _seeds
|
|
19
|
+
from optio_agents.fs_grants import fs_isolation_dirs
|
|
19
20
|
from optio_agents.input_listener import serialized, start_input_listener
|
|
20
21
|
from optio_agents.protocol.session import _SessionFailed, run_log_protocol_session
|
|
21
22
|
from optio_agents.account import EMPTY, accounts_to_metadata
|
|
@@ -303,6 +304,7 @@ async def run_codex_session(ctx: ProcessContext, config: CodexTaskConfig) -> Non
|
|
|
303
304
|
workdir_exclude=config.workdir_exclude,
|
|
304
305
|
supports_resume=config.supports_resume,
|
|
305
306
|
file_download=config.file_download,
|
|
307
|
+
fs_isolation_dirs=fs_isolation_dirs(config, host.workdir),
|
|
306
308
|
),
|
|
307
309
|
)
|
|
308
310
|
else:
|
|
@@ -891,6 +893,7 @@ async def _maybe_refresh_on_resume(
|
|
|
891
893
|
workdir_exclude=new_config.workdir_exclude,
|
|
892
894
|
supports_resume=new_config.supports_resume,
|
|
893
895
|
file_download=new_config.file_download,
|
|
896
|
+
fs_isolation_dirs=fs_isolation_dirs(new_config, host.workdir),
|
|
894
897
|
)
|
|
895
898
|
try:
|
|
896
899
|
existing = await hook_ctx.read_text_from_host("AGENTS.md", silent=True)
|
|
@@ -16,6 +16,7 @@ from unittest.mock import AsyncMock, MagicMock
|
|
|
16
16
|
import pytest
|
|
17
17
|
|
|
18
18
|
from optio_agents import get_protocol
|
|
19
|
+
from optio_agents.fs_grants import fs_isolation_dirs
|
|
19
20
|
from optio_codex.prompt import compose_agents_md
|
|
20
21
|
from optio_codex.types import CodexTaskConfig
|
|
21
22
|
|
|
@@ -239,7 +240,7 @@ async def test_restore_workdir_blob_decrypts(monkeypatch):
|
|
|
239
240
|
# --- P2 unit: _maybe_refresh_on_resume -------------------------------------
|
|
240
241
|
|
|
241
242
|
|
|
242
|
-
def _resume_fakes(existing_agents_md: str):
|
|
243
|
+
def _resume_fakes(existing_agents_md: str, workdir: str = "/workdir"):
|
|
243
244
|
"""Build (host, hook_ctx) fakes whose read returns ``existing_agents_md``
|
|
244
245
|
and whose write records the rewritten body."""
|
|
245
246
|
written: dict[str, str] = {}
|
|
@@ -249,6 +250,7 @@ def _resume_fakes(existing_agents_md: str):
|
|
|
249
250
|
|
|
250
251
|
fake_host = MagicMock()
|
|
251
252
|
fake_host.write_text = _write_text
|
|
253
|
+
fake_host.workdir = workdir
|
|
252
254
|
|
|
253
255
|
fake_hook = MagicMock()
|
|
254
256
|
fake_hook.read_text_from_host = AsyncMock(return_value=existing_agents_md)
|
|
@@ -260,6 +262,7 @@ async def test_maybe_refresh_identity_unchanged_is_noop():
|
|
|
260
262
|
|
|
261
263
|
protocol = get_protocol(browser="redirect")
|
|
262
264
|
cfg = _cfg(consumer_instructions="orig")
|
|
265
|
+
workdir = "/workdir"
|
|
263
266
|
existing = compose_agents_md(
|
|
264
267
|
cfg.consumer_instructions,
|
|
265
268
|
documentation=protocol.documentation if cfg.host_protocol else None,
|
|
@@ -267,8 +270,9 @@ async def test_maybe_refresh_identity_unchanged_is_noop():
|
|
|
267
270
|
workdir_exclude=cfg.workdir_exclude,
|
|
268
271
|
supports_resume=cfg.supports_resume,
|
|
269
272
|
file_download=cfg.file_download,
|
|
273
|
+
fs_isolation_dirs=fs_isolation_dirs(cfg, workdir),
|
|
270
274
|
)
|
|
271
|
-
host, hook, written = _resume_fakes(existing)
|
|
275
|
+
host, hook, written = _resume_fakes(existing, workdir=workdir)
|
|
272
276
|
|
|
273
277
|
refreshed = await _maybe_refresh_on_resume(host, hook, cfg, protocol)
|
|
274
278
|
assert refreshed == []
|
|
@@ -285,12 +289,14 @@ async def test_maybe_refresh_mutating_hook_rewrites_agents_md():
|
|
|
285
289
|
return dataclasses.replace(c, consumer_instructions="UPDATED INSTRUCTIONS")
|
|
286
290
|
|
|
287
291
|
cfg = _cfg(consumer_instructions="orig", on_resume_refresh=_bump)
|
|
292
|
+
workdir = "/workdir"
|
|
288
293
|
stale = compose_agents_md(
|
|
289
294
|
"orig",
|
|
290
295
|
documentation=protocol.documentation,
|
|
291
296
|
host_protocol=True,
|
|
297
|
+
fs_isolation_dirs=fs_isolation_dirs(cfg, workdir),
|
|
292
298
|
)
|
|
293
|
-
host, hook, written = _resume_fakes(stale)
|
|
299
|
+
host, hook, written = _resume_fakes(stale, workdir=workdir)
|
|
294
300
|
|
|
295
301
|
refreshed = await _maybe_refresh_on_resume(host, hook, cfg, protocol)
|
|
296
302
|
assert refreshed == ["AGENTS.md"]
|
|
@@ -84,3 +84,13 @@ def test_host_protocol_false_keeps_resume_section_and_explainer():
|
|
|
84
84
|
# passing; same caveat as test_host_protocol_false_adds_system_explainer.)
|
|
85
85
|
assert get_protocol(browser="redirect").documentation not in md
|
|
86
86
|
assert "## Log channel" not in md
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_sandbox_note_and_no_prompt_text_of_its_own():
|
|
90
|
+
import inspect
|
|
91
|
+
import optio_codex.prompt as m
|
|
92
|
+
out = compose_agents_md("x", fs_isolation_dirs=[("/wd", "rwx")])
|
|
93
|
+
assert "**Filesystem access:**" in out
|
|
94
|
+
src = inspect.getsource(m)
|
|
95
|
+
assert "This harness may pause your session" not in src
|
|
96
|
+
assert "originate from the harness" not in src
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
"""AGENTS.md composition for optio-codex.
|
|
2
|
-
|
|
3
|
-
Codex reads an ``AGENTS.md`` file in its workdir. The shared framing and
|
|
4
|
-
the keyword-protocol documentation are owned by ``optio-agents`` (the
|
|
5
|
-
prompt SSOT); this module threads codex's protocol mode through and owns
|
|
6
|
-
the codex-specific resume-awareness section (Stage 2), rendered from the
|
|
7
|
-
EFFECTIVE snapshot exclude list so its preservation claims never drift
|
|
8
|
-
from what the snapshot actually keeps.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from optio_agents.prompt import compose_agents_md as _compose_agents_md_host
|
|
12
|
-
from optio_agents.prompt import downloadables_block
|
|
13
|
-
from optio_agents.protocol import ProtocolFeatures, build_log_channel_prompt
|
|
14
|
-
|
|
15
|
-
from optio_codex.snapshots import effective_workdir_exclude
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# Self-contained System: explainer for sessions without the keyword-protocol
|
|
19
|
-
# docs (which normally explain the convention). Per-wrapper copy is the
|
|
20
|
-
# established pattern (claudecode/opencode/grok each carry their own).
|
|
21
|
-
_SYSTEM_PREFIX_EXPLAINER = """\
|
|
22
|
-
(Messages prefixed `System:` on your input channel originate from the
|
|
23
|
-
harness coordinating this session, not from the human user.)
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
RESUME_SECTION_TEMPLATE = """## Resumes
|
|
28
|
-
|
|
29
|
-
This harness may pause your session, save your context to a database,
|
|
30
|
-
terminate the underlying process, and later rehydrate it. From your
|
|
31
|
-
point of view the conversation is fully continuous — you keep your
|
|
32
|
-
prior context and will not "notice" the resume.
|
|
33
|
-
|
|
34
|
-
**A resume can happen at any point, not only at the start.** The host
|
|
35
|
-
environment may have changed across a resume — different host,
|
|
36
|
-
different running processes, files outside this workdir gone — even
|
|
37
|
-
though your context remembers everything as alive and well.
|
|
38
|
-
|
|
39
|
-
**The workdir (this directory) is preserved across resumes, with two
|
|
40
|
-
caveats:**
|
|
41
|
-
|
|
42
|
-
- {excludes_clause}
|
|
43
|
-
- **Anything outside the workdir is not preserved.**
|
|
44
|
-
|
|
45
|
-
- **Your `home/.codex/` directory — the codex session store (rollout
|
|
46
|
-
files under `home/.codex/sessions`, auth, config) — IS preserved
|
|
47
|
-
across resumes** (minus the excluded paths above), so your history
|
|
48
|
-
travels with you even when the underlying process and host change.
|
|
49
|
-
|
|
50
|
-
{outside_clause}
|
|
51
|
-
|
|
52
|
-
### Detecting a resume: `resume.log`
|
|
53
|
-
|
|
54
|
-
Each session start (fresh or resumed) appends one line to
|
|
55
|
-
`./resume.log`. Line format:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
<ISO 8601 UTC timestamp>[ REFRESHED:<comma-separated filenames>]
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The very first line is the original launch timestamp; each subsequent
|
|
62
|
-
line is a resume. The optional `REFRESHED:` suffix signals that the
|
|
63
|
-
harness rewrote the listed files on that resume (e.g.
|
|
64
|
-
`2026-05-28T13:15:42Z REFRESHED:AGENTS.md`) — your in-memory copy of
|
|
65
|
-
those files is stale and must be re-read before continuing.
|
|
66
|
-
|
|
67
|
-
**At the start of every new incoming user message, read
|
|
68
|
-
`./resume.log` first.** Compare the latest line to the value you
|
|
69
|
-
remembered last time you checked. If a new line has appeared, treat
|
|
70
|
-
the situation as a resume:
|
|
71
|
-
|
|
72
|
-
- Verify any tools, processes, or files you previously gathered
|
|
73
|
-
outside the workdir are still where you left them.
|
|
74
|
-
- Re-establish anything that's gone (re-launch a server, re-fetch a
|
|
75
|
-
file, etc.) before continuing.
|
|
76
|
-
- **If the latest line carries a `REFRESHED:` suffix, re-read each
|
|
77
|
-
listed file** (e.g. `cat ./AGENTS.md`) — the harness updated it
|
|
78
|
-
since your last context snapshot and the version you remember is
|
|
79
|
-
out of date.
|
|
80
|
-
- Then resume the work you were doing.
|
|
81
|
-
|
|
82
|
-
If a resume slips past unnoticed, a failing tool call is the
|
|
83
|
-
next-best signal — re-check `./resume.log` then.
|
|
84
|
-
|
|
85
|
-
You may also be notified of a resume by a `System:` message on your input
|
|
86
|
-
channel; when you see one, follow the `resume.log` procedure above.
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _render_resume_section(workdir_exclude: list[str] | None) -> str:
|
|
91
|
-
"""Render RESUME_SECTION_TEMPLATE with the EFFECTIVE exclude list.
|
|
92
|
-
|
|
93
|
-
``effective_workdir_exclude`` is the same resolver the snapshot archive
|
|
94
|
-
uses (``None`` → the codex defaults), so what this section claims is
|
|
95
|
-
preserved is exactly what the snapshot preserves.
|
|
96
|
-
"""
|
|
97
|
-
effective = effective_workdir_exclude(workdir_exclude)
|
|
98
|
-
if not effective:
|
|
99
|
-
excludes_clause = (
|
|
100
|
-
"**No paths are excluded** — every file in the workdir is preserved."
|
|
101
|
-
)
|
|
102
|
-
outside_clause = (
|
|
103
|
-
"If you need to stash large data, place it outside the workdir "
|
|
104
|
-
"(e.g. `/tmp/`) — but remember it may be missing when you next look."
|
|
105
|
-
)
|
|
106
|
-
else:
|
|
107
|
-
excludes_str = ", ".join(f"`{p}`" for p in effective)
|
|
108
|
-
excludes_clause = (
|
|
109
|
-
f"**Paths matching the snapshot exclude list are NOT preserved**, "
|
|
110
|
-
f"even inside the workdir. The current exclude list is: {excludes_str}."
|
|
111
|
-
)
|
|
112
|
-
outside_clause = (
|
|
113
|
-
"If you need to stash large data, place it outside the workdir "
|
|
114
|
-
"(e.g. `/tmp/`) or inside an excluded subdirectory — but remember "
|
|
115
|
-
"any such location may be missing when you next look."
|
|
116
|
-
)
|
|
117
|
-
return RESUME_SECTION_TEMPLATE.format(
|
|
118
|
-
excludes_clause=excludes_clause,
|
|
119
|
-
outside_clause=outside_clause,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def compose_agents_md(
|
|
124
|
-
consumer_instructions: str,
|
|
125
|
-
*,
|
|
126
|
-
documentation: str | None = None,
|
|
127
|
-
host_protocol: bool = True,
|
|
128
|
-
workdir_exclude: list[str] | None = None,
|
|
129
|
-
supports_resume: bool = True,
|
|
130
|
-
file_download: bool = False,
|
|
131
|
-
) -> str:
|
|
132
|
-
"""Build the full AGENTS.md body.
|
|
133
|
-
|
|
134
|
-
``documentation`` is the keyword-protocol block; the session passes
|
|
135
|
-
``get_protocol(browser="redirect").documentation``. Defaults (for unit
|
|
136
|
-
tests / standalone callers) to codex's ``redirect`` docs. It must
|
|
137
|
-
always come from the session's ``Protocol`` where one exists — never
|
|
138
|
-
rebuild features at a second site.
|
|
139
|
-
|
|
140
|
-
``host_protocol=False`` omits the keyword-protocol documentation and
|
|
141
|
-
instead includes a self-contained ``System:`` message explainer
|
|
142
|
-
(guide Part 2D); iframe mode always runs with ``host_protocol=True``
|
|
143
|
-
(validated in ``CodexTaskConfig``), the False branch serves
|
|
144
|
-
conversation mode in a later stage.
|
|
145
|
-
|
|
146
|
-
``supports_resume=True`` (default) appends the resume-awareness section
|
|
147
|
-
so the agent watches ``resume.log`` and knows ``home/.codex`` (minus
|
|
148
|
-
``workdir_exclude``) survives across resumes. ``workdir_exclude`` is
|
|
149
|
-
this task's snapshot exclude list (None → the codex defaults), used to
|
|
150
|
-
keep the section's claims in sync with what is actually preserved.
|
|
151
|
-
|
|
152
|
-
``file_download=True`` appends the downloadables instruction block so
|
|
153
|
-
codex offers files to the human via the ``optio-file:`` sentinel link
|
|
154
|
-
(conversation_ui file-download feature); the wording is comparative when
|
|
155
|
-
the keyword protocol is active (``host_protocol``).
|
|
156
|
-
"""
|
|
157
|
-
if file_download:
|
|
158
|
-
consumer_instructions = (
|
|
159
|
-
consumer_instructions.rstrip()
|
|
160
|
-
+ downloadables_block(comparative=host_protocol)
|
|
161
|
-
)
|
|
162
|
-
if host_protocol:
|
|
163
|
-
if documentation is None:
|
|
164
|
-
documentation = build_log_channel_prompt(
|
|
165
|
-
ProtocolFeatures(browser="redirect")
|
|
166
|
-
)
|
|
167
|
-
else:
|
|
168
|
-
documentation = None
|
|
169
|
-
resume_section: str | None = (
|
|
170
|
-
_render_resume_section(workdir_exclude) if supports_resume else None
|
|
171
|
-
)
|
|
172
|
-
if not host_protocol:
|
|
173
|
-
# The protocol docs normally explain the `System:` convention;
|
|
174
|
-
# without them the composed prompt carries its own explainer.
|
|
175
|
-
resume_section = (
|
|
176
|
-
resume_section + _SYSTEM_PREFIX_EXPLAINER
|
|
177
|
-
if resume_section
|
|
178
|
-
else _SYSTEM_PREFIX_EXPLAINER
|
|
179
|
-
)
|
|
180
|
-
return _compose_agents_md_host(
|
|
181
|
-
consumer_instructions,
|
|
182
|
-
documentation=documentation,
|
|
183
|
-
resume_section=resume_section,
|
|
184
|
-
)
|
|
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
|
|
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
|
|
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
|
|
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
|