Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pipeline {
AWS_REGION = 'ap-south-1'
ECR_REGISTRY = '463356420488.dkr.ecr.ap-south-1.amazonaws.com'
ECR_REPO = 'agent-platform'
IMAGE_TAG = "${env.GIT_COMMIT?.take(8) ?: 'latest'}"
IMAGE_TAG = "${env.GIT_COMMIT?.take(8) ?: 'unknown'}"
}

stages {
Expand All @@ -36,7 +36,6 @@ pipeline {
docker build \
-f Dockerfile.backend \
-t $ECR_REGISTRY/$ECR_REPO:backend-$IMAGE_TAG \
-t $ECR_REGISTRY/$ECR_REPO:backend-latest \
.
'''
}
Expand All @@ -47,7 +46,6 @@ pipeline {
docker build \
-f Dockerfile.frontend \
-t $ECR_REGISTRY/$ECR_REPO:frontend-$IMAGE_TAG \
-t $ECR_REGISTRY/$ECR_REPO:frontend-latest \
.
'''
}
Expand All @@ -61,15 +59,13 @@ pipeline {
steps {
sh '''
docker push $ECR_REGISTRY/$ECR_REPO:backend-$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPO:backend-latest
'''
}
}
stage('Push Frontend') {
steps {
sh '''
docker push $ECR_REGISTRY/$ECR_REPO:frontend-$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPO:frontend-latest
'''
}
}
Expand All @@ -81,9 +77,7 @@ pipeline {
always {
sh '''
docker rmi $ECR_REGISTRY/$ECR_REPO:backend-$IMAGE_TAG || true
docker rmi $ECR_REGISTRY/$ECR_REPO:backend-latest || true
docker rmi $ECR_REGISTRY/$ECR_REPO:frontend-$IMAGE_TAG || true
docker rmi $ECR_REGISTRY/$ECR_REPO:frontend-latest || true
'''
}
success {
Expand Down
8 changes: 4 additions & 4 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
"postcss": "^8.4.0",
"tailwindcss": "^3.4.0",
"typescript": "^5.7.0",
"vite": "^6.0.0"
"vite": "^6.4.2"
}
}
14 changes: 11 additions & 3 deletions frontend/src/hooks/useChatSessionWebSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,13 @@ export function useChatSessionWebSocket(sessionId: string | null) {
const data = JSON.parse(event.data)
if (data.type === 'activity' && data.activity) {
setActivity(data.activity)
// Append to activity log (dedup consecutive identical entries)
// Append to activity log (dedup consecutive identical entries, cap at 200)
const log = activityLogRef.current
if (log.length === 0 || log[log.length - 1] !== data.activity) {
const newLog = [...log, data.activity]
const MAX_LOG = 200
const newLog = log.length >= MAX_LOG
? [...log.slice(log.length - MAX_LOG + 1), data.activity]
: [...log, data.activity]
activityLogRef.current = newLog
setActivityLog(newLog)
persistState(boundSessionId, newLog, completedStreamingRef.current)
Expand All @@ -164,11 +167,16 @@ export function useChatSessionWebSocket(sessionId: string | null) {
setActivity(null)
} else if (data.type === 'token_done') {
// Round complete: move live text to completed, clear live buffer
// Cap at ~100 KB to prevent unbounded memory growth
if (streamingRef.current) {
const MAX_COMPLETED = 100_000
const text = streamingRef.current
const newCompleted = completedStreamingRef.current
let newCompleted = completedStreamingRef.current
? completedStreamingRef.current + '\n' + text
: text
if (newCompleted.length > MAX_COMPLETED) {
newCompleted = newCompleted.slice(newCompleted.length - MAX_COMPLETED)
}
completedStreamingRef.current = newCompleted
setCompletedStreaming(newCompleted)
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/ProjectChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ export default function ProjectChatPage() {
try {
await projectChat.sessions.delete(projectId, sessionId)
deletedSessionIdsRef.current.add(sessionId)
// Clean up sessionStorage for this session
try { sessionStorage.removeItem(`chat_ws:${sessionId}`) } catch {}
setSessions((prev) => {
const remaining = prev.filter((s) => s.id !== sessionId)
if (activeSessionIdRef.current === sessionId && remaining.length > 0) {
Expand Down
12 changes: 8 additions & 4 deletions src/agents/agents/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ def _register_tool(defn: BuiltinToolDef) -> None:
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path relative to repo root (use ../deps/{name}/path for dependency repos)"},
"offset": {"type": "integer", "description": "Start reading from this line number (0-based). Use to read remaining content after truncation."},
"limit": {"type": "integer", "description": "Max number of lines to return. Defaults to entire file."},
},
"required": ["path"],
},
prompt_hint="Read a file's contents. Args: path (relative to repo root, or ../deps/{name}/path for deps)",
example='read_file(path="src/main.py") # or read_file(path="../deps/auth-lib/src/index.ts")',
prompt_hint="Read a file's contents. Args: path, offset (optional, 0-based line), limit (optional, max lines). Use offset to read remaining content after truncation.",
example='read_file(path="src/main.py") # or read_file(path="src/main.py", offset=200, limit=100)',
))

_register_tool(BuiltinToolDef(
Expand Down Expand Up @@ -103,11 +105,13 @@ def _register_tool(defn: BuiltinToolDef) -> None:
"pattern": {"type": "string", "description": "Search pattern (regex supported)"},
"path": {"type": "string", "description": "Directory to search in, relative to repo root (empty for root, ../deps/{name}/ for deps)"},
"file_glob": {"type": "string", "description": "File glob filter, e.g. '*.py', '*.ts' (optional)"},
"max_results": {"type": "integer", "description": "Max matching lines to return (default 200). Use with offset to paginate."},
"offset": {"type": "integer", "description": "Skip this many matching lines before returning results. Use to see more results after truncation."},
},
"required": ["pattern"],
},
prompt_hint="Grep for a pattern across files. Args: pattern, path (optional, ../deps/{name}/ for deps), file_glob (optional)",
example='search_files(pattern="def handle_", file_glob="*.py") # or search_files(pattern="export", path="../deps/shared-types/")',
prompt_hint="Grep for a pattern across files. Args: pattern, path (optional), file_glob (optional), max_results (optional, default 200), offset (optional, for pagination)",
example='search_files(pattern="def handle_", file_glob="*.py") # or search_files(pattern="export", offset=200)',
))

_register_tool(BuiltinToolDef(
Expand Down
58 changes: 54 additions & 4 deletions src/agents/api/routes/project_chat_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
(chat, plan, debug, create_task).
"""

import gc
import json
import logging

Expand Down Expand Up @@ -603,6 +604,14 @@ async def _check_inject() -> str | None:
tool_events: list[dict] = []

async def _on_tool_event(event: dict) -> None:
# Cap tool_events to prevent unbounded memory growth during long loops.
# Keep all llm_thinking events (small, needed for token stats) but
# trim tool_result/tool_start entries to the most recent 200.
if len(tool_events) > 500:
# Keep llm_thinking events + last 200 others
thinking = [e for e in tool_events if e.get("type") == "llm_thinking"]
others = [e for e in tool_events if e.get("type") != "llm_thinking"]
tool_events[:] = thinking + others[-200:]
tool_events.append(event)

send_kwargs: dict = {}
Expand Down Expand Up @@ -686,7 +695,12 @@ async def _on_tool_event(event: dict) -> None:
""",
project_id, user_id, content, metadata, session_id,
)
return dict(msg_row)
# Free large locals and nudge GC after potentially heavy tool loops
tool_events.clear()
result = dict(msg_row)
del msg_row, content, metadata, response
gc.collect()
return result


# ── Plan Mode ────────────────────────────────────────────────────────
Expand Down Expand Up @@ -1049,6 +1063,14 @@ async def _check_inject() -> str | None:
tool_events: list[dict] = []

async def _on_tool_event(event: dict) -> None:
# Cap tool_events to prevent unbounded memory growth during long loops.
# Keep all llm_thinking events (small, needed for token stats) but
# trim tool_result/tool_start entries to the most recent 200.
if len(tool_events) > 500:
# Keep llm_thinking events + last 200 others
thinking = [e for e in tool_events if e.get("type") == "llm_thinking"]
others = [e for e in tool_events if e.get("type") != "llm_thinking"]
tool_events[:] = thinking + others[-200:]
tool_events.append(event)

# ── Run tool loop ────────────────────────────────────────────────
Expand Down Expand Up @@ -1186,7 +1208,11 @@ async def _on_tool_event(event: dict) -> None:
""",
project_id, user_id, content, metadata, session_id,
)
return dict(msg_row)
tool_events.clear()
result = dict(msg_row)
del msg_row, content, metadata, response
gc.collect()
return result


# ── Task Creation from Plan ──────────────────────────────────────────
Expand Down Expand Up @@ -1604,6 +1630,14 @@ async def _check_inject() -> str | None:
tool_events: list[dict] = []

async def _on_tool_event(event: dict) -> None:
# Cap tool_events to prevent unbounded memory growth during long loops.
# Keep all llm_thinking events (small, needed for token stats) but
# trim tool_result/tool_start entries to the most recent 200.
if len(tool_events) > 500:
# Keep llm_thinking events + last 200 others
thinking = [e for e in tool_events if e.get("type") == "llm_thinking"]
others = [e for e in tool_events if e.get("type") != "llm_thinking"]
tool_events[:] = thinking + others[-200:]
tool_events.append(event)

send_kwargs: dict = {}
Expand Down Expand Up @@ -1679,7 +1713,11 @@ async def _on_tool_event(event: dict) -> None:
""",
project_id, user_id, content, metadata, session_id,
)
return dict(msg_row)
tool_events.clear()
result = dict(msg_row)
del msg_row, content, metadata, response
gc.collect()
return result


# ── Create Task Mode ────────────────────────────────────────────────
Expand Down Expand Up @@ -1862,6 +1900,14 @@ async def _execute_tool(name: str, arguments: dict) -> str:
tool_events: list[dict] = []

async def _on_tool_event(event: dict) -> None:
# Cap tool_events to prevent unbounded memory growth during long loops.
# Keep all llm_thinking events (small, needed for token stats) but
# trim tool_result/tool_start entries to the most recent 200.
if len(tool_events) > 500:
# Keep llm_thinking events + last 200 others
thinking = [e for e in tool_events if e.get("type") == "llm_thinking"]
others = [e for e in tool_events if e.get("type") != "llm_thinking"]
tool_events[:] = thinking + others[-200:]
tool_events.append(event)

send_kwargs: dict = {}
Expand Down Expand Up @@ -1921,4 +1967,8 @@ async def _on_tool_event(event: dict) -> None:
""",
project_id, user_id, content, metadata, session_id,
)
return dict(msg_row)
tool_events.clear()
result = dict(msg_row)
del msg_row, content, metadata, response
gc.collect()
return result
12 changes: 8 additions & 4 deletions src/agents/orchestrator/context_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ async def build_agent_prompt(
out = r.get("output_result", {})
summary = out.get("approach", "") or out.get("summary", "") if isinstance(out, dict) else ""
prev_items.append(
f"- [{r.get('agent_role', '?')}] {r.get('title', '?')}: {summary[:300]}"
f"- [{r.get('agent_role', '?')}] {r.get('title', '?')}: {summary[:600]}"
)
prev_context = "\n\nCompleted sub-tasks:\n" + "\n".join(prev_items)

Expand Down Expand Up @@ -587,7 +587,9 @@ async def build_iteration_context(
learnings_block += " \u2014 " + "; ".join(entry["learnings"])
learnings_block += "\n"
if entry.get("error_output"):
err = entry["error_output"][:500]
err = entry["error_output"]
if len(err) > 1500:
err = err[:1500] + f"\n... ({len(entry['error_output']) - 1500} more chars — run the failing command to see full output)"
learnings_block += f" Error: {err}\n"
system += learnings_block
except Exception:
Expand All @@ -601,7 +603,9 @@ async def build_iteration_context(
learnings_block += " \u2014 " + "; ".join(entry["learnings"])
learnings_block += "\n"
if entry.get("error_output"):
err = entry["error_output"][:500]
err = entry["error_output"]
if len(err) > 1500:
err = err[:1500] + f"\n... ({len(entry['error_output']) - 1500} more chars — run the failing command to see full output)"
learnings_block += f" Error: {err}\n"
system += learnings_block

Expand All @@ -613,7 +617,7 @@ async def build_iteration_context(
out = r.get("output_result", {})
summary = out.get("approach", "") or out.get("summary", "") if isinstance(out, dict) else ""
prev_items.append(
f"- [{r.get('agent_role', '?')}] {r.get('title', '?')}: {summary[:300]}"
f"- [{r.get('agent_role', '?')}] {r.get('title', '?')}: {summary[:600]}"
)
prev_context = "\n\nCompleted sub-tasks:\n" + "\n".join(prev_items)

Expand Down
7 changes: 5 additions & 2 deletions src/agents/orchestrator/handlers/on_coder_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,11 @@ async def _git(args):
if diff_stat:
desc_parts.append(f"\n## Git Diff Summary\n```\n{diff_stat}\n```")
if diff_full:
if len(diff_full) > 10_000:
diff_full = diff_full[:10_000] + "\n... (truncated, use git diff to see full changes)"
if len(diff_full) > 30_000:
diff_full = diff_full[:30_000] + (
f"\n... (diff truncated at 30K of {len(diff_full)} chars. "
f"Use `run_command(command='git diff HEAD')` to see the full diff.)"
)
desc_parts.append(f"\n## Full Diff\n```diff\n{diff_full}\n```")
except Exception:
logger.warning("[%s] Failed to capture git diff for reviewer", ctx.todo_id, exc_info=True)
Expand Down
6 changes: 3 additions & 3 deletions src/agents/orchestrator/handlers/on_reviewer_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async def handle_reviewer_completion(
"task_id": ctx.todo_id,
"subtask_title": sub_task.get("title", ""),
"verdict": "approved",
"feedback": output.get("content", "")[:500] if isinstance(output, dict) else "",
"feedback": output.get("content", "")[:1500] if isinstance(output, dict) else "",
"summary": output.get("summary", "") if isinstance(output, dict) else "",
},
)
Expand Down Expand Up @@ -90,13 +90,13 @@ async def handle_reviewer_completion(
await ctx.post_system_message(
f"**Code review: Changes requested**\n\n"
+ (f"{review_summary}\n\n" if review_summary else "")
+ (issues_text if issues_text else reviewer_feedback[:1500]),
+ (issues_text if issues_text else reviewer_feedback[:3000]),
metadata={
"action": "code_review_verdict",
"task_id": ctx.todo_id,
"subtask_title": sub_task.get("title", ""),
"verdict": "needs_changes",
"feedback": reviewer_feedback[:2000],
"feedback": reviewer_feedback[:4000],
"issues": structured_issues[:20],
"summary": review_summary,
},
Expand Down
Loading