
    i}                       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dfdZdgdZdhdZdidZd ZdjdZdkdZdkdZdkdZdkdZdkdZdkdZdkd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,d-d#id.d/d0g d%d&Zd1d2d"d#ed$d#d3d$d4d5gd%d&Zd6d7d"d#ed$d#d8d$d9g d%d&Z d:d;d"d#d<d$d#d=d$d#d>d$d?dd@gd%d&Z!dAdBd"d#dCd$d#dDd$d#dEd$d,d-d#idFd/d#dGd$dHdId$d#g dJdKdLd#dMd$dNdOd$d#dPd$dHdQd$d,d-d#idRd/dSdTdUgd%d&Z"dVdWd"d#dXd$d#dYd$dZd[d\gd%d&Z# e
j$        d d]eeed^_            e
j$        d'd]eeed`_            e
j$        d1d]eeeda_            e
j$        d6d]e eedb_            e
j$        d:d]e!eedc_            e
j$        dAd]e"eedd_            e
j$        dVd]e#eede_           dS )lu  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                     t           j                            d          rdS 	 ddlm}   |             }|                    dg           }d|v S # t
          $ r Y dS w xY w)a  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 all seven.
    HERMES_KANBAN_TASKTr   )load_configtoolsetskanbanF)osenvirongethermes_cli.configr   	Exception)r   cfgr   s      7/home/piyush/.hermes/hermes-agent/tools/kanban_tools.py_check_kanban_moder   *   s     
z~~*++ t111111kmm77:r**8##   uus   )A 
AA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   J   s-    
 
jnn122G?d    task_idstr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)r   raws     r   _worker_run_idr%   R   st    	z~~*++w66t
*../
0
0C t3xx   tts   
A 
A'&A'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   )r&   r   s     r   _enforce_worker_task_ownershipr(   _   sp    & jnn122G t
g~~@ @ @@ @ @
 
 	

 4r   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   c                 2    t          j        ddi|           S )NokT)jsondumps)r/   s    r   _okr4      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.r   :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_dict   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;   profiler?   outcomesummaryerrormetadatarF   ended_atrM   )rs    r   	_run_dictz_handle_show.<locals>._run_dict   s:    $19h19 y17 !
"#,AJ  r   c                8    g | ]}|j         |j        |j        d S )authorr=   rE   rW   .0cs     r   
<listcomp>z _handle_show.<locals>.<listcomp>   s?          !x#$<1 1  r   c                D    g | ]}|j         |j        |j        |j        d S )kindpayloadrE   run_idr^   )rZ   es     r   r\   z _handle_show.<locals>.<listcomp>   sE         V	#$<18E E  r   ic                &    g | ]} |          S  rd   )rZ   rT   rU   s     r   r\   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_idsr2   r3   build_worker_contextr   logger	exception)r5   kwr&   r-   connre   rh   ri   rj   rf   rg   rK   rb   rU   s                @r   _handle_showrx      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                X   t          |                     d                    }|st          d          S t          |          }|r|S |                     d          }|                     d          }|                     d          }|                     d          }|dt	          |t
                    r|g}t	          |t          t          f          s$t          dt          |          j	                   S d	 |D             }|s|st          d
          S |9t	          |t                    s$t          dt          |          j	                   S 	 t                      \  }}		 	 |                    |	|||||t          |                    }
nV# |j        $ rI}t          dd                    |j                   d          cY d}~|	                                 S d}~ww xY w|
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   r8   rP   rR   rH   created_cardsNz.created_cards must be a list of task ids, got c                    g | ]D}t          |                                          #t          |                                          ES rd   )r   striprY   s     r   r\   z$_handle_complete.<locals>.<listcomp>   sI     
 
 
 SVV\\^^
FFLLNN
 
 
r   z4provide at least one of: summary (preferred), resultz%metadata must be an object/dict, got )rH   rP   rR   rz   expected_run_idzfkanban_complete blocked: the following created_cards do not exist or were not created by this worker: z, zq. Either omit them, use only ids returned from successful kanban_create calls, or remove the created_cards field.zcould not complete z! (unknown id or already terminal)r   ra   zkanban_complete failedzkanban_complete: )r   r   r   r(   
isinstancer   listtupletype__name__r6   r.   complete_taskr%   HallucinatedCardsErrorjoinphantomrm   
latest_runr4   r;   r   rt   ru   )r5   rv   r&   ownership_errrP   rR   rH   rz   r-   rw   r1   hall_errrunrb   s                 r   _handle_completer      s8   
488I..
/
/C 
H
 
 	
 3377M hhy!!Gxx
##HXXhFHH_--M mS)) 	,*OM-$77 	2&&/2 2  

 
$1
 
 
  
