@brandon_9527/tcode 1.0.6 → 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 (34) hide show
  1. package/dist/python-src/main.py +2 -1
  2. package/dist/python-src/skill_agent.py +144 -0
  3. package/dist/python-src/src/agents/token_tracker.py +44 -0
  4. package/dist/python-src/src/claw/__init__.py +0 -0
  5. package/dist/python-src/src/claw/bus/__init__.py +3 -0
  6. package/dist/python-src/src/claw/bus/events.py +10 -0
  7. package/dist/python-src/src/claw/bus/queue.py +43 -0
  8. package/dist/python-src/src/claw/channels/__init__.py +3 -0
  9. package/dist/python-src/src/claw/channels/base.py +30 -0
  10. package/dist/python-src/src/claw/channels/feishu.py +89 -0
  11. package/dist/python-src/src/claw/channels/manager.py +47 -0
  12. package/dist/python-src/src/claw/config/schema.py +46 -0
  13. package/dist/python-src/src/managers/manager_agent.py +2 -2
  14. package/dist/python-src/src/managers/manager_instruction.py +7 -7
  15. package/dist/python-src/src/managers/manager_skill.py +121 -0
  16. package/dist/python-src/src/managers/sandbox.py +3 -3
  17. package/dist/python-src/src/middlewares/dynamic_content.py +3 -3
  18. package/dist/python-src/src/middlewares/hitl.py +3 -3
  19. package/dist/python-src/src/middlewares/memory.py +2 -2
  20. package/dist/python-src/src/middlewares/skill.py +27 -0
  21. package/dist/python-src/src/middlewares/subagents.py +2 -2
  22. package/dist/python-src/src/middlewares/summary.py +37 -37
  23. package/dist/python-src/src/stream/formatter.py +19 -19
  24. package/dist/python-src/src/trackers/__init__.py +0 -0
  25. package/dist/python-src/src/trackers/token/__init__.py +0 -0
  26. package/dist/python-src/src/trackers/token/cli.py +45 -0
  27. package/dist/python-src/src/trackers/token/pricing.py +39 -0
  28. package/dist/python-src/src/trackers/token/report.py +114 -0
  29. package/dist/python-src/src/trackers/token/tracker.py +65 -0
  30. package/dist/python-src/src/tui/chatui.py +11 -10
  31. package/dist/python-src/src/tui/components/tlist.py +5 -5
  32. package/dist/python-src/src/tui/components/tscroll_panel.py +14 -14
  33. package/dist/python-src/src/tui/utils/trender.py +23 -22
  34. package/package.json +1 -1
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+ _O='output'
3
+ _N='by_session_id'
4
+ _M='by_user_id'
5
+ _L='by_tag'
6
+ _K='by_model'
7
+ _J='total_tokens'
8
+ _I='total_calls'
9
+ _H='total_cost_usd'
10
+ _G='period'
11
+ _F='by_period'
12
+ _E='until'
13
+ _D='since'
14
+ _C='calls'
15
+ _B='cost_usd'
16
+ _A=None
17
+ import json
18
+ from collections import defaultdict
19
+ from datetime import datetime
20
+ from typing import Optional
21
+ from.tracker import CostTracker
22
+ def dc(since,until,tag=_A,user_id=_A,session_id=_A):
23
+ F=session_id;E=user_id;D=until;C=since;A=[];B=[]
24
+ if C:A.append('timestamp >= ?');B.append(C.isoformat())
25
+ if D:A.append('timestamp <= ?');B.append(D.isoformat())
26
+ if tag:A.append('tag = ?');B.append(tag)
27
+ if E:A.append('user_id = ?');B.append(E)
28
+ if F:A.append('session_id = ?');B.append(F)
29
+ G=' AND '.join(A)if A else'1=1';return G,B
30
+ def dd(period):
31
+ A=period
32
+ if A=='daily':return"strftime('%Y-%m-%d', timestamp)"
33
+ elif A=='weekly':return"strftime('%Y-%W', timestamp)"
34
+ elif A=='monthly':return"strftime('%Y-%m', timestamp)"
35
+ else:raise ValueError(f"Unknown period: {A}")
36
+ class CostReport:
37
+ def __init__(A,tracker):A.tracker=tracker
38
+ def build(L,since=_A,until=_A,period='daily',tag=_A,user_id=_A,session_id=_A):I=period;H=until;G=since;F='output_tokens';E='input_tokens';B,C=dc(G,H,tag,user_id,session_id);A=L.tracker._conn.cursor();A.execute(f"""
39
+ SELECT
40
+ COUNT(*),
41
+ SUM(cost),
42
+ SUM(input_tokens),
43
+ SUM(output_tokens)
44
+ FROM usage
45
+ WHERE {B}
46
+ """,C);D=A.fetchone();M=D[0]or 0;N=D[1]or .0;J=D[2]or 0;K=D[3]or 0;A.execute(f"""
47
+ SELECT
48
+ model,
49
+ COUNT(*),
50
+ SUM(cost),
51
+ SUM(input_tokens),
52
+ SUM(output_tokens)
53
+ FROM usage
54
+ WHERE {B}
55
+ GROUP BY model
56
+ ORDER BY SUM(cost) DESC
57
+ """,C);O={A:{_B:round(C or 0,6),_C:B,E:D or 0,F:G or 0}for(A,B,C,D,G)in A.fetchall()};A.execute(f"""
58
+ SELECT
59
+ tag,
60
+ COUNT(*),
61
+ SUM(cost),
62
+ SUM(input_tokens),
63
+ SUM(output_tokens)
64
+ FROM usage
65
+ WHERE {B}
66
+ GROUP BY tag
67
+ ORDER BY SUM(cost) DESC
68
+ """,C);P={A:{_B:round(C or 0,6),_C:B,E:D or 0,F:G or 0}for(A,B,C,D,G)in A.fetchall()};A.execute(f"""
69
+ SELECT
70
+ user_id,
71
+ COUNT(*),
72
+ SUM(cost),
73
+ SUM(input_tokens),
74
+ SUM(output_tokens)
75
+ FROM usage
76
+ WHERE {B}
77
+ GROUP BY user_id
78
+ ORDER BY SUM(cost) DESC
79
+ """,C);Q={A:{_B:round(C or 0,6),_C:B,E:D or 0,F:G or 0}for(A,B,C,D,G)in A.fetchall()};A.execute(f"""
80
+ SELECT
81
+ session_id,
82
+ COUNT(*),
83
+ SUM(cost),
84
+ SUM(input_tokens),
85
+ SUM(output_tokens)
86
+ FROM usage
87
+ WHERE {B}
88
+ GROUP BY session_id
89
+ ORDER BY SUM(cost) DESC
90
+ """,C);R={A:{_B:round(C or 0,6),_C:B,E:D or 0,F:G or 0}for(A,B,C,D,G)in A.fetchall()};S=dd(I);A.execute(f"""
91
+ SELECT
92
+ {S} as bucket,
93
+ COUNT(*),
94
+ SUM(cost)
95
+ FROM usage
96
+ WHERE {B}
97
+ GROUP BY bucket
98
+ ORDER BY bucket
99
+ """,C);T={A:{_B:round(C or 0,6),_C:B}for(A,B,C)in A.fetchall()};return{_G:I,_D:G.isoformat()if G else _A,_E:H.isoformat()if H else _A,_H:round(N,6),_I:M,_J:{'input':J,_O:K,'total':J+K},_K:O,_L:P,_M:Q,_N:R,_F:T}
100
+ def to_json(A,**B):return json.dumps(A.build(**B),indent=2)
101
+ def to_text(G,**H):
102
+ E='─';B=G.build(**H);A=[];A.append(E*60);A.append(' LLM Cost Report');A.append(E*60)
103
+ if B[_D]:A.append(f" Since: {B[_D]}")
104
+ if B[_E]:A.append(f" Until: {B[_E]}")
105
+ A.append('');A.append(f" Total cost : ${B[_H]:.4f}");A.append(f" Total calls : {B[_I]}");D=B[_J];A.append(f" Tokens : {D['input']:,} in / {D[_O]:,} out ({D['total']:,})")
106
+ def C(title,data):
107
+ if not data:return
108
+ A.append(f"\n {title}:")
109
+ for(C,B)in data.items():A.append(f" {C:<30} ${B[_B]:.4f} ({B[_C]} calls)")
110
+ C('By Model',B[_K]);C('By Tag',B[_L]);C('By User',B[_M]);C('By Session',B[_N])
111
+ if B[_F]:
112
+ A.append(f"\n {B[_G].capitalize()} Breakdown:")
113
+ for(I,F)in B[_F].items():A.append(f" {I:<15} ${F[_B]:.4f} ({F[_C]} calls)")
114
+ A.append('\n'+E*60);return'\n'.join(A)
@@ -0,0 +1,65 @@
1
+ _D=True
2
+ _C='anonymous'
3
+ _B='default'
4
+ _A=None
5
+ import asyncio,json,os,sqlite3,uuid
6
+ from dataclasses import dataclass,asdict
7
+ from datetime import datetime,timezone
8
+ from pathlib import Path
9
+ from typing import Optional,List,Dict
10
+ @dataclass
11
+ class UsageRecord:
12
+ model:str;input_tokens:int;output_tokens:int;tag:str=_B;user_id:str=_C;session_id:str=_B;timestamp:str=_A;record_id:str=_A;cost_usd:float=.0
13
+ def __post_init__(A):
14
+ if not A.timestamp:A.timestamp=datetime.now(timezone.utc).isoformat()
15
+ if not A.record_id:A.record_id=str(uuid.uuid4())[:8]
16
+ def to_tuple(A):return A.record_id,A.model,A.input_tokens,A.output_tokens,A.cost_usd,A.tag,A.user_id,A.session_id,A.timestamp
17
+ class BudgetExceeded(Exception):0
18
+ @dataclass
19
+ class BudgetGuard:tag:Optional[str]=_A;user_id:Optional[str]=_A;session_id:Optional[str]=_A;limit_usd:float;alert_at:float=.8;raise_on_exceed:bool=_D
20
+ class CostTracker:
21
+ DEFAULT_DB=Path.home()/'.config'/'llm-cost-tracker'/'usage.db'
22
+ def __init__(A,db_path=_A,pricing=_A,budgets=_A,batch_size=50,flush_interval=1.):A.db_path=Path(db_path or os.environ.get('LLM_COST_DB',A.DEFAULT_DB));A.db_path.parent.mkdir(parents=_D,exist_ok=_D);A.pricing=pricing;A.budgets={A.tag or f"{A.user_id or _C}_{A.session_id or _B}":A for A in budgets or[]};A.batch_size=batch_size;A.flush_interval=flush_interval;A._queue=asyncio.Queue();A._task=_A;A._conn=_A
23
+ async def start(A):A._conn=sqlite3.connect(A.db_path,isolation_level=_A,check_same_thread=False);A._conn.execute('PRAGMA journal_mode=WAL;');A._conn.execute('PRAGMA synchronous=NORMAL;');A.db();A._task=asyncio.create_task(A._writer_loop())
24
+ async def stop(A):
25
+ await A._queue.put(_A)
26
+ if A._task:await A._task
27
+ if A._conn:A._conn.close()
28
+ def db(A):A._conn.execute(' \n CREATE TABLE IF NOT EXISTS usage (\n id TEXT PRIMARY KEY,\n model TEXT,\n input_tokens INTEGER,\n output_tokens INTEGER,\n cost REAL,\n \n tag TEXT,\n user_id TEXT,\n session_id TEXT,\n \n timestamp TEXT\n )\n ');A._conn.execute('CREATE INDEX IF NOT EXISTS idx_tag ON usage(tag)');A._conn.execute('CREATE INDEX IF NOT EXISTS idx_user_id ON usage(user_id)');A._conn.execute('CREATE INDEX IF NOT EXISTS idx_session_id ON usage(session_id)');A._conn.execute('CREATE INDEX IF NOT EXISTS idx_user_session ON usage(user_id, session_id)')
29
+ async def add_call(A,model,input_tokens,output_tokens,tag=_B,user_id=_C,session_id=_C):
30
+ D=output_tokens;C=input_tokens;B=model;E=.0
31
+ if A.pricing:
32
+ try:E=A.pricing.compute_cost(B,C,D)
33
+ except Exception:pass
34
+ F=UsageRecord(model=B,input_tokens=C,output_tokens=D,tag=tag,user_id=user_id,session_id=session_id,cost_usd=E);await A._queue.put(F)
35
+ async def _write_loop(B):
36
+ A=[]
37
+ while _D:
38
+ try:C=await asyncio.wait_for(B._queue.get(),timeout=B.flush_interval)
39
+ except asyncio.TimeoutError:C=_A
40
+ if C is _A:
41
+ if A:B.da(A);A.clear()
42
+ if C is _A and B._queue.empty():break
43
+ continue
44
+ A.append(C)
45
+ if len(A)>=B.batch_size:B.da(A);A.clear()
46
+ def da(D,records):
47
+ F=records;A=D._conn.cursor()
48
+ try:
49
+ A.execute('BEGIN');A.executemany(' \n INSERT INTO usage\n (id, model, input_tokens, output_tokens, cost, tag, user_id, session_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n ',[A.to_tuple()for A in F]);H={A.tag for A in F}
50
+ for B in H:
51
+ if B not in D.budgets:continue
52
+ C=D.budgets[B];A.execute('SELECT SUM(cost) from usage WHERE tag=?',(B,));G=A.fetchone()[0]or .0;E=G/C.limit_usd if C.limit_usd else 0
53
+ if E>=1.:
54
+ if C.raise_on_exceed:raise BudgetExceeded(f"{B} exceeded: {G:.4f}/{C.limit_usd:.4f}")
55
+ elif E>=C.alert_at:print(f"[WARN] {B} budget {E:.0%}")
56
+ A.execute('COMMIT')
57
+ except Exception:A.execute('ROLLBACK');raise
58
+ def cz(A):
59
+ B=[];C=[]
60
+ if A.tag:B.append('tag=?');C.append(A.tag)
61
+ if A.user_id:B.append('user_id=?');C.append(A.user_id)
62
+ if A.session_id:B.append('session_id=?');C.append(A.session_id)
63
+ D=' AND '.join(B)if B else'1=1';return D,C
64
+ def total_cost(A,tag=_A,user_id=_A,session_id=_A):B=A._conn.cursor();C=BudgetGuard(tag,user_id,session_id);D,E=A.cz(C);B.execute(f"SELECT SUM(cost) FROM usage WHERE {D}",E);return B.fetchone()[0]or .0
65
+ def total_tokens(C,tag=_A,user_id=_A,session_id=_A):E=BudgetGuard(tag,user_id,session_id);D=C._conn.cursor();F,G=C.cz(E);D.execute(f"SELECT SUM(input_tokens), SUM(output_tokens) FROM usage WHERE {F}",G);A,B=D.fetchone();A=A or 0;B=B or 0;return{'input':A,'output':B,'total':A+B}
@@ -26,6 +26,7 @@ from prompt_toolkit.styles import Style
26
26
  from prompt_toolkit import Application
