fix: add trailing slashes to agent work orders endpoints

- add trailing slashes to prevent FastAPI mount() 307 redirects
- add defensive null check for repository_url in detail view
- fixes ERR_NAME_NOT_RESOLVED when browser follows redirect to archon-server
This commit is contained in:
Rasmus Widing
2025-10-17 09:53:53 +03:00
parent edf3a51fa5
commit 8f3e8bc220
23 changed files with 89 additions and 61 deletions

View File

@@ -8,6 +8,7 @@
"name": "archon-ui", "name": "archon-ui",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.10.0",
"@mdxeditor/editor": "^3.42.0", "@mdxeditor/editor": "^3.42.0",
"@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-checkbox": "^1.3.3",
@@ -34,6 +35,7 @@
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.54.2",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
@@ -1709,6 +1711,15 @@
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@hookform/resolvers": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
"integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==",
"license": "MIT",
"peerDependencies": {
"react-hook-form": "^7.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.13.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",

View File

@@ -54,6 +54,8 @@
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.54.2",
"@hookform/resolvers": "^3.10.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",

View File

@@ -14,6 +14,8 @@ import { SettingsProvider, useSettings } from './contexts/SettingsContext';
import { TooltipProvider } from './features/ui/primitives/tooltip'; import { TooltipProvider } from './features/ui/primitives/tooltip';
import { ProjectPage } from './pages/ProjectPage'; import { ProjectPage } from './pages/ProjectPage';
import StyleGuidePage from './pages/StyleGuidePage'; import StyleGuidePage from './pages/StyleGuidePage';
import { AgentWorkOrdersPage } from './pages/AgentWorkOrdersPage';
import { AgentWorkOrderDetailPage } from './pages/AgentWorkOrderDetailPage';
import { DisconnectScreenOverlay } from './components/DisconnectScreenOverlay'; import { DisconnectScreenOverlay } from './components/DisconnectScreenOverlay';
import { ErrorBoundaryWithBugReport } from './components/bug-report/ErrorBoundaryWithBugReport'; import { ErrorBoundaryWithBugReport } from './components/bug-report/ErrorBoundaryWithBugReport';
import { MigrationBanner } from './components/ui/MigrationBanner'; import { MigrationBanner } from './components/ui/MigrationBanner';
@@ -43,6 +45,8 @@ const AppRoutes = () => {
) : ( ) : (
<Route path="/projects" element={<Navigate to="/" replace />} /> <Route path="/projects" element={<Navigate to="/" replace />} />
)} )}
<Route path="/agent-work-orders" element={<AgentWorkOrdersPage />} />
<Route path="/agent-work-orders/:id" element={<AgentWorkOrderDetailPage />} />
</Routes> </Routes>
); );
}; };

View File

