2929 sys .path .insert (0 , str (ROOT ))
3030
3131from scripts .live_canary .auth_registry import SeededProviderCase , configured_seeded_cases
32- from scripts .live_canary .auth_runtime import activate_extension , install_extension , put_secret
32+ from scripts .live_canary .auth_runtime import (
33+ activate_extension ,
34+ install_extension ,
35+ put_secret ,
36+ write_memory ,
37+ )
3338from scripts .live_canary .common import (
3439 DEFAULT_SECRETS_MASTER_KEY ,
3540 DEFAULT_VENV ,
4954
5055DEFAULT_OUTPUT_DIR = ROOT / "artifacts" / "auth-live-canary"
5156OWNER_USER_ID = "auth-live-owner"
52- GOOGLE_SCOPE_DEFAULT = "gmail.modify gmail.compose calendar.events"
57+ GOOGLE_SCOPE_DEFAULT = " " .join (
58+ [
59+ "https://www.googleapis.com/auth/gmail.modify" ,
60+ "https://www.googleapis.com/auth/gmail.compose" ,
61+ "https://www.googleapis.com/auth/calendar.events" ,
62+ "https://www.googleapis.com/auth/drive" ,
63+ "https://www.googleapis.com/auth/documents" ,
64+ "https://www.googleapis.com/auth/spreadsheets" ,
65+ "https://www.googleapis.com/auth/presentations" ,
66+ ]
67+ )
5368
5469
5570def expire_secret_in_db (db_path : Path , user_id : str , secret_name : str ) -> None :
@@ -95,6 +110,7 @@ async def create_response_probe(
95110 response_id = body .get ("id" )
96111 output = body .get ("output" , [])
97112 tool_names = [item .get ("name" ) for item in output if item .get ("type" ) == "function_call" ]
113+ expected_tool_names = probe .required_tool_names
98114 tool_outputs = [
99115 item .get ("output" , "" )
100116 for item in output
@@ -120,14 +136,14 @@ async def create_response_probe(
120136
121137 success = (
122138 body .get ("status" ) == "completed"
123- and probe . expected_tool_name in tool_names
139+ and all ( tool_name in tool_names for tool_name in expected_tool_names )
124140 and bool (tool_outputs )
125141 and not any (
126142 marker in output_text .lower ()
127143 for output_text in tool_outputs
128144 for marker in ("error" , "authentication required" , "unauthorized" , "forbidden" )
129145 )
130- and probe .expected_text in response_text
146+ and ( not probe .expected_text or probe . expected_text in response_text )
131147 and fetched_status == 200
132148 )
133149
@@ -140,6 +156,7 @@ async def create_response_probe(
140156 "response_id" : response_id ,
141157 "status" : body .get ("status" ),
142158 "tool_names" : tool_names ,
159+ "expected_tool_names" : expected_tool_names ,
143160 "tool_outputs" : tool_outputs ,
144161 "response_text" : response_text ,
145162 "get_status_code" : fetched_status ,
@@ -291,6 +308,64 @@ async def seed_live_credentials(base_url: str, token: str, db_path: Path) -> Non
291308 provider = "mcp:notion" ,
292309 )
293310
311+ linear_access = env_str ("AUTH_LIVE_LINEAR_ACCESS_TOKEN" )
312+ linear_refresh = env_str ("AUTH_LIVE_LINEAR_REFRESH_TOKEN" )
313+ if linear_refresh and not linear_access :
314+ raise CanaryError (
315+ "AUTH_LIVE_LINEAR_ACCESS_TOKEN is required when AUTH_LIVE_LINEAR_REFRESH_TOKEN is set"
316+ )
317+ if linear_access :
318+ await put_secret (
319+ base_url ,
320+ token ,
321+ user_id = OWNER_USER_ID ,
322+ name = "mcp_linear_access_token" ,
323+ value = linear_access ,
324+ provider = "mcp:linear" ,
325+ )
326+ if linear_refresh :
327+ await put_secret (
328+ base_url ,
329+ token ,
330+ user_id = OWNER_USER_ID ,
331+ name = "mcp_linear_access_token_refresh_token" ,
332+ value = linear_refresh ,
333+ provider = "mcp:linear" ,
334+ )
335+
336+ for env_name , secret_name , provider in (
337+ ("AUTH_LIVE_BRAVE_API_KEY" , "brave_api_key" , "brave" ),
338+ ("AUTH_LIVE_SLACK_BOT_TOKEN" , "slack_bot_token" , "slack" ),
339+ ("AUTH_LIVE_COMPOSIO_API_KEY" , "composio_api_key" , "composio" ),
340+ ("AUTH_LIVE_TELEGRAM_API_ID" , "telegram_api_id" , "telegram" ),
341+ ("AUTH_LIVE_TELEGRAM_API_HASH" , "telegram_api_hash" , "telegram" ),
342+ ):
343+ value = env_str (env_name )
344+ if value :
345+ await put_secret (
346+ base_url ,
347+ token ,
348+ user_id = OWNER_USER_ID ,
349+ name = secret_name ,
350+ value = value ,
351+ provider = provider ,
352+ )
353+
354+ telegram_api_id = env_str ("AUTH_LIVE_TELEGRAM_API_ID" )
355+ telegram_api_hash = env_str ("AUTH_LIVE_TELEGRAM_API_HASH" )
356+ telegram_session = env_str ("AUTH_LIVE_TELEGRAM_SESSION_JSON" )
357+ if telegram_api_id :
358+ await write_memory (base_url , token , path = "telegram/api_id" , content = telegram_api_id )
359+ if telegram_api_hash :
360+ await write_memory (base_url , token , path = "telegram/api_hash" , content = telegram_api_hash )
361+ if telegram_session :
362+ await write_memory (
363+ base_url ,
364+ token ,
365+ path = "telegram/session.json" ,
366+ content = telegram_session ,
367+ )
368+
294369
295370def parse_args () -> argparse .Namespace :
296371 parser = argparse .ArgumentParser (description = __doc__ )
@@ -325,7 +400,14 @@ def parse_args() -> argparse.Namespace:
325400 parser .add_argument (
326401 "--case" ,
327402 action = "append" ,
328- choices = ("gmail" , "google_calendar" , "github" , "notion" ),
403+ choices = (
404+ "gmail" ,
405+ "google_calendar" ,
406+ "github" ,
407+ "notion" ,
408+ "linear" ,
409+ "ops_workflow" ,
410+ ),
329411 help = "Limit the run to specific providers. Repeat for multiple values." ,
330412 )
331413 parser .add_argument (
@@ -374,21 +456,27 @@ async def async_main(args: argparse.Namespace) -> int:
374456 try :
375457 await seed_live_credentials (stack .base_url , stack .gateway_token , stack .db_path )
376458
459+ installed : dict [str , dict [str , Any ]] = {}
377460 for probe in probes :
378- ext = await install_extension (
379- stack .base_url ,
380- stack .gateway_token ,
381- name = probe .extension_install_name ,
382- expected_display_name = probe .expected_display_name ,
383- install_kind = probe .install_kind ,
384- install_url = probe .install_url ,
385- )
386- await activate_extension (
387- stack .base_url ,
388- stack .gateway_token ,
389- extension_name = ext ["name" ],
390- expected_display_name = ext .get ("display_name" ) or probe .expected_display_name ,
391- )
461+ for installation in probe .installations :
462+ if installation .name in installed :
463+ continue
464+ ext = await install_extension (
465+ stack .base_url ,
466+ stack .gateway_token ,
467+ name = installation .name ,
468+ expected_display_name = installation .expected_display_name ,
469+ install_kind = installation .install_kind ,
470+ install_url = installation .install_url ,
471+ )
472+ await activate_extension (
473+ stack .base_url ,
474+ stack .gateway_token ,
475+ extension_name = ext ["name" ],
476+ expected_display_name = ext .get ("display_name" )
477+ or installation .expected_display_name ,
478+ )
479+ installed [installation .name ] = ext
392480
393481 results : list [ProbeResult ] = []
394482 for probe in probes :
0 commit comments