27
27
  from rich.markdown import Markdown
28
28
  from rich.columns import Columns
29
+ from rich.console import Group
29
30
  from rich.markup import render
30
31
  from rich.panel import Panel
31
32
  from rich.text import Text
@@ -41,11 +42,11 @@ from src.tui.utils.trender import display_tool_call,display_tool_result,display_
41
42
  from langchain_core.messages import HumanMessage
42
43
  from langgraph.types import Command
43
44
  from dotenv import find_dotenv,load_dotenv
44
- d=load_dotenv(find_dotenv())
45
+ a=load_dotenv(find_dotenv())
45
46
  class LiveChatUI:
46
- def __init__(A,agent=_A,saver=_A,workspace=_A,**C):B='#afafff';A.agent=agent;A.saver=saver;A.kwargs=C;A.workspace=re.sub('^(\\/Users\\/[^/]+|\\/home\\/[^/]+)','~',workspace);A.thread_id=1;A.token_count=0;A.mode=_E;A.context=C.get('context',_A);A.instruction_manager=C.get('instruction_manager',_A);A.spinner=Spinner('block');A.max_input_lines=10;A.cancel_event=_A;A.logo_label=Label(LOGO.format(A.workspace),style='class:logo');A.begin_items=[A.logo_label];A.begin_area=HSplit([*A.begin_items],padding=1);A.log_control=ScrollableFormattedLogControl();A.output_area=Window(content=A.log_control,wrap_lines=_B,always_hide_cursor=_C,height=D(weight=1));A.status_label=FormattedTextControl(text=[(_H,' 状态: 等待输入 | Tokens: 0 (⌥ + ⏎ 换行 Esc 中断 ctrl + c 退出)')]);F=FormattedTextControl(text=[('class:spinner',f"{A.spinner.current_frame()}")],show_cursor=_C);G=FormattedTextControl(text=[(_F,f"mode: {A.mode}")]);A.status_bar=VSplit([Window(F,width=D(weight=5),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1)),Window(A.status_label,width=D(weight=85),height=D(weight=1)),Window(G,width=D(weight=10),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1))],width=D(weight=100));A.input_box=TextArea(height=1,prompt='> ',multiline=_B,wrap_lines=_B,scrollbar=_B,style='class:input_box');A.input_box.buffer.on_text_changed+=lambda _:A.update_input_area_height(d);A.kb=KeyBindings();A.a();H=Frame(body=A.input_box,style='class:frame');A.input_items=[A.status_bar,H];A.input_area=HSplit([*A.input_items],padding=0);A.interact_items=[];A.interact_area=HSplit([*A.interact_items],padding=1);E=_F;A.footer=VSplit([Window(FormattedTextControl([(E,f"{A.workspace} (main) ")]),width=D(weight=50)),Window(FormattedTextControl([(E,f"MCP: (0/0) ")]),width=D(weight=20)),Window(FormattedTextControl([(E,'Env: (local) ')]),width=D(weight=20)),Window(FormattedTextControl([(E,f"Model: kimi-k2-0711-preview ")]),wrap_lines=_B,dont_extend_width=_C,always_hide_cursor=_B,width=D(weight=10))],width=D(weight=100),height=1);A.suggest_items=[A.footer];A.suggest_area=HSplit([*A.suggest_items],padding=0);A.logo_area=DynamicContainer(lambda:A.begin_area);A.display_container=DynamicContainer(lambda:A.output_area);A.input_container=DynamicContainer(lambda:A.input_area);A.status_area=DynamicContainer(lambda:A.suggest_area);A.layout=Layout(HSplit([A.logo_area,A.output_area,A.interact_area,A.input_container,A.status_area],padding=0),focused_element=A.input_box);A.style=Style.from_dict({'logo':B,'output':B,'input_box':B,'status':B,'frame.border':B,'suggestions':B,'footer':B,'suggestion.label':B,'suggestion.desc':'#5f5f5f','spinner':B,'suggestion.selected':'bold #00afff'});A.app=Application(layout=A.layout,key_bindings=A.kb,style=A.style,full_screen=_C,mouse_support=_B);A.app.input_area=A.input_box;A.app.kb=A.kb;A.interrupt_tools=C.get('interrupt_tools',[]);A.toolcall_mode='manul'
47
- def c(A,role='user',spinner='●',status='',tokens=0):A.status_label.text=[(_H,f" 状态: {status} | ({role}) | Tokens: {tokens} (esc + ⏎ 换行 按两次 esc 中断 ctrl + c 退出)")];A.app.invalidate()
48
- def e(A,workspace=_A,mcp_status=_A,sandbox_status=_A,model_status=_A):
47
+ def __init__(A,agent=_A,saver=_A,workspace=_A,**C):B='#afafff';A.agent=agent;A.saver=saver;A.kwargs=C;A.workspace=re.sub('^(\\/Users\\/[^/]+|\\/home\\/[^/]+)','~',workspace);A.thread_id=1;A.token_count=0;A.mode=_E;A.context=C.get('context',_A);A.instruction_manager=C.get('instruction_manager',_A);A.spinner=Spinner('block');A.max_input_lines=10;A.cancel_event=_A;A.logo_label=Label(LOGO.format(A.workspace),style='class:logo');A.begin_items=[A.logo_label];A.begin_area=HSplit([*A.begin_items],padding=1);A.log_control=ScrollableFormattedLogControl();A.output_area=Window(content=A.log_control,wrap_lines=_B,always_hide_cursor=_C,height=D(weight=1));A.status_label=FormattedTextControl(text=[(_H,' 状态: 等待输入 | Tokens: 0 (⌥ + ⏎ 换行 Esc 中断 ctrl + c 退出)')]);F=FormattedTextControl(text=[('class:spinner',f"{A.spinner.current_frame()}")],show_cursor=_C);G=FormattedTextControl(text=[(_F,f"mode: {A.mode}")]);A.status_bar=VSplit([Window(F,width=D(weight=5),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1)),Window(A.status_label,width=D(weight=85),height=D(weight=1)),Window(G,width=D(weight=10),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1))],width=D(weight=100));A.input_box=TextArea(height=1,prompt='> ',multiline=_B,wrap_lines=_B,scrollbar=_B,style='class:input_box');A.input_box.buffer.on_text_changed+=lambda _:A.update_input_area_height(a);A.kb=KeyBindings();A.e();H=Frame(body=A.input_box,style='class:frame');A.input_items=[A.status_bar,H];A.input_area=HSplit([*A.input_items],padding=0);A.interact_items=[];A.interact_area=HSplit([*A.interact_items],padding=1);E=_F;A.footer=VSplit([Window(FormattedTextControl([(E,f"{A.workspace} (main) ")]),width=D(weight=50)),Window(FormattedTextControl([(E,f"MCP: (0/0) ")]),width=D(weight=20)),Window(FormattedTextControl([(E,'Env: (local) ')]),width=D(weight=20)),Window(FormattedTextControl([(E,f"Model: kimi-k2-0711-preview ")]),wrap_lines=_B,dont_extend_width=_C,always_hide_cursor=_B,width=D(weight=10))],width=D(weight=100),height=1);A.suggest_items=[A.footer];A.suggest_area=HSplit([*A.suggest_items],padding=0);A.logo_area=DynamicContainer(lambda:A.begin_area);A.display_container=DynamicContainer(lambda:A.output_area);A.input_container=DynamicContainer(lambda:A.input_area);A.status_area=DynamicContainer(lambda:A.suggest_area);A.layout=Layout(HSplit([A.logo_area,A.output_area,A.interact_area,A.input_container,A.status_area],padding=0),focused_element=A.input_box);A.style=Style.from_dict({'logo':B,'output':B,'input_box':B,'status':B,'frame.border':B,'suggestions':B,'footer':B,'suggestion.label':B,'suggestion.desc':'#5f5f5f','spinner':B,'suggestion.selected':'bold #00afff'});A.app=Application(layout=A.layout,key_bindings=A.kb,style=A.style,full_screen=_B,mouse_support=_B);A.app.input_area=A.input_box;A.app.kb=A.kb;A.interrupt_tools=C.get('interrupt_tools',[]);A.toolcall_mode='manul'
48
+ def d(A,role='user',spinner='●',status='',tokens=0):A.status_label.text=[(_H,f" 状态: {status} | ({role}) | Tokens: {tokens} (esc + ⏎ 换行 按两次 esc 中断 ctrl + c 退出)")];A.app.invalidate()
49
+ def b(A,workspace=_A,mcp_status=_A,sandbox_status=_A,model_status=_A):
49
50
  F=model_status;E=sandbox_status;D=mcp_status;C=workspace;B=_F
