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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 24 additions & 120 deletions apps/server/src/agents/types.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,33 @@
export type AgentStatus =
| "creating"
| "running"
| "stopping"
| "stopped"
| "archiving"
| "error"
| "unknown";
/**
* Server-side agent types.
*
* The wire contract itself (`AgentRecord` and its member unions) lives in
* `@dispatch/shared` so the web client agrees on it without reaching into the
* server. It is re-exported here because the modules that already import these
* names from this path — and, through `agents/manager.ts`, the route layer —
* keep resolving unchanged.
*/

import type { AgentLatestEventType, AgentRecord } from "@dispatch/shared";

export type {
AgentGitContext,
AgentLatestEvent,
AgentLatestEventType,
AgentPin,
AgentRecord,
AgentRole,
AgentStatus,
ArchivePhase,
SetupPhase,
WorktreeCleanupMode,
} from "@dispatch/shared";

// Re-exported so the ~15 modules that already import AgentType from here keep
// working, while the member list itself lives in one place.
export type { AgentType } from "../shared/agent-types.js";
import type { AgentType } from "../shared/agent-types.js";

export type AgentRole = "standard" | "review" | "assisted_update";

export type AgentLatestEventType =
| "working"
| "blocked"
| "waiting_user"
| "done"
| "idle";

export type SetupPhase = "worktree" | "env" | "deps" | "session" | null;

export type ArchivePhase =
| "stopping"
| "worktree-check"
| "worktree-cleanup"
| "finalizing"
| null;

export type { PinShortcutVariant, PinType } from "../pins.js";
import type { PinShortcutVariant, PinType } from "../pins.js";

export type AgentPin = {
id?: string;
label: string;
value: string;
type: PinType;
/** Inline-markdown caption rendered under the pin. Any pin type. */
caption?: string;
/** Renders this pin under a shared heading with pins of the same group. */
group?: string;
/** Icon name for a shortcut pin's button. Shortcut pins only. */
icon?: string;
/** Button styling for a shortcut pin. Shortcut pins only. */
variant?: PinShortcutVariant;
/** When true, clicking a shortcut pin asks for confirmation first. */
confirm?: boolean;
/**
* When true, the shortcut renders non-interactive instead of being
* deleted — for an action that has become temporarily or permanently
* unavailable but is still worth showing (e.g. a launch pin once its
* builder is already running). `caption` doubles as the reason shown in
* place of its normal subtitle. Shortcut pins only.
*/
disabled?: boolean;
};

export type AgentLatestEvent = {
type: AgentLatestEventType;
message: string;
updatedAt: string;
metadata: Record<string, unknown> | null;
};

export type AgentGitContext = {
repoRoot: string;
branch: string;
worktreePath: string;
worktreeName: string;
isWorktree: boolean;
repoIconPath?: string | null;
};

export type WorktreeCleanupMode = "auto" | "keep" | "force";

// Canonical home is `shared/git/worktree-status.ts` — this re-export is
// here so existing importers (manager.ts's public surface, and through
Expand All @@ -91,53 +44,4 @@ export type AgentLatestEventInput = {
metadata?: Record<string, unknown>;
};

export type AgentRecord = {
id: string;
name: string;
type: AgentType;
role: AgentRole;
status: AgentStatus;
cwd: string;
worktreePath: string | null;
worktreeBranch: string | null;
tmuxSession: string | null;
simulatorUdid: string | null;
mediaDir: string | null;
agentArgs: string[];
model: string | null;
fullAccess: boolean;
setupPhase: SetupPhase;
archivePhase: ArchivePhase;
archiveCleanupMode: WorktreeCleanupMode | null;
lastError: string | null;
latestEvent: AgentLatestEvent | null;
pins: AgentPin[];
gitContext: AgentGitContext | null;
gitContextStale: boolean;
gitContextUpdatedAt: string | null;
persona: string | null;
parentAgentId: string | null;
/**
* The agent that ran dispatch_launch_agent / dispatch_launch_persona to
* create this one. Set for every agent-originated launch, including
* `child: false` launches whose `parentAgentId` is deliberately null.
*/
launchedByAgentId: string | null;
personaContext: string | null;
reviewAgentType: AgentType | null;
submittedReviewId: number | null;
baseBranch: string | null;
templateId: string | null;
autoReview: boolean;
/** Present when this agent was spawned for a job run. */
jobRun?: {
continuationEnabled: boolean;
iteration: number | null;
maxIterations: number | null;
} | null;
cliSessionId: string | null;
createdAt: string;
updatedAt: string;
};

