
    `gj                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZmZmZmZ 	 ddlmZ 	 ddlmZ  e       ZdZ ej4                         Zdaded<   dadddddddZded<    ej@                  dejB                        Z" ej@                  dejB                        Z# ej@                  dejB                        Z$ ej@                  dejB                        Z% ej@                  d      Z&g dZ'ddZ(ddZ)g dddddd d! e(g d"      d#d$d%d&dd'd(d) e(g d*      d#d+d,d-ddd.d/ e(g d0      d#d1d2d3dd4d5 e)d6d7       e)d8d       e)d9d:      gd;d<d=d>dd'd?d@ e(g dA      d#dBdCdDdd'dEdF e(g dG      d#dHdIdJdd'dKdL e(g d*      d#dMdNdOdPd'dQdR e(g dS      d#dTdUdVdPd'dWdX e(g dY      d#dZd[d\dPd'd]d^ e(g d*      d#d_d`dadPd'dbdcdd e(g de      df	dgdhdidPd'djdcdk e(g dl      df	dmdndodPd4dp e)dqdr       e)dsdt      gd;dudvdwdPd4dx e)dydz       e)dRd{      gd;d|d}d~dPd'ddcd e(g d      df	ddddPd'ddcd e(g d*      df	ddddPd'ddcd e(g d      df	ddddddd e(g d      d#ddddddd8 e(g d      d#ddddddd e(g d      d#ddddd'dd e(g d      d#ddddd4d e)ddz       e)d!d      gd;ddddd'dd e(g d      d#ddddd4ddc e)dd       e)dRd{      gdddddd'dd e(g d      d#ddddd'dd e(g dl      d#ddddd'dd e(g dȢ      d#ddddd'dd e(g dȢ      d#ddddd'dd e(g d      d#ddddd'dd e(g d      d#ddddd'dd e(g d*      d#ddddd'ddcd e(g d      df	ddddd'dd e(g d      d#ddddd'dd e(g dȢ      d#ddddd'dd e(g d      d#ddddd'dd e(g d      d#ddddd'dd e(g d      d#d dddd'dd e(g d      d#ddddd'd	d
 e(g d      d#ddddd'dd e(g dȢ      d#ddddd'dd e(g d      d#ddddd'dd e(g d      d#ddddd'dd  e(g d      d#d!d"d#dd'd$d% e(g d      d#d&d'd(dd'd)d* e(g d+      d#d,d-d.dd'd)d* e(g d/      d#d0d1d2dd'dd3 e(g d4      d#d5d6d7dd'dWd e(g d      d#d8d9d:dd'dd; e(g d<      d#d=d>d?dd'dd@ e(g d<      d#dAdBdCdd'ddD e(g dE      d#dFdGdHdd'ddI e(g dJ      d#dKdLdMdd'ddN e(g dO      d#dPdQdRdd'd?dS e(g d      d#dTdUdVdd'dEdW e(g dX      d#dYdZd[dd'dd\ e(g d]      d#d^d_d`dad'dbdc e(g dd      d#dedfdgdad'dhdi e(g dl      d#djdkdldad'dmdn e(g dl      d#dodpdqdad'drdcds e(g dȢ      df	Z*dedt<   dduZ+ddvZ,ddwZ-ddxZ.ddyZ/ddzZ0dd{Z1dd|Z2dd}Z3dd~Z4ddZ5ddZ6dddZ7dddZ8ddZ9ddZ:ddZ;ddZ<ddZ=ddZ>ddZ?ddZ@ddZAi d!dd/dd6dd8dd9dddddd@ddFddLddRddXdd^dddddkddqddsdi dyddddddddddddddddddddǐdd͐ddҐddאddܐdddddi ddd dd%ddsdddddddd)dddddd
dddddd*dd3dd;dd@ddddddÐdĐdŐdƐdǐdȐdɐdʜZBdd˄ZCdd̄ZDdd̈́ZE	 	 	 d	 	 	 	 	 	 	 d d΄ZFddτZGddЄZHddфZIdҐdӜddԄZJdddՄZKdaLded<    ej4                         ZMdd؄ZNdddلZOd	dڄZPd
ddۄZQej                  dܫ      d݄        ZSej                  dޫ      d߄        ZTej                  d      d        ZUej                  d      dd       ZVej                  d      d        ZXej                  d      d        ZYy# e$ r ddlZddZY w xY w# e$ r  G d	 d
      ZY w xY w(  zqHermes Achievements dashboard plugin backend.

Mounted at /api/plugins/hermes-achievements/ by Hermes dashboard.
    )annotationsN)Path)AnyDictListOptionalSetget_hermes_homec                     t         j                  j                  d      xs dj                         } | rt	        |       S t	        j
                         dz  S )NHERMES_HOME z.hermes)_osenvirongetstripr   home)vals    c/root/.hermes/venv/lib/python3.12/site-packages/plugins/hermes-achievements/dashboard/plugin_api.pyr   r      s>    {{}-3::<tCy<TYY[9%<<    )	APIRouterc                      e Zd Zd Zd Zy)r   c                    d S )Nc                    | S N fns    r   <lambda>zAPIRouter.get.<locals>.<lambda>       b r   r   self_args_kwargss      r   r   zAPIRouter.get         r   c                    d S )Nc                    | S r   r   r   s    r   r   z APIRouter.post.<locals>.<lambda>   r    r   r   r!   s      r   postzAPIRouter.post   r%   r   N)__name__
__module____qualname__r   r(   r   r   r   r   r      s    	!	!r   r   x   Optional[Dict[str, Any]]_SNAPSHOT_CACHEidle)state
started_atfinished_at
last_errorlast_duration_ms	run_countDict[str, Any]_SCAN_STATUSzv\b(error|failed|failure|traceback|exception|permission denied|not found|eaddrinuse|already in use|timed out|blocked)\bz^\b(port\s+)?(3000|5173|8000|8080|9119)\b.*\b(in use|already|taken|eaddrinuse)\b|\beaddrinuse\bz-\b(npm|pnpm|yarn|pip|uv)\b.*\b(install|add)\bzO\b(success|passed|built|compiled|done|exit_code[\"']?\s*[:=]\s*0|verified|ok)\bz[(?:/home/|~/?|\./|/mnt/)[\w./-]+\.(?:py|js|ts|tsx|jsx|css|html|md|json|yaml|yml|svg|sql|sh))CopperSilverGoldDiamondOlympianList[Dict[str, Any]]c                Z    t        t        |       D cg c]
  \  }}||d c}}S c c}}w )N)name	threshold)zip