50
51
  if C is not _A:A._footer_workspace.text=[(B,f"{C}(main) ")]
51
52
  if D is not _A:A._footer_context.text=[(B,f"{D} ")]
@@ -58,7 +59,7 @@ class LiveChatUI:
58
59
  def clear(A):A.log_control.clear();A.input_box.text='';B=render_info(LOGO.format(A.workspace),style='light_stell_blue',markdown=_C);A.log_control.append_text(B);A.app.invalidate();A.app.layout.focus(A.input_box)
59
60
  async def updater(A):await A.spinner.run(A.app)
60
61
  async def run_async(A):await asyncio.gather(A.app.run_async(),A.updater())
61
- def a(A):
62
+ def e(A):
62
63
  D='enter';C='escape'
63
64
  @A.kb.add(D)
64
65
  def B(event):
@@ -77,19 +78,19 @@ class LiveChatUI:
77
78
  if A.logo_label in A.begin_items:A.begin_items.remove(A.logo_label);A.begin_area.children=list(A.begin_items);J=render_info(LOGO.format(A.workspace),style=D,markdown=_C);A.log_control.append_text(J);A.app.invalidate()
78
79
  if B.strip()in['quit','exit','q']:get_app().exit();return
79
80
  if B.strip()in['/clear','clear']:A.clear();return
