
    `gj;                      U d Z ddlmZ ddlZddlZddlZddlmZmZ ddl	m
Z
 ddlmZ ddlmZmZ ddlmZmZ  ej&                  e      Zd	Zd
ZddZddZddZddZddZ	 	 	 	 	 	 ddZddZdddZ e ddh      Z!ddZ"dZ#da$de%d<   ddZ&ddZ'ddZ(dd dd!Z)dd"Z*dd#Z+dd$Z,dd%Z-dd&Z.dd'Z/dd(Z0dd)Z1dd*Z2d+Z3dd-Z4dd.Z5dd/Z6dd0Z7dd1Z8dd2Z9dd3Z:d4Z;d5Z<dd6Z=d7d8d9d:e;d; e=       d<g d=d>Z>d?d@d9d:dAd;d:g dBdCdDd:dEd;dFdGd;dHdId; e=       dJg d=d>Z?dKdLd9d:e;d;d:dMd;d9dNd;d:dOd;dPdQd:idRdSdPdQd:idTdS e=       dUg d=d>Z@dVdWd9d:e;d;d:dXd;d:g dYdZdD e=       d[d\gd=d>ZAd]d^d9d:e;d;d:d_d; e=       d`g d=d>ZBdadbd9d:dcd;d:ddd; e=       deddfgd=d>ZCdgdhd9d:e;d;d:did;d:djd;d:dkd; e=       dldmdngd=d>ZDdodpd9d:e;d;d:dqd;d:drd;d:dsd; e=       dtd,gd=d>ZEdudvd9d:e;d; e=       d<g d=d>ZFdwdxd9i dyd:dzd;d{d:d|d;dfd:d}d;d~dPdQd:iddSdd:dd;ddHdd;dd:g dddDdd:dd;dd:dd;ddFdd;dd:dd;ddHdd;dd:ddgddDddPdQd:iddSddFdd;ddHdd;d e=       dyd{gd=d>ZGddd9d:dd; e=       d<dgd=d>ZHddd9d:dd;d:dd; e=       dddgd=d>ZI ej                  d7de>e,ed        ej                  d?de?e-ed        ej                  dKde@e.ed        ej                  dVdeAe/ed        ej                  d]deBe0ed        ej                  dadeCe1ed        ej                  dgdeDe2ed        ej                  dodeEe5ed        ej                  dudeFe6ed        ej                  dwdeGe7ed        ej                  ddeHe9ed        ej                  ddeIe:ed       y)uz  Kanban tools — structured tool-call surface for worker + orchestrator agents.

These tools are registered into the model's schema when the agent is
running under the dispatcher (env var ``HERMES_KANBAN_TASK`` set) or when
the active profile explicitly enables the ``kanban`` toolset for
orchestrator work. A normal ``hermes chat`` session still sees **zero**
kanban tools in its schema unless configured.

Why tools instead of just shelling out to ``hermes kanban``?

1. **Backend portability.** A worker whose terminal tool points at Docker
   / Modal / Singularity / SSH would run ``hermes kanban complete …``
   inside the container, where ``hermes`` isn't installed and the DB
   isn't mounted. Tools run in the agent's Python process, so they
   always reach ``~/.hermes/kanban.db`` regardless of terminal backend.

2. **No shell-quoting footguns.** Passing ``--metadata '{"x": [...]}'``
   through shlex+argparse is fragile. Structured tool args skip it.

3. **Better errors.** Tool-call failures return structured JSON the
   model can reason about, not stderr strings it has to parse.

Humans continue to use the CLI (``hermes kanban …``), the dashboard
(``hermes dashboard``), and the slash command (``/kanban …``) — all
three bypass the agent entirely. The tools are for dispatcher-spawned
worker handoffs and for configured orchestrator profiles that route work
through the board.
    )annotationsN)AnyOptional)redact_sensitive_text)
judge_goal)registry
tool_error)cfg_getload_config2      c                 h    	 ddl m}   |        }|j                  dg       }d|v S # t        $ r Y yw xY w)Nr   )r   toolsetskanbanF)hermes_cli.configr   get	Exception)r   cfgr   s      E/root/.hermes/venv/lib/python3.12/site-packages/tools/kanban_tools.py_profile_has_kanban_toolsetr   4   s>    1m77:r*8## s   "% 	11c                 V    t         j                  j                  d      ryt               S )a  Task-lifecycle tools are available when:

    1. ``HERMES_KANBAN_TASK`` is set (dispatcher-spawned worker), OR
    2. The current profile has ``kanban`` in its toolsets config
       (orchestrator profiles like techlead that route work via Kanban).

    Humans running ``hermes chat`` without the kanban toolset see zero
    kanban tools. Workers spawned by the kanban dispatcher (gateway-
    embedded by default) and orchestrator profiles with the kanban
    toolset enabled see the Kanban lifecycle tool surface.
    HERMES_KANBAN_TASKTosenvironr   r        r   _check_kanban_moder   A   s!     
zz~~*+&((r   c                 V    t         j                  j                  d      ryt               S )a  Board-routing tools (kanban_list, kanban_unblock) are intentionally
    hidden from task workers.

    Dispatcher-spawned workers should close their own task via the
    lifecycle tools (complete/block/heartbeat), not enumerate or unblock
    board state. Profiles that explicitly opt into the kanban toolset
    and are NOT scoped to a single task are the orchestrator surface.
    r   Fr   r   r   r   _check_kanban_orchestrator_moder    R   s!     
zz~~*+&((r   c                T    | r| S t         j                  j                  d      }|xs dS )zGResolve ``task_id`` arg or fall back to the env var the dispatcher set.r   N)r   r   r   )argenv_tids     r   _default_task_idr$   d   s'    

jjnn12G?dr   task_idc                    t         j                  j                  d      | k7  ryt         j                  j                  d      }|sy	 t        |      S # t        $ r Y yw xY w)zDReturn this worker's dispatcher run id when it is scoped to task_id.r   NHERMES_KANBAN_RUN_ID)r   r   r   int
ValueError)r%   raws     r   _worker_run_idr+   l   sT    	zz~~*+w6
**../
0C3x s   
A 	AAc                    t         j                  j                  d      | k7  r|S t         j                  j                  d      }|s|S t        |xs i       }||d<   |S )zBAdd trusted worker session id metadata for this worker's own task.r   HERMES_SESSION_IDworker_session_id)r   r   r   dict)r%   metadata
session_idstampeds       r   _stamp_worker_session_metadatar3   y   sX     
zz~~*+w6 34J8>r"G#-G Nr   c                v    t         j                  j                  d      }|sy| |k7  rt        d| d|  d      S y)u_  Reject worker-driven destructive calls on foreign task IDs.

    A process spawned by the dispatcher has ``HERMES_KANBAN_TASK`` set
    to its own task id. Tools like ``kanban_complete`` / ``kanban_block``
    / ``kanban_heartbeat`` mutate run-lifecycle state, so a buggy or
    prompt-injected worker that passed an explicit ``task_id`` for some
    other task could corrupt sibling or cross-tenant runs (see #19534).

    Orchestrator profiles (kanban toolset enabled but **no**
    ``HERMES_KANBAN_TASK`` in env) aren't subject to this check — their
    job is routing, and they sometimes legitimately close out child
    tasks or reopen blocked ones. Workers are narrowly scoped to their
    one task.

    Returns ``None`` when the call is allowed, or a tool-error string
    when it must be rejected. Callers should ``return`` the error
    verbatim.
    r   Nzworker is scoped to task z; refusing to mutate zf. Use kanban_comment to hand off information to other tasks, or kanban_create to spawn follow-up work.r   r   r   r	   )tidr#   s     r   _enforce_worker_task_ownershipr7      sP    & jjnn12G
