chore: format with prettier [skip ci]

This commit is contained in:
github-actions[bot] 2026-06-25 22:36:23 +00:00
parent 2e5d66e398
commit 75fdf8941c
19 changed files with 3220 additions and 3195 deletions

View File

@ -50,7 +50,7 @@ An Agentic IDE that supervises parallel AI coding agents in isolated workspaces,
## Features
| Feature | Description |
| :--- | :--- |
| :----------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Agent-Agnostic Platform** | 23+ agent adapters including [Claude Code](https://code.claude.com/docs/en/overview), [OpenAI Codex](https://openai.com/), [Cursor](https://cursor.com/), [OpenCode](https://opencode.ai/), [Aider](https://aider.chat/), [Amp](https://ampcode.com/manual), [Goose](https://goose-docs.ai/), [GitHub Copilot](https://github.com/features/copilot), [Grok](https://x.ai/grok), [Qwen Code](https://github.com/QwenLM/qwen-code), [Kimi Code](https://www.kimi.com/code), [Cline](https://cline.bot/), [Continue](https://www.continue.dev/), [Kiro](https://kiro.dev/), and more |
| **Isolated Workspaces** | Each session spawns into its own git worktree with dedicated runtime |
| **Platform-Native Runtimes** | tmux on Darwin/Linux, conpty on Windows for optimal performance |
@ -74,13 +74,14 @@ Works with 23+ CLI-based coding agents including Claude Code, OpenAI Codex, Curs
### Prerequisites
| Requirement | Minimum | Recommended |
|-------------|---------|-------------|
| ----------- | ------- | ----------- |
| Go | 1.25+ | Latest |
| Node.js | 20+ | Latest LTS |
| Git | Any | Latest |
| pnpm | Any | Latest |
**Optional:**
- `tmux` (Darwin/Linux) - For Unix runtime
- `gh` (GitHub CLI) - For authenticated GitHub API calls
@ -89,7 +90,7 @@ Works with 23+ CLI-based coding agents including Claude Code, OpenAI Codex, Curs
Download the latest release for your platform:
| Platform | Download |
|----------|----------|
| ----------- | ------------------------------------------------------------------------------------------------- |
| **Windows** | [Setup.exe](https://github.com/AgentWrapper/agent-orchestrator/releases/latest) |
| **macOS** | [Agent Orchestrator.dmg](https://github.com/AgentWrapper/agent-orchestrator/releases/latest) |
| **Linux** | [Agent Orchestrator.AppImage](https://github.com/AgentWrapper/agent-orchestrator/releases/latest) |
@ -105,6 +106,7 @@ Agent Orchestrator is a long-running Go daemon built around **inbound/outbound p
**Core mental model:** OBSERVE external facts → UPDATE durable facts → DERIVE display status / ACT
**Key components:**
- **Frontend** - Electron + React UI with TanStack Router/Query and shadcn/ui
- **Backend Daemon** - Go-based HTTP server with controllers, services, and adapters
- **Runtime** - Platform-specific: `tmux` on Darwin/Linux, `conpty` on Windows
@ -118,7 +120,7 @@ For detailed architecture diagrams, data flows, and load-bearing rules, see [arc
## Documentation
| Document | Description |
|----------|-------------|
| -------------------------------------------------------- | ------------------------------------------------------- |
| [Architecture](docs/architecture.md) | System architecture, data flows, and load-bearing rules |
| [Backend Code Structure](docs/backend-code-structure.md) | Package-by-package ownership and dependency rules |
| [AGENTS.md](AGENTS.md) | Contributor and worker-agent contract |
@ -148,7 +150,7 @@ npx @redwoodjs/agent-ci run --all
All configuration is environment-driven. The daemon takes no config file.
| Variable | Default | Purpose |
|----------|---------|---------|
| --------------------- | -------------------- | --------------------------- |
| `AO_PORT` | `3001` | HTTP bind port |
| `AO_REQUEST_TIMEOUT` | `60s` | Per-request timeout |
| `AO_SHUTDOWN_TIMEOUT` | `10s` | Graceful shutdown cap |

View File

@ -531,7 +531,20 @@ export interface components {
projectId: string;
prs: components["schemas"]["SessionPRFacts"][];
/** @enum {string} */
status: "working" | "pr_open" | "draft" | "ci_failed" | "review_pending" | "changes_requested" | "approved" | "mergeable" | "merged" | "needs_input" | "idle" | "terminated" | "no_signal";
status:
| "working"
| "pr_open"
| "draft"
| "ci_failed"
| "review_pending"
| "changes_requested"
| "approved"
| "mergeable"
| "merged"
| "needs_input"
| "idle"
| "terminated"
| "no_signal";
terminalHandleId?: string;
/** Format: date-time */
updatedAt: string;
@ -837,7 +850,30 @@ export interface components {
SpawnSessionRequest: {
branch?: string;
/** @enum {string} */
harness?: "claude-code" | "codex" | "aider" | "opencode" | "grok" | "droid" | "amp" | "agy" | "crush" | "cursor" | "qwen" | "copilot" | "goose" | "auggie" | "continue" | "devin" | "cline" | "kimi" | "kiro" | "kilocode" | "vibe" | "pi" | "autohand";
harness?:
| "claude-code"
| "codex"
| "aider"
| "opencode"
| "grok"
| "droid"
| "amp"
| "agy"
| "crush"
| "cursor"
| "qwen"
| "copilot"
| "goose"
| "auggie"
| "continue"
| "devin"
| "cline"
| "kimi"
| "kiro"
| "kilocode"
| "vibe"
| "pi"
| "autohand";
issueId?: string;
/** @enum {string} */
kind?: "worker" | "orchestrator";

View File

@ -8,171 +8,169 @@
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as ShellRouteImport } from './routes/_shell'
import { Route as ShellIndexRouteImport } from './routes/_shell.index'
import { Route as ShellPrsRouteImport } from './routes/_shell.prs'
import { Route as ShellSessionsSessionIdRouteImport } from './routes/_shell.sessions.$sessionId'
import { Route as ShellProjectsProjectIdRouteImport } from './routes/_shell.projects.$projectId'
import { Route as ShellProjectsProjectIdSettingsRouteImport } from './routes/_shell.projects.$projectId_.settings'
import { Route as ShellProjectsProjectIdSessionsSessionIdRouteImport } from './routes/_shell.projects.$projectId_.sessions.$sessionId'
import { Route as rootRouteImport } from "./routes/__root";
import { Route as ShellRouteImport } from "./routes/_shell";
import { Route as ShellIndexRouteImport } from "./routes/_shell.index";
import { Route as ShellPrsRouteImport } from "./routes/_shell.prs";
import { Route as ShellSessionsSessionIdRouteImport } from "./routes/_shell.sessions.$sessionId";
import { Route as ShellProjectsProjectIdRouteImport } from "./routes/_shell.projects.$projectId";
import { Route as ShellProjectsProjectIdSettingsRouteImport } from "./routes/_shell.projects.$projectId_.settings";
import { Route as ShellProjectsProjectIdSessionsSessionIdRouteImport } from "./routes/_shell.projects.$projectId_.sessions.$sessionId";
const ShellRoute = ShellRouteImport.update({
id: '/_shell',
id: "/_shell",
getParentRoute: () => rootRouteImport,
} as any)
} as any);
const ShellIndexRoute = ShellIndexRouteImport.update({
id: '/',
path: '/',
id: "/",
path: "/",
getParentRoute: () => ShellRoute,
} as any)
} as any);
const ShellPrsRoute = ShellPrsRouteImport.update({
id: '/prs',
path: '/prs',
id: "/prs",
path: "/prs",
getParentRoute: () => ShellRoute,
} as any)
} as any);
const ShellSessionsSessionIdRoute = ShellSessionsSessionIdRouteImport.update({
id: '/sessions/$sessionId',
path: '/sessions/$sessionId',
id: "/sessions/$sessionId",
path: "/sessions/$sessionId",
getParentRoute: () => ShellRoute,
} as any)
} as any);
const ShellProjectsProjectIdRoute = ShellProjectsProjectIdRouteImport.update({
id: '/projects/$projectId',
path: '/projects/$projectId',
id: "/projects/$projectId",
path: "/projects/$projectId",
getParentRoute: () => ShellRoute,
} as any)
const ShellProjectsProjectIdSettingsRoute =
ShellProjectsProjectIdSettingsRouteImport.update({
id: '/projects/$projectId_/settings',
path: '/projects/$projectId/settings',
} as any);
const ShellProjectsProjectIdSettingsRoute = ShellProjectsProjectIdSettingsRouteImport.update({
id: "/projects/$projectId_/settings",
path: "/projects/$projectId/settings",
getParentRoute: () => ShellRoute,
} as any)
const ShellProjectsProjectIdSessionsSessionIdRoute =
ShellProjectsProjectIdSessionsSessionIdRouteImport.update({
id: '/projects/$projectId_/sessions/$sessionId',
path: '/projects/$projectId/sessions/$sessionId',
} as any);
const ShellProjectsProjectIdSessionsSessionIdRoute = ShellProjectsProjectIdSessionsSessionIdRouteImport.update({
id: "/projects/$projectId_/sessions/$sessionId",
path: "/projects/$projectId/sessions/$sessionId",
getParentRoute: () => ShellRoute,
} as any)
} as any);
export interface FileRoutesByFullPath {
'/': typeof ShellIndexRoute
'/prs': typeof ShellPrsRoute
'/projects/$projectId': typeof ShellProjectsProjectIdRoute
'/sessions/$sessionId': typeof ShellSessionsSessionIdRoute
'/projects/$projectId/settings': typeof ShellProjectsProjectIdSettingsRoute
'/projects/$projectId/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute
"/": typeof ShellIndexRoute;
"/prs": typeof ShellPrsRoute;
"/projects/$projectId": typeof ShellProjectsProjectIdRoute;
"/sessions/$sessionId": typeof ShellSessionsSessionIdRoute;
"/projects/$projectId/settings": typeof ShellProjectsProjectIdSettingsRoute;
"/projects/$projectId/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute;
}
export interface FileRoutesByTo {
'/prs': typeof ShellPrsRoute
'/': typeof ShellIndexRoute
'/projects/$projectId': typeof ShellProjectsProjectIdRoute
'/sessions/$sessionId': typeof ShellSessionsSessionIdRoute
'/projects/$projectId/settings': typeof ShellProjectsProjectIdSettingsRoute
'/projects/$projectId/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute
"/prs": typeof ShellPrsRoute;
"/": typeof ShellIndexRoute;
"/projects/$projectId": typeof ShellProjectsProjectIdRoute;
"/sessions/$sessionId": typeof ShellSessionsSessionIdRoute;
"/projects/$projectId/settings": typeof ShellProjectsProjectIdSettingsRoute;
"/projects/$projectId/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute;
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/_shell': typeof ShellRouteWithChildren
'/_shell/prs': typeof ShellPrsRoute
'/_shell/': typeof ShellIndexRoute
'/_shell/projects/$projectId': typeof ShellProjectsProjectIdRoute
'/_shell/sessions/$sessionId': typeof ShellSessionsSessionIdRoute
'/_shell/projects/$projectId_/settings': typeof ShellProjectsProjectIdSettingsRoute
'/_shell/projects/$projectId_/sessions/$sessionId': typeof ShellProjectsProjectIdSessionsSessionIdRoute
__root__: typeof rootRouteImport;
"/_shell": typeof ShellRouteWithChildren;
"/_shell/prs": typeof ShellPrsRoute;
"/_shell/": typeof ShellIndexRoute;
"/_shell/projects/$projectId": typeof ShellProjectsProjectIdRoute;
"/_shell/sessions/$sessionId": typeof ShellSessionsSessionIdRoute;
"/_shell/projects/$projectId_/settings": typeof ShellProjectsProjectIdSettingsRoute;
"/_shell/projects/$projectId_/sessions/$sessionId": typeof ShellProjectsProjectIdSessionsSessionIdRoute;
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths:
| '/'
| '/prs'
| '/projects/$projectId'
| '/sessions/$sessionId'
| '/projects/$projectId/settings'
| '/projects/$projectId/sessions/$sessionId'
fileRoutesByTo: FileRoutesByTo
| "/"
| "/prs"
| "/projects/$projectId"
| "/sessions/$sessionId"
| "/projects/$projectId/settings"
| "/projects/$projectId/sessions/$sessionId";
fileRoutesByTo: FileRoutesByTo;
to:
| '/prs'
| '/'
| '/projects/$projectId'
| '/sessions/$sessionId'
| '/projects/$projectId/settings'
| '/projects/$projectId/sessions/$sessionId'
| "/prs"
| "/"
| "/projects/$projectId"
| "/sessions/$sessionId"
| "/projects/$projectId/settings"
| "/projects/$projectId/sessions/$sessionId";
id:
| '__root__'
| '/_shell'
| '/_shell/prs'
| '/_shell/'
| '/_shell/projects/$projectId'
| '/_shell/sessions/$sessionId'
| '/_shell/projects/$projectId_/settings'
| '/_shell/projects/$projectId_/sessions/$sessionId'
fileRoutesById: FileRoutesById
| "__root__"
| "/_shell"
| "/_shell/prs"
| "/_shell/"
| "/_shell/projects/$projectId"
| "/_shell/sessions/$sessionId"
| "/_shell/projects/$projectId_/settings"
| "/_shell/projects/$projectId_/sessions/$sessionId";
fileRoutesById: FileRoutesById;
}
export interface RootRouteChildren {
ShellRoute: typeof ShellRouteWithChildren
ShellRoute: typeof ShellRouteWithChildren;
}
declare module '@tanstack/react-router' {
declare module "@tanstack/react-router" {
interface FileRoutesByPath {
'/_shell': {
id: '/_shell'
path: ''
fullPath: '/'
preLoaderRoute: typeof ShellRouteImport
parentRoute: typeof rootRouteImport
}
'/_shell/': {
id: '/_shell/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof ShellIndexRouteImport
parentRoute: typeof ShellRoute
}
'/_shell/prs': {
id: '/_shell/prs'
path: '/prs'
fullPath: '/prs'
preLoaderRoute: typeof ShellPrsRouteImport
parentRoute: typeof ShellRoute
}
'/_shell/sessions/$sessionId': {
id: '/_shell/sessions/$sessionId'
path: '/sessions/$sessionId'
fullPath: '/sessions/$sessionId'
preLoaderRoute: typeof ShellSessionsSessionIdRouteImport
parentRoute: typeof ShellRoute
}
'/_shell/projects/$projectId': {
id: '/_shell/projects/$projectId'
path: '/projects/$projectId'
fullPath: '/projects/$projectId'
preLoaderRoute: typeof ShellProjectsProjectIdRouteImport
parentRoute: typeof ShellRoute
}
'/_shell/projects/$projectId_/settings': {
id: '/_shell/projects/$projectId_/settings'
path: '/projects/$projectId/settings'
fullPath: '/projects/$projectId/settings'
preLoaderRoute: typeof ShellProjectsProjectIdSettingsRouteImport
parentRoute: typeof ShellRoute
}
'/_shell/projects/$projectId_/sessions/$sessionId': {
id: '/_shell/projects/$projectId_/sessions/$sessionId'
path: '/projects/$projectId/sessions/$sessionId'
fullPath: '/projects/$projectId/sessions/$sessionId'
preLoaderRoute: typeof ShellProjectsProjectIdSessionsSessionIdRouteImport
parentRoute: typeof ShellRoute
}
"/_shell": {
id: "/_shell";
path: "";
fullPath: "/";
preLoaderRoute: typeof ShellRouteImport;
parentRoute: typeof rootRouteImport;
};
"/_shell/": {
id: "/_shell/";
path: "/";
fullPath: "/";
preLoaderRoute: typeof ShellIndexRouteImport;
parentRoute: typeof ShellRoute;
};
"/_shell/prs": {
id: "/_shell/prs";
path: "/prs";
fullPath: "/prs";
preLoaderRoute: typeof ShellPrsRouteImport;
parentRoute: typeof ShellRoute;
};
"/_shell/sessions/$sessionId": {
id: "/_shell/sessions/$sessionId";
path: "/sessions/$sessionId";
fullPath: "/sessions/$sessionId";
preLoaderRoute: typeof ShellSessionsSessionIdRouteImport;
parentRoute: typeof ShellRoute;
};
"/_shell/projects/$projectId": {
id: "/_shell/projects/$projectId";
path: "/projects/$projectId";
fullPath: "/projects/$projectId";
preLoaderRoute: typeof ShellProjectsProjectIdRouteImport;
parentRoute: typeof ShellRoute;
};
"/_shell/projects/$projectId_/settings": {
id: "/_shell/projects/$projectId_/settings";
path: "/projects/$projectId/settings";
fullPath: "/projects/$projectId/settings";
preLoaderRoute: typeof ShellProjectsProjectIdSettingsRouteImport;
parentRoute: typeof ShellRoute;
};
"/_shell/projects/$projectId_/sessions/$sessionId": {
id: "/_shell/projects/$projectId_/sessions/$sessionId";
path: "/projects/$projectId/sessions/$sessionId";
fullPath: "/projects/$projectId/sessions/$sessionId";
preLoaderRoute: typeof ShellProjectsProjectIdSessionsSessionIdRouteImport;
parentRoute: typeof ShellRoute;
};
}
}
interface ShellRouteChildren {
ShellPrsRoute: typeof ShellPrsRoute
ShellIndexRoute: typeof ShellIndexRoute
ShellProjectsProjectIdRoute: typeof ShellProjectsProjectIdRoute
ShellSessionsSessionIdRoute: typeof ShellSessionsSessionIdRoute
ShellProjectsProjectIdSettingsRoute: typeof ShellProjectsProjectIdSettingsRoute
ShellProjectsProjectIdSessionsSessionIdRoute: typeof ShellProjectsProjectIdSessionsSessionIdRoute
ShellPrsRoute: typeof ShellPrsRoute;
ShellIndexRoute: typeof ShellIndexRoute;
ShellProjectsProjectIdRoute: typeof ShellProjectsProjectIdRoute;
ShellSessionsSessionIdRoute: typeof ShellSessionsSessionIdRoute;
ShellProjectsProjectIdSettingsRoute: typeof ShellProjectsProjectIdSettingsRoute;
ShellProjectsProjectIdSessionsSessionIdRoute: typeof ShellProjectsProjectIdSessionsSessionIdRoute;
}
const ShellRouteChildren: ShellRouteChildren = {
@ -181,15 +179,12 @@ const ShellRouteChildren: ShellRouteChildren = {
ShellProjectsProjectIdRoute: ShellProjectsProjectIdRoute,
ShellSessionsSessionIdRoute: ShellSessionsSessionIdRoute,
ShellProjectsProjectIdSettingsRoute: ShellProjectsProjectIdSettingsRoute,
ShellProjectsProjectIdSessionsSessionIdRoute:
ShellProjectsProjectIdSessionsSessionIdRoute,
}
ShellProjectsProjectIdSessionsSessionIdRoute: ShellProjectsProjectIdSessionsSessionIdRoute,
};
const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren)
const ShellRouteWithChildren = ShellRoute._addFileChildren(ShellRouteChildren);
const rootRouteChildren: RootRouteChildren = {
ShellRoute: ShellRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
};
export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes<FileRouteTypes>();