v 
B
 
 	
 Jx$>$>MDNN4KMM
 
 	
3::D	%%#!7X"/$23$7$7	 &   , 
 
 
 "Oyy!122O O O       JJLLLL%
  !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3sr   I) #)F I 
G *GG I I) G  I 7I) /I ;I) I&&I) )
J)3+J$J)$J)c                    t          |                     d                    }|st          d          S t          |          }|r|S |                     d          }|r!t	          |                                          st          d          S 	 t                      \  }}	 |                    |||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   r8   reasonu2   reason is required — explain what input you need)r   r}   zcould not block z% (unknown id or not in running/ready)Nr~   zkanban_block failedzkanban_block: )r   r   r   r(   r   r|   r.   
block_taskr%   rm   r   r4   r;   r   rt   ru   )
r5   rv   r&   r   r   r-   rw   r1   r   rb   s
             r   _handle_blockr     s   
488I..
/
/C 
H
 
 	
 3377M XXhF PV**,, PNOOO0::D	c .s 3 3   B
  !&s & & &  JJLLLL --c**CsS+B366dCCCJJLLLLDJJLLLL 0 0 0.///.1..////////0sB   E !:D4 E 0/D4 E 4E

E 
F+FFFc                   t          |                     d                    }|st          d          S t          |          }|r|S |                     d          }	 t	                      \  }}	 t
          j                            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)u  Signal that the worker is still alive during a long operation.

    Extends the claim TTL via ``heartbeat_claim`` AND records a heartbeat
    event via ``heartbeat_worker``. Without the ``heartbeat_claim`` half,
    a diligent worker that loops this tool while a single tool call
    blocks the agent for >DEFAULT_CLAIM_TTL_SECONDS still gets reclaimed
    by ``release_stale_claims`` — which is exactly the trap that
    ``heartbeat_claim``'s docstring warns against.
    r   r8   noteHERMES_KANBAN_CLAIM_LOCK)claimer)r   r}   zcould not heartbeat z (unknown id or not running))r   zkanban_heartbeat failedzkanban_heartbeat: N)r   r   r   r(   r.   r   r   heartbeat_claimheartbeat_workerr%   rm   r4   r   rt   ru   )
r5   rv   r&   r   r   r-   rw   
claim_lockr1   rb   s
             r   _handle_heartbeatr   =  s    488I..
/
/C 
H
 
 	
 3377M 88FD4::D	 (BCCJtS*===$$ .s 3 3	 %  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4sC   D2 /A1D  D2 5D D2 D//D2 2
E2<+E-'E2-E2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 requiredrX   HERMES_PROFILEworker)rX   r=   )r   
comment_idzkanban_comment failedzkanban_comment: N)r   r   r   r|   r   r   r.   add_commentr4   rm   r   rt   ru   )	r5   rv   r&   r=   rX   r-   rw   cidrb   s	            r   _handle_commentr   m  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=   rf   r@   HERMES_TENANTrA   rB   scratchrC   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>   rf   r@   rA   rB   rC   r   r   r   r   rD   )r   r?   zkanban_create failedzkanban_create: )r   r   r|   r   r   r   r	   r   r   r   r   r   r.   create_taskr"   rl   r4   r?   rm   r   rt   ru   )r5   rv   r<   r>   r=   rf   r@   rA   rB   rC   r   r   r   r   r-   rw   new_tidnew_taskrb   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_tasksr4   rm   r#   r   rt   ru   )r5   rv   r   r   r-   rw   rb   s          r   _handle_linkr     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.objectstring)r   description)r   
propertiesrequired)namer   
parameterskanban_completeu  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.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   )r   rP   rR   rH   rz   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   r   r   kanban_heartbeatu   Signal that you're still alive during a long operation (training, encoding, large crawls). Call every few minutes so humans see liveness separately from PID checks. Pure side effect — no work changes.zHOptional short note describing current progress. Shown in the event log.)r   r   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=   rX   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.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.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).)r   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=   rf   r@   rA   rB   rC   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   r   u   📋)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   )r/   r   r   r   )r5   r6   r   r   )%__doc__
__future__r   r2   loggingr   typingr   r   tools.registryr   r   	getLoggerr   rt   r   r   r%   r(   r.   r4   rx   r   r   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registerrd   r   r   <module>r      s   2 # " " " " "   				                 / / / / / / / /		8	$	$   @   
 
 
 
   @  . . . .D/ D/ D/ D/NB3 B3 B3 B3J 0  0  0  0F-4 -4 -4 -4`2 2 2 20E1 E1 E1 E1P/ / / /48  	&  4 
 	 	  . 		  !4 
 !(  !5  !B    (+	0	 ?/
 /
` e3 3B B J 	  !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   