80
- A.spinner.start();A.b('● user',f"● {B}",style='light_salmon3');await asyncio.sleep(.05)
81
+ A.spinner.start();A.c('● user',f"● {B}",style='light_salmon3');await asyncio.sleep(.05)
81
82
  if B.strip()in['/commands']and A.instruction_manager:
82
83
  E=[]
83
84
  for F in A.instruction_manager.list_instructions():E.append(f"/{F.name}: - {F.settings[_D]}")
84
- K='\n'.join(E);A.b('● bot',K,style=D,markdown=_B);return
85
+ K='\n'.join(E);A.c('● bot',K,style=D,markdown=_B);return
85
86
  if A.instruction_manager:G=A.instruction_manager.parse(B);H,I=G['executed_instruction'],G['message'];B=f"""
86
87
  [注意]: 执行用户请求必须严格遵循如下准则:
87
88
  {H}
88
89
 
89
90
  用户请求:
90
91
  {I}"""if H else I
91
- C=A.context if C is _A else C;L=await A._handle_stream('○ bot',A._stream_generate(B,C),style=D,markdown=_B,context=C);A.spinner.stop();A.c(spinner='',status='等待输入',tokens=A.token_count);A.app.layout.focus(A.input_box);return L
92
- def b(A,sender,message,style='green',markdown=_C):D=markdown;C=style;B=message;E=Markdown(B)if D else Text(B,style=C);F=render_panel(sender,E,C,D);A.log_control.append_text(F);A.app.invalidate()
92
+ C=A.context if C is _A else C;L=await A._handle_stream('○ bot',A._stream_generate(B,C),style=D,markdown=_B,context=C);A.spinner.stop();A.d(spinner='',status='等待输入',tokens=A.token_count);A.app.layout.focus(A.input_box);return L
93
+ def c(A,sender,message,style='green',markdown=_C):D=markdown;C=style;B=message;E=Markdown(B)if D else Text(B,style=C);F=render_panel(sender,E,C,D);A.log_control.append_text(F);A.app.invalidate()
93
94
  async def _stream_generate(A,prompt,context=_A):
