nju-coding-agent-harness 0.2.2__tar.gz → 0.2.4__tar.gz

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 (84) hide show
  1. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/PKG-INFO +1 -1
  2. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/ask.py +12 -1
  3. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/webui/server.py +4 -1
  4. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/webui/static/index.html +95 -41
  5. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/webui/stream_agent.py +9 -5
  6. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/nju_coding_agent_harness.egg-info/PKG-INFO +1 -1
  7. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/pyproject.toml +1 -1
  8. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/README.md +0 -0
  9. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/__init__.py +0 -0
  10. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/agent.py +0 -0
  11. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/config.py +0 -0
  12. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/credentials.py +0 -0
  13. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/fake_llm.py +0 -0
  14. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/guardrails.py +0 -0
  15. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/hooks.py +0 -0
  16. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/llm.py +0 -0
  17. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/main.py +0 -0
  18. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/mcp.py +0 -0
  19. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/memory.py +0 -0
  20. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/policy.py +0 -0
  21. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/registry.py +0 -0
  22. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/sandbox.py +0 -0
  23. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/state.py +0 -0
  24. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/task_executor.py +0 -0
  25. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/__init__.py +0 -0
  26. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/conftest.py +0 -0
  27. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/fixtures/fake_mcp_server.py +0 -0
  28. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/mechanism_demo/__init__.py +0 -0
  29. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/mechanism_demo/demo_1_guardrail_deny.py +0 -0
  30. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/mechanism_demo/demo_2_feedback_change.py +0 -0
  31. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/mechanism_demo/demo_3_hitl_trace.py +0 -0
  32. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_acceptance_matrix.py +0 -0
  33. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_agent_context.py +0 -0
  34. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_agent_core.py +0 -0
  35. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_agent_end.py +0 -0
  36. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_agent_feedback.py +0 -0
  37. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_agent_prompt.py +0 -0
  38. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_config.py +0 -0
  39. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_credentials.py +0 -0
  40. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_docs.py +0 -0
  41. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_guardrails.py +0 -0
  42. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_hooks.py +0 -0
  43. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_llm.py +0 -0
  44. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_mcp.py +0 -0
  45. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_mechanism_demo.py +0 -0
  46. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_memory.py +0 -0
  47. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_perf_smoke.py +0 -0
  48. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_policy.py +0 -0
  49. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_registry.py +0 -0
  50. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_repl.py +0 -0
  51. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_sandbox.py +0 -0
  52. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_sandbox_wiring.py +0 -0
  53. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_security_scan.py +0 -0
  54. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_skeleton.py +0 -0
  55. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_state.py +0 -0
  56. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_task_executor.py +0 -0
  57. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_task_executor_web.py +0 -0
  58. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_tools_ask.py +0 -0
  59. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_tools_bash.py +0 -0
  60. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_tools_files.py +0 -0
  61. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_tools_memory.py +0 -0
  62. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_tools_notes.py +0 -0
  63. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_tools_skills.py +0 -0
  64. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_tools_subagent.py +0 -0
  65. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/test_tools_web.py +0 -0
  66. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/webui/test_server.py +0 -0
  67. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tests/webui/test_stream_agent.py +0 -0
  68. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/__init__.py +0 -0
  69. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/bash.py +0 -0
  70. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/files.py +0 -0
  71. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/memory.py +0 -0
  72. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/notes.py +0 -0
  73. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/search.py +0 -0
  74. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/skills.py +0 -0
  75. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/subagent.py +0 -0
  76. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/tools/web.py +0 -0
  77. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/transcript.py +0 -0
  78. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/harness/webui/__init__.py +0 -0
  79. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/nju_coding_agent_harness.egg-info/SOURCES.txt +0 -0
  80. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/nju_coding_agent_harness.egg-info/dependency_links.txt +0 -0
  81. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/nju_coding_agent_harness.egg-info/entry_points.txt +0 -0
  82. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/nju_coding_agent_harness.egg-info/requires.txt +0 -0
  83. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/nju_coding_agent_harness.egg-info/top_level.txt +0 -0
  84. {nju_coding_agent_harness-0.2.2 → nju_coding_agent_harness-0.2.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nju-coding-agent-harness
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Requires-Python: >=3.11
5
5
  Requires-Dist: openai
6
6
  Requires-Dist: requests
@@ -6,7 +6,18 @@ from typing import TYPE_CHECKING, Any, Callable
6
6
  if TYPE_CHECKING:
7
7
  from harness.registry import Context
8
8
 
9
- SCHEMA: dict = {"question": "string", "options": "array[string]"}
9
+ SCHEMA: dict = {
10
+ "type": "object",
11
+ "properties": {
12
+ "question": {"type": "string", "description": "问题内容"},
13
+ "options": {
14
+ "type": "array",
15
+ "items": {"type": "string"},
16
+ "description": "选项列表(可选)",
17
+ },
18
+ },
19
+ "required": ["question"],
20
+ }
10
21
 
11
22
 
12
23
  @dataclass
@@ -115,7 +115,10 @@ async def websocket_endpoint(websocket: WebSocket):
115
115
  if msg.get("type") == "chat":
116
116
  async def push(event: dict):
117
117
  await websocket.send_text(json.dumps(event, ensure_ascii=False))
118
- await agent.chat(msg["content"], push)
118
+ try:
119
+ await agent.chat(msg["content"], push)
120
+ except Exception as e:
121
+ await push({"type": "error", "content": str(e)})
119
122
  elif msg.get("type") == "stop":
120
123
  await agent.stop()
121
124
  except WebSocketDisconnect:
@@ -36,12 +36,13 @@
36
36
 
37
37
  body {
38
38
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
39
- background-color: var(--bg-dark);
40
- color: var(--text-dark);
41
- line-height: 1.5;
39
+ background-color: var(--bg-light);
40
+ color: var(--text-light);
41
+ line-height: 1.6;
42
42
  height: 100vh;
43
43
  display: flex;
44
44
  overflow: hidden;
45
+ font-size: 15px; /* 增加基础字体大小 */
45
46
  }
46
47
 
47
48
  /* Theme switcher */
@@ -82,11 +83,13 @@
82
83
  }
