agentic-runner 3.11.0__tar.gz → 3.12.1__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.
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/PKG-INFO +1 -1
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/__init__.py +1 -1
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/service.py +38 -4
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/acp_runtime.py +155 -17
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/settings.py +3 -1
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/.gitignore +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/LICENSE +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/README.md +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/pyproject.toml +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/activities.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/attempts.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/auth_mode.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/build.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/callback.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/child_watcher.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/cli.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/cli_floor.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/config.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/credentials.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/device_login_activities.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/egress.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/harness_self_test.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/heartbeat_link.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/hooks.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/host_store.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/__init__.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/__init__.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/contracts.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/evidence.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/workspace.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/github/__init__.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/github/auth.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/github/fake_client.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/github/gh_client.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/lifecycle.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/llm_proxy.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/mcp.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/message_store.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/oauth_connectors.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/private_state.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/py.typed +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/recipient_key_secret.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/registration.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/runtime/__init__.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/runtime/verifier_command.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/sealed_box.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/testing/__init__.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/testing/__main__.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/testing/control_plane.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/testing/plugin.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/testing/test_conformance.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/testing/workflow.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/tiny_http.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/triage_activities.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/usage_windows.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/user_sources.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/__init__.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/_runtime_support.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/agent_runtime.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/claude_runtime.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/claude_sign_in.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/codex_runtime.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/command_policy.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/contract_device_login.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/contract_isolation.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/fastapi_client.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/harness_outcome.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/harness_usage.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/mcp_config.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/skills.py +0 -0
- {agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workstation.py +0 -0
|
@@ -121,6 +121,7 @@ from agentic_runner_contracts.channel_messages import TranscriptDelivery
|
|
|
121
121
|
from agentic_runner_contracts.routing import RunnerRoutingIdentity
|
|
122
122
|
from agentic_runner_contracts.runner_registration import (
|
|
123
123
|
RECENT_OUTCOMES_MAX,
|
|
124
|
+
AcpBridgeVersion,
|
|
124
125
|
CliVersion,
|
|
125
126
|
DirectiveOutcome,
|
|
126
127
|
EgressPosture,
|
|
@@ -411,13 +412,12 @@ def build_agent_runtimes(
|
|
|
411
412
|
local-agents 18) reaches `clis` through :func:`with_profile_acp_cli_kinds`.
|
|
412
413
|
"""
|
|
413
414
|
|
|
414
|
-
|
|
415
|
+
pinned_kinds = _pinned_acp_cli_kinds(settings)
|
|
415
416
|
profile_kinds = set(profile_acp_executables(settings))
|
|
416
417
|
return {
|
|
417
418
|
cli.cli_kind: (
|
|
418
419
|
AcpRuntime(cli_kind=cli.cli_kind, settings=settings)
|
|
419
|
-
if cli.cli_kind in profile_kinds
|
|
420
|
-
or (cli.cli_kind in acp_kinds and cli.cli_kind in ACP_BRIDGES)
|
|
420
|
+
if cli.cli_kind in profile_kinds or cli.cli_kind in pinned_kinds
|
|
421
421
|
else _RUNTIMES[cli.cli_kind](settings)
|
|
422
422
|
)
|
|
423
423
|
for cli in clis
|
|
@@ -425,6 +425,40 @@ def build_agent_runtimes(
|
|
|
425
425
|
}
|
|
426
426
|
|
|
427
427
|
|
|
428
|
+
def _pinned_acp_cli_kinds(settings: WorkerSettings) -> set[str]:
|
|
429
|
+
"""The kinds `ACP_CLI_KINDS` routes through a bridge this Runner pins."""
|
|
430
|
+
|
|
431
|
+
named = {kind.strip() for kind in settings.ACP_CLI_KINDS.split(",") if kind.strip()}
|
|
432
|
+
return named & ACP_BRIDGES.keys()
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def with_acp_bridges(
|
|
436
|
+
attestation: HostAttestation | None, settings: WorkerSettings
|
|
437
|
+
) -> HostAttestation | None:
|
|
438
|
+
"""The attestation, with each kind this Runner serves through its pinned ACP bridge
|
|
439
|
+
carrying that bridge's package and version (QTS-1322), so the console shows what
|
|
440
|
+
actually drives the CLI. A kind below its floor is not served, so it names no bridge.
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
if attestation is None:
|
|
444
|
+
return None
|
|
445
|
+
pinned_kinds = _pinned_acp_cli_kinds(settings)
|
|
446
|
+
clis = [
|
|
447
|
+
cli.model_copy(
|
|
448
|
+
update={
|
|
449
|
+
"acp_bridge": AcpBridgeVersion(
|
|
450
|
+
package=ACP_BRIDGES[cli.cli_kind].package,
|
|
451
|
+
version=ACP_BRIDGES[cli.cli_kind].version,
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
if cli.meets_floor and cli.cli_kind in pinned_kinds
|
|
456
|
+
else cli
|
|
457
|
+
for cli in attestation.clis
|
|
458
|
+
]
|
|
459
|
+
return attestation.model_copy(update={"clis": clis})
|
|
460
|
+
|
|
461
|
+
|
|
428
462
|
def with_profile_acp_cli_kinds(
|
|
429
463
|
attestation: HostAttestation | None, settings: WorkerSettings
|
|
430
464
|
) -> HostAttestation | None:
|
|
@@ -958,7 +992,7 @@ async def run(
|
|
|
958
992
|
os.environ.setdefault("INTERNAL_FASTAPI_BASE_URL", control_plane)
|
|
959
993
|
settings = get_worker_settings()
|
|
960
994
|
try:
|
|
961
|
-
attestation = with_profile_acp_cli_kinds(attestation, settings)
|
|
995
|
+
attestation = with_profile_acp_cli_kinds(with_acp_bridges(attestation, settings), settings)
|
|
962
996
|
except ValueError as error:
|
|
963
997
|
print(f"refusing to start: {error}", flush=True)
|
|
964
998
|
return 1
|
|
@@ -34,7 +34,19 @@ Facts this module stands on (the LA-02 spike, re-read against the pinned bridge
|
|
|
34
34
|
is refused, and a harness-root ``config.toml`` may hold only ``_CODEX_HARNESS_ROOT_KEYS``
|
|
35
35
|
(LA-19): nothing may load that the Runner did not choose.
|
|
36
36
|
- claude-agent-acp loads the Workspace's settings, hooks, ``.mcp.json`` and ``CLAUDE.md``
|
|
37
|
-
unless ``session/new`` carries the three options in ``_CLAUDE_SESSION_OPTIONS``.
|
|
37
|
+
unless ``session/new`` carries the three options in ``_CLAUDE_SESSION_OPTIONS``. The
|
|
38
|
+
harness root is the Contract's uid's to write, and its ``user`` source is more than
|
|
39
|
+
``settings.json``: a skill's or command's ``allowed-tools`` is an allow rule, and an
|
|
40
|
+
agent, a hook or ``CLAUDE.md`` would steer every later Directive of that Contract. So no
|
|
41
|
+
setting source loads at all, the ``user`` one included: measured on claude 2.1.280, the
|
|
42
|
+
harness root's skills, commands, agents, ``CLAUDE.md`` and settings stay out while its
|
|
43
|
+
``.credentials.json`` login still signs the requests (LA-12d item 2). The bridge itself
|
|
44
|
+
still reads the harness root's ``settings.json`` for the initial mode, bypass and model
|
|
45
|
+
list, so that file may hold only ``_CLAUDE_HARNESS_ROOT_KEYS``.
|
|
46
|
+
- A Claude permission request for an MCP tool names no command. It carries the serving
|
|
47
|
+
server in ``_meta.claudeCode.mcpServer``, and ``source: "dynamic"`` is a server from the
|
|
48
|
+
CLI's ``--mcp-config``: the ones ``session/new`` sent, the only ones ``strictMcpConfig``
|
|
49
|
+
loads. A granted server's tool is allowed (LA-12d item 1).
|
|
38
50
|
|
|
39
51
|
A ``cli_kind`` with no pinned bridge (local-agents 18) runs the command its Agent Runtime
|
|
40
52
|
Profile names, and only on a Runner whose host pinned that kind's executable in
|
|
@@ -187,9 +199,21 @@ _CODEX_HARNESS_ROOT_KEYS: Final[frozenset[str]] = frozenset(
|
|
|
187
199
|
{"model", "model_reasoning_effort", "model_reasoning_summary", "model_verbosity", "notice"}
|
|
188
200
|
)
|
|
189
201
|
|
|
202
|
+
# What a harness-root `settings.json` may set: the choices Claude Code writes there itself.
|
|
203
|
+
# The CLI no longer loads it, but the bridge does; `permissions`, `hooks`, `env`,
|
|
204
|
+
# `apiKeyHelper`, `enabledPlugins` and the rest would be the Contract's own file steering it.
|
|
205
|
+
_CLAUDE_HARNESS_ROOT_KEYS: Final[frozenset[str]] = frozenset(
|
|
206
|
+
{"$schema", "model", "effortLevel", "alwaysThinkingEnabled"}
|
|
207
|
+
)
|
|
208
|
+
# The `mcpServer.source` of a server the CLI got from `--mcp-config`, which is where the
|
|
209
|
+
# bridge puts `session/new`'s servers. A name alone is the config key as authored, so a
|
|
210
|
+
# server from any other source could reuse a granted slug.
|
|
211
|
+
_CLAUDE_SENT_MCP_SOURCE: Final[str] = "dynamic"
|
|
212
|
+
|
|
190
213
|
_CLAUDE_SESSION_OPTIONS: Final[Mapping[str, object]] = {
|
|
191
|
-
#
|
|
192
|
-
|
|
214
|
+
# No settings file, hook, skill, command, agent or CLAUDE.md from anywhere, the
|
|
215
|
+
# Contract's harness root included; the login is not a setting source and still loads.
|
|
216
|
+
"settingSources": [],
|
|
193
217
|
# Only the servers sent over ACP; never the Workspace's `.mcp.json`.
|
|
194
218
|
"strictMcpConfig": True,
|
|
195
219
|
# Bypass is never offered as a mode, whatever a settings file asks for.
|
|
@@ -267,6 +291,7 @@ class _Turn:
|
|
|
267
291
|
output: list[str] = field(default_factory=list)
|
|
268
292
|
notes: list[str] = field(default_factory=list)
|
|
269
293
|
held: bool = False
|
|
294
|
+
session_id: str = ""
|
|
270
295
|
usage_windows: tuple[UsageWindow, ...] = ()
|
|
271
296
|
|
|
272
297
|
|
|
@@ -389,6 +414,10 @@ class AcpRuntime:
|
|
|
389
414
|
)
|
|
390
415
|
if config_refusal:
|
|
391
416
|
return refuse("refused: Codex config file", config_refusal)
|
|
417
|
+
if self._is_claude and harness_root is not None:
|
|
418
|
+
settings_refusal = _claude_settings_refusal(harness_root)
|
|
419
|
+
if settings_refusal:
|
|
420
|
+
return refuse("refused: Claude settings file", settings_refusal)
|
|
392
421
|
|
|
393
422
|
# The activity refuses this first, with Evidence; checked again where it runs.
|
|
394
423
|
command_refusal = self.acp_command_refusal(request.acp_command)
|
|
@@ -539,7 +568,7 @@ class AcpRuntime:
|
|
|
539
568
|
with recorded_group(process.pid):
|
|
540
569
|
try:
|
|
541
570
|
stop_reason, model_usage, error = await asyncio.wait_for(
|
|
542
|
-
self._turn(connection, request, workspace_path, extra_env),
|
|
571
|
+
self._turn(connection, request, workspace_path, extra_env, turn),
|
|
543
572
|
timeout=self._timeout_seconds,
|
|
544
573
|
)
|
|
545
574
|
finally:
|
|
@@ -551,6 +580,17 @@ class AcpRuntime:
|
|
|
551
580
|
await terminate_process_tree(process)
|
|
552
581
|
raise
|
|
553
582
|
stderr = await stderr_task
|
|
583
|
+
if self._is_codex and turn.session_id:
|
|
584
|
+
# After the bridge exited, so Codex has flushed the rollout, and before the
|
|
585
|
+
# api_key attempt root holding it is removed.
|
|
586
|
+
codex_home = Path(env[ACP_BRIDGES["codex_cli"].config_root_env])
|
|
587
|
+
total = await asyncio.to_thread(
|
|
588
|
+
_codex_rollout_usage, codex_home, turn.session_id, model_usage
|
|
589
|
+
)
|
|
590
|
+
if total is None:
|
|
591
|
+
turn.notes.append("usage: the bridge's last model call only; no rollout total")
|
|
592
|
+
else:
|
|
593
|
+
model_usage = total
|
|
554
594
|
return stop_reason, model_usage, stderr, error
|
|
555
595
|
|
|
556
596
|
async def _turn(
|
|
@@ -559,6 +599,7 @@ class AcpRuntime:
|
|
|
559
599
|
request: DirectiveRequest,
|
|
560
600
|
workspace_path: Path,
|
|
561
601
|
extra_env: Mapping[str, str],
|
|
602
|
+
turn: _Turn,
|
|
562
603
|
) -> tuple[str, tuple[ModelUsage, ...], str]:
|
|
563
604
|
try:
|
|
564
605
|
await connection.request(
|
|
@@ -584,7 +625,7 @@ class AcpRuntime:
|
|
|
584
625
|
}
|
|
585
626
|
}
|
|
586
627
|
session = await connection.request("session/new", session_params)
|
|
587
|
-
session_id = str(session.get("sessionId") or "")
|
|
628
|
+
session_id = turn.session_id = str(session.get("sessionId") or "")
|
|
588
629
|
if self._is_claude and _offers_mode(session, _CLAUDE_MODE):
|
|
589
630
|
await connection.request(
|
|
590
631
|
"session/set_mode", {"sessionId": session_id, "modeId": _CLAUDE_MODE}
|
|
@@ -625,19 +666,22 @@ class AcpRuntime:
|
|
|
625
666
|
turn: _Turn,
|
|
626
667
|
) -> Mapping[str, Any]:
|
|
627
668
|
tool_call = params.get("toolCall") or {}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
shlex.join(argv)
|
|
632
|
-
if argv
|
|
633
|
-
else str(tool_call.get("title") or "an action")
|
|
634
|
-
if isinstance(tool_call, Mapping)
|
|
635
|
-
else "an action"
|
|
669
|
+
title = str(tool_call.get("title") or "") if isinstance(tool_call, Mapping) else ""
|
|
670
|
+
mcp_server = (
|
|
671
|
+
_granted_mcp_server(tool_call, request.mcp_servers or ()) if self._is_claude else None
|
|
636
672
|
)
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
673
|
+
if mcp_server is not None:
|
|
674
|
+
# Checked first: an MCP tool's arguments are not a command, even one named so.
|
|
675
|
+
described = _redact(f"{title} (MCP server {mcp_server})")
|
|
676
|
+
verdict, reason = _Verdict.ALLOW, "a granted MCP server's tool"
|
|
677
|
+
else:
|
|
678
|
+
raw_input = tool_call.get("rawInput") if isinstance(tool_call, Mapping) else None
|
|
679
|
+
argv, cwd = _requested_command(raw_input, workspace_path)
|
|
680
|
+
described = _redact(shlex.join(argv) if argv else title or "an action")
|
|
681
|
+
# The Directive's own Workspace, not WORKSPACE_ROOT: without a Contract uid
|
|
682
|
+
# nothing else keeps an allowlisted command out of another Work Record's
|
|
683
|
+
# Workspace.
|
|
684
|
+
verdict, reason = _verdict(argv, cwd, request, workspace_path)
|
|
641
685
|
options = params.get("options") or []
|
|
642
686
|
if verdict == _Verdict.ALLOW:
|
|
643
687
|
turn.notes.append(f"permission allowed: {described}")
|
|
@@ -879,6 +923,44 @@ def _codex_config_refusal(workspace_path: Path, harness_root: Path | None) -> st
|
|
|
879
923
|
return None
|
|
880
924
|
|
|
881
925
|
|
|
926
|
+
def _claude_settings_refusal(harness_root: Path) -> str | None:
|
|
927
|
+
"""Why the harness root's ``settings.json`` refuses a Claude Directive, or None.
|
|
928
|
+
|
|
929
|
+
As ``_codex_config_refusal``: the key names, never the path, reach the control plane.
|
|
930
|
+
"""
|
|
931
|
+
|
|
932
|
+
try:
|
|
933
|
+
text = (harness_root / "settings.json").read_text(encoding="utf-8")
|
|
934
|
+
except FileNotFoundError:
|
|
935
|
+
return None
|
|
936
|
+
except OSError:
|
|
937
|
+
return "the harness root's settings.json cannot be read"
|
|
938
|
+
try:
|
|
939
|
+
settings = json.loads(text)
|
|
940
|
+
except ValueError:
|
|
941
|
+
return "the harness root's settings.json does not parse"
|
|
942
|
+
if not isinstance(settings, dict):
|
|
943
|
+
return "the harness root's settings.json is not an object"
|
|
944
|
+
unexpected = sorted(set(settings) - _CLAUDE_HARNESS_ROOT_KEYS)
|
|
945
|
+
if unexpected:
|
|
946
|
+
return f"the harness root's settings.json sets {', '.join(unexpected)}; remove them"
|
|
947
|
+
return None
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def _granted_mcp_server(tool_call: object, granted: Sequence[McpServerEntry]) -> str | None:
|
|
951
|
+
"""The granted server a Claude permission request's MCP tool is served by, or None."""
|
|
952
|
+
|
|
953
|
+
if not isinstance(tool_call, Mapping):
|
|
954
|
+
return None
|
|
955
|
+
meta = tool_call.get("_meta")
|
|
956
|
+
claude = meta.get("claudeCode") if isinstance(meta, Mapping) else None
|
|
957
|
+
server = claude.get("mcpServer") if isinstance(claude, Mapping) else None
|
|
958
|
+
if not isinstance(server, Mapping) or server.get("source") != _CLAUDE_SENT_MCP_SOURCE:
|
|
959
|
+
return None
|
|
960
|
+
name = server.get("name")
|
|
961
|
+
return name if isinstance(name, str) and name in {e.slug for e in granted} else None
|
|
962
|
+
|
|
963
|
+
|
|
882
964
|
@contextlib.contextmanager
|
|
883
965
|
def _attempt_codex_home(
|
|
884
966
|
proxy_url: str, harness_root: Path, sandbox: DirectiveSandbox | None
|
|
@@ -1034,6 +1116,62 @@ def _model_usage(response: Mapping[str, Any]) -> tuple[ModelUsage, ...]:
|
|
|
1034
1116
|
return tuple(usage)
|
|
1035
1117
|
|
|
1036
1118
|
|
|
1119
|
+
def _codex_rollout_usage(
|
|
1120
|
+
codex_home: Path, session_id: str, reported: tuple[ModelUsage, ...]
|
|
1121
|
+
) -> tuple[ModelUsage, ...] | None:
|
|
1122
|
+
"""The whole turn's usage from the Codex thread's rollout, or None if it has none.
|
|
1123
|
+
|
|
1124
|
+
codex-acp 2.1.1 reports ``tokenUsage.last`` -- the turn's last model call -- as the
|
|
1125
|
+
prompt response's ``model_usage`` (LA-12d item 4). One Directive is one session with
|
|
1126
|
+
one turn, so the thread's ``total_token_usage`` is that turn's. The ACP session id is
|
|
1127
|
+
the Codex thread id, which names the rollout file.
|
|
1128
|
+
"""
|
|
1129
|
+
|
|
1130
|
+
# The id goes into a glob, and a subscription harness root keeps other Directives'
|
|
1131
|
+
# rollouts: anything but a thread id could match, and meter, one of theirs.
|
|
1132
|
+
try:
|
|
1133
|
+
uuid.UUID(session_id)
|
|
1134
|
+
except ValueError:
|
|
1135
|
+
return None
|
|
1136
|
+
total: Mapping[str, Any] | None = None
|
|
1137
|
+
model = reported[0].model if reported else None
|
|
1138
|
+
try:
|
|
1139
|
+
for path in (codex_home / "sessions").rglob(f"rollout-*-{session_id}.jsonl"):
|
|
1140
|
+
with path.open(encoding="utf-8") as rollout:
|
|
1141
|
+
for line in rollout:
|
|
1142
|
+
try:
|
|
1143
|
+
entry = json.loads(line)
|
|
1144
|
+
except ValueError:
|
|
1145
|
+
continue
|
|
1146
|
+
payload = entry.get("payload") if isinstance(entry, dict) else None
|
|
1147
|
+
if not isinstance(payload, dict):
|
|
1148
|
+
continue
|
|
1149
|
+
if entry.get("type") == "turn_context" and isinstance(
|
|
1150
|
+
payload.get("model"), str
|
|
1151
|
+
):
|
|
1152
|
+
model = model or payload["model"]
|
|
1153
|
+
info = payload.get("info") if payload.get("type") == "token_count" else None
|
|
1154
|
+
usage = info.get("total_token_usage") if isinstance(info, dict) else None
|
|
1155
|
+
if isinstance(usage, dict):
|
|
1156
|
+
total = usage
|
|
1157
|
+
except OSError:
|
|
1158
|
+
return None
|
|
1159
|
+
if total is None or model is None:
|
|
1160
|
+
return None
|
|
1161
|
+
cached = _count(total, "cached_input_tokens")
|
|
1162
|
+
return (
|
|
1163
|
+
ModelUsage(
|
|
1164
|
+
model=model,
|
|
1165
|
+
# Net of the cached part, as the bridge's own count is.
|
|
1166
|
+
input_tokens=max(_count(total, "input_tokens") - cached, 0),
|
|
1167
|
+
output_tokens=_count(total, "output_tokens"),
|
|
1168
|
+
cached_read_tokens=cached,
|
|
1169
|
+
cached_write_tokens=_count(total, "cache_write_input_tokens"),
|
|
1170
|
+
reasoning_output_tokens=_count(total, "reasoning_output_tokens"),
|
|
1171
|
+
),
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
|
|
1037
1175
|
def _count(values: Mapping[str, Any], key: str) -> int:
|
|
1038
1176
|
value = values.get(key)
|
|
1039
1177
|
return int(value) if isinstance(value, int | float) and not isinstance(value, bool) else 0
|
|
@@ -137,7 +137,9 @@ class WorkerSettings(BaseSettings):
|
|
|
137
137
|
default="",
|
|
138
138
|
description=(
|
|
139
139
|
"Comma-separated cli_kinds (`codex_cli`, `claude_code`) this Runner serves through "
|
|
140
|
-
"the pinned ACP bridge instead of the per-CLI runtime (local-agents 12)."
|
|
140
|
+
"the pinned ACP bridge instead of the per-CLI runtime (local-agents 12). Never "
|
|
141
|
+
"`codex_cli` on a Runner pod: its bwrap sandbox cannot start under the default "
|
|
142
|
+
"seccomp profile (LA-12d item 3)."
|
|
141
143
|
),
|
|
142
144
|
)
|
|
143
145
|
ACP_PROFILE_CLI_KINDS: str = Field(
|
|
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
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/device_login_activities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/contracts.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/evidence.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/git/workspace.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/github/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/github/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/github/gh_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/integrations/oauth/__init__.py
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
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/runtime/verifier_command.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/testing/test_conformance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/_runtime_support.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/claude_runtime.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/claude_sign_in.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/command_policy.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/contract_device_login.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/contract_isolation.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/fastapi_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.11.0 → agentic_runner-3.12.1}/src/agentic_runner/workers/harness_outcome.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|