94
95
  B=A.agent.astream({_G:[HumanMessage(content=prompt)]},config={_I:{_J:A.thread_id}},stream_mode=[_G,_K,_L],context=context);A.cancel_event=asyncio.Event()
95
96
  async for C in astream_handler(B,interrupt_tools=A.interrupt_tools,tool_mode=A.toolcall_mode):
@@ -142,7 +143,7 @@ class LiveChatUI:
142
143
  D.spinner.stop();j=[];p=B['interrupt_id']
143
144
  for d in B[m][m]['action_requests']:w=d[P];x=d[Q];y=d[_D];q=await D._handle_human_interrupt(message=f" 允许执行当前函数么? ",options=[{h:'是的,允许当前函数执行',_D:''},{h:'是的,总是允许执行,当前对话过程中不再提示',_D:''},{h:'不, 不允许当前函数执行',_D:''}]);k=['approve',_E,'reject'][q];D.toolcall_mode=_E if k==_E else'manual';j.append({I:k})
144
145
  D.spinner.start();await D._handle_stream(a,D._resume_generate(p,j,i),style=b,markdown=c,items=A,context=i);break
145
- r=Columns([A[0]for A in A],column_first=_B,expand=_B);D.log_control.update_last(render_panel(a,r,b,c));D.app.invalidate();await asyncio.sleep(.03);s='';D.c(spinner=s,status='正在生成 ...',tokens=D.token_count)
146
+ r=Group(*[A[0]for A in A]);D.log_control.update_last(render_panel(a,r,b,c));D.app.invalidate();await asyncio.sleep(.03);s='';D.d(spinner=s,status='正在生成 ...',tokens=D.token_count)
146
147
  return R