TIER_NAMES)valuesr?   r@   s      r   tiersrD   8   s'    JMjZ`JabtYT	2bbbs   'c                    | |dS )Nmetricgter   rF   s     r   reqrI   <   s    S))r   let_him_cookzLet Him Cookz>Let Hermes run a serious autonomous tool chain in one session.zAgent Autonomybest_sessionflamemax_tool_calls_in_session)         @  )idr?   descriptioncategorykindiconthreshold_metricrD   autonomous_avalanchezAutonomous AvalanchezEAccumulate a lifetime avalanche of Hermes tool calls across sessions.lifetime	avalanchetotal_tool_calls)  rQ   rR    N  P  toolchain_maxxerzToolchain Maxxerz:Use a wide spread of distinct Hermes tools in one session.nodesmax_distinct_tools_in_session)      -   F   d   	full_sendz	Full SendzBTerminal, files, and web/browser all get involved in one real run.multi_conditionrocketmax_terminal_calls_in_session   max_file_tool_calls_in_session max_web_browser_calls_in_session<   )rS   r?   rT   rU   rV   rW   requirementssubagent_commanderzSubagent Commanderz Coordinate delegated agent work.branchtotal_delegate_calls)   (   rg   r]     background_process_enjoyerzBackground Process EnjoyerzDStart or control enough long-running processes to deserve the title.daemontotal_process_calls),       p  :  cron_necromancerzCron Necromancerz.Raise scheduled autonomous jobs from the dead.clocktotal_cron_callsred_text_connoisseurzRed Text Connoisseurz9Encounter enough errors to develop a palate for red text.zDebugging Chaoswarningtotal_errors)    '  a  i$ stack_trace_sommelierzStack Trace Sommelierz/Taste tracebacks by the flight, not by the sip.winetraceback_events)rz   r]   rQ   rR   r^   actually_read_the_logszActually Read The Logsz,Inspect logs repeatedly instead of guessing.scrolllog_read_eventsport_3000_takenzPort 3000 Is TakenzGDiscover dev-server port conflict patterns enough times to become numb.plugTport_conflict_events)   ru   rg   rz   r]   )	rS   r?   rT   rU   rV   rW   secretrX   rD   permission_denied_any_percentzPermission Denied Any%zSpeedrun into permission walls.lockpermission_denied_events)   K   rN   X  r   dependency_hell_touristzDependency Hell Touristz3Package installs fail, then somehow life continues.package_skullinstall_error_eventsr   install_success_events
   the_fix_was_restartingzThe Fix Was Restarting Itz;Restart after enough error clusters to call it a technique.restartrestart_after_error_events2   r   forgot_the_env_varzForgot The Env VarzIAuth or configuration failed because an environment variable was missing.keyenv_var_error_events)rv   r~   i@  i i yaml_colon_incidentzYAML Colon Incidentz Configuration syntax bites back.colonyaml_error_eventsdocker_name_collisionzDocker Name Collisionz3A container name already exists. Of course it does.	containerdocker_conflict_events)r   rN   r   r   r   supposed_to_be_quickzThis Was Supposed To Be Quickz(A tiny ask becomes an entire expedition.zVibe Codingmelting_clockmax_messages_in_session)rz   r   rP   i	  r}   one_more_small_changezOne More Small ChangezLMake enough file edits in one session to invalidate the phrase small change.pencil)     r]   rQ   rR   vibe_architectzVibe Architectz2Touch a broad surface area in one project session.	blueprintmax_files_touched_in_session)rz   i  r   r   r   pixel_goblinzPixel Goblinz2Do sustained frontend, CSS, SVG, or visual tuning.pixelfrontend_activity_events)r^   r_   i i i 5 ship_first_ask_laterzShip First, Ask Laterz(Git activity after a serious tool chain.ship
git_eventsrO   css_exorcistzCSS Exorcistz2Cast repeated styling demons out of the interface.spark_cursorcss_activity_events)r   i0u  i8 i@ i  one_character_fixzOne Character Fixz7A tiny edit after a pile of errors. Painful. Beautiful.needletiny_patch_after_errors_eventsrt   )rS   r?   rT   rU   rV   rW   r   rp   
skillsmith
Skillsmithz5Work with Hermes skills enough to leave fingerprints.zHermes Nativehammer_scrollskill_eventsskill_issue_skill_createdzSkill Issue? Skill Created.zACreate or patch durable procedures instead of repeating yourself.anvilskill_manage_eventsmemory_keeperzMemory Keeperz3Persist durable knowledge with memory or Mnemosyne.crystalmemory_events)rg   rz   r]   rQ   rR   memory_palacezMemory Palacez%Build a serious durable-memory trail.palacememory_write_eventscontext_dragonzContext DragonzFBrush against compression, huge context, or token pressure repeatedly.dragoncontext_eventsgateway_dwellerzGateway Dwellerz0Live through gateway-connected Hermes workflows.antennagateway_eventsplugin_goblinzPlugin Goblinz9Use or develop plugins enough that the dashboard notices.puzzleplugin_eventsrollback_wizardzRollback Wizardz*Invoke rollback/checkpoint recovery magic.rewindrollback_events)rO   r   r   r   r   rabbit_hole_certifiedzRabbit Hole CertifiedzESearch or extract enough web content to qualify as a research spiral.zResearch/Webspiraltotal_web_calls)r   rP   rQ   rR   r^   citation_goblinzCitation Goblinz4Extract enough web pages to become a tiny librarian.quotetotal_web_extract_callsdocs_archaeologistzDocs Archaeologistz0Dig through documentation sources over and over.compassdocs_activity_eventsbrowser_possessionzBrowser Possessionz0Possess a browser through automation repeatedly.browserbrowser_callsterminal_goblinzTerminal Goblinz!Spend serious time in shell-land.zTool Masteryterminaltotal_terminal_calls)  r|   r}   r~   r_   patch_wizardzPatch Wizardz.Bend files to your will with targeted patches.wandtotal_patch_calls)   r   r|   r}   r~   file_archaeologistzFile Archaeologistz3Dig through the filesystem with reads and searches.foldertotal_file_reads_searchesimage_whispererzImage Whispererz<Use image generation or vision tools enough for visual work.eyeimage_vision_callsvoice_of_the_machinezVoice Of The Machinez/Use text-to-speech or voice tooling repeatedly.wave	tts_calls)r      rg   rz   r{   model_hopperzModel Hopperz>Switch or inspect providers/models enough to count as a habit.z
Model Loreswapmodel_eventsopenrouter_enjoyerzOpenRouter Enjoyerz/Route model work through OpenRouter repeatedly.routeropenrouter_eventscodex_conjurerzCodex Conjurerz;Summon Codex-flavored assistance often enough for a ritual.codexcodex_eventsmulti_model_magezMulti-Model Magez@Use a real spread of distinct model names across Hermes history.prismdistinct_model_count)r      ru   P      five_model_flightzFive-Model FlightzQTry at least five distinct LLMs instead of marrying the first model that answers.)rt   r   r  ru   r  provider_polyglotzProvider Polyglotz9Use models from multiple providers across Hermes history.distinct_provider_count)      rt         model_sommelierzModel SommelierzATaste enough model/provider conversations to develop preferences.claude_confidantzClaude Confidantz=Bring Claude-flavored reasoning into the workflow repeatedly.claude_events)r   r   rO   r   r   gemini_cartographerzGemini Cartographerz8Map enough Gemini-related workflows to know the terrain.gemini_eventsopen_weights_pilgrimzOpen Weights PilgrimzLActually chat with local/open-weight models through Hermes session metadata.local_model_chat_sessions)   r  r   r   rg   toolset_cartographerzToolset CartographerzJNavigate Hermes toolsets deliberately instead of treating tools as a blur.toolset_events)r  ro   rN   r   r   config_surgeonzConfig Surgeonz]Operate on real config files, manifests, env files, and dashboard settings without flinching.config_events)rg   rz   r]   rQ   r   rebase_acrobatzRebase AcrobatzFHandle real git history surgery: rebase, conflict, merge, fetch, push.git_history_eventstest_suite_tamerzTest Suite TamerzLRun enough verification commands that green text becomes part of the ritual.test_events)rg   rz   r{   i`	  r}   screenshot_hunterzScreenshot HunterzLCapture, inspect, and polish visual proof instead of just claiming it works.screenshot_events)r   r   rO   r   rv   marathon_operatorzMarathon Operatorz/Accumulate a serious number of Hermes sessions.	Lifestylemarathonsession_count)r   rN   rO   r   rv   weekend_warriorzWeekend Warriorz;Run Hermes on weekends enough times to make it a lifestyle.calendarweekend_sessionsnight_shift_operatorzNight Shift Operatorz-Run sessions during gremlin hours repeatedly.moonnight_sessionscache_hit_appreciatorzCache Hit Appreciatorz-Notice or benefit from prompt/cache behavior.cachecache_eventsACHIEVEMENTSc                 (    t               dz  dz  dz  S )Npluginshermes-achievementsz
state.jsonr
   r   r   r   
state_pathr2     s    y(+@@<OOr   c                 (    t               dz  dz  dz  S )Nr0  r1  zscan_snapshot.jsonr
   r   r   r   snapshot_pathr4     s    y(+@@CWWWr   c                 (    t               dz  dz  dz  S )Nr0  r1  zscan_checkpoint.jsonr
   r   r   r   checkpoint_pathr6     s    y(+@@CYYYr   c                     t               } | j                         sdi iS 	 t        j                  | j	                               S # t
        $ r di icY S w xY w)Nunlocks)r2  existsjsonloads	read_text	Exception)paths    r   
load_stater?     sP    <D;;=2zz$..*++ 2s   "A AAc                    t               }|j                  j                  dd       |j                  t	        j
                  | dd             y NT)parentsexist_okr  )indent	sort_keys)r2  parentmkdir
write_textr:  dumps)r0   r>  s     r   
save_staterJ     s9    <DKKdT2OODJJuQ$?@r   c                8   t        | t              r-| j                         D ci c]  \  }}|t        |       c}}S t        | t        t
        f      r| D cg c]  }t        |       c}S t        | t              rt        d | D              S | S c c}}w c c}w )Nc              3  2   K   | ]  }t        |        y wr   )
_json_safe).0vs     r   	<genexpr>z_json_safe.<locals>.<genexpr>   s     3jm3s   )
isinstancedictitemsrM  listtuplesetsorted)valuekrO  s      r   rM  rM     s{    %-2[[];TQ:a= ;;%$'',-!
1--%3U333L <-s   BBc                     t               } | j                         sy 	 t        j                  | j	                               }t        |t              r|S 	 y # t        $ r Y y w xY wr   )r4  r9  r:  r;  r<  rQ  rR  r=  r>  datas     r   load_snapshotr]     s\    ?D;;=zz$..*+dD!K "   s   4A 	A A c                    t               }|j                  j                  dd       |j                  t	        j
                  t        |       dd             y rA  )r4  rF  rG  rH  r:  rI  rM  r\  r>  s     r   save_snapshotr`     s>    ?DKKdT2OODJJz$/TJKr   c                    t               } | j                         sddi dS 	 t        j                  | j	                               }t        |t              rW|j                  dd       |j                  dd       |j                  di        t        |j                  d      t              r|S ddi dS # t        $ r Y w xY w)Nr  r   schema_versiongenerated_atsessionsrc  rd  re  )
