agentic-runner 3.12.0__tar.gz → 3.12.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.
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/PKG-INFO +1 -1
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/__init__.py +1 -1
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/cli.py +21 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/acp_runtime.py +155 -17
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/settings.py +3 -1
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workstation.py +28 -1
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/.gitignore +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/LICENSE +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/README.md +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/pyproject.toml +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/activities.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/attempts.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/auth_mode.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/build.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/callback.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/child_watcher.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/cli_floor.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/config.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/credentials.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/device_login_activities.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/egress.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/harness_self_test.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/heartbeat_link.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/hooks.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/host_store.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/__init__.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/__init__.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/contracts.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/evidence.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/workspace.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/github/__init__.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/github/auth.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/github/fake_client.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/github/gh_client.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/lifecycle.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/llm_proxy.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/mcp.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/message_store.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/oauth_connectors.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/private_state.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/py.typed +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/recipient_key_secret.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/registration.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/runtime/__init__.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/runtime/verifier_command.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/sealed_box.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/service.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/testing/__init__.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/testing/__main__.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/testing/control_plane.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/testing/plugin.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/testing/test_conformance.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/testing/workflow.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/tiny_http.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/triage_activities.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/usage_windows.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/user_sources.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/__init__.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/_runtime_support.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/agent_runtime.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/claude_runtime.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/claude_sign_in.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/codex_runtime.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/command_policy.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/contract_device_login.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/contract_isolation.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/fastapi_client.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/harness_outcome.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/harness_usage.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/mcp_config.py +0 -0
- {agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/skills.py +0 -0
|
@@ -158,6 +158,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
158
158
|
action="store_true",
|
|
159
159
|
help="no TLS to Temporal -- a local development server only",
|
|
160
160
|
)
|
|
161
|
+
install.add_argument(
|
|
162
|
+
"--acp-cli-kinds",
|
|
163
|
+
default=None,
|
|
164
|
+
help="comma-separated cli_kinds served through the pinned ACP bridge (codex_cli, "
|
|
165
|
+
"claude_code); omitted keeps the previous install's, '' turns ACP off",
|
|
166
|
+
)
|
|
161
167
|
install.add_argument("--no-start", action="store_true", help="write the agent, do not start")
|
|
162
168
|
install.add_argument("--root", default=None, type=Path, help="state root (default per OS)")
|
|
163
169
|
for service_verb in ("start", "stop"):
|
|
@@ -342,6 +348,11 @@ def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
|
342
348
|
if not arguments.control_plane:
|
|
343
349
|
print(f"--control-plane (or {CONTROL_PLANE_ENV}) is required", file=sys.stderr)
|
|
344
350
|
return 2
|
|
351
|
+
try:
|
|
352
|
+
acp_cli_kinds = _acp_cli_kinds(arguments.acp_cli_kinds, paths)
|
|
353
|
+
except ValueError as error:
|
|
354
|
+
print(str(error), file=sys.stderr)
|
|
355
|
+
return 2
|
|
345
356
|
# Pasted, never a flag: a flag is in the process table for every uid on the box.
|
|
346
357
|
token = os.environ.get(AGENT_TOKEN_ENV, "").strip() or getpass.getpass(
|
|
347
358
|
"Agent Token (yours from /me/runners, or the Organisation Admin's for an "
|
|
@@ -354,6 +365,7 @@ def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
|
354
365
|
temporal_tls=not arguments.temporal_plaintext,
|
|
355
366
|
path=os.environ.get("PATH", ""),
|
|
356
367
|
install_channel=workstation.install_channel(),
|
|
368
|
+
acp_cli_kinds=acp_cli_kinds,
|
|
357
369
|
)
|
|
358
370
|
try:
|
|
359
371
|
lines = asyncio.run(
|
|
@@ -369,6 +381,15 @@ def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
|
369
381
|
return 0
|
|
370
382
|
|
|
371
383
|
|
|
384
|
+
def _acp_cli_kinds(flag: str | None, paths: workstation.OrgPaths) -> str:
|
|
385
|
+
# A reinstall to pick up a new PATH or retry a token must not turn ACP off (QTS-1355).
|
|
386
|
+
if flag is not None:
|
|
387
|
+
return workstation.parse_acp_cli_kinds(flag)
|
|
388
|
+
if not paths.settings_file.is_file():
|
|
389
|
+
return ""
|
|
390
|
+
return workstation.WorkstationSettings.load(paths).acp_cli_kinds
|
|
391
|
+
|
|
392
|
+
|
|
372
393
|
def _callback(arguments: argparse.Namespace) -> int:
|
|
373
394
|
socket_path = os.environ.get(CALLBACK_SOCKET_ENV, "")
|
|
374
395
|
token = os.environ.get(CALLBACK_TOKEN_ENV, "")
|
|
@@ -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(
|
|
@@ -50,6 +50,7 @@ from agentic_runner.host_store import open_workstation_store
|
|
|
50
50
|
from agentic_runner.lifecycle import LifecycleOutbox
|
|
51
51
|
from agentic_runner.registration import load_state
|
|
52
52
|
from agentic_runner.service import HEARTBEAT_STAMP_NAME, PIDFILE_NAME
|
|
53
|
+
from agentic_runner.workers.acp_runtime import ACP_BRIDGES
|
|
53
54
|
from agentic_runner_contracts.runner_registration import (
|
|
54
55
|
CliVersion,
|
|
55
56
|
EgressPosture,
|
|
@@ -75,6 +76,7 @@ __all__ = [
|
|
|
75
76
|
"install",
|
|
76
77
|
"install_channel",
|
|
77
78
|
"locate_clis",
|
|
79
|
+
"parse_acp_cli_kinds",
|
|
78
80
|
"require_any_cli",
|
|
79
81
|
"run_environment",
|
|
80
82
|
"run_process",
|
|
@@ -204,6 +206,9 @@ class WorkstationSettings(BaseModel):
|
|
|
204
206
|
# would not find a Homebrew or `~/.local/bin` CLI without it.
|
|
205
207
|
path: str
|
|
206
208
|
install_channel: InstallChannel
|
|
209
|
+
# The Runner's `ACP_CLI_KINDS`, kept here rather than hand-added to the login agent,
|
|
210
|
+
# which every `install` rewrites (QTS-1355).
|
|
211
|
+
acp_cli_kinds: str = ""
|
|
207
212
|
|
|
208
213
|
def save(self, paths: OrgPaths) -> None:
|
|
209
214
|
paths.settings_file.write_text(self.model_dump_json(indent=2), encoding="utf-8")
|
|
@@ -218,6 +223,23 @@ class WorkstationSettings(BaseModel):
|
|
|
218
223
|
return cls.model_validate_json(paths.settings_file.read_text(encoding="utf-8"))
|
|
219
224
|
|
|
220
225
|
|
|
226
|
+
def parse_acp_cli_kinds(value: str) -> str:
|
|
227
|
+
"""``--acp-cli-kinds``, refused if it names a kind with no pinned bridge.
|
|
228
|
+
|
|
229
|
+
The Runner itself drops such a kind without a word, so a typo here would turn ACP off
|
|
230
|
+
as quietly as the rewritten login agent did (QTS-1355).
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
kinds = [kind.strip() for kind in value.split(",") if kind.strip()]
|
|
234
|
+
unknown = sorted(set(kinds) - ACP_BRIDGES.keys())
|
|
235
|
+
if unknown:
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"--acp-cli-kinds names {', '.join(unknown)}; a pinned ACP bridge exists only for "
|
|
238
|
+
f"{', '.join(sorted(ACP_BRIDGES))}"
|
|
239
|
+
)
|
|
240
|
+
return ",".join(dict.fromkeys(kinds))
|
|
241
|
+
|
|
242
|
+
|
|
221
243
|
def install_channel(
|
|
222
244
|
platform: str = sys.platform, *, executable: str = sys.executable
|
|
223
245
|
) -> InstallChannel:
|
|
@@ -235,7 +257,7 @@ def run_environment(paths: OrgPaths, settings: WorkstationSettings) -> dict[str,
|
|
|
235
257
|
a unit, a task -- stays a one-liner, and the three OSes cannot drift apart.
|
|
236
258
|
"""
|
|
237
259
|
|
|
238
|
-
|
|
260
|
+
environment = {
|
|
239
261
|
"PATH": settings.path,
|
|
240
262
|
"AGENTIC_RUNNER_STATE_DIR": str(paths.state_dir),
|
|
241
263
|
"AGENTIC_RUNNER_WORKSPACE_ROOT": str(paths.workspaces),
|
|
@@ -254,6 +276,11 @@ def run_environment(paths: OrgPaths, settings: WorkstationSettings) -> dict[str,
|
|
|
254
276
|
# port inherited from a pod-style environment would collide on the second one.
|
|
255
277
|
service.READINESS_PORT_ENV: "0",
|
|
256
278
|
}
|
|
279
|
+
# Only when set: a login agent hand-edited with `ACP_CLI_KINDS` before QTS-1355 keeps
|
|
280
|
+
# ACP on across an upgrade until its next `install`.
|
|
281
|
+
if settings.acp_cli_kinds:
|
|
282
|
+
environment["ACP_CLI_KINDS"] = settings.acp_cli_kinds
|
|
283
|
+
return environment
|
|
257
284
|
|
|
258
285
|
|
|
259
286
|
# ------------------------------------------------------------------ the CLIs
|
|
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.12.0 → agentic_runner-3.12.2}/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.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/contracts.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/evidence.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/git/workspace.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/github/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/github/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/integrations/github/gh_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/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.12.0 → agentic_runner-3.12.2}/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
|
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/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.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/_runtime_support.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/claude_runtime.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/claude_sign_in.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/command_policy.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/contract_device_login.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/contract_isolation.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/fastapi_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.12.0 → agentic_runner-3.12.2}/src/agentic_runner/workers/harness_outcome.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|