147
148
  async def _handle_human_interrupt(A,message,options):
148
149
  E=asyncio.get_event_loop();C=E.create_future();D=A.app.key_bindings
@@ -19,20 +19,20 @@ from prompt_toolkit.layout import Layout,HSplit
19
19
  from prompt_toolkit.styles import Style
20
20
  from prompt_toolkit import Application
21
21
  class InterruptSelector:
22
- def __init__(A,description,options,callback):B=description;A.options=options;A.description=B;A.selected_index=0;A.callback=callback;A.rows=A.p();A.list_container=HSplit(A.rows,padding=0);A.markdown=A.r(B);C=Window(content=A.markdown.content,height=A.markdown.height,dont_extend_height=_B,style='class:desc');D=Frame(body=C);A.container=HSplit([C,A.list_container]);A.kb=KeyBindings();A.q()
22
+ def __init__(A,description,options,callback):B=description;A.options=options;A.description=B;A.selected_index=0;A.callback=callback;A.rows=A.p();A.list_container=HSplit(A.rows,padding=0);A.markdown=A.r(B);C=Window(content=A.markdown.content,height=A.markdown.height,dont_extend_height=_B,style='class:desc');D=Frame(body=C);A.container=HSplit([C,A.list_container]);A.kb=KeyBindings();A.v()
23
23
  def r(C,content):A=StringIO();B=Console(file=A,width=80,force_terminal=_B,color_system='truecolor');B.print(Align.left(Markdown(content)),justify='left');return Window(content=FormattedTextControl(ANSI(A.getvalue())),height=D(min=1))
24
24
  def p(A):
25
25
  E='class:suggestion.selected';B=[]
26
26
  for(F,C)in enumerate(A.options):D=F==A.selected_index;G='> 'if D else' ';H=E if D else'class:suggestion.label';I='class:suggestion.desc';J=VSplit([Window(FormattedTextControl([(E,G)]),width=2),Window(FormattedTextControl([(H,C[_A])]),width=60),Window(FormattedTextControl([(I,C[_C])]),wrap_lines=_B,dont_extend_width=False,always_hide_cursor=_B)],height=1);B.append(J)
27
27
  return B
28
- def t(A):A.rows=A.p();A.list_container.children=A.rows;get_app().invalidate()
29
- def q(A):
28
+ def s(A):A.rows=A.p();A.list_container.children=A.rows;get_app().invalidate()
29
+ def v(A):
30
30
  @A.kb.add('up')
31
31
  def B(event):
32
- if A.selected_index>0:A.selected_index-=1;A.t()
32
+ if A.selected_index>0:A.selected_index-=1;A.s()
33
33
  @A.kb.add('down')
34
34
  def C(event):
35
- if A.selected_index<len(A.options)-1:A.selected_index+=1;A.t()
35
+ if A.selected_index<len(A.options)-1:A.selected_index+=1;A.s()
36
36
  @A.kb.add('enter')
37
37
  def D(event):A.callback(A.selected_index)
38
38
  async def demo():
@@ -6,44 +6,44 @@ from prompt_toolkit.layout.controls import UIControl,UIContent
6
6
  from prompt_toolkit.data_structures import Point
7
7
  from prompt_toolkit.formatted_text import ANSI
8
8
  class ScrollableFormattedLogControl(FormattedTextControl):
9
- def __init__(A):A.lines=[];A.scroll_offset=0;A._height=0;A.last_count=0;super().__init__(A.y,focusable=True,show_cursor=False)
9
+ def __init__(A):A.lines=[];A.scroll_offset=0;A._height=0;A.last_count=0;super().__init__(A.z,focusable=True,show_cursor=False)
10
10
  def clear(A):A.lines=[];A.scroll_offset=0;A.last_count=0;A._height=0
11
- def append_text(A,ansi_text):B=ansi_text.splitlines();A.lines.extend(B);A.last_count=len(B);A.z()
11
+ def append_text(A,ansi_text):B=ansi_text.splitlines();A.lines.extend(B);A.last_count=len(B);A.x()
12
12
  def update_last(A,ansi_text):
13
13
  B=ansi_text.splitlines()
14
14
  if A.lines:A.lines=A.lines[:-A.last_count]+B
15
15
  else:A.lines=B
16
- A.last_count=len(B);A.z()
16
+ A.last_count=len(B);A.x()
17
17
  def refresh_scroll(A):
18
18
  if A._height:A.scroll_offset=max(0,len(A.lines)-A._height)
19
- def z(A):
19
+ def x(A):
20
20
  if A._height:A.scroll_offset=max(0,len(A.lines)-A._height)
21
- def x(A,amount):
21
+ def y(A,amount):
22
22
  if A._height:B=max(0,len(A.lines)-A._height);A.scroll_offset=max(0,min(A.scroll_offset+amount,B))
23
23
  def is_focusable(A):return True
24
- def y(A):
24
+ def z(A):
25
25
  C=A._height or 100;D=A.lines[A.scroll_offset:A.scroll_offset+C];B=[]
26
26
  for E in D:F=sanitize_ansi_text(E);B.extend(ANSI(F).__pt_formatted_text__());B.append(('','\n'))
27
27
  return B