83
84
 
84
85
  .sidebar-header {
85
- padding: 16px;
86
+ padding: 18px;
86
87
  border-bottom: 1px solid var(--border);
87
88
  display: flex;
88
89
  align-items: center;
89
90
  justify-content: space-between;
91
+ font-size: 17px;
92
+ font-weight: 600;
90
93
  }
91
94
 
92
95
  .sidebar-toggle {
@@ -94,9 +97,9 @@
94
97
  border: none;
95
98
  color: var(--text-muted);
96
99
  cursor: pointer;
97
- padding: 4px;
100
+ padding: 6px;
98
101
  border-radius: 4px;
99
- font-size: 16px;
102
+ font-size: 18px;
100
103
  }
101
104
 
102
105
  .sidebar-toggle:hover {
@@ -106,7 +109,7 @@
106
109
  .sidebar-content {
107
110
  flex: 1;
108
111
  overflow-y: auto;
109
- padding: 8px;
112
+ padding: 12px;
110
113
  }
111
114
 
112
115
  .sidebar-section {
@@ -114,8 +117,8 @@
114
117
  }
115
118
 
116
119
  .sidebar-section h3 {
117
- margin-bottom: 8px;
118
- font-size: 14px;
120
+ margin-bottom: 12px;
121
+ font-size: 15px;
119
122
  font-weight: 600;
120
123
  color: var(--text-muted);
121
124
  text-transform: uppercase;
@@ -124,18 +127,19 @@
124
127
 
125
128
  .sidebar-button {
126
129
  width: 100%;
127
- padding: 12px 16px;
130
+ padding: 14px 18px;
128
131
  background: none;
129
132
  border: 1px solid var(--border);
130
133
  color: var(--text);
131
- border-radius: 6px;
134
+ border-radius: 8px;
132
135
  cursor: pointer;
133
136
  text-align: left;
134
- margin-bottom: 8px;
137
+ margin-bottom: 10px;
135
138
  transition: all 0.2s ease;
136
139
  display: flex;
137
140
  align-items: center;
138
- gap: 8px;
141
+ gap: 10px;
142
+ font-size: 15px;
139
143
  }
140
144
 
141
145
  .sidebar-button:hover {
@@ -160,16 +164,16 @@
160
164
  .chat-area {
161
165
  flex: 1;
162
166
  overflow-y: auto;
163
- padding: 20px;
167
+ padding: 24px;
164
168
  display: flex;
165
169
  flex-direction: column;
166
- gap: 16px;
170
+ gap: 20px;
167
171
  }
168
172
 
169
173
  .message {
170
174
  max-width: 75%;
171
- padding: 12px 16px;
172
- border-radius: 8px;
175
+ padding: 14px 18px;
176
+ border-radius: 10px;
173
177
  position: relative;
174
178
  }
175
179
 
@@ -189,14 +193,15 @@
189
193
  word-wrap: break-word;
190
194
  white-space: pre-wrap;
191
195
  font-family: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;
192
- font-size: 14px;
193
- line-height: 1.6;
196
+ font-size: 15px;
197
+ line-height: 1.7;
194
198
  }
195
199
 
196
200
  .tool-call {
197
- margin-top: 12px;
201
+ margin-top: 8px;
202
+ border: 1px solid var(--border);
198
203
  border-left: 3px solid;
199
- border-radius: 4px;
204
+ border-radius: 6px;
200
205
  overflow: hidden;
201
206
  background-color: var(--card);
202
207
  }
@@ -207,25 +212,43 @@
207
212
  .tool-call.other { border-color: var(--text-muted); }
208
213
 
209
214
  .tool-header {
210
- padding: 8px 12px;
215
+ padding: 6px 10px;
211
216
  background-color: var(--sidebar);
212
217
  cursor: pointer;
213
218
  display: flex;
214
219
  align-items: center;
215
- justify-content: space-between;
220
+ gap: 8px;
216
221
  font-weight: 500;
222
+ font-size: 13px;
223
+ user-select: none;
224
+ }
225
+
226
+ .tool-name {
227
+ font-weight: 600;
228
+ white-space: nowrap;
217
229
  }
218
230
 
219
231
  .tool-trigger {
220
232
  font-family: 'JetBrains Mono', monospace;
221
233
  font-size: 12px;
222
- opacity: 0.8;
234
+ color: var(--text-muted);
235
+ flex: 1;
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ white-space: nowrap;
239
+ }
240
+
241
+ .tool-toggle {
242
+ font-size: 10px;
243
+ color: var(--text-muted);
244
+ flex-shrink: 0;
223
245
  }
224
246
 
225
247
  .tool-content {
226
- padding: 12px;
248
+ padding: 10px;
227
249
  font-family: 'JetBrains Mono', monospace;
228
250
  font-size: 12px;
251
+ line-height: 1.5;
229
252
  white-space: pre-wrap;
230
253
  background-color: var(--bg);
231
254
  border-top: 1px solid var(--border);
@@ -235,9 +258,25 @@
235
258
  display: none;
236
259
  }
237
260
 
261
+ .tool-result {
262
+ padding: 4px 10px 6px;
263
+ font-family: 'JetBrains Mono', monospace;
264
+ font-size: 12px;
265
+ color: var(--text-muted);
266
+ white-space: nowrap;
267
+ overflow: hidden;
268
+ text-overflow: ellipsis;
269
+ border-top: 1px solid var(--border);
270
+ display: none;
271
+ }
272
+
273
+ .tool-call.completed .tool-result {
274
+ display: block;
275
+ }
276
+
238
277
  /* Input area */
239
278
  .input-area {
240
- padding: 16px 20px;
279
+ padding: 18px 24px;
241
280
  border-top: 1px solid var(--border);
242
281
  background-color: var(--bg);
243
282
  }
@@ -245,20 +284,20 @@
245
284
  .input-wrapper {
246
285
  position: relative;
247
286
  display: flex;
248
- gap: 8px;
287
+ gap: 10px;
249
288
  }
250
289
 
251
290
  .text-input {
252
291
  flex: 1;
253
- min-height: 40px;
292
+ min-height: 44px;
254
293
  max-height: 120px;
255
- padding: 8px 12px;
294
+ padding: 10px 14px;
256
295
  border: 1px solid var(--border);
257
296
  border-radius: 6px;
258
297
  background-color: var(--card);
259
298
  color: var(--text);
260
299
  font-family: inherit;
261
- font-size: 14px;
300
+ font-size: 15px;
262
301
  resize: vertical;
263
302
  outline: none;
264
303
  }
@@ -269,13 +308,14 @@
269
308
  }
270
309
 
271
310
  .send-button {
272
- padding: 8px 16px;
311
+ padding: 10px 18px;
273
312
  background-color: var(--accent);
274
313
  color: white;
275
314
  border: none;
276
315
  border-radius: 6px;
277
316
  cursor: pointer;
278
317
  font-weight: 500;
318
+ font-size: 14px;
279
319
  transition: background-color 0.2s ease;
280
320
  }
281
321
 
@@ -289,13 +329,14 @@
289
329
  }
