
    )Xjqd                    |   d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	 dddd	Z
 eej                  j                  d
d            Z eej                  j                  dd            Z eej                  j                  dd            ZdZddZddZddZ G d d      Z G d d      Z e       ZddZd Zy)u	  Pre-warmed, supervised background workers — the agent pool.

WHY THIS EXISTS (2026-07-14, Ahmed's call after three failed fixes): the old
delegation path ran workers as background Task-tool subagents INSIDE the
brain's CLI session. That broke in three ways at once:
  1. SLOW START — a "spawn" was a full coordinator turn streaming the whole
     worker prompt into a Task call before the subagent even booted (10-20s).
  2. BLIND — the SDK stream is only read while a brain turn is open, so a
     worker finishing/dying/stalling while the room was quiet sat unread:
     HUD chip stuck "running" forever, no report, Ahmed waiting on nothing.
  3. HONOUR-SYSTEM REPORTS — a worker only reported by remembering to append
     to control/report.txt; a crashed or stuck worker wrote nothing, and
     nothing supervised it.

The pool fixes all three by making workers SEPARATE ClaudeSDKClient sessions
owned by the engine, exactly like Claude Code's own harness:
  - PRE-WARMED: WARM_SPARES (default 1) idle sessions sit fully connected
    (CLI subprocess + MCP servers booted). dispatch() hands the prompt to a
    warm spare INSTANTLY, and the moment a spare is taken a replacement
    starts warming — there is always one ready (Ahmed's N+1 rule).
  - SUPERVISED: every running worker has a dedicated reader task consuming
    its stream the whole time. Every tool call emits task_progress (live HUD
    chip note), a no-activity watchdog reports a stall LOUDLY instead of
    letting Ahmed wait forever, a crash/death reports LOUDLY, and the final
    result is delivered through report_sink (the engine inbox rail) the
    moment it lands — no report.txt honour system.
  - KILLABLE: kill() disconnects the CLI subprocess immediately and clears
    the HUD chip in the same breath — no zombie UI. The warm spare means
    capacity is instantly back.

Tiers map to models exactly like the old _AGENTS: worker=sonnet (the only
pre-warmed tier — it's the default), heavy=opus, genius=fable (cold-started
on demand; a few seconds slower, which is fine for rare heavy jobs).

Env knobs: WARM_SPARES (1), AGENT_MAX (4 running), WORKER_STALL_S (300,
0=off), AGENT_POOL=0 disables the whole pool (llm.py then restores the old
Task-tool path).

Wiring: main.py sets pool.report_sink = app.inbox.put_nowait and calls
pool.start() at boot / pool.stop() at shutdown; llm.py registers
build_server() as the "agents" MCP server on the BRAIN session only
(workers never get it — no recursive fan-out).
    )annotationsNtask_ledger)emitsonnetopusfableworkerheavygeniusWARM_SPARES1	AGENT_MAX4WORKER_STALL_S300g      4@c                @    	 ddl m}  ||        y# t        $ r Y yw xY w)uG   Same trail as llm.py's _log_agent — one lifecycle log for everything.r   )
_log_agentN)	voice.llmr   	Exception)liner   s     >/Users/ahmed/devFolder/Ultron/claude-voice/voice/agent_pool.py_logr   @   s$    (4 s    	c                   | r| j                  d      ry| j                  d      s#| j                  d      s| j                  d      sy	 ddlm} ddlm} |j	                         syd	|j                  |       z   }|j                  |d
       |j                  | j                  dd             y# t        $ r$}t        dt        |      dd         Y d}~yd}~ww xY w)u  Drop a milestone into jarvis-memory when a task ends, so "what happened
    to X?" works days later from any device — not just this process's lifetime.
    Fire-and-forget (never blocks the pool); skips trivial no-op tasks and only
    checkpoints once per task.checkpointedNresulterrorstepsr   r   )memory_controlu   Background task — task)kindid zPOOL checkpoint skipped: x   )getvoicer   r    enabled	summarize	fire_savemark_checkpointedr   r   str)rec_tlr    textes        r   _checkpointr1   I   s    
 #''.)GGH!1SWWW5E	9,(%%'%c(::  F 3cggdB/0 9(Q7889s   