28
28
  def create_content(B,width,height):A=height;B._height=A or 100;return super().create_content(width,A)
29
29
  def mouse_handler(A,mouse_event):
30
30
  B=mouse_event;C=5
31
- if B.event_type==MouseEventType.SCROLL_UP:A.x(-C);return
32
- elif B.event_type==MouseEventType.SCROLL_DOWN:A.x(C);return
31
+ if B.event_type==MouseEventType.SCROLL_UP:A.y(-C);return
32
+ elif B.event_type==MouseEventType.SCROLL_DOWN:A.y(C);return
33
33
  return NotImplemented
34
34
  class ScrollableLogControl(UIControl):
35
35
  def __init__(A):A.lines=[];A.scroll_offset=0;A.visible_lines=[];A._height=0;A.last_count=0
36
36
  def clear(A):A.visible_lines=[];A.lines=[];A.scroll_offset=0;A.last_count=0;A._height=0
37
- def append_text(A,ansi_text):B=ansi_text.splitlines();A.lines.extend(B);A.last_count=len(B);A.z()
37
+ def append_text(A,ansi_text):B=ansi_text.splitlines();A.lines.extend(B);A.last_count=len(B);A.x()
38
38
  def update_last(A,ansi_text):
39
39
  B=ansi_text.splitlines()
40
40
  if A.lines:A.lines=A.lines[:-A.last_count]+B
41
41
  else:A.lines=B
42
- A.last_count=len(B);A.z()
42
+ A.last_count=len(B);A.x()
43
43
  def refresh_scroll(A):
44
44
  if A._height:A.scroll_offset=max(0,len(A.lines)-A._height)
45
- def z(A):A.scroll_offset=max(0,len(A.lines)-A._height)
46
- def x(A,amount):A.scroll_offset=max(0,min(A.scroll_offset+amount,max(0,len(A.lines)-A._height)))
45
+ def x(A):A.scroll_offset=max(0,len(A.lines)-A._height)
46
+ def y(A,amount):A.scroll_offset=max(0,min(A.scroll_offset+amount,max(0,len(A.lines)-A._height)))
47
47
  def is_focusable(A):return True
48
48
  def create_content(A,width,height):B=height;A._height=B;A.visible_lines=A.lines[A.scroll_offset:A.scroll_offset+B];return UIContent(get_line=A.get_line,line_count=len(A.visible_lines),cursor_position=Point(0,len(A.visible_lines)-1))
49
49
  def get_line(B,lineno):
@@ -52,7 +52,7 @@ class ScrollableLogControl(UIControl):
52
52
  C=sanitize_ansi_text(B.visible_lines[A]);return ANSI(C).__pt_formatted_text__()
53
53
  def mouse_handler(A,mouse_event):
54
54
  B=mouse_event
55
- if B.event_type==MouseEventType.SCROLL_UP:A.x(-1);return
56
- elif B.event_type==MouseEventType.SCROLL_DOWN:A.x(1);return
55
+ if B.event_type==MouseEventType.SCROLL_UP:A.y(-1);return
56
+ elif B.event_type==MouseEventType.SCROLL_DOWN:A.y(1);return
57
57
  return NotImplemented
58
58
  def sanitize_ansi_text(text):return re.sub('[¹²³⁰⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉]',' ',text)
@@ -19,6 +19,7 @@ _A='\n'
19
19
  from pathlib import Path,PurePath
20
20
  import json,os
21
21
  from rich.columns import Columns
22
+ from rich.console import Group
22
23
  from rich.panel import Panel
23
24
  from rich.text import Text
24
25
  from rich.tree import Tree
@@ -27,7 +28,7 @@ from src.tui.components.tdiff import render_diff_as_markdown
27
28
  from src.tui.components.tdisplay import render_content_with_line_limit
28
29
  from src.tui.utils.render import markdown_to_wrapped_text
29
30
  def display_tool_call(tool_name,args,events=[]):
30
- C=tool_name;A=events;E=format_tool_compact(C,args);D=h(C,args);B=Tree(Text(_I,style=_J)+Text(f"{E}",style=_C),guide_style=_D)
31
+ C=tool_name;A=events;E=format_tool_compact(C,args);D=l(C,args);B=Tree(Text(_I,style=_J)+Text(f"{E}",style=_C),guide_style=_D)
31
32
  if A and len(A)>0:F=display_sub_panel(D,sub_events=A);B.add(F)
32
33
  else:B.add(D)
33
34
  return B
@@ -36,14 +37,14 @@ def display_tool_result(tool_name,args=_E,result=_E,events=[]):
36
37
  if A is _E:A={}
37
38
  H=format_tool_compact(G,A);C=G.lower()
38
39
  try:
39
- if C==_K and isinstance(A,dict):B=f(A)
40
- elif C==_L and isinstance(A,dict):B=f(A)
41
- elif C in[_M]and isinstance(A,dict):B=f(A)
42
- elif C==_H and isinstance(A,dict):B=o(A,D)
43
- elif C==_N and isinstance(A,dict):B=m(A,D)
44
- elif C==_O and isinstance(A,dict):B=n(A,D)
45
- elif C==_P and isinstance(A,dict):B=l(A,D)
46
- else:B=k(A,D)
40
+ if C==_K and isinstance(A,dict):B=o(A)
41
+ elif C==_L and isinstance(A,dict):B=o(A)
42
+ elif C in[_M]and isinstance(A,dict):B=o(A)
43
+ elif C==_H and isinstance(A,dict):B=f(A,D)
44
+ elif C==_N and isinstance(A,dict):B=g(A,D)
45
+ elif C==_O and isinstance(A,dict):B=h(A,D)
46
+ elif C==_P and isinstance(A,dict):B=i(A,D)
47
+ else:B=j(A,D)
47
48
  except Exception as I:import traceback as J;B=Text(f"Error: {str(I)}\n{J.format_exc()}",style='red')
