@brandon_9527/tcode 1.0.3 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/python-src/entry.py +59 -17
  2. package/dist/python-src/main.py +71 -12
  3. package/dist/python-src/pyproject.toml +2 -1
  4. package/dist/python-src/skill_agent.py +144 -0
  5. package/dist/python-src/src/agents/token_tracker.py +44 -0
  6. package/dist/python-src/src/claw/__init__.py +0 -0
  7. package/dist/python-src/src/claw/bus/__init__.py +3 -0
  8. package/dist/python-src/src/claw/bus/events.py +10 -0
  9. package/dist/python-src/src/claw/bus/queue.py +43 -0
  10. package/dist/python-src/src/claw/channels/__init__.py +3 -0
  11. package/dist/python-src/src/claw/channels/base.py +30 -0
  12. package/dist/python-src/src/claw/channels/feishu.py +89 -0
  13. package/dist/python-src/src/claw/channels/manager.py +47 -0
  14. package/dist/python-src/src/claw/config/schema.py +46 -0
  15. package/dist/python-src/src/core/deepagents.py +1 -1
  16. package/dist/python-src/src/managers/manager_agent.py +15 -15
  17. package/dist/python-src/src/managers/manager_context.py +1 -1
  18. package/dist/python-src/src/managers/manager_instruction.py +16 -16
  19. package/dist/python-src/src/managers/manager_skill.py +121 -0
  20. package/dist/python-src/src/managers/sandbox.py +2 -2
  21. package/dist/python-src/src/middlewares/dynamic_content.py +15 -12
  22. package/dist/python-src/src/middlewares/hitl.py +3 -3
  23. package/dist/python-src/src/middlewares/inject_content.py +0 -0
  24. package/dist/python-src/src/middlewares/memory.py +2 -2
  25. package/dist/python-src/src/middlewares/skill.py +27 -0
  26. package/dist/python-src/src/middlewares/subagents.py +4 -4
  27. package/dist/python-src/src/middlewares/summary.py +33 -33
  28. package/dist/python-src/src/prompts/prompts.py +1 -1
  29. package/dist/python-src/src/stream/formatter.py +16 -16
  30. package/dist/python-src/src/stream/handler.py +44 -45
  31. package/dist/python-src/src/stream/handler_with_tracker.py +7 -7
  32. package/dist/python-src/src/tools/tools.py +2 -2
  33. package/dist/python-src/src/trackers/__init__.py +0 -0
  34. package/dist/python-src/src/trackers/token/__init__.py +0 -0
  35. package/dist/python-src/src/trackers/token/cli.py +45 -0
  36. package/dist/python-src/src/trackers/token/pricing.py +39 -0
  37. package/dist/python-src/src/trackers/token/report.py +114 -0
  38. package/dist/python-src/src/trackers/token/tracker.py +65 -0
  39. package/dist/python-src/src/tui/chatui.py +38 -41
  40. package/dist/python-src/src/tui/components/tlist.py +7 -7
  41. package/dist/python-src/src/tui/components/tscroll_panel.py +12 -12
  42. package/dist/python-src/src/tui/demo.py +22 -0
  43. package/dist/python-src/src/tui/utils/trender.py +22 -21
  44. package/dist/python-src/uv.lock +1974 -1939
  45. package/package.json +1 -1
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+ import asyncio
3
+ from typing import Any,TYPE_CHECKING
4
+ from logging import getLogger
5
+ from src.claw.bus.events import OutboundMessage
6
+ from src.claw.bus.queue import MessageBus
7
+ from src.claw.channels.base import BaseChannel
8
+ from src.claw.config.schema import Config
9
+ logger=getLogger(__name__)
10
+ class ChannelManager:
11
+ def __init__(A,config,bus):A.config=config;A.bus=bus;A.channels={};A._dispatch_task=None;A.dk()
12
+ def dk(A):
13
+ if A.config.channels.feishu.enabled:
14
+ try:from src.claw.channels.feishu import FeishuChannel as B;A.channels['feishu']=B(A.config.channels.feishu,A.bus);logger.info('Feishu channel enabled')
15
+ except ImportError as C:logger.warning(f"Feishu channel not available: {C}")
16
+ async def _start_channel(B,name,channel):
17
+ try:await channel.start()
18
+ except Exception as A:logger.error(f"Failed to start channel {name}: {A}")
19
+ async def start_all(A):
20
+ if not A.channels:logger.warning('No channels enabled');return
21
+ A._dispatch_task=asyncio.create_task(A._dispatch_outbound());B=[]
22
+ for(C,D)in A.channels.items():logger.info(f"Starting {C} channel ...");B.append(asyncio.create_task(A._start_channel(C,D)))
23
+ await asyncio.gather(*B,return_exceptions=True)
24
+ async def stop_all(A):
25
+ logger.info('Stopping all channels ...')
26
+ if A._dispatch_task:
27
+ A._dispatch_task.cancel()
28
+ try:await A._dispatch_task
29
+ except asyncio.CancelledError:pass
30
+ for(B,C)in A.channels.items():
31
+ try:await C.stop();logger.info(f"Stopped {B} channel")
32
+ except Exception as D:logger.error(f"Error stopping {B}: {D}")
33
+ async def _dispatch_outbound(B):
34
+ logger.info('Outbound dispatcher started')
35
+ while True:
36
+ try:
37
+ A=await asyncio.wait_for(B.bus.consume_outbound(),timeout=1.);C=B.channels.get(A.channel)
38
+ if C:
39
+ try:await C.send(A)
40
+ except Exception as D:logger.error(f"Error sending to {A.channel}: {D}")
41
+ else:logger.warning(f"Unknown channel: {A.channel}")
42
+ except asyncio.TimeoutError:continue
43
+ except asyncio.CancelledError:break
44
+ def get_channel(A,name):return A.channels.get(name)
45
+ def get_status(A):return{A:{'enabled':True,'running':B.is_running}for(A,B)in A.channels.items()}
46
+ @property
47
+ def enabled_channels(self):return list(self.channels.keys())
@@ -0,0 +1,46 @@
1
+ _C=True
2
+ _B=False
3
+ _A=None
4
+ from pathlib import Path
5
+ from pydantic import BaseModel,Field
6
+ from pydantic_settings import BaseSettings
7
+ class FeishuConfig(BaseModel):enabled:bool=_B;app_id:str='';app_secret:str='';encrypt_key:str='';verification_token:str='';allow_from:list[str]=Field(default_factory=list)
8
+ class DingTalkConfig(BaseModel):enabled:bool=_B;client_id:str='';client_secret:str='';allow_from:list[str]=Field(default_factory=list)
9
+ class EmailConfig(BaseModel):enabled:bool=_B;consent_granted:bool=_B;imap_host:str='';imap_port:int=993;imap_username:str='';imap_password:str='';imap_mailbox:str='INBOX';imap_use_ssl:bool=_C;smtp_host:str='';smtp_port:int=587;smtp_username:str='';smtp_password:str='';smtp_use_tls:bool=_C;smtp_use_ssl:bool=_B;from_address:str='';auto_reply_enabled:bool=_C;poll_interval_seconds:int=30;mark_seen:bool=_C;max_body_chars:int=12000;subject_prefix:str='Re: ';allow_from:list[str]=Field(default_factory=list)
10
+ class MochatMentionConfig(BaseModel):require_in_groups:bool=_B
11
+ class MochatGroupRule(BaseModel):require_mention:bool=_B
12
+ class MochatConfig(BaseModel):enabled:bool=_B;base_url:str='https://mochat.io';socket_url:str='';socket_path:str='/socket.io';socket_disable_msgpack:bool=_B;socket_reconnect_delay_ms:int=1000;socket_max_reconnect_delay_ms:int=10000;socket_connect_timeout_ms:int=10000;refresh_interval_ms:int=30000;watch_timeout_ms:int=25000;watch_limit:int=100;retry_delay_ms:int=500;max_retry_attempts:int=0;claw_token:str='';agent_user_id:str='';sessions:list[str]=Field(default_factory=list);panels:list[str]=Field(default_factory=list);allow_from:list[str]=Field(default_factory=list);mention:MochatMentionConfig=Field(default_factory=MochatMentionConfig);groups:dict[str,MochatGroupRule]=Field(default_factory=dict);reply_delay_mode:str='non-mention';reply_delay_ms:int=120000
13
+ class QQConfig(BaseModel):enabled:bool=_B;app_id:str='';secret:str='';allow_from:list[str]=Field(default_factory=list)
14
+ class ChannelsConfig(BaseModel):feishu:FeishuConfig=Field(default_factory=FeishuConfig);mochat:MochatConfig=Field(default_factory=MochatConfig);dingtalk:DingTalkConfig=Field(default_factory=DingTalkConfig);email:EmailConfig=Field(default_factory=EmailConfig);qq:QQConfig=Field(default_factory=QQConfig)
15
+ class AgentDefaults(BaseModel):workspace:str='~/.nanobot/workspace';model:str='qwen-plus';max_tokens:int=8192;temperature:float=.7;max_tool_iterations:int=200
16
+ class AgentsConfig(BaseModel):defaults:AgentDefaults=Field(default_factory=AgentDefaults)
17
+ class ProviderConfig(BaseModel):api_key:str='';api_base:str|_A=_A;extra_headers:dict[str,str]|_A=_A
18
+ class ProvidersConfig(BaseModel):anthropic:ProviderConfig=Field(default_factory=ProviderConfig);openai:ProviderConfig=Field(default_factory=ProviderConfig);openrouter:ProviderConfig=Field(default_factory=ProviderConfig);deepseek:ProviderConfig=Field(default_factory=ProviderConfig);groq:ProviderConfig=Field(default_factory=ProviderConfig);zhipu:ProviderConfig=Field(default_factory=ProviderConfig);dashscope:ProviderConfig=Field(default_factory=ProviderConfig);vllm:ProviderConfig=Field(default_factory=ProviderConfig);gemini:ProviderConfig=Field(default_factory=ProviderConfig);moonshot:ProviderConfig=Field(default_factory=ProviderConfig);minimax:ProviderConfig=Field(default_factory=ProviderConfig);aihubmix:ProviderConfig=Field(default_factory=ProviderConfig)
19
+ class GatewayConfig(BaseModel):host:str='0.0.0.0';port:int=18790
20
+ class WebSearchConfig(BaseModel):api_key:str='';max_results:int=5
21
+ class WebToolsConfig(BaseModel):search:WebSearchConfig=Field(default_factory=WebSearchConfig)
22
+ class ExecToolConfig(BaseModel):timeout:int=60
23
+ class ToolsConfig(BaseModel):web:WebToolsConfig=Field(default_factory=WebToolsConfig);exec:ExecToolConfig=Field(default_factory=ExecToolConfig);restrict_to_workspace:bool=_B
24
+ class Config(BaseSettings):
25
+ agents:AgentsConfig=Field(default_factory=AgentsConfig);channels:ChannelsConfig=Field(default_factory=ChannelsConfig);providers:ProvidersConfig=Field(default_factory=ProvidersConfig);gateway:GatewayConfig=Field(default_factory=GatewayConfig);tools:ToolsConfig=Field(default_factory=ToolsConfig)
26
+ @property
27
+ def workspace_path(self):return Path(self.agents.defaults.workspace).expanduser()
28
+ def df(C,model=_A):
29
+ from nanobot.providers.registry import PROVIDERS as D;E=(model or C.agents.defaults.model).lower()
30
+ for B in D:
31
+ A=getattr(C.providers,B.name,_A)
32
+ if A and any(A in E for A in B.keywords)and A.api_key:return A,B.name
33
+ for B in D:
34
+ A=getattr(C.providers,B.name,_A)
35
+ if A and A.api_key:return A,B.name
36
+ return _A,_A
37
+ def get_provider(A,model=_A):B,C=A.df(model);return B
38
+ def get_provider_name(A,model=_A):C,B=A.df(model);return B
39
+ def get_api_key(B,model=_A):A=B.get_provider(model);return A.api_key if A else _A
40
+ def get_api_base(D,model=_A):
41
+ from nanobot.providers.registry import find_by_name as E;B,C=D.df(model)
42
+ if B and B.api_base:return B.api_base
43
+ if C:
44
+ A=E(C)
45
+ if A and A.is_gateway and A.default_api_base:return A.default_api_base
46
+ class Config:env_prefix='NANOBOT_';env_nested_delimiter='__'
@@ -20,7 +20,7 @@ from src.middlewares.hitl import HumanInTheLoopMiddleware
20
20
  from dotenv import find_dotenv,load_dotenv
