@brandon_9527/tcode 1.0.3 → 1.0.6
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.
- package/dist/python-src/entry.py +59 -17
- package/dist/python-src/main.py +69 -11
- package/dist/python-src/pyproject.toml +2 -1
- package/dist/python-src/src/core/deepagents.py +1 -1
- package/dist/python-src/src/managers/manager_agent.py +13 -13
- package/dist/python-src/src/managers/manager_context.py +1 -1
- package/dist/python-src/src/managers/manager_instruction.py +9 -9
- package/dist/python-src/src/managers/sandbox.py +3 -3
- package/dist/python-src/src/middlewares/dynamic_content.py +13 -10
- package/dist/python-src/src/middlewares/inject_content.py +0 -0
- package/dist/python-src/src/middlewares/subagents.py +4 -4
- package/dist/python-src/src/middlewares/summary.py +36 -36
- package/dist/python-src/src/prompts/prompts.py +1 -1
- package/dist/python-src/src/stream/formatter.py +19 -19
- package/dist/python-src/src/stream/handler.py +44 -45
- package/dist/python-src/src/stream/handler_with_tracker.py +7 -7
- package/dist/python-src/src/tools/tools.py +2 -2
- package/dist/python-src/src/tui/chatui.py +32 -36
- package/dist/python-src/src/tui/components/tlist.py +7 -7
- package/dist/python-src/src/tui/components/tscroll_panel.py +8 -8
- package/dist/python-src/src/tui/demo.py +22 -0
- package/dist/python-src/src/tui/utils/trender.py +20 -20
- package/dist/python-src/uv.lock +1974 -1939
- package/package.json +1 -1
package/dist/python-src/entry.py
CHANGED
|
@@ -6,37 +6,79 @@
|
|
|
6
6
|
# main()
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
# ===================== 强制修复 Git Bash 报错:NoConsoleScreenBufferError =====================
|
|
10
|
-
import os
|
|
11
|
-
import sys
|
|
9
|
+
# # ===================== 强制修复 Git Bash 报错:NoConsoleScreenBufferError =====================
|
|
10
|
+
# import os
|
|
11
|
+
# import sys
|
|
12
12
|
|
|
13
|
-
# 🔥 核心:强制让 prompt_toolkit 使用兼容模式,彻底关闭 Windows 控制台检测
|
|
14
|
-
os.environ["PROMPT_TOOLKIT_NO_WIN32"] = "1"
|
|
15
|
-
os.environ["PROMPT_TOOLKIT_BASIC_OUTPUT"] = "1"
|
|
16
|
-
os.environ["PROMPT_TOOLKIT_FORCE_VT100"] = "1"
|
|
13
|
+
# # 🔥 核心:强制让 prompt_toolkit 使用兼容模式,彻底关闭 Windows 控制台检测
|
|
14
|
+
# os.environ["PROMPT_TOOLKIT_NO_WIN32"] = "1"
|
|
15
|
+
# os.environ["PROMPT_TOOLKIT_BASIC_OUTPUT"] = "1"
|
|
16
|
+
# os.environ["PROMPT_TOOLKIT_FORCE_VT100"] = "1"
|
|
17
17
|
|
|
18
|
-
# 🔥 强制设置终端类型,让程序认为是 Linux 终端
|
|
19
|
-
if sys.platform == "win32":
|
|
20
|
-
|
|
18
|
+
# # 🔥 强制设置终端类型,让程序认为是 Linux 终端
|
|
19
|
+
# if sys.platform == "win32":
|
|
20
|
+
# os.environ["TERM"] = "xterm-256color"
|
|
21
21
|
|
|
22
|
-
# ===================== 下面才是你原来的代码 =====================
|
|
22
|
+
# # ===================== 下面才是你原来的代码 =====================
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
import warnings
|
|
26
26
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
27
27
|
warnings.filterwarnings("ignore", message="Pydantic serializer warnings")
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
import typer
|
|
31
|
+
import asyncio
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
from main import (
|
|
30
35
|
agent_ui,
|
|
31
36
|
asingle_agent,
|
|
32
37
|
team_main,
|
|
38
|
+
run_once,
|
|
39
|
+
teminal_chat
|
|
33
40
|
)
|
|
34
41
|
|
|
42
|
+
app = typer.Typer()
|
|
43
|
+
interact_app = typer.Typer()
|
|
44
|
+
app.add_typer(interact_app)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.callback(invoke_without_command=True, no_args_is_help=False)
|
|
48
|
+
def root(
|
|
49
|
+
ctx: typer.Context,
|
|
50
|
+
prompt: str = typer.Option(None, "-p", "--prompt", help="用户输入请求内容"),
|
|
51
|
+
verbose: bool = typer.Option(False, "-v", "--verbose", help="显示agent执行详细过程"),
|
|
52
|
+
mode: str = typer.Option("team", "-m", "--mode", help="运行模式,single或team"),
|
|
53
|
+
):
|
|
54
|
+
if ctx.invoked_subcommand is None:
|
|
55
|
+
if not prompt:
|
|
56
|
+
if mode == "single":
|
|
57
|
+
asyncio.run(asingle_agent())
|
|
58
|
+
elif mode == "team":
|
|
59
|
+
asyncio.run(team_main())
|
|
60
|
+
|
|
61
|
+
# 同步环境执行异步任务
|
|
62
|
+
asyncio.run(run_once(prompt, verbose))
|
|
63
|
+
# typer.echo(result)
|
|
64
|
+
# raise typer.Exit()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@interact_app.command("interact")
|
|
68
|
+
def interact_mode(
|
|
69
|
+
verbose: bool = typer.Option(False, "-v", "--verbose", help="显示agent执行详细过程"),
|
|
70
|
+
):
|
|
71
|
+
# 执行交互模式
|
|
72
|
+
asyncio.run(teminal_chat(verbose))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
35
76
|
if __name__ == "__main__":
|
|
36
|
-
|
|
77
|
+
app()
|
|
78
|
+
# import asyncio
|
|
37
79
|
|
|
38
|
-
try:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
except Exception as e:
|
|
42
|
-
|
|
80
|
+
# try:
|
|
81
|
+
# # asyncio.run(asingle_agent())
|
|
82
|
+
# asyncio.run(team_main())
|
|
83
|
+
# except Exception as e:
|
|
84
|
+
# pass
|
package/dist/python-src/main.py
CHANGED
|
@@ -335,7 +335,7 @@ async def team_main():
|
|
|
335
335
|
|
|
336
336
|
agents = []
|
|
337
337
|
for member_name, member in team_conf["members"].items():
|
|
338
|
-
print(f"member_name: {member_name}, member: {member}")
|
|
338
|
+
# print(f"member_name: {member_name}, member: {member}")
|
|
339
339
|
|
|
340
340
|
tools = []
|
|
341
341
|
for tool_name in member["tools"]:
|
|
@@ -404,8 +404,7 @@ async def team_main():
|
|
|
404
404
|
await ui.run_async()
|
|
405
405
|
|
|
406
406
|
|
|
407
|
-
|
|
408
|
-
""" """
|
|
407
|
+
def build_team():
|
|
409
408
|
from dataclasses import dataclass, field
|
|
410
409
|
from pathlib import Path
|
|
411
410
|
|
|
@@ -414,8 +413,6 @@ async def teminal_chat():
|
|
|
414
413
|
from src.utils.prompt import apply_template, apply_prompt
|
|
415
414
|
from src.managers.sandbox import Container
|
|
416
415
|
from src.prompts.prompts import leader
|
|
417
|
-
from src.stream.handler import ainput, anormal_handler
|
|
418
|
-
|
|
419
416
|
from src.tools.tools import (
|
|
420
417
|
SkillAgentContext,
|
|
421
418
|
shell,
|
|
@@ -431,13 +428,15 @@ async def teminal_chat():
|
|
|
431
428
|
from src.tools.web import (
|
|
432
429
|
web_search,
|
|
433
430
|
web_fetch
|
|
434
|
-
)
|
|
435
|
-
|
|
431
|
+
)
|
|
432
|
+
from src.middlewares.dynamic_content import DynamicContentMiddleware
|
|
433
|
+
|
|
436
434
|
from langgraph.checkpoint.memory import MemorySaver
|
|
437
|
-
from langchain_core.messages import HumanMessage
|
|
438
435
|
from langchain_core.tools import BaseTool
|
|
439
436
|
|
|
440
437
|
|
|
438
|
+
|
|
439
|
+
|
|
441
440
|
def _prepare(workspace, run_mode, session_id):
|
|
442
441
|
saver = MemorySaver()
|
|
443
442
|
|
|
@@ -493,14 +492,14 @@ async def teminal_chat():
|
|
|
493
492
|
|
|
494
493
|
def _build_team(agents_conf: Dict[str, Any], domain: str, llm: ChatOpenAI, toolkits: Dict[str, List[BaseTool]], workspace: str, saver=None, store=None, recursion_limit=1000):
|
|
495
494
|
""" """
|
|
496
|
-
team_conf = agents_conf
|
|
495
|
+
team_conf = agents_conf.setdefault(domain, {"members":{}})
|
|
497
496
|
|
|
498
497
|
# for mname, mconf in team_conf['members'].items():
|
|
499
498
|
# print(f"member_name: {mname}, member: {mconf}")
|
|
500
499
|
|
|
501
500
|
agents = []
|
|
502
501
|
for member_name, member in team_conf["members"].items():
|
|
503
|
-
print(f"member_name: {member_name}, member: {member}")
|
|
502
|
+
# print(f"member_name: {member_name}, member: {member}")
|
|
504
503
|
|
|
505
504
|
tools = []
|
|
506
505
|
for tool_name in member["tools"]:
|
|
@@ -540,6 +539,9 @@ async def teminal_chat():
|
|
|
540
539
|
# prompt_name="leader_",
|
|
541
540
|
# WORKSPACE=workspace
|
|
542
541
|
# ),
|
|
542
|
+
middleware=[
|
|
543
|
+
DynamicContentMiddleware(),
|
|
544
|
+
],
|
|
543
545
|
system_prompt=apply_prompt(leader, WORKSPACE=workspace),
|
|
544
546
|
checkpointer=saver
|
|
545
547
|
).with_config({"recursion_limit": recursion_limit})
|
|
@@ -564,6 +566,15 @@ async def teminal_chat():
|
|
|
564
566
|
recursion_limit=1000
|
|
565
567
|
)
|
|
566
568
|
|
|
569
|
+
return agent, context, instruction_manager
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
async def teminal_chat(verbose=True):
|
|
573
|
+
""" """
|
|
574
|
+
from langchain_core.messages import HumanMessage
|
|
575
|
+
from src.stream.handler import ainput, anormal_handler
|
|
576
|
+
|
|
577
|
+
agent, context, instruction_manager = build_team()
|
|
567
578
|
|
|
568
579
|
try:
|
|
569
580
|
while True:
|
|
@@ -580,6 +591,9 @@ async def teminal_chat():
|
|
|
580
591
|
|
|
581
592
|
new_query = f"\n [注意]: 执行用户请求必须严格遵循如下准则:\n{executed_instruction}\n\n 用户请求:\n{message}" if executed_instruction else message
|
|
582
593
|
|
|
594
|
+
|
|
595
|
+
print(f"【new_query】:\n {new_query}")
|
|
596
|
+
|
|
583
597
|
stream = agent.astream(
|
|
584
598
|
{
|
|
585
599
|
"messages":[HumanMessage(content=new_query)]
|
|
@@ -589,7 +603,7 @@ async def teminal_chat():
|
|
|
589
603
|
context=context
|
|
590
604
|
) # .with_config({"recursion_limit": 1000})
|
|
591
605
|
|
|
592
|
-
await anormal_handler(stream, detail=
|
|
606
|
+
await anormal_handler(stream, detail=verbose)
|
|
593
607
|
|
|
594
608
|
except Exception as e:
|
|
595
609
|
import traceback
|
|
@@ -597,6 +611,50 @@ async def teminal_chat():
|
|
|
597
611
|
finally:
|
|
598
612
|
pass
|
|
599
613
|
|
|
614
|
+
|
|
615
|
+
async def run_once(prompt:str = None, verbose: bool=True):
|
|
616
|
+
from langchain_core.messages import HumanMessage
|
|
617
|
+
from src.stream.handler import ainput, anormal_handler
|
|
618
|
+
|
|
619
|
+
agent, context, instruction_manager = build_team()
|
|
620
|
+
|
|
621
|
+
query = prompt
|
|
622
|
+
|
|
623
|
+
try:
|
|
624
|
+
if query == "/comands":
|
|
625
|
+
for instruction in instruction_manager.list_instructions():
|
|
626
|
+
# print(instruction)
|
|
627
|
+
print(f"command: {instruction.name}, description: {instruction.description}")
|
|
628
|
+
|
|
629
|
+
instruction_result = instruction_manager.parse(query)
|
|
630
|
+
executed_instruction, message = instruction_result["executed_instruction"], instruction_result['message']
|
|
631
|
+
|
|
632
|
+
new_query = f"\n [注意]: 执行用户请求必须严格遵循如下准则:\n{executed_instruction}\n\n 用户请求:\n{message}" if executed_instruction else message
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
if verbose:
|
|
636
|
+
print(f"user> {new_query}")
|
|
637
|
+
|
|
638
|
+
stream = agent.astream(
|
|
639
|
+
{
|
|
640
|
+
"messages":[HumanMessage(content=new_query)]
|
|
641
|
+
},
|
|
642
|
+
config = {"configurable": {"thread_id": 1}},
|
|
643
|
+
stream_mode=["updates", "custom"],
|
|
644
|
+
context=context
|
|
645
|
+
) # .with_config({"recursion_limit": 1000})
|
|
646
|
+
|
|
647
|
+
await anormal_handler(stream, detail=verbose)
|
|
648
|
+
|
|
649
|
+
except Exception as e:
|
|
650
|
+
import traceback
|
|
651
|
+
traceback.print_exc()
|
|
652
|
+
|
|
653
|
+
finally:
|
|
654
|
+
pass
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
|
|
600
658
|
|
|
601
659
|
|
|
602
660
|
if __name__ == "__main__":
|
|
@@ -32,6 +32,7 @@ dependencies = [
|
|
|
32
32
|
"langchain-core==1.2.13",
|
|
33
33
|
"python-minifier>=3.2.0",
|
|
34
34
|
"ddgs>=9.11.4",
|
|
35
|
+
"typer>=0.24.1",
|
|
35
36
|
]
|
|
36
37
|
|
|
37
38
|
#[project.scripts]
|
|
@@ -56,7 +57,7 @@ skip = [
|
|
|
56
57
|
"resources",
|
|
57
58
|
"_workspace",
|
|
58
59
|
".autodev",
|
|
59
|
-
"tools"
|
|
60
|
+
"tools",
|
|
60
61
|
]
|
|
61
62
|
|
|
62
63
|
exclude = [
|
|
@@ -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(
|
|
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)
|
|
@@ -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
|
-
|
|
35
|
-
if not os.path.exists(
|
|
36
|
-
for(G,P,I)in os.walk(
|
|
37
|
-
H=os.path.relpath(G,
|
|
38
|
-
if H=='.':
|
|
39
|
-
else:
|
|
40
|
-
if
|
|
41
|
-
for
|
|
42
|
-
if
|
|
43
|
-
J=os.path.join(G,
|
|
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(
|
|
47
|
-
except Exception as N:print(f"加载 {
|
|
48
|
-
return
|
|
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
|
|
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
|
|
@@ -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(
|
|
174
|
+
def parse(H,query):
|
|
175
175
|
A=query
|
|
176
|
-
if not A.strip().startswith('/'):
|
|
177
|
-
|
|
178
|
-
if not
|
|
179
|
-
|
|
180
|
-
for
|
|
181
|
-
if
|
|
182
|
-
else:
|
|
183
|
-
|
|
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_})")
|
|
@@ -21,14 +21,14 @@ sys.path.append(PROJ_PATH)
|
|
|
21
21
|
logger=logging.getLogger(__file__)
|
|
22
22
|
class Container:
|
|
23
23
|
def __init__(A,user_id=_C,workspace_path=_B,dockerfile_path='.',image_name=_J,container_basename='sandbox'):A.client=docker.from_env();A.container_basename=container_basename;A.workspace_path=workspace_path;A.dockerfile_path=dockerfile_path;A.image_name=image_name;A.sock=_B;A.queue=_B;A.container=_B;A.user_id=user_id;A.images={_G:'ubuntu:22.04',_H:'python:3.12-slim','node':'node:20-bullseye-slim',_C:_J}
|
|
24
|
-
def
|
|
24
|
+
def br(A):
|
|
25
25
|
try:
|
|
26
26
|
B=A.client.images.get(A.image_name)
|
|
27
27
|
if B:logger.info(f"Image {A.image_name} exists.");return _A
|
|
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
|
|
31
|
+
def bs(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.
|
|
47
|
+
if C==_C and not A.br():A.bs()
|
|
48
48
|
try:
|
|
49
49
|
A.container=A.client.containers.get(B)
|
|
50
50
|
if A.container.status==E:logger.info(f"容器 {B} 已存在且运行中,直接复用")
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
_H='.tcode/
|
|
2
|
-
_G='
|
|
3
|
-
_F='.tcode/
|
|
4
|
-
_E='
|
|
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
|
-
|
|
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
|
|
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]]
|
|
@@ -19,6 +21,7 @@ class DynamicContentUpdate(TypedDict):review_contents:dict[str,str];security_con
|
|
|
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
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
26
|
def cu(G,prompt,content_type,contents):
|
|
24
27
|
E='( No rules loaded)';C=contents;B=content_type;A=prompt
|
|
@@ -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
|
|
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(
|
|
58
|
-
B=request;F=B.state.get(_C,{});G=B.state.get(_D,{});C
|
|
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.cu(REVIEW_SYSTEM_PROMPT,'review_rules',F);E=C.cu(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)
|
|
File without changes
|
|
@@ -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
|
|
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
|
|
83
|
-
G='Tool call ID is required for subagent invocation';F='custom';A=task_description;B,H=
|
|
82
|
+
def bz(*,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=
|
|
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=bz(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))
|