tagword-codeagent 1.1.6__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.
- codeagent/__init__.py +10 -0
- codeagent/__main__.py +13 -0
- codeagent/cli/__init__.py +13 -0
- codeagent/cli/entrypoint.py +161 -0
- codeagent/cli/main.py +979 -0
- codeagent/core/__init__.py +2 -0
- codeagent/core/attachments.py +342 -0
- codeagent/core/audio_models.py +50 -0
- codeagent/core/bootstrap.py +178 -0
- codeagent/core/default_presets.py +159 -0
- codeagent/core/env.py +106 -0
- codeagent/core/folder_picker.py +151 -0
- codeagent/core/image_understanding.py +90 -0
- codeagent/core/models.py +6 -0
- codeagent/core/paths.py +238 -0
- codeagent/core/pricing.py +107 -0
- codeagent/core/process_ports.py +258 -0
- codeagent/core/seed_bridge.py +21 -0
- codeagent/core/settings.py +98 -0
- codeagent/core/speech_synth.py +330 -0
- codeagent/core/team_config.py +247 -0
- codeagent/core/team_manager.py +245 -0
- codeagent/core/token_counter.py +41 -0
- codeagent/core/tts_voices.py +103 -0
- codeagent/core/usage_billing.py +72 -0
- codeagent/core/vision_models.py +93 -0
- codeagent/memory/diary.py +144 -0
- codeagent/persona_defaults/README.md +12 -0
- codeagent/persona_defaults/agent.md +352 -0
- codeagent/persona_defaults/identity.md +29 -0
- codeagent/persona_defaults/memory.md +4 -0
- codeagent/persona_defaults/skills.md +325 -0
- codeagent/persona_defaults/soul.md +38 -0
- codeagent/persona_defaults/tools.md +41 -0
- codeagent/persona_defaults/user.md +18 -0
- codeagent/runtime/__init__.py +11 -0
- codeagent/runtime/agent_context.py +6 -0
- codeagent/runtime/agent_runtime.py +21 -0
- codeagent/runtime/compact_prompt.py +9 -0
- codeagent/runtime/compact_state.py +40 -0
- codeagent/runtime/llm_worker.py +132 -0
- codeagent/runtime/orchestrator.py +161 -0
- codeagent/runtime/prompt_enrichment.py +324 -0
- codeagent/runtime/session_manager.py +30 -0
- codeagent/runtime/task_split.py +38 -0
- codeagent/runtime/turn_loop.py +13 -0
- codeagent/runtime/worker.py +48 -0
- codeagent/server/__init__.py +505 -0
- codeagent/server/app_factory.py +1292 -0
- codeagent/server/attachment_api.py +261 -0
- codeagent/server/hub.py +175 -0
- codeagent/server/icon.png +0 -0
- codeagent/server/self_healing.py +143 -0
- codeagent/server/team_engine.py +243 -0
- codeagent/server/tts_api.py +51 -0
- codeagent/server/webui_api_app.py +3180 -0
- codeagent/skills/__init__.py +2 -0
- codeagent/skills/select.py +144 -0
- codeagent/tools/__init__.py +6 -0
- codeagent/tools/agent_tools.py +10 -0
- codeagent/web/__init__.py +1 -0
- codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
- codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
- codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
- codeagent/web/auth_impl.py +326 -0
- codeagent/web/static/00-marked.min.js +6 -0
- codeagent/web/static/00-mermaid.min.js +2314 -0
- codeagent/web/static/00-purify.min.js +3 -0
- codeagent/web/static/00-storage.js +83 -0
- codeagent/web/static/00-utils.js +51 -0
- codeagent/web/static/01-theme.css +96 -0
- codeagent/web/static/01a-scroll.js +86 -0
- codeagent/web/static/01b-think.js +62 -0
- codeagent/web/static/01c-session-identity.js +81 -0
- codeagent/web/static/01c-session-thinking.js +61 -0
- codeagent/web/static/01c-session-tree-menus.js +349 -0
- codeagent/web/static/01c-session-tree.js +531 -0
- codeagent/web/static/01c-session-utilities.js +83 -0
- codeagent/web/static/01c-vision-model.js +132 -0
- codeagent/web/static/01d-image-gen-model.js +84 -0
- codeagent/web/static/01e-audio-model.js +97 -0
- codeagent/web/static/01f-compose-ui.js +88 -0
- codeagent/web/static/01g-tts-settings.js +128 -0
- codeagent/web/static/01h-music-model.js +85 -0
- codeagent/web/static/01i-model-stack.js +291 -0
- codeagent/web/static/01j-video-gen-model.js +85 -0
- codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
- codeagent/web/static/01k-model-stack-state.js +209 -0
- codeagent/web/static/01q-inflight.js +195 -0
- codeagent/web/static/02-reset.css +22 -0
- codeagent/web/static/02-session-ui.js +201 -0
- codeagent/web/static/03-layout-p1.css +2 -0
- codeagent/web/static/03-layout-p2.css +2 -0
- codeagent/web/static/03a-links-stream.js +88 -0
- codeagent/web/static/03b-stream-text.js +119 -0
- codeagent/web/static/03c-render-bubble.js +520 -0
- codeagent/web/static/03d-agent-bubble.js +126 -0
- codeagent/web/static/03e-tool-trace.js +182 -0
- codeagent/web/static/03f-timeline.js +54 -0
- codeagent/web/static/03g-bubble-tts.js +292 -0
- codeagent/web/static/04-components-p1.css +1 -0
- codeagent/web/static/04-components-p2b.css +1 -0
- codeagent/web/static/04-components-p3.css +2 -0
- codeagent/web/static/04-components-p4.css +1 -0
- codeagent/web/static/04-components-p5.css +1 -0
- codeagent/web/static/04-components-p6.css +58 -0
- codeagent/web/static/04-components-p7.css +4 -0
- codeagent/web/static/04-components-p8.css +1 -0
- codeagent/web/static/04-components-p9.css +1 -0
- codeagent/web/static/04-ws-connect.js +351 -0
- codeagent/web/static/04-ws-helpers.js +116 -0
- codeagent/web/static/05-chat-p1.css +1058 -0
- codeagent/web/static/05-chat-p2.css +1 -0
- codeagent/web/static/05-chat-p3.css +295 -0
- codeagent/web/static/05a-session-history.js +176 -0
- codeagent/web/static/05b-session-list.js +114 -0
- codeagent/web/static/05c-dialog.js +123 -0
- codeagent/web/static/05d-project-create.js +172 -0
- codeagent/web/static/05e-project-remote.js +143 -0
- codeagent/web/static/06-chat.js +552 -0
- codeagent/web/static/06-utilities.css +181 -0
- codeagent/web/static/06b-attachments.js +304 -0
- codeagent/web/static/06c-camera.js +488 -0
- codeagent/web/static/07-activity-bar.css +127 -0
- codeagent/web/static/09-git-p1.css +1 -0
- codeagent/web/static/09-git-p2.css +1 -0
- codeagent/web/static/09a-init-bind.js +110 -0
- codeagent/web/static/09b-panel-toggle.js +80 -0
- codeagent/web/static/09c-api-refresh.js +100 -0
- codeagent/web/static/09d-status-log.js +80 -0
- codeagent/web/static/09e-commit.js +80 -0
- codeagent/web/static/09f-remote.js +93 -0
- codeagent/web/static/11a-cron-parse.js +117 -0
- codeagent/web/static/11b-cron-panel.js +100 -0
- codeagent/web/static/11c-cron-card.js +133 -0
- codeagent/web/static/11d-cron-form.js +105 -0
- codeagent/web/static/11e-env-config.js +168 -0
- codeagent/web/static/11f-env-llm-a-state.js +136 -0
- codeagent/web/static/11f-env-llm-b-form.js +409 -0
- codeagent/web/static/11f-env-llm-c-board.js +322 -0
- codeagent/web/static/11g-env-paths-git.js +973 -0
- codeagent/web/static/11h-env-mcp-a-state.js +161 -0
- codeagent/web/static/11h-env-mcp-b-form.js +229 -0
- codeagent/web/static/11h-env-mcp-c-board.js +565 -0
- codeagent/web/static/12-plugins.js +127 -0
- codeagent/web/static/13a-skills.js +156 -0
- codeagent/web/static/13a-todo-utils.js +140 -0
- codeagent/web/static/13b-todo-fetch.js +106 -0
- codeagent/web/static/13b-tools.js +127 -0
- codeagent/web/static/13c-todo-crud.js +92 -0
- codeagent/web/static/14-activity-bar.js +177 -0
- codeagent/web/static/15-plans.css +194 -0
- codeagent/web/static/15-plans.js +219 -0
- codeagent/web/static/16-mobile.css +612 -0
- codeagent/web/static/16-mobile.js +178 -0
- codeagent/web/static/17-preset-ux.css +287 -0
- codeagent/web/static/18-agent-mgr.css +616 -0
- codeagent/web/static/18-agent-mgr.js +204 -0
- codeagent/web/static/24-health-dashboard.js +68 -0
- codeagent/web/static/99-brand-polish.css +152 -0
- codeagent/web/static/body.html +824 -0
- codeagent/web/static/setup/setup_form.html +171 -0
- codeagent/web/static/setup/setup_head.html +100 -0
- codeagent/web/static/setup/setup_tail.html +264 -0
- codeagent/web/static/setup/setup_test.html +38 -0
- codeagent/web/web_login.html +265 -0
- codeagent/web/webui.html +35 -0
- tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
- tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
- tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
- tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
- tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
- tagword_codeagent-1.1.6.dist-info/top_level.txt +1 -0
codeagent/cli/main.py
ADDED
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
from seed_tools import setup_builtin_tools
|
|
10
|
+
|
|
11
|
+
from seed.core.persistence import ensure_session_dir, list_sessions, save_session
|
|
12
|
+
from seed.core.routing import find_commands, get_all_commands, get_command
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def cmd_restart(args):
|
|
16
|
+
tgt = getattr(args, "restart_cmd", "")
|
|
17
|
+
if tgt != "serve":
|
|
18
|
+
print("Usage: codeagent restart serve [--host ... --port ...]", file=sys.stderr)
|
|
19
|
+
sys.exit(2)
|
|
20
|
+
|
|
21
|
+
port = int(getattr(args, "port", 8765))
|
|
22
|
+
timeout_sec = float(getattr(args, "timeout_sec", 5.0))
|
|
23
|
+
|
|
24
|
+
from codeagent.core.process_ports import stop_listeners_on_port
|
|
25
|
+
|
|
26
|
+
stopped = stop_listeners_on_port(
|
|
27
|
+
port,
|
|
28
|
+
timeout_sec=timeout_sec,
|
|
29
|
+
exclude_pid=os.getpid(),
|
|
30
|
+
log=print,
|
|
31
|
+
)
|
|
32
|
+
if not stopped and os.name != "nt":
|
|
33
|
+
import shutil
|
|
34
|
+
|
|
35
|
+
if not shutil.which("lsof") and not shutil.which("ss") and not shutil.which("fuser"):
|
|
36
|
+
print(
|
|
37
|
+
"restart serve: install lsof (macOS) or iproute2/ss (Linux) "
|
|
38
|
+
"to stop the previous listener automatically.",
|
|
39
|
+
file=sys.stderr,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Start server in current process (foreground).
|
|
43
|
+
cmd_serve(args)
|
|
44
|
+
|
|
45
|
+
def cmd_run(args):
|
|
46
|
+
"""Handle run subcommand."""
|
|
47
|
+
prompt = getattr(args, 'prompt', '')
|
|
48
|
+
if not prompt:
|
|
49
|
+
print("Usage: codeagent run [options] <prompt>")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
ensure_session_dir()
|
|
53
|
+
session_id = args.save if args.save else f"agent-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
|
54
|
+
|
|
55
|
+
matched = find_commands(prompt, limit=5)
|
|
56
|
+
matches = [c for c in matched if hasattr(c, 'name') and c]
|
|
57
|
+
|
|
58
|
+
print("=== CodeAgent Run Result")
|
|
59
|
+
print(f"Session ID: {session_id}")
|
|
60
|
+
print(f"Prompt: {prompt}")
|
|
61
|
+
print(f"Matched commands: {len(matches)}")
|
|
62
|
+
|
|
63
|
+
for cmd_entry in matched:
|
|
64
|
+
name = cmd_entry.name if hasattr(cmd_entry, 'name') else repr(cmd_entry)
|
|
65
|
+
cmd = get_command(name)
|
|
66
|
+
if cmd:
|
|
67
|
+
print(f" - {name}: {cmd.description}")
|
|
68
|
+
|
|
69
|
+
if args.save:
|
|
70
|
+
save_session(session_id, [prompt], 0, 0)
|
|
71
|
+
from seed.core.llm_sess import agent_sessions_dir
|
|
72
|
+
|
|
73
|
+
print(f"Session saved to: {agent_sessions_dir() / f'{session_id}.json'}")
|
|
74
|
+
|
|
75
|
+
def cmd_route(args):
|
|
76
|
+
"""Handle route subcommand."""
|
|
77
|
+
prompt = getattr(args, 'prompt', '')
|
|
78
|
+
limit = getattr(args, 'limit', 20)
|
|
79
|
+
|
|
80
|
+
print("=== CodeAgent Route Result")
|
|
81
|
+
print(f"Query: {prompt}")
|
|
82
|
+
|
|
83
|
+
matched = find_commands(prompt, limit=limit)
|
|
84
|
+
print(f"Matches found: {len(matched)}")
|
|
85
|
+
|
|
86
|
+
for cmd in matched[:limit]:
|
|
87
|
+
name = cmd.name if hasattr(cmd, 'name') else repr(cmd)
|
|
88
|
+
desc = cmd.description if hasattr(cmd, 'description') else ""
|
|
89
|
+
print(f" - {name}: {desc}")
|
|
90
|
+
|
|
91
|
+
def cmd_commands(args):
|
|
92
|
+
"""Handle commands subcommand."""
|
|
93
|
+
all_cmds = get_all_commands()
|
|
94
|
+
limit = getattr(args, 'limit', 50)
|
|
95
|
+
result_cmds = all_cmds[:limit]
|
|
96
|
+
|
|
97
|
+
print("=== CodeAgent Commands")
|
|
98
|
+
print(f"Total commands: {len(result_cmds)}")
|
|
99
|
+
|
|
100
|
+
for cmd in result_cmds:
|
|
101
|
+
cat = "default"
|
|
102
|
+
if hasattr(cmd, 'category') and cmd.category:
|
|
103
|
+
cat = cmd.category
|
|
104
|
+
if hasattr(cmd, 'name'):
|
|
105
|
+
print(f" [{cat}] {cmd.name}")
|
|
106
|
+
if hasattr(cmd, 'description') and cmd.description:
|
|
107
|
+
print(f" {cmd.description}")
|
|
108
|
+
|
|
109
|
+
def cmd_tools(args):
|
|
110
|
+
"""Handle tools subcommand."""
|
|
111
|
+
registry, _executor = setup_builtin_tools()
|
|
112
|
+
tools = registry.list_all()
|
|
113
|
+
limit = getattr(args, 'limit', 50)
|
|
114
|
+
result_tools = tools[:limit]
|
|
115
|
+
|
|
116
|
+
print("=== CodeAgent Tools")
|
|
117
|
+
print(f"Total tools: {len(result_tools)}")
|
|
118
|
+
|
|
119
|
+
for tool in result_tools:
|
|
120
|
+
print(f" - [basic]: {tool.name} - {tool.description}")
|
|
121
|
+
|
|
122
|
+
def cmd_summary(args):
|
|
123
|
+
"""Handle summary subcommand."""
|
|
124
|
+
session_id = getattr(args, 'session', None)
|
|
125
|
+
|
|
126
|
+
print("=== CodeAgent Session Summary")
|
|
127
|
+
|
|
128
|
+
from seed.core.llm_sess import agent_sessions_dir, list_stored_sessions_meta
|
|
129
|
+
|
|
130
|
+
if session_id:
|
|
131
|
+
from codeagent.core import env as ca_env
|
|
132
|
+
|
|
133
|
+
aid = ca_env.default_agent_id()
|
|
134
|
+
file_path = agent_sessions_dir(aid) / f"{session_id}.json"
|
|
135
|
+
|
|
136
|
+
if not file_path.is_file():
|
|
137
|
+
print(f"Warning: Session file not found: {file_path}")
|
|
138
|
+
rows = list_stored_sessions_meta(limit=50, agent_id=aid)
|
|
139
|
+
print(f"Available chat sessions: {len(rows)}")
|
|
140
|
+
else:
|
|
141
|
+
print(f"Session: {session_id}")
|
|
142
|
+
print(f"File: {file_path}")
|
|
143
|
+
else:
|
|
144
|
+
sessions = list_sessions()
|
|
145
|
+
if not sessions:
|
|
146
|
+
print("No saved sessions (legacy QueryEngine store).")
|
|
147
|
+
print(f"Chat sessions directory: {agent_sessions_dir()}")
|
|
148
|
+
rows = list_stored_sessions_meta(limit=200)
|
|
149
|
+
print(f"Stored chat sessions: {len(rows)}")
|
|
150
|
+
print(f"Legacy persistence sessions: {len(sessions)}")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
CodeAgent CLI - Main entry point
|
|
156
|
+
Provides command routing, tool execution, and session management.
|
|
157
|
+
"""
|
|
158
|
+
import argparse # noqa: E402
|
|
159
|
+
from typing import Any # noqa: E402
|
|
160
|
+
|
|
161
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _win_no_window_kwargs() -> dict[str, Any]:
|
|
165
|
+
"""Suppress the black cmd window when this CLI helper runs under a windowed
|
|
166
|
+
parent (e.g. ``CodeAgentTray.exe``). No-op on POSIX."""
|
|
167
|
+
if os.name != "nt":
|
|
168
|
+
return {}
|
|
169
|
+
si = subprocess.STARTUPINFO() # type: ignore[attr-defined]
|
|
170
|
+
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore[attr-defined]
|
|
171
|
+
si.wShowWindow = 0 # SW_HIDE
|
|
172
|
+
return {"startupinfo": si, "creationflags": 0x08000000} # CREATE_NO_WINDOW
|
|
173
|
+
|
|
174
|
+
def main():
|
|
175
|
+
"""Main CLI entry point."""
|
|
176
|
+
|
|
177
|
+
from codeagent.core.bootstrap import bootstrap_codeagent_runtime
|
|
178
|
+
|
|
179
|
+
bootstrap_codeagent_runtime()
|
|
180
|
+
|
|
181
|
+
parser = argparse.ArgumentParser(
|
|
182
|
+
prog='codeagent',
|
|
183
|
+
description='CodeAgent: Autonomous agent for task execution with command routing and session management',
|
|
184
|
+
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
parser.add_argument('--version', action='version', version='CodeAgent v1.1.0')
|
|
188
|
+
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
|
189
|
+
|
|
190
|
+
# Run command
|
|
191
|
+
run_parser = subparsers.add_parser('run', help='Run agent with a prompt')
|
|
192
|
+
run_parser.add_argument('prompt', nargs='?', help='Prompt to execute', default='')
|
|
193
|
+
run_parser.add_argument('--max-turns', type=int, default=8, help='Maximum turns')
|
|
194
|
+
run_parser.add_argument('--budget', type=int, default=2000, help='Token budget')
|
|
195
|
+
run_parser.add_argument('--save', '-s', help='Save session ID')
|
|
196
|
+
|
|
197
|
+
# Route command
|
|
198
|
+
route_parser = subparsers.add_parser('route', help='Route prompt to commands')
|
|
199
|
+
route_parser.add_argument('prompt', help='Prompt to route')
|
|
200
|
+
route_parser.add_argument('--limit', type=int, default=20, help='Limit results')
|
|
201
|
+
|
|
202
|
+
# Commands command
|
|
203
|
+
cmd_parser = subparsers.add_parser('commands', help='List all commands')
|
|
204
|
+
cmd_parser.add_argument('--query', '-q', help='Filter by query', default=None)
|
|
205
|
+
cmd_parser.add_argument('--limit', '-n', type=int, default=50, help='Limit output')
|
|
206
|
+
cmd_parser.add_argument('--category', '-c', help='Filter by category', default=None)
|
|
207
|
+
|
|
208
|
+
# Tools command
|
|
209
|
+
tools_parser = subparsers.add_parser('tools', help='List all tools')
|
|
210
|
+
tools_parser.add_argument('--query', '-i', help='Filter by query')
|
|
211
|
+
tools_parser.add_argument('--limit', '-n', type=int, default=50, help='Limit output')
|
|
212
|
+
tools_parser.add_argument('--simple', '-s', action='store_true', help='Basic tools only')
|
|
213
|
+
|
|
214
|
+
# Summary command
|
|
215
|
+
summary_parser = subparsers.add_parser('summary', help='Show session summary')
|
|
216
|
+
summary_parser.add_argument('--session', '-S', help='Session ID')
|
|
217
|
+
|
|
218
|
+
# Session command
|
|
219
|
+
sess_parser = subparsers.add_parser('session', help='Manage chat sessions (agents/<id>/sessions/)')
|
|
220
|
+
sess_parser.add_argument(
|
|
221
|
+
'action',
|
|
222
|
+
choices=['list', 'migrate', 'delete', 'audit-list', 'audit-show', 'help'],
|
|
223
|
+
help='Action',
|
|
224
|
+
)
|
|
225
|
+
sess_parser.add_argument(
|
|
226
|
+
'--seq',
|
|
227
|
+
type=int,
|
|
228
|
+
default=None,
|
|
229
|
+
help='With audit-show: snapshot sequence number from audit-list',
|
|
230
|
+
)
|
|
231
|
+
sess_parser.add_argument(
|
|
232
|
+
'--project-id',
|
|
233
|
+
default=None,
|
|
234
|
+
help='Project id when session lives under projects-data/<id>/sessions/',
|
|
235
|
+
)
|
|
236
|
+
sess_parser.add_argument('session_id', nargs='?', help='Session ID (for delete)')
|
|
237
|
+
sess_parser.add_argument(
|
|
238
|
+
'--agent-id',
|
|
239
|
+
default=None,
|
|
240
|
+
help='Agent id (default: CODEAGENT_AGENT_ID or default)',
|
|
241
|
+
)
|
|
242
|
+
sess_parser.add_argument(
|
|
243
|
+
'--dry-run',
|
|
244
|
+
action='store_true',
|
|
245
|
+
help='With migrate: print planned moves without writing',
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Interactive chat (command routing only until LLM runtime is wired)
|
|
249
|
+
chat_parser = subparsers.add_parser(
|
|
250
|
+
'chat',
|
|
251
|
+
help='Interactive REPL: use --llm for full tool loop; else command routing only',
|
|
252
|
+
)
|
|
253
|
+
chat_parser.add_argument('--limit', '-n', type=int, default=8, help='Max matches per line (without --llm)')
|
|
254
|
+
chat_parser.add_argument(
|
|
255
|
+
'--session',
|
|
256
|
+
'-S',
|
|
257
|
+
help='Chat session id: resume/save under agents/<agent>/sessions (override SEED_AGENT_SESSIONS_DIR or SEED_LLM_SESSIONS_DIR)',
|
|
258
|
+
)
|
|
259
|
+
chat_parser.add_argument(
|
|
260
|
+
'--llm',
|
|
261
|
+
action='store_true',
|
|
262
|
+
help='Use SEED_LLM_BASEURL / SEED_LLM_MODEL (aliases CODEAGENT_*) and builtin tools; optional config/env',
|
|
263
|
+
)
|
|
264
|
+
chat_parser.add_argument('--max-tool-rounds', type=int, default=16, help='Max LLM/tool cycles per user line')
|
|
265
|
+
chat_parser.add_argument(
|
|
266
|
+
'--image',
|
|
267
|
+
action='append',
|
|
268
|
+
metavar='PATH',
|
|
269
|
+
help='Attach image/document file (repeatable); included with next user line',
|
|
270
|
+
)
|
|
271
|
+
chat_parser.add_argument(
|
|
272
|
+
'--image-dir',
|
|
273
|
+
metavar='PATH',
|
|
274
|
+
help='Stage all images under directory (or reference with --image-dir-ref)',
|
|
275
|
+
)
|
|
276
|
+
chat_parser.add_argument(
|
|
277
|
+
'--image-pattern',
|
|
278
|
+
help='Glob patterns with --image-dir, e.g. "*.png,*.jpg"',
|
|
279
|
+
)
|
|
280
|
+
chat_parser.add_argument(
|
|
281
|
+
'--image-dir-ref',
|
|
282
|
+
action='store_true',
|
|
283
|
+
help='Write [image_dir:path] reference instead of copying files',
|
|
284
|
+
)
|
|
285
|
+
chat_parser.add_argument(
|
|
286
|
+
'--vision-llm',
|
|
287
|
+
metavar='PRESET_ID',
|
|
288
|
+
help='Vision preset for vision_analyze when sending images (or CODEAGENT_VISION_PRESET_ID)',
|
|
289
|
+
)
|
|
290
|
+
chat_parser.add_argument(
|
|
291
|
+
'--image-gen-llm',
|
|
292
|
+
metavar='PRESET_ID',
|
|
293
|
+
help='Image generation preset for image_generate (or CODEAGENT_IMAGE_GEN_PRESET_ID)',
|
|
294
|
+
)
|
|
295
|
+
chat_parser.add_argument(
|
|
296
|
+
'--audio-llm',
|
|
297
|
+
metavar='PRESET_ID',
|
|
298
|
+
help='Audio transcription preset for audio_transcribe (or CODEAGENT_AUDIO_PRESET_ID)',
|
|
299
|
+
)
|
|
300
|
+
chat_parser.add_argument(
|
|
301
|
+
'--music-llm',
|
|
302
|
+
metavar='PRESET_ID',
|
|
303
|
+
help='Music generation preset for music_generate (or CODEAGENT_MUSIC_GEN_PRESET_ID)',
|
|
304
|
+
)
|
|
305
|
+
chat_parser.add_argument(
|
|
306
|
+
'--video-gen-llm',
|
|
307
|
+
metavar='PRESET_ID',
|
|
308
|
+
help='Video generation preset for video_generate (or CODEAGENT_VIDEO_GEN_PRESET_ID)',
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
serve_parser = subparsers.add_parser(
|
|
312
|
+
'serve',
|
|
313
|
+
help=(
|
|
314
|
+
'HTTP: GET /, POST /api/chat, WS /ws; optional Web UI token (codeagent webui-token init); '
|
|
315
|
+
'webhooks. 默认 --host 0.0.0.0:启动时会打印本机与局域网 URL,便于其它设备打开配置或填写 Webhook。'
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
serve_parser.add_argument(
|
|
319
|
+
'--host',
|
|
320
|
+
default='0.0.0.0',
|
|
321
|
+
help='Bind address(0.0.0.0 表示局域网可访问)',
|
|
322
|
+
)
|
|
323
|
+
serve_parser.add_argument('--port', type=int, default=8765, help='Port')
|
|
324
|
+
|
|
325
|
+
restart_parser = subparsers.add_parser(
|
|
326
|
+
'restart',
|
|
327
|
+
help='Restart helpers (e.g. restart serve). Tries to stop existing listener first.',
|
|
328
|
+
)
|
|
329
|
+
restart_sub = restart_parser.add_subparsers(dest='restart_cmd', required=True, metavar='target')
|
|
330
|
+
restart_serve = restart_sub.add_parser('serve', help='Restart HTTP server (kill listener on port, then run serve)')
|
|
331
|
+
restart_serve.add_argument('--host', default='0.0.0.0', help='Bind address')
|
|
332
|
+
restart_serve.add_argument('--port', type=int, default=8765, help='Port')
|
|
333
|
+
restart_serve.add_argument(
|
|
334
|
+
'--timeout-sec',
|
|
335
|
+
type=float,
|
|
336
|
+
default=5.0,
|
|
337
|
+
help='How long to wait for port to be released after killing (seconds)',
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
cfg_parser = subparsers.add_parser('config', help='Manage Markdown config under config/')
|
|
341
|
+
cfg_sub = cfg_parser.add_subparsers(dest='cfg_cmd', help='Config action')
|
|
342
|
+
cfg_sub.add_parser('init', help='Create default agent.md, identity.md, soul.md, tools.md, user.md')
|
|
343
|
+
|
|
344
|
+
wt_parser = subparsers.add_parser(
|
|
345
|
+
"webui-token",
|
|
346
|
+
help="Web UI: create/show/reset access token (local CLI only; file config/webui.token)",
|
|
347
|
+
)
|
|
348
|
+
wt_sub = wt_parser.add_subparsers(dest="wt_action", required=True, metavar="action")
|
|
349
|
+
wt_sub.add_parser("init", help="Create token file if missing (fails if file already exists)")
|
|
350
|
+
wt_sub.add_parser("reset", help="Write a new token (invalidates existing browser sessions)")
|
|
351
|
+
wt_sub.add_parser("show", help="Print token from file (environment CODEAGENT_WEBUI_TOKEN is not printed)")
|
|
352
|
+
|
|
353
|
+
if len(sys.argv) < 2:
|
|
354
|
+
parser.print_help()
|
|
355
|
+
sys.exit(0)
|
|
356
|
+
|
|
357
|
+
args = parser.parse_args()
|
|
358
|
+
|
|
359
|
+
if not args.command:
|
|
360
|
+
parser.print_help()
|
|
361
|
+
sys.exit(0)
|
|
362
|
+
|
|
363
|
+
cmd = getattr(args, 'command', None)
|
|
364
|
+
if cmd == 'run':
|
|
365
|
+
cmd_run(args)
|
|
366
|
+
elif cmd == 'route':
|
|
367
|
+
cmd_route(args)
|
|
368
|
+
elif cmd == 'commands':
|
|
369
|
+
cmd_commands(args)
|
|
370
|
+
elif cmd == 'tools':
|
|
371
|
+
cmd_tools(args)
|
|
372
|
+
elif cmd == 'summary':
|
|
373
|
+
cmd_summary(args)
|
|
374
|
+
elif cmd == 'session':
|
|
375
|
+
cmd_session(args)
|
|
376
|
+
elif cmd == 'chat':
|
|
377
|
+
cmd_chat(args)
|
|
378
|
+
elif cmd == 'serve':
|
|
379
|
+
cmd_serve(args)
|
|
380
|
+
elif cmd == 'restart':
|
|
381
|
+
cmd_restart(args)
|
|
382
|
+
elif cmd == 'config':
|
|
383
|
+
cmd_config(args)
|
|
384
|
+
elif cmd == "webui-token":
|
|
385
|
+
cmd_webui_token(args)
|
|
386
|
+
else:
|
|
387
|
+
parser.print_help()
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
import asyncio # noqa: E402
|
|
393
|
+
import contextlib # noqa: E402
|
|
394
|
+
import os # noqa: E402
|
|
395
|
+
import sys # noqa: E402
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def cmd_chat(args):
|
|
399
|
+
"""Interactive loop: routing-only or LLM + tools."""
|
|
400
|
+
if getattr(args, 'llm', False):
|
|
401
|
+
_cmd_chat_llm(args)
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
limit = getattr(args, 'limit', 8)
|
|
405
|
+
sess = getattr(args, 'session', None)
|
|
406
|
+
print("CodeAgent chat — 每行输入会做命令匹配(无 --llm);exit / quit 退出。")
|
|
407
|
+
if sess:
|
|
408
|
+
print(f"Session label: {sess}")
|
|
409
|
+
while True:
|
|
410
|
+
try:
|
|
411
|
+
line = input("> ").strip()
|
|
412
|
+
except (EOFError, KeyboardInterrupt):
|
|
413
|
+
print()
|
|
414
|
+
break
|
|
415
|
+
if not line:
|
|
416
|
+
continue
|
|
417
|
+
if line.lower() in ('exit', 'quit', 'q'):
|
|
418
|
+
break
|
|
419
|
+
matched = find_commands(line, limit=limit)
|
|
420
|
+
if not matched:
|
|
421
|
+
print(" (无匹配命令)")
|
|
422
|
+
continue
|
|
423
|
+
for c in matched:
|
|
424
|
+
desc = c.description if getattr(c, 'description', None) else ''
|
|
425
|
+
print(f" - {c.name}: {desc}")
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _cli_audio_preset_id(args) -> str:
|
|
429
|
+
from codeagent.core import env as ca_env
|
|
430
|
+
|
|
431
|
+
pid = (getattr(args, 'audio_llm', None) or '').strip()
|
|
432
|
+
if pid:
|
|
433
|
+
return pid
|
|
434
|
+
return ca_env.pick_default('', 'CODEAGENT_AUDIO_PRESET_ID').strip()
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _cli_image_gen_preset_id(args) -> str:
|
|
438
|
+
from codeagent.core import env as ca_env
|
|
439
|
+
|
|
440
|
+
pid = (getattr(args, 'image_gen_llm', None) or '').strip()
|
|
441
|
+
if pid:
|
|
442
|
+
return pid
|
|
443
|
+
return ca_env.pick_default('', 'CODEAGENT_IMAGE_GEN_PRESET_ID').strip()
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _cli_music_preset_id(args) -> str:
|
|
447
|
+
from codeagent.core import env as ca_env
|
|
448
|
+
|
|
449
|
+
pid = (getattr(args, 'music_llm', None) or '').strip()
|
|
450
|
+
if pid:
|
|
451
|
+
return pid
|
|
452
|
+
return ca_env.pick_default('', 'CODEAGENT_MUSIC_GEN_PRESET_ID').strip()
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _cli_video_gen_preset_id(args) -> str:
|
|
456
|
+
from codeagent.core import env as ca_env
|
|
457
|
+
|
|
458
|
+
pid = (getattr(args, 'video_gen_llm', None) or '').strip()
|
|
459
|
+
if pid:
|
|
460
|
+
return pid
|
|
461
|
+
return ca_env.pick_default('', 'CODEAGENT_VIDEO_GEN_PRESET_ID').strip()
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _cli_vision_preset_id(args) -> str:
|
|
465
|
+
from codeagent.core import env as ca_env
|
|
466
|
+
|
|
467
|
+
vid = (getattr(args, 'vision_llm', None) or '').strip()
|
|
468
|
+
if vid:
|
|
469
|
+
return vid
|
|
470
|
+
return ca_env.pick_default('', 'CODEAGENT_VISION_PRESET_ID').strip()
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _cli_stage_path_attachment(path: str, *, agent_id: str, session_id: str):
|
|
474
|
+
from pathlib import Path
|
|
475
|
+
|
|
476
|
+
import mimetypes
|
|
477
|
+
|
|
478
|
+
from codeagent.core.attachments import save_attachment
|
|
479
|
+
|
|
480
|
+
p = Path(path).expanduser()
|
|
481
|
+
if not p.is_file():
|
|
482
|
+
print(f' [warn] not a file: {path}')
|
|
483
|
+
return None
|
|
484
|
+
try:
|
|
485
|
+
raw = p.read_bytes()
|
|
486
|
+
mime = mimetypes.guess_type(str(p))[0] or ''
|
|
487
|
+
return save_attachment(
|
|
488
|
+
agent_id=agent_id,
|
|
489
|
+
session_id=session_id,
|
|
490
|
+
raw_bytes=raw,
|
|
491
|
+
filename=p.name,
|
|
492
|
+
mime=mime,
|
|
493
|
+
)
|
|
494
|
+
except Exception as e:
|
|
495
|
+
print(f' [warn] attach failed {path}: {e}')
|
|
496
|
+
return None
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _cli_startup_attachments(args, *, agent_id: str, session_id: str, project_root):
|
|
500
|
+
from pathlib import Path
|
|
501
|
+
|
|
502
|
+
from codeagent.core import env as ca_env
|
|
503
|
+
from codeagent.core.attachments import scan_image_directory
|
|
504
|
+
|
|
505
|
+
metas = []
|
|
506
|
+
image_dir_tag = ''
|
|
507
|
+
for path in getattr(args, 'image', None) or []:
|
|
508
|
+
meta = _cli_stage_path_attachment(path, agent_id=agent_id, session_id=session_id)
|
|
509
|
+
if meta:
|
|
510
|
+
metas.append(meta)
|
|
511
|
+
print(f' [attach] {meta.filename} → {meta.id}')
|
|
512
|
+
|
|
513
|
+
image_dir = (getattr(args, 'image_dir', None) or '').strip()
|
|
514
|
+
if image_dir:
|
|
515
|
+
if getattr(args, 'image_dir_ref', False):
|
|
516
|
+
rel = image_dir.replace('\\', '/').lstrip('/')
|
|
517
|
+
mx = ca_env.pick_int(32, 'CODEAGENT_ATTACHMENTS_DIR_MAX_FILES')
|
|
518
|
+
image_dir_tag = f'[image_dir:{rel} max={mx}]'
|
|
519
|
+
print(f' [image-dir-ref] {image_dir_tag}')
|
|
520
|
+
else:
|
|
521
|
+
pattern = (getattr(args, 'image_pattern', None) or '').strip() or None
|
|
522
|
+
try:
|
|
523
|
+
paths, truncated = scan_image_directory(
|
|
524
|
+
Path(project_root),
|
|
525
|
+
image_dir,
|
|
526
|
+
pattern=pattern,
|
|
527
|
+
)
|
|
528
|
+
except ValueError as e:
|
|
529
|
+
print(f' [warn] image-dir: {e}')
|
|
530
|
+
paths, truncated = [], False
|
|
531
|
+
for p in paths:
|
|
532
|
+
meta = _cli_stage_path_attachment(str(p), agent_id=agent_id, session_id=session_id)
|
|
533
|
+
if meta:
|
|
534
|
+
metas.append(meta)
|
|
535
|
+
if metas:
|
|
536
|
+
print(f' [image-dir] staged {len(metas)} file(s)' + (' (truncated)' if truncated else ''))
|
|
537
|
+
elif not truncated:
|
|
538
|
+
print(f' [warn] no images found in {image_dir}')
|
|
539
|
+
return metas, image_dir_tag
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _cli_handle_slash_attach(line: str, *, agent_id: str, session_id: str, project_root):
|
|
543
|
+
"""Return (handled, attachments, image_dir_tag, message_text)."""
|
|
544
|
+
from pathlib import Path
|
|
545
|
+
|
|
546
|
+
from codeagent.core import env as ca_env
|
|
547
|
+
from codeagent.core.attachments import scan_image_directory
|
|
548
|
+
|
|
549
|
+
low = line.strip().lower()
|
|
550
|
+
if low in ('/clear-vision', '/clear_vision'):
|
|
551
|
+
return 'clear_vision', [], '', ''
|
|
552
|
+
if line.startswith('/attach-dir '):
|
|
553
|
+
path = line[len('/attach-dir '):].strip()
|
|
554
|
+
if not path:
|
|
555
|
+
print(' usage: /attach-dir <path>')
|
|
556
|
+
return True, [], '', ''
|
|
557
|
+
mx = ca_env.pick_int(32, 'CODEAGENT_ATTACHMENTS_DIR_MAX_FILES')
|
|
558
|
+
tag = f'[image_dir:{path.replace(chr(92), "/").lstrip("/")} max={mx}]'
|
|
559
|
+
print(f' [image-dir-ref] {tag}')
|
|
560
|
+
return True, [], tag, ''
|
|
561
|
+
if line.startswith('/attach '):
|
|
562
|
+
path = line[len('/attach '):].strip()
|
|
563
|
+
if not path:
|
|
564
|
+
print(' usage: /attach <path>')
|
|
565
|
+
return True, [], '', ''
|
|
566
|
+
meta = _cli_stage_path_attachment(path, agent_id=agent_id, session_id=session_id)
|
|
567
|
+
if meta:
|
|
568
|
+
print(f' [attach] {meta.filename} → {meta.id}')
|
|
569
|
+
return True, [meta], '', ''
|
|
570
|
+
return True, [], '', ''
|
|
571
|
+
return False, [], '', line
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _cmd_chat_llm(args):
|
|
575
|
+
|
|
576
|
+
from seed_tools import setup_builtin_tools
|
|
577
|
+
|
|
578
|
+
from seed.core.agent_context import (
|
|
579
|
+
set_active_agent_id,
|
|
580
|
+
set_active_audio_preset,
|
|
581
|
+
set_active_image_gen_preset,
|
|
582
|
+
set_active_llm_session,
|
|
583
|
+
set_active_vision_preset,
|
|
584
|
+
set_active_music_preset,
|
|
585
|
+
set_active_video_gen_preset,
|
|
586
|
+
)
|
|
587
|
+
from seed.core.agent_runtime import (
|
|
588
|
+
build_api_projection_messages,
|
|
589
|
+
maybe_compact_context_messages,
|
|
590
|
+
merge_llm_tail_into_full,
|
|
591
|
+
persist_compact_summary,
|
|
592
|
+
run_llm_tool_loop,
|
|
593
|
+
strip_ephemeral_message_fields,
|
|
594
|
+
)
|
|
595
|
+
from codeagent.runtime.prompt_enrichment import (
|
|
596
|
+
build_skills_suffix,
|
|
597
|
+
fresh_system_prompt,
|
|
598
|
+
get_cached_system_prompt,
|
|
599
|
+
record_chat_turn_diary,
|
|
600
|
+
)
|
|
601
|
+
from seed.core.llm_exec import LLMError
|
|
602
|
+
from seed.core.llm_presets import llm_executor_from_resolved, resolve_preset
|
|
603
|
+
from seed.core.llm_sess import (
|
|
604
|
+
load_or_create_chat_session,
|
|
605
|
+
merge_fresh_system,
|
|
606
|
+
persist_chat_session,
|
|
607
|
+
)
|
|
608
|
+
from seed.core.mem_bridge import finalize_episodic_for_llm
|
|
609
|
+
from seed.integrations.session_title import maybe_llm_refresh_session_title
|
|
610
|
+
|
|
611
|
+
name = getattr(args, 'session', None) or 'cli-chat'
|
|
612
|
+
max_rounds = getattr(args, 'max_tool_rounds', 16) or 16
|
|
613
|
+
|
|
614
|
+
registry, executor = setup_builtin_tools()
|
|
615
|
+
llm = llm_executor_from_resolved(resolve_preset(None))
|
|
616
|
+
from seed.core.config_plane import project_root as _project_root
|
|
617
|
+
|
|
618
|
+
project_root = _project_root()
|
|
619
|
+
from codeagent.core import env as ca_env
|
|
620
|
+
|
|
621
|
+
agent_id = ca_env.default_agent_id()
|
|
622
|
+
vision_preset_id = _cli_vision_preset_id(args)
|
|
623
|
+
image_gen_preset_id = _cli_image_gen_preset_id(args)
|
|
624
|
+
audio_preset_id = _cli_audio_preset_id(args)
|
|
625
|
+
music_preset_id = _cli_music_preset_id(args)
|
|
626
|
+
video_gen_preset_id = _cli_video_gen_preset_id(args)
|
|
627
|
+
chat_sess = load_or_create_chat_session(name, agent_id)
|
|
628
|
+
fresh_sys = get_cached_system_prompt(chat_sess, agent_id=agent_id)
|
|
629
|
+
chat_sess.messages[:] = merge_fresh_system(chat_sess.messages, fresh_sys)
|
|
630
|
+
|
|
631
|
+
pending_attachments, pending_dir_tag = _cli_startup_attachments(
|
|
632
|
+
args,
|
|
633
|
+
agent_id=agent_id,
|
|
634
|
+
session_id=name,
|
|
635
|
+
project_root=project_root,
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
print(f"CodeAgent LLM chat — session «{name}» — {len(registry.list_all())} tools — exit / quit 结束。")
|
|
639
|
+
print(f" API: {llm.baseURL} model: {llm.model}")
|
|
640
|
+
if vision_preset_id:
|
|
641
|
+
print(f" Vision preset: {vision_preset_id}")
|
|
642
|
+
if image_gen_preset_id:
|
|
643
|
+
print(f" Image gen preset: {image_gen_preset_id}")
|
|
644
|
+
if audio_preset_id:
|
|
645
|
+
print(f" Audio preset: {audio_preset_id}")
|
|
646
|
+
if music_preset_id:
|
|
647
|
+
print(f" Music preset: {music_preset_id}")
|
|
648
|
+
if video_gen_preset_id:
|
|
649
|
+
print(f" Video gen preset: {video_gen_preset_id}")
|
|
650
|
+
print(" 命令: /attach <path> /attach-dir <path> /clear-vision")
|
|
651
|
+
while True:
|
|
652
|
+
try:
|
|
653
|
+
line = input("> ").strip()
|
|
654
|
+
except (EOFError, KeyboardInterrupt):
|
|
655
|
+
print()
|
|
656
|
+
break
|
|
657
|
+
if not line and not pending_attachments and not pending_dir_tag:
|
|
658
|
+
continue
|
|
659
|
+
if line.lower() in ('exit', 'quit', 'q'):
|
|
660
|
+
break
|
|
661
|
+
|
|
662
|
+
slash_atts: list = []
|
|
663
|
+
slash_dir_tag = ''
|
|
664
|
+
message_text = line
|
|
665
|
+
if line.startswith('/'):
|
|
666
|
+
handled, slash_atts, slash_dir_tag, message_text = _cli_handle_slash_attach(
|
|
667
|
+
line,
|
|
668
|
+
agent_id=agent_id,
|
|
669
|
+
session_id=name,
|
|
670
|
+
project_root=project_root,
|
|
671
|
+
)
|
|
672
|
+
if handled == 'clear_vision':
|
|
673
|
+
if isinstance(chat_sess.metadata, dict):
|
|
674
|
+
chat_sess.metadata.pop('vision_context', None)
|
|
675
|
+
print(' 视觉上下文已清除')
|
|
676
|
+
continue
|
|
677
|
+
if handled is True and not slash_atts and not slash_dir_tag:
|
|
678
|
+
continue
|
|
679
|
+
else:
|
|
680
|
+
handled = False
|
|
681
|
+
|
|
682
|
+
if slash_atts:
|
|
683
|
+
pending_attachments.extend(slash_atts)
|
|
684
|
+
if slash_dir_tag:
|
|
685
|
+
pending_dir_tag = slash_dir_tag
|
|
686
|
+
|
|
687
|
+
from codeagent.core.attachments import (
|
|
688
|
+
build_user_message,
|
|
689
|
+
message_has_audio_attachments,
|
|
690
|
+
message_has_image_attachments,
|
|
691
|
+
message_has_video_attachments,
|
|
692
|
+
)
|
|
693
|
+
from codeagent.core.audio_models import preset_supports_audio_id
|
|
694
|
+
from codeagent.core.vision_models import preset_supports_vision_id
|
|
695
|
+
|
|
696
|
+
user_msg = build_user_message(
|
|
697
|
+
message_text,
|
|
698
|
+
pending_attachments,
|
|
699
|
+
image_dir_tag=pending_dir_tag,
|
|
700
|
+
)
|
|
701
|
+
from codeagent.core.image_understanding import image_attachment_allowed, video_attachment_allowed
|
|
702
|
+
|
|
703
|
+
if message_has_image_attachments(user_msg) or pending_dir_tag:
|
|
704
|
+
if not image_attachment_allowed(vision_preset_id or ""):
|
|
705
|
+
print(
|
|
706
|
+
'[error] 发送图片需要 --vision-llm(supports_vision)'
|
|
707
|
+
' 或已配置 MiniMax MCP(understand_image)'
|
|
708
|
+
)
|
|
709
|
+
continue
|
|
710
|
+
if message_has_video_attachments(user_msg):
|
|
711
|
+
if not video_attachment_allowed(vision_preset_id or ""):
|
|
712
|
+
print('[error] 发送视频需要有效的 --vision-llm 或 CODEAGENT_VISION_PRESET_ID')
|
|
713
|
+
continue
|
|
714
|
+
if message_has_audio_attachments(user_msg):
|
|
715
|
+
if not audio_preset_id or not preset_supports_audio_id(audio_preset_id):
|
|
716
|
+
print('[error] 发送音频需要有效的 --audio-llm 或 CODEAGENT_AUDIO_PRESET_ID')
|
|
717
|
+
continue
|
|
718
|
+
|
|
719
|
+
pending_attachments = []
|
|
720
|
+
pending_dir_tag = ''
|
|
721
|
+
|
|
722
|
+
chat_sess.messages.append(user_msg)
|
|
723
|
+
user_text = str(user_msg.get('content') or '')
|
|
724
|
+
from codeagent.core.attachments import content_text_for_skills
|
|
725
|
+
|
|
726
|
+
_skills_suffix = build_skills_suffix(agent_id, user_text=content_text_for_skills(user_text))
|
|
727
|
+
api_msgs = build_api_projection_messages(
|
|
728
|
+
chat_sess.messages,
|
|
729
|
+
skills_suffix=_skills_suffix,
|
|
730
|
+
)
|
|
731
|
+
compact_result = maybe_compact_context_messages(api_msgs, llm)
|
|
732
|
+
persist_compact_summary(chat_sess.messages, compact_result)
|
|
733
|
+
strip_ephemeral_message_fields(api_msgs)
|
|
734
|
+
if not isinstance(chat_sess.metadata, dict):
|
|
735
|
+
chat_sess.metadata = {}
|
|
736
|
+
finalize_episodic_for_llm(
|
|
737
|
+
api_msgs,
|
|
738
|
+
chat_sess.metadata,
|
|
739
|
+
agent_id=agent_id,
|
|
740
|
+
session_id=name,
|
|
741
|
+
project_id=None,
|
|
742
|
+
compact_happened=compact_result is not None,
|
|
743
|
+
)
|
|
744
|
+
set_active_llm_session(f'{agent_id}::{name}')
|
|
745
|
+
set_active_agent_id(agent_id)
|
|
746
|
+
set_active_vision_preset(vision_preset_id or None)
|
|
747
|
+
set_active_image_gen_preset(image_gen_preset_id or None)
|
|
748
|
+
set_active_audio_preset(audio_preset_id or None)
|
|
749
|
+
set_active_music_preset(music_preset_id or None)
|
|
750
|
+
set_active_video_gen_preset(video_gen_preset_id or None)
|
|
751
|
+
try:
|
|
752
|
+
n_before = len(api_msgs)
|
|
753
|
+
reply, _, tools_used, _tool_trace, _loop_meta = asyncio.run(
|
|
754
|
+
run_llm_tool_loop(
|
|
755
|
+
llm,
|
|
756
|
+
executor,
|
|
757
|
+
messages=api_msgs,
|
|
758
|
+
registry=registry,
|
|
759
|
+
max_tool_rounds=max_rounds,
|
|
760
|
+
)
|
|
761
|
+
)
|
|
762
|
+
tail = merge_llm_tail_into_full(chat_sess.messages, api_msgs, n_before)
|
|
763
|
+
print(reply)
|
|
764
|
+
if tools_used:
|
|
765
|
+
print(f" [tools] {', '.join(tools_used)}")
|
|
766
|
+
with contextlib.suppress(Exception):
|
|
767
|
+
persist_chat_session(chat_sess)
|
|
768
|
+
with contextlib.suppress(Exception):
|
|
769
|
+
maybe_llm_refresh_session_title(llm, chat_sess)
|
|
770
|
+
with contextlib.suppress(Exception):
|
|
771
|
+
record_chat_turn_diary(
|
|
772
|
+
agent_id,
|
|
773
|
+
user_text=user_text,
|
|
774
|
+
reply=reply or "",
|
|
775
|
+
tools_used=tools_used,
|
|
776
|
+
)
|
|
777
|
+
if os.environ.get("CODEAGENT_MEMORY_LOG", "1").lower() not in ("0", "false", "no"):
|
|
778
|
+
try:
|
|
779
|
+
from seed.core.mem_sys import MemorySystem
|
|
780
|
+
|
|
781
|
+
mem = MemorySystem(base_path=project_root)
|
|
782
|
+
mem.log_experience(
|
|
783
|
+
task_id=f"chat-{datetime.now().isoformat()}",
|
|
784
|
+
outcome=(reply or "")[:2000],
|
|
785
|
+
tools_used=tools_used,
|
|
786
|
+
session_id=name,
|
|
787
|
+
)
|
|
788
|
+
except Exception:
|
|
789
|
+
pass
|
|
790
|
+
except LLMError as e:
|
|
791
|
+
print(f"[LLM error] {e}")
|
|
792
|
+
chat_sess.messages.pop()
|
|
793
|
+
finally:
|
|
794
|
+
set_active_llm_session(None)
|
|
795
|
+
set_active_vision_preset(None)
|
|
796
|
+
set_active_image_gen_preset(None)
|
|
797
|
+
set_active_audio_preset(None)
|
|
798
|
+
set_active_agent_id(None)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def cmd_webui_token(args):
|
|
802
|
+
import secrets
|
|
803
|
+
|
|
804
|
+
from codeagent.web.auth_impl import TOKEN_FILENAME
|
|
805
|
+
|
|
806
|
+
# cmd_webui_token can be invoked directly; ensure env file is loaded.
|
|
807
|
+
from codeagent.core.bootstrap import bootstrap_codeagent_runtime
|
|
808
|
+
|
|
809
|
+
bootstrap_codeagent_runtime()
|
|
810
|
+
from seed.core.config_plane import project_root as _project_root
|
|
811
|
+
|
|
812
|
+
root = _project_root()
|
|
813
|
+
cfg = root / "config"
|
|
814
|
+
cfg.mkdir(parents=True, exist_ok=True)
|
|
815
|
+
path = cfg / TOKEN_FILENAME
|
|
816
|
+
act = args.wt_action
|
|
817
|
+
|
|
818
|
+
if act == "show":
|
|
819
|
+
if not path.is_file():
|
|
820
|
+
print(f"(no token file) {path}")
|
|
821
|
+
if os.environ.get("CODEAGENT_WEBUI_TOKEN", "").strip():
|
|
822
|
+
print("CODEAGENT_WEBUI_TOKEN is set in the environment (value not shown).")
|
|
823
|
+
return
|
|
824
|
+
print(path)
|
|
825
|
+
print(path.read_text(encoding="utf-8").strip())
|
|
826
|
+
return
|
|
827
|
+
|
|
828
|
+
if act == "init":
|
|
829
|
+
if path.exists():
|
|
830
|
+
print(f"Already exists: {path}")
|
|
831
|
+
print("Use: codeagent webui-token reset")
|
|
832
|
+
sys.exit(1)
|
|
833
|
+
token = secrets.token_urlsafe(32)
|
|
834
|
+
path.write_text(token + "\n", encoding="utf-8")
|
|
835
|
+
with contextlib.suppress(OSError, NotImplementedError, AttributeError):
|
|
836
|
+
os.chmod(path, 0o600)
|
|
837
|
+
print(f"Written: {path}")
|
|
838
|
+
print(f"Token (save it): {token}")
|
|
839
|
+
return
|
|
840
|
+
|
|
841
|
+
if act == "reset":
|
|
842
|
+
token = secrets.token_urlsafe(32)
|
|
843
|
+
path.write_text(token + "\n", encoding="utf-8")
|
|
844
|
+
with contextlib.suppress(OSError, NotImplementedError, AttributeError):
|
|
845
|
+
os.chmod(path, 0o600)
|
|
846
|
+
print(f"Written: {path}")
|
|
847
|
+
print(f"New token: {token}")
|
|
848
|
+
return
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
import sys # noqa: E402
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
def cmd_serve(args):
|
|
857
|
+
try:
|
|
858
|
+
from codeagent.server import main as server_main
|
|
859
|
+
except ImportError as e:
|
|
860
|
+
print(f"无法加载 HTTP 服务模块: {e}", file=sys.stderr)
|
|
861
|
+
print(
|
|
862
|
+
"请确认已安装 server 依赖: pip install 'codeagent'(默认已包含 starlette + uvicorn)",
|
|
863
|
+
file=sys.stderr,
|
|
864
|
+
)
|
|
865
|
+
sys.exit(1)
|
|
866
|
+
try:
|
|
867
|
+
server_main(host=args.host, port=args.port)
|
|
868
|
+
except OSError as e:
|
|
869
|
+
print(f"服务启动失败(端口或地址不可用): {e}", file=sys.stderr)
|
|
870
|
+
print(
|
|
871
|
+
f"可尝试更换端口: codeagent serve --host {args.host} --port {(args.port or 8765) + 1}",
|
|
872
|
+
file=sys.stderr,
|
|
873
|
+
)
|
|
874
|
+
sys.exit(1)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def cmd_serve_tray(args):
|
|
878
|
+
print("serve-tray has been removed.", file=sys.stderr)
|
|
879
|
+
sys.exit(1)
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def cmd_config(args):
|
|
883
|
+
from seed.core.config_plane import ensure_default_config_files, project_root
|
|
884
|
+
|
|
885
|
+
sub = getattr(args, 'cfg_cmd', None)
|
|
886
|
+
if sub == 'init':
|
|
887
|
+
root = project_root()
|
|
888
|
+
ensure_default_config_files(root)
|
|
889
|
+
ex = root / "config" / "env.example"
|
|
890
|
+
print(f"Default Markdown written under: {root / 'config'}")
|
|
891
|
+
print("Tip: optional plugin skills live in config/skills/ directory.")
|
|
892
|
+
if ex.is_file():
|
|
893
|
+
print(f"Context/LLM env template: {ex} -> copy to config/env if you want repo-local settings.")
|
|
894
|
+
return
|
|
895
|
+
print("Usage: codeagent config init")
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def cmd_session(args):
|
|
899
|
+
"""Handle session subcommand (Seed chat sessions on disk)."""
|
|
900
|
+
from codeagent.core import env as ca_env
|
|
901
|
+
|
|
902
|
+
action = getattr(args, 'action', 'list')
|
|
903
|
+
session_id = getattr(args, 'session_id', None)
|
|
904
|
+
agent_id = (getattr(args, 'agent_id', None) or ca_env.default_agent_id()).strip() or 'default'
|
|
905
|
+
|
|
906
|
+
if action == 'list':
|
|
907
|
+
from seed.core.llm_sess import list_stored_sessions_meta
|
|
908
|
+
|
|
909
|
+
rows = list_stored_sessions_meta(limit=200, agent_id=agent_id)
|
|
910
|
+
print(f"=== Chat sessions (agent={agent_id})")
|
|
911
|
+
print(f"Total: {len(rows)}")
|
|
912
|
+
for row in rows:
|
|
913
|
+
title = row.get('display_title') or row.get('session_id')
|
|
914
|
+
print(f" - {row.get('session_id')}: {title}")
|
|
915
|
+
elif action == 'migrate':
|
|
916
|
+
from seed.core.llm_sess import migrate_legacy_agent_sessions
|
|
917
|
+
|
|
918
|
+
stats = migrate_legacy_agent_sessions(
|
|
919
|
+
agent_id,
|
|
920
|
+
dry_run=bool(getattr(args, 'dry_run', False)),
|
|
921
|
+
)
|
|
922
|
+
print(json.dumps(stats, ensure_ascii=False, indent=2))
|
|
923
|
+
elif action == 'audit-list':
|
|
924
|
+
if not session_id:
|
|
925
|
+
print("Usage: codeagent session audit-list <session_id> [--agent-id ...] [--project-id ...]")
|
|
926
|
+
return
|
|
927
|
+
from seed.core.projection_audit import list_projection_audit_index
|
|
928
|
+
|
|
929
|
+
rows = list_projection_audit_index(
|
|
930
|
+
session_id,
|
|
931
|
+
agent_id=agent_id,
|
|
932
|
+
project_id=getattr(args, 'project_id', None),
|
|
933
|
+
)
|
|
934
|
+
if not rows:
|
|
935
|
+
print("No LLM projection audit snapshots (enable SEED_LLM_PROJECTION_AUDIT=1).")
|
|
936
|
+
return
|
|
937
|
+
print(f"=== LLM projection audit (agent={agent_id}, session={session_id})")
|
|
938
|
+
for row in rows:
|
|
939
|
+
print(
|
|
940
|
+
f" seq={row.get('seq')} kind={row.get('kind')} round={row.get('round')} "
|
|
941
|
+
f"bytes={row.get('body_bytes')} file={row.get('file')}"
|
|
942
|
+
)
|
|
943
|
+
elif action == 'audit-show':
|
|
944
|
+
if not session_id:
|
|
945
|
+
print("Usage: codeagent session audit-show <session_id> --seq N")
|
|
946
|
+
return
|
|
947
|
+
seq = getattr(args, 'seq', None)
|
|
948
|
+
if seq is None:
|
|
949
|
+
print("--seq is required for audit-show")
|
|
950
|
+
return
|
|
951
|
+
from seed.core.projection_audit import load_projection_audit_snapshot
|
|
952
|
+
|
|
953
|
+
snap = load_projection_audit_snapshot(
|
|
954
|
+
session_id,
|
|
955
|
+
seq=seq,
|
|
956
|
+
agent_id=agent_id,
|
|
957
|
+
project_id=getattr(args, 'project_id', None),
|
|
958
|
+
)
|
|
959
|
+
if not snap:
|
|
960
|
+
print(f"Audit snapshot not found: seq={seq}")
|
|
961
|
+
return
|
|
962
|
+
print(json.dumps(snap, ensure_ascii=False, indent=2))
|
|
963
|
+
elif action == 'help':
|
|
964
|
+
print("Actions: list, migrate, delete, audit-list, audit-show, help")
|
|
965
|
+
print(" codeagent session migrate [--agent-id default] [--dry-run]")
|
|
966
|
+
print(" codeagent session audit-list <session_id>")
|
|
967
|
+
print(" codeagent session audit-show <session_id> --seq N")
|
|
968
|
+
print(" Enable snapshots: SEED_LLM_PROJECTION_AUDIT=1 in config/env")
|
|
969
|
+
elif action == 'delete' and session_id:
|
|
970
|
+
from seed.core.llm_sess import delete_stored_session
|
|
971
|
+
|
|
972
|
+
if delete_stored_session(session_id, agent_id):
|
|
973
|
+
print(f"Session deleted: {session_id}")
|
|
974
|
+
else:
|
|
975
|
+
print(f"Session not found: {session_id}")
|
|
976
|
+
else:
|
|
977
|
+
print(f"Session action '{action}': invalid or missing arguments.")
|
|
978
|
+
|
|
979
|
+
# Note: entry points are __main__.py / main.py / cli_pkg, not this file directly
|