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.
Files changed (173) hide show
  1. codeagent/__init__.py +10 -0
  2. codeagent/__main__.py +13 -0
  3. codeagent/cli/__init__.py +13 -0
  4. codeagent/cli/entrypoint.py +161 -0
  5. codeagent/cli/main.py +979 -0
  6. codeagent/core/__init__.py +2 -0
  7. codeagent/core/attachments.py +342 -0
  8. codeagent/core/audio_models.py +50 -0
  9. codeagent/core/bootstrap.py +178 -0
  10. codeagent/core/default_presets.py +159 -0
  11. codeagent/core/env.py +106 -0
  12. codeagent/core/folder_picker.py +151 -0
  13. codeagent/core/image_understanding.py +90 -0
  14. codeagent/core/models.py +6 -0
  15. codeagent/core/paths.py +238 -0
  16. codeagent/core/pricing.py +107 -0
  17. codeagent/core/process_ports.py +258 -0
  18. codeagent/core/seed_bridge.py +21 -0
  19. codeagent/core/settings.py +98 -0
  20. codeagent/core/speech_synth.py +330 -0
  21. codeagent/core/team_config.py +247 -0
  22. codeagent/core/team_manager.py +245 -0
  23. codeagent/core/token_counter.py +41 -0
  24. codeagent/core/tts_voices.py +103 -0
  25. codeagent/core/usage_billing.py +72 -0
  26. codeagent/core/vision_models.py +93 -0
  27. codeagent/memory/diary.py +144 -0
  28. codeagent/persona_defaults/README.md +12 -0
  29. codeagent/persona_defaults/agent.md +352 -0
  30. codeagent/persona_defaults/identity.md +29 -0
  31. codeagent/persona_defaults/memory.md +4 -0
  32. codeagent/persona_defaults/skills.md +325 -0
  33. codeagent/persona_defaults/soul.md +38 -0
  34. codeagent/persona_defaults/tools.md +41 -0
  35. codeagent/persona_defaults/user.md +18 -0
  36. codeagent/runtime/__init__.py +11 -0
  37. codeagent/runtime/agent_context.py +6 -0
  38. codeagent/runtime/agent_runtime.py +21 -0
  39. codeagent/runtime/compact_prompt.py +9 -0
  40. codeagent/runtime/compact_state.py +40 -0
  41. codeagent/runtime/llm_worker.py +132 -0
  42. codeagent/runtime/orchestrator.py +161 -0
  43. codeagent/runtime/prompt_enrichment.py +324 -0
  44. codeagent/runtime/session_manager.py +30 -0
  45. codeagent/runtime/task_split.py +38 -0
  46. codeagent/runtime/turn_loop.py +13 -0
  47. codeagent/runtime/worker.py +48 -0
  48. codeagent/server/__init__.py +505 -0
  49. codeagent/server/app_factory.py +1292 -0
  50. codeagent/server/attachment_api.py +261 -0
  51. codeagent/server/hub.py +175 -0
  52. codeagent/server/icon.png +0 -0
  53. codeagent/server/self_healing.py +143 -0
  54. codeagent/server/team_engine.py +243 -0
  55. codeagent/server/tts_api.py +51 -0
  56. codeagent/server/webui_api_app.py +3180 -0
  57. codeagent/skills/__init__.py +2 -0
  58. codeagent/skills/select.py +144 -0
  59. codeagent/tools/__init__.py +6 -0
  60. codeagent/tools/agent_tools.py +10 -0
  61. codeagent/web/__init__.py +1 -0
  62. codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
  63. codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
  64. codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
  65. codeagent/web/auth_impl.py +326 -0
  66. codeagent/web/static/00-marked.min.js +6 -0
  67. codeagent/web/static/00-mermaid.min.js +2314 -0
  68. codeagent/web/static/00-purify.min.js +3 -0
  69. codeagent/web/static/00-storage.js +83 -0
  70. codeagent/web/static/00-utils.js +51 -0
  71. codeagent/web/static/01-theme.css +96 -0
  72. codeagent/web/static/01a-scroll.js +86 -0
  73. codeagent/web/static/01b-think.js +62 -0
  74. codeagent/web/static/01c-session-identity.js +81 -0
  75. codeagent/web/static/01c-session-thinking.js +61 -0
  76. codeagent/web/static/01c-session-tree-menus.js +349 -0
  77. codeagent/web/static/01c-session-tree.js +531 -0
  78. codeagent/web/static/01c-session-utilities.js +83 -0
  79. codeagent/web/static/01c-vision-model.js +132 -0
  80. codeagent/web/static/01d-image-gen-model.js +84 -0
  81. codeagent/web/static/01e-audio-model.js +97 -0
  82. codeagent/web/static/01f-compose-ui.js +88 -0
  83. codeagent/web/static/01g-tts-settings.js +128 -0
  84. codeagent/web/static/01h-music-model.js +85 -0
  85. codeagent/web/static/01i-model-stack.js +291 -0
  86. codeagent/web/static/01j-video-gen-model.js +85 -0
  87. codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
  88. codeagent/web/static/01k-model-stack-state.js +209 -0
  89. codeagent/web/static/01q-inflight.js +195 -0
  90. codeagent/web/static/02-reset.css +22 -0
  91. codeagent/web/static/02-session-ui.js +201 -0
  92. codeagent/web/static/03-layout-p1.css +2 -0
  93. codeagent/web/static/03-layout-p2.css +2 -0
  94. codeagent/web/static/03a-links-stream.js +88 -0
  95. codeagent/web/static/03b-stream-text.js +119 -0
  96. codeagent/web/static/03c-render-bubble.js +520 -0
  97. codeagent/web/static/03d-agent-bubble.js +126 -0
  98. codeagent/web/static/03e-tool-trace.js +182 -0
  99. codeagent/web/static/03f-timeline.js +54 -0
  100. codeagent/web/static/03g-bubble-tts.js +292 -0
  101. codeagent/web/static/04-components-p1.css +1 -0
  102. codeagent/web/static/04-components-p2b.css +1 -0
  103. codeagent/web/static/04-components-p3.css +2 -0
  104. codeagent/web/static/04-components-p4.css +1 -0
  105. codeagent/web/static/04-components-p5.css +1 -0
  106. codeagent/web/static/04-components-p6.css +58 -0
  107. codeagent/web/static/04-components-p7.css +4 -0
  108. codeagent/web/static/04-components-p8.css +1 -0
  109. codeagent/web/static/04-components-p9.css +1 -0
  110. codeagent/web/static/04-ws-connect.js +351 -0
  111. codeagent/web/static/04-ws-helpers.js +116 -0
  112. codeagent/web/static/05-chat-p1.css +1058 -0
  113. codeagent/web/static/05-chat-p2.css +1 -0
  114. codeagent/web/static/05-chat-p3.css +295 -0
  115. codeagent/web/static/05a-session-history.js +176 -0
  116. codeagent/web/static/05b-session-list.js +114 -0
  117. codeagent/web/static/05c-dialog.js +123 -0
  118. codeagent/web/static/05d-project-create.js +172 -0
  119. codeagent/web/static/05e-project-remote.js +143 -0
  120. codeagent/web/static/06-chat.js +552 -0
  121. codeagent/web/static/06-utilities.css +181 -0
  122. codeagent/web/static/06b-attachments.js +304 -0
  123. codeagent/web/static/06c-camera.js +488 -0
  124. codeagent/web/static/07-activity-bar.css +127 -0
  125. codeagent/web/static/09-git-p1.css +1 -0
  126. codeagent/web/static/09-git-p2.css +1 -0
  127. codeagent/web/static/09a-init-bind.js +110 -0
  128. codeagent/web/static/09b-panel-toggle.js +80 -0
  129. codeagent/web/static/09c-api-refresh.js +100 -0
  130. codeagent/web/static/09d-status-log.js +80 -0
  131. codeagent/web/static/09e-commit.js +80 -0
  132. codeagent/web/static/09f-remote.js +93 -0
  133. codeagent/web/static/11a-cron-parse.js +117 -0
  134. codeagent/web/static/11b-cron-panel.js +100 -0
  135. codeagent/web/static/11c-cron-card.js +133 -0
  136. codeagent/web/static/11d-cron-form.js +105 -0
  137. codeagent/web/static/11e-env-config.js +168 -0
  138. codeagent/web/static/11f-env-llm-a-state.js +136 -0
  139. codeagent/web/static/11f-env-llm-b-form.js +409 -0
  140. codeagent/web/static/11f-env-llm-c-board.js +322 -0
  141. codeagent/web/static/11g-env-paths-git.js +973 -0
  142. codeagent/web/static/11h-env-mcp-a-state.js +161 -0
  143. codeagent/web/static/11h-env-mcp-b-form.js +229 -0
  144. codeagent/web/static/11h-env-mcp-c-board.js +565 -0
  145. codeagent/web/static/12-plugins.js +127 -0
  146. codeagent/web/static/13a-skills.js +156 -0
  147. codeagent/web/static/13a-todo-utils.js +140 -0
  148. codeagent/web/static/13b-todo-fetch.js +106 -0
  149. codeagent/web/static/13b-tools.js +127 -0
  150. codeagent/web/static/13c-todo-crud.js +92 -0
  151. codeagent/web/static/14-activity-bar.js +177 -0
  152. codeagent/web/static/15-plans.css +194 -0
  153. codeagent/web/static/15-plans.js +219 -0
  154. codeagent/web/static/16-mobile.css +612 -0
  155. codeagent/web/static/16-mobile.js +178 -0
  156. codeagent/web/static/17-preset-ux.css +287 -0
  157. codeagent/web/static/18-agent-mgr.css +616 -0
  158. codeagent/web/static/18-agent-mgr.js +204 -0
  159. codeagent/web/static/24-health-dashboard.js +68 -0
  160. codeagent/web/static/99-brand-polish.css +152 -0
  161. codeagent/web/static/body.html +824 -0
  162. codeagent/web/static/setup/setup_form.html +171 -0
  163. codeagent/web/static/setup/setup_head.html +100 -0
  164. codeagent/web/static/setup/setup_tail.html +264 -0
  165. codeagent/web/static/setup/setup_test.html +38 -0
  166. codeagent/web/web_login.html +265 -0
  167. codeagent/web/webui.html +35 -0
  168. tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
  169. tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
  170. tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
  171. tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
  172. tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
  173. 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