View File

@ -1,31 +1,29 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
export default {
preset: 'ts-jest/presets/default',
testEnvironment: 'jsdom',
testMatch: ['**/src/**/*.test.ts', '**/src/**/*.test.tsx'],
preset: "ts-jest/presets/default",
testEnvironment: "jsdom",
testMatch: ["**/src/**/*.test.ts", "**/src/**/*.test.tsx"],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
'^react$': '<rootDir>/node_modules/react',
'^react-dom$': '<rootDir>/node_modules/react-dom',
"^(\\.{1,2}/.*)\\.js$": "$1",
"^react$": "<rootDir>/node_modules/react",
"^react-dom$": "<rootDir>/node_modules/react-dom",
},
transform: {
'^.+\\.tsx?$': [
'ts-jest',
"^.+\\.tsx?$": [
"ts-jest",
{
tsconfig: {
types: ['jest'],
jsx: 'react',
module: 'ESNext',
moduleResolution: 'bundler',
types: ["jest"],
jsx: "react",
module: "ESNext",
moduleResolution: "bundler",
allowSyntheticDefaultImports: true,
esModuleInterop: true,
},
},
],
},
transformIgnorePatterns: [
'node_modules/(?!(react|react-dom|@tanstack/react-query|@testing-library)/)',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.cjs'],
moduleDirectories: ['node_modules', '<rootDir>/node_modules'],
transformIgnorePatterns: ["node_modules/(?!(react|react-dom|@tanstack/react-query|@testing-library)/)"],
setupFilesAfterEnv: ["<rootDir>/jest.setup.cjs"],
moduleDirectories: ["node_modules", "<rootDir>/node_modules"],
};

View File

@ -1 +1 @@
module.exports = require('@testing-library/jest-dom');
module.exports = require("@testing-library/jest-dom");

View File

@ -1,8 +1,8 @@
// packages/shared/src/api/client.ts
import { getPlatformBridge } from '../lib/bridge';
import { getPlatformBridge } from "../lib/bridge";
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
body?: any;
headers?: Record<string, string>;
}
@ -11,10 +11,10 @@ export class ApiError extends Error {
constructor(
public status: number,
public statusText: string,
public body?: any
public body?: any,
) {
super(`API Error ${status}: ${statusText}`);
this.name = 'ApiError';
this.name = "ApiError";
}
}
@ -31,11 +31,11 @@ export class ApiClient {
private getDefaultHeaders(): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
"Content-Type": "application/json",
};
if (this.authToken) {
headers['Authorization'] = `Bearer ${this.authToken}`;
headers["Authorization"] = `Bearer ${this.authToken}`;
}
return headers;
@ -46,7 +46,7 @@ export class ApiClient {
const url = `${baseUrl}${path}`;
const response = await fetch(url, {
method: options.method || 'GET',
method: options.method || "GET",
headers: {
...this.getDefaultHeaders(),
...options.headers,
@ -68,23 +68,23 @@ export class ApiClient {
}
async get<T>(path: string, headers?: Record<string, string>): Promise<T> {
return this.request<T>(path, { method: 'GET', headers });
return this.request<T>(path, { method: "GET", headers });
}
async post<T>(path: string, body: any, headers?: Record<string, string>): Promise<T> {
return this.request<T>(path, { method: 'POST', body, headers });
return this.request<T>(path, { method: "POST", body, headers });
}
async put<T>(path: string, body: any, headers?: Record<string, string>): Promise<T> {
return this.request<T>(path, { method: 'PUT', body, headers });
return this.request<T>(path, { method: "PUT", body, headers });
}
async delete<T>(path: string, headers?: Record<string, string>): Promise<T> {
return this.request<T>(path, { method: 'DELETE', headers });
return this.request<T>(path, { method: "DELETE", headers });
}
async patch<T>(path: string, body: any, headers?: Record<string, string>): Promise<T> {
return this.request<T>(path, { method: 'PATCH', body, headers });
return this.request<T>(path, { method: "PATCH", body, headers });
}
setAuthToken(token: string): void {

View File

@ -1,6 +1,6 @@
// packages/shared/src/api/paths.ts
// API path constants
export const API_PATHS = {
workspaces: '/api/v1/workspaces',
sessions: '/api/v1/sessions',
workspaces: "/api/v1/workspaces",
sessions: "/api/v1/sessions",
} as const;

View File

@ -1,6 +1,6 @@
// packages/shared/src/hooks/useDaemonStatus.ts
import { useEffect, useState } from 'react';
import { getPlatformBridge, DaemonStatus } from '../lib/bridge';
import { useEffect, useState } from "react";
import { getPlatformBridge, DaemonStatus } from "../lib/bridge";
export function useDaemonStatus() {
const [status, setStatus] = useState<DaemonStatus>({ running: false, port: 0 });

View File

@ -1,13 +1,13 @@
// packages/shared/src/hooks/useWorkspaceQuery.test.tsx
import React from 'react';
import { describe, it, expect, beforeEach } from '@jest/globals';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useWorkspaceQuery } from './useWorkspaceQuery';
import { ApiClient } from '../api/client';
import { setPlatformBridge } from '../lib/bridge';
import React from "react";
import { describe, it, expect, beforeEach } from "@jest/globals";
import { renderHook, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useWorkspaceQuery } from "./useWorkspaceQuery";
import { ApiClient } from "../api/client";
import { setPlatformBridge } from "../lib/bridge";
describe('useWorkspaceQuery', () => {
describe("useWorkspaceQuery", () => {
let queryClient: QueryClient;
beforeEach(() => {
@ -21,15 +21,15 @@ describe('useWorkspaceQuery', () => {
getDaemonStatus: async () => ({ running: true, port: 3001 }),
startDaemon: async () => {},
stopDaemon: async () => {},
getApiBaseUrl: () => 'http://test:3001',
getApiBaseUrl: () => "http://test:3001",
subscribeApiBaseUrl: () => () => {},
});
});
it('should fetch workspace data', async () => {
it("should fetch workspace data", async () => {
const mockWorkspaces = [
{ id: '1', name: 'Project A', sessions: [] },
{ id: '2', name: 'Project B', sessions: [] },
{ id: "1", name: "Project A", sessions: [] },
{ id: "2", name: "Project B", sessions: [] },
];
globalThis.fetch = globalThis.fetch || jest.fn();
@ -39,11 +39,7 @@ describe('useWorkspaceQuery', () => {
});
const { result } = renderHook(() => useWorkspaceQuery(), {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
),
wrapper: ({ children }) => <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>,
});
// Trigger the query and wait for it to complete

View File

@ -1,6 +1,6 @@
// packages/shared/src/hooks/useWorkspaceQuery.ts
import { useQuery } from '@tanstack/react-query';
import { ApiClient } from '../api/client';
import { useQuery } from "@tanstack/react-query";
import { ApiClient } from "../api/client";
export interface Session {
id: string;
@ -20,7 +20,7 @@ export interface WorkspaceResponse {
workspaces: Project[];
}
const WORKSPACE_QUERY_KEY = ['workspaces'] as const;
const WORKSPACE_QUERY_KEY = ["workspaces"] as const;
export function useWorkspaceQuery(client?: ApiClient) {
const apiClient = client || new ApiClient();
@ -28,7 +28,7 @@ export function useWorkspaceQuery(client?: ApiClient) {
const query = useQuery({
queryKey: WORKSPACE_QUERY_KEY,
queryFn: async () => {
const response = await apiClient.get<WorkspaceResponse>('/api/v1/workspaces');
const response = await apiClient.get<WorkspaceResponse>("/api/v1/workspaces");
return response;
},
enabled: false, // Manually refetch on SSE events

View File

@ -1,9 +1,9 @@
// packages/shared/src/index.ts
export * from './api/client';
export * from './api/paths';
export * from './api/types';
export * from './lib/bridge';
export * from './lib/event-transport';
export * from './lib/events-connection';
export * from './hooks/useWorkspaceQuery';
export * from './hooks/useDaemonStatus';
export * from "./api/client";
export * from "./api/paths";
export * from "./api/types";
export * from "./lib/bridge";
export * from "./lib/event-transport";
export * from "./lib/events-connection";
export * from "./hooks/useWorkspaceQuery";
export * from "./hooks/useDaemonStatus";

View File

@ -22,7 +22,7 @@ export function setPlatformBridge(bridge: PlatformBridge): void {
export function getPlatformBridge(): PlatformBridge {
if (!platformBridgeInstance) {
throw new Error('PlatformBridge not initialized. Call setPlatformBridge() first.');
throw new Error("PlatformBridge not initialized. Call setPlatformBridge() first.");
}
return platformBridgeInstance;
}

View File

@ -1,10 +1,10 @@
// packages/shared/src/lib/event-transport.test.ts
import { describe, it, expect, beforeEach } from '@jest/globals';
import { createEventTransport } from './event-transport';
import { QueryClient } from '@tanstack/react-query';
import { setPlatformBridge } from './bridge';
import { describe, it, expect, beforeEach } from "@jest/globals";
import { createEventTransport } from "./event-transport";
import { QueryClient } from "@tanstack/react-query";
import { setPlatformBridge } from "./bridge";
describe('EventTransport', () => {
describe("EventTransport", () => {
let queryClient: QueryClient;
beforeEach(() => {
@ -13,19 +13,19 @@ describe('EventTransport', () => {
getDaemonStatus: async () => ({ running: true, port: 3001 }),
startDaemon: async () => {},
stopDaemon: async () => {},
getApiBaseUrl: () => 'http://test:3001',
getApiBaseUrl: () => "http://test:3001",
subscribeApiBaseUrl: () => () => {},
});
});
it('should create transport with connect method', () => {
it("should create transport with connect method", () => {
const transport = createEventTransport(queryClient);
expect(typeof transport.connect).toBe('function');
expect(typeof transport.connect).toBe("function");
});
it('should return cleanup function from connect', () => {
it("should return cleanup function from connect", () => {
const transport = createEventTransport(queryClient);
const cleanup = transport.connect();
expect(typeof cleanup).toBe('function');
expect(typeof cleanup).toBe("function");
});
});

View File

@ -1,8 +1,8 @@
// packages/shared/src/lib/event-transport.ts
import type { QueryClient } from '@tanstack/react-query';
import { getPlatformBridge } from './bridge';
import { setEventsConnectionState } from './events-connection';
import { WORKSPACE_QUERY_KEY } from '../hooks/useWorkspaceQuery';
import type { QueryClient } from "@tanstack/react-query";
import { getPlatformBridge } from "./bridge";
import { setEventsConnectionState } from "./events-connection";
import { WORKSPACE_QUERY_KEY } from "../hooks/useWorkspaceQuery";
export type EventTransport = {
connect: () => () => void;
@ -13,14 +13,14 @@ const SSE_RETRY_MS = 5_000;
const EVENTSOURCE_CLOSED = 2;
const CDC_EVENT_TYPES = [
'session_created',
'session_updated',
'pr_created',
'pr_updated',
'pr_check_recorded',
'pr_session_changed',
'pr_review_thread_added',
'pr_review_thread_resolved',
"session_created",
"session_updated",
"pr_created",
"pr_updated",
"pr_check_recorded",
"pr_session_changed",
"pr_review_thread_added",
"pr_review_thread_resolved",
] as const;
export function createEventTransport(queryClient: QueryClient): EventTransport {
@ -49,20 +49,20 @@ export function createEventTransport(queryClient: QueryClient): EventTransport {
const bridge = getPlatformBridge();
const connectSource = () => {
if (typeof EventSource === 'undefined') return;
if (typeof EventSource === "undefined") return;
const baseUrl = bridge.getApiBaseUrl();
if (source && sourceBaseUrl === baseUrl && source.readyState !== EVENTSOURCE_CLOSED) return;
source?.close();
source = undefined;
sourceBaseUrl = baseUrl;
try {
source = new EventSource(`${baseUrl.replace(/\/+$/, '')}/api/v1/events`);
source = new EventSource(`${baseUrl.replace(/\/+$/, "")}/api/v1/events`);
source.onopen = () => {
setEventsConnectionState('connected');
setEventsConnectionState("connected");
refreshWorkspaces();
};
source.onerror = () => {
setEventsConnectionState('disconnected');
setEventsConnectionState("disconnected");
if (source?.readyState === EVENTSOURCE_CLOSED) scheduleRetry();
};
source.onmessage = refreshWorkspaces;
@ -82,7 +82,7 @@ export function createEventTransport(queryClient: QueryClient): EventTransport {
if (retryTimer) clearTimeout(retryTimer);
removeBaseUrlListener();
source?.close();
setEventsConnectionState('idle');
setEventsConnectionState("idle");
};
},
};

View File

@ -1,7 +1,7 @@
// packages/shared/src/lib/events-connection.ts
type ConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected';
type ConnectionState = "idle" | "connecting" | "connected" | "disconnected";
let connectionState: ConnectionState = 'idle';
let connectionState: ConnectionState = "idle";
const listeners: Set<(state: ConnectionState) => void> = new Set();
export function setEventsConnectionState(state: ConnectionState): void {
@ -13,9 +13,7 @@ export function getEventsConnectionState(): ConnectionState {
return connectionState;
}
export function subscribeEventsConnectionState(
callback: (state: ConnectionState) => void
): () => void {
export function subscribeEventsConnectionState(callback: (state: ConnectionState) => void): () => void {
listeners.add(callback);
callback(connectionState);
return () => listeners.delete(callback);