agentic-runner 3.13.0__tar.gz → 3.15.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.13.0 → agentic_runner-3.15.0}/PKG-INFO +1 -1
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/__init__.py +1 -1
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/activities.py +32 -10
- agentic_runner-3.15.0/src/agentic_runner/auth_mode.py +126 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/cli.py +43 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/device_login_activities.py +19 -21
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/sealed_box.py +1 -12
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/service.py +73 -43
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +7 -4
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/claude_sign_in.py +188 -250
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/contract_device_login.py +21 -24
- agentic_runner-3.13.0/src/agentic_runner/auth_mode.py +0 -86
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/.gitignore +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/LICENSE +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/README.md +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/pyproject.toml +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/attempts.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/build.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/callback.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/child_watcher.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/cli_floor.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/config.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/credentials.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/egress.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/harness_self_test.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/heartbeat_link.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/hooks.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/host_store.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/__init__.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/git/__init__.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/git/contracts.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/git/evidence.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/git/workspace.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/github/__init__.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/github/auth.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/github/fake_client.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/github/gh_client.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/lifecycle.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/llm_proxy.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/mcp.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/message_store.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/oauth_connectors.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/private_state.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/py.typed +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/recipient_key_secret.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/registration.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/runtime/__init__.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/runtime/verifier_command.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/skill.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/testing/__init__.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/testing/__main__.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/testing/control_plane.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/testing/plugin.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/testing/test_conformance.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/testing/workflow.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/tiny_http.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/triage_activities.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/usage_windows.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/user_sources.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/__init__.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/_runtime_support.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/acp_runtime.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/agent_runtime.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/claude_runtime.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/codex_runtime.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/command_policy.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/contract_isolation.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/fastapi_client.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/harness_outcome.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/harness_usage.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/mcp_config.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/settings.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workers/skills.py +0 -0
- {agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/workstation.py +0 -0
|
@@ -137,7 +137,7 @@ from agentic_runner.workers.agent_runtime import (
|
|
|
137
137
|
PermissionFallback,
|
|
138
138
|
ResumableAgentRuntime,
|
|
139
139
|
)
|
|
140
|
-
from agentic_runner.workers.contract_device_login import login_file_counts
|
|
140
|
+
from agentic_runner.workers.contract_device_login import holds_login, login_file_counts
|
|
141
141
|
from agentic_runner.workers.contract_isolation import (
|
|
142
142
|
NO_CONTRACT,
|
|
143
143
|
ContractIsolation,
|
|
@@ -2995,20 +2995,29 @@ class RunnerRalphActivities:
|
|
|
2995
2995
|
"""This Directive's auth mode, or a non-retryable refusal with Evidence naming the
|
|
2996
2996
|
rule (local-agents 04).
|
|
2997
2997
|
|
|
2998
|
-
Decided from the host party
|
|
2999
|
-
key is present for the Contract
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
2998
|
+
Decided from what this process can check -- the host party it was registered with,
|
|
2999
|
+
whether a key is present for the Contract, whether the Contract runs under its own
|
|
3000
|
+
uid here and whether its harness root holds a login (QTS-1450) -- never from the
|
|
3001
|
+
payload. A runtime that declares no modes (a test double built before the port
|
|
3002
|
+
grew the field) reads as ``api_key``-only, and a kind this Runner does not serve
|
|
3003
|
+
reads the same way; that Directive fails closed in :meth:`_agent_runtime_for`
|
|
3004
|
+
regardless.
|
|
3003
3005
|
"""
|
|
3004
3006
|
|
|
3005
3007
|
runtime = self._agent_runtimes.get(state.cli_kind)
|
|
3006
3008
|
host_party = self._routing_identity.host_party if self._routing_identity else None
|
|
3009
|
+
contract_uid = self._runs_under_contract_uid(state)
|
|
3007
3010
|
try:
|
|
3008
3011
|
return choose_auth_mode(
|
|
3009
3012
|
host_party=host_party,
|
|
3010
3013
|
key_present=self._api_key_present(state, runtime),
|
|
3011
3014
|
runtime_modes=getattr(runtime, "auth_modes", frozenset({AuthMode.API_KEY})),
|
|
3015
|
+
contract_uid=contract_uid,
|
|
3016
|
+
harness_login=(
|
|
3017
|
+
contract_uid
|
|
3018
|
+
and self._contract_isolation is not None
|
|
3019
|
+
and holds_login(self._contract_isolation, state.contract_id, state.cli_kind)
|
|
3020
|
+
),
|
|
3012
3021
|
)
|
|
3013
3022
|
except AuthModeRefusedError as refusal:
|
|
3014
3023
|
await _raise_recorded(
|
|
@@ -3026,6 +3035,16 @@ class RunnerRalphActivities:
|
|
|
3026
3035
|
},
|
|
3027
3036
|
)
|
|
3028
3037
|
|
|
3038
|
+
def _runs_under_contract_uid(self, state: _RuntimeContextState) -> bool:
|
|
3039
|
+
"""Whether this Directive spawns as its own Contract's uid. A Work Record with no
|
|
3040
|
+
Contract shares the one no-Contract tree, so it never does."""
|
|
3041
|
+
|
|
3042
|
+
return (
|
|
3043
|
+
self._contract_isolation is not None
|
|
3044
|
+
and self._contract_isolation.can_separate_uids
|
|
3045
|
+
and _optional_uuid(state.contract_id) is not None
|
|
3046
|
+
)
|
|
3047
|
+
|
|
3029
3048
|
def _api_key_present(self, state: _RuntimeContextState, runtime: AgentRuntime | None) -> bool:
|
|
3030
3049
|
"""A delivered slot for the Contract (or its funder), or the host operator's key."""
|
|
3031
3050
|
|
|
@@ -3039,18 +3058,21 @@ class RunnerRalphActivities:
|
|
|
3039
3058
|
)
|
|
3040
3059
|
|
|
3041
3060
|
async def _report_login_residue(self, work_record_id: str, state: _RuntimeContextState) -> None:
|
|
3042
|
-
"""On a shared Runner, say once that a Contract's
|
|
3061
|
+
"""On a shared Runner that cannot run a subscription, say once that a Contract's
|
|
3062
|
+
harness root holds a login.
|
|
3043
3063
|
|
|
3044
3064
|
A count per harness, never a path or a byte of the file: the Runner does not open
|
|
3045
|
-
it, a shared Runner never runs on it, and
|
|
3046
|
-
the Contract. Saying so is what turns a
|
|
3047
|
-
hand) into something the Organisation
|
|
3065
|
+
it, a shared Runner without Contract uids never runs on it (QTS-1450), and
|
|
3066
|
+
``wipe_contract_residue`` removes it with the Contract. Saying so is what turns a
|
|
3067
|
+
sign-in made before local-agents 03 (or by hand) into something the Organisation
|
|
3068
|
+
can see and act on.
|
|
3048
3069
|
"""
|
|
3049
3070
|
|
|
3050
3071
|
host_party = self._routing_identity.host_party if self._routing_identity else None
|
|
3051
3072
|
if (
|
|
3052
3073
|
self._contract_isolation is None
|
|
3053
3074
|
or not is_shared_runner(host_party)
|
|
3075
|
+
or self._runs_under_contract_uid(state)
|
|
3054
3076
|
or state.contract_id in self._residue_checked
|
|
3055
3077
|
):
|
|
3056
3078
|
return
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Which auth mode one Directive runs in, decided on the Runner before spawn (local-agents 04).
|
|
2
|
+
|
|
3
|
+
Subscriptions follow the person and the actual operator, not the host party (QTS-1349,
|
|
4
|
+
QTS-1450). A harness runs on a subscription login where the Runner can verify that the
|
|
5
|
+
login is the Contract's alone:
|
|
6
|
+
|
|
7
|
+
* on the Contract person's own Runner -- host party ``user``, which routing has already
|
|
8
|
+
tied to the Contract's person (``assert_routed`` here, the platform's
|
|
9
|
+
``_subscription_bound`` there);
|
|
10
|
+
* on a shared Runner (``organisation`` or ``account``, e.g. a customer-operated cluster)
|
|
11
|
+
only when the Directive runs as its Contract's own uid and that Contract's harness root
|
|
12
|
+
holds a login. The uid is what keeps one Contract's login out of every other
|
|
13
|
+
Contract's reach on the same host; the login is what the person made there themselves.
|
|
14
|
+
|
|
15
|
+
The platform decides who may use a subscription at the door (the Subscription Principal
|
|
16
|
+
and the Organisation's opt-in, QTS-1350); this module is the backstop, so it decides from
|
|
17
|
+
what the Runner itself can check and never from the payload.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from collections.abc import Collection
|
|
23
|
+
from typing import Final
|
|
24
|
+
|
|
25
|
+
from agentic_runner.workers.agent_runtime import AuthMode
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"NO_RUNTIME_SUBSCRIPTION_RULE",
|
|
29
|
+
"SETUP_TOKEN_REFUSED_RULE",
|
|
30
|
+
"SHARED_HOST_PARTIES",
|
|
31
|
+
"SIGN_IN_NEEDS_CONTRACT_UID_RULE",
|
|
32
|
+
"SUBSCRIPTION_NEEDS_CONTRACT_UID_RULE",
|
|
33
|
+
"SUBSCRIPTION_NEEDS_HARNESS_LOGIN_RULE",
|
|
34
|
+
"AuthModeRefusedError",
|
|
35
|
+
"choose_auth_mode",
|
|
36
|
+
"is_setup_token",
|
|
37
|
+
"is_shared_runner",
|
|
38
|
+
"runs_subscriptions",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
SHARED_HOST_PARTIES: Final[frozenset[str]] = frozenset({"organisation", "account"})
|
|
42
|
+
|
|
43
|
+
# The rule names a refusal's Evidence carries, so a support read can say which line held.
|
|
44
|
+
SUBSCRIPTION_NEEDS_CONTRACT_UID_RULE: Final[str] = "subscription_needs_contract_uid"
|
|
45
|
+
SUBSCRIPTION_NEEDS_HARNESS_LOGIN_RULE: Final[str] = "subscription_needs_harness_login"
|
|
46
|
+
NO_RUNTIME_SUBSCRIPTION_RULE: Final[str] = "runtime_has_no_subscription_mode"
|
|
47
|
+
SIGN_IN_NEEDS_CONTRACT_UID_RULE: Final[str] = "sign_in_needs_contract_uid"
|
|
48
|
+
SETUP_TOKEN_REFUSED_RULE: Final[str] = "setup_token_refused"
|
|
49
|
+
# `claude setup-token` mints an OAuth bearer under this prefix; an Anthropic API key is
|
|
50
|
+
# `sk-ant-api…`. The sealed wire carries no mode, so the value's own shape is what the
|
|
51
|
+
# Runner can refuse on.
|
|
52
|
+
_SETUP_TOKEN_PREFIX: Final[str] = "sk-ant-oat"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AuthModeRefusedError(RuntimeError):
|
|
56
|
+
"""A Directive or sign-in this Runner will not run; ``rule`` is the stable code."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, rule: str, message: str) -> None:
|
|
59
|
+
super().__init__(message)
|
|
60
|
+
self.rule = rule
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def is_shared_runner(host_party: str | None) -> bool:
|
|
64
|
+
return host_party in SHARED_HOST_PARTIES
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def runs_subscriptions(host_party: str | None, *, separates_contract_uids: bool) -> bool:
|
|
68
|
+
"""Whether a subscription login made on this Runner could ever run a Directive.
|
|
69
|
+
|
|
70
|
+
What a sign-in is checked against: one made where no Directive could use it would
|
|
71
|
+
only leave a login lying in a harness root. An unregistered process (no host party)
|
|
72
|
+
makes no routing assertion, so it is nobody's own Runner and never qualifies.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
return host_party == "user" or (is_shared_runner(host_party) and separates_contract_uids)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def choose_auth_mode(
|
|
79
|
+
*,
|
|
80
|
+
host_party: str | None,
|
|
81
|
+
key_present: bool,
|
|
82
|
+
runtime_modes: Collection[AuthMode],
|
|
83
|
+
contract_uid: bool,
|
|
84
|
+
harness_login: bool,
|
|
85
|
+
) -> AuthMode:
|
|
86
|
+
"""``api_key`` whenever a key is present; otherwise ``subscription`` where the Runner
|
|
87
|
+
can verify the login is this Contract's, and a refusal on a shared Runner where it
|
|
88
|
+
cannot.
|
|
89
|
+
|
|
90
|
+
``contract_uid`` says the Directive runs as its own Contract's uid (never the shared
|
|
91
|
+
no-Contract tree's); ``harness_login`` that this Contract's harness root for the
|
|
92
|
+
Directive's kind holds a login. A shared Runner with no key and either one missing has
|
|
93
|
+
nothing left that could authenticate the harness, so it refuses, naming the missing
|
|
94
|
+
one. An unregistered process (``host_party`` ``None``) never runs a subscription; it
|
|
95
|
+
runs on a key or fails at the proxy for want of one.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
if key_present:
|
|
99
|
+
return AuthMode.API_KEY
|
|
100
|
+
if host_party == "user":
|
|
101
|
+
return AuthMode.SUBSCRIPTION if AuthMode.SUBSCRIPTION in runtime_modes else AuthMode.API_KEY
|
|
102
|
+
if not is_shared_runner(host_party):
|
|
103
|
+
return AuthMode.API_KEY
|
|
104
|
+
if not contract_uid:
|
|
105
|
+
raise AuthModeRefusedError(
|
|
106
|
+
SUBSCRIPTION_NEEDS_CONTRACT_UID_RULE,
|
|
107
|
+
f"this Runner is hosted by {host_party!r} and does not run this Contract under its "
|
|
108
|
+
"own uid, so it runs API keys only, and no API key is present for this Contract",
|
|
109
|
+
)
|
|
110
|
+
if not harness_login:
|
|
111
|
+
raise AuthModeRefusedError(
|
|
112
|
+
SUBSCRIPTION_NEEDS_HARNESS_LOGIN_RULE,
|
|
113
|
+
"no API key is present for this Contract and its harness root on this Runner holds "
|
|
114
|
+
"no login; sign it in on this Runner with `agentic-runner sign-in`",
|
|
115
|
+
)
|
|
116
|
+
if AuthMode.SUBSCRIPTION not in runtime_modes:
|
|
117
|
+
raise AuthModeRefusedError(
|
|
118
|
+
NO_RUNTIME_SUBSCRIPTION_RULE,
|
|
119
|
+
"no API key is present for this Contract and this Runner's runtime for its CLI "
|
|
120
|
+
"kind runs on API keys only",
|
|
121
|
+
)
|
|
122
|
+
return AuthMode.SUBSCRIPTION
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def is_setup_token(value: str) -> bool:
|
|
126
|
+
return value.strip().startswith(_SETUP_TOKEN_PREFIX)
|
|
@@ -15,6 +15,10 @@ 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
|
+
|
|
18
22
|
``skill install | print`` hands the setup skill to the person's own Claude Code or Codex
|
|
19
23
|
(``skill.py``, console-v2 issue 21).
|
|
20
24
|
|
|
@@ -36,6 +40,7 @@ import os
|
|
|
36
40
|
import sys
|
|
37
41
|
from collections.abc import Sequence
|
|
38
42
|
from pathlib import Path
|
|
43
|
+
from uuid import UUID
|
|
39
44
|
|
|
40
45
|
from agentic_runner import __version__ as runner_version
|
|
41
46
|
from agentic_runner import service, skill, workstation
|
|
@@ -59,6 +64,7 @@ from agentic_runner.service import (
|
|
|
59
64
|
TAGS_ENV,
|
|
60
65
|
parse_tags,
|
|
61
66
|
)
|
|
67
|
+
from agentic_runner.workers.claude_sign_in import OAUTH_TOKEN, SIGN_IN_METHODS
|
|
62
68
|
from agentic_runner_contracts import __version__ as contracts_version
|
|
63
69
|
from agentic_runner_contracts.runner_registration import (
|
|
64
70
|
HostAttestation,
|
|
@@ -198,6 +204,21 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
198
204
|
status.add_argument("org", nargs="?", help="a workstation Organisation (issue 47)")
|
|
199
205
|
status.add_argument("--root", default=None, type=Path)
|
|
200
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
|
+
|
|
201
222
|
annotate = subcommands.add_parser("annotate", help="attach an annotation to this Work Record")
|
|
202
223
|
annotate.add_argument("--context", default="default")
|
|
203
224
|
annotate.add_argument(
|
|
@@ -303,6 +324,8 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
303
324
|
for line in lines:
|
|
304
325
|
print(line)
|
|
305
326
|
return 0
|
|
327
|
+
if arguments.command == "sign-in":
|
|
328
|
+
return _sign_in(arguments)
|
|
306
329
|
if arguments.command == "skill":
|
|
307
330
|
if arguments.action == "print":
|
|
308
331
|
sys.stdout.write(skill.text())
|
|
@@ -366,6 +389,26 @@ def _image_attestation() -> HostAttestation:
|
|
|
366
389
|
)
|
|
367
390
|
|
|
368
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
|
+
|
|
369
412
|
def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
370
413
|
if not arguments.control_plane:
|
|
371
414
|
print(f"--control-plane (or {CONTROL_PLANE_ENV}) is required", file=sys.stderr)
|
{agentic_runner-3.13.0 → agentic_runner-3.15.0}/src/agentic_runner/device_login_activities.py
RENAMED
|
@@ -13,8 +13,7 @@ from datetime import UTC, datetime
|
|
|
13
13
|
from temporalio import activity
|
|
14
14
|
from temporalio.exceptions import ApplicationError
|
|
15
15
|
|
|
16
|
-
from agentic_runner.auth_mode import
|
|
17
|
-
from agentic_runner.workers.claude_sign_in import OAUTH_TOKEN
|
|
16
|
+
from agentic_runner.auth_mode import SIGN_IN_NEEDS_CONTRACT_UID_RULE, runs_subscriptions
|
|
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 (
|
|
@@ -38,7 +37,8 @@ class ContractDeviceLoginActivities:
|
|
|
38
37
|
) -> None:
|
|
39
38
|
self._contract_isolation = contract_isolation
|
|
40
39
|
# The party this process was registered as hosted by. A shared Runner signs in only
|
|
41
|
-
#
|
|
40
|
+
# where its Directives run as each Contract's own uid (QTS-1450); a person signs
|
|
41
|
+
# 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_where_no_subscription_runs()
|
|
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")
|
|
@@ -74,7 +70,7 @@ class ContractDeviceLoginActivities:
|
|
|
74
70
|
self, request: ContractDeviceLoginStatusInput
|
|
75
71
|
) -> ContractDeviceLoginStatusResult:
|
|
76
72
|
if request.runtime_kind == "claude_code":
|
|
77
|
-
self.
|
|
73
|
+
self._refuse_where_no_subscription_runs()
|
|
78
74
|
status = await self._device_login.claude.status(request.contract_id)
|
|
79
75
|
return ContractDeviceLoginStatusResult(
|
|
80
76
|
contract_id=request.contract_id,
|
|
@@ -84,7 +80,7 @@ class ContractDeviceLoginActivities:
|
|
|
84
80
|
auth_method=status.auth_method,
|
|
85
81
|
subscription_type=status.subscription_type,
|
|
86
82
|
)
|
|
87
|
-
self.
|
|
83
|
+
self._refuse_where_no_subscription_runs()
|
|
88
84
|
present = self._device_login.token_present(
|
|
89
85
|
request.contract_id, runtime_kind=request.runtime_kind
|
|
90
86
|
)
|
|
@@ -102,20 +98,22 @@ class ContractDeviceLoginActivities:
|
|
|
102
98
|
delivered_at=delivered_at,
|
|
103
99
|
)
|
|
104
100
|
|
|
105
|
-
def
|
|
101
|
+
def _refuse_where_no_subscription_runs(self) -> None:
|
|
106
102
|
"""Non-retryable, and typed by the rule: there is no Work Record to write Evidence
|
|
107
103
|
on here, so the failure the platform's sign-in workflow receives names it.
|
|
108
104
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`choose_auth_mode` never runs a subscription on it.
|
|
105
|
+
A sign-in, or a read of one, only where a Directive could run on it
|
|
106
|
+
(``auth_mode.runs_subscriptions``): the person's own Runner, or a shared one that
|
|
107
|
+
runs each Contract as its own uid. One whose state names no host party (registered
|
|
108
|
+
before issue 42) is refused, as `choose_auth_mode` never runs a subscription on it.
|
|
109
|
+
"""
|
|
113
110
|
|
|
114
|
-
if
|
|
111
|
+
if not runs_subscriptions(
|
|
112
|
+
self._host_party, separates_contract_uids=self._contract_isolation.can_separate_uids
|
|
113
|
+
):
|
|
115
114
|
raise ApplicationError(
|
|
116
|
-
f"this Runner is hosted by {self._host_party!r}
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
type=SHARED_RUNNER_SIGN_IN_RULE,
|
|
115
|
+
f"this Runner is hosted by {self._host_party!r} and does not run each Contract "
|
|
116
|
+
"under its own uid, so no subscription runs on it and it signs nothing in",
|
|
117
|
+
type=SIGN_IN_NEEDS_CONTRACT_UID_RULE,
|
|
120
118
|
non_retryable=True,
|
|
121
119
|
)
|
|
@@ -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 (
|
|
@@ -46,6 +48,7 @@ from temporalio.worker import (
|
|
|
46
48
|
from agentic_runner import __version__ as runner_version
|
|
47
49
|
from agentic_runner.activities import RunnerRalphActivities
|
|
48
50
|
from agentic_runner.attempts import AttemptRecords
|
|
51
|
+
from agentic_runner.auth_mode import SIGN_IN_NEEDS_CONTRACT_UID_RULE, runs_subscriptions
|
|
49
52
|
from agentic_runner.child_watcher import install_stop_tolerant_child_watcher
|
|
50
53
|
from agentic_runner.config import RunnerConfig
|
|
51
54
|
from agentic_runner.config import load as load_config
|
|
@@ -110,7 +113,7 @@ from agentic_runner.user_sources import ProxyTriage, UserSourcePoller
|
|
|
110
113
|
from agentic_runner.workers.acp_runtime import ACP_BRIDGES, AcpRuntime, profile_acp_executables
|
|
111
114
|
from agentic_runner.workers.agent_runtime import AgentRuntime
|
|
112
115
|
from agentic_runner.workers.claude_runtime import ClaudeRuntime
|
|
113
|
-
from agentic_runner.workers.claude_sign_in import ClaudeSignIns
|
|
116
|
+
from agentic_runner.workers.claude_sign_in import ClaudeSignInError, ClaudeSignIns
|
|
114
117
|
from agentic_runner.workers.codex_runtime import CodexRuntime
|
|
115
118
|
from agentic_runner.workers.contract_device_login import ContractDeviceLogin
|
|
116
119
|
from agentic_runner.workers.contract_isolation import UID_MAP_FILENAME, ContractIsolation
|
|
@@ -135,11 +138,7 @@ from agentic_runner_contracts.runner_registration import (
|
|
|
135
138
|
ResourcePressure,
|
|
136
139
|
)
|
|
137
140
|
from agentic_runner_contracts.runtime_context import WorkerRuntimeContextResolver
|
|
138
|
-
from agentic_runner_contracts.sealed_credential import
|
|
139
|
-
SignInCodeOutcome,
|
|
140
|
-
SignInCodeRelay,
|
|
141
|
-
key_fingerprint,
|
|
142
|
-
)
|
|
141
|
+
from agentic_runner_contracts.sealed_credential import key_fingerprint
|
|
143
142
|
|
|
144
143
|
__all__ = [
|
|
145
144
|
"AGENT_TOKEN_ENV",
|
|
@@ -166,6 +165,7 @@ __all__ = [
|
|
|
166
165
|
"run",
|
|
167
166
|
"run_runner_lifecycle_hook",
|
|
168
167
|
"run_worker_until_terminated",
|
|
168
|
+
"sign_in_claude",
|
|
169
169
|
"with_profile_acp_cli_kinds",
|
|
170
170
|
]
|
|
171
171
|
|
|
@@ -657,13 +657,8 @@ class ControlPlaneStream:
|
|
|
657
657
|
usage_windows: UsageWindows | None = None
|
|
658
658
|
# Local-agents 04b: which delivered references fill the LLM proxy's slot, and where.
|
|
659
659
|
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
660
|
# Console-v2 issue 30: the OAuth Connectors whose code, tokens and verifier stay here.
|
|
663
661
|
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
662
|
_delivered: set[tuple[str, str]] = field(default_factory=set)
|
|
668
663
|
_offered_to_proxy: dict[tuple[str, str], str] = field(default_factory=dict)
|
|
669
664
|
_last_seen: tuple[float, datetime] | None = None
|
|
@@ -773,17 +768,14 @@ class ControlPlaneStream:
|
|
|
773
768
|
clock_skew_seconds=self._clock_skew(now),
|
|
774
769
|
source_status=self.sources.statuses() if self.sources is not None else [],
|
|
775
770
|
tool_servers=self.tool_servers.latest() if self.tool_servers is not None else [],
|
|
776
|
-
sign_in_codes=self._sign_in_outcomes,
|
|
777
771
|
oauth_authorizations=self.oauth.take_authorizations() if self.oauth else [],
|
|
778
772
|
oauth_tokens=self.oauth.uploads() if self.oauth else [],
|
|
779
773
|
oauth_outcomes=self.oauth.take_outcomes() if self.oauth else [],
|
|
780
774
|
)
|
|
781
|
-
self._sign_in_outcomes = []
|
|
782
775
|
ack = await self.client.heartbeat(self.state, envelope)
|
|
783
776
|
if self.sources is not None:
|
|
784
777
|
self.sources.assign(ack.user_sources)
|
|
785
778
|
await self._deliver_transcripts(ack)
|
|
786
|
-
await self._relay_sign_in_codes(ack)
|
|
787
779
|
if self.oauth is not None:
|
|
788
780
|
# Before the sealed values are applied: a Disconnect revokes with the tokens
|
|
789
781
|
# this Runner holds, and the same ack may already have withdrawn their ciphertext.
|
|
@@ -836,28 +828,6 @@ class ControlPlaneStream:
|
|
|
836
828
|
"transcript %s could not be delivered: %s", request.request_id, error
|
|
837
829
|
)
|
|
838
830
|
|
|
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
831
|
def _sync_delivered(self) -> None:
|
|
862
832
|
"""The resolver holds exactly what the last ack let this installation open."""
|
|
863
833
|
|
|
@@ -1021,6 +991,69 @@ async def run(
|
|
|
1021
991
|
await readiness.close()
|
|
1022
992
|
|
|
1023
993
|
|
|
994
|
+
async def sign_in_claude(contract_id: str, *, method: str) -> int:
|
|
995
|
+
"""``agentic-runner sign-in``: Claude Code's own flow for one Contract, in this terminal.
|
|
996
|
+
|
|
997
|
+
Runs with the Runner's environment (``kubectl exec``, or ``--org`` on a workstation),
|
|
998
|
+
so the Contract gets the uid and the harness root its Directives already run under.
|
|
999
|
+
Nothing reaches the control plane: the console only reads the outcome back through
|
|
1000
|
+
``claude auth status`` (QTS-1448).
|
|
1001
|
+
|
|
1002
|
+
Refused up front where no Directive could run on the login (``runs_subscriptions``,
|
|
1003
|
+
QTS-1450), and confirmed at a prompt for a Contract this Runner holds no harness root
|
|
1004
|
+
for yet: a mistyped id would otherwise get a uid and a signed-in tree of its own.
|
|
1005
|
+
"""
|
|
1006
|
+
|
|
1007
|
+
mode = isolation_mode()
|
|
1008
|
+
can = can_separate_uids()
|
|
1009
|
+
try:
|
|
1010
|
+
require_isolation_supported(mode, can_separate_uids=can)
|
|
1011
|
+
except RunnerRegistrationError as error:
|
|
1012
|
+
print(f"refusing to sign in ({error.reason}): {error}", file=sys.stderr)
|
|
1013
|
+
return 1
|
|
1014
|
+
control_plane = os.environ.get(CONTROL_PLANE_ENV, "").strip()
|
|
1015
|
+
if control_plane:
|
|
1016
|
+
os.environ.setdefault("INTERNAL_FASTAPI_BASE_URL", control_plane)
|
|
1017
|
+
try:
|
|
1018
|
+
settings = get_worker_settings()
|
|
1019
|
+
except ValidationError:
|
|
1020
|
+
print(
|
|
1021
|
+
"refusing to sign in: run this with the Runner's environment "
|
|
1022
|
+
"(`--org <org>` on a workstation, `kubectl exec` into the Runner's pod)",
|
|
1023
|
+
file=sys.stderr,
|
|
1024
|
+
)
|
|
1025
|
+
return 2
|
|
1026
|
+
state = load_state(load_config().state_dir)
|
|
1027
|
+
host_party = (state.host_party if state is not None else "") or None
|
|
1028
|
+
if not runs_subscriptions(host_party, separates_contract_uids=can):
|
|
1029
|
+
runner = f"hosted by {host_party!r}" if host_party else "not registered"
|
|
1030
|
+
print(
|
|
1031
|
+
f"refusing to sign in ({SIGN_IN_NEEDS_CONTRACT_UID_RULE}): this Runner is {runner} "
|
|
1032
|
+
"and does not run each Contract under its own uid, so no Directive would run on "
|
|
1033
|
+
"the login",
|
|
1034
|
+
file=sys.stderr,
|
|
1035
|
+
)
|
|
1036
|
+
return 1
|
|
1037
|
+
sign_ins = ClaudeSignIns(build_contract_isolation(settings, can_separate_uids=can))
|
|
1038
|
+
if not sign_ins.has_harness_root(contract_id) and not _confirm_new_contract(contract_id):
|
|
1039
|
+
print("not signed in", file=sys.stderr)
|
|
1040
|
+
return 1
|
|
1041
|
+
try:
|
|
1042
|
+
return await sign_ins.sign_in(contract_id, method=method)
|
|
1043
|
+
except ClaudeSignInError as error:
|
|
1044
|
+
print(f"sign-in failed: {error}", file=sys.stderr)
|
|
1045
|
+
return 1
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def _confirm_new_contract(contract_id: str) -> bool:
|
|
1049
|
+
answer = input(
|
|
1050
|
+
f"This Runner holds no Claude Code harness root for Contract {contract_id} yet. Check "
|
|
1051
|
+
"the id against the console: a mistyped one signs in a Contract no Directive runs as. "
|
|
1052
|
+
"Sign it in? [y/N] "
|
|
1053
|
+
)
|
|
1054
|
+
return answer.strip().lower() in {"y", "yes"}
|
|
1055
|
+
|
|
1056
|
+
|
|
1024
1057
|
async def _serve(
|
|
1025
1058
|
*,
|
|
1026
1059
|
config: RunnerConfig,
|
|
@@ -1157,7 +1190,6 @@ async def _serve_registered(
|
|
|
1157
1190
|
messages = MessageStore(state_dir / MESSAGES_DIR)
|
|
1158
1191
|
stream.messages = messages
|
|
1159
1192
|
sign_ins = ClaudeSignIns(build_contract_isolation(settings, can_separate_uids=can_change_uid))
|
|
1160
|
-
stream.sign_ins = sign_ins
|
|
1161
1193
|
stream.oauth = OAuthConnectors(
|
|
1162
1194
|
sealed=stream.sealed, provider=HttpOAuthProvider(), credentials=credentials
|
|
1163
1195
|
)
|
|
@@ -1243,7 +1275,6 @@ async def _serve_registered(
|
|
|
1243
1275
|
task.cancel()
|
|
1244
1276
|
with contextlib.suppress(asyncio.CancelledError):
|
|
1245
1277
|
await task
|
|
1246
|
-
await sign_ins.close()
|
|
1247
1278
|
await run_runner_lifecycle_hook(hooks, HookName.RUNNER_SHUTDOWN)
|
|
1248
1279
|
await _report_stop(link, lifecycle)
|
|
1249
1280
|
return 1 if revoked.is_set() else 0
|
|
@@ -1346,13 +1377,12 @@ def build_usage_windows(
|
|
|
1346
1377
|
isolation: ContractIsolation,
|
|
1347
1378
|
in_flight: DirectivesInFlight,
|
|
1348
1379
|
) -> UsageWindows | None:
|
|
1349
|
-
"""A probe per served harness, or None
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
so only there is a usage window anyone's to read.
|
|
1380
|
+
"""A probe per served harness, or None on a Runner that runs no subscription
|
|
1381
|
+
(local-agents 10): only where one can run is there a usage window anyone's to read
|
|
1382
|
+
(``auth_mode.runs_subscriptions``, QTS-1450).
|
|
1353
1383
|
"""
|
|
1354
1384
|
|
|
1355
|
-
if host_party
|
|
1385
|
+
if not runs_subscriptions(host_party, separates_contract_uids=isolation.can_separate_uids):
|
|
1356
1386
|
return None
|
|
1357
1387
|
probes: dict[str, UsageWindowProbe] = {
|
|
1358
1388
|
"codex_cli": CodexRateLimitsProbe(),
|