python-codex 0.2.7__py3-none-any.whl → 0.3.0__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.
- pycodex/__init__.py +14 -14
- pycodex/agent.py +465 -499
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -510
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +324 -253
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +76 -45
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +245 -983
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -176
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +1 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +58 -25
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +7 -5
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +41 -72
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/{image_utils.py → utils/image_utils.py} +8 -11
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +217 -163
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- responses_server/config.py +23 -23
- responses_server/messages_api.py +51 -53
- responses_server/payload_processors.py +25 -20
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +101 -98
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +36 -14
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +461 -375
- workspace_server/workspace.html +852 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +137 -79
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -560
- python_codex-0.2.7.dist-info/METADATA +0 -455
- python_codex-0.2.7.dist-info/RECORD +0 -93
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,713 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import re
|
|
3
|
-
import shlex
|
|
4
|
-
import typing
|
|
5
|
-
|
|
6
|
-
from ..protocol import ToolCall, ToolResult
|
|
7
|
-
|
|
8
|
-
ANSI_RESET = "\x1b[0m"
|
|
9
|
-
ANSI_BOLD = "\x1b[1m"
|
|
10
|
-
ANSI_DIM = "\x1b[2m"
|
|
11
|
-
ANSI_RED = "\x1b[31m"
|
|
12
|
-
ANSI_GREEN = "\x1b[32m"
|
|
13
|
-
ANSI_YELLOW = "\x1b[33m"
|
|
14
|
-
ANSI_BLUE = "\x1b[34m"
|
|
15
|
-
ANSI_MAGENTA = "\x1b[35m"
|
|
16
|
-
ANSI_CYAN = "\x1b[36m"
|
|
17
|
-
TOOL_ARGUMENT_PREVIEW_LIMIT = 200
|
|
18
|
-
RUNNING_SESSION_MARKER = "Process running with session ID "
|
|
19
|
-
PYTHON_HEREDOC_RE = re.compile(
|
|
20
|
-
r"\bpython(?:\d+(?:\.\d+)?)?\s+-\s+<<[-]?\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\1"
|
|
21
|
-
)
|
|
22
|
-
TOOL_COLOR_MAP = {
|
|
23
|
-
"update_plan": ANSI_CYAN,
|
|
24
|
-
"exec_command": ANSI_YELLOW,
|
|
25
|
-
"write_stdin": ANSI_MAGENTA,
|
|
26
|
-
"shell": ANSI_YELLOW,
|
|
27
|
-
"shell_command": ANSI_YELLOW,
|
|
28
|
-
"exec": ANSI_YELLOW,
|
|
29
|
-
"wait": ANSI_MAGENTA,
|
|
30
|
-
"web_search": ANSI_MAGENTA,
|
|
31
|
-
"spawn_agent": ANSI_BLUE,
|
|
32
|
-
"send_input": ANSI_BLUE,
|
|
33
|
-
"wait_agent": ANSI_BLUE,
|
|
34
|
-
"resume_agent": ANSI_BLUE,
|
|
35
|
-
"close_agent": ANSI_BLUE,
|
|
36
|
-
"read_file": ANSI_DIM,
|
|
37
|
-
"list_dir": ANSI_DIM,
|
|
38
|
-
"grep_files": ANSI_DIM,
|
|
39
|
-
"view_image": ANSI_DIM,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def colorize_cli_message(text: "str", kind: "str", enabled: "bool") -> "str":
|
|
44
|
-
if not enabled:
|
|
45
|
-
return text
|
|
46
|
-
palette = {
|
|
47
|
-
"assistant": ANSI_GREEN,
|
|
48
|
-
"status": ANSI_CYAN,
|
|
49
|
-
"error": ANSI_RED,
|
|
50
|
-
}
|
|
51
|
-
color = palette.get(kind)
|
|
52
|
-
return _colorize_with(text, color, enabled)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def colorize_tool_message(
|
|
56
|
-
message: "str",
|
|
57
|
-
enabled: "bool",
|
|
58
|
-
tool_name: "typing.Union[str, None]" = None,
|
|
59
|
-
) -> "str":
|
|
60
|
-
if message.startswith("[error]"):
|
|
61
|
-
return colorize_cli_message(message, "error", enabled)
|
|
62
|
-
if message.startswith((" [x]", " [>]", " [ ]")):
|
|
63
|
-
return _colorize_with(message, TOOL_COLOR_MAP.get("update_plan"), enabled)
|
|
64
|
-
resolved_tool_name = _tool_name_from_message(message) or (tool_name or "")
|
|
65
|
-
if resolved_tool_name == "exec_command" and "session_id=" in message:
|
|
66
|
-
return _colorize_with(message, ANSI_DIM, enabled)
|
|
67
|
-
if resolved_tool_name == "exec_command" and not message.startswith("["):
|
|
68
|
-
return _colorize_with(message, "", enabled)
|
|
69
|
-
return _colorize_with(
|
|
70
|
-
message, TOOL_COLOR_MAP.get(resolved_tool_name, ANSI_DIM), enabled
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _colorize_with(
|
|
75
|
-
text: "str", color: "typing.Union[str, None]", enabled: "bool"
|
|
76
|
-
) -> "str":
|
|
77
|
-
if not enabled or color is None:
|
|
78
|
-
return text
|
|
79
|
-
return f"{ANSI_BOLD}{color}{text}{ANSI_RESET}"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _tool_name_from_message(message: "str") -> "str":
|
|
83
|
-
if not message.startswith("["):
|
|
84
|
-
return ""
|
|
85
|
-
end = message.find("]")
|
|
86
|
-
if end <= 1:
|
|
87
|
-
return ""
|
|
88
|
-
return message[1:end]
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def tool_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
92
|
-
tool_name = str(payload.get("tool_name", "")).strip()
|
|
93
|
-
handler = _TOOL_MESSAGE_HANDLERS.get(tool_name, _generic_tool_message)
|
|
94
|
-
return handler(payload)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def _paired_payload(
|
|
98
|
-
payload: "typing.Dict[str, object]",
|
|
99
|
-
) -> "typing.Tuple[typing.Union[ToolCall, None], typing.Union[ToolResult, None]]":
|
|
100
|
-
call = payload.get("call")
|
|
101
|
-
result = payload.get("result")
|
|
102
|
-
if isinstance(call, ToolCall) and isinstance(result, ToolResult):
|
|
103
|
-
return call, result
|
|
104
|
-
return None, None
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _legacy_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
108
|
-
return str(payload.get("summary", "") or "").strip()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _combine_call_and_result(
|
|
112
|
-
call_summary: "typing.Union[str, None]",
|
|
113
|
-
result_summary: "typing.Union[str, None]",
|
|
114
|
-
) -> "str":
|
|
115
|
-
if call_summary and result_summary:
|
|
116
|
-
return f"{call_summary} -> {result_summary}"
|
|
117
|
-
if call_summary:
|
|
118
|
-
return call_summary
|
|
119
|
-
return result_summary or ""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _agent_status_summary(status: "object") -> "str":
|
|
123
|
-
if isinstance(status, str):
|
|
124
|
-
return status
|
|
125
|
-
if isinstance(status, dict):
|
|
126
|
-
if "completed" in status:
|
|
127
|
-
completed = status.get("completed")
|
|
128
|
-
if completed is None:
|
|
129
|
-
return "completed"
|
|
130
|
-
return f"completed: {_truncate_text(str(completed), limit=48)}"
|
|
131
|
-
if "errored" in status:
|
|
132
|
-
return f"errored: {_truncate_text(str(status.get('errored', '')), limit=48)}"
|
|
133
|
-
return _truncate_text(json.dumps(status, ensure_ascii=False, separators=(",", ":")))
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def _result_output_summary(result: "ToolResult") -> "typing.Union[str, None]":
|
|
137
|
-
if isinstance(result.output, (dict, list)):
|
|
138
|
-
return _truncate_text(
|
|
139
|
-
json.dumps(result.output, ensure_ascii=False, separators=(",", ":"))
|
|
140
|
-
)
|
|
141
|
-
return _output_text_summary(result.output_text())
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def _output_text_summary(text: "str") -> "typing.Union[str, None]":
|
|
145
|
-
lines = [line.strip() for line in text.splitlines()]
|
|
146
|
-
if "Output:" in lines:
|
|
147
|
-
output_index = lines.index("Output:")
|
|
148
|
-
for line in lines[output_index + 1 :]:
|
|
149
|
-
if line:
|
|
150
|
-
return _truncate_text(line)
|
|
151
|
-
for line in lines:
|
|
152
|
-
if not line:
|
|
153
|
-
continue
|
|
154
|
-
if line.startswith(("Exit code:", "Wall time:", "Command:")):
|
|
155
|
-
continue
|
|
156
|
-
return _truncate_text(line)
|
|
157
|
-
return None
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _running_exec_session_id(result: "ToolResult") -> "typing.Union[str, None]":
|
|
161
|
-
for line in result.output_text().splitlines():
|
|
162
|
-
line = line.strip()
|
|
163
|
-
if line.startswith(RUNNING_SESSION_MARKER):
|
|
164
|
-
session_id = line[len(RUNNING_SESSION_MARKER) :].strip()
|
|
165
|
-
if session_id:
|
|
166
|
-
return session_id
|
|
167
|
-
return None
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def _exec_command_preview(command: "str") -> "str":
|
|
171
|
-
heredoc = _python_heredoc_preview(command)
|
|
172
|
-
if heredoc:
|
|
173
|
-
return heredoc
|
|
174
|
-
return _truncate_text(command, limit=TOOL_ARGUMENT_PREVIEW_LIMIT)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def _python_heredoc_preview(command: "str") -> "typing.Union[str, None]":
|
|
178
|
-
lines = command.splitlines()
|
|
179
|
-
if not lines:
|
|
180
|
-
return None
|
|
181
|
-
|
|
182
|
-
match = PYTHON_HEREDOC_RE.search(lines[0])
|
|
183
|
-
if match is None:
|
|
184
|
-
return None
|
|
185
|
-
delimiter = match.group(2)
|
|
186
|
-
|
|
187
|
-
end_index = None
|
|
188
|
-
for index, line in enumerate(lines[1:], start=1):
|
|
189
|
-
if line.strip() == delimiter:
|
|
190
|
-
end_index = index
|
|
191
|
-
break
|
|
192
|
-
if end_index is None:
|
|
193
|
-
return None
|
|
194
|
-
|
|
195
|
-
return "\n".join(lines[: end_index + 1])
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def _plan_progress_summary(plan: "typing.List[object]") -> "str":
|
|
199
|
-
total = len(plan)
|
|
200
|
-
completed = 0
|
|
201
|
-
in_progress = 0
|
|
202
|
-
for item in plan:
|
|
203
|
-
if not isinstance(item, dict):
|
|
204
|
-
continue
|
|
205
|
-
status = str(item.get("status", "")).strip()
|
|
206
|
-
if status == "completed":
|
|
207
|
-
completed += 1
|
|
208
|
-
elif status == "in_progress":
|
|
209
|
-
in_progress += 1
|
|
210
|
-
if total == 0:
|
|
211
|
-
return "0 steps"
|
|
212
|
-
if completed >= total:
|
|
213
|
-
return f"Done {completed}/{total}"
|
|
214
|
-
if in_progress:
|
|
215
|
-
return f"Working on {completed + in_progress}/{total}"
|
|
216
|
-
return f"Planned {completed}/{total}"
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def _exec_command_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
220
|
-
call, result = _paired_payload(payload)
|
|
221
|
-
if call is None or result is None:
|
|
222
|
-
return _legacy_summary(payload)
|
|
223
|
-
|
|
224
|
-
command = None
|
|
225
|
-
if isinstance(call.arguments, dict):
|
|
226
|
-
cmd = call.arguments.get("cmd")
|
|
227
|
-
if cmd not in (None, ""):
|
|
228
|
-
command = _exec_command_preview(str(cmd))
|
|
229
|
-
session_id = _running_exec_session_id(result)
|
|
230
|
-
if session_id:
|
|
231
|
-
return _combine_call_and_result(command, f"session_id={session_id}")
|
|
232
|
-
return _combine_call_and_result(command, _result_output_summary(result))
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def _shell_command_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
236
|
-
call, result = _paired_payload(payload)
|
|
237
|
-
if call is None or result is None:
|
|
238
|
-
return _legacy_summary(payload)
|
|
239
|
-
|
|
240
|
-
command = None
|
|
241
|
-
if isinstance(call.arguments, dict):
|
|
242
|
-
raw_command = call.arguments.get("command")
|
|
243
|
-
if raw_command not in (None, ""):
|
|
244
|
-
command = _truncate_text(
|
|
245
|
-
str(raw_command), limit=TOOL_ARGUMENT_PREVIEW_LIMIT
|
|
246
|
-
)
|
|
247
|
-
return _combine_call_and_result(command, _result_output_summary(result))
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _shell_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
251
|
-
call, result = _paired_payload(payload)
|
|
252
|
-
if call is None or result is None:
|
|
253
|
-
return _legacy_summary(payload)
|
|
254
|
-
|
|
255
|
-
command = None
|
|
256
|
-
if isinstance(call.arguments, dict):
|
|
257
|
-
raw_command = call.arguments.get("command")
|
|
258
|
-
if isinstance(raw_command, list) and raw_command:
|
|
259
|
-
rendered = " ".join(shlex.quote(str(part)) for part in raw_command)
|
|
260
|
-
command = _truncate_text(rendered, limit=TOOL_ARGUMENT_PREVIEW_LIMIT)
|
|
261
|
-
return _combine_call_and_result(command, _result_output_summary(result))
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
def _write_stdin_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
265
|
-
call, result = _paired_payload(payload)
|
|
266
|
-
if call is None or result is None:
|
|
267
|
-
return _legacy_summary(payload)
|
|
268
|
-
|
|
269
|
-
command = None
|
|
270
|
-
if isinstance(call.arguments, dict):
|
|
271
|
-
session_id_value = call.arguments.get("session_id")
|
|
272
|
-
if session_id_value not in (None, ""):
|
|
273
|
-
session_id = int(session_id_value)
|
|
274
|
-
chars = call.arguments.get("chars") or ""
|
|
275
|
-
if not chars:
|
|
276
|
-
command = f"poll session {session_id}"
|
|
277
|
-
else:
|
|
278
|
-
command = (
|
|
279
|
-
f"session {session_id} <- {_truncate_text(str(chars), limit=32)}"
|
|
280
|
-
)
|
|
281
|
-
return _combine_call_and_result(command, _result_output_summary(result))
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def _exec_code_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
285
|
-
call, result = _paired_payload(payload)
|
|
286
|
-
if call is None or result is None:
|
|
287
|
-
return _legacy_summary(payload)
|
|
288
|
-
return _result_output_summary(result) or ""
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def _wait_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
292
|
-
call, result = _paired_payload(payload)
|
|
293
|
-
if call is None or result is None:
|
|
294
|
-
return _legacy_summary(payload)
|
|
295
|
-
return _result_output_summary(result) or ""
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def _read_file_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
299
|
-
call, result = _paired_payload(payload)
|
|
300
|
-
if call is None or result is None:
|
|
301
|
-
return _legacy_summary(payload)
|
|
302
|
-
|
|
303
|
-
path = None
|
|
304
|
-
if isinstance(call.arguments, dict):
|
|
305
|
-
raw_path = call.arguments.get("file_path")
|
|
306
|
-
if raw_path not in (None, ""):
|
|
307
|
-
path = _truncate_text(str(raw_path), limit=TOOL_ARGUMENT_PREVIEW_LIMIT)
|
|
308
|
-
return _combine_call_and_result(path, _result_output_summary(result))
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
def _list_dir_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
312
|
-
call, result = _paired_payload(payload)
|
|
313
|
-
if call is None or result is None:
|
|
314
|
-
return _legacy_summary(payload)
|
|
315
|
-
|
|
316
|
-
path = None
|
|
317
|
-
if isinstance(call.arguments, dict):
|
|
318
|
-
raw_path = call.arguments.get("dir_path")
|
|
319
|
-
if raw_path not in (None, ""):
|
|
320
|
-
path = _truncate_text(str(raw_path), limit=TOOL_ARGUMENT_PREVIEW_LIMIT)
|
|
321
|
-
return _combine_call_and_result(path, _result_output_summary(result))
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def _grep_files_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
325
|
-
call, result = _paired_payload(payload)
|
|
326
|
-
if call is None or result is None:
|
|
327
|
-
return _legacy_summary(payload)
|
|
328
|
-
|
|
329
|
-
query = None
|
|
330
|
-
if isinstance(call.arguments, dict):
|
|
331
|
-
pattern = call.arguments.get("pattern")
|
|
332
|
-
path = call.arguments.get("path")
|
|
333
|
-
if pattern not in (None, "") and path not in (None, ""):
|
|
334
|
-
query = _truncate_text(
|
|
335
|
-
f"{pattern} @ {path}", limit=TOOL_ARGUMENT_PREVIEW_LIMIT
|
|
336
|
-
)
|
|
337
|
-
elif pattern not in (None, ""):
|
|
338
|
-
query = _truncate_text(str(pattern), limit=TOOL_ARGUMENT_PREVIEW_LIMIT)
|
|
339
|
-
return _combine_call_and_result(query, _result_output_summary(result))
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def _view_image_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
343
|
-
call, result = _paired_payload(payload)
|
|
344
|
-
if call is None or result is None:
|
|
345
|
-
return _legacy_summary(payload)
|
|
346
|
-
|
|
347
|
-
path = None
|
|
348
|
-
if isinstance(call.arguments, dict):
|
|
349
|
-
raw_path = call.arguments.get("path")
|
|
350
|
-
if raw_path not in (None, ""):
|
|
351
|
-
path = _truncate_text(str(raw_path), limit=TOOL_ARGUMENT_PREVIEW_LIMIT)
|
|
352
|
-
|
|
353
|
-
result_summary = None
|
|
354
|
-
if isinstance(result.output, list):
|
|
355
|
-
result_summary = f"{len(result.output)} image item(s)"
|
|
356
|
-
if result_summary is None:
|
|
357
|
-
result_summary = _result_output_summary(result)
|
|
358
|
-
return _combine_call_and_result(path, result_summary)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
def _update_plan_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
362
|
-
call, result = _paired_payload(payload)
|
|
363
|
-
if call is None or result is None:
|
|
364
|
-
return _legacy_summary(payload)
|
|
365
|
-
|
|
366
|
-
if isinstance(call.arguments, dict):
|
|
367
|
-
plan = call.arguments.get("plan")
|
|
368
|
-
if isinstance(plan, list):
|
|
369
|
-
return _plan_progress_summary(plan)
|
|
370
|
-
|
|
371
|
-
if isinstance(result.output, dict):
|
|
372
|
-
plan = result.output.get("plan")
|
|
373
|
-
if isinstance(plan, list):
|
|
374
|
-
return f"{len(plan)} steps"
|
|
375
|
-
return _result_output_summary(result) or ""
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
def _spawn_agent_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
379
|
-
call, result = _paired_payload(payload)
|
|
380
|
-
if call is None or result is None:
|
|
381
|
-
return _legacy_summary(payload)
|
|
382
|
-
|
|
383
|
-
if isinstance(result.output, dict):
|
|
384
|
-
agent_id = str(result.output.get("agent_id", "")).strip()
|
|
385
|
-
nickname = str(result.output.get("nickname", "")).strip()
|
|
386
|
-
if nickname and agent_id:
|
|
387
|
-
return f"{nickname} ({_short_id(agent_id)})"
|
|
388
|
-
return _result_output_summary(result) or ""
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
def _send_input_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
392
|
-
call, result = _paired_payload(payload)
|
|
393
|
-
if call is None or result is None:
|
|
394
|
-
return _legacy_summary(payload)
|
|
395
|
-
|
|
396
|
-
command = None
|
|
397
|
-
if isinstance(call.arguments, dict):
|
|
398
|
-
agent_id = call.arguments.get("id")
|
|
399
|
-
message = call.arguments.get("message")
|
|
400
|
-
prefix = f"{_short_id(str(agent_id))} <- " if agent_id else ""
|
|
401
|
-
if message not in (None, ""):
|
|
402
|
-
command = f"{prefix}{_truncate_text(str(message), limit=40)}"
|
|
403
|
-
elif prefix:
|
|
404
|
-
command = prefix.rstrip()
|
|
405
|
-
|
|
406
|
-
result_summary = None
|
|
407
|
-
if isinstance(result.output, dict):
|
|
408
|
-
submission_id = str(result.output.get("submission_id", "")).strip()
|
|
409
|
-
if submission_id:
|
|
410
|
-
result_summary = f"queued {_short_id(submission_id)}"
|
|
411
|
-
if result_summary is None:
|
|
412
|
-
result_summary = _result_output_summary(result)
|
|
413
|
-
return _combine_call_and_result(command, result_summary)
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
def _wait_agent_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
417
|
-
call, result = _paired_payload(payload)
|
|
418
|
-
if call is None or result is None:
|
|
419
|
-
return _legacy_summary(payload)
|
|
420
|
-
|
|
421
|
-
if isinstance(result.output, dict):
|
|
422
|
-
if result.output.get("timed_out") is True:
|
|
423
|
-
return "timed out"
|
|
424
|
-
status = result.output.get("status")
|
|
425
|
-
if isinstance(status, dict):
|
|
426
|
-
parts = []
|
|
427
|
-
for agent_id, agent_status in status.items():
|
|
428
|
-
if not isinstance(agent_id, str):
|
|
429
|
-
continue
|
|
430
|
-
parts.append(
|
|
431
|
-
f"{_short_id(agent_id)}={_agent_status_summary(agent_status)}"
|
|
432
|
-
)
|
|
433
|
-
if parts:
|
|
434
|
-
return _truncate_text(", ".join(parts), limit=96)
|
|
435
|
-
return _result_output_summary(result) or ""
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
def _resume_agent_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
439
|
-
call, result = _paired_payload(payload)
|
|
440
|
-
if call is None or result is None:
|
|
441
|
-
return _legacy_summary(payload)
|
|
442
|
-
|
|
443
|
-
agent_id_summary = None
|
|
444
|
-
if isinstance(call.arguments, dict):
|
|
445
|
-
agent_id = call.arguments.get("id")
|
|
446
|
-
if agent_id not in (None, ""):
|
|
447
|
-
agent_id_summary = _short_id(str(agent_id))
|
|
448
|
-
|
|
449
|
-
result_summary = None
|
|
450
|
-
if isinstance(result.output, dict):
|
|
451
|
-
result_summary = _agent_status_summary(result.output.get("status"))
|
|
452
|
-
if result_summary is None:
|
|
453
|
-
result_summary = _result_output_summary(result)
|
|
454
|
-
return _combine_call_and_result(agent_id_summary, result_summary)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
def _close_agent_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
458
|
-
call, result = _paired_payload(payload)
|
|
459
|
-
if call is None or result is None:
|
|
460
|
-
return _legacy_summary(payload)
|
|
461
|
-
|
|
462
|
-
agent_id_summary = None
|
|
463
|
-
if isinstance(call.arguments, dict):
|
|
464
|
-
agent_id = call.arguments.get("id")
|
|
465
|
-
if agent_id not in (None, ""):
|
|
466
|
-
agent_id_summary = _short_id(str(agent_id))
|
|
467
|
-
|
|
468
|
-
result_summary = None
|
|
469
|
-
if isinstance(result.output, dict):
|
|
470
|
-
result_summary = _agent_status_summary(result.output.get("status"))
|
|
471
|
-
if result_summary is None:
|
|
472
|
-
result_summary = _result_output_summary(result)
|
|
473
|
-
return _combine_call_and_result(agent_id_summary, result_summary)
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
def _generic_payload_summary(payload: "typing.Dict[str, object]") -> "str":
|
|
477
|
-
call, result = _paired_payload(payload)
|
|
478
|
-
if call is None or result is None:
|
|
479
|
-
return _legacy_summary(payload)
|
|
480
|
-
return _result_output_summary(result) or ""
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
def _update_plan_message(payload: "typing.Dict[str, object]") -> "str":
|
|
484
|
-
summary = _update_plan_summary(payload)
|
|
485
|
-
if bool(payload.get("is_error")):
|
|
486
|
-
return (
|
|
487
|
-
f"[error] update_plan failed: {summary}"
|
|
488
|
-
if summary
|
|
489
|
-
else "[error] update_plan failed"
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
lines = [f"[update_plan] {summary}" if summary else "[update_plan] Plan updated"]
|
|
493
|
-
plan_items = []
|
|
494
|
-
call = payload.get("call")
|
|
495
|
-
if isinstance(call, ToolCall) and isinstance(call.arguments, dict):
|
|
496
|
-
raw_plan = call.arguments.get("plan")
|
|
497
|
-
if isinstance(raw_plan, list):
|
|
498
|
-
plan_items = raw_plan
|
|
499
|
-
else:
|
|
500
|
-
raw_plan_items = payload.get("plan_items")
|
|
501
|
-
if isinstance(raw_plan_items, list):
|
|
502
|
-
plan_items = raw_plan_items
|
|
503
|
-
for item in plan_items:
|
|
504
|
-
if not isinstance(item, dict):
|
|
505
|
-
continue
|
|
506
|
-
step = str(item.get("step", "")).strip()
|
|
507
|
-
status = str(item.get("status", "")).strip()
|
|
508
|
-
if not step:
|
|
509
|
-
continue
|
|
510
|
-
marker = {
|
|
511
|
-
"completed": "[x]",
|
|
512
|
-
"in_progress": "[>]",
|
|
513
|
-
"pending": "[ ]",
|
|
514
|
-
}.get(status, "[ ]")
|
|
515
|
-
lines.append(f" {marker} {step}")
|
|
516
|
-
return "\n".join(lines)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
def _exec_command_message(payload: "typing.Dict[str, object]") -> "str":
|
|
520
|
-
summary = _exec_command_summary(payload)
|
|
521
|
-
if bool(payload.get("is_error")):
|
|
522
|
-
return (
|
|
523
|
-
f"[error] exec_command failed: {summary}"
|
|
524
|
-
if summary
|
|
525
|
-
else "[error] exec_command failed"
|
|
526
|
-
)
|
|
527
|
-
return f"[exec_command] {summary}" if summary else "[exec_command]"
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
def _write_stdin_message(payload: "typing.Dict[str, object]") -> "str":
|
|
531
|
-
summary = _write_stdin_summary(payload)
|
|
532
|
-
if bool(payload.get("is_error")):
|
|
533
|
-
return (
|
|
534
|
-
f"[error] write_stdin failed: {summary}"
|
|
535
|
-
if summary
|
|
536
|
-
else "[error] write_stdin failed"
|
|
537
|
-
)
|
|
538
|
-
return f"[write_stdin] {summary}" if summary else "[write_stdin]"
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
def _shell_message(payload: "typing.Dict[str, object]") -> "str":
|
|
542
|
-
summary = _shell_summary(payload)
|
|
543
|
-
if bool(payload.get("is_error")):
|
|
544
|
-
return f"[error] shell failed: {summary}" if summary else "[error] shell failed"
|
|
545
|
-
return f"[shell] {summary}" if summary else "[shell]"
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
def _shell_command_message(payload: "typing.Dict[str, object]") -> "str":
|
|
549
|
-
summary = _shell_command_summary(payload)
|
|
550
|
-
if bool(payload.get("is_error")):
|
|
551
|
-
return (
|
|
552
|
-
f"[error] shell_command failed: {summary}"
|
|
553
|
-
if summary
|
|
554
|
-
else "[error] shell_command failed"
|
|
555
|
-
)
|
|
556
|
-
return f"[shell_command] {summary}" if summary else "[shell_command]"
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
def _exec_message(payload: "typing.Dict[str, object]") -> "str":
|
|
560
|
-
summary = _exec_code_summary(payload)
|
|
561
|
-
if bool(payload.get("is_error")):
|
|
562
|
-
return f"[error] exec failed: {summary}" if summary else "[error] exec failed"
|
|
563
|
-
return f"[exec] {summary}" if summary else "[exec]"
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
def _wait_message(payload: "typing.Dict[str, object]") -> "str":
|
|
567
|
-
summary = _wait_summary(payload)
|
|
568
|
-
if bool(payload.get("is_error")):
|
|
569
|
-
return f"[error] wait failed: {summary}" if summary else "[error] wait failed"
|
|
570
|
-
return f"[wait] {summary}" if summary else "[wait]"
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
def _read_file_message(payload: "typing.Dict[str, object]") -> "str":
|
|
574
|
-
summary = _read_file_summary(payload)
|
|
575
|
-
if bool(payload.get("is_error")):
|
|
576
|
-
return (
|
|
577
|
-
f"[error] read_file failed: {summary}"
|
|
578
|
-
if summary
|
|
579
|
-
else "[error] read_file failed"
|
|
580
|
-
)
|
|
581
|
-
return f"[read_file] {summary}" if summary else "[read_file]"
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
def _list_dir_message(payload: "typing.Dict[str, object]") -> "str":
|
|
585
|
-
summary = _list_dir_summary(payload)
|
|
586
|
-
if bool(payload.get("is_error")):
|
|
587
|
-
return (
|
|
588
|
-
f"[error] list_dir failed: {summary}"
|
|
589
|
-
if summary
|
|
590
|
-
else "[error] list_dir failed"
|
|
591
|
-
)
|
|
592
|
-
return f"[list_dir] {summary}" if summary else "[list_dir]"
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
def _grep_files_message(payload: "typing.Dict[str, object]") -> "str":
|
|
596
|
-
summary = _grep_files_summary(payload)
|
|
597
|
-
if bool(payload.get("is_error")):
|
|
598
|
-
return (
|
|
599
|
-
f"[error] grep_files failed: {summary}"
|
|
600
|
-
if summary
|
|
601
|
-
else "[error] grep_files failed"
|
|
602
|
-
)
|
|
603
|
-
return f"[grep_files] {summary}" if summary else "[grep_files]"
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
def _view_image_message(payload: "typing.Dict[str, object]") -> "str":
|
|
607
|
-
summary = _view_image_summary(payload)
|
|
608
|
-
if bool(payload.get("is_error")):
|
|
609
|
-
return (
|
|
610
|
-
f"[error] view_image failed: {summary}"
|
|
611
|
-
if summary
|
|
612
|
-
else "[error] view_image failed"
|
|
613
|
-
)
|
|
614
|
-
return f"[view_image] {summary}" if summary else "[view_image]"
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
def _spawn_agent_message(payload: "typing.Dict[str, object]") -> "str":
|
|
618
|
-
summary = _spawn_agent_summary(payload)
|
|
619
|
-
if bool(payload.get("is_error")):
|
|
620
|
-
return (
|
|
621
|
-
f"[error] spawn_agent failed: {summary}"
|
|
622
|
-
if summary
|
|
623
|
-
else "[error] spawn_agent failed"
|
|
624
|
-
)
|
|
625
|
-
return f"[spawn_agent] spawned {summary}" if summary else "[spawn_agent] spawned"
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
def _send_input_message(payload: "typing.Dict[str, object]") -> "str":
|
|
629
|
-
summary = _send_input_summary(payload)
|
|
630
|
-
if bool(payload.get("is_error")):
|
|
631
|
-
return (
|
|
632
|
-
f"[error] send_input failed: {summary}"
|
|
633
|
-
if summary
|
|
634
|
-
else "[error] send_input failed"
|
|
635
|
-
)
|
|
636
|
-
return f"[send_input] {summary}" if summary else "[send_input]"
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
def _wait_agent_message(payload: "typing.Dict[str, object]") -> "str":
|
|
640
|
-
summary = _wait_agent_summary(payload)
|
|
641
|
-
if bool(payload.get("is_error")):
|
|
642
|
-
return (
|
|
643
|
-
f"[error] wait_agent failed: {summary}"
|
|
644
|
-
if summary
|
|
645
|
-
else "[error] wait_agent failed"
|
|
646
|
-
)
|
|
647
|
-
return f"[wait_agent] {summary}" if summary else "[wait_agent]"
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
def _resume_agent_message(payload: "typing.Dict[str, object]") -> "str":
|
|
651
|
-
summary = _resume_agent_summary(payload)
|
|
652
|
-
if bool(payload.get("is_error")):
|
|
653
|
-
return (
|
|
654
|
-
f"[error] resume_agent failed: {summary}"
|
|
655
|
-
if summary
|
|
656
|
-
else "[error] resume_agent failed"
|
|
657
|
-
)
|
|
658
|
-
return f"[resume_agent] {summary}" if summary else "[resume_agent]"
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
def _close_agent_message(payload: "typing.Dict[str, object]") -> "str":
|
|
662
|
-
summary = _close_agent_summary(payload)
|
|
663
|
-
if bool(payload.get("is_error")):
|
|
664
|
-
return (
|
|
665
|
-
f"[error] close_agent failed: {summary}"
|
|
666
|
-
if summary
|
|
667
|
-
else "[error] close_agent failed"
|
|
668
|
-
)
|
|
669
|
-
return f"[close_agent] {summary}" if summary else "[close_agent]"
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
def _generic_tool_message(payload: "typing.Dict[str, object]") -> "str":
|
|
673
|
-
tool_name = str(payload.get("tool_name", "")).strip()
|
|
674
|
-
summary = _generic_payload_summary(payload)
|
|
675
|
-
if bool(payload.get("is_error")):
|
|
676
|
-
if summary:
|
|
677
|
-
return f"[error] {tool_name} failed: {summary}"
|
|
678
|
-
return f"[error] {tool_name} failed"
|
|
679
|
-
return f"[{tool_name}] {summary}" if summary else f"[{tool_name}]"
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
_TOOL_MESSAGE_HANDLERS = {
|
|
683
|
-
"update_plan": _update_plan_message,
|
|
684
|
-
"exec_command": _exec_command_message,
|
|
685
|
-
"write_stdin": _write_stdin_message,
|
|
686
|
-
"shell": _shell_message,
|
|
687
|
-
"shell_command": _shell_command_message,
|
|
688
|
-
"exec": _exec_message,
|
|
689
|
-
"wait": _wait_message,
|
|
690
|
-
"read_file": _read_file_message,
|
|
691
|
-
"list_dir": _list_dir_message,
|
|
692
|
-
"grep_files": _grep_files_message,
|
|
693
|
-
"view_image": _view_image_message,
|
|
694
|
-
"spawn_agent": _spawn_agent_message,
|
|
695
|
-
"send_input": _send_input_message,
|
|
696
|
-
"wait_agent": _wait_agent_message,
|
|
697
|
-
"resume_agent": _resume_agent_message,
|
|
698
|
-
"close_agent": _close_agent_message,
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
def _short_id(value: "str", limit: "int" = 8) -> "str":
|
|
703
|
-
compact = value.strip()
|
|
704
|
-
if len(compact) <= limit + 4:
|
|
705
|
-
return compact
|
|
706
|
-
return f"{compact[:limit]}...{compact[-4:]}"
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
def _truncate_text(text: "str", limit: "int" = 96) -> "str":
|
|
710
|
-
compact = " ".join(text.split())
|
|
711
|
-
if len(compact) <= limit:
|
|
712
|
-
return compact
|
|
713
|
-
return compact[: limit - 3].rstrip() + "..."
|