export type AgentEventListener = (agent: AgentRecord) => void;
28 changes: 11 additions & 17 deletions apps/server/src/pins.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
const VALID_PIN_TYPES = [
"string",
"url",
"port",
"code",
"pr",
"filename",
"markdown",
"shortcut",
] as const;
const VALID_PIN_SHORTCUT_VARIANTS = [
"default",
"primary",
"destructive",
] as const;
import {
VALID_PIN_SHORTCUT_VARIANTS,
VALID_PIN_TYPES,
type PinShortcutVariant,
type PinType,
} from "@dispatch/shared";

// The tables live in @dispatch/shared because AgentPin is part of the agent
// wire contract; validation and sanitization stay here.
export type { PinShortcutVariant, PinType };

/**
* Icons a shortcut pin may use. Mirrored by the web icon map
* (apps/web/src/lib/pin-shortcut-icons.ts); a guard test asserts the two stay
Expand Down Expand Up @@ -118,8 +114,6 @@ function validateMarkdownPinValue(value: string): void {
}
}

export type PinType = (typeof VALID_PIN_TYPES)[number];
export type PinShortcutVariant = (typeof VALID_PIN_SHORTCUT_VARIANTS)[number];
export type PinShortcutIcon = (typeof VALID_PIN_SHORTCUT_ICONS)[number];

export function isPinType(value: string): value is PinType {
Expand Down
35 changes: 13 additions & 22 deletions apps/server/src/shared/agent-types.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
/**
* Agent-type tables and predicates.
* Agent-type predicates over the shared table.
*
* Single source of truth shared by the server (settings sanitization, job
* and persona agent validation) and the web client (agent pickers, settings
* toggles) — web imports this module directly across the workspace boundary
* (see apps/web/src/lib/agent-types.ts). Keep it dependency-free: no node
* The member lists themselves live in `@dispatch/shared` so the web client
* agrees on them without reaching into the server. They are re-exported here
* because ~15 server modules (and apps/web/src/lib/agent-types.ts) already
* import them from this path. Keep this module dependency-free: no node
* imports, no browser globals.
*/

export const AGENT_TYPES = [
"claude",
"codex",
"cursor",
"opencode",
"terminal",
] as const;
export type AgentType = (typeof AGENT_TYPES)[number];
import {
AGENT_TYPES,
CLI_AGENT_TYPES,
type AgentType,
type CliAgentType,
} from "@dispatch/shared";

// Agent types that run an AI CLI — eligible for jobs, review assignment, and
// persona launches. Terminal agents are excluded because they don't run a CLI.
export const CLI_AGENT_TYPES = [
"claude",
"codex",
"cursor",
"opencode",
] as const;
export type CliAgentType = (typeof CLI_AGENT_TYPES)[number];
export { AGENT_TYPES, CLI_AGENT_TYPES };
export type { AgentType, CliAgentType };

export function isCliAgentType(value: unknown): value is CliAgentType {
return (
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/app/agent-card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ describe("AgentCardStatus wiring", () => {
type: "blocked",
message: "Waiting on a merge conflict",
updatedAt: new Date().toISOString(),
metadata: {},
},
});
const { rerender } = renderCard({ agent });
Expand All @@ -435,6 +436,7 @@ describe("AgentCardStatus wiring", () => {
type: "working" as const,
message: "Running tests",
updatedAt: new Date().toISOString(),
metadata: {},
};
const { rerender } = renderCard({
agent: makeAgent({ type: "terminal", latestEvent }),
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/app/child-agent-row.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const baseAgent: Agent = {
type: "working",
message: "Reviewing changed routes",
updatedAt: "2026-07-15T12:00:00.000Z",
metadata: {},
},
mediaDir: null,
persona: "security-review",
Expand Down Expand Up @@ -103,6 +104,7 @@ describe("ChildAgentRow", () => {
type: "done",
message: "Incorrect stale event",
updatedAt: "2026-07-15T12:00:00.000Z",
metadata: {},
},
});

Expand Down
Loading
Loading