mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 12:17:45 +02:00
working chat mode
This commit is contained in:
@@ -54,7 +54,11 @@ class CommsManager(BaseModel):
|
||||
if not event.future.done():
|
||||
event.future.set_result(result)
|
||||
return
|
||||
await self.broadcaster.send_to_session(session_id, event.event, event.model_dump(mode="json"))
|
||||
# by_alias=True so nested AgentSnapshot serializes session_id as "id"
|
||||
# (matching the REST API and the frontend's expected shape).
|
||||
await self.broadcaster.send_to_session(
|
||||
session_id, event.event, event.model_dump(mode="json", by_alias=True),
|
||||
)
|
||||
return emit
|
||||
|
||||
@typechecked
|
||||
|
||||
@@ -11,7 +11,7 @@ import json
|
||||
import time
|
||||
from typing import Any, Callable, Dict, Tuple, Optional, List
|
||||
|
||||
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny, PermissionResult
|
||||
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny, PermissionResult, ToolPermissionContext
|
||||
|
||||
from backend.core.shared_structs.agent.Message.Message import ToolResultMessage
|
||||
from backend.core.shared_structs.agent.Message.agent_outputs import ToolResultContent
|
||||
@@ -31,7 +31,7 @@ def create_sdk_hooks(
|
||||
tool_start_times: Dict[str, float] = {}
|
||||
|
||||
@typechecked
|
||||
async def can_use_tool(tool_name: str, input_data: Any) -> PermissionResult:
|
||||
async def can_use_tool(tool_name: str, input_data: Any, context: ToolPermissionContext) -> PermissionResult:
|
||||
permission: Optional[TOOL_PERMISSIONS] = (
|
||||
agent.toolkit.resolve_permission(tool_name) if agent.toolkit else None
|
||||
)
|
||||
|
||||
@@ -37,6 +37,26 @@ from claude_agent_sdk.types import HookMatcher, McpServerConfig
|
||||
from backend.core.tools.shared_structs.Toolkit import Toolkit
|
||||
from backend.apps.agents.agent_utils.build_agent_toolkit import build_agent_toolkit
|
||||
|
||||
NINE_ROUTER_MODEL_MAP: dict[str, str] = {
|
||||
"sonnet": "cc/claude-sonnet-4-6",
|
||||
"opus": "cc/claude-opus-4-6",
|
||||
"haiku": "cc/claude-haiku-4-5-20251001",
|
||||
}
|
||||
|
||||
def _resolve_nine_router_model(model: str, has_api_key: bool) -> str:
|
||||
"""Resolve a model name for the active connection mode.
|
||||
|
||||
When using a direct API key the model passes through unchanged.
|
||||
When routing through 9Router, short aliases are mapped to their
|
||||
``cc/``-prefixed canonical IDs; models that already carry the prefix
|
||||
are returned as-is to avoid double-prefixing.
|
||||
"""
|
||||
if has_api_key:
|
||||
return model
|
||||
if model.startswith("cc/"):
|
||||
return model
|
||||
return NINE_ROUTER_MODEL_MAP.get(model, f"cc/{model}")
|
||||
|
||||
AGENT_STORE: PydanticStore[Agent] = PydanticStore[Agent](
|
||||
model_cls=Agent,
|
||||
data_dir=os.path.join(DB_ROOT, "sessions"),
|
||||
@@ -133,12 +153,13 @@ async def get_all_sessions(dashboard_id: str = "") -> dict:
|
||||
result: List[Agent] = list(SESSIONS.values())
|
||||
if dashboard_id:
|
||||
result = [a for a in result if a.dashboard_id == dashboard_id]
|
||||
return {"sessions": [a.model_dump(mode="json") for a in result]}
|
||||
return {"sessions": [a.snapshot().model_dump(mode="json") for a in result]}
|
||||
|
||||
|
||||
@agents.router.get("/get_session")
|
||||
async def get_session(session_id: str) -> dict:
|
||||
return get_agent(session_id).model_dump(mode="json")
|
||||
debug("get_session id=%s", session_id)
|
||||
return get_agent(session_id).snapshot().model_dump(mode="json")
|
||||
|
||||
|
||||
|
||||
@@ -183,12 +204,7 @@ async def launch_agent(
|
||||
nine_router_port=NINE_ROUTER_PORT if not settings.anthropic_api_key else None,
|
||||
)
|
||||
|
||||
NINE_ROUTER_MODEL_MAP = {
|
||||
"sonnet": "cc/claude-sonnet-4-6",
|
||||
"opus": "cc/claude-opus-4-6",
|
||||
"haiku": "cc/claude-haiku-4-5-20251001",
|
||||
}
|
||||
resolved_model = NINE_ROUTER_MODEL_MAP.get(model, f"cc/{model}") if not settings.anthropic_api_key else model
|
||||
resolved_model = _resolve_nine_router_model(model, bool(settings.anthropic_api_key))
|
||||
|
||||
agent.config = ClaudeAgentOptions(
|
||||
env=env,
|
||||
@@ -211,6 +227,7 @@ async def launch_agent(
|
||||
session_id=agent.session_id, status="stopped",
|
||||
session=agent.snapshot(),
|
||||
))
|
||||
debug("launch_agent id=%s model=%s mode=%s", agent.session_id, model, mode)
|
||||
return {"session_id": agent.session_id, "session": agent.snapshot().model_dump(mode="json")}
|
||||
|
||||
|
||||
@@ -231,6 +248,7 @@ async def update_system_prompt(
|
||||
|
||||
@agents.router.delete("/delete_session")
|
||||
async def delete_session(session_id: str = Body()) -> dict:
|
||||
debug("delete_session id=%s", session_id)
|
||||
agent: Optional[Agent] = SESSIONS.pop(session_id, None)
|
||||
if agent is not None:
|
||||
await agent.stop_agent()
|
||||
@@ -283,7 +301,11 @@ async def send_message(
|
||||
toolkit=agent.toolkit,
|
||||
)
|
||||
agent.config.system_prompt = resolved_mode_config.system_prompt
|
||||
agent.config.model = agent.model
|
||||
if model_changed:
|
||||
settings = load_settings()
|
||||
agent.config.model = _resolve_nine_router_model(
|
||||
agent.model, bool(settings.anthropic_api_key),
|
||||
)
|
||||
agent.config.allowed_tools = resolved_mode_config.allowed_tools
|
||||
agent.config.disallowed_tools = resolved_mode_config.disallowed_tools
|
||||
if resolved_mode_config.cwd:
|
||||
@@ -299,12 +321,14 @@ async def send_message(
|
||||
forced_tools=forced_tools or [],
|
||||
hidden=hidden,
|
||||
)
|
||||
debug("send_message id=%s prompt=%s", session_id, prompt[:80])
|
||||
await agent.send_message(msg)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@agents.router.post("/stop_agent")
|
||||
async def stop_agent(session_id: str = Body()) -> dict:
|
||||
debug("stop_agent id=%s", session_id)
|
||||
agent: Agent = get_agent(session_id)
|
||||
await agent.stop_agent()
|
||||
return {"ok": True}
|
||||
@@ -366,6 +390,7 @@ async def switch_branch(
|
||||
|
||||
@agents.router.post("/close_session")
|
||||
async def close_session(session_id: str = Body()) -> dict:
|
||||
debug("close_session id=%s", session_id)
|
||||
agent: Optional[Agent] = SESSIONS.pop(session_id, None)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
@@ -382,8 +407,9 @@ async def close_session(session_id: str = Body()) -> dict:
|
||||
|
||||
@agents.router.post("/resume_session")
|
||||
async def resume_session(session_id: str = Body()) -> dict:
|
||||
debug("resume_session id=%s", session_id)
|
||||
if session_id in SESSIONS:
|
||||
return {"session": SESSIONS[session_id].model_dump(mode="json")}
|
||||
return {"session": SESSIONS[session_id].snapshot().model_dump(mode="json")}
|
||||
agent: Optional[Agent] = AGENT_STORE.load_or_none(session_id)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="Session not found in history")
|
||||
@@ -439,8 +465,11 @@ async def get_history(
|
||||
q: str = "",
|
||||
limit: int = 20,
|
||||
offset: int = 0,
|
||||
dashboard_id: str = "",
|
||||
) -> dict:
|
||||
all_agents: List[Agent] = AGENT_STORE.load_all()
|
||||
if dashboard_id:
|
||||
all_agents = [a for a in all_agents if a.dashboard_id == dashboard_id]
|
||||
all_agents.sort(
|
||||
key=lambda a: a.messages.messages[-1].timestamp if a.messages.messages else datetime.min,
|
||||
reverse=True,
|
||||
|
||||
@@ -49,11 +49,22 @@ class Agent(BaseModel):
|
||||
|
||||
@typechecked
|
||||
def snapshot(self) -> AgentSnapshot:
|
||||
msgs = self.messages.messages
|
||||
created_at = msgs[0].timestamp.isoformat() if msgs else ""
|
||||
name = "New chat"
|
||||
for m in msgs:
|
||||
if m.role == "user" and isinstance(m.content, str):
|
||||
name = m.content[:50] + ("..." if len(m.content) > 50 else "")
|
||||
break
|
||||
|
||||
return AgentSnapshot(
|
||||
session_id=self.session_id,
|
||||
model=self.model,
|
||||
mode=self.mode,
|
||||
status=self.status,
|
||||
name=name,
|
||||
created_at=created_at,
|
||||
cost_usd=0,
|
||||
dashboard_id=self.dashboard_id,
|
||||
branch_id=self.branch_id,
|
||||
parent_id=self.parent_id,
|
||||
@@ -133,6 +144,7 @@ class Agent(BaseModel):
|
||||
debug(f"[Agent.send_message] Agent {self.session_id} is already running")
|
||||
return
|
||||
|
||||
debug("Agent.send_message id=%s msg_count=%s", self.session_id, len(self.messages))
|
||||
await self.emit(AgentMessageEvent(
|
||||
session_id=self.session_id,
|
||||
message=msg,
|
||||
@@ -164,6 +176,7 @@ class Agent(BaseModel):
|
||||
|
||||
@typechecked
|
||||
async def stop_agent(self):
|
||||
debug("Agent.stop_agent id=%s status=%s", self.session_id, self.status)
|
||||
for child in self.sub_agents:
|
||||
await child.stop_agent()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, field_serializer
|
||||
from typing import Any, Optional, List
|
||||
from backend.core.shared_structs.agent.MessageLog import MessageLog
|
||||
from backend.core.shared_structs.agent.ApprovalRequest import ApprovalRequest
|
||||
@@ -9,6 +9,9 @@ class AgentSnapshot(BaseModel):
|
||||
model: str
|
||||
mode: str
|
||||
status: str
|
||||
name: str = "New chat"
|
||||
created_at: str = ""
|
||||
cost_usd: float = 0
|
||||
dashboard_id: Optional[str] = None
|
||||
branch_id: str = "main"
|
||||
parent_id: Optional[str] = None
|
||||
@@ -17,6 +20,15 @@ class AgentSnapshot(BaseModel):
|
||||
sub_agents: list = Field(default_factory=list)
|
||||
sub_branches: list = Field(default_factory=list)
|
||||
|
||||
# Flatten MessageLog -> list[Message] on the wire so consumers (frontend,
|
||||
# WS clients) get a plain array instead of {"messages": [...]}. Using a
|
||||
# field_serializer ensures this works whether AgentSnapshot is dumped
|
||||
# directly or as a nested field of a parent model (e.g. AgentStatusEvent),
|
||||
# which a custom model_dump override does not handle.
|
||||
@field_serializer("messages")
|
||||
def _serialize_messages(self, messages: MessageLog, _info: Any) -> list[dict[str, Any]]:
|
||||
return [m.model_dump(mode="json") for m in messages.messages]
|
||||
|
||||
def model_dump(self, **kwargs: Any) -> dict[str, Any]:
|
||||
kwargs.setdefault("by_alias", True)
|
||||
return super().model_dump(**kwargs)
|
||||
return super().model_dump(**kwargs)
|
||||
|
||||
Generated
+2
-407
@@ -1007,85 +1007,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/archiver": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
|
||||
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"archiver-utils": "^2.1.0",
|
||||
"async": "^3.2.4",
|
||||
"buffer-crc32": "^0.2.1",
|
||||
"readable-stream": "^3.6.0",
|
||||
"readdir-glob": "^1.1.2",
|
||||
"tar-stream": "^2.2.0",
|
||||
"zip-stream": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/archiver-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
|
||||
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.4",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"lazystream": "^1.0.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.union": "^4.6.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"readable-stream": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/archiver-utils/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/archiver-utils/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/archiver-utils/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
|
||||
@@ -1703,23 +1624,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compress-commons": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
|
||||
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-crc32": "^0.2.13",
|
||||
"crc32-stream": "^4.0.2",
|
||||
"normalize-path": "^3.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -1815,7 +1719,8 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/crc": {
|
||||
"version": "3.8.0",
|
||||
@@ -1828,35 +1733,6 @@
|
||||
"buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/crc32-stream": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
|
||||
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"crc-32": "^1.2.0",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -2250,61 +2126,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder-squirrel-windows": {
|
||||
"version": "25.1.8",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz",
|
||||
"integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "25.1.8",
|
||||
"archiver": "^5.3.1",
|
||||
"builder-util": "25.1.7",
|
||||
"fs-extra": "^10.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder-squirrel-windows/node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder/node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
@@ -2749,14 +2570,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
@@ -3355,14 +3168,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/isbinaryfile": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
|
||||
@@ -3490,56 +3295,6 @@
|
||||
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lazystream": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
|
||||
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readable-stream": "^2.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/lazystream/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lazystream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lazystream/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
@@ -3547,36 +3302,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.difference": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
|
||||
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.escaperegexp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
@@ -3584,22 +3315,6 @@
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.union": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
|
||||
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||
@@ -4064,17 +3779,6 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-url": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
||||
@@ -4313,14 +4017,6 @@
|
||||
"node": ">=10.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
@@ -4414,50 +4110,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/readdir-glob": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
|
||||
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimatch": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdir-glob/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/readdir-glob/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdir-glob/node_modules/minimatch": {
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
|
||||
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -4946,24 +4598,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
@@ -5329,45 +4963,6 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zip-stream": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
|
||||
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"archiver-utils": "^3.0.4",
|
||||
"compress-commons": "^4.1.2",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/zip-stream/node_modules/archiver-utils": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
|
||||
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.2.3",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"lazystream": "^1.0.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.union": "^4.6.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "^8.12.0",
|
||||
"@babel/core": "^7.28.0",
|
||||
"@babel/preset-env": "^7.28.0",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
|
||||
@@ -146,7 +146,13 @@ export const launchAgent = createAsyncThunk('agents/launchAgent', async (config:
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to launch agent (${res.status})`);
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!data?.session?.id) {
|
||||
throw new Error('Launch agent response missing session.id');
|
||||
}
|
||||
return data.session as AgentSession;
|
||||
});
|
||||
|
||||
@@ -231,7 +237,13 @@ export const fetchSession = createAsyncThunk(
|
||||
'agents/fetchSession',
|
||||
async (sessionId: string) => {
|
||||
const res = await fetch(`${AGENTS_API}/get_session?session_id=${encodeURIComponent(sessionId)}`);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch session (${res.status})`);
|
||||
}
|
||||
const session = await res.json();
|
||||
if (!session?.id) {
|
||||
throw new Error('Fetch session response missing id');
|
||||
}
|
||||
return session as AgentSession;
|
||||
}
|
||||
);
|
||||
@@ -244,7 +256,13 @@ export const launchAndSendFirstMessage = createAsyncThunk(
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
if (!launchRes.ok) {
|
||||
throw new Error(`Failed to launch agent (${launchRes.status})`);
|
||||
}
|
||||
const launchData = await launchRes.json();
|
||||
if (!launchData?.session?.id) {
|
||||
throw new Error('Launch response missing session.id');
|
||||
}
|
||||
const session = launchData.session as AgentSession;
|
||||
|
||||
await fetch(`${AGENTS_API}/send_message`, {
|
||||
@@ -254,7 +272,13 @@ export const launchAndSendFirstMessage = createAsyncThunk(
|
||||
});
|
||||
|
||||
const refreshRes = await fetch(`${AGENTS_API}/get_session?session_id=${encodeURIComponent(session.id)}`);
|
||||
if (!refreshRes.ok) {
|
||||
throw new Error(`Failed to refresh launched session (${refreshRes.status})`);
|
||||
}
|
||||
const updatedSession = await refreshRes.json() as AgentSession;
|
||||
if (!updatedSession?.id) {
|
||||
throw new Error('Refreshed session response missing id');
|
||||
}
|
||||
|
||||
return { draftId, session: updatedSession };
|
||||
}
|
||||
@@ -353,6 +377,9 @@ export const duplicateSession = createAsyncThunk(
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to duplicate session');
|
||||
const data = await res.json();
|
||||
if (!data?.session?.id) {
|
||||
throw new Error('Duplicate session response missing session.id');
|
||||
}
|
||||
return data.session as AgentSession;
|
||||
}
|
||||
);
|
||||
@@ -412,7 +439,13 @@ export const resumeSession = createAsyncThunk(
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ session_id: sessionId }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to resume session (${res.status})`);
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!data?.session?.id) {
|
||||
throw new Error('Resume session response missing session.id');
|
||||
}
|
||||
return data.session as AgentSession;
|
||||
}
|
||||
);
|
||||
@@ -536,33 +569,39 @@ const agentsSlice = createSlice({
|
||||
},
|
||||
|
||||
updateSession(state, action: PayloadAction<AgentSession>) {
|
||||
if (state.history[action.payload.id]) {
|
||||
if (action.payload.status === 'running' || action.payload.mode === 'browser-agent') {
|
||||
delete state.history[action.payload.id];
|
||||
const payload = action.payload as Partial<AgentSession>;
|
||||
const sessionId = payload.id;
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.history[sessionId]) {
|
||||
if (payload.status === 'running' || payload.mode === 'browser-agent') {
|
||||
delete state.history[sessionId];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const existing = state.sessions[action.payload.id];
|
||||
const existing = state.sessions[sessionId];
|
||||
// Don't let a stale "running" message overwrite a terminal status
|
||||
const terminal = ['stopped', 'error'] as const;
|
||||
if (existing && terminal.includes(existing.status as any) && action.payload.status === 'running') {
|
||||
if (existing && terminal.includes(existing.status as any) && payload.status === 'running') {
|
||||
return;
|
||||
}
|
||||
// Preserve local pending_approvals if the server payload has none but
|
||||
// the frontend has some (avoids race where backend clears approvals
|
||||
// before the frontend processes the removal).
|
||||
const mergedApprovals = existing?.pending_approvals?.length && !action.payload.pending_approvals?.length
|
||||
const mergedApprovals = existing?.pending_approvals?.length && !payload.pending_approvals?.length
|
||||
? existing.pending_approvals
|
||||
: action.payload.pending_approvals ?? [];
|
||||
state.sessions[action.payload.id] = {
|
||||
...action.payload,
|
||||
: payload.pending_approvals ?? [];
|
||||
state.sessions[sessionId] = {
|
||||
...(payload as AgentSession),
|
||||
pending_approvals: mergedApprovals,
|
||||
streamingMessage: existing?.streamingMessage ?? action.payload.streamingMessage ?? null,
|
||||
tool_group_meta: { ...existing?.tool_group_meta, ...action.payload.tool_group_meta },
|
||||
streamingMessage: existing?.streamingMessage ?? payload.streamingMessage ?? null,
|
||||
tool_group_meta: { ...existing?.tool_group_meta, ...payload.tool_group_meta },
|
||||
};
|
||||
if (action.payload.status === 'running' && !state.trackedNotificationIds.includes(action.payload.id)) {
|
||||
state.trackedNotificationIds.push(action.payload.id);
|
||||
if (payload.status === 'running' && !state.trackedNotificationIds.includes(sessionId)) {
|
||||
state.trackedNotificationIds.push(sessionId);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -816,6 +855,7 @@ const agentsSlice = createSlice({
|
||||
state.loading = false;
|
||||
})
|
||||
.addCase(launchAgent.fulfilled, (state, action) => {
|
||||
if (!action.payload?.id) return;
|
||||
state.sessions[action.payload.id] = { ...action.payload, streamingMessage: null, tool_group_meta: action.payload.tool_group_meta ?? {} };
|
||||
state.activeSessionId = action.payload.id;
|
||||
if (!state.expandedSessionIds.includes(action.payload.id)) {
|
||||
@@ -827,6 +867,7 @@ const agentsSlice = createSlice({
|
||||
})
|
||||
.addCase(launchAndSendFirstMessage.fulfilled, (state, action) => {
|
||||
const { draftId, session } = action.payload;
|
||||
if (!session?.id) return;
|
||||
const shouldExpand = action.meta.arg.expand !== false;
|
||||
delete state.sessions[draftId];
|
||||
state.sessions[session.id] = { ...session, streamingMessage: null, tool_group_meta: session.tool_group_meta ?? {} };
|
||||
@@ -902,6 +943,7 @@ const agentsSlice = createSlice({
|
||||
})
|
||||
.addCase(duplicateSession.fulfilled, (state, action) => {
|
||||
const session = action.payload;
|
||||
if (!session?.id) return;
|
||||
state.sessions[session.id] = session;
|
||||
})
|
||||
.addCase(closeSession.fulfilled, (state, action) => {
|
||||
@@ -969,6 +1011,7 @@ const agentsSlice = createSlice({
|
||||
})
|
||||
.addCase(resumeSession.fulfilled, (state, action) => {
|
||||
const session = action.payload;
|
||||
if (!session?.id) return;
|
||||
state.sessions[session.id] = { ...session, streamingMessage: null, tool_group_meta: session.tool_group_meta ?? {} };
|
||||
delete state.history[session.id];
|
||||
state.activeSessionId = session.id;
|
||||
|
||||
@@ -162,7 +162,7 @@ class WebSocketManager {
|
||||
|
||||
switch (event) {
|
||||
case 'agent:status':
|
||||
if (data.session) {
|
||||
if (data.session && data.session.id) {
|
||||
store.dispatch(updateSession(data.session));
|
||||
} else if (session_id) {
|
||||
store.dispatch(updateSessionStatus({ sessionId: session_id, status: data.status }));
|
||||
|
||||
+15
-1
@@ -161,6 +161,19 @@ if (( elapsed >= MAX_WAIT )); then
|
||||
fi
|
||||
|
||||
# --- Start frontend ---
|
||||
# Reject port conflicts early: a generic curl to :3000 is not enough — anything
|
||||
# listening there makes curl succeed, then our new webpack fails with EADDRINUSE.
|
||||
if HTTP_BODY=$(curl -fsS --connect-timeout 1 "http://127.0.0.1:${FRONTEND_PORT}/" 2>/dev/null); then
|
||||
if echo "$HTTP_BODY" | grep -qF 'Open Swarm'; then
|
||||
echo -e "${RED}${BOLD}Port ${FRONTEND_PORT} is already serving this app (likely a leftover dev server).${RESET}"
|
||||
echo -e "${RED}Stop the old process (e.g. previous run/local.sh) or free the port, then try again.${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${RED}${BOLD}Port ${FRONTEND_PORT} responds to HTTP but is not this project's dev server.${RESET}"
|
||||
echo -e "${RED}Free the port or change frontend.dev in ports.config.json.${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}${BOLD}[frontend]${RESET} Starting frontend dev server..."
|
||||
bash "$PROJECT_ROOT/frontend/run.sh" > >(
|
||||
while IFS= read -r line; do
|
||||
@@ -175,7 +188,8 @@ echo -e "${YELLOW}${BOLD}Waiting for frontend (http://localhost:${FRONTEND_PORT}
|
||||
FRONTEND_MAX_WAIT=60
|
||||
frontend_elapsed=0
|
||||
while (( frontend_elapsed < FRONTEND_MAX_WAIT )); do
|
||||
if curl -s -o /dev/null --connect-timeout 1 "http://localhost:${FRONTEND_PORT}/" 2>/dev/null; then
|
||||
if HTTP_BODY=$(curl -fsS --connect-timeout 1 "http://127.0.0.1:${FRONTEND_PORT}/" 2>/dev/null) \
|
||||
&& echo "$HTTP_BODY" | grep -qF 'Open Swarm'; then
|
||||
echo -e "${GREEN}${BOLD}Frontend is ready!${RESET}"
|
||||
break
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user