290
330
 
291
331
  .stop-button {
292
- padding: 8px 16px;
332
+ padding: 10px 18px;
293
333
  background-color: var(--danger);
294
334
  color: white;
295
335
  border: none;
296
336
  border-radius: 6px;
297
337
  cursor: pointer;
298
338
  font-weight: 500;
339
+ font-size: 14px;
299
340
  transition: background-color 0.2s ease;
300
341
  }
301
342
 
@@ -305,11 +346,11 @@
305
346
 
306
347
  /* Status indicators */
307
348
  .status-indicator {
308
- font-size: 12px;
349
+ font-size: 13px;
309
350
  color: var(--text-muted);
310
351
  text-align: center;
311
- padding: 4px;
312
- margin-top: 8px;
352
+ padding: 6px;
353
+ margin-top: 10px;
313
354
  }
314
355
 
315
356
  .status-indicator.connected { color: var(--success); }
@@ -336,7 +377,7 @@
336
377
  .flex { display: flex; }
337
378
  </style>
338
379
  </head>
339
- <body class="dark-theme">
380
+ <body class="light-theme">
340
381
  <!-- Sidebar -->
341
382
  <div class="sidebar" id="sidebar">
342
383
  <div class="sidebar-header">
@@ -631,11 +672,18 @@
631
672
  handleToolOutput(data) {
632
673
  const toolKey = data.name + '_' + (data.index || 0);
633
674
  const toolElement = this.currentToolElements[toolKey];
634
-
675
+
635
676
  if (toolElement) {
636
677
  const contentDiv = toolElement.querySelector('.tool-content');
637
678
  contentDiv.textContent = this.formatToolOutput(data);
638
- contentDiv.classList.remove('collapsed');
679
+ contentDiv.classList.add('collapsed');
680
+
681
+ const resultDiv = toolElement.querySelector('.tool-result');
682
+ const output = this.formatToolOutput(data);
683
+ const firstLine = output.split('\n')[0];
684
+ resultDiv.textContent = firstLine.length > 80 ? firstLine.substring(0, 80) + '...' : firstLine;
685
+
686
+ toolElement.classList.add('completed');
639
687
  }
640
688
  }
