agent-code 2.0.0__py3-none-any.whl → 2.0.1__py3-none-any.whl
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.
- agent_code/__init__.py +1 -1
- agent_code/agent.py +70 -4
- agent_code/tui.py +87 -16
- agent_code-2.0.1.dist-info/METADATA +1048 -0
- {agent_code-2.0.0.dist-info → agent_code-2.0.1.dist-info}/RECORD +9 -9
- agent_code-2.0.0.dist-info/METADATA +0 -702
- {agent_code-2.0.0.dist-info → agent_code-2.0.1.dist-info}/WHEEL +0 -0
- {agent_code-2.0.0.dist-info → agent_code-2.0.1.dist-info}/entry_points.txt +0 -0
- {agent_code-2.0.0.dist-info → agent_code-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {agent_code-2.0.0.dist-info → agent_code-2.0.1.dist-info}/top_level.txt +0 -0
agent_code/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Agent + Skill Store — local AI agent with headless browser and skills."""
|
|
2
|
-
__version__ = "2.0.
|
|
2
|
+
__version__ = "2.0.1"
|
agent_code/agent.py
CHANGED
|
@@ -655,7 +655,7 @@ MAX_LOG_LINES = 900
|
|
|
655
655
|
MAX_LOG_BATCH = 40
|
|
656
656
|
MAX_RENDERED_RESPONSE_CHARS = 24000
|
|
657
657
|
MAX_USER_MESSAGE_CHARS = 100000
|
|
658
|
-
LLM_TIMEOUT =
|
|
658
|
+
LLM_TIMEOUT = 600
|
|
659
659
|
|
|
660
660
|
PERMISSION_READONLY = "readonly"
|
|
661
661
|
PERMISSION_ASK = "ask"
|
|
@@ -6364,6 +6364,12 @@ class App:
|
|
|
6364
6364
|
self.activity.see("end")
|
|
6365
6365
|
self.activity.configure(state="disabled")
|
|
6366
6366
|
continue
|
|
6367
|
+
if tag in ("stream_chunk", "stream_end"):
|
|
6368
|
+
self.log.configure(state="normal")
|
|
6369
|
+
self.log.insert("end", text if tag == "stream_chunk" else "\n")
|
|
6370
|
+
self.log.see("end")
|
|
6371
|
+
self.log.configure(state="disabled")
|
|
6372
|
+
continue
|
|
6367
6373
|
self.log.configure(state="normal")
|
|
6368
6374
|
if tag == "agent_md":
|
|
6369
6375
|
# Bound markdown parsing/rendering cost for very long answers.
|
|
@@ -6827,12 +6833,70 @@ class App:
|
|
|
6827
6833
|
if self.execution_mode == "build" else
|
|
6828
6834
|
[schema for schema in self.all_schemas
|
|
6829
6835
|
if schema.get("function", {}).get("name") in plan_read_only])
|
|
6830
|
-
|
|
6836
|
+
# Stream model output into the desktop transcript as it arrives.
|
|
6837
|
+
# Tool calls are accumulated by index and are not dispatched until
|
|
6838
|
+
# the provider has finished sending the complete arguments.
|
|
6839
|
+
stream = self._create_completion_with_retry(
|
|
6831
6840
|
purpose="the model response",
|
|
6832
6841
|
model=self.model, messages=self.messages,
|
|
6833
6842
|
tools=compact_tool_schemas(active_schemas) or None,
|
|
6834
6843
|
tool_choice=("auto" if active_schemas else "none"),
|
|
6835
|
-
temperature=0.2, timeout=LLM_TIMEOUT)
|
|
6844
|
+
temperature=0.2, timeout=LLM_TIMEOUT, stream=True)
|
|
6845
|
+
content_parts = []
|
|
6846
|
+
tool_parts = {}
|
|
6847
|
+
try:
|
|
6848
|
+
for chunk in stream:
|
|
6849
|
+
if self._stop_event.is_set():
|
|
6850
|
+
close = getattr(stream, "close", None)
|
|
6851
|
+
if callable(close):
|
|
6852
|
+
close()
|
|
6853
|
+
break
|
|
6854
|
+
choices = getattr(chunk, "choices", None) or []
|
|
6855
|
+
if not choices:
|
|
6856
|
+
continue
|
|
6857
|
+
delta = getattr(choices[0], "delta", None)
|
|
6858
|
+
if delta is None:
|
|
6859
|
+
continue
|
|
6860
|
+
piece = getattr(delta, "content", None)
|
|
6861
|
+
if isinstance(piece, str) and piece:
|
|
6862
|
+
content_parts.append(piece)
|
|
6863
|
+
self.log_line(piece, "stream_chunk")
|
|
6864
|
+
for call in (getattr(delta, "tool_calls", None) or []):
|
|
6865
|
+
index = getattr(call, "index", 0)
|
|
6866
|
+
slot = tool_parts.setdefault(index, {"id": "", "name": "", "arguments": ""})
|
|
6867
|
+
if getattr(call, "id", None):
|
|
6868
|
+
slot["id"] = call.id
|
|
6869
|
+
function = getattr(call, "function", None)
|
|
6870
|
+
if function is not None:
|
|
6871
|
+
if getattr(function, "name", None):
|
|
6872
|
+
slot["name"] += function.name
|
|
6873
|
+
if getattr(function, "arguments", None):
|
|
6874
|
+
slot["arguments"] += function.arguments
|
|
6875
|
+
finally:
|
|
6876
|
+
close = getattr(stream, "close", None)
|
|
6877
|
+
if callable(close):
|
|
6878
|
+
try:
|
|
6879
|
+
close()
|
|
6880
|
+
except Exception:
|
|
6881
|
+
pass
|
|
6882
|
+
if self._stop_event.is_set():
|
|
6883
|
+
self.log_line("[stopped during response streaming]", "error")
|
|
6884
|
+
self._stopped_last = True
|
|
6885
|
+
self._can_continue = True
|
|
6886
|
+
self._last_run_status = "stopped"
|
|
6887
|
+
return
|
|
6888
|
+
from types import SimpleNamespace
|
|
6889
|
+
assembled_calls = []
|
|
6890
|
+
for index, slot in sorted(tool_parts.items()):
|
|
6891
|
+
if not slot["id"]:
|
|
6892
|
+
slot["id"] = "call_" + uuid.uuid4().hex
|
|
6893
|
+
assembled_calls.append(SimpleNamespace(
|
|
6894
|
+
id=slot["id"],
|
|
6895
|
+
function=SimpleNamespace(name=slot["name"], arguments=slot["arguments"])))
|
|
6896
|
+
if content_parts:
|
|
6897
|
+
self.log_line("", "stream_end")
|
|
6898
|
+
msg = SimpleNamespace(content="".join(content_parts), tool_calls=assembled_calls)
|
|
6899
|
+
resp = SimpleNamespace(choices=[SimpleNamespace(message=msg)])
|
|
6836
6900
|
except Exception as e:
|
|
6837
6901
|
raw_error = f"{type(e).__name__}: {e}"
|
|
6838
6902
|
import re
|
|
@@ -6869,7 +6933,9 @@ class App:
|
|
|
6869
6933
|
if self._last_agent_answer.lower() in {"(done)", "done", "(completed)", "completed"}:
|
|
6870
6934
|
self.log_line("Done.", "dim")
|
|
6871
6935
|
elif self._last_agent_answer:
|
|
6872
|
-
|
|
6936
|
+
# The response has already been appended incrementally through
|
|
6937
|
+
# stream_chunk events; do not print it a second time.
|
|
6938
|
+
pass
|
|
6873
6939
|
else:
|
|
6874
6940
|
self.log_line("Done.", "dim")
|
|
6875
6941
|
self._can_continue = False
|
agent_code/tui.py
CHANGED
|
@@ -39,7 +39,7 @@ def api_keys(cfg):
|
|
|
39
39
|
class TUI:
|
|
40
40
|
def __init__(self, screen):
|
|
41
41
|
self.s=screen; self.cfg=read_config(); self.messages=[]; self.notice="Welcome. Press ? for help."; self.mode="Build"
|
|
42
|
-
self.input=""; self.scroll=0; self.cwd="."; self.page="Chat"; self.running=True; self.busy=False; self.cancel_event=threading.Event(); self.events=queue.Queue(); self.activity=["Ready — send a task to watch agent progress."]; self.tool_details=[]
|
|
42
|
+
self.input=""; self.scroll=0; self.cwd="."; self.page="Chat"; self.running=True; self.busy=False; self.cancel_event=threading.Event(); self.events=queue.Queue(); self.activity=["Ready — send a task to watch agent progress."]; self.tool_details=[]; self.activity_scroll=0; self.stream_text=""
|
|
43
43
|
curses.curs_set(1); curses.start_color(); curses.use_default_colors()
|
|
44
44
|
curses.init_pair(1, curses.COLOR_CYAN, -1); curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN)
|
|
45
45
|
curses.init_pair(3, curses.COLOR_GREEN, -1); curses.init_pair(4, curses.COLOR_YELLOW, -1)
|
|
@@ -137,14 +137,24 @@ class TUI:
|
|
|
137
137
|
elif self.page=="Activity":
|
|
138
138
|
self.safe_add(4,content,"LIVE ACTIVITY — tool calls and results, not private model reasoning",curses.color_pair(1))
|
|
139
139
|
self.safe_add(6,content,"Workspace: "+str(WORKSPACE))
|
|
140
|
-
|
|
140
|
+
# Activity is a complete chronological event log. Tool arguments and
|
|
141
|
+
# results are included here (never private model reasoning).
|
|
142
|
+
lines = []
|
|
143
|
+
for item in self.activity:
|
|
144
|
+
lines.extend(textwrap.wrap(str(item), max(10,w-content-3), replace_whitespace=False) or [""])
|
|
145
|
+
for item in self.tool_details:
|
|
146
|
+
lines.append("─ TOOL DETAIL ─")
|
|
147
|
+
lines.extend(textwrap.wrap(str(item), max(10,w-content-3), replace_whitespace=False) or [""])
|
|
148
|
+
viewport=max(1,h-12)
|
|
149
|
+
max_back=max(0,len(lines)-viewport)
|
|
150
|
+
self.activity_scroll=max(0,min(self.activity_scroll,max_back))
|
|
151
|
+
end=len(lines)-self.activity_scroll
|
|
152
|
+
start=max(0,end-viewport)
|
|
153
|
+
for i,line in enumerate(lines[start:end]):
|
|
141
154
|
self.safe_add(8+i,content,line)
|
|
142
|
-
if
|
|
143
|
-
self.safe_add(
|
|
144
|
-
|
|
145
|
-
for j,line in enumerate(textwrap.wrap(detail, max(10,w-content-3))[:3]):
|
|
146
|
-
self.safe_add(max(9,h-4)+j,content,line)
|
|
147
|
-
self.safe_add(h-3,content,"Updates and recent tool output. Press 1 for Chat.",curses.A_DIM)
|
|
155
|
+
if max_back:
|
|
156
|
+
self.safe_add(6,content,f"Activity log · {len(lines)} lines · {self.activity_scroll} lines back (PgUp/PgDn)",curses.A_DIM)
|
|
157
|
+
self.safe_add(h-3,content,"Full activity and tool details. Press 1 for Chat.",curses.A_DIM)
|
|
148
158
|
elif self.page=="Skills":
|
|
149
159
|
d=paths.global_skills_dir(); names=sorted(p.name for p in d.iterdir() if p.is_dir()) if d.exists() else []
|
|
150
160
|
self.safe_add(4,content,f"Installed skills: {len(names)}")
|
|
@@ -314,7 +324,7 @@ class TUI:
|
|
|
314
324
|
client=make_client(keys,base,provider)
|
|
315
325
|
if client is None:
|
|
316
326
|
self.messages.append(("assistant","No API key configured. Add one or more keys in Settings.")); return
|
|
317
|
-
client.timeout=
|
|
327
|
+
client.timeout=600
|
|
318
328
|
# Use the same core workspace tools as the desktop agent, not a chat-only stub.
|
|
319
329
|
names={"read_file","write_file","append_file","edit_file","glob_files","grep_files","shell_run","background_shell","web_search","web_fetch","task","task_status"}
|
|
320
330
|
schemas=[x for x in engine.CORE_SCHEMAS if x.get("function",{}).get("name") in names]
|
|
@@ -378,8 +388,58 @@ class TUI:
|
|
|
378
388
|
self.activity.append("Task stopped by user.")
|
|
379
389
|
break
|
|
380
390
|
self.activity.append("Requesting model response…")
|
|
381
|
-
response
|
|
382
|
-
|
|
391
|
+
# Stream every response so slow/large generations remain visible.
|
|
392
|
+
# Assemble tool-call deltas before dispatch; partial JSON is never run.
|
|
393
|
+
self.stream_text = ""
|
|
394
|
+
self.messages.append(("assistant", ""))
|
|
395
|
+
stream = client.chat.completions.create(
|
|
396
|
+
model=model, messages=history, tools=active_schemas or None,
|
|
397
|
+
tool_choice=("auto" if active_schemas else "none"), stream=True,
|
|
398
|
+
timeout=600,
|
|
399
|
+
)
|
|
400
|
+
content_parts=[]
|
|
401
|
+
tool_parts={}
|
|
402
|
+
try:
|
|
403
|
+
for chunk in stream:
|
|
404
|
+
if self.cancel_event.is_set():
|
|
405
|
+
close=getattr(stream,"close",None)
|
|
406
|
+
if callable(close): close()
|
|
407
|
+
break
|
|
408
|
+
choices=getattr(chunk,"choices",[]) or []
|
|
409
|
+
if not choices: continue
|
|
410
|
+
delta=getattr(choices[0],"delta",None)
|
|
411
|
+
if delta is None: continue
|
|
412
|
+
piece=getattr(delta,"content",None)
|
|
413
|
+
if isinstance(piece,str) and piece:
|
|
414
|
+
content_parts.append(piece)
|
|
415
|
+
self.stream_text += piece
|
|
416
|
+
self.messages[-1] = ("assistant", self.stream_text)
|
|
417
|
+
for call in (getattr(delta,"tool_calls",None) or []):
|
|
418
|
+
idx=getattr(call,"index",0)
|
|
419
|
+
slot=tool_parts.setdefault(idx,{"id":"","name":"","arguments":""})
|
|
420
|
+
if getattr(call,"id",None): slot["id"]=call.id
|
|
421
|
+
fn=getattr(call,"function",None)
|
|
422
|
+
if fn:
|
|
423
|
+
if getattr(fn,"name",None): slot["name"] += fn.name
|
|
424
|
+
if getattr(fn,"arguments",None): slot["arguments"] += fn.arguments
|
|
425
|
+
finally:
|
|
426
|
+
close=getattr(stream,"close",None)
|
|
427
|
+
if callable(close):
|
|
428
|
+
try: close()
|
|
429
|
+
except Exception: pass
|
|
430
|
+
if self.cancel_event.is_set():
|
|
431
|
+
answer="Task stopped by user."
|
|
432
|
+
self.activity.append("Task stopped by user during streaming.")
|
|
433
|
+
break
|
|
434
|
+
from types import SimpleNamespace
|
|
435
|
+
assembled_calls=[]
|
|
436
|
+
for idx,slot in sorted(tool_parts.items()):
|
|
437
|
+
if not slot["id"]: slot["id"]="call_"+uuid.uuid4().hex
|
|
438
|
+
assembled_calls.append(SimpleNamespace(id=slot["id"],function=SimpleNamespace(name=slot["name"],arguments=slot["arguments"])))
|
|
439
|
+
msg=SimpleNamespace(content="".join(content_parts),tool_calls=assembled_calls)
|
|
440
|
+
# The streamed assistant placeholder becomes commentary when tools
|
|
441
|
+
# follow, or the final answer when no tools were requested.
|
|
442
|
+
self.messages.pop()
|
|
383
443
|
if self.cancel_event.is_set():
|
|
384
444
|
answer = "Task stopped by user."
|
|
385
445
|
self.activity.append("Task stopped by user.")
|
|
@@ -439,10 +499,16 @@ class TUI:
|
|
|
439
499
|
# Keep full tool data for the model, but show only a compact
|
|
440
500
|
# outcome in Chat so the final answer remains visible.
|
|
441
501
|
self.messages.append(("tool", user_result))
|
|
442
|
-
self.tool_details.append(
|
|
443
|
-
|
|
502
|
+
self.tool_details.append(
|
|
503
|
+
f"TOOL: {name}\nARGUMENTS: {json.dumps(args, ensure_ascii=False, default=str)}\nRESULT: {result_text[:12000]}"
|
|
504
|
+
)
|
|
505
|
+
self.tool_details = self.tool_details[-100:]
|
|
444
506
|
history.append({"role":"tool","tool_call_id":tc.id,"content":result_text[:12000]})
|
|
445
|
-
except Exception as e:
|
|
507
|
+
except Exception as e:
|
|
508
|
+
answer=f"Request failed: {type(e).__name__}: {e}"
|
|
509
|
+
if self.messages and self.messages[-1][0] == "assistant" and not self.messages[-1][1]:
|
|
510
|
+
self.messages.pop()
|
|
511
|
+
self.activity.append("Provider request failed: " + answer[:500])
|
|
446
512
|
self.messages.append(("assistant",answer)); self.activity.append("Task finished."); self.activity=self.activity[-200:]; self.notice="Ready"; self.busy=False
|
|
447
513
|
|
|
448
514
|
def install_podman(self):
|
|
@@ -480,6 +546,7 @@ class TUI:
|
|
|
480
546
|
self.messages.clear()
|
|
481
547
|
self.scroll = 0
|
|
482
548
|
self.tool_details.clear()
|
|
549
|
+
self.activity_scroll = 0
|
|
483
550
|
self.activity = ["Chat cleared."]
|
|
484
551
|
self.notice = "Chat cleared."
|
|
485
552
|
|
|
@@ -596,10 +663,14 @@ class TUI:
|
|
|
596
663
|
self.input += ch
|
|
597
664
|
continue
|
|
598
665
|
if ch == curses.KEY_PPAGE:
|
|
599
|
-
|
|
666
|
+
amount=max(3,self.s.getmaxyx()[0]//2)
|
|
667
|
+
if self.page=="Activity": self.activity_scroll += amount
|
|
668
|
+
else: self.scroll += amount
|
|
600
669
|
continue
|
|
601
670
|
if ch == curses.KEY_NPAGE:
|
|
602
|
-
|
|
671
|
+
amount=max(3,self.s.getmaxyx()[0]//2)
|
|
672
|
+
if self.page=="Activity": self.activity_scroll=max(0,self.activity_scroll-amount)
|
|
673
|
+
else: self.scroll=max(0,self.scroll-amount)
|
|
603
674
|
continue
|
|
604
675
|
if ch == curses.KEY_HOME and self.page == "Chat":
|
|
605
676
|
self.scroll = 10**9
|