g~'y0Ee ?@
 	

 r   boardc                6    ddl m} ||j                  |       fS )u  Import + connect lazily so the module imports cleanly in non-kanban
    contexts (e.g. test rigs that import every tool module).

    When ``board`` is provided it's forwarded to :func:`kb.connect`, which
    routes the connection to that board's sqlite file. ``None`` (the
    default) preserves the legacy resolution chain
    (``HERMES_KANBAN_DB`` → ``HERMES_KANBAN_BOARD`` env → current symlink
    → ``default``). Per-tool ``board`` lets a Telegram-side agent override
    the env-pinned active board without restarting Hermes.
    r   	kanban_dbr8   )
hermes_clir;   connect)r8   kbs     r   _connectr@      s     +rzzz&&&r   
dependencyneeds_inputc                 f    	 ddl m}   | d      \  }}|duxr t        |      S # t        $ r Y yw xY w)ap  True when an auxiliary client is configured for the goal judge.

    ``judge_goal`` is fail-open at the source: when no auxiliary model can
    be reached it returns a ``"continue"`` verdict that is indistinguishable
    from a real "not done yet" judgment. The completion gate must not treat
    that as a rejection, or an unconfigured/degraded auxiliary model would
    wedge every ``goal_mode`` worker (it could never close its own task).

    So we probe availability first and only enforce the gate when a judge is
    actually reachable. This mirrors the same client lookup ``judge_goal``
    performs internally.
    r   )get_text_auxiliary_client
goal_judgeFN)agent.auxiliary_clientrD   r   bool)rD   clientmodels      r   _goal_judge_availablerJ      sB    D1,? -$u+-  s   $ 	00g      N@g        float_auto_heartbeat_last_attemptc                 B   t         j                  j                  d      } | syddl}|j	                         }|t
        z
  t        k  ry|a	 t               \  }}	 t         j                  j                  d      }	 |j                  || |       t         j                  j                  d
      }	 |rt        |      nd}	 |j                  || d|       	 |j!                          y# t        $ r t        j                  dd	       Y yw xY w# t        t        f$ r d}Y aw xY w# t        $ r t        j                  dd	       Y qw xY w# t        $ r Y yw xY w# 	 |j!                          w # t        $ r Y w w xY wxY w# t        $ r t        j                  dd	       Y yw xY w)u  Best-effort: extend the kanban claim + bump board heartbeat for the
    current dispatcher-spawned worker, using identity from env vars.

    Returns True if a write was attempted (whether or not it succeeded);
    False if the call was skipped (not a kanban worker, rate-limited, or
    swallowed exception). The boolean is informational — callers should
    not branch on it.

    Identity comes from:
      * ``HERMES_KANBAN_TASK`` — task id (required; absence means no-op)
      * ``HERMES_KANBAN_RUN_ID`` — pins the run row so we don't heartbeat
        a stale run that may have already been reclaimed
      * ``HERMES_KANBAN_CLAIM_LOCK`` — claim lock for ``heartbeat_claim``;
        falls back to the default ``_claimer_id()`` for locally-driven
        workers that never went through the dispatcher path

    Rate-limited via the module-level ``_auto_heartbeat_last_attempt``
    timestamp (monotonic clock); not thread-safe in the strict sense, but
    the worst case is one extra DB write per race, which is harmless.
    r   Fr   NHERMES_KANBAN_CLAIM_LOCKclaimerz&auto-heartbeat: heartbeat_claim failedTexc_infor'   noteexpected_run_idz'auto-heartbeat: heartbeat_worker failedzauto-heartbeat: bridge failed)r   r   r   time	monotonicrL   $_AUTO_HEARTBEAT_MIN_INTERVAL_SECONDSr@   heartbeat_claimr   loggerdebugr(   	TypeErrorr)   heartbeat_workerclose)r6   _timenowr?   conn
claim_lock
run_id_rawrun_ids           r   !heartbeat_current_worker_from_envre      s   , **..-
.C
//
C**.RR#& :D	(BCJV""4j"A (>?J,6ZDW##D#D&#Q

 #  VEPTUV z*   WFQUVW
  

   4tDs   E; E 9C$ E -D
 =D! E $ DE DE 
DE DE ! EE EE 	EE; EE; E8E)(E8)	E52E84E55E88E; ; FFc                 4    t        j                  ddi|       S )NokT)jsondumps)fieldss    r   _okrk   "  s    ::tT,V,--r   c                h    | yt        |       j                         }|r|j                         dv ry|S )zANormalize CLI-compatible assignee sentinels for the tool surface.N>   nonenull-)strstriplower)valuetexts     r   _normalize_profileru   &  s5    }u:D4::<#88Kr   Fdefaultc                   | j                  |      }||d fS t        |t              r|d fS t        |      j	                         j                         }|dv ry|dv ry|| dfS )N>   1yestrue)TN>   0nofalse)FNz$ must be a boolean or 'true'/'false')r   
isinstancerG   rp   rq   rr   )argsnamerw   rs   rt   s        r   _parse_bool_argr   0  sx    HHTNE}}%d{u:##%D####tf@AAAr   c                ^    t         j                  j                  d      rt        |  d      S y)a  Belt-and-suspenders runtime guard for orchestrator-only handlers.

    The check_fn (`_check_kanban_orchestrator_mode`) keeps these tools
    out of the worker schema entirely, but in case a stale registration
    or test harness routes a worker to one of them anyway, return a
    structured tool_error so the model gets a clear refusal instead of
    silently mutating board state from a worker context.
    r   z is orchestrator-only; dispatcher-spawned workers must use kanban_complete, kanban_block, kanban_heartbeat, or kanban_comment for their assigned task.Nr5   )	tool_names    r   _require_orchestrator_toolr   >  s6     
zz~~*+k 6 6
 	

 r   c                6   | j                  ||j                        }| j                  ||j                        }i d|j                  d|j                  d|j                  d|j
                  d|j                  d|j                  d|j                  d|j                  d	|j                  d
|j                  d|j                  d|j                  d|j                  d|j                  d|j                   d|d|t#        |      t#        |      dS )z+Compact task shape for board-listing tools.idtitleassigneestatusprioritytenantworkspace_kindworkspace_path
project_id
created_by
created_at
started_atcompleted_atcurrent_run_idmodel_overrideparentschildren)parent_countchild_count)
parent_idsr   	child_idsr   r   r   r   r   r   r   r   r   r   r   r   r   r   len)r?   ra   taskr   r   s        r   _task_summary_dictr   P  sW   mmD$''*G||D$''*Hdgg 	DMM 	$++	
 	DMM 	$++ 	$-- 	$-- 	doo 	doo 	doo 	doo 	)) 	$-- 	$--  	7!" 	H#$ G8}' r   c                <   t        | j                  d            }|st        d      S | j                  d      }	 t        |      \  }}	 |j	                  ||      }|t        d| d      |j                          S |j                  ||      }|j                  ||      }|j                  ||      }	|j                  ||      }
