agentic-runner 3.12.2__tar.gz → 3.14.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.12.2 → agentic_runner-3.14.0}/PKG-INFO +1 -1
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/__init__.py +1 -1
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/cli.py +66 -1
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/device_login_activities.py +6 -10
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/sealed_box.py +1 -12
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/service.py +41 -38
- agentic_runner-3.14.0/src/agentic_runner/skill.py +58 -0
- agentic_runner-3.14.0/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +151 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/claude_sign_in.py +145 -252
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/contract_device_login.py +3 -17
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/.gitignore +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/LICENSE +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/README.md +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/pyproject.toml +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/activities.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/attempts.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/auth_mode.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/build.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/callback.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/child_watcher.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/cli_floor.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/config.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/credentials.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/egress.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/harness_self_test.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/heartbeat_link.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/hooks.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/host_store.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/__init__.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/__init__.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/contracts.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/evidence.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/workspace.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/github/__init__.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/github/auth.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/github/fake_client.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/github/gh_client.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/lifecycle.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/llm_proxy.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/mcp.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/message_store.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/oauth_connectors.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/private_state.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/py.typed +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/recipient_key_secret.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/registration.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/runtime/__init__.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/runtime/verifier_command.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/__init__.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/__main__.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/control_plane.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/plugin.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/test_conformance.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/workflow.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/tiny_http.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/triage_activities.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/usage_windows.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/user_sources.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/__init__.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/_runtime_support.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/acp_runtime.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/agent_runtime.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/claude_runtime.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/codex_runtime.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/command_policy.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/contract_isolation.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/fastapi_client.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/harness_outcome.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/harness_usage.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/mcp_config.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/settings.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/skills.py +0 -0
- {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workstation.py +0 -0
|
@@ -15,6 +15,13 @@ the Organisation's cap.
|
|
|
15
15
|
Runner (PRD issue 47, ``workstation.py``): one login agent per Organisation, managed by
|
|
16
16
|
the user who hosts it, with no ``sudo`` anywhere.
|
|
17
17
|
|
|
18
|
+
``sign-in --contract <id>`` signs one Contract's Claude Code in with Anthropic's own flow,
|
|
19
|
+
in this terminal, as that Contract's uid in its harness root (QTS-1448). No code or token
|
|
20
|
+
passes through the control plane.
|
|
21
|
+
|
|
22
|
+
``skill install | print`` hands the setup skill to the person's own Claude Code or Codex
|
|
23
|
+
(``skill.py``, console-v2 issue 21).
|
|
24
|
+
|
|
18
25
|
The remaining subcommands are what a Directive calls back with, over its own attempt
|
|
19
26
|
socket (``callback.py``): ``annotate``, ``artifact upload``, ``verb`` and ``message send |
|
|
20
27
|
list`` (the Channel seam, PRD issue 52) and ``ask`` (a Question, issue 60). They read the
|
|
@@ -33,9 +40,10 @@ import os
|
|
|
33
40
|
import sys
|
|
34
41
|
from collections.abc import Sequence
|
|
35
42
|
from pathlib import Path
|
|
43
|
+
from uuid import UUID
|
|
36
44
|
|
|
37
45
|
from agentic_runner import __version__ as runner_version
|
|
38
|
-
from agentic_runner import service, workstation
|
|
46
|
+
from agentic_runner import service, skill, workstation
|
|
39
47
|
from agentic_runner.callback import (
|
|
40
48
|
CALLBACK_SOCKET_ENV,
|
|
41
49
|
CALLBACK_TOKEN_ENV,
|
|
@@ -56,6 +64,7 @@ from agentic_runner.service import (
|
|
|
56
64
|
TAGS_ENV,
|
|
57
65
|
parse_tags,
|
|
58
66
|
)
|
|
67
|
+
from agentic_runner.workers.claude_sign_in import OAUTH_TOKEN, SIGN_IN_METHODS
|
|
59
68
|
from agentic_runner_contracts import __version__ as contracts_version
|
|
60
69
|
from agentic_runner_contracts.runner_registration import (
|
|
61
70
|
HostAttestation,
|
|
@@ -195,6 +204,21 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
195
204
|
status.add_argument("org", nargs="?", help="a workstation Organisation (issue 47)")
|
|
196
205
|
status.add_argument("--root", default=None, type=Path)
|
|
197
206
|
|
|
207
|
+
sign_in = subcommands.add_parser(
|
|
208
|
+
"sign-in",
|
|
209
|
+
help="sign a Contract's Claude Code in with Anthropic's own flow, in this terminal",
|
|
210
|
+
)
|
|
211
|
+
sign_in.add_argument("--contract", required=True, help="the Contract's id")
|
|
212
|
+
sign_in.add_argument(
|
|
213
|
+
"--method",
|
|
214
|
+
default=OAUTH_TOKEN,
|
|
215
|
+
choices=SIGN_IN_METHODS,
|
|
216
|
+
help="oauth_token: a long-lived token (claude setup-token); claude_ai: a Claude.ai "
|
|
217
|
+
"login (claude auth login --claudeai)",
|
|
218
|
+
)
|
|
219
|
+
sign_in.add_argument("--org", default=None, help="a workstation Organisation (issue 47)")
|
|
220
|
+
sign_in.add_argument("--root", default=None, type=Path, help=argparse.SUPPRESS)
|
|
221
|
+
|
|
198
222
|
annotate = subcommands.add_parser("annotate", help="attach an annotation to this Work Record")
|
|
199
223
|
annotate.add_argument("--context", default="default")
|
|
200
224
|
annotate.add_argument(
|
|
@@ -250,6 +274,18 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
250
274
|
"--base", default="", help="`branch` only: the base branch; default the repository's"
|
|
251
275
|
)
|
|
252
276
|
|
|
277
|
+
skill_parser = subcommands.add_parser(
|
|
278
|
+
"skill", help="the setup skill for your own Claude Code or Codex (console-v2 issue 21)"
|
|
279
|
+
)
|
|
280
|
+
skill_parser.add_argument("action", choices=("install", "print"))
|
|
281
|
+
only = skill_parser.add_mutually_exclusive_group()
|
|
282
|
+
only.add_argument(
|
|
283
|
+
"--claude", dest="targets", action="store_const", const=("claude",), help="Claude only"
|
|
284
|
+
)
|
|
285
|
+
only.add_argument(
|
|
286
|
+
"--codex", dest="targets", action="store_const", const=("codex",), help="Codex only"
|
|
287
|
+
)
|
|
288
|
+
|
|
253
289
|
subcommands.add_parser("config", help="print the resolved configuration (flag > env > file)")
|
|
254
290
|
|
|
255
291
|
arguments = parser.parse_args(argv)
|
|
@@ -288,6 +324,15 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
288
324
|
for line in lines:
|
|
289
325
|
print(line)
|
|
290
326
|
return 0
|
|
327
|
+
if arguments.command == "sign-in":
|
|
328
|
+
return _sign_in(arguments)
|
|
329
|
+
if arguments.command == "skill":
|
|
330
|
+
if arguments.action == "print":
|
|
331
|
+
sys.stdout.write(skill.text())
|
|
332
|
+
return 0
|
|
333
|
+
for path in skill.install(arguments.targets or skill.TARGETS):
|
|
334
|
+
print(f"installed {path}")
|
|
335
|
+
return 0
|
|
291
336
|
if arguments.command == "config":
|
|
292
337
|
config = load_config(config_file=arguments.config, hooks_path=arguments.hooks_path)
|
|
293
338
|
print(config.model_dump_json(indent=2))
|
|
@@ -344,6 +389,26 @@ def _image_attestation() -> HostAttestation:
|
|
|
344
389
|
)
|
|
345
390
|
|
|
346
391
|
|
|
392
|
+
def _sign_in(arguments: argparse.Namespace) -> int:
|
|
393
|
+
try:
|
|
394
|
+
contract_id = str(UUID(arguments.contract))
|
|
395
|
+
except ValueError:
|
|
396
|
+
print(f"--contract expects a Contract id, got {arguments.contract!r}", file=sys.stderr)
|
|
397
|
+
return 2
|
|
398
|
+
if not (os.isatty(0) and os.isatty(1)):
|
|
399
|
+
print(
|
|
400
|
+
"sign-in is interactive: run it in a terminal (`kubectl exec -it`, `docker exec -it`)",
|
|
401
|
+
file=sys.stderr,
|
|
402
|
+
)
|
|
403
|
+
return 2
|
|
404
|
+
if arguments.org:
|
|
405
|
+
paths = _org_paths(arguments)
|
|
406
|
+
os.environ.update(
|
|
407
|
+
workstation.run_environment(paths, workstation.WorkstationSettings.load(paths))
|
|
408
|
+
)
|
|
409
|
+
return asyncio.run(service.sign_in_claude(contract_id, method=arguments.method))
|
|
410
|
+
|
|
411
|
+
|
|
347
412
|
def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
348
413
|
if not arguments.control_plane:
|
|
349
414
|
print(f"--control-plane (or {CONTROL_PLANE_ENV}) is required", file=sys.stderr)
|
{agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/device_login_activities.py
RENAMED
|
@@ -14,7 +14,6 @@ from temporalio import activity
|
|
|
14
14
|
from temporalio.exceptions import ApplicationError
|
|
15
15
|
|
|
16
16
|
from agentic_runner.auth_mode import SHARED_RUNNER_SIGN_IN_RULE, is_shared_runner
|
|
17
|
-
from agentic_runner.workers.claude_sign_in import OAUTH_TOKEN
|
|
18
17
|
from agentic_runner.workers.contract_device_login import ContractDeviceLogin
|
|
19
18
|
from agentic_runner.workers.contract_isolation import ContractIsolation
|
|
20
19
|
from agentic_runner_contracts.activity_io import (
|
|
@@ -37,8 +36,9 @@ class ContractDeviceLoginActivities:
|
|
|
37
36
|
host_party: str | None = None,
|
|
38
37
|
) -> None:
|
|
39
38
|
self._contract_isolation = contract_isolation
|
|
40
|
-
# The party this process was registered as hosted by. A shared Runner
|
|
41
|
-
#
|
|
39
|
+
# The party this process was registered as hosted by. A shared Runner starts no
|
|
40
|
+
# device login and reports only Claude Code's sign-in (local-agents 04, amended by
|
|
41
|
+
# 21); a person signs Claude Code in on the Runner itself (QTS-1448).
|
|
42
42
|
self._host_party = host_party
|
|
43
43
|
# Injectable for tests; None falls through to the real vendor CLIs.
|
|
44
44
|
self._device_login = device_login or ContractDeviceLogin(contract_isolation)
|
|
@@ -53,12 +53,9 @@ class ContractDeviceLoginActivities:
|
|
|
53
53
|
async def sign_in_contract_device_login(
|
|
54
54
|
self, request: ContractDeviceLoginInput
|
|
55
55
|
) -> ContractDeviceLoginResult:
|
|
56
|
-
|
|
57
|
-
OAUTH_TOKEN
|
|
58
|
-
)
|
|
59
|
-
self._refuse_on_shared_runner(allow=long_lived)
|
|
56
|
+
self._refuse_on_shared_runner()
|
|
60
57
|
prompt = await self._device_login.sign_in(
|
|
61
|
-
request.contract_id, runtime_kind=request.runtime_kind
|
|
58
|
+
request.contract_id, runtime_kind=request.runtime_kind
|
|
62
59
|
)
|
|
63
60
|
return ContractDeviceLoginResult(
|
|
64
61
|
contract_id=prompt.contract_id,
|
|
@@ -66,7 +63,6 @@ class ContractDeviceLoginActivities:
|
|
|
66
63
|
verification_uri=prompt.verification_uri,
|
|
67
64
|
user_code=prompt.user_code,
|
|
68
65
|
expires_at=prompt.expires_at.isoformat(),
|
|
69
|
-
sign_in_id=prompt.sign_in_id,
|
|
70
66
|
)
|
|
71
67
|
|
|
72
68
|
@activity.defn(name="check_contract_device_login_status")
|
|
@@ -107,7 +103,7 @@ class ContractDeviceLoginActivities:
|
|
|
107
103
|
on here, so the failure the platform's sign-in workflow receives names it.
|
|
108
104
|
|
|
109
105
|
Only a Runner known to be the person's own signs in, except that a shared Runner
|
|
110
|
-
may
|
|
106
|
+
may report (``allow``) a Claude Code long-lived token (local-agents 21). One whose
|
|
111
107
|
state names no host party (registered before issue 42) is refused either way, as
|
|
112
108
|
`choose_auth_mode` never runs a subscription on it."""
|
|
113
109
|
|
|
@@ -63,7 +63,6 @@ from agentic_runner_contracts.sealed_credential import (
|
|
|
63
63
|
SEAL_DOMAIN,
|
|
64
64
|
OpenedCredential,
|
|
65
65
|
SealedCredential,
|
|
66
|
-
SealedSignInCode,
|
|
67
66
|
delivery_binding,
|
|
68
67
|
key_fingerprint,
|
|
69
68
|
)
|
|
@@ -554,20 +553,10 @@ class SealedCredentialStream:
|
|
|
554
553
|
if not self._awaiting:
|
|
555
554
|
self._keys.retire_previous()
|
|
556
555
|
|
|
557
|
-
def open_sign_in_code(self, sealed: SealedSignInCode) -> str | None:
|
|
558
|
-
"""A relayed sign-in code, or None when it does not open (local-agents 05).
|
|
559
|
-
|
|
560
|
-
Opened with the same Recipient Key as a slot, but never held: the caller types it
|
|
561
|
-
into the waiting CLI and drops it.
|
|
562
|
-
"""
|
|
563
|
-
|
|
564
|
-
return self._open_with(sealed.recipient_key_id, sealed.binding(), sealed.ciphertext)
|
|
565
|
-
|
|
566
556
|
def open_oauth_code(self, sealed: SealedOAuthCode) -> str | None:
|
|
567
557
|
"""A relayed OAuth code, or None when it does not open (console-v2 issue 30).
|
|
568
558
|
|
|
569
|
-
Never held
|
|
570
|
-
drops it.
|
|
559
|
+
Never held: the caller spends it on the token endpoint and drops it.
|
|
571
560
|
"""
|
|
572
561
|
|
|
573
562
|
return self._open_with(sealed.recipient_key_id, sealed.binding(), sealed.ciphertext)
|
|
@@ -24,6 +24,7 @@ import logging
|
|
|
24
24
|
import os
|
|
25
25
|
import shutil
|
|
26
26
|
import signal
|
|
27
|
+
import sys
|
|
27
28
|
import tempfile
|
|
28
29
|
import time
|
|
29
30
|
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
@@ -34,6 +35,7 @@ from typing import Any, Protocol
|
|
|
34
35
|
from uuid import UUID
|
|
35
36
|
|
|
36
37
|
import httpx
|
|
38
|
+
from pydantic import ValidationError
|
|
37
39
|
from temporalio import activity
|
|
38
40
|
from temporalio.client import Client
|
|
39
41
|
from temporalio.worker import (
|
|
@@ -110,7 +112,7 @@ from agentic_runner.user_sources import ProxyTriage, UserSourcePoller
|
|
|
110
112
|
from agentic_runner.workers.acp_runtime import ACP_BRIDGES, AcpRuntime, profile_acp_executables
|
|
111
113
|
from agentic_runner.workers.agent_runtime import AgentRuntime
|
|
112
114
|
from agentic_runner.workers.claude_runtime import ClaudeRuntime
|
|
113
|
-
from agentic_runner.workers.claude_sign_in import ClaudeSignIns
|
|
115
|
+
from agentic_runner.workers.claude_sign_in import ClaudeSignInError, ClaudeSignIns
|
|
114
116
|
from agentic_runner.workers.codex_runtime import CodexRuntime
|
|
115
117
|
from agentic_runner.workers.contract_device_login import ContractDeviceLogin
|
|
116
118
|
from agentic_runner.workers.contract_isolation import UID_MAP_FILENAME, ContractIsolation
|
|
@@ -135,11 +137,7 @@ from agentic_runner_contracts.runner_registration import (
|
|
|
135
137
|
ResourcePressure,
|
|
136
138
|
)
|
|
137
139
|
from agentic_runner_contracts.runtime_context import WorkerRuntimeContextResolver
|
|
138
|
-
from agentic_runner_contracts.sealed_credential import
|
|
139
|
-
SignInCodeOutcome,
|
|
140
|
-
SignInCodeRelay,
|
|
141
|
-
key_fingerprint,
|
|
142
|
-
)
|
|
140
|
+
from agentic_runner_contracts.sealed_credential import key_fingerprint
|
|
143
141
|
|
|
144
142
|
__all__ = [
|
|
145
143
|
"AGENT_TOKEN_ENV",
|
|
@@ -166,6 +164,7 @@ __all__ = [
|
|
|
166
164
|
"run",
|
|
167
165
|
"run_runner_lifecycle_hook",
|
|
168
166
|
"run_worker_until_terminated",
|
|
167
|
+
"sign_in_claude",
|
|
169
168
|
"with_profile_acp_cli_kinds",
|
|
170
169
|
]
|
|
171
170
|
|
|
@@ -657,13 +656,8 @@ class ControlPlaneStream:
|
|
|
657
656
|
usage_windows: UsageWindows | None = None
|
|
658
657
|
# Local-agents 04b: which delivered references fill the LLM proxy's slot, and where.
|
|
659
658
|
llm_slots: Mapping[str, LlmProvider] = field(default_factory=lambda: dict(LLM_SLOT_REFERENCES))
|
|
660
|
-
# Local-agents 05: the Claude Code sign-ins a relayed code is typed into.
|
|
661
|
-
sign_ins: ClaudeSignIns | None = None
|
|
662
659
|
# Console-v2 issue 30: the OAuth Connectors whose code, tokens and verifier stay here.
|
|
663
660
|
oauth: OAuthConnectors | None = None
|
|
664
|
-
# Reported once, on the beat after the relay: a lost beat loses the outcome, and the
|
|
665
|
-
# console's own wait on the sign-in is what tells the person.
|
|
666
|
-
_sign_in_outcomes: list[SignInCodeRelay] = field(default_factory=list)
|
|
667
661
|
_delivered: set[tuple[str, str]] = field(default_factory=set)
|
|
668
662
|
_offered_to_proxy: dict[tuple[str, str], str] = field(default_factory=dict)
|
|
669
663
|
_last_seen: tuple[float, datetime] | None = None
|
|
@@ -773,17 +767,14 @@ class ControlPlaneStream:
|
|
|
773
767
|
clock_skew_seconds=self._clock_skew(now),
|
|
774
768
|
source_status=self.sources.statuses() if self.sources is not None else [],
|
|
775
769
|
tool_servers=self.tool_servers.latest() if self.tool_servers is not None else [],
|
|
776
|
-
sign_in_codes=self._sign_in_outcomes,
|
|
777
770
|
oauth_authorizations=self.oauth.take_authorizations() if self.oauth else [],
|
|
778
771
|
oauth_tokens=self.oauth.uploads() if self.oauth else [],
|
|
779
772
|
oauth_outcomes=self.oauth.take_outcomes() if self.oauth else [],
|
|
780
773
|
)
|
|
781
|
-
self._sign_in_outcomes = []
|
|
782
774
|
ack = await self.client.heartbeat(self.state, envelope)
|
|
783
775
|
if self.sources is not None:
|
|
784
776
|
self.sources.assign(ack.user_sources)
|
|
785
777
|
await self._deliver_transcripts(ack)
|
|
786
|
-
await self._relay_sign_in_codes(ack)
|
|
787
778
|
if self.oauth is not None:
|
|
788
779
|
# Before the sealed values are applied: a Disconnect revokes with the tokens
|
|
789
780
|
# this Runner holds, and the same ack may already have withdrawn their ciphertext.
|
|
@@ -836,28 +827,6 @@ class ControlPlaneStream:
|
|
|
836
827
|
"transcript %s could not be delivered: %s", request.request_id, error
|
|
837
828
|
)
|
|
838
829
|
|
|
839
|
-
async def _relay_sign_in_codes(self, ack: HeartbeatAck) -> None:
|
|
840
|
-
"""Type each relayed code into the sign-in it names (local-agents 05).
|
|
841
|
-
|
|
842
|
-
The code is opened, written to that sign-in's PTY and dropped here; nothing logs
|
|
843
|
-
it and only its outcome travels back.
|
|
844
|
-
"""
|
|
845
|
-
|
|
846
|
-
for sealed in ack.sign_in_codes:
|
|
847
|
-
code = self.sealed.open_sign_in_code(sealed)
|
|
848
|
-
if code is None:
|
|
849
|
-
outcome = SignInCodeOutcome.UNOPENABLE
|
|
850
|
-
elif self.sign_ins is None:
|
|
851
|
-
outcome = SignInCodeOutcome.UNKNOWN
|
|
852
|
-
else:
|
|
853
|
-
outcome = await self.sign_ins.relay(
|
|
854
|
-
str(sealed.contract_id), sealed.sign_in_id, code
|
|
855
|
-
)
|
|
856
|
-
del code
|
|
857
|
-
self._sign_in_outcomes.append(
|
|
858
|
-
SignInCodeRelay(sign_in_id=sealed.sign_in_id, outcome=outcome)
|
|
859
|
-
)
|
|
860
|
-
|
|
861
830
|
def _sync_delivered(self) -> None:
|
|
862
831
|
"""The resolver holds exactly what the last ack let this installation open."""
|
|
863
832
|
|
|
@@ -1021,6 +990,42 @@ async def run(
|
|
|
1021
990
|
await readiness.close()
|
|
1022
991
|
|
|
1023
992
|
|
|
993
|
+
async def sign_in_claude(contract_id: str, *, method: str) -> int:
|
|
994
|
+
"""``agentic-runner sign-in``: Claude Code's own flow for one Contract, in this terminal.
|
|
995
|
+
|
|
996
|
+
Runs with the Runner's environment (``kubectl exec``, or ``--org`` on a workstation),
|
|
997
|
+
so the Contract gets the uid and the harness root its Directives already run under.
|
|
998
|
+
Nothing reaches the control plane: the console only reads the outcome back through
|
|
999
|
+
``claude auth status`` (QTS-1448).
|
|
1000
|
+
"""
|
|
1001
|
+
|
|
1002
|
+
mode = isolation_mode()
|
|
1003
|
+
can = can_separate_uids()
|
|
1004
|
+
try:
|
|
1005
|
+
require_isolation_supported(mode, can_separate_uids=can)
|
|
1006
|
+
except RunnerRegistrationError as error:
|
|
1007
|
+
print(f"refusing to sign in ({error.reason}): {error}", file=sys.stderr)
|
|
1008
|
+
return 1
|
|
1009
|
+
control_plane = os.environ.get(CONTROL_PLANE_ENV, "").strip()
|
|
1010
|
+
if control_plane:
|
|
1011
|
+
os.environ.setdefault("INTERNAL_FASTAPI_BASE_URL", control_plane)
|
|
1012
|
+
try:
|
|
1013
|
+
settings = get_worker_settings()
|
|
1014
|
+
except ValidationError:
|
|
1015
|
+
print(
|
|
1016
|
+
"refusing to sign in: run this with the Runner's environment "
|
|
1017
|
+
"(`--org <org>` on a workstation, `kubectl exec` into the Runner's pod)",
|
|
1018
|
+
file=sys.stderr,
|
|
1019
|
+
)
|
|
1020
|
+
return 2
|
|
1021
|
+
sign_ins = ClaudeSignIns(build_contract_isolation(settings, can_separate_uids=can))
|
|
1022
|
+
try:
|
|
1023
|
+
return await sign_ins.sign_in(contract_id, method=method)
|
|
1024
|
+
except ClaudeSignInError as error:
|
|
1025
|
+
print(f"sign-in failed: {error}", file=sys.stderr)
|
|
1026
|
+
return 1
|
|
1027
|
+
|
|
1028
|
+
|
|
1024
1029
|
async def _serve(
|
|
1025
1030
|
*,
|
|
1026
1031
|
config: RunnerConfig,
|
|
@@ -1157,7 +1162,6 @@ async def _serve_registered(
|
|
|
1157
1162
|
messages = MessageStore(state_dir / MESSAGES_DIR)
|
|
1158
1163
|
stream.messages = messages
|
|
1159
1164
|
sign_ins = ClaudeSignIns(build_contract_isolation(settings, can_separate_uids=can_change_uid))
|
|
1160
|
-
stream.sign_ins = sign_ins
|
|
1161
1165
|
stream.oauth = OAuthConnectors(
|
|
1162
1166
|
sealed=stream.sealed, provider=HttpOAuthProvider(), credentials=credentials
|
|
1163
1167
|
)
|
|
@@ -1243,7 +1247,6 @@ async def _serve_registered(
|
|
|
1243
1247
|
task.cancel()
|
|
1244
1248
|
with contextlib.suppress(asyncio.CancelledError):
|
|
1245
1249
|
await task
|
|
1246
|
-
await sign_ins.close()
|
|
1247
1250
|
await run_runner_lifecycle_hook(hooks, HookName.RUNNER_SHUTDOWN)
|
|
1248
1251
|
await _report_stop(link, lifecycle)
|
|
1249
1252
|
return 1 if revoked.is_set() else 0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""``agentic-runner skill install | print``: the setup skill for the person's own coding CLI.
|
|
2
|
+
|
|
3
|
+
The skill (console-v2 issue 21) is read and run by the person's own Claude Code or Codex,
|
|
4
|
+
so the control plane calls no LLM to onboard anyone (ADR-0010 §4). It ships inside the
|
|
5
|
+
wheel so ``uvx agentic-runner skill install`` needs nothing but PyPI, and the copy a CLI
|
|
6
|
+
reads is always the one that matches the Runner it installs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from collections.abc import Mapping
|
|
13
|
+
from importlib.resources import files
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
SKILL_NAME = "agentic-runner-setup"
|
|
18
|
+
|
|
19
|
+
Target = Literal["claude", "codex"]
|
|
20
|
+
TARGETS: tuple[Target, ...] = ("claude", "codex")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def text() -> str:
|
|
24
|
+
return files("agentic_runner").joinpath("skills", SKILL_NAME, "SKILL.md").read_text("utf-8")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def skill_dir(target: Target, *, home: Path, environ: Mapping[str, str]) -> Path:
|
|
28
|
+
"""Where each CLI reads a user skill from.
|
|
29
|
+
|
|
30
|
+
Claude Code honours ``CLAUDE_CONFIG_DIR`` for its whole user directory; Codex reads user
|
|
31
|
+
skills from ``~/.agents/skills``, not from ``CODEX_HOME``.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
if target == "claude":
|
|
35
|
+
root = Path(environ.get("CLAUDE_CONFIG_DIR") or home / ".claude")
|
|
36
|
+
else:
|
|
37
|
+
root = home / ".agents"
|
|
38
|
+
return root / "skills" / SKILL_NAME
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def install(
|
|
42
|
+
targets: tuple[Target, ...],
|
|
43
|
+
*,
|
|
44
|
+
home: Path | None = None,
|
|
45
|
+
environ: Mapping[str, str] | None = None,
|
|
46
|
+
) -> list[Path]:
|
|
47
|
+
written = []
|
|
48
|
+
for target in targets:
|
|
49
|
+
directory = skill_dir(
|
|
50
|
+
target,
|
|
51
|
+
home=home or Path.home(),
|
|
52
|
+
environ=os.environ if environ is None else environ,
|
|
53
|
+
)
|
|
54
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
path = directory / "SKILL.md"
|
|
56
|
+
path.write_text(text(), encoding="utf-8")
|
|
57
|
+
written.append(path)
|
|
58
|
+
return written
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentic-runner-setup
|
|
3
|
+
description: Install and register an Agentic OS Runner on this machine, a Docker host or a Kubernetes cluster, put the Contract's LLM credential in place, and confirm the Runner is online. Use when the person asks to set up, install or connect a Runner, or pastes the prompt from the console's welcome page.
|
|
4
|
+
license: AGPL-3.0-only
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Set up an Agentic OS Runner
|
|
8
|
+
|
|
9
|
+
You help a person who has never run a Runner. A **Runner** is the program that does their
|
|
10
|
+
Agents' work: it runs Codex or Claude Code on a machine they choose and connects out to the
|
|
11
|
+
Agentic OS control plane. It belongs to one **Organisation** (the company the work is for).
|
|
12
|
+
|
|
13
|
+
The install documents in the `qtsone/agentic-runner` repository are the source for every
|
|
14
|
+
command. Do not reconstruct a command from memory: open the section named in each step and
|
|
15
|
+
follow it.
|
|
16
|
+
|
|
17
|
+
## What the person gives you
|
|
18
|
+
|
|
19
|
+
The prompt they pasted from the console holds these. Ask for any that is missing.
|
|
20
|
+
|
|
21
|
+
- **Organisation**: a short local name for it, lowercase letters, digits and `-` (`acme`).
|
|
22
|
+
- **Control plane URL**: the Runner's `controlPlane.url`, copied from the console's
|
|
23
|
+
Runners page. It is the only control plane address you use, and only as a value you
|
|
24
|
+
pass to the Runner. Never call it or any other control plane address yourself.
|
|
25
|
+
- **Temporal address**: `host:port`, from the same page.
|
|
26
|
+
|
|
27
|
+
The **Agent Token** is not one of them. It is the reusable credential a Runner registers
|
|
28
|
+
with, and the person types it only where the Runner reads it, never into this
|
|
29
|
+
conversation.
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
|
|
33
|
+
- **Say before you run.** Before each command, show the person the exact command and what
|
|
34
|
+
it does, in one line.
|
|
35
|
+
- **Stop on the first failure.** Show the exact error, link the doc section below that
|
|
36
|
+
covers it, and stop. Do not retry with other flags, do not work around it.
|
|
37
|
+
- **Never ask for the Agent Token or any credential value in this conversation.** Anything
|
|
38
|
+
said here reaches the model vendor and stays in this CLI's history. Every command that
|
|
39
|
+
reads a credential is one the person runs in their own terminal, and the person types the
|
|
40
|
+
value there. If the person pastes a token or a key here anyway, do not use it: tell them
|
|
41
|
+
to revoke it in the console and mint or create a new one.
|
|
42
|
+
- You use only the `agentic-runner` command, the standard tools named in the docs (`uv`,
|
|
43
|
+
`docker`, `kubectl`, `helm`) and read-only checks such as `command -v`.
|
|
44
|
+
|
|
45
|
+
## Steps
|
|
46
|
+
|
|
47
|
+
### 1. Look at the machine
|
|
48
|
+
|
|
49
|
+
Run and report, in one short list:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
uname -sm
|
|
53
|
+
command -v uv python3 docker kubectl helm codex claude
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A Runner does its work with **Codex** or **Claude Code** (each is an Agent Runtime). If the
|
|
57
|
+
way the person picks runs on this machine and neither `codex` nor `claude` is found, say
|
|
58
|
+
so and link their install pages
|
|
59
|
+
(https://developers.openai.com/codex/cli, https://code.claude.com/docs/en/setup). You do
|
|
60
|
+
not install them.
|
|
61
|
+
|
|
62
|
+
### 2. Ask which way
|
|
63
|
+
|
|
64
|
+
Ask the person to pick one, and open the matching document:
|
|
65
|
+
|
|
66
|
+
| Way | When | Document |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| This machine | A laptop or desktop they use every day. Needs `uv`. | https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md |
|
|
69
|
+
| Docker | A server or always-on machine with Docker. | https://github.com/qtsone/agentic-runner/blob/main/docs/docker.md |
|
|
70
|
+
| Kubernetes | A cluster they run, with `kubectl` and `helm`. | https://github.com/qtsone/agentic-runner/blob/main/docs/helm.md |
|
|
71
|
+
|
|
72
|
+
Read its "Before you start" section with the person, and stop if something on it is
|
|
73
|
+
missing.
|
|
74
|
+
|
|
75
|
+
### 3. Install and register
|
|
76
|
+
|
|
77
|
+
**This machine.** Follow
|
|
78
|
+
https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md#install. Install the
|
|
79
|
+
Runner, then show the person the command that registers it for the Organisation and ask
|
|
80
|
+
them to run it in their own terminal. The Runner asks them for the Agent Token there,
|
|
81
|
+
without showing it:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
uv tool install agentic-runner
|
|
85
|
+
agentic-runner install <org> --control-plane <control-plane-url> --temporal-address <temporal-address>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
You run the first line. Do not run the second yourself: wait until the person says it
|
|
89
|
+
finished, and ask for its last lines of output.
|
|
90
|
+
|
|
91
|
+
**Docker.** Follow
|
|
92
|
+
https://github.com/qtsone/agentic-runner/blob/main/docs/docker.md#the-env-file and then
|
|
93
|
+
https://github.com/qtsone/agentic-runner/blob/main/docs/docker.md#docker-run. Create
|
|
94
|
+
`runner.env` with the URL and the address filled in, and ask the person to add the Agent
|
|
95
|
+
Token line to it themselves.
|
|
96
|
+
|
|
97
|
+
**Kubernetes.** Follow
|
|
98
|
+
https://github.com/qtsone/agentic-runner/blob/main/docs/helm.md#install. The person creates
|
|
99
|
+
the token Secret themselves (the `read -rs` lines in that section). You run the `helm`
|
|
100
|
+
command.
|
|
101
|
+
|
|
102
|
+
`registration refused (…)` means the token is revoked or expired, the Account is on hold,
|
|
103
|
+
or the Organisation has reached its Runner limit: the person mints a new token in the
|
|
104
|
+
console. Other errors: the "Check it" section of the same document.
|
|
105
|
+
|
|
106
|
+
### 4. Put the Contract's LLM credential in place
|
|
107
|
+
|
|
108
|
+
A **Contract** is the agreement under which the person works for the Organisation. It says
|
|
109
|
+
whose LLM key or subscription the Agents spend. Ask which of the two the Contract uses.
|
|
110
|
+
|
|
111
|
+
- **An API key (a Credential Reference).** A Credential Reference is the name of a secret
|
|
112
|
+
whose value lives only on the Runner. On this machine, ask the person to run, in their own
|
|
113
|
+
terminal, the line from
|
|
114
|
+
https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md#operate:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
printf %s "$VALUE" | agentic-runner credential set <org> <reference>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For Docker and Kubernetes the value is delivered from the console, sealed to the Runner:
|
|
121
|
+
https://github.com/qtsone/agentic-runner/blob/main/docs/helm.md#the-recipient-key.
|
|
122
|
+
- **A Codex or Claude Code subscription.** This works only on a Runner the person hosts
|
|
123
|
+
themselves. For Codex, send them to the Contract's page in the console and its
|
|
124
|
+
**Connect** button. For Claude Code, ask the person to run, in their own terminal, the
|
|
125
|
+
sign-in line for their way of installing: `agentic-runner sign-in --org <org> --contract
|
|
126
|
+
<contract-id>` on this machine, or the `docker exec -it` / `kubectl exec -it` line in
|
|
127
|
+
docker.md or helm.md. See
|
|
128
|
+
https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md#sign-in-to-codex-and-claude-code.
|
|
129
|
+
The sign-in is the person's own, with their own Anthropic account: you never run it, and
|
|
130
|
+
you do not run `codex login` or `claude auth login`.
|
|
131
|
+
|
|
132
|
+
### 5. Wait for the Runner to come online
|
|
133
|
+
|
|
134
|
+
On this machine, run every 10 seconds, for at most 3 minutes:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
agentic-runner status <org>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The Runner is online when `process` says `running` and `heartbeat` shows a number of
|
|
141
|
+
seconds, not `never`. For Docker and Kubernetes, use the commands in the document's "Check
|
|
142
|
+
it" section. If 3 minutes pass, stop and show the last output and the "Check it" link.
|
|
143
|
+
|
|
144
|
+
### 6. Report
|
|
145
|
+
|
|
146
|
+
One screen, nothing more:
|
|
147
|
+
|
|
148
|
+
- the way, the Organisation and the Runner's identity line from `status`;
|
|
149
|
+
- whether the LLM credential is in place, or the one thing the person still does in the
|
|
150
|
+
console;
|
|
151
|
+
- the `next` line `status` prints: where in the console to see the Runner and give it work.
|