641
689
 
@@ -643,12 +691,14 @@
643
691
  const toolDiv = document.createElement('div');
644
692
  const toolType = this.getToolType(data.name);
645
693
  toolDiv.className = `tool-call ${toolType}`;
694
+
695
+ const args = typeof data.arguments === 'object' ? JSON.stringify(data.arguments) : String(data.arguments || '');
646
696
 
647
697
  const header = document.createElement('div');
648
698
  header.className = 'tool-header';
649
699
  header.innerHTML = `
650
- <span>${data.name}</span>
651
- <span class="tool-trigger">${JSON.stringify(data.arguments).substring(0, 50)}...</span>
700
+ <span class="tool-name">${data.name}</span>
701
+ <span class="tool-trigger" title="${this.escapeHtml(args)}">${this.escapeHtml(args)}</span>
652
702
  <span class="tool-toggle">▶</span>
653
703
  `;
654
704
 
@@ -656,6 +706,9 @@
656
706
  content.className = 'tool-content collapsed';
657
707
  content.innerHTML = '<div class="loading"></div>';
658
708
 
709
+ const result = document.createElement('div');
710
+ result.className = 'tool-result';
711
+
659
712
  header.addEventListener('click', () => {
660
713
  content.classList.toggle('collapsed');
661
714
  const toggle = header.querySelector('.tool-toggle');
@@ -663,8 +716,9 @@
663
716
  });
664
717
 
665
718
  toolDiv.appendChild(header);
719
+ toolDiv.appendChild(result);
666
720
  toolDiv.appendChild(content);
667
-
721
+
668
722
  return toolDiv;
669
723
  }
670
724
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  from typing import Any, Callable, Coroutine
4
5
 
5
6
  from harness.agent import Agent
@@ -106,14 +107,17 @@ class StreamAgent:
106
107
 
107
108
  async def chat(self, message: str, push: Callable[[dict], Coroutine[Any, Any, None]]):
108
109
  """处理用户消息,通过 push 协程逐条推送事件。"""
109
- events = []
110
+ loop = asyncio.get_running_loop()
110
111
 
111
112
  def on_event(event_type: str, data: dict):
112
- events.append({"type": event_type, **data})
113
+ try:
114
+ asyncio.run_coroutine_threadsafe(
115
+ push({"type": event_type, **data}), loop
116
+ )
117
+ except Exception:
118
+ pass
113
119
 
114
- result = self.agent.chat(message, on_event=on_event)
115
- for event in events:
116
- await push(event)
120
+ result = await asyncio.to_thread(self.agent.chat, message, on_event=on_event)
117
121
  await push({"type": "done"})
118
122
  return result
119
123
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nju-coding-agent-harness
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Requires-Python: >=3.11
5
5
  Requires-Dist: openai
6
6
  Requires-Dist: requests
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nju-coding-agent-harness"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  requires-python = ">=3.11"
9
9
  dependencies = [
10
10
  "openai",