B0 'AB0 0	C9CCc                   t        | dd      xs d}t        | dd      xs i }d}	 |dk(  rt        |j                  dd            dd }n|d	v r<t        j                  j                  t        |j                  d
d                  dd }n|dk(  rt        |j                  dd            dd }no|dk(  rt        |j                  dd            dd }nK|j                         D ]8  }t        |t              s|j                         s%|j                         dd } n |j                  d      d   }|r| d| S |S # t        $ r d}Y ,w xY w)uF   One short human line for a tool call — what the HUD chip shows live.namer$   toolinputNBashcommand0   )ReadWriteEdit	file_path(   	WebSearchqueryWebFetchurl__: )getattrr,   r&   ospathbasenamevalues
isinstancestripr   split)blockr3   argshintvs        r   
_tool_noterQ   _   sL   5&"%/D5'4(.BDD6>txx	2./4D..77##Cb(A$BCCRHD[ txx,-cr2DZtxxr*+CR0D [[]a%!'')779Sb>D # ::dBD $dV2dV.$.  s   CD< 6D< D< <E
	E
c                  $    e Zd ZddZddZddZy)_Workerc                    || _         || _        || _        || _        d | _        t        j                         | _        t        j                         | _        d| _	        d| _
        d| _        y )NstartingFr$   )r#   tiertitleclientreadertime	monotonicstartedlast_activity	last_notestall_warned	ledger_id)selfwidrV   rW   rX   s        r   __init__z_Worker.__init__z   sX    	
+/~~'!^^-#!    c                    t        t        j                         | j                  z
        }|dk\  r|dz   d|dz  ddS | dS )N<   m02ds)intrZ   r[   r\   )ra   ri   s     r   agez_Worker.age   sJ     4<</0-."W!r'!AF3<q)AQCq'Ard   c                D    t        j                         | j                  z
  S N)rZ   r[   r]   ra   s    r   idlez_Worker.idle   s    ~~$"4"444rd   N)rb   r,   rV   r,   rW   r,   returnNonerp   r,   )rp   float)__name__
__module____qualname__rc   rk   ro    rd   r   rS   rS   y   s    
B5rd   rS   c                      e Zd ZdZddZddZddZddZddZddZ	dddZ
ddd	Zdd
ZddZddZddZ	 d	 	 	 ddZy)	AgentPoolzBOwns every background worker session: warm spares + running tasks.c                X    g | _         d| _        i | _        d| _        d| _        d | _        y )Nr   F)_spares_warming_running_seq_startedreport_sinkrn   s    r   rc   zAgentPool.__init__   s-    ,.	rd   c                2    d| _         | j                          y)zABegin keeping WARM_SPARES sessions warm (call on the event loop).TN)r   _ensure_warmrn   s    r   startzAgentPool.start   s    rd   c                N  K   d| _         t        | j                  j                               D ]  }| j	                  |d       d {     | j
                  D ]  }	 |j                          d {     | j
                  j                          y 7 K7 ## t        $ r Y Hw xY ww)NF)announce)	r   listr}   rI   
_terminater{   
disconnectr   clear)ra   wrX   s      r   stopzAgentPool.stop   s     dmm**,-A//!e/444 .llF''))) #
 	 5 * sH   AB%BB%B0B1B5B%B	B"B%!B""B%c                0   | j                   syt        t        | j                        z
  | j                  z
  }t        t        d|            D ]H  }| xj                  dz  c_        t        j                         j                  | j                                J y)z:Top the spare pool back up to WARM_SPARES, asynchronously.Nr      )r   r   lenr{   r|   rangemaxasyncioget_running_loopcreate_task	_warm_one)ra   want_s      r   r   zAgentPool._ensure_warm   si    }}S..>s1d|$AMMQM$$&224>>3CD %rd   c                  K   	 | j                  d       d {   }| j                  j                  |       t        dt	        | j                         d       | xj                  d	z  c_        y 7 W# t
        $ r?}t        dt        |      d d         t        dt        |      d d  d       Y d }~[d }~ww xY w# | xj                  d	z  c_        w xY ww)
