
    i3e                       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
mZ  ej        e          ZdadZdbdZd ZdcdZdddZdddZdddZdddZdddZdddZdddZdZddddded ig d!d"Zd#d$dded dd%d dd&d dd'd d(g d!d"Zd)d*dded dd+d d,d-gd!d"Zd.d/dded dd0d d1g d!d"Zd2d3ddd4d dd5d dd6d d7dd8gd!d"Zd9d:ddd;d dd<d dd=d d>d?did@dAddBd dCdDd dg dEdFdGddHd dIdJd ddKd dCdLd d>d?didMdAdNdOdPgd!d"Z dQdRdddSd ddTd dUdVdWgd!d"Z! e
j"        ddXeeedYZ            e
j"        d#dXeeed[Z            e
j"        d)dXeeed\Z            e
j"        d.dXeeed]Z            e
j"        d2dXeeed^Z            e
j"        d9dXe eed_Z            e
j"        dQdXe!eed`Z           dS )eu  Kanban tools — structured tool-call surface for worker + orchestrator agents.

These tools are only registered into the model's schema when the agent is
running under the dispatcher (env var ``HERMES_KANBAN_TASK`` set). A
normal ``hermes chat`` session sees **zero** kanban tools in its schema.

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 ONLY for the worker
agent's handoff back to the kernel.
    )annotationsN)AnyOptional)registry
tool_errorreturnboolc                 Z    t          t          j                            d                    S )a&  Tools are available iff the current process has ``HERMES_KANBAN_TASK``
    set in its env, which the dispatcher sets when spawning a worker.

    Humans running ``hermes chat`` see zero kanban tools. Workers spawned
    by the kanban dispatcher (gateway-embedded by default) see all seven.
    HERMES_KANBAN_TASK)r	   osenvironget     7/home/ubuntu/.hermes/hermes-agent/tools/kanban_tools.py_check_kanban_moder   *   s!     
344555r   argOptional[str]c                P    | r| S t           j                            d          }|pdS )zGResolve ``task_id`` arg or fall back to the env var the dispatcher set.r   N)r   r   r   )r   env_tids     r   _default_task_idr   8   s-    
 
jnn122G?dr   c                 :    ddl m}  | |                                 fS )zImport + connect lazily so the module imports cleanly in non-kanban
    contexts (e.g. test rigs that import every tool module).r   )	kanban_db)
hermes_clir   connect)kbs    r   _connectr   @   s)     +*****rzz||r   fieldsr   strc                 2    t          j        ddi|           S )NokT)jsondumps)r   s    r   _okr$   G   s    :tT,V,---r   argsdictc                   t          |                     d                    }|st          d          S 	 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          $ 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.task_id:task_id is required (or set HERMES_KANBAN_TASK in the env)Nztask z
 not foundc                    | j         | j        | j        | j        | j        | j        | j        | j        | j        | j	        | j
        | j        | j        | j        | j        dS )Nidtitlebodyassigneestatustenantpriorityworkspace_kindworkspace_path
created_by
created_at
started_atcompleted_atresultcurrent_run_idr+   )ts    r   
_task_dictz _handle_show.<locals>._task_dictc   sZ    $!& !
ahhAJ&'&6&'&6"#,al"#,$%Nh&'&6  r   c           
     t    | j         | j        | j        | j        | j        | j        | j        | j        | j        d	S )N	r,   profiler0   outcomesummaryerrormetadatar7   ended_atr>   )rs    r   	_run_dictz_handle_show.<locals>._run_dictq   s:    $19h19 y17 !
"#,AJ  r   c                8    g | ]}|j         |j        |j        d S )authorr.   r6   rH   ).0cs     r   
<listcomp>z _handle_show.<locals>.<listcomp>~   s?          !x#$<1 1  r   c                D    g | ]}|j         |j        |j        |j        d S )kindpayloadr6   run_idrN   )rJ   es     r   rL   z _handle_show.<locals>.<listcomp>   sE         V	#$<18E E  r   ic                &    g | ]} |          S r   r   )rJ   rE   rF   s     r   rL   z _handle_show.<locals>.<listcomp>   s!    444!1444r   )taskparentschildrencommentseventsrunsworker_contextzkanban_show failedzkanban_show: )r   r   r   r   get_taskcloselist_commentslist_events	list_runs
parent_ids	child_idsr"   r#   build_worker_context	Exceptionlogger	exception)r%   kwtidr   connrT   rW   rX   rY   rU   rV   r<   rR   rF   s                @r   _handle_showri   O   s2    488I..
/
/C 
H
 
 	
</::D7	;;tS))D|!"9#"9"9"9::h JJLLLLg ''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 / / /-...-!--......../sB   F	 *E0 2F	 CE0 F	 0FF	 	
G	+G>G	G	c                n   t          |                     d                    }|st          d          S |                     d          }|                     d          }|                     d          }|s|st          d          S |9t          |t                    s$t          dt          |          j                   S 	 t                      \  }}	 |                    |||||	          }|s't          d
| d          |	                                 S |
                    ||          }	t          ||	r|	j        nd          |	                                 S # |	                                 w xY 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.r(   r)   rA   rC   r9   z4provide at least one of: summary (preferred), resultNz%metadata must be an object/dict, got )r9   rA   rC   zcould not complete z! (unknown id or already terminal)r(   rQ   zkanban_complete failedzkanban_complete: )r   r   r   