r6  r9  r:  r;  r<  rQ  rR  
setdefaultr   r=  r[  s     r   load_checkpointrg     s    D;;="#QBGG	zz$..*+dD!OO,a0OONA.OOJ+$((:.5  CC  s   B	B2 2	B>=B>c                    t               }|j                  j                  dd       |j                  t	        j
                  t        |       dd             y rA  )r6  rF  rG  rH  r:  rI  rM  r_  s     r   save_checkpointri     s?    DKKdT2OODJJz$/TJKr   c                    | j                  d      | j                  d      | j                  d      | j                  d      xs | j                  d      xs ddS )Nlast_activer1   modeltitlepreviewUntitled)rk  r1   rl  rm  r   )metas    r   session_fingerprintrr     sO    xx.hh|,'"'"Gdhhy&9GZ	 r   c                6    t         d uxr | t        z
  t        k  S r   )r.   _SNAPSHOT_CACHE_ATSNAPSHOT_TTL_SECONDS)nows    r   _cache_is_freshrw     s    $&]C2D,DI]+]]r   c                    t        | t              syt        | j                  d      xs d      }t        |xs t	        j                               }|dk  ry||z
  t
        kD  S )NTrd  r   )rQ  rR  intr   timeru  )snapshotrv  tscurrents       r   _is_snapshot_staler~     sV    h%	X\\.).Q	/B#$%G	QwbL000r   c                   t        | xs t        j                               }t        t        t              rt        nd }|r"t        |xs i j                  d      xs d      nd}t        j                  dd      t        j                  d      t        j                  d      t        j                  d      t        j                  d      t        j                  d	d      t        |xs d |r||z
  nd t        ||      d

S )Nrd  r   r0   r/   r1   r2   r3   r4   r5   )
r0   r1   r2   r3   r4   r5   ttl_secondssnapshot_generated_atsnapshot_age_secondssnapshot_stale)	ry  rz  rQ  r.   rR  r   r7   ru  r~  )rv  r}  snaprd  s       r   _scan_status_payloadr     s    #$%G($??TDAE3
''7<1=1L!!'62"&&|4#''6"&&|4(,,-?@!%%k15+!-!5<H<!7d,T7; r   c                    t        | t              sy | j                  d      xs i }| j                  d      xs |j                  d      S )Nfunctionr?   )rQ  rR  r   )callr   s     r   _tool_name_from_callr  
  s>    dD!	*		#B88F-rvvf~-r   c                    | j                  d      }|yt        |t              r|S 	 t        j                  |      S # t
        $ r t        |      cY S w xY w)Ncontentr   )r   rQ  strr:  rI  r=  )msgr  s     r   _contentr    sT    ggi G'3zz'"" 7|s   = AAc                p    | D cg c]  }|j                          }}t        fd|D              S c c}w )Nc              3  N   K   | ]  t        fd D              sd  yw)c              3  &   K   | ]  }|v  
 y wr   r   )rN  r   r?   s     r   rP  z(_count_tool.<locals>.<genexpr>.<genexpr>  s     +QvFdN+Q   r  N)any)rN  r?   needless    @r   rP  z_count_tool.<locals>.<genexpr>  s     RT+Q+Q(QqRs   %%)lowersum)
tool_namesr  r?   lowereds    `  r   _count_toolr    s2    (23tzz|3G3RWRRR 4s   3c                   | xs dj                         j                         }|r|dk(  ry d|v r|j                  dd      d   S dD ]  }||v s|dk(  rdc S |c S  |j                  d	d      d   j                  d
d      d   S )Nr   none/r  r   )openai	anthropicgooglegeminimistralrq  qwendeepseekxainousollamagroq
openrouterr   r  r  :-)r   r  split)
model_namer?   providers      r   model_providerr  "  s    "##%++-D46>
d{zz#q!!$$ _ Bt'838AAB ::c1a &&sA.q11r   c                    | xs dj                         j                         rdk(  ryg d}t        fd|D              S )Nr   r  F)r  z	llama.cpp	localhostz	127.0.0.1zlocal/zlocal:ggufz
vllm-localc              3  &   K   | ]  }|v  
 y wr   r   )rN  markerr?   s     r   rP  z&is_local_model_name.<locals>.<genexpr>3  s     :&v~:r  )r   r  r  )r  local_markersr?   s     @r   is_local_model_namer  .  sA    "##%++-D46>oM:M:::r   c                &   t               }g }t               }g }d}|D ]?  }t        |      }	|j                  |	       |j                  d      rDt	        |d         }
|j                  |
       |j                  d      dk7  r|j                  |
       |j                  d      xs g D ]2  }t        |      }
|
s|j                  |
       |j                  |
       4 t        j                  |	      r|dz  }|	}|j                  d      r0|dt        j                  |j                  d      t              z   z  }|j                  t        j                  |             B d	j                  |      }|j                         }t!        |d
      }t!        |dd      }t!        |d      }t!        |d      }||z   }t!        |d      }t!        |dd      }t!        |dddd      }t!        |d      }t!        |d      t#        t%        j                  d|t$        j&                              z   }t!        |d      }t!        |dd      }t!        |dd      }t!        |d      t#        t%        j                  d|            z   }t!        |d      }t!        |dd      }t!        |dd      }i d | d!|xs d"d#t#        |      d$t#        |      d%|d&t#        |      d'|d(|d)|d*|d+|d,|d-|d.|d/|d0t#        |      d1|i d2|d3|d4|d5|d6|d7|d8|d9|d:t)        t*        j                  |            d;t*        j                  |      rdndd<t#        t%        j                  d=|t$        j&                              d>t#        t%        j                  d?|t$        j&                              d@t#        t%        j                  dA|t$        j&                              dBt,        j                  |      rt        j                  |      rdnddCt,        j                  |      rt.        j                  |      rdnddD|r't%        j                  dE|t$        j&                        rdnddFt#        t%        j                  dG|t$        j&                              i dHt        j                  |      r.t#        t%        j                  dI|t$        j&                              nddJt#        t%        j                  dK|t$        j&                              dLt#        t%        j                  dM|t$        j&                              dNt#        t%        j                  dO|t$        j&                              dPt#        t%        j                  dQ|t$        j&                              dR|dSk\  r't%        j                  dT|t$        j&                        rdnddUt#        t%        j                  dV|t$        j&                              dWt#        t%        j                  dX|t$        j&                              dYt#        t%        j                  dZ|t$        j&                              d[t#        t%        j                  d\|t$        j&                              d]t#        t%        j                  d^|t$        j&                              d_t#        t%        j                  d`|t$        j&                              dat#        t%        j                  db|t$        j&                              dct#        t%        j                  dd|t$        j&                              det#        t%        j                  df|t$        j&                              dgt#        t%        j                  dh|t$        j&                              dit#        t%        j                  dj|t$        j&                              t#        t%        j                  dk|t$        j&                              t#        t%        j                  dl|t$        j&                              t#        t%        j                  dm|t$        j&                              t#        t%        j                  dn|t$        j&                              t#        t%        j                  do|t$        j&                              t#        t%        j                  dp|t$        j&                              t#        t%        j                  dq|t$        j&                              t               drS )sNr   	tool_nameroletool
