agentic-runner 3.28.1__tar.gz → 3.30.0__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.28.1 → agentic_runner-3.30.0}/PKG-INFO +2 -2
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/pyproject.toml +1 -1
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/__init__.py +1 -1
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/cli.py +57 -1
- agentic_runner-3.30.0/src/agentic_runner/organisation_name.py +30 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/service.py +38 -7
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workstation.py +74 -10
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/.gitignore +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/LICENSE +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/README.md +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/activities.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/attempts.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/auth_mode.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/build.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/callback.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/child_watcher.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/cli_floor.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/config.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/credentials.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/egress.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/harness_self_test.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/heartbeat_link.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/hooks.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/host_store.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/__init__.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/__init__.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/contracts.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/evidence.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/workspace.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/github/__init__.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/github/auth.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/github/fake_client.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/github/gh_client.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/launch.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/lifecycle.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/llm_contracts.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/llm_proxy.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/mcp.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/message_store.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/oauth_connectors.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/pool_key.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/private_state.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/py.typed +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/recipient_key_secret.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/registration.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/runner_limit.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/runtime/__init__.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/runtime/verifier_command.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/sealed_box.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/sign_in.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/skill.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/testing/__init__.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/testing/__main__.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/testing/control_plane.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/testing/plugin.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/testing/test_conformance.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/testing/workflow.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/tiny_http.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/triage_activities.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/usage_windows.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/user_sources.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/__init__.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/_runtime_support.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/acp_runtime.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/agent_runtime.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/claude_runtime.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/claude_sign_in.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/codex_runtime.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/command_policy.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/contract_isolation.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/fastapi_client.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/harness_logins.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/harness_outcome.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/harness_usage.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/mcp_config.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/settings.py +0 -0
- {agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/skills.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentic-runner
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.30.0
|
|
4
4
|
Summary: The Agentic OS Runner: an activity-only Temporal executor for workspaces, Agent Runtimes and verb seams
|
|
5
5
|
License-Expression: AGPL-3.0-only
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.12
|
|
8
|
-
Requires-Dist: agentic-runner-contracts==3.
|
|
8
|
+
Requires-Dist: agentic-runner-contracts==3.30.0
|
|
9
9
|
Requires-Dist: cryptography<51,>=50
|
|
10
10
|
Requires-Dist: httpx<1.0,>=0.27
|
|
11
11
|
Requires-Dist: pydantic-settings<3.0,>=2.0
|
|
@@ -10,7 +10,7 @@ dependencies = [
|
|
|
10
10
|
# Exactly this release's contracts: the two ship as one version, and an unpinned
|
|
11
11
|
# requirement let `uv tool upgrade` keep an older contracts wheel the Runner cannot
|
|
12
12
|
# import from (QTS-1473). `scripts/set-version.py` moves it with the version.
|
|
13
|
-
"agentic-runner-contracts==3.
|
|
13
|
+
"agentic-runner-contracts==3.30.0",
|
|
14
14
|
# Imported directly by `registration.py` (the Ed25519 identity that signs every
|
|
15
15
|
# heartbeat); already present transitively through pyjwt[crypto], declared because
|
|
16
16
|
# we import it.
|
|
@@ -11,7 +11,7 @@ namespace and queue this process was assigned. It is idempotent by state file --
|
|
|
11
11
|
restart re-reads what is already there rather than registering a second Runner against
|
|
12
12
|
the Organisation's cap.
|
|
13
13
|
|
|
14
|
-
``install | start | stop | status <name>``, ``credential set`` and ``list`` are the
|
|
14
|
+
``install | start | stop | restart | status <name>``, ``credential set`` and ``list`` are the
|
|
15
15
|
workstation Runner (PRD issue 47, ``workstation.py``): one login agent per Runner, managed
|
|
16
16
|
by the user who hosts it, with no ``sudo`` anywhere. ``<name>`` is the Runner's, the one the
|
|
17
17
|
console lists it under (QTS-1773); a machine with one Runner needs none (QTS-1774).
|
|
@@ -215,6 +215,17 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
215
215
|
driven.add_argument("name", nargs="?", help=_INSTALLED_NAME_HELP)
|
|
216
216
|
driven.add_argument("--root", default=None, type=Path)
|
|
217
217
|
|
|
218
|
+
restart = subcommands.add_parser(
|
|
219
|
+
"restart",
|
|
220
|
+
help="restart running workstation Runners on the agentic-runner installed now, as "
|
|
221
|
+
"after `uv tool upgrade`",
|
|
222
|
+
)
|
|
223
|
+
restart.add_argument("name", nargs="?", help=_INSTALLED_NAME_HELP)
|
|
224
|
+
which = restart.add_mutually_exclusive_group()
|
|
225
|
+
which.add_argument("--name", dest="name_flag", default=None, help=_INSTALLED_NAME_HELP)
|
|
226
|
+
which.add_argument("--all", action="store_true", help="every Runner running on this machine")
|
|
227
|
+
restart.add_argument("--root", default=None, type=Path)
|
|
228
|
+
|
|
218
229
|
listed = subcommands.add_parser(
|
|
219
230
|
"list", help="the workstation Runners installed on this machine, and which are running"
|
|
220
231
|
)
|
|
@@ -368,6 +379,8 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
368
379
|
return asyncio.run(service.run(attestation=_image_attestation(), install=_run_install()))
|
|
369
380
|
if arguments.command in {"install", "start", "stop", "credential"}:
|
|
370
381
|
return _workstation(arguments)
|
|
382
|
+
if arguments.command == "restart":
|
|
383
|
+
return _restart(arguments)
|
|
371
384
|
if arguments.command == "list":
|
|
372
385
|
for line in workstation.list_lines(arguments.root or workstation.default_root()):
|
|
373
386
|
print(line)
|
|
@@ -462,6 +475,49 @@ def _workstation(arguments: argparse.Namespace) -> int:
|
|
|
462
475
|
return 1
|
|
463
476
|
|
|
464
477
|
|
|
478
|
+
def _restart(arguments: argparse.Namespace) -> int:
|
|
479
|
+
"""``restart <name> | --name <name> | --all``: exit 2 names the Runners installed here.
|
|
480
|
+
|
|
481
|
+
A stopped Runner is not started: it was stopped on purpose, and ``systemctl restart``
|
|
482
|
+
would start it where ``launchctl kickstart`` refuses to, so the two OSes would differ.
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
root = arguments.root or workstation.default_root()
|
|
486
|
+
name = arguments.name or arguments.name_flag
|
|
487
|
+
if arguments.all and name:
|
|
488
|
+
print("restart takes a name or --all, not both", file=sys.stderr)
|
|
489
|
+
return 2
|
|
490
|
+
try:
|
|
491
|
+
if arguments.all:
|
|
492
|
+
targets = [
|
|
493
|
+
workstation.RunnerPaths(root=root, name=installed)
|
|
494
|
+
for installed in workstation.installed_names(root)
|
|
495
|
+
]
|
|
496
|
+
if not targets:
|
|
497
|
+
raise ValueError(f"no workstation Runner is installed under {root}")
|
|
498
|
+
elif name:
|
|
499
|
+
targets = [workstation.installed_runner(root, name)]
|
|
500
|
+
else:
|
|
501
|
+
targets = [workstation.only_runner(root, how="name one, or restart --all")]
|
|
502
|
+
except ValueError as error:
|
|
503
|
+
print(error, file=sys.stderr)
|
|
504
|
+
return 2
|
|
505
|
+
failed = False
|
|
506
|
+
for paths in targets:
|
|
507
|
+
if workstation.process_id(paths) is None:
|
|
508
|
+
print(f"{paths.name} is stopped; `agentic-runner start {paths.name}` starts it")
|
|
509
|
+
failed = failed or not arguments.all
|
|
510
|
+
continue
|
|
511
|
+
try:
|
|
512
|
+
pid, version = workstation.restart(paths)
|
|
513
|
+
except (FileNotFoundError, RuntimeError) as error:
|
|
514
|
+
print(f"{paths.name}: restart failed: {error}", file=sys.stderr)
|
|
515
|
+
failed = True
|
|
516
|
+
continue
|
|
517
|
+
print(f"restarted {paths.name}: pid {pid}, agentic-runner {version}")
|
|
518
|
+
return 1 if failed else 0
|
|
519
|
+
|
|
520
|
+
|
|
465
521
|
def _image_attestation() -> HostAttestation:
|
|
466
522
|
"""What a container Runner says about itself, where issue 47 gave the workstation its
|
|
467
523
|
own: the CLIs it found are what the registry serves and routing matches on."""
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""The Organisation this Runner serves, by the name the last heartbeat ack gave (QTS-1909).
|
|
2
|
+
|
|
3
|
+
``agentic-runner list`` is a separate process and reads it from this file, which the
|
|
4
|
+
running Runner rewrites whenever an ack changes it. Absent while the control plane names
|
|
5
|
+
no Organisation -- one below contracts 3.29 -- so ``list`` shows the id instead.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Final
|
|
12
|
+
|
|
13
|
+
from agentic_runner.private_state import private_read, private_write
|
|
14
|
+
|
|
15
|
+
__all__ = ["ORGANISATION_NAME_FILENAME", "load_organisation_name", "save_organisation_name"]
|
|
16
|
+
|
|
17
|
+
ORGANISATION_NAME_FILENAME: Final[str] = "organisation-name"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def save_organisation_name(state_dir: Path, name: str | None) -> None:
|
|
21
|
+
path = state_dir / ORGANISATION_NAME_FILENAME
|
|
22
|
+
if name is None:
|
|
23
|
+
path.unlink(missing_ok=True)
|
|
24
|
+
else:
|
|
25
|
+
private_write(path, name.encode("utf-8"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_organisation_name(state_dir: Path) -> str | None:
|
|
29
|
+
raw = private_read(state_dir / ORGANISATION_NAME_FILENAME)
|
|
30
|
+
return None if raw is None else raw.decode("utf-8", errors="replace")
|
|
@@ -33,7 +33,7 @@ from collections.abc import AsyncIterator, Awaitable, Callable, Mapping, Sequenc
|
|
|
33
33
|
from dataclasses import dataclass, field
|
|
34
34
|
from datetime import UTC, datetime, timedelta
|
|
35
35
|
from pathlib import Path
|
|
36
|
-
from typing import Any, Final, Protocol
|
|
36
|
+
from typing import Any, Final, Literal, Protocol
|
|
37
37
|
from uuid import UUID
|
|
38
38
|
|
|
39
39
|
import httpx
|
|
@@ -85,6 +85,7 @@ from agentic_runner.llm_proxy import (
|
|
|
85
85
|
from agentic_runner.mcp import ToolServerHealthLog
|
|
86
86
|
from agentic_runner.message_store import MESSAGES_DIR, MessageStore, TemporalWorkflowSignaller
|
|
87
87
|
from agentic_runner.oauth_connectors import OAuthConnectors
|
|
88
|
+
from agentic_runner.organisation_name import save_organisation_name
|
|
88
89
|
from agentic_runner.pool_key import (
|
|
89
90
|
POOL_KEY_ENV,
|
|
90
91
|
POOL_KEY_FILENAME,
|
|
@@ -852,6 +853,9 @@ class ControlPlaneStream:
|
|
|
852
853
|
# QTS-1773: the control plane's contracts version, from its ack; None until an ack from
|
|
853
854
|
# one on 3.27 or later, the first to send it and to parse `RunnerInstall.name`.
|
|
854
855
|
_control_plane_contracts: str | None = None
|
|
856
|
+
# QTS-1909: the name in the copy `list` reads, None while there is none. "unwritten"
|
|
857
|
+
# until this process first writes it, so the first ack replaces what a previous one left.
|
|
858
|
+
_organisation_name: str | None | Literal["unwritten"] = "unwritten"
|
|
855
859
|
_last_seen: tuple[float, datetime] | None = None
|
|
856
860
|
|
|
857
861
|
def _observe_sleep(self, now: datetime) -> None:
|
|
@@ -1081,6 +1085,7 @@ class ControlPlaneStream:
|
|
|
1081
1085
|
self._take_pool(ack.pool)
|
|
1082
1086
|
if ack.contracts_version is not None:
|
|
1083
1087
|
self._control_plane_contracts = ack.contracts_version
|
|
1088
|
+
self._take_organisation_name(ack.organisation_name)
|
|
1084
1089
|
await self._sync_llm_slots()
|
|
1085
1090
|
if ack.runner_token is not None:
|
|
1086
1091
|
self.runner_token = ack.runner_token
|
|
@@ -1139,6 +1144,15 @@ class ControlPlaneStream:
|
|
|
1139
1144
|
if self.state_dir is not None:
|
|
1140
1145
|
save_llm_contracts(self.state_dir, terms)
|
|
1141
1146
|
|
|
1147
|
+
def _take_organisation_name(self, name: str | None) -> None:
|
|
1148
|
+
"""Keep the copy ``list`` reads when the ack's name changes. Unlike the Contract
|
|
1149
|
+
terms, an ack without one clears it: only the name, never the id, can go stale."""
|
|
1150
|
+
|
|
1151
|
+
if self.state_dir is None or name == self._organisation_name:
|
|
1152
|
+
return
|
|
1153
|
+
save_organisation_name(self.state_dir, name)
|
|
1154
|
+
self._organisation_name = name
|
|
1155
|
+
|
|
1142
1156
|
def _take_pool(self, pool: PoolTerms | None) -> None:
|
|
1143
1157
|
"""Keep the ack's pool. An ack without one (a personal Runner, or a control plane
|
|
1144
1158
|
below 3.26) leaves what is held."""
|
|
@@ -1287,14 +1301,17 @@ async def sign_in(which: str | None, *, store: KeyStore, method: str = OAUTH_TOK
|
|
|
1287
1301
|
can = can_separate_uids()
|
|
1288
1302
|
try:
|
|
1289
1303
|
require_isolation_supported(mode, can_separate_uids=can)
|
|
1304
|
+
# `run` gives the settings this default; a sign-in must build the same ones, or a
|
|
1305
|
+
# workstation, whose environment sets only the control plane, is refused (QTS-2157).
|
|
1306
|
+
_default_callback_url(os.environ.get(CONTROL_PLANE_ENV, "").strip())
|
|
1290
1307
|
settings = get_worker_settings()
|
|
1291
1308
|
except RunnerRegistrationError as error:
|
|
1292
1309
|
print(f"refusing to sign in ({error.reason}): {error}", file=sys.stderr)
|
|
1293
1310
|
return 1
|
|
1294
|
-
except ValidationError:
|
|
1311
|
+
except ValidationError as error:
|
|
1295
1312
|
print(
|
|
1296
|
-
"refusing to sign in: run this with the Runner's
|
|
1297
|
-
"`kubectl exec`, or on the workstation that runs it)",
|
|
1313
|
+
f"refusing to sign in: {_invalid_settings(error)}; run this with the Runner's "
|
|
1314
|
+
"environment (`docker exec`, `kubectl exec`, or on the workstation that runs it)",
|
|
1298
1315
|
file=sys.stderr,
|
|
1299
1316
|
)
|
|
1300
1317
|
return 2
|
|
@@ -1322,6 +1339,22 @@ async def sign_in(which: str | None, *, store: KeyStore, method: str = OAUTH_TOK
|
|
|
1322
1339
|
_SIGN_IN_CLIS: Final[Mapping[str, str]] = {"claude_code": "claude", "codex_cli": "codex"}
|
|
1323
1340
|
|
|
1324
1341
|
|
|
1342
|
+
def _default_callback_url(control_plane: str) -> None:
|
|
1343
|
+
# The control plane the activities call back is the one this process registered
|
|
1344
|
+
# with, unless the operator points them at an internal address explicitly.
|
|
1345
|
+
if control_plane:
|
|
1346
|
+
os.environ.setdefault("INTERNAL_FASTAPI_BASE_URL", control_plane)
|
|
1347
|
+
|
|
1348
|
+
|
|
1349
|
+
def _invalid_settings(error: ValidationError) -> str:
|
|
1350
|
+
"""``INTERNAL_FASTAPI_BASE_URL (Field required)``, one per field, never the value."""
|
|
1351
|
+
|
|
1352
|
+
return ", ".join(
|
|
1353
|
+
f"{'.'.join(str(part) for part in detail['loc'])} ({detail['msg']})"
|
|
1354
|
+
for detail in error.errors(include_input=False, include_url=False)
|
|
1355
|
+
)
|
|
1356
|
+
|
|
1357
|
+
|
|
1325
1358
|
async def run(
|
|
1326
1359
|
*,
|
|
1327
1360
|
http_client: httpx.AsyncClient | None = None,
|
|
@@ -1402,9 +1435,7 @@ async def _run(
|
|
|
1402
1435
|
except (RunnerRegistrationError, UnsafeStateError) as error:
|
|
1403
1436
|
print(f"refusing to start ({error.reason}): {error}", flush=True)
|
|
1404
1437
|
return 1
|
|
1405
|
-
|
|
1406
|
-
# with, unless the operator points them at an internal address explicitly.
|
|
1407
|
-
os.environ.setdefault("INTERNAL_FASTAPI_BASE_URL", control_plane)
|
|
1438
|
+
_default_callback_url(control_plane)
|
|
1408
1439
|
settings = get_worker_settings()
|
|
1409
1440
|
try:
|
|
1410
1441
|
attestation = with_profile_acp_cli_kinds(with_acp_bridges(attestation, settings), settings)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""The workstation Runner: one login agent per Runner (PRD issue 47, map ticket 23).
|
|
2
2
|
|
|
3
|
-
``agentic-runner install | start | stop | status <name>`` and ``list``. Everything
|
|
4
|
-
precedent does on a developer machine and nothing a daemon would need:
|
|
3
|
+
``agentic-runner install | start | stop | restart | status <name>`` and ``list``. Everything
|
|
4
|
+
a CI-runner precedent does on a developer machine and nothing a daemon would need:
|
|
5
5
|
|
|
6
6
|
* **One process per Runner** (23 item 3), each with its own state directory, identity,
|
|
7
7
|
namespace, Runner Token, hooks, credential store and Workspaces under ``<root>/<name>/``.
|
|
@@ -37,11 +37,12 @@ import shutil
|
|
|
37
37
|
import subprocess
|
|
38
38
|
import sys
|
|
39
39
|
import tempfile
|
|
40
|
+
import time
|
|
40
41
|
from collections.abc import Callable, Mapping, Sequence
|
|
41
42
|
from dataclasses import dataclass
|
|
42
43
|
from datetime import UTC, datetime
|
|
43
44
|
from pathlib import Path
|
|
44
|
-
from typing import Any, Final
|
|
45
|
+
from typing import Any, Final, Literal
|
|
45
46
|
from xml.sax.saxutils import escape
|
|
46
47
|
|
|
47
48
|
import httpx
|
|
@@ -55,6 +56,7 @@ from agentic_runner.config import CONFIG_FILE_ENV
|
|
|
55
56
|
from agentic_runner.host_store import open_workstation_store
|
|
56
57
|
from agentic_runner.lifecycle import LifecycleOutbox
|
|
57
58
|
from agentic_runner.llm_contracts import load_llm_contracts
|
|
59
|
+
from agentic_runner.organisation_name import load_organisation_name
|
|
58
60
|
from agentic_runner.pool_key import parse_pool_key, save_pool_key
|
|
59
61
|
from agentic_runner.private_state import UnsafeStateError
|
|
60
62
|
from agentic_runner.registration import load_state
|
|
@@ -64,6 +66,7 @@ from agentic_runner_contracts.public_metadata import NAMESPACE_PREFIX
|
|
|
64
66
|
from agentic_runner_contracts.runner_registration import (
|
|
65
67
|
RUNNER_NAME_PATTERN,
|
|
66
68
|
CliVersion,
|
|
69
|
+
ContractLlmTerms,
|
|
67
70
|
EgressPosture,
|
|
68
71
|
HostAttestation,
|
|
69
72
|
InstallChannel,
|
|
@@ -89,11 +92,13 @@ __all__ = [
|
|
|
89
92
|
"install",
|
|
90
93
|
"install_channel",
|
|
91
94
|
"installed_names",
|
|
95
|
+
"installed_runner",
|
|
92
96
|
"list_lines",
|
|
93
97
|
"locate_clis",
|
|
94
98
|
"only_runner",
|
|
95
99
|
"parse_acp_cli_kinds",
|
|
96
100
|
"require_any_cli",
|
|
101
|
+
"restart",
|
|
97
102
|
"run_environment",
|
|
98
103
|
"run_process",
|
|
99
104
|
"service_definition",
|
|
@@ -156,6 +161,16 @@ def installed_names(root: Path) -> list[str]:
|
|
|
156
161
|
)
|
|
157
162
|
|
|
158
163
|
|
|
164
|
+
def installed_runner(root: Path, name: str) -> RunnerPaths:
|
|
165
|
+
"""``<name>``, refused with the names installed here when it is not one of them."""
|
|
166
|
+
|
|
167
|
+
names = installed_names(root)
|
|
168
|
+
if name in names:
|
|
169
|
+
return RunnerPaths(root=root, name=name)
|
|
170
|
+
installed = ", ".join(names) if names else "none"
|
|
171
|
+
raise ValueError(f"no Runner named {name!r} is installed under {root}; installed: {installed}")
|
|
172
|
+
|
|
173
|
+
|
|
159
174
|
def only_runner(root: Path, *, how: str = "name one") -> RunnerPaths:
|
|
160
175
|
"""The one Runner installed here, so a command needs no name (QTS-1774)."""
|
|
161
176
|
|
|
@@ -442,6 +457,7 @@ class ServiceDefinition:
|
|
|
442
457
|
install: list[list[str]]
|
|
443
458
|
start: list[list[str]]
|
|
444
459
|
stop: list[list[str]]
|
|
460
|
+
restart: list[list[str]]
|
|
445
461
|
|
|
446
462
|
|
|
447
463
|
def runner_command(name: str, root: Path) -> list[str]:
|
|
@@ -502,6 +518,9 @@ def _launch_agent(
|
|
|
502
518
|
install=[],
|
|
503
519
|
start=[["launchctl", "bootstrap", domain, str(plist)]],
|
|
504
520
|
stop=[["launchctl", "bootout", f"{domain}/{name}"]],
|
|
521
|
+
# One call, not bootout then bootstrap: launchd refuses a bootstrap of a label it
|
|
522
|
+
# still holds, and it holds it until the old process has drained.
|
|
523
|
+
restart=[["launchctl", "kickstart", "-k", f"{domain}/{name}"]],
|
|
505
524
|
)
|
|
506
525
|
|
|
507
526
|
|
|
@@ -544,6 +563,7 @@ def _systemd_user_unit(
|
|
|
544
563
|
],
|
|
545
564
|
start=[[*systemctl, "enable", "--now", unit_name]],
|
|
546
565
|
stop=[[*systemctl, "disable", "--now", unit_name]],
|
|
566
|
+
restart=[[*systemctl, "restart", unit_name]],
|
|
547
567
|
)
|
|
548
568
|
|
|
549
569
|
|
|
@@ -597,6 +617,10 @@ def _logon_task(
|
|
|
597
617
|
["schtasks", "/End", "/TN", task_name],
|
|
598
618
|
["schtasks", "/Change", "/TN", task_name, "/DISABLE"],
|
|
599
619
|
],
|
|
620
|
+
restart=[
|
|
621
|
+
["schtasks", "/End", "/TN", task_name],
|
|
622
|
+
["schtasks", "/Run", "/TN", task_name],
|
|
623
|
+
],
|
|
600
624
|
)
|
|
601
625
|
|
|
602
626
|
|
|
@@ -670,9 +694,9 @@ _LIST_HEADER: Final = (
|
|
|
670
694
|
def list_lines(root: Path) -> list[str]:
|
|
671
695
|
"""``list``: one row per Runner installed here (QTS-1774).
|
|
672
696
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
rev 7).
|
|
697
|
+
The Organisation's and the Contract's names once an ack from a control plane on 3.29
|
|
698
|
+
named them (QTS-1909); before that, the first eight characters of their ids, as the
|
|
699
|
+
console shows a Runner's id beside its name (Spec: Runners rev 7).
|
|
676
700
|
"""
|
|
677
701
|
|
|
678
702
|
names = installed_names(root)
|
|
@@ -705,17 +729,21 @@ def _list_row_or_reason(paths: RunnerPaths) -> tuple[str, str, str, str, str, st
|
|
|
705
729
|
def _list_row(paths: RunnerPaths) -> tuple[str, str, str, str, str, str]:
|
|
706
730
|
state = load_state(paths.state_dir)
|
|
707
731
|
contracts = load_llm_contracts(paths.state_dir)
|
|
732
|
+
organisation_name = _printable(load_organisation_name(paths.state_dir))
|
|
708
733
|
try:
|
|
709
734
|
version = paths.started_version.read_text(encoding="utf-8").strip() or "-"
|
|
710
735
|
except OSError:
|
|
711
736
|
version = "-"
|
|
712
737
|
return (
|
|
713
738
|
paths.name,
|
|
714
|
-
|
|
739
|
+
(
|
|
740
|
+
organisation_name
|
|
741
|
+
or (state.temporal_namespace.removeprefix(NAMESPACE_PREFIX)[:8] if state else "-")
|
|
742
|
+
),
|
|
715
743
|
(
|
|
716
744
|
"-"
|
|
717
745
|
if not contracts
|
|
718
|
-
else
|
|
746
|
+
else _contract_label(contracts[0])
|
|
719
747
|
if len(contracts) == 1
|
|
720
748
|
else f"{len(contracts)} Contracts"
|
|
721
749
|
),
|
|
@@ -725,6 +753,15 @@ def _list_row(paths: RunnerPaths) -> tuple[str, str, str, str, str, str]:
|
|
|
725
753
|
)
|
|
726
754
|
|
|
727
755
|
|
|
756
|
+
def _contract_label(terms: ContractLlmTerms) -> str:
|
|
757
|
+
return _printable(terms.contract_name) or str(terms.contract_id)[:8]
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def _printable(name: str | None) -> str:
|
|
761
|
+
# A name someone typed in the console: an escape sequence in it would reach the terminal.
|
|
762
|
+
return "".join(character for character in name or "" if character.isprintable())
|
|
763
|
+
|
|
764
|
+
|
|
728
765
|
def next_step_lines(host_party: str) -> list[str]:
|
|
729
766
|
"""Where to go once the Runner is live (QTS-1314): the dogfood install ended at
|
|
730
767
|
``started`` and left the person with no idea what the console wanted next.
|
|
@@ -822,9 +859,36 @@ def stop(paths: RunnerPaths, **driver: Any) -> None:
|
|
|
822
859
|
_drive_service(paths, "stop", **driver)
|
|
823
860
|
|
|
824
861
|
|
|
862
|
+
def restart(
|
|
863
|
+
paths: RunnerPaths,
|
|
864
|
+
*,
|
|
865
|
+
timeout: float = 60.0,
|
|
866
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
867
|
+
clock: Callable[[], float] = time.monotonic,
|
|
868
|
+
**driver: Any,
|
|
869
|
+
) -> tuple[int, str]:
|
|
870
|
+
"""Restart a running Runner and wait for its new process: ``(pid, version)``.
|
|
871
|
+
|
|
872
|
+
``uv tool upgrade`` replaces the console script, not the process, so a login agent keeps
|
|
873
|
+
running the old version until this (QTS-2158). The process writes its version before
|
|
874
|
+
its pidfile, so a new pid means the version beside it is the new process's.
|
|
875
|
+
"""
|
|
876
|
+
|
|
877
|
+
previous = process_id(paths)
|
|
878
|
+
_drive_service(paths, "restart", **driver)
|
|
879
|
+
deadline = clock() + timeout
|
|
880
|
+
while (pid := process_id(paths)) is None or pid == previous:
|
|
881
|
+
if clock() >= deadline:
|
|
882
|
+
raise RuntimeError(
|
|
883
|
+
f"no new process wrote {paths.pidfile} within {timeout:.0f}s; see {paths.log}"
|
|
884
|
+
)
|
|
885
|
+
sleep(0.5)
|
|
886
|
+
return pid, paths.started_version.read_text(encoding="utf-8").strip()
|
|
887
|
+
|
|
888
|
+
|
|
825
889
|
def _drive_service(
|
|
826
890
|
paths: RunnerPaths,
|
|
827
|
-
verb:
|
|
891
|
+
verb: Literal["start", "stop", "restart"],
|
|
828
892
|
*,
|
|
829
893
|
platform: str = sys.platform,
|
|
830
894
|
home: Path | None = None,
|
|
@@ -832,7 +896,7 @@ def _drive_service(
|
|
|
832
896
|
) -> None:
|
|
833
897
|
settings = WorkstationSettings.load(paths)
|
|
834
898
|
definition = service_definition(paths, settings, platform=platform, home=home)
|
|
835
|
-
drive(definition
|
|
899
|
+
drive(getattr(definition, verb), run=run)
|
|
836
900
|
|
|
837
901
|
|
|
838
902
|
def run_process(paths: RunnerPaths, *, login_agent: bool) -> int:
|
|
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
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/contracts.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/evidence.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/git/workspace.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/github/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/github/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/integrations/github/gh_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/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
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/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.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/_runtime_support.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/claude_runtime.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/claude_sign_in.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/command_policy.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/contract_isolation.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/fastapi_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/harness_logins.py
RENAMED
|
File without changes
|
{agentic_runner-3.28.1 → agentic_runner-3.30.0}/src/agentic_runner/workers/harness_outcome.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|