isinstancer&   type__name__r   complete_taskr\   
latest_runr$   r,   rc   rd   re   )r%   rf   rg   rA   rC   r9   r   rh   r!   runrR   s              r   _handle_completerr      s   
488I..
/
/C 
H
 
 	
 hhy!!Gxx
##HXXhF 
v 
B
 
 	
 Jx$>$>MDNN4KMM
 
 	
3::D	!!cw "  B  !P#PPP  JJLLLL --c**CsS+B366dCCCJJLLLLDJJLLLL 3 3 312221a11222222223sB   E4 .E E4 /E E4 E11E4 4
F4>+F/)F4/F4c                   t          |                     d                    }|st          d          S |                     d          }|r!t          |                                          st          d          S 	 t                      \  }}	 |                    |||          }|s't          d| d          |                                 S |                    ||          }t          ||r|j
        nd	          |                                 S # |                                 w xY 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.r(   r)   reasonu2   reason is required — explain what input you need)rt   zcould not block z% (unknown id or not in running/ready)Nrk   zkanban_block failedzkanban_block: )r   r   r   r   stripr   
block_taskr\   rp   r$   r,   rc   rd   re   )	r%   rf   rg   rt   r   rh   r!   rq   rR   s	            r   _handle_blockrw      s   
488I..
/
/C 
H
 
 	
 XXhF PV**,, PNOOO0::D
	tS88B !&s & & &  JJLLLL --c**CsS+B366dCCCJJLLLLDJJLLLL 0 0 0.///.1..////////0sB   <D, ,D :D, /D >D, D))D, ,
E,6+E'!E,'E,c                :   t          |                     d                    }|st          d          S |                     d          }	 t                      \  }}	 |                    |||          }|s't          d| d          |                                 S t          |          |                                 S # |                                 w xY w# t          $ r6}t          	                    d           t          d	|           cY d
}~S d
}~ww xY w)z>Signal that the worker is still alive during a long operation.r(   r)   note)ry   zcould not heartbeat z (unknown id or not running))r(   zkanban_heartbeat failedzkanban_heartbeat: N)
r   r   r   r   heartbeat_workerr\   r$   rc   rd   re   )r%   rf   rg   ry   r   rh   r!   rR   s           r   _handle_heartbeatr{      s;   
488I..
/
/C 
H
 
 	
 88FD4::D	$$T3T$::B !L3LLL 
 JJLLLL s###JJLLLLDJJLLLL 4 4 423332q22333333334sB   
C ,C C C ,C CC 
D$+DDDc                   |                      d          }|st          d          S |                      d          }|r!t          |                                          st          d          S |                      d          p t          j                             d          pd}	 t                      \  }}	 |                    |||t          |                    }t          ||	          |	                                 S # |	                                 w xY 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.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 requiredrI   HERMES_PROFILEworker)rI   r.   )r(   
comment_idzkanban_comment failedzkanban_comment: N)r   r   r   ru   r   r   r   add_commentr$   r\   rc   rd   re   )	r%   rf   rg   r.   rI   r   rh   cidrR   s	            r   _handle_commentr      s_   
((9

C 
B
 
 	
 88FD .s4yy(( .,---XXhO2:>>2B#C#COxF	2::D	..s6D		.JJCss333JJLLLLDJJLLLL 2 2 201110Q00111111112s6   %D 76D -D DD 
E%+EEEc                   |                      d          }|r!t          |                                          st          d          S |                      d          }|st          d          S |                      d          }|                      d          pg }|                      d          pt          j                             d          }|                      d	          }|                      d
          pd}|                      d          }	t          |                      d                    }
|                      d          }|                      d          }|                      d          }t          |t                    r|g}|@t          |t          t          f          s$t          dt          |          j                   S t          |t                    r|g}t          |t          t          f          s$t          dt          |          j                   S 	 t                      \  }}	 |                    |t          |                                          |t          |          t          |          ||t          |          ndt          |          |	|
||t          |          nd|t          j                             d          pd          }|                    ||          }t!          ||r|j        nd          |                                 S # |                                 w xY w# t&          $ 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.
    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.   rU   r1   HERMES_TENANTr2   r3   scratchr4   triageidempotency_keymax_runtime_secondsskillsNz*skills must be a list of skill names, got z(parents must be a list of task ids, got r   r}   r~   )r-   r.   r/   rU   r1   r2   r3   r4   r   r   r   r   r5   )r(   r0   zkanban_create failedzkanban_create: )r   r   ru   r   r   r   r	   rl   listtuplerm   rn   r   create_taskintr[   r$   r0   r\   rc   rd   re   )r%   rf   r-   r/   r.   rU   r1   r2   r3   r4   r   r   r   r   r   rh   new_tidnew_taskrR   s                      r   _handle_creater     sk    HHWE /E