21
21
  import os
22
22
  _=load_dotenv(find_dotenv())
23
- def get_default_model(streaming=False):return ChatOpenAI(model_name=os.getenv('DEFAULT_MODEL'),base_url=os.getenv('OPENAI_API_BASE'),api_key=os.getenv('OPENAI_API_KEY'),streaming=streaming,model_kwargs={'stream_options':{'include_usage':True}})
23
+ def get_default_model(streaming=False):D='OPENAI_API_KEY';C='OPENAI_API_BASE';B='DEFAULT_MODEL';A=streaming;return ChatOpenAI(model_name=os.getenv(B),base_url=os.getenv(C),api_key=os.getenv(D),streaming=A,model_kwargs={'stream_options':{'include_usage':True}})if A else ChatOpenAI(model_name=os.getenv(B),base_url=os.getenv(C),api_key=os.getenv(D),streaming=A)
24
24
  def create_deep_agent(model=_A,tools=_A,*,system_prompt=_A,middleware=(),subagents=_A,response_format=_A,context_schema=_A,checkpointer=_A,store=_A,interrupt_on=_A,debug=False,name=_A,cache=_A,recursion_limit=1000):
25
25
  G=subagents;F=middleware;E=system_prompt;D=tools;B=interrupt_on;A=model
26
26
  if A is _A:A=get_default_model(streaming=True)