|j                  ||      }d }d	 }t        j                   ||      |
||D cg c]&  }|j                  |j                  |j                  d
( c}|dd D cg c]1  }|j                   |j"                  |j                  |j$                  d3 c}|	D cg c]
  } ||       c}|j'                  ||      d      |j                          S c c}w c c}w c c}w # |j                          w xY w# t(        $ r}t        d|       cY d}~S d}~wt*        $ r-}t,        j/                  d       t        d|       cY d}~S d}~ww xY w)zsRead a task's full state: task row, parents, children, comments,
    runs (attempt history), and the last N events.r%   :task_id is required (or set HERMES_KANBAN_TASK in the env)r8   r<   Ntask 
 not foundc                   i d| j                   d| j                  d| j                  d| j                  d| j                  d| j
                  d| j                  d| j                  d	| j                  d
| j                  d| j                  d| j                  d| j                  d| j                  d| j                  d| j                  S )Nr   r   bodyr   r   r   r   r   r   r   r   r   r   resultr   r   )r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )ts    r   
_task_dictz _handle_show.<locals>._task_dict  s
   !$$ '28!&&

,4ahh ahh )3AJJ %a&6&6	
 %a&6&6 !!,, 1=all !!,, #ANN ahh %a&6&6 %a&6&6 r   c           
         | j                   | j                  | j                  | j                  | j                  | j
                  | j                  | j                  | j                  d	S )N	r   profiler   outcomesummaryerrorr0   r   ended_atr   )rs    r   	_run_dictz_handle_show.<locals>._run_dict  sG    $$199hh199 yy177 !

"#,,AJJ r   )authorr   r   i)kindpayloadr   rd   )r   r   r   commentseventsrunsworker_contextzkanban_show: zkanban_show failed)r$   r   r	   r@   get_taskr^   list_commentslist_events	list_runsr   r   rh   ri   r   r   r   r   r   rd   build_worker_contextr)   r   rZ   	exception)r   kwr6   r8   r?   ra   r   r   r   r   r   r   r   r   cer   s                    r   _handle_showr   o  s
    488I.
/CH
 	
 HHWE@/%(D8	;;tS)D|!E#j"9:j JJLi ''c2H^^D#.F<<c*DmmD#.G||D#.H ::"4("$ &   !xx#$<<1 $CD\  VV		#$<<188E
 044!14
 #%"9"9$"D' , JJL%

 5 JJL /M!-.. /-.M!-../s~   G 
"F1 ,G =A<F1 9+F"$	F1 -6F'#F1 )F,8F1 G "F1 1GG 	HG"H"H."HHHc           
        t        d      }|r|S | j                  d      }| j                  d      }| j                  d      }t        | d      \  }}|rt        |      S | j                  d      }|t        }	 t        |      }|d	k  rt        d
      S |t        kD  rt        dt               S | j                  d      }		 t        |	      \  }
}	 |
j                  |      }|
j                  ||||||d	z         }t        |      |kD  }|d| }t        j                  |D cg c]  }t        |
||       c}t        |      |||r|t        k  rt!        |dz  t              nd|d      |j#                          S # t        t        f$ r t        d      cY S w xY wc c}w # |j#                          w xY w# t        $ r}t        d|       cY d}~S d}~wt$        $ r-}t&        j)                  d       t        d|       cY d}~S d}~ww xY w)z:List task summaries with the same core filters as the CLI.kanban_listr   r   r   include_archivedlimitNzlimit must be an integer   zlimit must be >= 1zlimit must be <= r8   r<   )r   r   r   r   r      )taskscountr   	truncated
next_limitpromotedzkanban_list: zkanban_list failed)r   r   r   r	   KANBAN_LIST_DEFAULT_LIMITr(   r\   r)   KANBAN_LIST_MAX_LIMITr@   recompute_ready
list_tasksr   rh   ri   r   minr^   r   rZ   r   )r   r   guardr   r   r   r   
bool_errorr   r8   r?   ra   r   rowsr   r   r   r   s                     r   _handle_listr     s   &}5Exx
#HXXhFXXhF#249K#L j*%%HHWE})6E
 qy.//$$-.C-DEFFHHWE#/%(D	 ))$/H ==!!1ai ! D D	E)I%LE::CHIa,Rq9IU& !U-B%B 	#89HL$
 
 JJLK z" 645564 J JJL /M!-.. /-.M!-../sm   9E; F5 AF  "F64F  *F5 ;FFF   F22F5 5	H
>GH
H
"H?H
H
c                   t        | j                  d            }|st        d      S t        |      }|r|S | j                  d      }| j                  d      }| j                  d      }|rt	        t        |      d      }|rt	        t        |      d      }|Ht        |t              r8t        j                  |      }t	        |d      }	 t        j                  |      }| j                  d	      }| j                  d
      }	|t        |t
              r|g}t        |t        t        f      s!t        dt        |      j                         S |D 
cg c]5  }
t        |
      j!                         st        |
      j!                         7 }}
|	Vt        |	t
              r|	g}	t        |	t        t        f      s!t        dt        |	      j                         S |	D cg c]5  }t        |      j!                         st        |      j!                         7 }	}|	r|i }n1t        |t              s!t        dt        |      j                         S |j                  d
      }t        |t        t        f      rhg }t#               }t        |      |	z   D ]E  }t        |      j!                         }|s||vs$|j%                  |       |j'                  |       G ||d
<   n|	|d
<   |s|st        d      S |1t        |t              s!t        dt        |      j                         S t)        ||      }| j                  d      }	 t+        |      \  }}	 |j-                  ||      }|r|j.                  rt1               rd}d}	 t3        |j4                   d|j6                  xs d j!                         |xs |xs dj!                               \  }}}}}|dk7  r"t        d| d| d      |j?                          S 	 |jA                  ||||||tC        |            }|st        d | d!      |j?                          S |jM                  ||      }tO        ||r|jP                  nd"      |j?                          S # t        j                  $ r Y w xY wc c}
w c c}w # t8        $ r"}t:        j=                  d|d       Y d}~d}~ww xY w# |jD                  $ r)}t        d| d      cY d}~|j?                          S d}~w|jF                  $ rB}t        ddjI                  |jJ                         d      cY d}~|j?                          S d}~ww xY w# |j?                          w xY w# tR        $ r}t        d#|       cY d}~S d}~wt8        $ r-}t:        jU                  d$       t        d#|       cY d}~S d}~ww xY w)%z5Mark the current task done with a structured handoff.r%   r   r   r0   r   TforceNcreated_cards	artifactsz.created_cards must be a list of task ids, got z,artifacts must be a list of file paths, got z%metadata must be an object/dict, got z4provide at least one of: summary (preferred), resultr8   r<   done z

)goallast_responsez0goal judge check failed, allowing completion: %srQ   z#Goal completion rejected by judge: z. To proceed, either: (1) provide explicit acceptance evidence in your summary matching the task's criteria, or (2) create continuation tasks with parents=[z] and keep this task alive.)r   r   r0   r   rU   z;kanban_complete could not preserve the declared artifacts: z. Your task is still in-flight and its scratch workspace was kept. Fix the artifact path or storage error, then retry kanban_complete with the same handoff.zfkanban_complete blocked: the following created_cards do not exist or were not created by this worker: z, z. Your task is still in-flight (no state change). Retry kanban_complete with the same summary/metadata and either drop these ids from created_cards, or pass created_cards=[] to skip the card-claim check entirely.zcould not complete z! (unknown id or already terminal))r%   rd   zkanban_complete: zkanban_complete failed)+r$   r   r	   r7   r   rp   r   r/   rh   ri   loadsJSONDecodeErrorlisttupletype__name__rq   setaddappendr3   r@   r   	goal_moderJ   r   r   r   r   rZ   warningr^   complete_taskr+   ArtifactPreservationErrorHallucinatedCardsErrorjoinphantom
latest_runrk   r   r)   r   )r   r   r6   ownership_errr   r0   r   	meta_jsonr   r   r   pexistingmergedseenitemsr8   r?   ra   r   verdictreason_	judge_excrg   artifact_errhall_errrunr   s                                 r   _handle_completer     s   
488I.
/CH
 	
 337Mhhy!Gxx