tool_callsr   )default
r   
web_searchweb_extractr   patch	read_filesearch_files
write_filedelegate_taskprocesszbackground\s*=\s*truecronjobimagevisionttstext_to_speechskillz\bskillskill_managememory	mnemosynemnemosyne_remember
session_idrm  zUntitled sessionmessage_counttool_call_countr  distinct_tool_counterror_countterminal_calls	web_callsweb_extract_callsr   web_browser_callspatch_callsfile_reads_searchesfile_tool_callsfiles_touched_countdelegate_callsprocess_calls
cron_callsr   r   r   r   r   r   port_conflictr   r   ztraceback|exceptionr   z6gateway\.log|errors\.log|agent\.log|/api/logs|\blogs\br   z0permission denied|eacces|operation not permittedr   r   r   z\brestart|reload|kill|start\br   zKmissing .*env|api key|environment variable|not configured|unauthorized|authr   zyaml|yml|colon|parse errorr   zRdocker.*(name|container).*already|container name conflict|Conflict\. The containerr   z+\.(css|svg|tsx|jsx)|frontend|tailwind|reactr   z%\.css|tailwind|style|className|visualr   z.\bgit\s+(commit|push|merge|rebase|status|diff)r   rt   z#one character|single character|typor   z#compress|context window|token|cacher   z)gateway|discord|telegram|slack|api_serverr   z7plugin|dashboard-plugins|__HERMES_PLUGIN|manifest\.jsonr   zrollback|checkpointr   z$docs|documentation|docusaurus|READMEr   zkmodel|provider|openrouter|codex|gemini|claude|anthropic|openai|mistral|qwen|deepseek|llama|ollama|vllm|ggufr   r  r  r   r  zclaude|anthropicr  zgemini|google ai|google modellocal_model_eventszCollama|llama\.cpp|gguf|vllm|local model|open[- ]weight|open weightszFtoolset|enabled_toolsets|browser tool|terminal tool|file tool|web toolzconfig\.ya?ml|\b[a-z0-9_-]+config\.(?:js|ts|json|ya?ml)|\.env(?:\b|\.)|manifest\.json|settings\.json|pyproject\.toml|package\.jsonzb\bgit\s+(rebase|merge|fetch|pull|push|tag|checkout)|merge conflict|conflict\s*\(|rebase --continuezapytest|unittest|vitest|playwright|npm test|pnpm test|node --check|py_compile|tests? passed|\bOK\bzDscreenshot|playwright|vision_analyze|browser_vision|\.png|image dataz>\bgit\s+tag|release|version bump|changelog|publish|pushed? tagz#cache hit|prompt caching|cache_read)r  r  r  r  r   release_eventsr-  model_names)rV  r  appendr   r  addr  ERROR_REsearchr:  rI  updateFILE_REfindalljoinr  r  lenreIboolPORT_RE
INSTALL_RE
SUCCESS_RE) r  rm  messagesr  tool_sequencefiles_touchedfull_text_partsr  r  textr?   r  blob	full_textr  r  r  r  r   r  r  r  r  r  r  r  r   r   r   r   r   r   s                                    r   analyze_messagesr  6  s	   5J!M!eM!#OK 4}t$77;s;'(DNN4  wwv&($$T*GGL)/R 	+D'-Dt$$$T*		+
 ??4 1K77< C$**SWW\%:CHHHDW__T23)4, 		/*IOOE 
;NM<GI#M=Ay9M!M1mW5K%m[.Q!-lGUcdO @Ny9C

Kcenprptpt@u<vvM]I6J$]GXFM52BCI}g6RZZ
TY=Z9[[L%m^DxEM%m5I8T<j<,,< 	X< 	3}-	<
 	j< 	s:< 	{< 	.< 	Y< 	.< 	< 	.< 	{< 	2< 	?<  	s=1!<" 	.#<$ 	%<& 	j'<( 	0)<* 	Y+<, 	-<. 	2/<0 	1<2 	23<4 	gnnY785<6 	W^^I%>A7<8 	C

+A9bdd ST9<: 	3rzz*cenprptptuv;<< 	#C

3fhqsuswsw(x$y=<> 	Z%6%6y%AhooV_F`fg?<@ 	!z'8'8'C
HYHYZcHd!jkA<B 	%;299Eegprtrvrv;wa}~C<D 	BJJ/}  @I  KM  KO  KO  %P  !QE<F 	`h`o`opy`zS,I9VXVZVZ![\  ABG<H 	!#bjj  2G  IR  TV  TX  TX  'Y  #ZI<J 	#C

3aclnpnrnr(s$tK<L 	s2::.VXacecgcg#hiM<N 	c"**%VXacecgcghiO<P 	){a/?BIINtv  BD  BF  BF  EG!  MNQ<R 	#bjj)OQZ\^\`\`abS<T 	#bjj)UW`bdbfbfghU<V 	RZZ(bdmoqosostuW<X 	3rzz*@)RTTRSY<Z 	BJJ/VXacecgcg$h i[<\ 	BJJ  (V  Xa  ce  cg  cg  h  i]<^ 	SM9bdd!KL_<` 	BJJxBDDABa<b 	RZZ(;YMNc<d 	RZZ(H)UWUYUYZ[e<f 	c"**-su~  AC  AE  AE  #F  Gg<h bjj)rt}  @B  @D  @D  E  FRZZ  )n  py  {}  {  {  @  A!"**  .S  U^  `b  `d  `d  #e  f2::  'K  MV  XZ  X\  X\  ]  ^ ,su~  AC  AE  AE  "F  Gbjj)jluwyw{w{|}BJJ'MyZ\Z^Z^_`uw< <r   c           
        | d   }t        |j                  |d      xs d      }t        | j                  dg       d       }|D cg c]  }||d   k\  s| }}|D cg c]  }||d   k  s| }}|r|d   d   nd }|r|d   d   nd }	|r|d   d   n|r|d   d   nd	}
|r|d   d   nd}t        d	|
|z
        }|s|rd
n1t        dt	        dt        j                  ||z
  |z  d
z                    }t        |      }t        |dkD        }|rdn| j                  d      r|sdnd}||xs | j                  d       ||||	|
|dS c c}w c c}w )NrX   r   rD   c                    | d   S Nr@   r   ts    r   r   z!evaluate_tiered.<locals>.<lambda>  s
    1[> r   r   r@   r?   r  rg   c   unlockedr   
discoveredr  r  r0   tierprogress	next_tiernext_thresholdprogress_pct)ry  r   rW  maxminmathfloorr  )
definition	aggregaterG   r  
tiers_listr  achieved
next_tiersr  r  r	  current_thresholddenompctr  r  r0   s                    r   evaluate_tieredr    s   *+F9==+0q1H
w39QRJ%DaQ{^)CDHD'E8an+D!EJE#+8B<D)3
1f%I3=Z];/cmJrNS^D_stN5=[11>$556EH##aRhYjNjnsMswzLzA{9|2}CH~Hhl#J"JZ^^H5MV`frE 
0Z*..QYBZ>Zejtx  GO  ^g  {I  [^  _  _ EEs   EEE-Ec                |   | j                  dg       }|s.d| j                  d       | j                  d      rdndd dd dddS g }d}d}|D ]y  }t        |j                  |d	   d      xs d      }t        |j                  d
d            }|xs |dkD  }|xr ||k\  }|j                  t        d|t	        d|      z               { t        j                  t        |      t        |      z  dz        }	|rdn| j                  d      r|sdnd}
||xs | j                  d       |
d |	d d|rddS t        d|	      dS )Nrp   Fr   r  r   r  r  TrG   rH   g      ?rg   r  r  )	r   ry  r  r  r  r  r  r  r  )r  r  rp   partsany_progresscompleterequirementrX  r@   r  r0   s              r   evaluate_requirementsr    s   >>."5L!Z^^H5M1Mdndrdrs{d|X`  CO  Y]  kl  {  ST  fg  h  	hELH# :IMM+h"7;@qAq12	#0uqy2 2Sec!Y&7789: **c%j3u:-4
5C"JZ^^H5MVbhtE 0\JNNS[D\@\glvz  IL  [_  sv  OW  HK  j  j  ]`  ac  eh  ]i  j  jr   c           	     n    t        |j                  | d               }|d|rdndd |rdndd d|rddS ddS )	NrG   Tr  r  r  r   rg   r  )r  r   )r  r  r  s      r   evaluate_booleanr    sy    IMM*X"678H Xz[gqu  IQ  DE  WX  gk  @  Ya  RU  i  i  gh  i  ir   ztool calls in one sessionz)distinct Hermes tools used in one sessionzterminal calls in one sessionz&file/search/patch calls in one sessionz2web search/extract or browser calls in one sessionzmessages in one sessionzfiles touched in one sessionzlifetime delegate_task callsz&lifetime background process operationsz!lifetime scheduled-job operationsz(error/failed/traceback messages observedztraceback or exception mentionszlog inspectionsz#dev-server port conflict detectionszpermission-denied errorszpackage-install failuresz.successful package installs after package workz+restart/reload actions after error clustersz/missing auth/config/environment-variable eventszYAML/config parse incidentszDocker/container-name conflictsz(frontend/CSS/SVG/React activity mentionsz-CSS, styling, Tailwind, or className activityzgit workflow commandsz*tiny typo-style fixes after error clustersz!Hermes skill mentions or tool usez+skill_manage create/patch/delete operationszmemory or Mnemosyne tool eventszdurable memory writesz7context, compression, token, or cache-pressure mentionsz"gateway/API/chat-platform activityz-dashboard plugin development or usage signalsz%rollback/checkpoint recovery mentionsz"documentation/README/docs activityzmodel/provider-related activityzOpenRouter mentionszCodex mentionszprompt-cache/cache-hit mentionsz%lifetime web_search/web_extract callszlifetime web_extract callsz!lifetime browser automation callszlifetime Hermes tool callszlifetime terminal callszlifetime targeted patch editsz%lifetime read_file/search_files callsz%image generation or vision tool callsz"text-to-speech or voice tool callsz-distinct model names seen in session metadataz7distinct model providers inferred from session metadatazClaude/Anthropic model mentionszGemini/Google model mentionsz local/open-weight model mentionsz9Hermes sessions whose model metadata is local/open-weightztoolset or tool-family mentionsz+configuration/environment/manifest activityzAgit history operations such as rebase, merge, fetch, push, or tagz(test/check/verification command mentionsz:screenshot, Playwright, PNG, or vision-inspection activityz,release, version, publish, or git tag eventszHermes sessionszsessions started on weekendsz*sessions started late night or before dawn)r  r  r  r  r  r  r   r  r$  r'  r*  c                N    t         j                  | | j                  dd            S )N_r  )METRIC_LABELSr   replace)rG   s    r   metric_labelr$    s     VV^^C%=>>r   c                   | j                  d      r| j                  d      dk(  ryd}d| v rWt        | j                  dg       d       }|s|d	z   S t        | d         }d
j                  d |D              }|d| d| dz   S | j                  d      xs g }|rS|D cg c].  }t        |d          dt	        |j                  dd             0 }}|dz   dj                  |      z   dz   S |dz   S c c}w )Nr   r0   zSecret: exact requirement hidden until Hermes sees the first matching signal. Keep using Hermes across debugging, tools, memory, skills, plugins, and model workflows to reveal it.r   rX   rD   c                    | d   S r  r   r  s    r   r   zcriteria_for.<locals>.<lambda>  s
    q~ r   r   z1Requirement: use Hermes in the matching workflow.z, c              3  6   K   | ]  }|d     d|d      yw)r?   r  r@   Nr   )rN  r  s     r   rP  zcriteria_for.<locals>.<genexpr>  s$     Oqai[!K.)9:Os   zRequirement: z. Tier ladder: .rp   rG   u    ≥ rH   r  z; z3Requirement: complete the matching Hermes behavior.)r   rW  r$  r  ry  )r  secret_prefixr  rG   ladderrp   rr  s           r   criteria_forr,    s   ~~hJNN7$;x$G EMZ'JNN7B7=UV
 #VVVj);<=OJOOvhofXQOOO>>.17RLT`aqL8-.eCeQ4H3IJaa.51AACGGPPP bs   3C3c                    t        |       }|j                  d      dk(  ri |ddt        |      ddS t        |      |d<   |S )Nr0   r   z???zdSecret achievement: hidden until Hermes detects the first relevant behavior in your session history.)r?   rT   criteriarW   r.  )rR  r   r,  )itemcleans     r   display_achievementr1  (  sn    JEyyX% R%  R  8^  lx  y~  l  IQ  R  	R$U+E*Lr   c           	        	 ddl m} t               }t	        |j                  d      t              r|j                  d      ni }d}d}| | dk  rd	n
t        |       }	 |       }
	 |
j                  |	d
d      }t        |      }g }i }t        |d      D ]  \  }}|j                  d      }|st        |      }t	        |t              r|j                  |      nd}t	        |t              r|j                  d      nd}t	        |t              r|j                  d      nd}t	        |t              r||k(  rt        |      }|dz  }nI|
j                  |      }t        ||j                  d      xs |j                  d      xs d|      }|dz  }||d<   |j                  d      xs( |j                  d      xs |j                  d      xs d|d<   |j                  d      |d<   |j                  d      |d<   |j                  d      |d<   |j                  d      r|j                  dt                      t	        |d   t              r-|d   j!                  t#        |j                  d                   n}t	        |d   t$              rLt#        |j                  d            |d   vrK|d   j'                  t#        |j                  d                   nt#        |j                  d            h|d<   |j'                  |       |t)        |      d||<   ||dkD  s||z  dk(  s||k  s	  |t%        |      ||        t+        dt        t-        j,                               |d       t/        |
dd      }|r |        	 |t1        |      |dkD  rdndt        |      ||t        |      |d d!S # t        $ r}g i d| ddddddcY d}~S d}~ww xY w# t        $ r Y [w xY w# t/        |
dd      }|r |        w w xY w)"u  Scan Hermes sessions and build per-session achievement stats.

    ``limit=None`` (the default) scans the ENTIRE session history. Prior
    versions capped this at 200, which silently reduced achievement totals
    to ~2% of history on long-running installs and made lifetime badges
    unreachable. SQLite's ``LIMIT -1`` means "unlimited"; we map ``None``
    and non-positive values to ``-1`` so callers get the full catalog.

    Warm scans stay cheap: the checkpoint cache stores per-session stats
    keyed by ``(started_at, last_active)`` and only re-analyzes sessions
    whose fingerprint changed. Cold scans on large histories (thousands
    of sessions) take tens of seconds to several minutes; ``evaluate_all``
    runs them on a background thread so the dashboard UI never blocks on
    the first request.

    ``progress_callback(partial_sessions, scanned_so_far, total)`` — when
    provided, fires every ``progress_every`` sessions with the sessions
    analyzed so far and progress counters. Background scans use this to
    publish intermediate snapshots so a long cold scan surfaces badges
    incrementally on each dashboard refresh instead of going all-at-once
    at the end.
    r   )	SessionDBzCould not import SessionDB: failedmodesessions_totalsessions_rescannedsessions_reused)re  r  error	scan_metaNre  r  TF)limitinclude_childrenproject_compression_tipsr  )startrS   statsfingerprintrm  rn  ro  r  r1   rk  sourcerl  r  )rA  r@  rb  closeincrementalfullr6  r7  r8  r9  sessions_scanned_so_farsessions_expected_totalre  r  r;  )hermes_stater3  r=  rg  rQ  r   rR  ry  list_sessions_richr  	enumeraterr  get_messagesr  rf  rV  r  r  rT  r  rM  ri  rz  getattraggregate_stats)r<  progress_callbackprogress_everyr3  exc
checkpointprevious_sessionsreused	rescanneddb_limitdbsessions_metatotal_sessionsre  checkpoint_sessionsidxrq  sidfpcachedcached_stats	cached_fpr@  r  rC  s                            r   scan_sessionsrb  0  s   6E* !"J6@PZA[]a6b
z2hjFI
 muzrE
H	B8--Htns-t]+)+.0"=: )	IC((4.C$T*B3=>OQU3V&**3/\`F2<VT2J6::g.PTL5?5M

=1SWI,-)r/\*!??3/(dhhw.?.d488ICV.dZdfnoQ	"%E,!XXg.i$((92EiSZI[i_iE'N"&((<"8E,#'88M#:E- "hhx0E(Oxx   6eM2C8-(,,S'1B-CDm 4d;488G,-U=5IIm,33C8I4JK,/0A,B+CE-(OOE"79JuDU'V$ ,!1C~I]abIbhkn|h|%d8nc>JK)	V 			,+
 	 GT*G$X.%+aZMV!(m"+%'*8}'5
 O  ER<XY\X]:^v~  ST  lm  BC  nD  E  	EEr !   GT*G s`   N> $J9O3 O3 &O3 0O3 8O#-O3 >	O OO O #	O0,O3 /O00O3 3Pc                	   i dt        |       ddddddddddddd	dd
dddddddddddddddddddddddddddd
}g d}|D ]  }d||<   	 t               }t               }| D ]  }t        |d   |j                  dd            |d<   t        |d   |j                  dd            |d<   t        |d   |j                  dd            |d<   t        |d   |j                  dd            |d<   t        |d   |j                  dd            |d<   t        |d   |j                  dd            |d<   t        |d	   |j                  dd            |d	<   t        |d
   |j                  dd            |d
<   |dxx   |j                  dd      z  cc<   |dxx   |j                  dd      z  cc<   |dxx   |j                  dd      z  cc<   |dxx   |j                  dd      z  cc<   |dxx   |j                  dd      z  cc<   |dxx   |j                  dd      z  cc<   |dxx   |j                  d d      z  cc<   |dxx   |j                  d!d      z  cc<   |d"xx   |j                  d#d      z  cc<   |d$xx   |j                  d%d      z  cc<   |d&xx   |j                  d&d      z  cc<   |d'xx   |j                  d'd      z  cc<   |d(xx   |j                  d(d      z  cc<   |D ]  }||xx   |j                  |d      z  cc<   ! |j	                  |j                  d)      xs
 t                      |j                  d)      xs
 t               }|D ]*  }t        t        |            }	|	s|j                  |	       , t        d* |D              r|d+xx   d,z  cc<   |j                  d-      s[	 t        j                  t        |j                  d-                  }
|
j                  d.k\  r|d/xx   d,z  cc<   |
j                  d0k  s|
j                  d1k\  r|d2xx   d,z  cc<    t        |D ch c]  }|s|d3k7  s| c}      |d4<   t        |      |d5<   |S # t        $ r Y w xY wc c}w )6Nr$  rM   r   rb   r   rk   rm   max_web_calls_in_sessionrn   r   r   r\   r   r   r   r   r   rs   )
ry   r   r   r   r   r  r
  r  r'  r*  )$r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r  r  r  r  r   r  r-  r  r  r  r  r  r  r  r  r  r  r  r  r  ry   r  r   r  r   r   r   r  c              3  D   K   | ]  }t        t        |              y wr   )r  r  )rN  r  s     r   rP  z"aggregate_stats.<locals>.<genexpr>  s     U
"3z?3Us    r  r  r1   rt   r'        r*  Noner  r
  )r  rV  r  r   r  r  r  r  r  rz  	localtimefloattm_wdaytm_hourr=  )re  aggsum_keysr   r  provider_namesssession_modelsr  r  ltms               r   rO  rO    sO   X#Q 	( 	"1	
 	( 	)! 	#A 	+A 	' 	 	A 	 	1 	"1 	Q  	$Q!" 	#$  ! !#$%&7C:H  C  EK"uN (+.s3N/OQRQVQVWhjkQl+m'(/237V3WYZY^Y^_tvwYx/y+,),S1J-KQUUSbdeMf)g%&/237V3WYZY^Y^_oqrYs/t+,03C8X4Y[\[`[`artu[v0w,-*-c2L.MquuU`bcOd*e&'25c:\6]_`_d_dexz{_|2}./.1#6T2UWXW\W\]rtuWv.w*+Nquu]A66155):A#>>"#quu-=q'AA#!%%Q"77%&!%%0CQ*GG& AEE-$;; '(AEE2G,KK("#quu-=q'AA#!"aeeOQ&??"155q#99Ooq 99 !QUU+?%CC!KAEE+q11 	&CHc1%H	&155/8359}-6( 	-J%c*o6H""8,	- UnUU+,1,55^^E!%%*=$>?::?*+q0+::>RZZ2%5()Q.)M(R #&+&SQqF{q&S"TC%(%8C!"J	  &Ss%   A4Q:R
R
R
:	RRc                Z    d| v rt        | |      S d| v rt        | |      S t        | |      S )NrX   rp   )r  r  r  )r  r  s     r   evaluate_definitionru    s9    Z'z955#$Z;;J	22r   c           	         |sy | j                  d      }dddddddd	d
}||v rI||   t        |fd      }|j                  d      |j                  d      |j                  d      dS y )NrX   r  r  r  r  r  r  r  r  )rM   rb   r   rk   rm   rd  rn   r   c                (    | j                  d      S )Nr   rp  )xr   s    r   r   zevidence_for.<locals>.<lambda>  s    c1 r   r   r  rm  r   )r  rm  rX  )r   r  )r  re  rG   metric_to_session_keyrp  r   s        @r   evidence_forrz    s    ^^./F%6)>#2)9*;$/,?(=	 &&#F+56eeL1AEE'NUVUZUZ[^`aUbccr   F
is_partialc               "   | j                  di       }|s
t               ndi i}|j                  di       }t        t	        j                               }g }t
        D ]  }t        ||      }|d   }	|s;|d   r6|	|vr2||j                  d      t        || j                  dg             d||	<   i ||}
|d   rf|j                  |	i       j                  d      |
d<   |j                  |	i       j                  d	      xs t        || j                  dg             |
d	<   |j                  t        |
              |st        |       |D cg c]
  }|d   s	| }}|D cg c]  }|j                  d
      dk(  s| }}|D cg c]  }|j                  d
      dk(  s| }}|| j                  dg       || j                  di       | j                  d      t        |      t        |      t        |      t        |      |d
S c c}w c c}w c c}w )ug  Evaluate every achievement definition against a scan result.

    Used by ``compute_all`` for finished scans AND by the background
    progress callback for partial, in-flight snapshots. ``is_partial=True``
    skips persisting ``state.json`` unlocks — we don't want to record an
    "unlock time" based on half a scan that a later session might shift.
    r  r8  rS   r  r  re  )unlocked_at
first_tierevidencer~  r  r0   r  r   r;  r:  
achievementsre  r  r;  r:  unlocked_countdiscovered_countsecret_counttotal_countrd  )r   r?  rf  ry  rz  r.  ru  rz  r  r1  rJ  r  )scanr|  r  r0   r8  rv  	evaluatedr  result	unlock_idr/  ar  r  r   s                  r   _compute_from_scanr    s&    b)I *JLBEy"-G
diik
CI" 	4
$Z;t$	fZ0Yg5M14FJJvDVdpq{  ~B  ~F  ~F  GQ  SU  ~V  eW  "XGI'*''*")++i"<"@"@"OD&{{9b9==jI\Zdfjfnfnoy{}f~MD,T23	4 5$6a*6H6&I!%%.L*H!IJI"AAaeeGn&@aAFA!HHZ,XXk2.'"h-
OF9~  7IAs$   	
HHH8HHHc                6    t        | |      }t        |d      S )N)rP  rQ  Fr{  )rb  r  )rP  rQ  r  s      r   compute_allr  =  s    +<^\Ddu55r   zOptional[threading.Thread]_BACKGROUND_SCAN_THREADc                   t         D cg c]z  }t        i |dd|j                  d      rdnddd|j                  d      xs i gd   j                  d      |j                  d      xs i gd   j                  dd      d	d
      | }}|g i dddddd	dt        d |D              t        d |D              t	        |      | d
S c c}w )zPlaceholder payload used while the first-ever scan is still running.

    Returns a structurally-complete response so the dashboard UI can render
    an empty achievement list + spinner without special-casing "no data yet".
    Fr   r  r   rD   r?   r@   r  N)r  r  r0   r  r
  r  r	  r  pendingr5  c              3  J   K   | ]  }|j                  d       dk(  sd  yw)r0   r  r  Nrp  rN  r  s     r   rP  z*_build_pending_snapshot.<locals>.<genexpr>T  s     Wag,8VW   ##c              3  J   K   | ]  }|j                  d       dk(  sd  yw)r0   r   r  Nrp  r  s     r   rP  z*_build_pending_snapshot.<locals>.<genexpr>U  s     O!AEE'Nh4NAOr  r  )r.  r1  r   r  r  )rv  dr  s      r   _build_pending_snapshotr  F  s    CO  P  ~$  &x  &x%uklkpkpqykz_g  AM  [\  no  @  D  D  EL  M  U  RT  QU  WX  ~Y  ~]  ~]  ^d  ~e  z{  z  z  @G  zH  zP  MO  LP  RS  yT  yX  yX  Yd  fg  yh  rv  .w  &x  y  PI  P!'1TUjklW9WWOyOO9~  Ps   A?Cc           	        t         5  t        t        j                               }dt        d<   |t        d<   dt        d<   d }| r|nd}	 t	        |      }t        |      at        t        j                  d      xs t        t        j                                     at        t               d	t        d<   t        t        j                               t        d<   t        t        d   |z
  dz        t        d<   t        t        j                  dd            dz   t        d<   	 ddd       y# t        $ r%}d
t        d<   t        |      t        d<   Y d}~d}~ww xY w# t        t        j                               t        d<   t        t        d   |z
  dz        t        d<   t        t        j                  dd            dz   t        d<   w xY w# 1 sw Y   yxY w)u*  Execute a scan + snapshot update. Called synchronously or from a thread.

    When ``publish_partial_snapshots=True`` (the default for background
    scans), the scanner periodically publishes an in-progress snapshot to
    ``_SNAPSHOT_CACHE`` so each dashboard refresh during a long cold scan
    shows more progress — badges unlock incrementally as sessions stream
    in, instead of staying at zero for minutes and then jumping to the
    final state. Synchronous /rescan callers pass ``False`` because they
    block on the full result anyway.
    runningr0   r1   Nr3   c           	         	 | t        |       d|dd||dd}t        |d      }t        |      aday # t
        $ r Y y w xY w)Nin_progressr   rF  rI  Tr{  )rO  r  rM  r.   rt  r=  )partial_sessionsscanned_so_fartotalpartial_scanpartials        r   _publish_partialz4_run_scan_and_update_cache.<locals>._publish_partialm  sc     0!01A!B -*8./+,3A38"  -\dK #-W"5%&" s   03 	??)rP  rd  r/   r4  r2   r]   r4   r5   r   r  )
_SCAN_LOCKry  rz  r7   r  rM  r.   r   rt  r`  r=  r  )publish_partial_snapshotsstartedr  callbackcomputedrR  s         r   _run_scan_and_update_cacher  [  s    
 .Rdiik" )W%,\"%)\"	6 (A#d	R"X>H(2O!$_%8%8%H%\CPTPYPYP[L\!]/*$*L!
 +.diik*:L'/2L4ORY4Y]a3a/bL+,(+L,<,<[!,L(MPQ(QL%].R .RP  	2$,L!),SL&	2 +.diik*:L'/2L4ORY4Y]a3a/bL+,(+L,<,<[!,L(MPQ(QL%].R .RsE   AG
A,D,6A,G,	E5EEEEA.GGGc                     t         5  t        } | | j                         r
	 ddd       yt        j                  t
        ddidd      }|a|j                          ddd       y# 1 sw Y   yxY w)a  Kick off a scan in a daemon thread if one isn't already running.

    Idempotent: concurrent callers see the in-flight thread and return
    immediately. The thread updates ``_SNAPSHOT_CACHE`` on completion so
    subsequent ``/achievements`` requests see fresh data. While running,
    it also publishes partial snapshots every ~250 sessions so the UI
    reflects incremental progress on long cold scans.
    Nr  Tzhermes-achievements-scan)targetkwargsr?   rx   )_BACKGROUND_SCAN_LOCKr  is_alive	threadingThreadr  r?  )existingthreads     r   _start_background_scanr    ss     
 *H$5$5$7  !!-/6+	
 #)  s   A$1A$$A-c                   t        t        j                               }| st        |      r
t        xs i S t        @t	               }t        |t              r&t        |j                  d      xs d      }|a|xs |a| r#t        d       t        t        S t        |      S t        t        |      s
t                t        S t                t        |      S )u  Return the current achievements payload.

    Behavior matrix:

    * Fresh in-memory cache → return it instantly.
    * Stale on-disk snapshot → load it, kick a background rescan, return
      the stale data (UI decorates it with ``is_stale=True``).
    * No snapshot yet (first-ever run) → kick a background scan, return
      an empty-but-valid "pending" payload so the UI can render a spinner
      without blocking.
    * ``force=True`` (manual /rescan) → run synchronously, block the
      caller, replace the cache.

    Warm scans stay cheap (the checkpoint cache reuses per-session stats).
    Cold scans on 8000+ session databases take minutes; the background
    thread prevents that from ever blocking the dashboard request path.
    rd  r   F)r  )ry  rz  rw  r.   r]  rQ  rR  r   rt  r  r  r  )forcerv  	persistedrd  s       r   evaluate_allr    s    & diik
C_S)$"$ !O	i&y}}^<ABL'O!-!4 	#UC&""&s++ "s#"$
 "3''r   z/achievementsc                    K   t               } dD ci c]  }|| v s|| |    }}t        |       |d<   i | j                  d      xs i dt               i|d<   |S c c}w w)N)r  r  r  r  r  r:  rd  is_staler;  status)r  r~  r   r  )r\  rY  payloads      r   r  r    s     >D $R  `a  VW  [_  V_q$q'z  `G  `,T2GJ88K &B&(GK N `s   A 	AA=A z/scan-statusc                    K   t               S wr   )r  r   r   r   scan_statusr    s     !!s   z/recent-unlocksc                 z   K   t               } t        | d   D cg c]
  }|d   s	| c}d d      d d S c c}w w)Nr  r  c                ,    | j                  d      xs dS )Nr~  r   rp  )r  s    r   r   z recent_unlocks.<locals>.<lambda>  s    TUTYTYZgThTmlm r   T)r   reverser  )r  rW  )r\  r  s     r   recent_unlocksr    sN     >Dd>2Da
m1DJmw{|  ~A  A  B  BDs   ;
66;z/sessions/{session_id}/badgesc                    K   t               }t         fd|d   D        d       }|s g dS t        |g      }g }t        D ]2  }t	        ||      }|d   s|j                  t        i ||             4  |dS w)Nc              3  4   K   | ]  }|d    k(  s|  yw)r  Nr   )rN  rp  r  s     r   rP  z!session_badges.<locals>.<genexpr>  s     Q!1\?j3PAQs   re  )r  badgesr  )r  nextrO  r.  ru  r  r1  )r  r\  sessionr  r  r  r  s   `      r   session_badgesr    s     >DQtJ/QSWXG(B77	*IF" I
$Z;*MM-.F.Fv.FGHI %77s   AA;%A;z/rescanc                 *   K   ddit        d      S w)NokT)r  )r  r   r   r   rescanr    s     $3,T233s   z/reset-statec                 <  K   t        di i       d adadt        d<   d t        d<   d t        d<   d t        d<   d t        d<   	 t	               j                  d	
       	 t               j                  d	
       dd	iS # t        $ r Y *w xY w# t        $ r Y dd	iS w xY ww)Nr8  r   r/   r0   r1   r2   r3   r4   T)
missing_okr  )rJ  r.   rt  r7   r4  unlinkr=  r6  r   r   r   reset_stater    s      	2O"L!%L"&L!%L'+L#$$/  D 1 $<    $<sG   ?BA; B
 7B;	BBBB
	BBBB)returnr   )rC   z	List[int]r  r=   )rG   r  rH   ry  r  r6   )r  r6   )r0   r6   r  rh  )rX  r   r  r   )r  r-   )r\  r6   r  rh  )rq  r6   r  r6   )rv  ry  r  r  r   )r{  r-   rv  Optional[int]r  r  )rv  r  r  r6   )r  r   r  Optional[str])r  r6   r  r  )r  z	List[str]r  r  r  ry  )r  r  r  r  )r  r  r  r  )r  r  rm  r  r  r=   r  r6   )r  r6   r  r6   r  r6   )rG   r  r  r  )r  r6   r  r  )r/  r6   r  r6   )NNr   )r<  r  rP  Optional[Any]rQ  ry  r  r6   )re  r=   r  r6   )r  r6   re  r=   r  r-   )r  r6   r|  r  r  r6   )Nr   )rP  r  rQ  ry  r  r6   )rv  ry  r  r6   )T)r  r  r  rh  )r  rh  )F)r  r  r  r6   )r  r  )Z__doc__
__future__r   r:  r  r  r  rz  pathlibr   typingr   r   r   r   r	   hermes_constantsr   ImportErrorosr   fastapir   r=  r   ru  Lockr  r.   __annotations__rt  r7   compiler  r  r  r  r  r  rB   rD   rI   r.  r2  r4  r6  r?  rJ  rM  r]  r`  rg  ri  rr  rw  r~  r  r  r  r  r  r  r  r  r  r  r"  r$  r,  r1  rb  rO  ru  rz  r  r  r  r  r  r  r  r  r   r  r  r  r  r(   r  r  r   r   r   <module>r     s   #   	    1 1=0!! 
 Y^^
,0) 0  n  2::  P  RT  RV  RV  W
"**vxzx|x|
}RZZH"$$O
RZZjlnlplpq

"**s
t@
c*N&>  CC  Qa  ky  CJ  `{  FK  Lh  Fi  jN& "+A  SZ  hx  BL  Va  wI  TY  Zz  T{  |N& '9  KG  Ue  o}  GN  dC  NS  Ti  Nj  k	N&
   =A  O_  iz  DL  _b  cB  DG  _H  JM  Nn  ps  Jt  vy  z\  ^`  va  ^b  cN&  )=Np  O  Yc  mu  Ka  lq  rJ  lK  LN& (1M  _e  sC  MW  ai  T  _d  eB  _C  DN& '9Jz  IY  cm  w~  Tf  qv  wW  qX  YN& "+A  SN  \m  wA  KT  jx  CH  Ij  Ck  lN& #,C  UF  Te  oy  CI  _q  |A  B`  |a  bN& $-E  WE  Sd  nx  BJ  `q  |A  Bb  |c  dN& &:  LU  ct  ~H  RX  dh  ~T  _d  e}  _~  N& +4L]~  M^  hr  |B  NR  hB  MR  Sk  Ml  mN&  %.G  YN  \m  wH  Ra  tw  xN  PR  tS  UX  Yq  su  Uv  sw  x!N&" $-H  ZW  ev  @Q  [d  wz  {W  Y[  w\  ^a  bp  rv  ^w  vx  y#N&$  )=  OZ  hy  CM  W\  hl  BX  ch  iM  cN  O%N&& !*?Pr  AR  \f  pw  CG  ]p  {@  Aa  {b  c'N&( #,C  UJ  Xi  s}  GR  ^b  xP  [`  a{  [|  })N&. "+J  \F  Ta  ky  CR  hA  LQ  Rn  Lo  p/N&0 #,C  Uc  q~  HV  `h  ~^  in  oK  iL  M1N&2 %5Fz  IV  `n  xC  Yw  BG  He  Bf  g3N&4 >Bv  ER  \f  pw  Mg  rw  x^  r_  `5N&6 "+BS}  LY  ct  ~D  WZ  [g  ik  Wl  nq  rM  OR  nS  VT  U7N&8 >Bv  ER  \f  p~  Ti  ty  z_  t`  a9N&: (;  MF  Ta  k|  FN  Z^  qt  uU  WX  qY  [^  _m  os  [t  pu  v;N&@ >u  DS  ]g  q@  Vd  ot  uY  oZ  [AN&B '0M  _b  p  IS  ]d  zO  Z_  `x  Zy  zCN&D ODy  HW  ak  u~  Tc  ns  tP  nQ  REN&F ODk  zI  S]  go  EZ  ej  kG  eH  IGN&H %5  GO  ]l  v@  JR  hx  CH  Im  Cn  oIN&J &7Hz  IX  bl  v  Ue  pu  vZ  p[  \KN&L OD  N]  gq  {C  Yh  sx  yY  sZ  [MN&N &7Ht  CR  \f  px  DH  ^o  z  @_  z`  aON&T #,C  U\  jx  BL  V^  tE  PU  Vt  Pu  vUN&V &7H~  M[  eo  y@  Vo  z  @\  z]  ^WN&X  )=  OA  O]  gq  {D  Zp  {@  Ae  {f  gYN&Z  )=  OA  O]  gq  {D  Zi  ty  zT  tU  V[N&` &7Hk  zH  R\  fp  F\  gl  mL  gM  NaN&b >Br  AO  Yc  ms  I\  gl  mJ  gK  LcN&d  )=  OD  R`  jt  ~F  \w  BG  Hg  Bh  ieN&f &7  IG  Uc  mw  AF  \p  {@  A]  {^  _gN&h "+A  SD  R`  jt  ~D  Ze  pu  vM  pN  OiN&n >  CC  Q]  gq  {A  We  pu  v[  p\  ]oN&p  )=N  NZ  dn  x@  Vi  ty  zW  tX  YqN&r %5  GD  R^  hr  |C  Yg  rw  xW  rX  YsN&t '9  KM  [g  q{  EL  bx  CH  I^  C_  `uN&v (;  M`  nz  DN  X_  uK  V[  \o  Vp  qwN&x (;  MH  Vb  lv  @F  \u  @E  FV  @W  XyN&z &7  IL  Zf  pz  DJ  `n  y~  \  y]  ^{N&| '9  KJ  Xd  nx  BI  _n  y~  Y  yZ  [}N&~ !*?  QK  Ye  oy  CL  bq  |A  B\  |]  ^N&@ "+A  Sa  o{  EO  Yc  yT  _d  ex  _y  zAN&F "+A  S_  m|  FP  Zc  yI  TY  Zr  Ts  tGN&H %5  Gf  tC  MW  af  |K  V[  \y  Vz  {IN&J %5  GO  ]j  t~  HP  fz  EJ  Kb  Ec  dKN&L '9  KY  gu  I  S[  q~  IN  Oj  Ik  lMN&N (;  M[  iw  AK  UZ  pC  NS  Tn  No  pON&T (;L}  LW  ak  u  Ud  ot  uO  oP  QUN&V &7  IF  T_  is  }G  ]o  z  @X  zY  ZWN&X "+A  SB  P[  eo  y  Ue  pu  vN  pO  PYN&Z #,C  UD  R]  gq  {B  NR  hv  AF  Gc  Ad  e[N&" NbPXZA
LD"L^1$.	S
	2;m`_$j$i?!<?#%P? $%D? %&N	?
 '(\? 8? #$B? :? C? ;? >? 9? (? A?  :?  6!?" N#?$ !"O%?& M'?( 6)?* ?+?,  J-?. J/?0 )1?2 %&R3?4 75?6 H7?8 69?: 2;?< O=?> :??@ DA?B >C?D @E?F 5G?H .I?J $K?L 5M?N >O?P ;Q?R 8S?T 4U?V 5W?X 8Y?Z  !H[?\ A]?^ 5_?` Ka?b Xc?d 6e?f 3g?h =!\7B]=UD&6B}?D?Q$  '+oo$o o 	odQh3* DI ' 'T6
 7; 3 :&	( *:Rz04(n O  N" " B B
 +,8 -8 Y4 4 ^ E   ===  !! !!s"   c c0 c-,c-0dd