@@ -15,8 +15,8 @@ from pathlib import Path
15
15
  import yaml,json,sys,re,os
16
16
  sys.path.append('/Users/brandon/workspace/coder/autodev')
17
17
  class AgentInfo:
18
- def __init__(A,name,content,src_):B=content;A.name=name;A.original_content=B;A.src_=src_;A.meta,A.body=A.bt(B)
19
- def bt(F,content):
18
+ def __init__(A,name,content,src_):B=content;A.name=name;A.original_content=B;A.src_=src_;A.meta,A.body=A.bw(B)
19
+ def bw(F,content):
20
20
  B=content;C='^---\\n(.*?)\\n---\\n(.*)$';A=re.match(C,B,re.S)
21
21
  if A:D=yaml.safe_load(A.group(1));E=A.group(2).strip();return D,E
22
22
  else:return{},B.strip()
@@ -31,21 +31,21 @@ class AgentManager:
31
31
  if not os.path.exists(A.user_agents_dir):os.makedirs(A.user_agents_dir)
32
32
  A.reload_agents()
33
33
  def bv(O,dir_path,source):
34
- E=source;D=dir_path;B={}
35
- if not os.path.exists(D):return B
36
- for(G,P,I)in os.walk(D):
37
- H=os.path.relpath(G,D)
38
- if H=='.':A=_K
39
- else:A=H.replace(os.sep,'/')
40
- if A not in B:B[A]={}
41
- for F in I:
42
- if F.endswith('.md'):
43
- J=os.path.join(G,F);C=os.path.splitext(F)[0];print(f"加载 {E} 来源 agent: {A}/{C}")
34
+ F=source;C=dir_path;A={}
35
+ if not os.path.exists(C):return A
36
+ for(G,P,I)in os.walk(C):
37
+ H=os.path.relpath(G,C)
38
+ if H=='.':B=_K
39
+ else:B=H.replace(os.sep,'/')
40
+ if B not in A:A[B]={}
41
+ for D in I:
42
+ if D.endswith('.md'):
43
+ J=os.path.join(G,D);E=os.path.splitext(D)[0]
44
44
  try:
45
45
  with open(J,'r',encoding=_I)as K:L=K.read()
46
- M=AgentInfo(C,L,E);B[A][C]=M
47
- except Exception as N:print(f"加载 {E} 来源 agent {A}/{C} 失败: {str(N)}")
48
- return B
46
+ M=AgentInfo(E,L,F);A[B][E]=M
47
+ except Exception as N:print(f"加载 {F} 来源 agent {B}/{E} 失败: {str(N)}")
48
+ return A
49
49
  def reload_agents(A):
50
50
  A.agents[_B].clear();A.agents[_C].clear();A.agents[_C]=A.bv(A.user_agents_dir,_C)
51
51
  if A.project_agents_dir:A.agents[_B]=A.bv(A.project_agents_dir,_B)
