
    .cjuL                      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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/dd4Z+dd5Z,dd7Z-dd8Z.dd9Z/dd:Z0dd;Z1dd<Z2dd=Z3dd>Z4d?Z5ddDZ6ddEZ7ddFZ8ddGZ9ddIZ:ddJZ;ddKZ<dLZ=dMZ>ddOZ?dPdQdRdSe=dT e?            dUg dVdWZ@dXdYdRdSdZdTdSg d[d\d]dSd^dTd_d`dTdadbdT e?            dcg dVdWZAdddedRdSe=dTdSdfdTdRdgdTdSdhdTdidjdSidkdldidjdSidmdl e?            dng dVdWZBdodpdRdSe=dTdSdqdTdSg drdsd] e?            dtdugdVdWZCdvdwdRdSe=dTdSdxdT e?            dyg dVdWZDdzd{dRdSd|dTdSd}dT e?            d~ddgdVdWZEdddRdSe=dTdSddTdSddTdSddT e?            dddgdVdWZFdddRdSe=dTdSddTdSddTdSddT e?            dd@gdVdWZGdddRdSe=dT e?            dUg dVdWZHdddRi ddSddTddSddTddSddTddidjdSiddlddSddTddaddTddSg ddd]ddSddTddSddTdd_ddTddSddTddaddTddSddgdd]ddidjdSiddldd_ddTddaddTddSddTdSddT e?            dddgdVdWZIdddRdSddT e?            dUdgdVdWZJdddRdSddTdSddT e?            dddgdVdWZK ejL        dPde@e.edìĦ            ejL        dXdeAe/edìĦ            ejL        dddeBe0edŬĦ            ejL        dodeCe1edƬĦ            ejL        dvdeDe2edǬĦ            ejL        dzdeEe3edȬĦ            ejL        ddeFe4edɬĦ            ejL        ddeGe7edɬĦ            ejL        ddeHe8edɬĦ            ejL        ddeIe9edʬĦ            ejL        ddeJe;edˬĦ            ejL        ddeKe<ed̬Ħ           dS )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      returnboolc                 z    	 ddl m}   |             }|                    dg           }d|v S # t          $ r Y dS w xY w)Nr   )r   toolsetskanbanF)hermes_cli.configr   get	Exception)r   cfgr   s      4/home/ice/.hermes/hermes-agent/tools/kanban_tools.py_profile_has_kanban_toolsetr   4   sd    111111kmm77:r**8##   uus   ), 
::c                 F    	 ddl m}   |             S # t          $ r Y dS w xY w)Nr   is_delegated_child_contextF)agent.delegation_contextr   r   r   s    r   _is_delegated_child_contextr   A   sJ    GGGGGG))+++   uus    
  	tool_namestrOptional[str]c                F    t                      sdS t          |  d          S )ap  Deny Kanban mutations from delegate_task children.

    A delegate_task child runs in the same process as its parent, so stale or
    inherited HERMES_KANBAN_* env vars are not proof of dispatcher ownership.
    The child may summarize findings to its parent, but it must not complete,
    block, heartbeat, comment, create, link, or unblock board tasks directly.
    Nz refused: delegate_task child agents are not Kanban run owners. Return findings to the parent agent; the dispatcher worker or an explicitly configured Kanban orchestrator must perform board mutations.)r   r	   r   s    r    _reject_delegated_child_mutationr#   J   s;     '(( t 	 	 	      c                     t                      rdS t          j                            d          rdS 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.
    FHERMES_KANBAN_TASKTr   osenvironr   r    r$   r   _check_kanban_moder+   \   s?     #$$ u	z~~*++ t&(((r$   c                     t                      rdS t          j                            d          rdS 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.
    Fr&   r'   r*   r$   r   _check_kanban_orchestrator_moder-   o   s?     #$$ u	z~~*++ u&(((r$   argc                p    | r| S t                      rdS t          j                            d          }|pdS )zGResolve ``task_id`` arg or fall back to the env var the dispatcher set.Nr&   )r   r(   r)   r   )r.   env_tids     r   _default_task_idr1      s@    
 
"$$ tjnn122G?dr$   task_idOptional[int]c                    t           j                            d          | k    rdS t           j                            d          }|sdS 	 t          |          S # t          $ r Y dS 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)r2   raws     r   _worker_run_idr9      st    	z~~*++w66t
*../
0
0C t3xx   tts   
A 
A'&A'metadataOptional[dict]c                    t           j                            d          | k    r|S t           j                            d          }|s|S t          |p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)r2   r:   
session_idstampeds       r   _stamp_worker_session_metadatarB      se     
z~~*++w66 344J 8>r""G#-G Nr$   tidc                    t           j                            d          }|sdS | |k    rt          d| d|  d          S dS )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	   )rC   r0   s     r   _enforce_worker_task_ownershiprF      sp    & jnn122G t
g~~@ @ @@ @ @
 
 	

 4r$   boardc                >    ddl m} ||                    |           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rG   )
hermes_clirJ   connect)rG   kbs     r   _connectrO      s/     +*****rzzz&&&&r$   
dependencyneeds_inputc                 v    	 ddl m}   | d          \  }}n# t          $ r Y dS w xY w|duot          |          S )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_clientrS   r   r   )rS   clientmodels      r   _goal_judge_availablerX      sg    DDDDDD11,??   uu-$u++-s    
%%g      N@g        float_auto_heartbeat_last_attemptc                    t           j                            d          } | sdS ddl}|                                }|t
          z
  t          k     rdS |a	 t                      \  }}	 t           j                            d          }	 |                    || |           n,# t          $ r t                              dd	           Y nw xY wt           j                            d
          }	 |rt          |          nd}n# t          t          f$ r d}Y nw xY w	 |                    || d|           n,# t          $ r t                              dd	           Y nw xY w	 |                                 n:# t          $ r Y n.w xY w# 	 |                                 w # t          $ r Y w w xY wxY wdS # t          $ r  t                              dd	           Y dS 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_infor5   noteexpected_run_idz'auto-heartbeat: heartbeat_worker failedzauto-heartbeat: bridge failed)r(   r)   r   time	monotonicrZ   $_AUTO_HEARTBEAT_MIN_INTERVAL_SECONDSrO   heartbeat_claimr   loggerdebugr6   	TypeErrorr7   heartbeat_workerclose)rC   _timenowrN   conn
claim_lock
run_id_rawrun_ids           r   !heartbeat_current_worker_from_envrs     sN   , *..-
.
.C u
//

C**.RRRu#& ::D	(BCCJV""4j"AAAA V V VEPTUUUUUV(>??J,6@ZDz*   W##D#D&#QQQQ W W WFQUVVVVVW

   

   t   4tDDDuus   F1 &F B F &CF C"F +C? >F ?DF DF D3 2F 3&EF EF  E5 4F1 5
F?F1 FF1 F,FF,
F)&F,(F))F,,F1 1&GGfieldsr   c                 2    t          j        ddi|           S )NokT)jsondumps)rt   s    r   _okry   C  s    :tT,V,---r$   valuec                    | dS t          |                                           }|r|                                dv rdS |S )zANormalize CLI-compatible assignee sentinels for the tool surface.N>   -nonenull)r   striplower)rz   texts     r   _normalize_profiler   G  sH    }tu::D 4::<<#888tKr$   Fdefaultargsr?   namer   c                   |                      |          }||d fS t          |t                    r|d fS t          |                                                                          }|dv rdS |dv rdS || dfS )N>   1yestrue)TN>   0nofalse)FNz$ must be a boolean or 'true'/'false')r   
isinstancer   r   r   r   )r   r   r   rz   r   s        r   _parse_bool_argr   Q  s    HHTNNE}}% d{u::##%%D###z###{tAAAAAr$   c                h    t           j                            d          rt          |  d          S dS )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.NrE   r"   s    r   _require_orchestrator_toolr   _  sF     
z~~*++ 
 6 6 6
 
 	

 4r$   dict[str, Any]c                   |                      ||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|j        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provider_overrideparents)childrenparent_countchild_count)
parent_idsr   	child_idsr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   len)rN   ro   taskr   r   s        r   _task_summary_dictr   q  sJ   mmD$'**G||D$'**Hdg 	DM 	$+	
 	DM 	$+ 	$- 	$- 	do 	do 	do 	do 	) 	$- 	$-  	T3!" 	7#$ G8}})   r$   c                   t          |                     d                    }|st          d          S |                     d          }	 t          |          \  }}	 |                    ||          }|'t          d| d          |                                 S |                    ||          }|                    ||          }|                    ||          }	|	                    ||          }
|
                    ||          }d }d	 t          j         ||          |
|d
 |D             d |dd         D             fd|	D             |                    ||          d          |                                 S # |                                 w xY w# t          $ r}t          d|           cY d}~S d}~wt          $ r6}t                               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.r2   :task_id is required (or set HERMES_KANBAN_TASK in the env)rG   rK   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        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   r   r   )ts    r   
_task_dictz _handle_show.<locals>._task_dict  s    !$ '28!&
,4ah ah )3AJ %a&6	
 %a&6 !!, 1=al !!, #AN ah %a&6 %a&6 ()< r$   c           
     t    | j         | j        | j        | j        | j        | j        | j        | j        | j        d	S )N	r   profiler   outcomesummaryerrorr:   r   ended_atr   )rs    r   	_run_dictz_handle_show.<locals>._run_dict  s:    $19h19 y17 !
"#,AJ  r$   c                8    g | ]}|j         |j        |j        d S )authorr   r   r   .0cs     r   
<listcomp>z _handle_show.<locals>.<listcomp>  s?          !x#$<1 1  r$   c                D    g | ]}|j         |j        |j        |j        d S )kindpayloadr   rr   r   )r   es     r   r   z _handle_show.<locals>.<listcomp>  sE         V	#$<18E E  r$   ic                &    g | ]} |          S r*   r*   )r   r   r   s     r   r   z _handle_show.<locals>.<listcomp>  s!    444!1444r$   )r   r   r   commentseventsrunsworker_contextzkanban_show: zkanban_show failed)r1   r   r	   rO   get_taskrl   list_commentslist_events	list_runsr   r   rw   rx   build_worker_contextr7   r   rh   	exception)r   kwrC   rG   rN   ro   r   r   r   r   r   r   r   r   r   s                 @r   _handle_showr     s    488I..
/
/C 
H
 
 	
 HHWEA/%(((D9	;;tS))D|!"9#"9"9"9::l JJLLLLk ''c22H^^D#..F<<c**DmmD#..G||D#..H      :"
4(("$  &  
  $CDD\  
 5444t444
 #%"9"9$"D"D'   , JJLLLLDJJLLLL / / /-!--........ / / /-...-!--......../sU   F  *F 	F  CF 2F  FF   
H*G;HH+G?9H?Hc           
        t          d          }|r|S |                     d          }|                     d          }|                     d          }t          | d          \  }}|rt          |          S |                     d          }|t          }	 t          |          }n&# t          t          f$ r t          d          cY S w xY w|d	k     rt          d
          S |t          k    rt          dt                     S |                     d          }		 t          |	          \  	 
                              }
                    |||||d	z             }t          |          |k    }|d|         }t          j        fd|D             t          |          |||r#|t          k     rt          |dz  t                    nd|
d                                           S #                                  w xY w# t          $ r}t          d|           cY d}~S d}~wt"          $ r6}t$                              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 <= rG   rK   )r   r   r   r   r   c                2    g | ]}t          |          S r*   )r   )r   r   ro   rN   s     r   r   z _handle_list.<locals>.<listcomp>  s&    IIIa,Rq99IIIr$      )taskscountr   	truncated
next_limitpromotedzkanban_list: zkanban_list failed)r   r   r   r	   KANBAN_LIST_DEFAULT_LIMITr6   rj   r7   KANBAN_LIST_MAX_LIMITrO   recompute_ready
list_tasksr   rw   rx   minrl   r   rh   r   )r   r   guardr   r   r   r   
bool_errorr   rG   r   rowsr   r   r   ro   rN   s                  @@r   _handle_listr     s   &}55E xx
##HXXhFXXhF#249K#L#L j &*%%%HHWE})6E

z" 6 6 64555556qyy.///$$$E.CEEFFFHHWE#/%(((D	 ))$//H ==!!1ai !  D D		E)I%LE:IIIII5IIIU& !M%*-B%B%B 	#8999HL$
 
 
 
 JJLLLLDJJLLLL / / /-!--........ / / /-...-!--......../s[   B( ( C
CH /B)G- H -HH 
I*H'!I*'I*4+I%I*%I*c                ~   t          d          }|r|S t          |                     d                    }|st          d          S t	          |          }|r|S |                     d          }|                     d          }|                     d          }|rt          t          |          d          }|rt          t          |          d          }|et          |t                    rPt          j
        |          }t          |d          }	 t          j        |          }n# t          j        $ r Y nw xY w|                     d
          }	|                     d          }
|	dt          |	t                    r|	g}	t          |	t          t          f          s$t          dt          |	          j                   S d |	D             }	|
Vt          |
t                    r|
g}
t          |
t          t          f          s$t          dt          |
          j                   S d |
D             }
|
r|i }n9t          |t                    s$t          dt          |          j                   S |                    d          }t          |t          t          f          r|g }t#                      }t          |          |
z   D ]S}t          |                                          }|r.||vr*|                    |           |                    |           T||d<   n|
|d<   |s|st          d          S |9t          |t                    s$t          dt          |          j                   S t+          ||          }|                     d          }	 t-          |          \  }}	 |                    ||          }|r|j        rt3                      rd}d}	 t5          |j         d|j        pd                                 |p|pd                                          \  }}}}}n4# t:          $ r'}t<                              d|d           Y d	}~nd	}~ww xY w|dk    r*t          d| d| d          |                                  S 	 |!                    ||||||	tE          |                    }n# |j#        $ r1}t          d| d          cY d	}~|                                  S d	}~w|j$        $ rI}t          d d!%                    |j&                   d"          cY d	}~|                                  S d	}~ww xY w|s't          d#| d$          |                                  S |'                    ||          }tQ          ||r|j)        nd	%          |                                  S # |                                  w xY w# tT          $ r}t          d&|           cY d	}~S d	}~wt:          $ r6}t<          +                    d'           t          d&|           cY d	}~S d	}~ww xY w)(z5Mark the current task done with a structured handoff.kanban_completer2   r   r   r:   r   TforceNcreated_cards	artifactsz.created_cards must be a list of task ids, got c                    g | ]D}t          |                                          #t          |                                          ES r*   r   r   r   s     r   r   z$_handle_complete.<locals>.<listcomp>B  sI     
 
 
 SVV\\^^
FFLLNN
 
 
r$   z,artifacts must be a list of file paths, got c                    g | ]D}t          |                                          #t          |                                          ES r*   r   )r   ps     r   r   z$_handle_complete.<locals>.<listcomp>N  sI     
 
 
 Q
FFLLNN
 
 
r$   z%metadata must be an object/dict, got z4provide at least one of: summary (preferred), resultrG   rK   done z

)goallast_responsez0goal judge check failed, allowing completion: %sr_   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   r:   r   rc   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))r2   rr   zkanban_complete: zkanban_complete failed),r#   r1   r   r	   rF   r   r   r   r?   rw   rx   loadsJSONDecodeErrorlisttupletype__name__setr   addappendrB   rO   r   	goal_moderX   r   r   r   r   rh   warningrl   complete_taskr9   ArtifactPreservationErrorHallucinatedCardsErrorjoinphantom
latest_runry   r   r7   r   )r   r   delegated_errrC   ownership_errr   r:   r   	meta_jsonr   r   existingmergedseenitemsrG   rN   ro   r   verdictreason_	judge_excrv   artifact_errhall_errrunr   s                                r   _handle_completer    s   45FGGM 
488I..
/
/C 
H
 
 	
 3377M hhy!!Gxx
##HXXhF B'GDAAA @&s6{{$???
8T : :Jx((	))4@@@		z),,HH# 	 	 	D	HH_--M%%I mS)) 	,*OM-$77 	2&&/2 2  

 
$1
 
 
 i%% 	$"I)dE]33 	.	??+. .  
 
$-
 
 
	  	2$// !1H~~.1 1    ||K00H(T5M22 
2$&!$ NNY6 ) )DD		))A )Qd]]a((((.%%(1% 
v 
B
 
 	
 Jx$>$>MDNN4KMM
 
 	
 .c8<<HHHWEU3%(((DN	 ;;tS))D  +@+B+B   0: $
AA	RAAGGII'.'>&'>B&E&E&G&G0 0 0,GVQ11 !    NNJ!!% #         f$$%5f 5 5 KN5 5 5 b JJLLLLS!%%#!7X"/$23$7$7	 &   /   !X#X X X      B JJLLLL7 ,    "Oyy!122O O O        JJLLLL7(  !P#PPP  JJLLLL --c**CsS+B366dCCCJJLLLLDJJLLLL 3 3 31a1122222222 3 3 312221a11222222223s   D+ +D=<D=W 11V? #AP4 3V? 4
Q%>Q V?  Q%%V? W )S V? 
US9U V? $W 9U*U
0U1V? 5W 
UV? &W ;/V? *W ?WW 
X<"W93X<9X<+X71X<7X<c           	        t          d          }|r|S t          |                     d                    }|st          d          S t	          |          }|r|S |                     d          }|r!t          |                                          st          d          S t          t          |          d          }|                     d          }|                     d	          }	 t          |
          \  }}	|B||j	        vr9|	
                                 t          dt          |j	                   d          S |                    |	|          }
|
rL|
j        rE|t          vr<|	
                                 t          dt          t                     d|d          S 	 |                    |	|||t!          |                    }|s't          d| d          |	
                                 S |                    |	|          }|                    |	|          }t%          ||r|j        nd|r|j        nd|          |	
                                 S # |	
                                 w xY w# t*          $ r}t          d|           cY d}~S d}~wt,          $ r6}t.                              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.kanban_blockr2   r   r  u2   reason is required — explain what input you needTr   r   rG   rK   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   rc   zcould not block z% (unknown id or not in running/ready)blocked)r2   rr   r   
block_kindzkanban_block: zkanban_block failed)r#   r1   r   r	   rF   r   r   r   rO   VALID_BLOCK_KINDSrl   sortedr   r  _GOAL_MODE_BLOCK_ALLOWED_KINDS
block_taskr9   r  ry   r   r   r7   r   rh   r   )r   r   r  rC   r  r  r   rG   rN   ro   r   rv   r  landedr   s                  r   _handle_blockr%    sZ   4^DDM 
488I..
/
/C 
H
 
 	
 3377M XXhF PV**,, PNOOO"3v;;d;;;F88FDHHWE;0%(((DB,@ @ @JJLLLRvb.B'C'CRRR   {{4%%		 :::JJLLL68996 6AE6 6 6  	c .s 3 3	   B  !&s & & &  JJLLLL --c**C [[s++F!$.svv$(.=v}}I	   JJLLLLDJJLLLL 0 0 0.1..//////// 0 0 0.///.1..////////0s]   *AI4 A#I4 &;I !I4 6AI I4 I11I4 4
K>JKK"+KKKc                   t          d          }|r|S t          |                     d                    }|st          d          S t	          |          }|r|S |                     d          }|                     d          }	 t          |          \  }}	 t          j                            d          }	|                    |||	           |	                    |||t          |          	          }
|
s't          d
| d          |                                 S t          |          |                                 S # |                                 w xY w# t          $ r}t          d|           cY d}~S d}~wt          $ r6}t                              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.
    kanban_heartbeatr2   r   rb   rG   rK   r\   r]   ra   zcould not heartbeat z (unknown id or not running))r2   zkanban_heartbeat: Nzkanban_heartbeat failed)r#   r1   r   r	   rF   rO   r(   r)   rg   rk   r9   rl   ry   r7   r   rh   r   )r   r   r  rC   r  rb   rG   rN   ro   rp   rv   r   s               r   _handle_heartbeatr(    s    55GHHM 
488I..
/
/C 
H
 
 	
 3377M 88FDHHWE4%(((D	 (BCCJtS*===$$ .s 3 3	 %  B  !L3LLL 
 JJLLLL s###JJLLLLDJJLLLL 4 4 42q2233333333 4 4 423332q22333333334sU   E A1E 
E E .E EE 
G &E=7G =G 
+F;5G ;G c                j   t          d          }|r|S |                     d          }|st          d          S |                     d          }|r!t          |                                          st          d          S t          t          |          d          }t          j                            d          pd	}|                     d
          }	 t          |          \  }}	 |	                    |||t          |                    }	t          ||	          |                                 S # |                                 w xY w# t          $ r}
t          d|
           cY d}
~
S d}
~
wt          $ r6}
t                              d           t          d|
           cY d}
~
S d}
~
ww xY w)z$Append a comment to a task's thread.kanban_commentr2   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workerrG   rK   )r   r   )r2   
comment_idzkanban_comment: Nzkanban_comment failed)r#   r   r	   r   r   r   r(   r)   rO   add_commentry   rl   r7   r   rh   r   )r   r   r  rC   r   r   rG   rN   ro   cidr   s              r   _handle_commentr0  U  s   45EFFM 
((9

C 
B
 
 	
 88FD .s4yy(( .,--- T$777D Z^^,--9FHHWE2%(((D	..s6D		.JJCss333JJLLLLDJJLLLL 2 2 20Q0011111111 2 2 201110Q00111111112sH   E *6D5  E 5EE 
F2E/)F2/F2<+F-'F2-F2c           	        ddl m} t          d          }|r|S t          |                     d                    }|st          d          S t          |          }|r|S |                     d          }|r!t          |                                          st          d          S |                     d          }|r!t          |                                          st          d	          S dd
l	}dd
l
}		 |                    t          |          d          }
n0# |	j        t          f$ r}t          d|           cY d
}~S d
}~ww xY w|                     d          }|                     d          }	 t          |          \  }}	 |                    ||t          |          |
|d|          }t!          ||t#          |
                    |                                 S # |                                 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(          $ r6}t*                              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   rI   kanban_attachr2   r   filenamezfilename is requiredcontent_base64zcontent_base64 is requiredNT)validatez$content_base64 is not valid base64: content_typerG   rK   agentr6  uploaded_byrG   r2   attachment_idsizezkanban_attach: zkanban_attach failed)rL   rJ   r#   r1   r   r	   rF   r   r   base64binascii	b64decodeErrorr7   rO   store_attachment_bytesry   r   rl   AttachmentTooLarger   rh   r   )r   r   rN   r  rC   r  r3  content_b64r=  r>  datar   r6  rG   r  ro   att_ids                    r   _handle_attachrF  }  s    +*****4_EEM 
488I..
/
/C 
H
 
 	
 3377M xx
##H 23x==..00 20111((+,,K 8c+..4466 86777MMMOOOFK 0 04@@NJ' F F FDDDEEEEEEEEF88N++LHHWE1'''4	..H)# /  F s&s4yyIIIJJLLLLDJJLLLL  1 1 1/A//00000000 1 1 1/A//00000000 1 1 1/000/A//000000001sy   7$D E	-E>E	E	7H  AG' H  'G==H   
J
H!J!J.I?JJ+J=JJ   url	max_bytesr6   tuple[bytes, Optional[str]]c                   ddl m}m} ddl}ddlm} | }t          t          dz             D ]} ||          j        pd	                                }|dvrt          d|d	           ||          st          d
|           g }	d}
|                    d|ddidd          5 }|j        rG|j                            d          }|st          d|            |||          }	 ddd           |                                 |j                            d          pd                    d          d                                         pd}|                    d          D ]E}|
t'          |          z  }
|
|k    rt          d|dz   d          |	                    |           F	 ddd           n# 1 swxY w Y   d                    |	          |fc S t          d|            )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.parserL  rM  httpxtools.url_safetyrN  range_MAX_ATTACH_URL_REDIRECTSschemer   r7   streamis_redirectrS  r   raise_for_statussplitr   
iter_bytesr   r  r	  )rH  rI  rL  rM  rY  rN  current_urlr  r]  chunkstotalresprV  r6  chunks                  r   _download_url_with_caprh    s     /.......LLL,,,,,,K,q011 ". ".(;''.4";;==***Q&QQQ   {;'' 	[k[[   !\\!#9:"  
 
 	%  <++J77 ]$%[k%[%[\\\%gk8<<	% 	% 	% 	% 	% 	% 	% !!### L,,^<<BII#NNqQWWYYa]aL55 % %U#9$$$SiK.HSSS   e$$$$%	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	%, xx----
9C99
:
::s   'AF;6B8F;;F?	F?	c           	     V   ddl m} t          d          }|r|S t          |                     d                    }|st          d          S t          |          }|r|S |                     d          }|r!t          |                                          st          d          S t          |                                          }|                     d          p|                     d	          }|r!t          |                                          sQdd
l	m
}m}	  | |	|          j                            dd          d                                                   }
|
pd}|                     d          }|                     d          }	 t          ||j                  \  }}nj# t           $ r}t          d|           cY d}~S d}~wt"          $ r9}t$                              d           t          d| d|           cY d}~S d}~ww xY w	 t)          |          \  }}	 |                    ||t          |          ||p|d|          }t-          ||t/          |                    |                                 S # |                                 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"          $ r6}t$                              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   rI   kanban_attach_urlr2   r   rH  zurl is requiredr3  r   )unquoterM  /r   downloadr6  rG   zkanban_attach_url: Nz!kanban_attach_url download failedz#kanban_attach_url: failed to fetch z: rK   r7  r8  r:  zkanban_attach_url failed)rL   rJ   r#   r1   r   r	   rF   r   r   rX  rk  rM  pathrsplitrh  KANBAN_ATTACHMENT_MAX_BYTESr7   r   rh   r   rO   rA  ry   r   rl   rB  )r   r   rN   r  rC   r  rH  r3  rk  rM  leafr6  rG   rD  
fetched_ctr   r  ro   rE  s                      r   _handle_attach_urlrt    s    +*****45HIIM 
488I..
/
/C 
H
 
 	
 3377M 
((5//C -c#hhnn&& -+,,,
c((..

Cxx
##8txx'8'8H &3x==..00 &22222222wxx}})00a88<==CCEE%:88N++LHHWEL1#r7UVVjj 5 5 533344444444 L L L<===JJJqJJKKKKKKKKL5'''4	..H)7Z# /  F s&s4yyIIIJJLLLLDJJLLLL  5 5 533344444444 5 5 533344444444 5 5 53444333444444445s   F* *
H4GHH.HHHJ  )A	J 2J  JJ   
L(*K;L(L(K%L(%L(2+L#L(#L(c                   t          |                     d                    }|st          d          S |                     d          }	 t          |          \  }}	 |                    ||          't          d| d          |                                 S |                    ||          }t          j        d|d	 |D             d
          |                                 S # |                                 w xY w# t          $ r}t          d|           cY d}~S d}~wt          $ r6}t                              d           t          d|           cY d}~S d}~ww xY w)z@List a task's attachments (read-only; no ownership restriction).r2   r   rG   rK   Nr   r   Tc           
     h    g | ]/}|j         |j        |j        |j        |j        |j        |j        d 0S )r   r3  r6  r<  r9  stored_pathr   rw  )r   as     r   r   z'_handle_attachments.<locals>.<listcomp>H  sT             d$%J() !'(}'(}&'l      r$   )rv   r2   attachmentszkanban_attachments: zkanban_attachments failed)r1   r   r	   rO   r   rl   list_attachmentsrw   rx   r7   r   rh   r   )r   r   rC   rG   rN   ro   attsr   s           r   _handle_attachmentsr}  7  s   
488I..
/
/C 
H
 
 	
 HHWE6%(((D	{{4%%-!"9#"9"9"9::& JJLLLL% &&tS11D:    "        " JJLLLLDJJLLLL 6 6 644455555555 6 6 64555444555555556sT   
D  (C' D  7C' D  'C==D   
E$
D!E$!E$.+EE$E$c                
   t          d          }|r|S |                     d          }|r!t          |                                          st	          d          S |                     d          }|st	          d          S |                     d          }|                     d          pg }|                     d          pt
          j                            d	          }d
dlm} |                     d          p( |            pt
          j                            d          }	|                     d          }
|                     d          }|                     d          }|                     d          p|                     d          }d}|du o|du }|d}t          | d          \  }}|rt	          |          S |                     d          }|                     d          }|                     d          pd}|                     d          }t          |t                    r|g}|@t          |t          t          f          s$t	          dt          |          j                   S t          | d          \  }}|rt	          |          S |                     d          }|                     d          }|                     d          }|r|st	          d           S t          |t                    r|g}t          |t          t          f          s$t	          d!t          |          j                   S |                     d"          }	 t          |#          \  }}	 |rP|Nt
          j                            d$          }|r-|                    ||          }||j        r|j        }|j        }|                    |t          |                                          |t          |          t          |          ||
t)          |
          nd
t          |          ||||||t)          |          nd|||||t)          |          ndt          |          t
          j                            d%          pd&|	'          } |                    ||           }!t+          ||           }"t-          | |!r|!j        nd|!r|!j        nd|!r|!j        nd|!r|!j        nd|"(          |                                 S # |                                 w xY w# t6          $ r}#t	          d)|#           cY d}#~#S d}#~#wt8          $ r6}#t:                              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.
    kanban_creater   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_TENANTr   )_current_origin_session_idr@   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_turnsrW   providerz-'provider' requires 'model' to be set as wellz(parents must be a list of task ids, got rG   rK   r&   r+  r,  )r   r   r   r   r   r   r   r   r   project_source_task_idr  r  r  r  r   r   r  r  r  r   r@   )r2   r   r   r   r   
subscribedzkanban_create: zkanban_create failed)r#   r   r   r   r	   r(   r)   tools.async_delegationr  r   r   r   r   r   r   rO   r   r   r   create_taskr6   _maybe_auto_subscribery   r   r   r   rl   r7   r   rh   r   )$r   r   r  r   r   r   r   r   r  r@   r   r   r   r   r  _inherit_projectr  r   r  r  r  r  r  goal_bool_errorr  r   r   rG   rN   ro   	_self_tid
_self_tasknew_tidnew_taskr  r   s$                                       r   _handle_creater  ^  s    5_EEM HHWE /E

((** /-...xx
##H 
K
 
 	
 88FDhhy!!'RGXXhB2:>>/#B#BF BAAAAA 	 	/%%''	/:>>-.. 
 xx
##H XX.//NXX.//N)$$>(>(>J!%-H.D2H"(x88FJ &*%%%hh011O((#899XX.//<9NXXhF&# *VdE]"C"CPf9NPP
 
 	
 "1{!C!CI +/***XX.//NXXg&&N,, K KIJJJ'3 )ge}-- 
OtG}}7MOO
 
 	
 HHWE:1%(((D3	   ?J$6JNN+?@@	 ?!#T9!=!=J!-*2G-%/%:
1;.nn%jj&&((Xg*2*>XA">22-%'= / +6 +,,,<@-"3#+9+EC'''4">22:>>*:;;Gx%7 %  G: {{411H.tW==J*2<x:BLx66:BLx662:D8..%   JJLLLLDJJLLLL 1 1 1/A//00000000 1 1 1/000/A//000000001sI   T (F S= (T =TT 
U: T71U:7U:+U5/U:5U:ro   c           	     |   	 t                      }t          |ddd          sdS n# t          $ r Y nw xY wd}d}	 ddlm}  |d	d          } |d
d          }|r|s4 |dd          pt
          j                            dd          }|sdS d}|} |dd          pd} |dd          pd} |dd          pt
          j                            d          }	ddlm	}
 |

                    | ||||||	           dS # t          $ r5}t                              d||t          |                     Y d}~dS 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_createTr   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+  rI   )r2   platformchat_id	thread_iduser_idnotifier_profilez9_maybe_auto_subscribe failed: %r (platform=%r key_set=%r))r   r
   r   gateway.session_contextr  r(   r)   r   rL   rJ   add_notify_subrh   r  r   )ro   r2   r   r  r  r  session_keyr  r  r  _kb_excs               r   r  r    s   LmmsH&@$OOO 	5	    	
 HG/;;;;;;"?#<bAA!/":B?? 	"w 	"   4b99 <:>>"6;;   uH!G#O$>CCKt	!/":B??G4O4b99 0z~~.// 	 	0/////'w-	 	 	
 	
 	
 t   G(DMM	
 	
 	
 uuuuus+   !& 
33AC< A-C< <
D;*D66D;c                   t          d          }|r|S t          d          }|r|S |                     d          }|st          d          S t	          t          |                    }|r|S |                     d          }	 t          |          \  }}	 |                    |t          |                    }	|	s't          d| d          |                                 S |	                    |t          |                    }
t          t          |          |
r|
j        nd	          |                                 S # |                                 w xY w# t          $ r}t          d
|           cY d}~S d}~wt          $ r6}t                              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_unblockr2   ztask_id is requiredrG   rK   zcould not unblock z (not blocked or unknown)N)r2   r   zkanban_unblock: zkanban_unblock failed)r#   r   r   r	   rF   r   rO   unblock_taskrl   r   ry   r   r7   r   rh   r   )r   r   r  r   rC   r  rG   rN   ro   rv   r   r   s               r   _handle_unblockr  N  s   45EFFM &'788E 
((9

C 1/00023s88<<M HHWE2%(((D	s3xx00B W!"Us"U"U"UVV JJLLLL ;;tSXX..Ds3xxt0MNNNJJLLLLDJJLLLL 2 2 20Q0011111111 2 2 201110Q00111111112sU   E 7E E #A	E ,E EE 
F>$E;5F>;F>+F93F>9F>c                p   t          d          }|r|S |                     d          }|                     d          }|r|st          d          S |                     d          }	 t          |          \  }}	 |                    |||           t          ||          |                                 S # |                                 w xY w# t          $ r}t          d|           cY d	}~S d	}~wt          $ r6}t          
                    d
           t          d|           cY d	}~S d	}~ww xY w)u4   Add a parent→child dependency edge after the fact.kanban_link	parent_idchild_idz(both parent_id and child_id are requiredrG   rK   )r  r  zkanban_link: Nzkanban_link failed)r#   r   r	   rO   
link_tasksry   rl   r7   r   rh   r   )	r   r   r  r  r  rG   rN   ro   r   s	            r   _handle_linkr  n  sm   4]CCM %%Ixx
##H FH FDEEEHHWE/%(((D	MM$)hMGGGX>>>JJLLLLDJJLLLL / / /-!--........ / / /-...-!--......../sH   'C ;(B8 #C 8CC 
D5C2,D52D5?+D0*D50D5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.dict[str, str]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    s     [9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  )r2   rG   )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readyr  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   rG   r   uh  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.)r2   r   r:   r   r   r   rG   r  uq  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.)rP   rQ   
capability	transientzWhy you're blocked. 'dependency' waits in todo and resumes automatically; the others surface to a human. Omit only if none apply.)r2   r  r   rG   r  r'  u   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.)r2   rb   rG   r*  u   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.)r2   r   rG   r   r2  u]  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').)r2   r3  r4  r6  rG   r3  r4  rj  u   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.)r2   rH  r3  r6  rG   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.r  uc  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'.r  r  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.r  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).r  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).rW   zPin the dispatched worker to this model instead of the assignee profile's configured model. Use the exact model name the target provider expects. Omit to use the profile default.u   Provider the 'model' belongs to (e.g. 'openrouter', 'anthropic', 'nous'). Set this whenever the model is not from the assignee profile's configured provider — a model name alone is resolved against the profile's provider and will fail if it belongs to a different one. Requires 'model'.)r  rG   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.r  u   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  rG   r  r  r   u   📋)r   toolsetschemahandlercheck_fnemojiu   ✔u   ⏸u   💓u   💬u   📎u   ➕u   ▶u   🔗)r   r   )r   r   r   r    )r.   r    r   r    )r2   r   r   r3   )r2   r   r:   r;   r   r;   )rC   r   r   r    )N)rG   r    )rt   r   r   r   )rz   r   r   r    )r   r?   r   r   r   r   )r   r   )r   r?   r   r   )rH  r   rI  r6   r   rJ  )ro   r   r2   r   r   r   )r   r  )M__doc__
__future__r   rw   loggingr(   typingr   r   agent.redactr   hermes_cli.goalsr   tools.registryr   r	   r   r
   r   	getLoggerr   rh   r   r   r   r   r#   r+   r-   r1   r9   rB   rF   rO   	frozensetr"  rX   rf   rZ   __annotations__rs   ry   r   r   r   r   r   r   r  r%  r(  r0  rF  r\  rh  rt  r}  r  r  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     s1    8 # " " " " "   				                 . . . . . . ' ' ' ' ' ' / / / / / / / / 2 2 2 2 2 2 2 2		8	$	$   
 
 
 
      $) ) ) )&) ) ) )(   
 
 
 
      @' ' ' ' ' "+L-+H!I!I . . . .T (, $&)  ) ) ) )8 8 8 8v. . . .    ?D B B B B B B   $   @J/ J/ J/ J/Z:/ :/ :/ :/zp3 p3 p3 p3fN0 N0 N0 N0b34 34 34 34l%2 %2 %2 %2P81 81 81 81v  :; :; :; :;z<5 <5 <5 <5~$6 $6 $6 $6NJ1 J1 J1 J1Z` ` ` `F2 2 2 2@/ / / /<8  : : : : 	&  !4  ('))
 
 
 
  0 	  !B 
 !    >  !J 
 "K! !
 "W  ('))3
 
6 ; + + \ 	>"  !4 
 !(  !5  !B    (+	0	 "   (+H	 * ('))IE
 E
L QI I'] ] @ 		4  !4 
 !O  !PPP/	  ('))-
 
0 J5 * * Z 	&  !4 
 !.  ('))
 
 !   : 	<  !C  !A  ('))
 
 '!   : 	6  !4 
 !P  !V 
 !M  (')))
 
,  !121 $ $ N  	  !4 
 !@ 
 !-  !7  ('))/
 
2 G7 % % P !	M  !4  ('))
 
 
 
  ( 	C c
 = c

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

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