#HXXhF'GDA&s6{$?
8T :JJx(	))4@		zz),H HH_-M%I mS)*OM-$7@&//02  %2
 SV\\^CFLLN
 
 i%"I)dE]3>	?++,. 
 %.
 QCFLLN
	 
 $/!;H~../1   ||K0H(T5M2$&!$ NY6 )DD	)AQd]a(	)
 )/%(1%vB
 	
 Jx$>3DN4K4K3LM
 	
 .c8<HHHWEU3%(DN	 ;;tS)D+@+B  0: $

|4		R/@AGGI'.'>&'>B&E&E&G0,GVQ1 f$%=fX FJ KN O45b JJLS!%%#!7X"/$23$7	 & B !)#.OP JJL --c*CsS366dCJJLe ## 		

| !  NNJ!!% #  0 // !Q#n %WX B JJL7 ,,  "Hyy!1!123 4NO   JJL76 JJL 3-aS122 312-aS1223s  R R5R#R$R$U6 .U! 
AR) U! 4U6 !S 'U! 7U6 ,U! 4U6 RR)	S2S
U! SU! U&T
4U5U! 9U6 
U'UUU! U6 UU! !U33U6 6	W?VWW"W WWc           	        t        | j                  d            }|st        d      S t        |      }|r|S | j                  d      }|rt	        |      j                         st        d      S t        t	        |      d      }| j                  d      }| j                  d      }	 t        |	      \  }}|@||j                  vr2|j                          t        dt        |j                         d      S |j                  ||      }	|	rC|	j                  r7|t        vr/|j                          t        dt        t               d|d      S 	 |j                  ||||t        |            }
|
st        d| d      |j                          S |j!                  ||      }|j                  ||      }t#        ||r|j$                  nd
|r|j&                  nd|      |j                          S # |j                          w xY w# t(        $ r}t        d|       cY d
}~S d
}~wt*        $ r-}t,        j/                  d       t        d|       cY d
}~S d
}~ww xY w)z?Transition the task to blocked with a reason a human will read.r%   r   r   u2   reason is required — explain what input you needTr   r   r8   r<   Nzkind must be one of z (or omit it)z,goal_mode tasks can only block with kind in z (got u   ). If the task is actually finished or cannot proceed for another reason, call kanban_complete instead — the completion judge will evaluate it.)r   r   rU   zcould not block z% (unknown id or not in running/ready)blocked)r%   rd   r   
block_kindzkanban_block: zkanban_block failed)r$   r   r	   r7   rp   rq   r   r@   VALID_BLOCK_KINDSr^   sortedr   r   _GOAL_MODE_BLOCK_ALLOWED_KINDS
block_taskr+   r   rk   r   r   r)   r   rZ   r   )r   r   r6   r   r   r   r8   r?   ra   r   rg   r   landedr   s                 r   _handle_blockr    s@   
488I.
/CH
 	
 337MXXhFV**,NOO"3v;d;F88FDHHWE;0%(DB,@,@ @JJL&vb.B.B'C&DMR  {{4%::JJL>89:& I56 	c .s 3	  B !&se ,% & JJL --c*C [[s+F!$svv$(.v}}I	 JJLDJJL 0N1#.// 0./N1#.//0s]   'AH 8AH /G/ ?H AG/ H /HH 	IH I I,"IIIc                   t        | j                  d            }|st        d      S t        |      }|r|S | j                  d      }| j                  d      }	 t	        |      \  }}	 t
        j                  j                  d      }|j                  |||       |j                  |||t        |            }	|	st        d	| d
      |j                          S t        |      |j                          S # |j                          w xY w# t        $ r}
t        d|
       cY d}
~
S d}
~
wt        $ r-}
t        j                  d       t        d|
       cY d}
~
S d}
~
ww xY w)u  Signal that the worker is still alive during a long operation.

    Extends the claim TTL via ``heartbeat_claim`` AND records a heartbeat
    event via ``heartbeat_worker``. Without the ``heartbeat_claim`` half,
    a diligent worker that loops this tool while a single tool call
    blocks the agent for >DEFAULT_CLAIM_TTL_SECONDS still gets reclaimed
    by ``release_stale_claims`` — which is exactly the trap that
    ``heartbeat_claim``'s docstring warns against.
    r%   r   rT   r8   r<   rN   rO   rS   zcould not heartbeat z (unknown id or not running))r%   zkanban_heartbeat: Nzkanban_heartbeat failed)r$   r   r	   r7   r@   r   r   rY   r]   r+   r^   rk   r)   r   rZ   r   )r   r   r6   r   rT   r8   r?   ra   rb   rg   r   s              r   _handle_heartbeatr
    sR    488I.
/CH
 	
 337M88FDHHWE4%(D	 (BCJtS*=$$ .s 3	 % B !*3%/KL
 JJL s#JJLDJJL 4.qc233 423.qc2334sU   D *A!C8 D C8 'D 8D

D 	E"D)#E")E"5"EE"E"c                   | j                  d      }|st        d      S | j                  d      }|rt        |      j                         st        d      S t	        t        |      d      }t
        j                  j                  d      xs d}| j                  d	      }	 t        |
      \  }}	 |j                  |||t        |            }t        ||      |j                          S # |j                          w xY w# t        $ r}	t        d|	       cY d}	~	S d}	~	wt        $ r-}	t        j                  d       t        d|	       cY d}	~	S d}	~	ww xY w)z$Append a comment to a task's thread.r%   uo   task_id is required (use the current task id if that's what you mean — pulls from env but kept explicit here)r   zbody is requiredTr   HERMES_PROFILEworkerr8   r<   )r   r   )r%   
comment_idzkanban_comment: Nzkanban_comment failed)r   r	   rp   rq   r   r   r   r@   add_commentrk   r^   r)   r   rZ   r   )
r   r   r6   r   r   r8   r?   ra   cidr   s
             r   _handle_commentr  )  s&   
((9
CB
 	
 88FDs4y(,-- T$7D ZZ^^,-9FHHWE2%(D	..s6D	.JCss3JJLDJJL 2,QC011 201,QC0112sH   !D 1*C, D ,C>>D 	E
DEE)"EEEc           	        ddl m} t        | j                  d            }|st	        d      S t        |      }|r|S | j                  d      }|rt        |      j                         st	        d      S | j                  d      }|rt        |      j                         st	        d      S dd	l}dd	l	}	 |j                  t        |      d
      }	| j                  d      }| j                  d      }	 t        |      \  }}	 |j                  ||t        |      |	|d|      }t        ||t!        |	            |j#                          S # |j                  t        f$ r}