Nr   zPOOL warm spare ready (spares=)zPOOL warm FAILED:    z$       [agent pool: warm-up failed: r%   ]r   )	_connectr{   appendr   r   r   r,   printr|   )ra   rX   r0   s      r   r   zAgentPool._warm_one   s     		==22FLL'1#dll2C1DAFG MMQM 3  	J%c!fTcl^458QaHII	J
 MMQMsK   CA2 A0AA2 C0A2 2	B:;5B50B= 5B::B= =CCc                x   K   ddl m} ddlm}  | ||            }|j	                          d {    |S 7 w)Nr   )ClaudeSDKClient)worker_options)options)claude_agent_sdkr   r   r   connect)ra   rV   r   r   rX   s        r   r   zAgentPool._connect   s5     4, )=>nn 	s   /:8:c           	       K   |t         vrd}t        | j                        t        k\  r=dj	                  d | j                  j                         D              }dt         d| dS | xj                  dz  c_        d| j                   }d	}|dk(  r*| j                  r| j                  j                  d
      }d}n	 | j                  |       d {   }t        |||d d |      }	|	| j                  |<   t        j                  ||	j                   ||      |	_        	 |j%                  |       d {    t+        j,                         j/                  | j1                  |	            |	_        t5        d|	j                   ||       t        d| d| |rdnd d|	j                          | j7                          d| d| d|rd  d"S d! d"S 7 # t        $ r<}t        d| dt        |      d d         d| dt        |      d d  dcY d }~S d }~ww xY w7 # t        $ r}| j                  j                  |d        	 |j'                          d {  7   n# t        $ r Y nw xY wt        j(                  |	j"                  dt        |      d d         t        d| dt        |      d d         dt        |      d d  dcY d }~S d }~ww xY ww)#Nr   , c              3  <   K   | ]  }d |j                    d   yw)'N)rW   .0r   s     r   	<genexpr>z%AgentPool.dispatch.<locals>.<genexpr>   s     M6L!''!n6Ls   z	REFUSED: z agents already running (z)). Finish or kill one first (agent_kill).r   r   Fr   TPOOL z connect FAILED: r   zFAILED to start a z agent:    z0. Tell Ahmed plainly; do not pretend it started.P   znever started: z query FAILED: zFAILED to hand the task over: .task_started)descagentr#   z DISPATCHED [z warmz coldz] title=zAgent z
 started (u   pre-warmed — already workingzfresh sessionz[). It reports back automatically when done; agent_status checks on it; agent_kill stops it.)_TIER_MODELSr   r}   r   joinrI   r~   r{   popr   r   r   r,   rS   r   createrW   r`   r?   r   failr   r   r   _readrY   r   r   )
ra   r!   rW   rV   namesrb   warmrX   r0   r   s
             r   dispatchzAgentPool.dispatch   s    |#Dt}}*IIMdmm6J6J6LMME	{*CE7 K= > ?		Q	$))o8\\%%a(FDK#}}T22
 CuSbz62c "((aggtTB
	D,,t$$$ ++-99$**Q-H^!''#>uSEtfW',J Kgg[" 	#ZvR7;3Q R./ 	0APQ R./ 	07 3 KuSE!23q6$3<.AB,TF(3q6$3<. II J KK % 	DMMc4(''))) Q[[OCF4CL>*JK5_SVDS\N;<3CF4CL>CC	Ds   B9K<G GG AKH 1H2H 6BKG 	H1H	H
KHKH 	K"K?IIIK	I%"K$I%%A KKKKKc                X  K   g }|xs dj                         j                         }|dk(  r$t        | j                  j	                               }n|dk(  r4| j                  rt        | j                  j	                               d   g}nb|| j                  v r| j                  |   g}nC| j                  j	                         D cg c]   }||j
                  j                         v r|" }}|s&d| j                  rd| j                          z   S dz   S g }|D ]b  }| j                  |dd	       d
{    |j                  |j                   d|j
                   d       t        d|j                   d       d | j                          ddj                  |      z   dz   S c c}w 7 vw)zAtarget: a worker id ('w3'), 'latest', 'all', or a title fragment.latestallrC   zNo matching agent running.z
 Running: r$   Fkilledr   statusN 'r   r   z KILLED by requestzKilled: ; z. A warm spare is ready.)rK   lowerr   r}   rI   rW   _status_shortr   r   r#   r   r   r   )ra   targetvictimstr   r   s         r   killzAgentPool.kill   s    !#x&&(..0:4==//12G(]}} 4 4 67;<$--}}Q'(G"&--"6"6"8 0"8Qqww}}. "8G 00-- $D$6$6$8#9:1 2-/1 2 A//!eH/EEELLADD6AGG9A./5012  	DIIe,,/III0 Fs    CF*%F#)AF*1F(2A7F*c                T   | j                   s(t        | j                        }d| d|dk7  rd dS d dS g }| j                   j                         D ]  }t	        |j                               }d| d|j                   }t        r|t        kD  rd	|d
z   d|j                   d}|j                  |j                   d|j                   d|j                   d|j                          d| 	        |j                  dt        | j                         d       dj                  |      S )NzNo agents running. z warm sparer   ri   r$   z ready.zlast activity u
   s ago — u#   POSSIBLY STUCK — no activity for rf   z	m (last: r   z [z] 'u   ' — running r   (z warm spare ready)
)r}   r   r{   rI   rj   ro   r^   STALL_Sr   r#   rV   rW   rk   r   )ra   sparelinesr   ro   states         r   r   zAgentPool.status  s(   }}%E)%#qjs1: ;.01: ;%%'Aqvvx=D$TF*Q[[MBE4'>>trzl K##$;;-q2LLADD6AFF83qwwi~aeegYb!7$ % ( 	qT\\*++=>?yyrd   c                b    dj                  d | j                  j                         D              S )Nr   c              3  T   K   | ]   }|j                    d |j                   d " yw)r   r   N)r#   rW   r   s     r   r   z*AgentPool._status_short.<locals>.<genexpr>(  s(     O8N1ADD6AGG9A.8Ns   &()r   r}   rI   rn   s    r   r   zAgentPool._status_short'  s$    yyO8L8L8NOOOrd   c                D  K   ddl m}m}m}m}m}m} d}d}	d}
g }d}	 |j                  j                         j                         }	 |#t        j                  |j                               }t        j                  |ht               d{   \  }}|st        r|j                   s|j#                         t        kD  rd|_        t%        |j#                         dz        }t'        d|j(                   d	| d
       t+        d|j(                  |j,                         | j/                  d|j,                   d| d|j0                   d       
	 |j3                         }	 d}t7        j8                         |_        d|_        t=        ||      r|j>                  D ]  }t=        ||      rtA        |      |_        t+        d|j(                  |j0                         tC        jD                  |jF                  |j0                         t'        d|j(                   d|j0                          t=        ||      s|jH                  jK                         s|jH                  jK                         }
 nt=        ||      rtM        |dd      xs g }t=        |tN              r|D ]  }t=        ||      stM        |dd      s|j>                  }t=        |tP              r|n
tQ        |      }|jS                  |jK                         dd        t'        d|j(                   d|jK                         dd          n=t=        ||      r1tM        |dd      rtM        |dd      xs d}	ntM        |dd      xs |
}n2|!|jW                         s|jY                          	 |	s!|s|
sd|rd|d    nd|j0                   dz   }	|	rt'        d|j(                   d |j]                          d!|	dd         tC        j^                  |jF                  |	      }ta        |       | jc                  |dd"#       d{    |rd$d%je                  |dd&        d'nd}| j/                  d(|j,                   d)|j]                          d!|	dd*  d'| d+	       nt'        d|j(                   d,|j]                          d!|xs |
dd        tC        jf                  |jF                  |xs |
      }ta        |       | jc                  |dd-#       d{    | j/                  d.|j,                   d/|j]                          d0|xs |
dd1 z          | ji                          y7 # t4        $ r Y d}w xY w# d}w xY w# t        jT                  $ rI | |jW                         s|jY                          Y |"|jW                         s|jY                          yyytZ        $ r}tQ        |      dd }	Y d}~pd}~ww xY w# |"|jW                         s|jY                          w w w xY w7 7 w)2zConsume the worker's whole stream: progress out, stall watchdog on,
        result/death delivered loudly. This is what makes agents visible.r   )AssistantMessageResultMessage	TextBlockToolResultBlockUserMessageToolUseBlockr$   NT)timeoutrf   r   z STALL warning (zm idle)task_stalled)r#   r   z Heads-up: the agent working on 'u%   ' has gone quiet — no activity for z minutes (last: zB). It may be stuck; Ahmed can say the word to kill it or leave it.Ftask_progress)r#   noteu    … contentis_errorr%   z TOOL-ERROR r   zthe session ended with an errorr   z1it ended without doing the job or saying anythingu    — every tool call failed: z (last thing it did: r   z FAILED after rD   failedr   z Tool errors seen: r      r   zThe agent working on 'z' FAILED after i,  uc    Tell Ahmed plainly what broke, then relaunch a fresh agent for it — do NOT do the task yourself.z DONE after 	completedz
Finished 'z' (z): i  )5r   r   r   r   r   r   r   rX   receive_response	__aiter__r   ensure_future	__anext__wait_POLL_Sr   r_   ro   rj   r   r#   r   rW   _reportr^   r   StopAsyncIterationrZ   r[   r]   rJ   r   rQ   r   stepr`   r/   rK   rE   r   r,   r   CancelledErrordonecancelr   rk   r   r1   r   r   finishr   )ra   r   r   r   r   r   r   r   reportr   	last_texttool_errorspendingstreamr   r   minsmsgrM   r   ctxtr0   r-   errss                            r   r   zAgentPool._read.  s    	4 	4 	!#)-M	!XX..0::<F ?%33F4D4D4FGG 'gY HHa !7 2)-"1668r>2uQTTF*:4&HI^177C>qwwi H>>BV D&&'kk] 3GHI
 #!..*C #G"&.."2!&c#34!$%e\:*4U*;AK QTTL',,Q[[!++F 5eAKK=!AB'y9ejj>N>N>P(-