((** /-...xx
##H 
K
 
 	
 88FDhhy!!'RGXXhB2:>>/#B#BFxx
##HXX.//<9NXX.//N$((8$$%%Fhh011O((#899XXhF&# *VdE]"C"CPf9NPP
 
 	
 '3 )ge}-- 
OtG}}7MOO
 
 	
1::D	nn%jj&&((Xg*2*>XA">22- / +6 +,,,<@:>>*:;;Gx# %  G& {{411H*2<x  
 JJLLLLDJJLLLL 1 1 1/000/A//000000001s7   #L< 5CL# L< #L99L< <
M<+M71M<7M<c                   |                      d          }|                      d          }|r|st          d          S 	 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.	parent_idchild_idz(both parent_id and child_id are requiredr   r   zkanban_link: Nzkanban_link failed)
r   r   r   
link_tasksr$   r\   
ValueErrorrc   rd   re   )r%   rf   r   r   r   rh   rR   s          r   _handle_linkr   P  s:   %%Ixx
##H FH FDEEE/::D	MM$)hMGGGX>>>JJLLLLDJJLLLL / / /-!--........ / / /-...-!--......../sG   B' (B 9B' B$$B' '
D1CDD+D DDzrTask id. If omitted, defaults to HERMES_KANBAN_TASK from the env (the task the dispatcher spawned you to work on).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(   string)rm   description)rm   
propertiesrequired)namer   
parameterskanban_completeaB  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.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.)r(   rA   rC   r9   kanban_blocku   Transition the task to blocked because you need human input to proceed. ``reason`` will be shown to the human on the board and included in context when someone unblocks you. Use for genuine blockers only — don't block on things you can resolve yourself.zWhat you need answered, in one or two sentences. Don't paste the whole conversation; the human has the board and can ask follow-ups via comments.)r(   rt   rt   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(   ry   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.zKOverride author name. Defaults to the current profile (HERMES_PROFILE env).)r(   r.   rI   r.   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.zShort task title (required).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.arrayrm   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.)rm   itemsr   zUOptional namespace for multi-project isolation. Defaults to HERMES_TENANT env if set.integerzZDispatcher tiebreaker. Higher = picked sooner when multiple ready tasks share an assignee.)r   dirworktreezWorkspace flavor: 'scratch' (fresh tmp dir, default), 'dir' (shared directory, requires absolute workspace_path), 'worktree' (git worktree).)rm   enumr   zYAbsolute path for 'dir' or 'worktree' workspace. Relative paths are rejected at dispatch.booleanu   If true, task lands in 'triage' instead of 'todo' — a specifier profile is expected to flesh out the body before work starts.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.zxPer-task runtime cap. When exceeded, the dispatcher SIGTERMs the worker and re-queues the task with outcome='timed_out'.u4  Skill names to force-load into the dispatched worker (in addition to the built-in kanban-worker skill). 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-   r/   r.   rU   r1   r2   r3   r4   r   r   r   r   r-   r/   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   r   kanbanu   📋)r   toolsetschemahandlercheck_fnemojiu   ✔u   ⏸u   💓u   💬u   ➕u   🔗)r   r	   )r   r   r   r   )r   r   r   r   )r%   r&   r   r   )#__doc__
__future__r   r"   loggingr   typingr   r   tools.registryr   r   	getLoggerrn   rd   r   r   r   r$   ri   rr   rw   r{   r   r   r   _DESC_TASK_ID_DEFAULTKANBAN_SHOW_SCHEMAKANBAN_COMPLETE_SCHEMAKANBAN_BLOCK_SCHEMAKANBAN_HEARTBEAT_SCHEMAKANBAN_COMMENT_SCHEMAKANBAN_CREATE_SCHEMAKANBAN_LINK_SCHEMAregisterr   r   r   <module>r      s`   2 # " " " " "   				                 / / / / / / / /		8	$	$6 6 6 6     . . . .D/ D/ D/ D/N#3 #3 #3 #3L0 0 0 084 4 4 402 2 2 20E1 E1 E1 E1P/ / / /48  	&  4 
 	 	  . 	1  !4 
 !(  !5  !B -
 
@ E# #. . b 	  !4 
 !E 
 
 J!   < 	&  !4 
 !. 
 
    8 	<  !C  !A 
 !4 
 
( '-     F 	C  != 
 !"  !#    (+(	
 
 !<  "C  !666K	  !?  "3  !E    "5$ $   (+*	 od
 d
J j)Oh hs s l 	:
 "*;LMM"*;KLL
 
 !*-   ,  	
     	!
     	
     	"
     	 
     	
     	
     r   