t	        d|
       cY d	}
~
S d	}
~
ww xY w# |j#                          w xY w# |j$                  $ r}
t	        d|
       cY d	}
~
S d	}
~
wt        $ r}
t	        d|
       cY d	}
~
S d	}
~
wt&        $ r-}
t(        j+                  d       t	        d|
       cY d	}
~
S d	}
~
ww xY w)uQ  Attach an inline (base64) file to a task.

    Mirrors the dashboard's upload endpoint for the agent surface: decode
    the payload, enforce the shared size cap, write it under the per-task
    attachments dir, and record the metadata row — all via
    ``kanban_db.store_attachment_bytes`` so the three surfaces stay in lockstep.
    r   r:   r%   r   filenamezfilename is requiredcontent_base64zcontent_base64 is requiredNT)validatez$content_base64 is not valid base64: content_typer8   r<   agentr  uploaded_byr8   r%   attachment_idsizezkanban_attach: zkanban_attach failed)r=   r;   r$   r   r	   r7   rp   rq   base64binascii	b64decodeErrorr)   r@   store_attachment_bytesrk   r   r^   AttachmentTooLarger   rZ   r   )r   r   r?   r6   r   r  content_b64r  r  datar   r  r8   r   ra   att_ids                   r   _handle_attachr&  N  s    +
488I.
/CH
 	
 337Mxx
#H3x=..0011((+,Kc+.446677FK 04@ 88N+LHHWE1'4	..H)# / F s&s4yIJJL% NNJ' F@DEEF$ JJL   1OA3/00 1OA3/00 1/0OA3/001sx   4E 3F 7E; :F E8 E3-E83E8;FF H
F2,H
2H
>GH
H
"H?H
H
   urlc                n   ddl m}m} ddl}ddlm} | }t        t        dz         D ]r  } ||      j                  xs dj                         }|dvrt        d|d	       ||      st        d
|       g }	d}
|j                  d|ddidd      5 }|j                  r>|j                  j                  d      }|st        d|        |||      }	 ddd       |j                          |j                  j                  d      xs dj!                  d      d   j#                         xs d}|j%                  d      D ]8  }|
t'        |      z  }
|
|kD  rt        d|dz   d      |	j)                  |       : 	 ddd       dj+                  |	      fc S  t        d|        # 1 sw Y   -xY w)u3  Fetch ``url`` over http(s) with SSRF guarding, capped at ``max_bytes``.

    Every hop — the initial URL and each redirect target — is validated with
    ``tools.url_safety.is_safe_url`` before it is fetched, so a
    model-controlled URL (or a public host 302ing to one) cannot reach
    loopback, private/CGNAT ranges, or cloud metadata endpoints. Redirects
    are followed manually (``follow_redirects=False``) so each Location is
    re-checked, mirroring ``tools.skills_hub._guarded_http_get``.

    Returns ``(data, content_type)``. Raises ``ValueError`` for a non-http(s)
    scheme, an SSRF-blocked target, too many redirects, or a body that
    overruns the cap (the caller maps it to a clean tool error). Reads in
    chunks so an oversize response is rejected without buffering the whole
    thing.
    r   )urljoinurlparseN)is_safe_urlr   r   )httphttpszunsupported URL scheme z; only http/https are allowedz;URL blocked by SSRF protection (private/internal address): GETz
User-Agentzhermes-kanban/attach   F)headerstimeoutfollow_redirectslocationz&redirect without Location header from zcontent-type;i   zattachment exceeds z	 MB limitr   ztoo many redirects fetching )urllib.parser*  r+  httpxtools.url_safetyr,  range_MAX_ATTACH_URL_REDIRECTSschemerr   r)   streamis_redirectr1  r   raise_for_statussplitrq   
iter_bytesr   r   r   )r(  	max_bytesr*  r+  r7  r,  current_urlr   r;  chunkstotalrespr4  r  chunks                  r   _download_url_with_caprG    s     /,K,q01 ".;'..4";;=**)&3PQ  ;'Mk][  !\\!#9:"  
 	% <<++J7$'Mk]%[\\%k8<	% 	% !!# LL,,^<BII#NqQWWYa]aL5 %U#9$$-iK.H-IS  e$%	%, xx--E".F 3C59
::/	% 	%s   AF+BF++F4	c           	     2   ddl m} t        | j                  d            }|st	        d      S t        |      }|r|S | j                  d      }|rt        |      j                         st	        d      S t        |      j                         }| j                  d      xs | j                  d      }|rt        |      j                         sGdd	lm	}m
}  | ||      j                  j                  d
d      d         j                         }	|	xs d}| j                  d      }
| j                  d      }	 t        ||j                        \  }}	 t'        |      \  }}	 |j)                  ||t        |      ||
xs |d|      }t+        ||t-        |            |j/                          S # t        $ r}t	        d|       cY d}~S d}~wt         $ r0}t"        j%                  d       t	        d| d|       cY d}~S d}~ww xY w# |j/                          w xY w# |j0                  $ r}t	        d|       cY d}~S d}~wt        $ r}t	        d|       cY d}~S d}~wt         $ r-}t"        j%                  d       t	        d|       cY d}~S d}~ww xY w)a  Attach a file fetched server-side from a URL.

    The agent passes a URL; Hermes downloads it (with the shared size cap)
    and stores it as a real attachment. Useful when the agent has a link
    rather than the bytes. Only http/https URLs are accepted.
    r   r:   r%   r   r(  zurl is requiredr  r   )unquoter+  /r   downloadr  r8   zkanban_attach_url: Nz!kanban_attach_url download failedz#kanban_attach_url: failed to fetch z: r<   r  r  r  zkanban_attach_url failed)r=   r;   r$   r   r	   r7   rp   rq   r6  rI  r+  pathrsplitrG  KANBAN_ATTACHMENT_MAX_BYTESr)   r   rZ   r   r@   r!  rk   r   r^   r"  )r   r   r?   r6   r   r(  r  rI  r+  leafr  r8   r$  
fetched_ctr   r   ra   r%  s                     r   _handle_attach_urlrR    sW    +
488I.
/CH
 	
 337M