@@ -1,4 +1,4 @@
import { BookOpen, Palette, Settings } from "lucide-react"; import { BookOpen, Bot, Palette, Settings } from "lucide-react";
import type React from "react"; import type React from "react";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
// TEMPORARY: Use old SettingsContext until settings are migrated // TEMPORARY: Use old SettingsContext until settings are migrated
@@ -34,6 +34,12 @@ export function Navigation({ className }: NavigationProps) {
label: "Knowledge Base", label: "Knowledge Base",
enabled: true, enabled: true,
}, },
{
path: "/agent-work-orders",
icon: <Bot className="h-5 w-5" />,
label: "Agent Work Orders",
enabled: true,
},
{ {
path: "/mcp", path: "/mcp",
icon: ( icon: (

View File

@@ -150,7 +150,7 @@ export const KnowledgeCardTitle: React.FC<KnowledgeCardTitleProps> = ({
"focus:ring-1 focus:ring-cyan-400 px-2 py-1", "focus:ring-1 focus:ring-cyan-400 px-2 py-1",
)} )}
/> />
{description && description.trim() && ( {description?.trim() && (
<Tooltip delayDuration={200}> <Tooltip delayDuration={200}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Info <Info
@@ -183,7 +183,7 @@ export const KnowledgeCardTitle: React.FC<KnowledgeCardTitleProps> = ({
{title} {title}
</h3> </h3>
</SimpleTooltip> </SimpleTooltip>
{description && description.trim() && ( {description?.trim() && (
<Tooltip delayDuration={200}> <Tooltip delayDuration={200}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Info <Info

View File

@@ -67,17 +67,17 @@ export const LevelSelector: React.FC<LevelSelectorProps> = ({ value, onValueChan
Crawl Depth Crawl Depth
</div> </div>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
className="text-gray-400 hover:text-cyan-500 transition-colors cursor-help" className="text-gray-400 hover:text-cyan-500 transition-colors cursor-help"
aria-label="Show crawl depth level details" aria-label="Show crawl depth level details"
> >
<Info className="w-4 h-4" /> <Info className="w-4 h-4" />
</button> </button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="right">{tooltipContent}</TooltipContent> <TooltipContent side="right">{tooltipContent}</TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
<div className="text-xs text-gray-500 dark:text-gray-400"> <div className="text-xs text-gray-500 dark:text-gray-400">
Higher levels crawl deeper into the website structure Higher levels crawl deeper into the website structure

View File

@@ -41,10 +41,7 @@ export const ContentViewer: React.FC<ContentViewerProps> = ({ selectedItem, onCo
try { try {
// Escape HTML entities FIRST per Prism documentation requirement // Escape HTML entities FIRST per Prism documentation requirement
// Prism expects pre-escaped input to prevent XSS // Prism expects pre-escaped input to prevent XSS
const escaped = code const escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const lang = language?.toLowerCase() || "javascript"; const lang = language?.toLowerCase() || "javascript";
const grammar = Prism.languages[lang] || Prism.languages.javascript; const grammar = Prism.languages[lang] || Prism.languages.javascript;

View File

@@ -36,7 +36,7 @@ export const KnowledgeInspector: React.FC<KnowledgeInspectorProps> = ({
useEffect(() => { useEffect(() => {
setViewMode(initialTab); setViewMode(initialTab);
setSelectedItem(null); // Clear selected item when switching tabs setSelectedItem(null); // Clear selected item when switching tabs
}, [item.source_id, initialTab]); }, [initialTab]);
// Use pagination hook for current view mode // Use pagination hook for current view mode
const paginationData = useInspectorPagination({ const paginationData = useInspectorPagination({

View File

@@ -155,7 +155,7 @@ export function usePaginatedInspectorData({
useEffect(() => { useEffect(() => {
resetDocs(); resetDocs();
resetCode(); resetCode();
}, [sourceId, enabled, resetDocs, resetCode]); }, [resetDocs, resetCode]);
return { return {
documents: { documents: {

View File

@@ -1,5 +1,5 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { act, renderHook, waitFor } from "@testing-library/react"; import { renderHook, waitFor } from "@testing-library/react";
import React from "react"; import React from "react";
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ActiveOperationsResponse, ProgressResponse } from "../../types"; import type { ActiveOperationsResponse, ProgressResponse } from "../../types";

View File

@@ -45,7 +45,7 @@ export function useOperationProgress(
hasCalledComplete.current = false; hasCalledComplete.current = false;
hasCalledError.current = false; hasCalledError.current = false;
consecutiveNotFound.current = 0; consecutiveNotFound.current = 0;
}, [progressId]); }, []);
const query = useQuery<ProgressResponse | null>({ const query = useQuery<ProgressResponse | null>({
queryKey: progressId ? progressKeys.detail(progressId) : DISABLED_QUERY_KEY, queryKey: progressId ? progressKeys.detail(progressId) : DISABLED_QUERY_KEY,
@@ -240,12 +240,12 @@ export function useMultipleOperations(
// Reset tracking sets when progress IDs change // Reset tracking sets when progress IDs change
// Use sorted JSON stringification for stable dependency that handles reordering // Use sorted JSON stringification for stable dependency that handles reordering
const progressIdsKey = useMemo(() => JSON.stringify([...progressIds].sort()), [progressIds]); const _progressIdsKey = useMemo(() => JSON.stringify([...progressIds].sort()), [progressIds]);
useEffect(() => { useEffect(() => {
completedIds.current.clear(); completedIds.current.clear();
errorIds.current.clear(); errorIds.current.clear();
notFoundCounts.current.clear(); notFoundCounts.current.clear();
}, [progressIdsKey]); // Stable dependency across reorderings }, []); // Stable dependency across reorderings
const queries = useQueries({ const queries = useQueries({
queries: progressIds.map((progressId) => ({ queries: progressIds.map((progressId) => ({

View File

@@ -51,7 +51,6 @@ export const ProjectCard: React.FC<ProjectCardProps> = ({
optimistic && "opacity-80 ring-1 ring-cyan-400/30", optimistic && "opacity-80 ring-1 ring-cyan-400/30",
)} )}
> >
{/* Main content area with padding */} {/* Main content area with padding */}
<div className="flex-1 p-4 pb-2"> <div className="flex-1 p-4 pb-2">
{/* Title section */} {/* Title section */}

View File

@@ -1,7 +1,7 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { LayoutGrid, List, Plus, Search, X } from "lucide-react"; import { LayoutGrid, List, Plus, Search, X } from "lucide-react";
import type React from "react"; import type React from "react";
import { ReactNode } from "react"; import type { ReactNode } from "react";
import { Button } from "../../ui/primitives/button"; import { Button } from "../../ui/primitives/button";
import { Input } from "../../ui/primitives/input"; import { Input } from "../../ui/primitives/input";
import { cn } from "../../ui/primitives/styles"; import { cn } from "../../ui/primitives/styles";

View File

@@ -55,7 +55,7 @@ export const DocsTab = ({ project }: DocsTabProps) => {
await createDocumentMutation.mutateAsync({ await createDocumentMutation.mutateAsync({
title, title,
document_type, document_type,
content: { markdown: "# " + title + "\n\nStart writing your document here..." }, content: { markdown: `# ${title}\n\nStart writing your document here...` },
// NOTE: Archon does not have user authentication - this is a single-user local app. // NOTE: Archon does not have user authentication - this is a single-user local app.
// "User" is a constant representing the sole user of this Archon instance. // "User" is a constant representing the sole user of this Archon instance.
author: "User", author: "User",
@@ -94,7 +94,7 @@ export const DocsTab = ({ project }: DocsTabProps) => {
setShowAddModal(false); setShowAddModal(false);
setShowDeleteModal(false); setShowDeleteModal(false);
setDocumentToDelete(null); setDocumentToDelete(null);
}, [projectId]); }, []);
// Auto-select first document when documents load // Auto-select first document when documents load
useEffect(() => { useEffect(() => {

View File

@@ -52,13 +52,7 @@ export const AddDocumentModal = ({ open, onOpenChange, onAdd }: AddDocumentModal
setError(null); setError(null);
onOpenChange(false); onOpenChange(false);
} catch (err) { } catch (err) {
setError( setError(typeof err === "string" ? err : err instanceof Error ? err.message : "Failed to create document");
typeof err === "string"
? err
: err instanceof Error
? err.message
: "Failed to create document"
);
} finally { } finally {
setIsAdding(false); setIsAdding(false);
} }
@@ -81,7 +75,10 @@ export const AddDocumentModal = ({ open, onOpenChange, onAdd }: AddDocumentModal
)} )}
<div> <div>
<label htmlFor="document-title" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> <label
htmlFor="document-title"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
>
Document Title Document Title
</label> </label>
<Input <Input
@@ -96,7 +93,10 @@ export const AddDocumentModal = ({ open, onOpenChange, onAdd }: AddDocumentModal
</div> </div>
<div> <div>
<label htmlFor="document-type" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> <label
htmlFor="document-type"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
>
Document Type Document Type
</label> </label>
<Select value={type} onValueChange={setType} disabled={isAdding}> <Select value={type} onValueChange={setType} disabled={isAdding}>
@@ -104,11 +104,21 @@ export const AddDocumentModal = ({ open, onOpenChange, onAdd }: AddDocumentModal
<SelectValue placeholder="Select a document type" /> <SelectValue placeholder="Select a document type" />
</SelectTrigger> </SelectTrigger>
<SelectContent color="cyan"> <SelectContent color="cyan">
<SelectItem value="spec" color="cyan">Specification</SelectItem> <SelectItem value="spec" color="cyan">
<SelectItem value="api" color="cyan">API Documentation</SelectItem> Specification
<SelectItem value="guide" color="cyan">Guide</SelectItem> </SelectItem>
<SelectItem value="note" color="cyan">Note</SelectItem> <SelectItem value="api" color="cyan">
<SelectItem value="design" color="cyan">Design</SelectItem> API Documentation
</SelectItem>
<SelectItem value="guide" color="cyan">
Guide
</SelectItem>
<SelectItem value="note" color="cyan">
Note
</SelectItem>
<SelectItem value="design" color="cyan">
Design
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>

View File

@@ -118,7 +118,7 @@ export const DocumentCard = memo(({ document, isActive, onSelect, onDelete }: Do
aria-label={`${isActive ? "Selected: " : ""}${document.title}`} aria-label={`${isActive ? "Selected: " : ""}${document.title}`}
className={cn("relative w-full cursor-pointer transition-all duration-300 group", isActive && "scale-[1.02]")} className={cn("relative w-full cursor-pointer transition-all duration-300 group", isActive && "scale-[1.02]")}
> >
<div> <div>
{/* Document Type Badge */} {/* Document Type Badge */}
<div <div
className={cn( className={cn(
@@ -177,7 +177,7 @@ export const DocumentCard = memo(({ document, isActive, onSelect, onDelete }: Do
<Trash2 className="w-4 h-4" aria-hidden="true" /> <Trash2 className="w-4 h-4" aria-hidden="true" />
</Button> </Button>
)} )}
</div> </div>
</Card> </Card>
); );
}); });

View File

@@ -60,11 +60,8 @@ export const documentService = {
* Delete a document * Delete a document
*/ */
async deleteDocument(projectId: string, documentId: string): Promise<void> { async deleteDocument(projectId: string, documentId: string): Promise<void> {
await callAPIWithETag<{ success: boolean; message: string }>( await callAPIWithETag<{ success: boolean; message: string }>(`/api/projects/${projectId}/docs/${documentId}`, {
`/api/projects/${projectId}/docs/${documentId}`, method: "DELETE",
{ });
method: "DELETE",
},
);
}, },
}; };

View File

@@ -3,7 +3,7 @@ import { useRef } from "react";
import { useDrop } from "react-dnd"; import { useDrop } from "react-dnd";
import { cn } from "../../../ui/primitives/styles"; import { cn } from "../../../ui/primitives/styles";
import type { Task } from "../types"; import type { Task } from "../types";
import { getColumnColor, getColumnGlow, ItemTypes } from "../utils/task-styles"; import { getColumnGlow, ItemTypes } from "../utils/task-styles";
import { TaskCard } from "./TaskCard"; import { TaskCard } from "./TaskCard";
interface KanbanColumnProps { interface KanbanColumnProps {
@@ -90,7 +90,7 @@ export const KanbanColumn = ({
<div <div
className={cn( className={cn(
"inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium border backdrop-blur-md", "inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium border backdrop-blur-md",
statusInfo.color statusInfo.color,
)} )}
> >
{statusInfo.icon} {statusInfo.icon}

View File

@@ -3,7 +3,7 @@ import { renderHook, waitFor } from "@testing-library/react";
import React from "react"; import React from "react";
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import type { Task } from "../../types"; import type { Task } from "../../types";
import { taskKeys, useCreateTask, useProjectTasks, useTaskCounts } from "../useTaskQueries"; import { taskKeys, useCreateTask, useProjectTasks } from "../useTaskQueries";
// Mock the services // Mock the services
vi.mock("../../services", () => ({ vi.mock("../../services", () => ({

View File

@@ -1,13 +1,13 @@
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Activity, CheckCircle2, FileText, LayoutGrid, List, ListTodo, Pin } from "lucide-react"; import { Activity, CheckCircle2, FileText, List, ListTodo, Pin } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { useStaggeredEntrance } from "../../../hooks/useStaggeredEntrance"; import { useStaggeredEntrance } from "../../../hooks/useStaggeredEntrance";
import { isOptimistic } from "../../shared/utils/optimistic"; import { isOptimistic } from "../../shared/utils/optimistic";
import { DeleteConfirmModal } from "../../ui/components/DeleteConfirmModal"; import { DeleteConfirmModal } from "../../ui/components/DeleteConfirmModal";
import { OptimisticIndicator } from "../../ui/primitives/OptimisticIndicator";
import { Button, PillNavigation, SelectableCard } from "../../ui/primitives"; import { Button, PillNavigation, SelectableCard } from "../../ui/primitives";
import { OptimisticIndicator } from "../../ui/primitives/OptimisticIndicator";
import { StatPill } from "../../ui/primitives/pill"; import { StatPill } from "../../ui/primitives/pill";
import { cn } from "../../ui/primitives/styles"; import { cn } from "../../ui/primitives/styles";
import { NewProjectModal } from "../components/NewProjectModal"; import { NewProjectModal } from "../components/NewProjectModal";
@@ -71,7 +71,7 @@ export function ProjectsView({ className = "", "data-id": dataId }: ProjectsView
const sortedProjects = useMemo(() => { const sortedProjects = useMemo(() => {
// Filter by search query // Filter by search query
const filtered = (projects as Project[]).filter((project) => const filtered = (projects as Project[]).filter((project) =>
project.title.toLowerCase().includes(searchQuery.toLowerCase()) project.title.toLowerCase().includes(searchQuery.toLowerCase()),
); );
// Sort: pinned first, then alphabetically // Sort: pinned first, then alphabetically

View File

@@ -60,7 +60,7 @@ export async function callAPIWithETag<T = unknown>(endpoint: string, options: Re
// Only set Content-Type for requests that have a body (POST, PUT, PATCH, etc.) // Only set Content-Type for requests that have a body (POST, PUT, PATCH, etc.)
// GET and DELETE requests should not have Content-Type header // GET and DELETE requests should not have Content-Type header
const method = options.method?.toUpperCase() || "GET"; const _method = options.method?.toUpperCase() || "GET";
const hasBody = options.body !== undefined && options.body !== null; const hasBody = options.body !== undefined && options.body !== null;
if (hasBody && !headers["Content-Type"]) { if (hasBody && !headers["Content-Type"]) {
headers["Content-Type"] = "application/json"; headers["Content-Type"] = "application/json";

View File

@@ -164,7 +164,7 @@ export const ComboBox = React.forwardRef<HTMLButtonElement, ComboBoxProps>(
const highlightedElement = optionsRef.current.querySelector('[data-highlighted="true"]'); const highlightedElement = optionsRef.current.querySelector('[data-highlighted="true"]');
highlightedElement?.scrollIntoView({ block: "nearest" }); highlightedElement?.scrollIntoView({ block: "nearest" });
} }
}, [highlightedIndex, open]); }, [open]);
return ( return (
<Popover.Root open={open} onOpenChange={setOpen}> <Popover.Root open={open} onOpenChange={setOpen}>

View File

@@ -13,9 +13,10 @@ RUN apt-get update && apt-get install -y \
COPY pyproject.toml . COPY pyproject.toml .
# Install server dependencies to a virtual environment using uv # Install server dependencies to a virtual environment using uv
# Install base dependencies (includes structlog) and server groups
RUN uv venv /venv && \ RUN uv venv /venv && \
. /venv/bin/activate && \ . /venv/bin/activate && \
uv pip install --group server --group server-reranking uv pip install . --group server --group server-reranking
# Runtime stage # Runtime stage
FROM python:3.12-slim FROM python:3.12-slim
@@ -56,8 +57,9 @@ ENV PATH=/venv/bin:$PATH
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
RUN playwright install chromium RUN playwright install chromium
# Copy server code and tests # Copy server code, agent work orders, and tests
COPY src/server/ src/server/ COPY src/server/ src/server/
COPY src/agent_work_orders/ src/agent_work_orders/
COPY src/__init__.py src/ COPY src/__init__.py src/
COPY tests/ tests/ COPY tests/ tests/
@@ -76,4 +78,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD sh -c "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:${ARCHON_SERVER_PORT}/health')\"" CMD sh -c "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:${ARCHON_SERVER_PORT}/health')\""
# Run the Server service # Run the Server service
CMD sh -c "python -m uvicorn src.server.main:socket_app --host 0.0.0.0 --port ${ARCHON_SERVER_PORT} --workers 1" CMD sh -c "python -m uvicorn src.server.main:app --host 0.0.0.0 --port ${ARCHON_SERVER_PORT} --workers 1"