chore: switch mergemaster to opus and add cargo fmt guidance
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-13
@@ -253,35 +253,30 @@ When the auto-resolver fails, you have access to the merge worktree at `.story_k
|
||||
6. Call the `run_tests` MCP tool to start tests, then poll `get_test_result` until complete
|
||||
7. If it compiles, commit and re-trigger merge_agent_work
|
||||
|
||||
### Common conflict patterns in this project:
|
||||
### Common conflict patterns:
|
||||
|
||||
**Story file rename/rename conflicts:** Both branches moved the story .md file to different pipeline directories. Resolution: `git rm` both sides — story files in `work/2_current/`, `work/3_qa/`, `work/4_merge/` are gitignored and don't need to be committed.
|
||||
|
||||
**bot.rs tokio::select! conflicts:** Master has a `tokio::select!` loop in `handle_message()` that handles permission forwarding (story 275). Feature branches created before story 275 have a simpler direct `provider.chat_stream().await` call. Resolution: KEEP master's tokio::select! loop. Integrate only the feature's new logic (e.g. typing indicators, new callbacks) into the existing loop structure. Do NOT replace the loop with the old direct call.
|
||||
**Story file rename/rename conflicts:** Both branches moved the story .md file to different pipeline directories. Resolution: `git rm` both sides — story files in pipeline directories are gitignored and don't need to be committed.
|
||||
|
||||
**Duplicate functions/imports:** The auto-resolver keeps both sides, producing duplicates. Resolution: keep one copy (prefer master's version), delete the duplicate.
|
||||
|
||||
**Formatting-only conflicts:** Both sides reformatted the same code differently. Resolution: pick either side (prefer master).
|
||||
|
||||
**IMPORTANT: After resolving ANY conflict or fixing ANY gate failure in the merge workspace, run `script/lint` (if it exists) or the project's formatter before recommitting.** The auto-resolver frequently produces code that compiles but fails formatting checks. Running the formatter after every fix prevents repeated gate failures.
|
||||
**IMPORTANT: After resolving ANY conflict or fixing ANY gate failure in the merge workspace, use the `run_lint` MCP tool to check formatting, then `run_tests` to verify everything passes before recommitting.** The auto-resolver frequently produces code that compiles but fails formatting or linting checks.
|
||||
|
||||
## Fixing Gate Failures
|
||||
|
||||
If quality gates fail, attempt to fix issues yourself in the merge worktree. Use the run_tests MCP tool (then poll get_test_result) to verify — do not run script/test via Bash.
|
||||
If quality gates fail, attempt to fix issues yourself in the merge workspace. Use the run_tests MCP tool to verify before recommitting.
|
||||
|
||||
**Fix yourself (up to 3 attempts total):**
|
||||
- Syntax errors (missing semicolons, brackets, commas)
|
||||
- Syntax errors
|
||||
- Duplicate definitions from merge artifacts
|
||||
- Simple type annotation errors
|
||||
- Unused import warnings flagged by clippy
|
||||
- Mismatched braces from bad conflict resolution
|
||||
- Trivial formatting issues that block compilation or linting
|
||||
- Unused import warnings
|
||||
- Formatting issues that block linting
|
||||
|
||||
**Report to human without attempting a fix:**
|
||||
- Logic errors or incorrect business logic
|
||||
- Missing function implementations
|
||||
- Architectural changes required
|
||||
- Non-trivial refactoring needed
|
||||
|
||||
**Max retry limit:** If gates still fail after 3 fix attempts, call report_merge_failure to record the failure, then stop immediately and report the full gate output to the human.
|
||||
|
||||
@@ -292,4 +287,4 @@ If quality gates fail, attempt to fix issues yourself in the merge worktree. Use
|
||||
- Report conflict resolution outcomes clearly
|
||||
- Report gate failures with full output so the human can act if needed
|
||||
- The server automatically runs acceptance gates when your process exits"""
|
||||
system_prompt = "You are the mergemaster agent. Your primary job is to merge feature branches to master. First try the merge_agent_work MCP tool. If the auto-resolver fails on complex conflicts, resolve them yourself in the merge worktree — you are an opus-class agent capable of understanding both sides of a conflict and producing correct merged code. Common patterns: keep master's tokio::select! permission loop in bot.rs, discard story file rename conflicts (gitignored), remove duplicate definitions. After resolving, verify compilation before re-triggering merge. CRITICAL: Never manually move story files or call accept_story. After 3 failed fix attempts, call report_merge_failure and stop."
|
||||
system_prompt = "You are the mergemaster agent. Your primary job is to merge feature branches to master. First try the merge_agent_work MCP tool. If the auto-resolver fails on complex conflicts, resolve them yourself in the merge workspace. Common patterns: discard story file rename conflicts (gitignored), remove duplicate definitions/imports. After resolving, verify with run_tests MCP tool before re-triggering merge. CRITICAL: Never manually move story files or call accept_story. After 3 failed fix attempts, call report_merge_failure and stop."
|
||||
|
||||
@@ -8,6 +8,18 @@ export interface JoinedAgent {
|
||||
label: string;
|
||||
address: string;
|
||||
registered_at: number;
|
||||
/// Project this agent is assigned to, if any.
|
||||
assigned_project?: string;
|
||||
}
|
||||
|
||||
export interface GatewayProject {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface GatewayInfo {
|
||||
active: string;
|
||||
projects: GatewayProject[];
|
||||
}
|
||||
|
||||
export interface GenerateTokenResponse {
|
||||
@@ -61,4 +73,17 @@ export const gatewayApi = {
|
||||
method: "DELETE",
|
||||
});
|
||||
},
|
||||
|
||||
/// Assign an agent to a project, or unassign it by passing null.
|
||||
assignAgent(id: string, project: string | null): Promise<JoinedAgent> {
|
||||
return gatewayRequest<JoinedAgent>(`/gateway/agents/${id}/assign`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ project }),
|
||||
});
|
||||
},
|
||||
|
||||
/// Get the list of registered projects from the gateway.
|
||||
getGatewayInfo(): Promise<GatewayInfo> {
|
||||
return gatewayRequest<GatewayInfo>("/api/gateway");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
/// Provides:
|
||||
/// - An "Add Agent" button that generates a one-time join token.
|
||||
/// - Instructions for running a build agent with the token.
|
||||
/// - A list of connected agents with per-agent "Remove" buttons.
|
||||
/// - A list of connected agents with per-agent project assignment and "Remove" buttons.
|
||||
|
||||
import * as React from "react";
|
||||
import { gatewayApi, type JoinedAgent } from "../api/gateway";
|
||||
import { gatewayApi, type JoinedAgent, type GatewayProject } from "../api/gateway";
|
||||
|
||||
const { useCallback, useEffect, useState } = React;
|
||||
|
||||
@@ -90,12 +90,17 @@ function TokenDisplay({ token }: { token: string }) {
|
||||
|
||||
function AgentRow({
|
||||
agent,
|
||||
projects,
|
||||
onRemove,
|
||||
onAssign,
|
||||
}: {
|
||||
agent: JoinedAgent;
|
||||
projects: GatewayProject[];
|
||||
onRemove: (id: string) => void;
|
||||
onAssign: (id: string, project: string | null) => void;
|
||||
}) {
|
||||
const registeredAt = new Date(agent.registered_at * 1000).toLocaleString();
|
||||
const isAssigned = Boolean(agent.assigned_project);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -116,9 +121,10 @@ function AgentRow({
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
borderRadius: "50%",
|
||||
background: "#3fb950",
|
||||
background: isAssigned ? "#3fb950" : "#6e7681",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
title={isAssigned ? "Assigned" : "Idle (unassigned)"}
|
||||
/>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontWeight: 600, color: "#e6edf3" }}>{agent.label}</div>
|
||||
@@ -129,6 +135,29 @@ function AgentRow({
|
||||
Registered {registeredAt}
|
||||
</div>
|
||||
</div>
|
||||
<select
|
||||
data-testid={`assign-agent-${agent.id}`}
|
||||
value={agent.assigned_project ?? ""}
|
||||
onChange={(e) =>
|
||||
onAssign(agent.id, e.target.value === "" ? null : e.target.value)
|
||||
}
|
||||
style={{
|
||||
fontSize: "0.8em",
|
||||
padding: "4px 8px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #30363d",
|
||||
background: "#0d1117",
|
||||
color: "#e6edf3",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<option value="">— unassigned —</option>
|
||||
{projects.map((p) => (
|
||||
<option key={p.name} value={p.name}>
|
||||
{p.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
data-testid={`remove-agent-${agent.id}`}
|
||||
@@ -152,6 +181,7 @@ function AgentRow({
|
||||
/// Gateway management panel — rendered when running in `--gateway` mode.
|
||||
export function GatewayPanel() {
|
||||
const [agents, setAgents] = useState<JoinedAgent[]>([]);
|
||||
const [projects, setProjects] = useState<GatewayProject[]>([]);
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -161,6 +191,10 @@ export function GatewayPanel() {
|
||||
.listAgents()
|
||||
.then(setAgents)
|
||||
.catch(() => setAgents([]));
|
||||
gatewayApi
|
||||
.getGatewayInfo()
|
||||
.then((info) => setProjects(info.projects))
|
||||
.catch(() => setProjects([]));
|
||||
}, []);
|
||||
|
||||
const handleAddAgent = useCallback(async () => {
|
||||
@@ -186,6 +220,20 @@ export function GatewayPanel() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAssignAgent = useCallback(
|
||||
async (id: string, project: string | null) => {
|
||||
try {
|
||||
const updated = await gatewayApi.assignAgent(id, project);
|
||||
setAgents((prev) =>
|
||||
prev.map((a) => (a.id === updated.id ? updated : a)),
|
||||
);
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -273,7 +321,9 @@ export function GatewayPanel() {
|
||||
<AgentRow
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
projects={projects}
|
||||
onRemove={handleRemoveAgent}
|
||||
onAssign={handleAssignAgent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
+137
-24
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
@@ -64,6 +64,9 @@ pub struct JoinedAgent {
|
||||
pub address: String,
|
||||
/// Unix timestamp when the agent registered.
|
||||
pub registered_at: f64,
|
||||
/// Project this agent is assigned to, if any.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub assigned_project: Option<String>,
|
||||
}
|
||||
|
||||
/// A one-time join token that has been generated but not yet consumed.
|
||||
@@ -80,6 +83,14 @@ struct RegisterAgentRequest {
|
||||
address: String,
|
||||
}
|
||||
|
||||
/// Request body for assigning or reassigning an agent to a project.
|
||||
///
|
||||
/// Send `{"project": "my-project"}` to assign, or `{"project": null}` to unassign.
|
||||
#[derive(Deserialize)]
|
||||
struct AssignAgentRequest {
|
||||
project: Option<String>,
|
||||
}
|
||||
|
||||
// ── Gateway state ────────────────────────────────────────────────────
|
||||
|
||||
/// Shared gateway state threaded through HTTP handlers.
|
||||
@@ -95,22 +106,51 @@ pub struct GatewayState {
|
||||
pub joined_agents: Arc<RwLock<Vec<JoinedAgent>>>,
|
||||
/// One-time join tokens that have been issued but not yet consumed.
|
||||
pending_tokens: Arc<RwLock<HashMap<String, PendingToken>>>,
|
||||
/// Directory containing `projects.toml`, used for persisting agent data.
|
||||
pub config_dir: PathBuf,
|
||||
}
|
||||
|
||||
/// Load persisted agents from `<config_dir>/gateway_agents.json`.
|
||||
/// Returns an empty list if the file does not exist or cannot be parsed.
|
||||
fn load_agents(config_dir: &Path) -> Vec<JoinedAgent> {
|
||||
let path = config_dir.join("gateway_agents.json");
|
||||
match std::fs::read(&path) {
|
||||
Ok(data) => serde_json::from_slice(&data).unwrap_or_default(),
|
||||
Err(_) => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Persist the current agent list to `<config_dir>/gateway_agents.json`.
|
||||
/// Silently ignores write errors (e.g. read-only filesystem or empty path).
|
||||
async fn save_agents(agents: &[JoinedAgent], config_dir: &Path) {
|
||||
if config_dir == Path::new("") {
|
||||
return;
|
||||
}
|
||||
let path = config_dir.join("gateway_agents.json");
|
||||
if let Ok(data) = serde_json::to_vec_pretty(agents) {
|
||||
let _ = tokio::fs::write(&path, data).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayState {
|
||||
/// Create a new gateway state from a config. The first project in the config
|
||||
/// becomes the active project by default.
|
||||
pub fn new(config: GatewayConfig) -> Result<Self, String> {
|
||||
/// Create a new gateway state from a config and config directory.
|
||||
///
|
||||
/// The first project in the config becomes the active project by default.
|
||||
/// Previously registered agents are loaded from `gateway_agents.json` in
|
||||
/// `config_dir` if the file exists.
|
||||
pub fn new(config: GatewayConfig, config_dir: PathBuf) -> Result<Self, String> {
|
||||
if config.projects.is_empty() {
|
||||
return Err("projects.toml must define at least one project".to_string());
|
||||
}
|
||||
let first = config.projects.keys().next().unwrap().clone();
|
||||
let agents = load_agents(&config_dir);
|
||||
Ok(Self {
|
||||
config,
|
||||
active_project: Arc::new(RwLock::new(first)),
|
||||
client: Client::new(),
|
||||
joined_agents: Arc::new(RwLock::new(Vec::new())),
|
||||
joined_agents: Arc::new(RwLock::new(agents)),
|
||||
pending_tokens: Arc::new(RwLock::new(HashMap::new())),
|
||||
config_dir,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -622,6 +662,7 @@ pub async fn gateway_register_agent_handler(
|
||||
label: req.label,
|
||||
address: req.address,
|
||||
registered_at: chrono::Utc::now().timestamp() as f64,
|
||||
assigned_project: None,
|
||||
};
|
||||
|
||||
crate::slog!(
|
||||
@@ -630,7 +671,11 @@ pub async fn gateway_register_agent_handler(
|
||||
agent.id
|
||||
);
|
||||
|
||||
state.joined_agents.write().await.push(agent.clone());
|
||||
{
|
||||
let mut agents = state.joined_agents.write().await;
|
||||
agents.push(agent.clone());
|
||||
save_agents(&agents, &state.config_dir).await;
|
||||
}
|
||||
|
||||
let body = serde_json::to_vec(&agent).unwrap_or_default();
|
||||
Response::builder()
|
||||
@@ -656,11 +701,16 @@ pub async fn gateway_remove_agent_handler(
|
||||
PoemPath(id): PoemPath<String>,
|
||||
state: Data<&Arc<GatewayState>>,
|
||||
) -> Response {
|
||||
let mut agents = state.joined_agents.write().await;
|
||||
let before = agents.len();
|
||||
agents.retain(|a| a.id != id);
|
||||
let removed = agents.len() < before;
|
||||
drop(agents);
|
||||
let removed = {
|
||||
let mut agents = state.joined_agents.write().await;
|
||||
let before = agents.len();
|
||||
agents.retain(|a| a.id != id);
|
||||
let removed = agents.len() < before;
|
||||
if removed {
|
||||
save_agents(&agents, &state.config_dir).await;
|
||||
}
|
||||
removed
|
||||
};
|
||||
|
||||
if removed {
|
||||
crate::slog!("[gateway] Removed agent id={id}");
|
||||
@@ -674,6 +724,63 @@ pub async fn gateway_remove_agent_handler(
|
||||
}
|
||||
}
|
||||
|
||||
/// `POST /gateway/agents/:id/assign` — assign or unassign an agent to a project.
|
||||
///
|
||||
/// Body: `{ "project": "my-project" }` to assign, or `{ "project": null }` to unassign.
|
||||
/// Returns the updated `JoinedAgent` on success. The assignment is persisted to disk
|
||||
/// so it survives gateway restarts.
|
||||
#[handler]
|
||||
pub async fn gateway_assign_agent_handler(
|
||||
PoemPath(id): PoemPath<String>,
|
||||
body: Json<AssignAgentRequest>,
|
||||
state: Data<&Arc<GatewayState>>,
|
||||
) -> Response {
|
||||
let project = body
|
||||
.0
|
||||
.project
|
||||
.and_then(|p| if p.is_empty() { None } else { Some(p) });
|
||||
|
||||
if let Some(ref p) = project
|
||||
&& !state.config.projects.contains_key(p.as_str())
|
||||
{
|
||||
return Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Body::from(format!("unknown project '{p}'")));
|
||||
}
|
||||
|
||||
let updated = {
|
||||
let mut agents = state.joined_agents.write().await;
|
||||
match agents.iter_mut().find(|a| a.id == id) {
|
||||
None => None,
|
||||
Some(a) => {
|
||||
a.assigned_project = project;
|
||||
Some(a.clone())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match updated {
|
||||
None => Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::from("agent not found")),
|
||||
Some(agent) => {
|
||||
crate::slog!(
|
||||
"[gateway] Agent '{}' (id={}) assigned to {:?}",
|
||||
agent.label,
|
||||
agent.id,
|
||||
agent.assigned_project
|
||||
);
|
||||
let agents = state.joined_agents.read().await.clone();
|
||||
save_agents(&agents, &state.config_dir).await;
|
||||
let body = serde_json::to_vec(&agent).unwrap_or_default();
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(body))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Health aggregation endpoint ──────────────────────────────────────
|
||||
|
||||
/// HTTP GET `/health` handler for the gateway — aggregates health from all projects.
|
||||
@@ -948,8 +1055,14 @@ pub async fn gateway_switch_handler(
|
||||
|
||||
/// Start the gateway HTTP server. This is the entry point when `--gateway` is used.
|
||||
pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
||||
// Locate the gateway config directory (parent of `projects.toml`).
|
||||
let config_dir = config_path
|
||||
.parent()
|
||||
.unwrap_or(std::path::Path::new("."))
|
||||
.to_path_buf();
|
||||
|
||||
let config = GatewayConfig::load(config_path).map_err(std::io::Error::other)?;
|
||||
let state = GatewayState::new(config).map_err(std::io::Error::other)?;
|
||||
let state = GatewayState::new(config, config_dir.clone()).map_err(std::io::Error::other)?;
|
||||
let state_arc = Arc::new(state);
|
||||
|
||||
let active = state_arc.active_project.read().await.clone();
|
||||
@@ -965,12 +1078,6 @@ pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
// Locate the gateway config directory (parent of `projects.toml`).
|
||||
let config_dir = config_path
|
||||
.parent()
|
||||
.unwrap_or(std::path::Path::new("."))
|
||||
.to_path_buf();
|
||||
|
||||
// Write `.mcp.json` so that the gateway's Matrix bot's Claude Code CLI
|
||||
// connects to this gateway's MCP endpoint (which proxies to the active project).
|
||||
if let Err(e) = write_gateway_mcp_json(&config_dir, port) {
|
||||
@@ -1010,6 +1117,10 @@ pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
||||
"/gateway/agents/:id",
|
||||
poem::delete(gateway_remove_agent_handler),
|
||||
)
|
||||
.at(
|
||||
"/gateway/agents/:id/assign",
|
||||
poem::post(gateway_assign_agent_handler),
|
||||
)
|
||||
// Serve the embedded React frontend so the gateway has a UI.
|
||||
.at(
|
||||
"/assets/*path",
|
||||
@@ -1127,7 +1238,7 @@ url = "http://localhost:3002"
|
||||
let config = GatewayConfig {
|
||||
projects: BTreeMap::new(),
|
||||
};
|
||||
assert!(GatewayState::new(config).is_err());
|
||||
assert!(GatewayState::new(config, PathBuf::new()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1146,7 +1257,7 @@ url = "http://localhost:3002"
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let state = GatewayState::new(config).unwrap();
|
||||
let state = GatewayState::new(config, PathBuf::new()).unwrap();
|
||||
let active = state.active_project.blocking_read().clone();
|
||||
assert_eq!(active, "alpha"); // BTreeMap sorts alphabetically.
|
||||
}
|
||||
@@ -1179,7 +1290,7 @@ url = "http://localhost:3002"
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let state = GatewayState::new(config).unwrap();
|
||||
let state = GatewayState::new(config, PathBuf::new()).unwrap();
|
||||
|
||||
let params = json!({ "arguments": { "project": "beta" } });
|
||||
let resp = handle_switch_project(¶ms, &state).await;
|
||||
@@ -1199,7 +1310,7 @@ url = "http://localhost:3002"
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let state = GatewayState::new(config).unwrap();
|
||||
let state = GatewayState::new(config, PathBuf::new()).unwrap();
|
||||
|
||||
let params = json!({ "arguments": { "project": "nonexistent" } });
|
||||
let resp = handle_switch_project(¶ms, &state).await;
|
||||
@@ -1216,7 +1327,7 @@ url = "http://localhost:3002"
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let state = GatewayState::new(config).unwrap();
|
||||
let state = GatewayState::new(config, PathBuf::new()).unwrap();
|
||||
|
||||
let url = state.active_url().await.unwrap();
|
||||
assert_eq!(url, "http://my:3001");
|
||||
@@ -1352,7 +1463,7 @@ enabled = false
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
Arc::new(GatewayState::new(config).unwrap())
|
||||
Arc::new(GatewayState::new(config, PathBuf::new()).unwrap())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1452,6 +1563,7 @@ enabled = false
|
||||
label: "agent-1".into(),
|
||||
address: "ws://a:3001/crdt-sync".into(),
|
||||
registered_at: 0.0,
|
||||
assigned_project: None,
|
||||
});
|
||||
let app = poem::Route::new()
|
||||
.at("/gateway/agents", poem::get(gateway_list_agents_handler))
|
||||
@@ -1472,6 +1584,7 @@ enabled = false
|
||||
label: "to-delete".into(),
|
||||
address: "ws://x:3001/crdt-sync".into(),
|
||||
registered_at: 0.0,
|
||||
assigned_project: None,
|
||||
});
|
||||
let app = poem::Route::new()
|
||||
.at(
|
||||
|
||||
Reference in New Issue
Block a user