((5/Cc#hnn&+,,
c(..
Cxx
#8txx'8H3x=..02x}))00a8<=CCE%:88N+LHHWEL1#r7U7UVj5'4	..H)7Z# / F s&s4yIJJL'  5/s344 L<=?uBqcJKKL" JJL   5/s344 5/s344 534/s3445s   6F, H  ;H H ,	H5GHH%G?9H?HHH J+H>8J>J
IJJ)"JJJc                   t        | j                  d            }|st        d      S | j                  d      }	 t        |      \  }}	 |j	                  ||      t        d| d      |j                          S |j                  ||      }t        j                  d||D cg c]R  }|j                  |j                  |j                  |j                  |j                  |j                  |j                  d	T c}d
      |j                          S c c}w # |j                          w xY w# t         $ r}t        d|       cY d}~S d}~wt"        $ r-}t$        j'                  d       t        d|       cY d}~S d}~ww xY w)z@List a task's attachments (read-only; no ownership restriction).r%   r   r8   r<   Nr   r   T)r   r  r  r  r  stored_pathr   )rg   r%   attachmentszkanban_attachments: zkanban_attachments failed)r$   r   r	   r@   r   r^   list_attachmentsrh   ri   r   r  r  r  r  rT  r   r)   r   rZ   r   )	r   r   r6   r8   r?   ra   attsar   s	            r   _handle_attachmentsrY    s_   
488I.
/CH
 	
 HHWE6%(D	{{4%-!E#j"9:& JJL% &&tS1D:: "    dd$%JJ() !'(}}'(}}&'ll  " JJL  JJL 60455 64504556sf   D, 
 D *D, ;'D "AD9D D, D D))D, ,	F5EFF"E<6F<Fc                   | j                  d      }|rt        |      j                         st        d      S | j                  d      }|st        d      S | j                  d      }| j                  d      xs g }| j                  d      xs t        j
                  j                  d      }| j                  d	      xs t        j
                  j                  d
      }| j                  d      }| j                  d      }	| j                  d      }
| j                  d      xs | j                  d      }|	du xr |
du }|	d}	t        | d      \  }}|rt        |      S | j                  d      }| j                  d      }| j                  d      xs d}| j                  d      }t        |t              r|g}|7t        |t        t        f      s!t        dt        |      j                         S t        | d      \  }}|rt        |      S | j                  d      }t        |t              r|g}t        |t        t        f      s!t        dt        |      j                         S | j                  d      }	 t        |      \  }}	 |rst        j
                  j                  d      }|rR|j                  ||      }|>|j                  r2|j                  }	|j                  }
||j                   r|j                   }|j#                  |t        |      j                         |t        |      t        |      ||t%        |      ndt        |	      |
||||t%        |      nd|||t%        |      ndt        |      t        j
                  j                  d       xs d!|"      }|j                  ||      }t'        ||      }t)        ||r|j*                  nd|#      |j-                          S # |j-                          w xY w# t.        $ r}t        d$|       cY d}~S d}~wt0        $ r-}t2        j5                  d%       t        d$|       cY d}~S d}~ww xY w)&zCreate a child task. Orchestrator workers use this to fan out.

    ``parents`` can be a list of task ids; dependency-gated promotion
    works as usual.
    r   ztitle is requiredr   u   assignee is required — name the profile that should execute this task (the dispatcher will only spawn tasks with an assignee)r   r   r   HERMES_TENANTr1   r-   r   r   r   projectr   Nscratchtriageidempotency_keymax_runtime_secondsinitial_statusrunningskillsz*skills must be a list of skill names, got r   goal_max_turnsz(parents must be a list of task ids, got r8   r<   r   r   r  r  )r   r   r   r   r   r   r   r   r   r^  r_  r`  rc  r   rd  ra  r   r1   )r%   r   
subscribedzkanban_create: zkanban_create failed)r   rp   rq   r	   r   r   r   r   r   r   r   r   r@   r   r   r   r   create_taskr(   _maybe_auto_subscriberk   r   r^   r)   r   rZ   r   )r   r   r   r   r   r   r   r1   r   r   r   r   _inherit_workspacer^  r   r_  r`  ra  rc  r   goal_bool_errorrd  r8   r?   ra   	_self_tid
_self_tasknew_tidnew_taskre  r   s                                  r   _handle_creatern  )  s    HHWEE
((*-..xx
#HK
 	
 88FDhhy!'RGXXhB2::>>/#BF ,'N2::>>:M+NJxx
#H XX./NXX./N)$>(>J'4/JNd4J"(x8FJ*%%hh01O((#89XX./<9NXXhF&#*VdE]"C8f9N9N8OP
 	
 "1{!CI/**XX./N'3)ge}-6tG}7M7M6NO
 	
 HHWE71%(D0	 "JJNN+?@	!#T9!=J!-*2K2K)3)B)B)3)B)B &-*2G2G)3)>)>Jnn%j&&(Xg*2*>XA">2-% / +6 +,<@#+9+EC'4">2::>>*:;Gx%1 % G4 {{41H.tW=J*2x% JJLDJJL 1OA3/00 1/0OA3/001sI   >P	 EO4 #P	 4PP	 		QP%Q%Q1"QQQc           	     2   	 t               }t        |ddd      sy	 d}d}	 ddlm}  |d	d      } |d
d      }|r|s2 |dd      xs  t
        j                  j                  dd      }|syd}|} |dd      xs d} |dd      xs d} |dd      xs t
        j                  j                  d      }	ddlm	}
 |
j                  | ||||||	       y# t        $ r Y w xY w# t        $ r+}t        j                  d||t        |             Y d}~yd}~ww xY w)a+  Auto-subscribe the calling session to task completion / block events.

    Returns True if a subscription row was written, False otherwise (no
    session context, config gate disabled, or best-effort failure). The
    caller surfaces this in the ``subscribed`` field of the kanban_create
    response so an orchestrator can decide whether to fall back to an
    explicit ``kanban_notify-subscribe`` or to polling.

    Gated by ``kanban.auto_subscribe_on_create`` in config.yaml (default
    True). Disable to mirror pre-feature behaviour, e.g. when the
    originating user/chat opted out via the per-platform notification
    toggle (see ``hermes dashboard``).

    Subscription paths:

    - **Gateway** (telegram/discord/slack/etc): ``HERMES_SESSION_PLATFORM``
      and ``HERMES_SESSION_CHAT_ID`` are set in ContextVars by the
      messaging gateway before agent dispatch. The notification poller
      already keys off these, so we just register a row.

    - **TUI** (herm desktop / herm TUI): the platform/chat_id ContextVars
      are intentionally cleared (TUI is a single-channel local UI, not
      a multi-tenant chat surface), but the agent subprocess inherits
      ``HERMES_SESSION_KEY`` from the parent session. We subscribe with
      ``platform="tui"`` and ``chat_id=<key>``; the TUI notification
      poller (``tui_gateway/server.py``) reads ``kanban_notify_subs``
      for these rows and posts the completion message into the running
      session.

    - **CLI / cron / test / unattached**: no persistent delivery channel,
      no-op.

    Failure mode: any exception inside the function is logged at WARNING
    with the offending exception + diagnostic env vars and swallowed.
    We never want a notification bookkeeping failure to fail the
    kanban_create that the agent is mid-conversation about.
    r   auto_subscribe_on_createTrv   Fr   r   )get_session_envHERMES_SESSION_PLATFORMHERMES_SESSION_CHAT_IDHERMES_SESSION_KEYtuiHERMES_SESSION_THREAD_IDNHERMES_SESSION_USER_IDHERMES_SESSION_PROFILEr  r:   )r%   platformchat_id	thread_iduser_idnotifier_profilez9_maybe_auto_subscribe failed: %r (platform=%r key_set=%r))r   r
   r   gateway.session_contextrq  r   r   r   r=   r;   add_notify_subrZ   r   rG   )ra   r%   r   ry  rz  rq  session_keyr{  r|  r}  _kb_excs               r   rg  rg    sW   LmsH&@$O P HG/;"#<bA!":B?w   4b9 <::>>"6;  H!G#$>CKt	!":B?G44b9 0zz~~./ 	 	0'w-	 	 	
 a   	b  G(DM	
 s0   C A	C" ,A&C" 	CC"	D+!DDc                   t        d      }|r|S | j                  d      }|st        d      S t        t	        |            }|r|S | j                  d      }	 t        |      \  }}	 |j                  |t	        |            }|st        d| d      |j                          S |j                  |t	        |            }	t        t	        |      |	r|	j                  nd	      |j                          S # |j                          w xY w# t        $ r}
t        d
|
       cY d}
~
S d}
~
wt        $ r-}
t        j                  d       t        d
|
       cY d}
~
S d}
~
ww xY w)zFTransition a blocked task to ready, or todo while parents remain open.kanban_unblockr%   ztask_id is requiredr8   r<   zcould not unblock z (not blocked or unknown)N)r%   r   zkanban_unblock: zkanban_unblock failed)r   r   r	   r7   rp   r@   unblock_taskr^   r   rk   r   r)   r   rZ   r   )r   r   r   r6   r   r8   r?   ra   rg   r   r   s              r   _handle_unblockr    s0   &'78E
((9
C/0023s8<MHHWE2%(D	s3x0B!$6se;T"UV JJL ;;tSX.Ds3xtNJJLDJJL 2,QC011 201,QC0112sT   D (+C3 D $>C3 "D 3DD 	ED$E$E0"EEEc                   | j                  d      }| j                  d      }|r|st        d      S | j                  d      }	 t        |      \  }}	 |j                  |||       t	        ||      |j                          S # |j                          w xY w# t        $ r}t        d|       cY d}~S d}~wt        $ r-}t        j                  d	       t        d|       cY d}~S d}~ww xY w)
u4   Add a parent→child dependency edge after the fact.	parent_idchild_idz(both parent_id and child_id are requiredr8   r<   )r  r  zkanban_link: Nzkanban_link failed)
r   r	   r@   
link_tasksrk   r^   r)   r   rZ   r   )r   r   r  r  r8   r?   ra   r   s           r   _handle_linkr     s    %Ixx
#HHDEEHHWE/%(D	MM$)hMGX>JJLDJJL /M!-.. /-.M!-../sH   B  B 4B BB 	C/#B60C/6C/"C*$C/*C/zrTask id. If omitted, defaults to HERMES_KANBAN_TASK from the env (the task the dispatcher spawned you to work on).uX  Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call.c                     dt         dS )zSchema fragment for the optional ``board`` parameter.

    Centralised so a future tweak to the description / validation hint
    only has to land in one place.
    stringr   description)_DESC_BOARDr   r   r   _board_schema_propr  I  s     [99r   kanban_showuO  Read a task's full state — title, body, assignee, parent task handoffs, your prior attempts on this task if any, comments, and recent events. Use this to (re)orient yourself before starting work, especially on retries. The response includes a pre-formatted ``worker_context`` string suitable for inclusion verbatim in your reasoning.objectr  r  )r%   r8   )r   
propertiesrequired)r   r  
parametersr   u  List Kanban task summaries so an orchestrator profile can discover work to route. Supports the same core filters as the CLI: assignee, status, tenant, include_archived, and limit. Returns compact rows with ids, title, status, assignee, priority, parent/child ids, and counts. Bounded to 50 rows by default, 200 max, with truncation metadata. Also recomputes ready tasks before listing, matching the CLI. Orchestrator-only — dispatcher-spawned task workers never see this tool.z!Optional assignee/profile filter.)r^  todoreadyrb  r  r   archivedzOptional task status filter.)r   enumr  z)Optional tenant/project namespace filter.booleanz*Include archived tasks. Defaults to false.integerz6Optional maximum rows to return (default 50, max 200).)r   r   r   r   r   r8   kanban_completeuh  Mark your current task done with a structured handoff for downstream workers and humans. Prefer ``summary`` for a human-readable 1-3 sentence description of what you did; put machine-readable facts in ``metadata`` (changed_files, tests_run, decisions, findings, etc). At least one of ``summary`` or ``result`` is required. If you created new tasks via ``kanban_create`` during this run, list their ids in ``created_cards`` — the kernel verifies them so phantom references are caught before they leak into downstream automation. If you produced deliverable files (charts, PDFs, spreadsheets, generated images), list their absolute paths in ``artifacts`` — the gateway notifier will upload them as native attachments to the human who subscribed to the task, so the deliverable lands in their chat alongside the summary instead of being a path they have to fetch by hand.zrHuman-readable handoff, 1-3 sentences. Appears in Run History on the dashboard and in downstream workers' context.u   Free-form dict of structured facts about this attempt — {"changed_files": [...], "tests_run": 12, "findings": [...]}. Surfaced to downstream workers alongside ``summary``.zShort result log line (legacy field, maps to task.result). Use ``summary`` instead when possible; this exists for compatibility with callers that still set --result on the CLI.arrayr   u  Optional structured manifest of task ids you created via ``kanban_create`` during this run. The kernel verifies each id exists and was created by this worker's profile; any phantom id blocks the completion with an error listing what went wrong (auditable in the task's events). Only list ids you got back from a successful ``kanban_create`` call — do not invent or remember ids from prose. Omit the field if you did not create any cards.)r   itemsr  u  Optional list of absolute paths to deliverable files you produced during this run — generated charts, PDFs, spreadsheets, images, archives. Examples: ["/tmp/q3-revenue.png", "/tmp/report.pdf"]. The gateway notifier uploads each path as a native attachment to the subscribed chat (images embed inline, everything else uploads as a file) so the deliverable lands with the completion notification. Skip intermediate scratch files and references that are not the deliverable. The path must exist on disk at completion. Files inside a managed scratch workspace are copied to durable task attachments before cleanup; a missing declared scratch artifact keeps the task in-flight so you can fix the path and retry.)r%   r   r0   r   r   r   r8   kanban_blockuq  Stop work on this task and route it according to WHY you're stuck. Set ``kind`` to say which: 'dependency' (waiting on another task — goes to todo and auto-resumes when that task finishes, no human needed), 'needs_input' (you need a human decision/answer), 'capability' (a hard wall: no access, missing credentials, an action no agent can do), or 'transient' (a flaky failure that may clear). ``reason`` is shown to the human on the board. If a task keeps getting unblocked and re-blocked for the same reason, it is auto-escalated to triage. Use for genuine blockers only — don't block on things you can resolve yourself.zWhat you need answered or what stopped you, in one or two sentences. Don't paste the whole conversation; the human has the board and can ask follow-ups via comments.)rA   rB   
capability	transientzWhy you're blocked. 'dependency' waits in todo and resumes automatically; the others surface to a human. Omit only if none apply.)r%   r   r   r8   r   kanban_heartbeatu   Signal that you're still alive during a long operation (training, encoding, large crawls). Call every few minutes so humans see liveness separately from PID checks. Pure side effect — no work changes.zHOptional short note describing current progress. Shown in the event log.)r%   rT   r8   kanban_commentu   Append a comment to a task's thread. Use for durable notes that should outlive this run (questions for the next worker, partial findings, rationale). Ephemeral reasoning doesn't belong here — use your normal response instead.uW   Task id. Required (may be your own task or another's — comment threads are per-task).z Markdown-supported comment body.)r%   r   r8   r   kanban_attachu]  Attach a file to a task by passing its bytes inline (base64). Use for genuine file artifacts the next worker or a human should be able to download — generated reports, images, exports. The file is stored as a real attachment (not a comment link) under the task's attachments dir, capped at 25 MB. Prefer kanban_attach_url when you only have a URL.zjFile name to store it under (e.g. 'report.pdf'). Directory components are stripped; only the leaf is kept.z5The file contents, base64-encoded. Max 25 MB decoded.z,Optional MIME type (e.g. 'application/pdf').)r%   r  r  r  r8   r  r  kanban_attach_urlu   Attach a file to a task by URL — Hermes downloads it server-side and stores it as a real attachment (capped at 25 MB). Use when you have a link rather than the bytes. Only http/https URLs are accepted.zhttp(s) URL to fetch and store.zKOptional name to store it under. Defaults to the URL path's leaf component.zMOptional MIME type override. Defaults to the Content-Type the server returns.)r%   r(  r  r  r8   kanban_attachmentszList the files attached to a task: id, filename, content_type, size, who uploaded it, and the absolute on-disk path you can read.kanban_createuc  Create a new kanban task, optionally as a child of the current one (pass the current task id in ``parents``). Used by orchestrator workers to fan out — decompose work into child tasks with specific assignees, link them into a pipeline, then complete your own task. The dispatcher picks up the new tasks on its next tick and spawns the assigned profiles.r   zShort task title (required).r   u   Profile name that should execute this task (e.g. 'researcher-a', 'reviewer', 'writer'). Required — tasks without an assignee are never dispatched.zkOpening post: full spec, acceptance criteria, links. The assigned worker reads this as part of its context.r   zParent task ids. The new task stays in 'todo' until every parent reaches 'done'; then it auto-promotes to 'ready'. Typical fan-in: list all the researcher task ids when creating a synthesizer task.r   zUOptional namespace for multi-project isolation. Defaults to HERMES_TENANT env if set.r   zZDispatcher tiebreaker. Higher = picked sooner when multiple ready tasks share an assignee.r   )r]  dirworktreezWorkspace flavor: 'scratch' (fresh tmp dir, default), 'dir' (shared directory, requires absolute workspace_path), 'worktree' (git worktree).r   zYAbsolute path for 'dir' or 'worktree' workspace. Relative paths are rejected at dispatch.r\  zOptional project id or slug to link the task to. When set, the task becomes a git worktree under the project's primary repo with a deterministic branch (project slug + task id), instead of a random branch.r^  u   If true, task lands in 'triage' instead of 'todo' — a specifier profile is expected to flesh out the body before work starts.r_  zIf a non-archived task with this key already exists, return that task's id instead of creating a duplicate. Useful for retry-safe automation.r`  zxPer-task runtime cap. When exceeded, the dispatcher SIGTERMs the worker and re-queues the task with outcome='timed_out'.ra  rb  r  zInitial card status. Use 'blocked' for tasks that require immediate human ops (R3 gate) to skip the brief running-to-blocked transition. Defaults to 'running', which preserves the usual dispatch path.rc  u:  Skill names to force-load into the dispatched worker. The kanban lifecycle is already injected automatically; use this to pin a task to a specialist context — e.g. ['translation'] for a translation task, ['github-code-review'] for a reviewer task. The names must match skills installed on the assignee's profile.r   a  Run the dispatched worker in a goal loop. When true, after each turn an auxiliary judge checks the worker's response against this card's title/body; if the work isn't done and budget remains, the worker keeps going in the same session until the judge agrees it's complete (or the goal-turn budget is exhausted, which blocks the task for human review). Use this for open-ended cards where one shot rarely finishes the work. Defaults to false (classic single-shot worker).rd  zTurn budget for goal_mode workers. Caps how many continuation turns the worker may take before the task is blocked for review. Ignored unless goal_mode is true. Defaults to the goal-engine default (20).r  u   Unblock a Kanban task. It moves to ready when all parents are done, or todo while any parent remains open. Orchestrator-only — only profiles with the kanban toolset can unblock routed work; dispatcher-spawned task workers never see this tool.z6Blocked task id to move to ready or parent-gated todo.kanban_linku   Add a parent→child dependency edge after both tasks already exist. The child won't promote to 'ready' until all parents are 'done'. Cycles and self-links are rejected.zParent task id.zChild task id.)r  r  r8   r  r  r   u   📋)r   toolsetschemahandlercheck_fnemojiu   ✔u   ⏸u   💓u   💬u   📎u   ➕u   ▶u   🔗)returnrG   )r"   Optional[str]r  r  )r%   rp   r  zOptional[int])r%   rp   r0   Optional[dict]r  r  )r6   rp   r  r  )N)r8   r  )rj   r   r  rp   )rs   r   r  r  )r   r/   r   rp   rw   rG   )r   rp   r  r  )r  zdict[str, Any])r   r/   r  rp   )r(  rp   rA  r(   r  ztuple[bytes, Optional[str]])ra   r   r%   rp   r  rG   )r  zdict[str, str])K__doc__
__future__r   rh   loggingr   typingr   r   agent.redactr   hermes_cli.goalsr   tools.registryr   r	   r   r
   r   	getLoggerr   rZ   r   r   r   r   r    r$   r+   r3   r7   r@   	frozensetr  rJ   rX   rL   __annotations__re   rk   ru   r   r   r   r   r   r   r  r
  r  r&  r:  rG  rR  rY  rn  rg  r  r  _DESC_TASK_ID_DEFAULTr  r  KANBAN_SHOW_SCHEMAKANBAN_LIST_SCHEMAKANBAN_COMPLETE_SCHEMAKANBAN_BLOCK_SCHEMAKANBAN_HEARTBEAT_SCHEMAKANBAN_COMMENT_SCHEMAKANBAN_ATTACH_SCHEMAKANBAN_ATTACH_URL_SCHEMAKANBAN_ATTACHMENTS_SCHEMAKANBAN_CREATE_SCHEMAKANBAN_UNBLOCK_SCHEMAKANBAN_LINK_SCHEMAregisterr   r   r   <module>r     s  8 #   	   . ' / 2			8	$   
)")$
*@' "+L-+H!I .T (, $&) e )8v. ?D B$>I/X:/zm3`K0\04f"2J51p  :;z95x$6Nt1n`F2:/68  : 	&  !4 ()
 
 0 	  !B
 !  > !J
 "K!
 "W ()3
6 ;+ \ 	>"  !4
 !( !5 !B   (+	0	"   (+H	* ()IE
L QI'] @ 		4  !4
 !O !P/	 ()-
0 J5* Z 	&  !4
 !. ()
 ! : 	<  !C !A ()
 '! : 	6  !4
 !P !V
 !M ())
,  !121$ N  	  !4
 !@
 !- !7 ()/
2 G7% P !	M  !4 ()
 
 ( 	C O
 =O

  "O
  #O
,  (+(	
-O
B  <CO
P !CQO
^  6K	_O
p  ?qO
~  <O
P !3QO
`  E aO
p "!5$qO
@  "I.J		AO
T  (+*	UO
n !LoO
J !FKO
\ ')]O
` j)eS^ B 	?  !W ()
 K
 , 	:
 "*;LM"*;KL')

 !*- .   	
   	,
   	!
   	
   	"
   	 
   	
   	#
   	$
   	
   	 ,
   	
r   