(8(8(:I "-  [1
 &c9d;ArG!'40%,E *5/ B(/z5(I$)MM+5a+=a3q6 + 2 2399;t3D E $uQTTF,syy{4C?P>Q%R S &-  ]3sJ6")#x"> #G%F  ")h!=!JE T "7<<>  fYI' 9Q8HI0Q?AF 5nQUUWIRt~FG""1;;7C//!eH/EEE" *$))KO*D)EQG(* LL1!''/EEG9Bvds|nAdV <56 7
 5l1557)2v7JDS6Q5TUV$$Q[[&2EIFC//!eK/HHHLL:aggYc!%%'#>"/i$78 9u I" * "G #GB %% 	"7<<>  "7<<>  ,:"  	"VDS\F	" "7<<>  ,:"  F Is   X A-U2 UCU2 U "CU2 :U2 AU2 2U2  B9U2 9B9X 2X3CX XAX U2 	U("U+ #U2 'U((U+ +U//U2 25W.'W1 (%X W.W)#W1 )W..W1 1&XX X c                |    | j                   	 | j                  |       yt        d|        y# t        $ r Y w xY w)zADeliver to the engine inbox rail (announced when Ahmed is quiet).NzTASK   )r   r   r   )ra   r/   s     r   r   zAgentPool._report  sI    '  & 	v  s   / 	;;c                \  K   | j                   j                  |j                  d       t        j                  |j
                  |       t        d|j                  |       |j                  Y|j                  j                         s?d}	 t        j                         }|j                  |ur|j                  j                          	 |j                  j                          d{    |r | j                  d|j                    d       yy# t        $ r Y yw xY w7 6# t        $ r Y ?w xY ww)u   Tear a worker down NOW: reader cancelled, CLI subprocess gone,
        HUD chip cleared in the same breath — never a zombie chip again.N	task_done)r#   r   zStopped the agent working on 'z'.)r}   r   r#   r   markr`   r   rY   r   r   current_taskr   r   rX   r   r   rW   )ra   r   r   r   currents        r   r   zAgentPool._terminate  s      	!$$% 	f-[QTT&188G!..0 xxw&!	((%%''' LL9!''"EF   
 ( 		s`   BD,
D (D,D $D%D )#D,	DD,DD,D 	D)&D,(D))D,Nrp   rq   )rV   r,   )r   )r!   r,   rW   r,   rV   r,   rp   r,   )r   )r   r,   rp   r,   rr   )r   rS   rp   rq   )r/   r,   rp   rq   )stopped)r   rS   r   boolr   r,   rp   rq   )rt   ru   rv   __doc__rc   r   r   r   r   r   r   r   r   r   r   r   r   rw   rd   r   ry   ry      sd    L 
	E
-0^J4 "Pun  (1G!$G59Grd   ry   c                 H    t         j                  j                  dd      dk7  S )N
AGENT_POOLr   0)rF   environr&   rw   rd   r   r(   r(     s    ::>>,,33rd   c                 (   ddl m} m} d$d | dddddd	dd
d	dg ddddddgd      d%fd       } | dddi d      d%fd       } | ddddddd	ig d      d%fd       } | ddddddd	ig d      d%fd        } |d!d"||||g#      S )&u   In-process MCP server exposing the pool to the BRAIN session only.
    Explicit JSON schemas (dict-shorthand marks every param required — the
    Asana-era bug); `task` and `title` required, the rest optional.r   )r4   create_sdk_mcp_serverc                    dd| dgiS )Nr   r/   )typer/   rw   )r   s    r   _textzbuild_server.<locals>._text  s    VS9:;;rd   r   uP  Hand a task to a pre-warmed background agent — THE way to delegate (the old Task tool is gone). Starts instantly and reports back automatically when done; you stay free to talk. `task` = the FULL self-contained job description (the agent can't hear the conversation — include every detail, path and constraint). `title` = a few words for the HUD chip ('scrape coffee shops'). `tier`: worker (default, Sonnet, pre-warmed = instant) | heavy (Opus, for genuinely complex/long jobs) | genius (Fable, hardest problems or when Ahmed asks for fable) — heavy/genius start fresh (a few seconds).objectstringzFull self-contained task prompt)r
  descriptionz2-5 word label for the HUDr
   z!worker (default) | heavy | genius)r
  enumr  )r!   rW   rV   r!   rW   )r
  
propertiesrequiredc           
     V  K   t        | j                  dd            j                         }t        | j                  dd            j                         xs |d d }|s d      S t        j	                  ||t        | j                  dd                   d {   } |      S 7 w)Nr!   r$   rW   rf   zdispatch failed: empty task.rV   r   )r,   r&   rK   poolr   )rN   r!   rW   outr  s       r   r   zbuild_server.<locals>.dispatch  s     . 488FB'(..0DHHWb)*002?d3Bi788MM$"%dhhvx&@"AC CSzCs   BB)B'B)agent_statuszLive status of every background agent: what it's doing right now, how long it's been running, and whether it looks stuck. Use when Ahmed asks how a task is going.)r
  r  c                @   K    t         j                               S wrm   )r  r   rN   r  s    r   r  z"build_server.<locals>.agent_status  s      T[[]##s   
agent_killzStop a background agent immediately (its HUD chip clears at once). `target`: an agent id from agent_status ('w3'), 'latest' (default), 'all', or a fragment of the task title.r   z$'w3' | 'latest' | 'all' | title textc           
        K    t         j                  t        | j                  dd                   d {         S 7 w)Nr   r   )r  r   r,   r&   r  s    r   r  z build_server.<locals>.agent_kill   s3      499S(H)E%FGGHHGs   4AA 	Atask_recalluJ  Look up what happened to a PAST or running background task from the durable ledger — the answer to 'did you do X?' / 'what happened to X?' / 'is that done yet?'. Survives restarts and crashes, unlike agent_status (which only sees agents alive right now). ALWAYS call this before telling Ahmed you don't know the state of something he asked you to do. `query` = a few words from the task (e.g. 'email Elie', 'coffee shops Khobar'); empty = the most recent tasks. Returns each match's status (done/running/failed/killed/interrupted), when, how long it took, and its result or the error.r?   z(words from the task; empty = most recentc           
        K    t        j                  t        | j                  dd            j	                                     S w)Nr?   r$   )r   recall_textr,   r&   rK   r  s    r   r  z!build_server.<locals>.task_recall  s8       [,,S'21F-G-M-M-OPQQs   AAagentsz1.0.0)r3   versiontools)r   r,   rp   dict)rN   r   rp   r   )r   r4   r  )r4   r  r   r  r  r  r  s         @r   build_serverr!    sB    =< 
*	  ('HJ!)(DF ( ='JL (
*+,-+, 
., 2
.	0
$0
$ 
,4 "X%K%M N 		II 
-A !H4:$; < 	RR !wz;? rd   )r   r,   rp   rq   r   rr   )rp   r  )r  
__future__r   r   rF   rZ   r'   r   voice.eventsr   r   rj   r  r&   r   r   rs   r   r   r   r1   rQ   rS   ry   r  r(   r!  rw   rd   r   <module>r$     s   *X #  	   "VwG"**..45

{C01	


/7
8 9,/45 5*wG wGv	 {4Qrd   