@@ -11,7 +11,7 @@ DOCKERFILE_PATH=os.path.join('../../resources','dockerfiles',_C,'Dockerfile.sand
11
11
  class ContextManager:
12
12
  def __init__(A,dockerfile_path=DOCKERFILE_PATH,mode=_C):A.mode=mode;A.dockerfile_path=dockerfile_path;A._docker_containers={};A._workspace_mapping={};A._instruction_managers={};A._agent_managers={}
13
13
  def create_environment(A,session_id=_B,workspace_path=_A):
14
- E='.autodev';D=True;C=session_id;B=workspace_path;print(f"create_environment: {C}, {B}")
14
+ E='.autodev';D=True;C=session_id;B=workspace_path
15
15
  if B:os.makedirs(B,exist_ok=D)
16
16
  else:raise ValueError('workspace_path 不能为空')
17
17
  A._workspace_mapping[C]=B
@@ -39,7 +39,7 @@ class InstructionManager:
39
39
  if A.project_commands_dir and not os.path.exists(A.project_commands_dir):os.makedirs(A.project_commands_dir)
40
40
  if not os.path.exists(A.user_dir):os.makedirs(A.user_dir)
41
41
  A.reload_instructions()
42
- def bw(O,dir_path,source):
42
+ def bx(O,dir_path,source):
43
43
  F=source;C=dir_path;A={}
44
44
  if not os.path.exists(C):return A
45
45
  for(G,P,I)in os.walk(C):
@@ -54,9 +54,9 @@ class InstructionManager:
54
54
  except Exception as N:print(f"从 {F} 加载命令 {B}/{E} 失败: {str(N)}")
55
55
  return A
56
56
  def reload_instructions(A):
57
- A.instructions[_B].clear();A.instructions[_C].clear();A.instructions[_C]=A.bw(A.user_dir,_C)
58
- if A.project_commands_dir:A.instructions[_B]=A.bw(A.project_commands_dir,_B)
59
- def bx(A,source,domain,name):
57
+ A.instructions[_B].clear();A.instructions[_C].clear();A.instructions[_C]=A.bx(A.user_dir,_C)
58
+ if A.project_commands_dir:A.instructions[_B]=A.bx(A.project_commands_dir,_B)
59
+ def by(A,source,domain,name):
60
60
  B=source
61
61
  if B==_B:
62
62
  if not A.project_commands_dir:raise ValueError('项目目录未初始化,无法操作project来源的指令')
@@ -79,7 +79,7 @@ class InstructionManager:
79
79
  def add_instruction(B,domain,name,settings,content,source=_B):
80
80
  E=source;D=name;C=domain
81
81
  if B.exists(C,D,E):raise ValueError(f"指令 '{E}/{C}/{D}' 已存在")
82
- G=B.bx(E,C,D);os.makedirs(os.path.dirname(G),exist_ok=_G)
82
+ G=B.by(E,C,D);os.makedirs(os.path.dirname(G),exist_ok=_G)
83
83
  with open(G,'w',encoding=_I)as A:
84
84
  A.write('---\n')
85
85
  for(H,F)in settings.items():
@@ -98,7 +98,7 @@ class InstructionManager:
98
98
  D=C.instructions[G][A][B]
99
99
  if I is not _A:D.settings=I
100
100
  if J is not _A:D.original_content=J
101
- K=C.bx(G,A,B);os.makedirs(os.path.dirname(K),exist_ok=_G)
101
+ K=C.by(G,A,B);os.makedirs(os.path.dirname(K),exist_ok=_G)
102
102
  with open(K,'w',encoding=_I)as E:
103
103
  E.write('---\n')
104
104
  for(L,H)in D.settings.items():
@@ -114,7 +114,7 @@ class InstructionManager:
114
114
  else:
115
115
  G,E=C.get_instruction_with_source(A,B)
116
116
  if not E:raise ValueError(f"指令 '{A}/{B}' 不存在于任何来源")
117
- F=C.bx(E,A,B)
117
+ F=C.by(E,A,B)
118
118
  if os.path.exists(F):os.remove(F)
119
119
  C.reload_instructions()
120
120
  def load_all_instructions(A,domain=_D,source=_A):
@@ -171,16 +171,16 @@ class InstructionManager:
171
171
  C=source;B=instruction_name;A=domain;D=E.get_instruction(A,B,C)
172
172
  if not D:raise ValueError(f"命令 '{A}/{B}' 不存在(来源: {C or'all'})")
173
173
  return D.execute(arguments)
174
- def parse(F,query):
174
+ def parse(H,query):
175
175
  A=query
176
- if not A.strip().startswith('/'):B=_D;C=_D;G=F.execute_instruction(B,C,arguments='');return{_H:_A,_F:A.strip()}
177
- D=re.findall('"[^"]*"|\\S+',A.strip())
178
- if not D:return{'instruction':_A,'args':[],_F:''}
179
- L=D[0][1:];H=L.split('/');B='/'.join(H[:-1]);C=H[-1];I=[];J=[];K=False
180
- for E in D[1:]:
181
- if E.startswith('--')and not K:I.append(E)
182
- else:K=_G;M=E.strip('"');J.append(M)
183
- N=' '.join(J);O=' '.join(I);G=F.execute_instruction(B,C,O);return{_H:G,_F:N}
176
+ if not A.strip().startswith('/'):return{_H:_A,_F:A.strip()}
177
+ B=re.findall('"[^"]*"|\\S+',A.strip())
178
+ if not B:return{'instruction':_A,'args':[],_F:''}
179
+ I=B[0][1:];D=I.split('/');J='/'.join(D[:-1]);K=D[-1];E=[];F=[];G=False
180
+ for C in B[1:]:
181
+ if C.startswith('--')and not G:E.append(C)
182
+ else:G=_G;L=C.strip('"');F.append(L)
183
+ M=' '.join(F);N=' '.join(E);O=H.execute_instruction(J,K,N);return{_H:O,_F:M}
184
184
  def curd():
185
185
  E='backup';D='tags';B='deploy';G='/Users/brandon/workspace/coder/autocoder';A=InstructionManager(G);A.add_instruction(domain=_D,name=B,settings={_E:'部署项目',D:['ci','cd']},content='执行部署脚本: $ARGUMENTS',source=_B);A.add_instruction(domain=_D,name=E,settings={_E:'备份用户数据',D:[E]},content='执行备份脚本: $ARGUMENTS',source=_C);A.update_instruction(domain=_D,name=B,settings={_E:'更新部署流程',D:['ci',B]},content='新的部署命令: $ARGUMENTS',source=_B);C=A.load_all_instructions(domain=_D);print('所有指令:')
186
186
  for F in C:print(f"- {F.name} (来源: {F.src_})")
@@ -0,0 +1,121 @@
1
+ _C='managed'
2
+ _B='user'
3
+ _A='project'
4
+ from typing import Optional,Dict,Any,List,Tuple
5
+ from dataclasses import dataclass,field
6
+ from pathlib import Path
7
+ import logging,yaml,re,os
8
+ logger=logging.getLogger(__name__)
9
+ @dataclass
10
+ class Skill:
11
+ name:str;description:str;content:str;path:Path;license:Optional[str]=None;allowed_tools:List[str]=field(default_factory=list);metadata:Dict[str,Any]=field(default_factory=dict);location:str=_A
12
+ @classmethod
13
+ def from_skill_md(C,skill_md_path,location=_A):
14
+ B=skill_md_path
15
+ if not B.exists():return
16
+ F=B.read_text(encoding='utf-8');A,G=C.br(F)
17
+ if not A:return
18
+ D=A.get('name');E=A.get('description')
19
+ if not D or not E:return
20
+ return C(name=D,description=E,content=G.strip(),path=B.parent,license=A.get('license'),allowed_tools=A.get('allowed-tools',[])or[],metadata=A.get('metadata',{})or{},location=location)
21
+ @staticmethod
22
+ def br(content):
23
+ A=content;C='^---\\s*\\n(.*?)\\n---\\s*\\n(.*)$';B=re.match(C,A,re.DOTALL)
24
+ if not B:return{},A
25
+ D=B.group(1);E=B.group(2)
26
+ try:F=yaml.safe_load(D)or{}
27
+ except yaml.YAMLError:return{},A
28
+ return F,E
29
+ def get_prompt(A):B=f"Loading: {A.name} \nBase directory: {A.path}\n \n ";return B+A.content
30
+ def to_xml(A):return f"<skill>\n<name>{A.name}</name>\n<description>{A.description}</description>\n<location>{A.location}</location>\n</skill>"
31
+ def __repr__(A):return f"Skill(name={A.name!r}, location={A.location!r})"
32
+ class SkillManager:
33
+ SKILL_DIRS=['.claude/skills','.autodev/skills'];SKILL_FILE='SKILL.md'
34
+ def __init__(A,project_root=None,home_path=Path.home()):C=home_path;B=project_root;A._skills={};A._skills_by_location={_A:[],_B:[],_C:[]};A.project_root=Path(B)if B else Path.cwd();A.home_dir=Path(C)if C else Path.home()
35
+ def register(B,skill):
36
+ A=skill;C=B._skills.get(A.name)
37
+ if C:
38
+ D={_A:0,_B:1,_C:2};E=D.get(C.location,99);F=D.get(A.location,99)
39
+ if F>=E:return False
40
+ B._skills[A.name]=A;B._skills_by_location[A.location].append(A);return True
41
+ def get(A,name):return A._skills.get(name)
42
+ def exists(A,name):return name in A._skills
43
+ def list_all(A):return list(A._skills.values())
44
+ def list_by_location(A,location):return A._skills_by_location.get(location,[])
45
+ def clear(A):
46
+ A._skills.clear()
47
+ for B in A._skills_by_location:A._skills_by_location[B].clear()
48
+ def generate_skills_prompt(E,char_budget=10000):
49
+ C=E.list_all()
50
+ if not C:return'Execute a skill within the main conversation.\n\nNo skills are currently available. Skills can be added to:\n- .claude/skills/ (project-level)\n- ~/.claude/skills/ (user-level)\n- .minion/skills/ (project-level)\n- ~/.minion/skills/ (user-level) \n '
51
+ A=[];D=0
52
+ for F in C:
53
+ B=F.to_xml()
54
+ if D+len(B)>char_budget:break
55
+ A.append(B);D+=len(B)
56
+ if not A:return'Execute a skill within the main conversation.\n\nNo skills are currently available. Skills can be added to:\n- .claude/skills/ (project-level)\n- ~/.claude/skills/ (user-level)\n- .minion/skills/ (project-level)\n- ~/.minion/skills/ (user-level) \n'
57
+ G='\n'.join(A);return f'''Execute a skill within the main conversation.
58
+
59
+ <skills_instructions>
60
+ When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
61
+
62
+ How to use skills:
63
+ - Invoke skills using skill tool with the skill name only (no arguments)
64
+ - When you invoke a skill, you will see <command-message>The "{{name}}" skill is loading</command-message>
65
+ - The skill\'s prompt will expand and provide detailed instructions on how to complete the task
66
+ - Base directory provided in output for resolving bundled resources (references/, scripts/, assets/)
67
+
68
+ Important:
69
+ - Only use skills listed in <available_skills> below
70
+ - Do not invoke a skill that is already running
71
+ </skills_instructions>
72
+
73
+ <available_skills>
74
+ {G}
75
+ </available_skills>'''
76
+ def __len__(A):return len(A._skills)
77
+ def __contains__(A,name):return name in A._skills
78
+ def __iter__(A):return iter(A._skills.values())
79
+ def get_search_paths(A):
80
+ B=[]
81
+ for C in A.SKILL_DIRS:D=A.project_root/C;B.append((D,_A))
82
+ for C in A.SKILL_DIRS:E=A.home_dir/C;B.append((E,_B))
83
+ return B
84
+ def discover_skills(D,skills_dir):
85
+ A=skills_dir
86
+ if not A.exists()or not A.is_dir():return[]
87
+ B=[]
88
+ for C in A.iterdir():
89
+ if C.is_dir():
90
+ E=C/D.SKILL_FILE
91
+ if E.exists():B.append(E)
92
+ else:
93
+ for F in C.iterdir():
94
+ if F.is_dir():
95
+ G=F/D.SKILL_FILE
96
+ if G.exists():B.append(G)
97
+ return B
98
+ def load_skill(D,skill_md_path,location):
99
+ A=skill_md_path
100
+ try:
101
+ B=Skill.from_skill_md(A,location)
102
+ if B:logger.debug(f"Loaded skill: {B.name} from {A}")
103
+ else:logger.warning(f"Failed to parse skill: {A}")
104
+ return B
105
+ except Exception as C:logger.error(f"Error loading skill from {A}: {C}");return
106
+ def load_all(A):
107
+ for(D,C)in A.get_search_paths():
108
+ E=A.discover_skills(D)
109
+ for F in E:
110
+ B=A.load_skill(F,C)
111
+ if B:
112
+ G=A.register(B)
113
+ if G:logger.info(f"Registered skill: {B.name} ({C})")
114
+ else:logger.debug(f"Skipped skill {B.name} - already registered from high priority location")
115
+ return A
116
+ def reload(A):A.clear();return A.load_all()
117
+ def main():
118
+ A=SkillManager(project_root=Path.cwd());A.load_all();C=A.list_all();print(f"Loaded {len(C)} skills");B=A.get('arxiv-search')
119
+ if B:print(B.get_prompt())
120
+ D=A.generate_skills_prompt(char_budget=5000);print(D);A.reload()
121
+ if __name__=='__main__':main()
@@ -28,7 +28,7 @@ class Container:
28
28
  else:logger.info(f"Image {A.image_name} not exists.");return _E
29
29
  except docker.errors.ImageNotFound:logger.error(f"Image {A.image_name} not exists.");return _E
30
30
  except Exception as C:logger.error(f"检查镜像时出错: {str(C)}");return _E
31
- def br(B):
31
+ def bt(B):
32
32
  F='arm64';print('🔧 Building Docker image...')
33
33
  try:
34
34
  C=platform.machine()
@@ -44,7 +44,7 @@ class Container:
44
44
  except Exception as D:logger.error(f"An unexpected error occurred: {D}");raise
45
45
  def get_or_create_container(A,container_name,image_type=_C):
46
46
  E='running';C=image_type;B=container_name;D=_F;F=A.workspace_path;B=f"{A.container_basename}_{A.user_id}"
47
- if C==_C and not A.bs():A.br()
47
+ if C==_C and not A.bs():A.bt()
48
48
  try:
49
49
  A.container=A.client.containers.get(B)
50
50
  if A.container.status==E:logger.info(f"容器 {B} 已存在且运行中,直接复用")
@@ -1,26 +1,29 @@
1
- _H='.tcode/security.md'
2
- _G='security.md'
3
- _F='.tcode/review.md'
4
- _E='review.md'
1
+ _H='.tcode/SECURITY.md'
2
+ _G='SECURITY.md'
3
+ _F='.tcode/REVIEW.md'
4
+ _E='REVIEW.md'
5
5
  _D='security_contents'
6
6
  _C='review_contents'
7
7
  _B='utf-8'
8
8
  _A='r'
9
9
  from typing import TYPE_CHECKING,Annotated,NotRequired,TypedDict
10
10
  import logging,os
11
- if TYPE_CHECKING:from collections.abc import Awaitable,Callable;from langchain_core.runnables import RunnableConfig;from langgraph.runtime import Runtime
11
+ from collections.abc import Awaitable,Callable
12
+ from langchain_core.runnables import RunnableConfig
13
+ from langgraph.runtime import Runtime
12
14
  from langchain.agents.middleware.types import AgentMiddleware,AgentState,ContextT,ModelRequest,ModelResponse,PrivateStateAttr,ResponseT
13
15
  from langchain.tools import ToolRuntime
14
- from langchain_core.messages import ContextBlock,SystemMessage
16
+ from langchain_core.messages import SystemMessage
15
17
  from src.middlewares.utils import append_to_system_message
16
18
  logger=logging.getLogger(__name__)
17
19
  class DynamicContentState(AgentState):review_contents:NotRequired[Annotated[dict[str,str],PrivateStateAttr]];security_contents:NotRequired[Annotated[dict[str,str],PrivateStateAttr]]
18
20
  class DynamicContentUpdate(TypedDict):review_contents:dict[str,str];security_contents:dict[str,str]
19
21
  REVIEW_SYSTEM_PROMPT=' \n<review_rules>\n {review_rules}\n</review_rules>\n\n<review_guidelines>\n 如果用户要求对项目进行 Review ,请严格依照 review_rules 标签中的规则 对项目进行 Review 。\n</review_guidelines>\n'
20
- SECURITY_SYSTEM_PROMPT=' \n<security_rules>\n {security_rules}\n</security_rules>\n\n<<security_guidelines>\n 如果用户要求对项目进行 安全检查 ,请严格依照 security_rules 标签中的规则 对项目进行安全检查 。\n</security_guidelines>\n'
22
+ SECURITY_SYSTEM_PROMPT=' \n<security_rules>\n {security_rules}\n</security_rules>\n\n<security_guidelines>\n 如果用户要求对项目进行 安全检查 ,请严格依照 security_rules 标签中的规则 对项目进行安全检查 。\n</security_guidelines>\n'
21
23
  class DynamicContentMiddleware(AgentMiddleware[DynamicContentState,ContextT,ResponseT]):
24
+ state_schema=DynamicContentState
22
25
  def __init__(A,*,home_path=os.path.expanduser('~'),project_path=os.getcwd()):A.home_path=home_path;A.project_path=project_path
23
- def cu(G,prompt,content_type,contents):
26
+ def cv(G,prompt,content_type,contents):
24
27
  E='( No rules loaded)';C=contents;B=content_type;A=prompt
25
28
  if not C:return A.format(**{B:E})
26
29
  D=[f"{A}\n{C[A]}"for A in C]
@@ -40,7 +43,7 @@ class DynamicContentMiddleware(AgentMiddleware[DynamicContentState,ContextT,Resp
40
43
  if A.home_path and os.path.exists(H):
41
44
  with open(H,_A,encoding=_B)as B:F[H]=B.read()
42
45
  return DynamicContentUpdate(review_contents=C,security_contents=F)
43
- async def after_agent(A,state,runtime,config):
46
+ async def abefore_agent(A,state,runtime,config):
44
47
  I=state
45
48
  if _C in I and _D in I:return
46
49
  C={};D=os.path.join(A.project_path,_E);E=os.path.join(A.home_path,_F)
@@ -54,10 +57,10 @@ class DynamicContentMiddleware(AgentMiddleware[DynamicContentState,ContextT,Resp
54
57
  if A.home_path and os.path.exists(H):
55
58
  with open(H,_A,encoding=_B)as B:F[H]=B.read()
56
59
  return DynamicContentUpdate(review_contents=C,security_contents=F)
57
- def modify_request(E,request):
58
- B=request;F=B.state.get(_C,{});G=B.state.get(_D,{});C=E.cu(C,'review_rules',F);D=E.cu(D,'security_rules',G);A=B.system_message
59
- if C:A=append_to_system_message(A,C)
60
+ def modify_request(C,request):
61
+ B=request;F=B.state.get(_C,{});G=B.state.get(_D,{});D=C.cv(REVIEW_SYSTEM_PROMPT,'review_rules',F);E=C.cv(SECURITY_SYSTEM_PROMPT,'security_rules',G);A=B.system_message
60
62
  if D:A=append_to_system_message(A,D)
63
+ if E:A=append_to_system_message(A,E)
61
64
  return B.override(system_message=A)
62
65
  def wrap_model_call(A,request,handler):B=A.modify_request(request);return handler(B)
63
66
  async def awrap_model_call(A,request,handler):B=A.modify_request(request);return await handler(B)
@@ -21,12 +21,12 @@ class ReviewConfig(TypedDict):action_name:str;allowed_decisions:list[DecisionTyp
21
21
  class HITLRequest(TypedDict):action_requests:list[ActionRequest];review_configs:list[ReviewConfig]
22
22
  class HumanInTheLoopMiddleware(AgentMiddleware[StateT,ContextT]):
23
23
  def __init__(A,interrupt_on=[],*,description_perfix='Tool execution requires approval'):super().__init__();A.interrupt_on=interrupt_on;A.description_prefix=description_perfix
24
- def cd(D,tool_call):A=tool_call;B=A[_E];C=A[_F];E=A['id'];F=f"{D.description_prefix}\n\nTool: {B}\nArgs: {C}";G=ActionRequest(name=B,id=E,args=C,description=F);H=ReviewConfig(action_name=B,allowed_decisions=[_B,_A,_C]);return G,H
24
+ def ce(D,tool_call):A=tool_call;B=A[_E];C=A[_F];E=A['id'];F=f"{D.description_prefix}\n\nTool: {B}\nArgs: {C}";G=ActionRequest(name=B,id=E,args=C,description=F);H=ReviewConfig(action_name=B,allowed_decisions=[_B,_A,_C]);return G,H
25
25
  async def awrap_tool_call(E,request,handler):
26
26
  C=handler;A=request;F=A.tool_call['id'];D=A.tool_call[_E];R=A.tool_call[_F];G=A.runtime;H=G.context;I=H.tool_mode
27
27
  if I==_A:return await C(A)
28
28
  if len(E.interrupt_on)>0 and not D.strip().lower()in E.interrupt_on:return await C(A)
29
- J,K=E.cd(A.tool_call);L=HITLRequest(action_requests=[J],review_configs=[K]);M=interrupt(L);B=M[_G][0][_D]
29
+ J,K=E.ce(A.tool_call);L=HITLRequest(action_requests=[J],review_configs=[K]);M=interrupt(L);B=M[_G][0][_D]
30
30
  if B==_B:return await C(A)
31
31
  if B==_A:return await C(A)
32
32
  if B==_C:N=f"User rejected the tool call for `{D}` with id {F}";O=ToolMessage(content=N,name=D,tool_call_id=F,status='error');return O
@@ -35,7 +35,7 @@ class HumanInTheLoopMiddleware(AgentMiddleware[StateT,ContextT]):
35
35
  C=handler;A=request;F=A.tool_call['id'];D=A.tool_call[_E];R=A.tool_call[_F];H=A.runtime;I=H.context;J=I.tool_mode
36
36
  if J==_A:return C(A)
37
37
  if len(E.interrupt_on)>0 and not D.strip().lower()in E.interrupt_on:return C(A)
38
- K,L=E.cd(A.tool_call);M=HITLRequest(action_requests=[K],review_configs=[L]);G=interrupt(M);print(f"[HITL] -> Human decision: {G}");B=G[_G][0][_D]
38
+ K,L=E.ce(A.tool_call);M=HITLRequest(action_requests=[K],review_configs=[L]);G=interrupt(M);print(f"[HITL] -> Human decision: {G}");B=G[_G][0][_D]
39
39
  if B==_B:return C(A)
40
40
  if B==_A:return C(A)
41
41
  if B==_C:N=f"User rejected the tool call for `{D}` with id {F}";O=ToolMessage(content=N,name=D,tool_call_id=F,status='error');return O
@@ -14,7 +14,7 @@ MEMORY_SYSTEM_PROMPT=' \n<agent_memory>\n{agent_memory}\n</agent_memory>\n\n<mem
14
14
  class MemoryMiddleware(AgentMiddleware[MemoryState,ContextT,ResponseT]):
15
15
  state_schema=MemoryState
16
16
  def __init__(A,*,home_path=os.path.expanduser('~'),project_path=os.getcwd()):A.home_path=home_path;A.project_path=project_path
17
- def ce(D,contents):
17
+ def cf(D,contents):
18
18
  C='(No memory loaded)';A=contents
19
19
  if not A:return MEMORY_SYSTEM_PROMPT.format(agent_memory=C)
20
20
  B=[f"{B}\n{A[B]}"for B in D.sources if A.get(B)]
@@ -39,6 +39,6 @@ class MemoryMiddleware(AgentMiddleware[MemoryState,ContextT,ResponseT]):
39
39
  H=f"Failed to download {C}: {A.error}";raise ValueError(H)
40
40
  if A.content is not None:E[C]=A.content.decode(_B);logger.debug('Loaded memory from: %s',C)
41
41
  return MemoryStateUpdate(memory_contents=E)
42
- def modify_request(B,request):A=request;C=A.state.get(_A,{});D=B.ce(C);E=append_to_system_message(A.system_message,D);return A.override(system_message=E)
42
+ def modify_request(B,request):A=request;C=A.state.get(_A,{});D=B.cf(C);E=append_to_system_message(A.system_message,D);return A.override(system_message=E)
43
43
  def wrap_model_call(A,request,handler):B=A.modify_request(request);return handler(B)
44
44
  async def awrap_model_call(A,request,handler):B=A.modify_request(request);return await handler(B)
@@ -0,0 +1,27 @@
1
+ from collections.abc import Awaitable,Callable
2
+ from langchain.agents.middleware.types import AgentMiddleware,ModelRequest,ModelResponse
3
+ from langchain.tools import BaseTool,ToolRuntime
4
+ from langchain_core.language_models import BaseChatModel
5
+ from langchain_core.messages import HumanMessage,ToolMessage
6
+ from langchain_core.runnables import Runnable
7
+ from langchain_core.tools import StructuredTool
8
+ from langgraph.types import Command
9
+ from langchain_core.tools import tool
10
+ from src.managers.manager_skill import SkillManager
11
+ def generate_skill_tool(manager):
12
+ B=manager;A=' Execute a skill within the main conversation.\n\nSkills are folders of instructions, scripts, and resources that Claude loads\ndynamically to improve performance on specialized tasks.\n\nUsage:\n- Invoke skills using this tool with the skill name only (no arguments)\n- When you invoke a skill, its prompt will expand and provide detailed instructions\n- Only use skills listed in <available_skills> in the system prompt\n\nImportant:\n- Only use skills that are listed as available\n- Do not use skills that are not listed as available\n'
13
+ @tool(description=A)
14
+ def C(skill):
15
+ D='success';C=skill;A=B.get(C)
16
+ if A is None:E=[A.name for A in B.list_all()];return{D:False,'error':f"Unknown skill: {C}",'available_skills':E[:10],'hint':'Use one of the available skills listed above'}
17
+ F=A.get_prompt();return{D:True,'skill_name':A.name,'skill_description':A.description,'skill_location':A.location,'skill_path':str(A.path),'prompt':F,'message':f'The "{A.name}" skill is loading','allowed_tools':A.allowed_tools}
18
+ return C
19
+ class SkillMiddleware(AgentMiddleware):
20
+ def __init__(A,*,home_path,workspace):B=home_path;super().__init__();A.workspace=workspace;A.home_path=B;A.manager=SkillManager(A.workspace,B);A.manager.load_all();C=generate_skill_tool(A.manager);A.tools=[C]
21
+ def modify_request(D,request):
22
+ A=request;B=D.manager.generate_skills_prompt(5000)
23
+ if A.system_prompt:C=A.system_prompt+'\n\n'+B
24
+ else:C=B
25
+ return A.override(system_prompt=C)
26
+ def wrap_model_call(A,request,handler):B=A.modify_request(request);return handler(B)
27
+ async def awrap_model_call(A,request,handler):B=A.modify_request(request);return await handler(B)
@@ -34,7 +34,7 @@ _EXCLUDED_STATE_KEYS=_B,'todos'
34
34
  TASK_TOOL_DESCRIPTION='Launch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.\n\nAvailable agent types and the tools they have access to:\n{available_agents}\n\nWhen using the Task tool, you must specify a subagent_type parameter to select which agent type to use.\n\n## Usage notes:\n1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses\n2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.\n3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.\n4. The agent\'s outputs should generally be trusted\n5. Clearly tell the agent whether you expect it to create content, perform analysis, or just do research (search, file reads, web fetches, etc.), since it is not aware of the user\'s intent\n6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.\n7. When only the general-purpose agent is provided, you should use it for all tasks. It is great for isolating context and token usage, and completing specific, complex tasks, as it has all the same capabilities as the main agent.\n\n### Example usage of the general-purpose agent:\n\n<example_agent_descriptions>\n"general-purpose": use this agent for general purpose tasks, it has access to all tools as the main agent.\n</example_agent_descriptions>\n\n<example>\nUser: "I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them."\nAssistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*\nAssistant: *Synthesizes the results of the three isolated research tasks and responds to the User*\n<commentary>\nResearch is a complex, multi-step task in it of itself.\nThe research of each individual player is not dependent on the research of the other players.\nThe assistant uses the task tool to break down the complex objective into three isolated tasks.\nEach research task only needs to worry about context and tokens about one player, then returns synthesized information about each player as the Tool Result.\nThis means each research task can dive deep and spend tokens and context deeply researching each player, but the final result is synthesized information, and saves us tokens in the long run when comparing the players to each other.\n</commentary>\n</example>\n\n<example>\nUser: "Analyze a single large code repository for security vulnerabilities and generate a report."\nAssistant: *Launches a single `task` subagent for the repository analysis*\nAssistant: *Receives report and integrates results into final summary*\n<commentary>\nSubagent is used to isolate a large, context-heavy task, even though there is only one. This prevents the main thread from being overloaded with details.\nIf the user then asks followup questions, we have a concise report to reference instead of the entire history of analysis and tool calls, which is good and saves us time and money.\n</commentary>\n</example>\n\n<example>\nUser: "Schedule two meetings for me and prepare agendas for each."\nAssistant: *Calls the task tool in parallel to launch two `task` subagents (one per meeting) to prepare agendas*\nAssistant: *Returns final schedules and agendas*\n<commentary>\nTasks are simple individually, but subagents help silo agenda preparation.\nEach subagent only needs to worry about the agenda for one meeting.\n</commentary>\n</example>\n\n<example>\nUser: "I want to order a pizza from Dominos, order a burger from McDonald\'s, and order a salad from Subway."\nAssistant: *Calls tools directly in parallel to order a pizza from Dominos, a burger from McDonald\'s, and a salad from Subway*\n<commentary>\nThe assistant did not use the task tool because the objective is super simple and clear and only requires a few trivial tool calls.\nIt is better to just complete the task directly and NOT use the `task`tool.\n</commentary>\n</example>\n\n### Example usage with custom agents:\n\n<example_agent_descriptions>\n"content-reviewer": use this agent after you are done creating significant content or documents\n"greeting-responder": use this agent when to respond to user greetings with a friendly joke\n"research-analyst": use this agent to conduct thorough research on complex topics\n</example_agent_description>\n\n<example>\nuser: "Please write a function that checks if a number is prime"\nassistant: Sure let me write a function that checks if a number is prime\nassistant: First let me use the Write tool to write a function that checks if a number is prime\nassistant: I\'m going to use the Write tool to write the following code:\n<code>\nfunction isPrime(n) {{\n if (n <= 1) return false\n for (let i = 2; i * i <= n; i++) {{\n if (n % i === 0) return false\n }}\n return true\n}}\n</code>\n<commentary>\nSince significant content was created and the task was completed, now use the content-reviewer agent to review the work\n</commentary>\nassistant: Now let me use the content-reviewer agent to review the code\nassistant: Uses the Task tool to launch with the content-reviewer agent\n</example>\n\n<example>\nuser: "Can you help me research the environmental impact of different renewable energy sources and create a comprehensive report?"\n<commentary>\nThis is a complex research task that would benefit from using the research-analyst agent to conduct thorough analysis\n</commentary>\nassistant: I\'ll help you research the environmental impact of renewable energy sources. Let me use the research-analyst agent to conduct comprehensive research on this topic.\nassistant: Uses the Task tool to launch with the research-analyst agent, providing detailed instructions about what research to conduct and what format the report should take\n</example>\n\n<example>\nuser: "Hello"\n<commentary>\nSince the user is greeting, use the greeting-responder agent to respond with a friendly joke\n</commentary>\nassistant: "I\'m going to use the Task tool to launch with the greeting-responder agent"\n</example>'
35
35
  TASK_SYSTEM_PROMPT='## `task` (subagent spawner)\n\nYou have access to a `task` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.\n\nWhen to use the task tool:\n- When a task is complex and multi-step, and can be fully delegated in isolation\n- When a task is independent of other tasks and can run in parallel\n- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread\n- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)\n- When you only care about the output of the subagent, and not the intermediate steps (ex. performing a lot of research and then returned a synthesized report, performing a series of computations or lookups to achieve a concise, relevant answer.)\n\nSubagent lifecycle:\n1. **Spawn** → Provide clear role, instructions, and expected output\n2. **Run** → The subagent completes the task autonomously\n3. **Return** → The subagent provides a single structured result\n4. **Reconcile** → Incorporate or synthesize the result into the main thread\n\nWhen NOT to use the task tool:\n- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)\n- If the task is trivial (a few tool calls or simple lookup)\n- If delegating does not reduce token usage, complexity, or context switching\n- If splitting would add latency without benefit\n- 用户仅仅时普通寒暄时,不应该使用任务工具。\n\n## Important Task Tool Usage Notes to Remember\n- Whenever possible, parallelize the work that you do. This is true for both tool_calls, and for tasks. Whenever you have independent steps to complete - make tool_calls, or kick off tasks (subagents) in parallel to accomplish them faster. This saves time for the user, which is incredibly important.\n- Remember to use the `task` tool to silo independent tasks within a multi-part objective.\n- You should use the `task` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete. These agents are highly competent and efficient.'
36
36
  DEFAULT_GENERAL_PURPOSE_DESCRIPTION='General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.'
37
- def cb(*,default_model,default_tools,default_middleware,default_interrupt_on,subagents,general_purpose_agent):
37
+ def cc(*,default_model,default_tools,default_middleware,default_interrupt_on,subagents,general_purpose_agent):
38
38
  M='middleware';L='runnable';F=default_tools;E=default_model;C=default_interrupt_on;D=default_middleware or[];B={};G=[]
39
39
  if general_purpose_agent:
40
40
  H=[*D]
@@ -79,8 +79,8 @@ async def astream_handler(stream,runtime):
79
79
  if isinstance(A,AIMessage)and len(A.tool_calls)==0:C=A
80
80
  if isinstance(A,ToolMessage):G=A.name;N=A.content;H=A.tool_call_id;I={_E:'tool_result',_G:D.tool_call_id,_H:H,_I:'',_C:G,_D:{},_J:N,_K:_O};J(json.dumps(I,indent=2,ensure_ascii=_L))
81
81
  O={_B:[C]if C and isinstance(C,AIMessage)else[]};return O
82
- def by(*,default_model,default_tools,default_middleware,default_interrupt_on,subagents,general_purpose_agent,task_description=_A):
83
- G='Tool call ID is required for subagent invocation';F='custom';A=task_description;B,H=cb(default_model=default_model,default_tools=default_tools,default_middleware=default_middleware,default_interrupt_on=default_interrupt_on,subagents=subagents,general_purpose_agent=general_purpose_agent);C='\n'.join(H)
82
+ def cd(*,default_model,default_tools,default_middleware,default_interrupt_on,subagents,general_purpose_agent,task_description=_A):
83
+ G='Tool call ID is required for subagent invocation';F='custom';A=task_description;B,H=cc(default_model=default_model,default_tools=default_tools,default_middleware=default_middleware,default_interrupt_on=default_interrupt_on,subagents=subagents,general_purpose_agent=general_purpose_agent);C='\n'.join(H)
84
84
  def D(result,tool_call_id):A=result;B={A:B for(A,B)in A.items()if A not in _EXCLUDED_STATE_KEYS};C=A[_B][-1];D=I(C);return Command(update={**B,_B:[ToolMessage(D,tool_call_id=tool_call_id)]})
85
85
  def I(msg):
86
86
  D='text';B=msg.content
@@ -110,7 +110,7 @@ def by(*,default_model,default_tools,default_middleware,default_interrupt_on,sub
110
110
  return D(L,A.tool_call_id)
111
111
  return StructuredTool.from_function(name='task',func=J,coroutine=K,description=A)
112
112
  class SubAgentMiddleware(AgentMiddleware):
113
- def __init__(A,*,default_model,default_tools=_A,default_middleware=_A,default_interrupt_on=_A,subagents=_A,system_prompt=TASK_SYSTEM_PROMPT,general_purpose_agent=True,task_description=_A):super().__init__();A.system_prompt=system_prompt;B=by(default_model=default_model,default_tools=default_tools or[],default_middleware=default_middleware,default_interrupt_on=default_interrupt_on,subagents=subagents or[],general_purpose_agent=general_purpose_agent,task_description=task_description);A.tools=[B]
113
+ def __init__(A,*,default_model,default_tools=_A,default_middleware=_A,default_interrupt_on=_A,subagents=_A,system_prompt=TASK_SYSTEM_PROMPT,general_purpose_agent=True,task_description=_A):super().__init__();A.system_prompt=system_prompt;B=cd(default_model=default_model,default_tools=default_tools or[],default_middleware=default_middleware,default_interrupt_on=default_interrupt_on,subagents=subagents or[],general_purpose_agent=general_purpose_agent,task_description=task_description);A.tools=[B]
114
114
  def wrap_model_call(B,request,handler):
115
115
  C=handler;A=request
116
116
  if B.system_prompt is not _A:D=A.system_prompt+'\n\n'+B.system_prompt if A.system_prompt else B.system_prompt;return C(A.override(system_prompt=D))