48
49
  F=Tree(Text(_I,style=_J)+Text(f"{H}",style=_C),guide_style=_D)
49
50
  if E and len(E)>0:K=display_sub_panel(B,sub_events=E);F.add(K)
@@ -101,12 +102,12 @@ def format_tool_compact(name,args):
101
102
  I=', '.join(M)
102
103
  if len(I)>50:I=I[:47]+_B
103
104
  return f"{J}({I})"
104
- def h(tool_name,arguments):
105
+ def l(tool_name,arguments):
105
106
  D=' Will search for pattern';C=arguments;B='bold grey';A=tool_name.lower()
106
107
  if A==_H:return Text(f"running ...",style=B)
107
108
  if A==_M:return Text(f" Executing ...",style=B)
108
- elif A==_L:return g(C)
109
- elif A==_K:return j(C)
109
+ elif A==_L:return m(C)
110
+ elif A==_K:return k(C)
110
111
  elif A in[_N,_O]:return Text(D,style=B)
111
112
  elif A==_P:return Text(D,stype=B)
112
113
  else:return Text(' Executing ...',style=B)
@@ -121,13 +122,13 @@ def display_sub_panel(result=_E,sub_events=[]):
121
122
  if isinstance(C,str):A.append(markdown_to_wrapped_text(f"{C[:200]}",prefix=_E))
122
123
  else:A.append(C)
123
124
  else:A.append(markdown_to_wrapped_text(f"calling ...",prefix=_E))
124
- J=Columns(A,column_first=True,expand=True);K=Box(' \n \n \n \n \n \n \n ');L=Panel(J,title_align='left',box=K);return L
125
- def j(arguments):
125
+ J=Group(*[A for A in A]);K=Box(' \n \n \n \n \n \n \n ');L=Panel(J,title_align='left',box=K);return L
126
+ def k(arguments):
126
127
  A=arguments;B=A.get(_F,'');C=A.get('old_string','');D=A.get('new_string','');E=''
127
128
  if os.path.exists(B):
128
129
  with open(B,'r',encoding='utf-8')as F:G=F.read()
129
130
  H=D if C==''else G.replace(C,D,1);I=render_diff_as_markdown(E,H);return I
130
- def g(arguments):
131
+ def m(arguments):
131
132
  D=arguments;A=D.get(_F,'');B=D.get(_R,'')
132
133
  if os.path.exists(A):C=open(A,'r').read()
133
134
  else:C=''
@@ -136,7 +137,7 @@ def g(arguments):
136
137
  E=recognize_language(A)
137
138
  if E:G=f"```\n{E}\n{B}\n```";return render_content_with_line_limit(G,'',100)
138
139
  else:return render_content_with_line_limit(B,'',100)
139
- def f(arguments):
140
+ def o(arguments):
140
141
  B=arguments.get(_F,'');C=''
141
142
  if os.path.exists(B):C=open(B,'r').read()
142
143
  E=C.split(_A);F=len(E);A=50
@@ -145,7 +146,7 @@ def f(arguments):
145
146
  H=recognize_language(B)
146
147
  if H:J=f"```{H}\n{D}\n```";return render_content_with_line_limit(J,'',-1)
147
148
  else:return render_content_with_line_limit(D,'',-1)
148
- def i(arguments,result):
149
+ def n(arguments,result):
149
150
  C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
150
151
  for(H,A)in enumerate(G):
151
152
  I=''if H==0 else''
@@ -154,7 +155,7 @@ def i(arguments,result):
154
155
  E=F-D
155
156
  if E>0:B.append(f" ... and {E} more lines")
156
157
  return Text(_A.join(B),style=_G)
157
- def o(arguments,result):
158
+ def f(arguments,result):
158
159
  C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
159
160
  for(H,A)in enumerate(G):
160
161
  I=''if H==0 else''
@@ -163,7 +164,7 @@ def o(arguments,result):
163
164
  E=F-D
164
165
  if E>0:B.append(f" ... and {E} more lines")
165
166
  return Text(_A.join(B),style=_G)
166
- def m(arguments,result):
167
+ def g(arguments,result):
167
168
  C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
168
169
  for(H,A)in enumerate(G):
169
170
  I=''if H==0 else''
@@ -172,7 +173,7 @@ def m(arguments,result):
172
173
  E=F-D
173
174
  if E>0:B.append(f" ... and {E} more lines")
174
175
  return Text(_A.join(B),style=_G)
175
- def n(arguments,result):
176
+ def h(arguments,result):
176
177
  C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
177
178
  for(H,A)in enumerate(G):
178
179
  I=''if H==0 else''
@@ -181,7 +182,7 @@ def n(arguments,result):
181
182
  E=F-D
182
183
  if E>0:B.append(f" ... and {E} more lines")
183
184
  return Text(_A.join(B),style=_G)
184
- def l(arguments,result):
185
+ def i(arguments,result):
185
186
  C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
186
187
  for(H,A)in enumerate(G):
187
188
  I=''if H==0 else''
@@ -190,7 +191,7 @@ def l(arguments,result):
190
191
  E=F-D
191
192
  if E>0:B.append(f" ... and {E} more lines")
192
193
  return Text(_A.join(B),style=_G)
193
- def k(arguments,result):
194
+ def j(arguments,result):
194
195
  B=result;A=str(B)if B else'tool completed successfully'
195
196
  if len(A)>500:C=A[:500]+'\n ... (truncated)'
196
197
  else:C=A
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandon_9527/tcode",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "TypeScript版 单仓库混合方案(Python源码随npm发布,本地Build)",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {