github-actions[bot] commited on
Commit
6ec98e7
·
1 Parent(s): 84cd838

Auto-deploy from GitHub: 5756a0af1b11482bda3fe8c090ee34cbae223e3e

Browse files
Files changed (1) hide show
  1. app/services/worker.py +64 -2
app/services/worker.py CHANGED
@@ -1,7 +1,9 @@
1
  import asyncio
2
  import json
3
  import os
 
4
  import shutil
 
5
  from app.core.config import settings
6
  from custom_logger import logger_config as logger
7
  from app.db import crud
@@ -142,6 +144,61 @@ async def _install_opencode():
142
  logger.info("✅ opencode installed successfully")
143
 
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  async def _run_opencode(system_prompt: str, text: str) -> str:
146
  if not shutil.which('opencode'):
147
  await _install_opencode()
@@ -165,13 +222,15 @@ async def _run_opencode(system_prompt: str, text: str) -> str:
165
  stderr_lines = []
166
 
167
  async def _read_stream(stream, lines, label):
 
168
  while True:
169
  line = await stream.readline()
170
  if not line:
171
  break
172
  decoded = line.decode(errors='replace').rstrip()
173
  lines.append(decoded)
174
- logger.info(f"opencode {label}: {decoded}")
 
175
 
176
  # A whole-book prompt (reconcile, the director pass) runs big-pickle for
177
  # around five minutes; 300s killed those just as they were finishing. Give
@@ -195,5 +254,8 @@ async def _run_opencode(system_prompt: str, text: str) -> str:
195
  stderr = '\n'.join(stderr_lines)
196
 
197
  if proc.returncode != 0:
198
- raise RuntimeError(f"opencode failed ({proc.returncode}): {stderr or 'unknown error'}")
 
 
 
199
  return stdout
 
1
  import asyncio
2
  import json
3
  import os
4
+ import re
5
  import shutil
6
+ import time
7
  from app.core.config import settings
8
  from custom_logger import logger_config as logger
9
  from app.db import crud
 
144
  logger.info("✅ opencode installed successfully")
145
 
146
 
147
+ # opencode's `--print-logs` emits one structured line per internal event, e.g.
148
+ # INFO 2026-07-22T17:23:10 +37ms service=bus type=message.part.delta publishing
149
+ # A single answer produces thousands of these, one per streamed token, which
150
+ # buried every other worker log. Parse them instead of echoing: real problems
151
+ # (WARN/ERROR) get their own line, the token firehose collapses into a single
152
+ # overwriting heartbeat, and a persistent summary is written at the end.
153
+ _OPENCODE_LOG_RE = re.compile(
154
+ r'^(?P<level>DEBUG|INFO|WARN|ERROR)\s+\S+\s+\S+\s+(?P<rest>.*)$'
155
+ )
156
+ _HEARTBEAT_INTERVAL = 1.0 # seconds between heartbeat repaints
157
+
158
+
159
+ class _OpencodeLog:
160
+ def __init__(self, label):
161
+ self.label = label
162
+ self.count = 0
163
+ self.started = time.monotonic()
164
+ self.last_beat = 0.0
165
+
166
+ def emit(self, line):
167
+ if not line:
168
+ return
169
+ self.count += 1
170
+
171
+ match = _OPENCODE_LOG_RE.match(line)
172
+ level = match.group('level') if match else None
173
+ detail = match.group('rest') if match else line
174
+
175
+ if level == 'ERROR':
176
+ logger.error(f"opencode {self.label}: {detail}")
177
+ return
178
+ if level == 'WARN':
179
+ logger.warning(f"opencode {self.label}: {detail}")
180
+ return
181
+
182
+ now = time.monotonic()
183
+ if now - self.last_beat < _HEARTBEAT_INTERVAL:
184
+ return
185
+ self.last_beat = now
186
+ elapsed = int(now - self.started)
187
+ logger.info(
188
+ f"opencode {self.label}: {self.count} lines / {elapsed}s | {_shorten(detail)}",
189
+ overwrite=True,
190
+ )
191
+
192
+ def flush(self):
193
+ elapsed = int(time.monotonic() - self.started)
194
+ logger.info(f"opencode {self.label}: done — {self.count} lines in {elapsed}s")
195
+
196
+
197
+ def _shorten(text, width=100):
198
+ text = text.strip()
199
+ return text if len(text) <= width else f"{text[:width - 1]}…"
200
+
201
+
202
  async def _run_opencode(system_prompt: str, text: str) -> str:
203
  if not shutil.which('opencode'):
204
  await _install_opencode()
 
222
  stderr_lines = []
223
 
224
  async def _read_stream(stream, lines, label):
225
+ log = _OpencodeLog(label)
226
  while True:
227
  line = await stream.readline()
228
  if not line:
229
  break
230
  decoded = line.decode(errors='replace').rstrip()
231
  lines.append(decoded)
232
+ log.emit(decoded)
233
+ log.flush()
234
 
235
  # A whole-book prompt (reconcile, the director pass) runs big-pickle for
236
  # around five minutes; 300s killed those just as they were finishing. Give
 
254
  stderr = '\n'.join(stderr_lines)
255
 
256
  if proc.returncode != 0:
257
+ # stderr can be thousands of suppressed event lines; the tail is where
258
+ # the actual failure is.
259
+ tail = '\n'.join(stderr.splitlines()[-20:])
260
+ raise RuntimeError(f"opencode failed ({proc.returncode}): {tail or 'unknown error'}")
261
  return stdout