176 lines
6.7 KiB
TypeScript
176 lines
6.7 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
export const TaskStatus = z.enum(['open', 'in_progress', 'review', 'done', 'cancelled']);
|
|
export const DecisionStatus = z.enum(['proposed', 'accepted', 'rejected', 'superseded']);
|
|
export const MemoryCategory = z.enum(['architecture', 'product', 'technical', 'implementation', 'lesson']);
|
|
export const Priority = z.enum(['low', 'medium', 'high', 'critical']);
|
|
export const Role = z.enum(['architect', 'implementer', 'reviewer', 'tester']);
|
|
export const DelegationMode = z.enum(['manual', 'suggest', 'auto']);
|
|
|
|
export const TaskSchema = z.object({
|
|
id: z.string().regex(/^TSK-\d{4}$/),
|
|
title: z.string().min(1),
|
|
description: z.string().default(''),
|
|
status: TaskStatus,
|
|
priority: Priority.default('medium'),
|
|
role: Role.optional(),
|
|
assignedTo: z.string().optional(),
|
|
claimedBy: z.string().optional(),
|
|
reviewer: z.string().optional(),
|
|
createdAt: z.string().datetime(),
|
|
updatedAt: z.string().datetime(),
|
|
dueDate: z.string().datetime().optional(),
|
|
tags: z.array(z.string()).default([]),
|
|
acceptanceCriteria: z.array(z.string()).default([]),
|
|
sourceHandoff: z.string().optional(),
|
|
// Optional completion metadata (set via `task done --tokens --duration --by`)
|
|
doneBy: z.string().optional(),
|
|
doneTokens: z.number().int().nonnegative().optional(),
|
|
doneDuration: z.number().int().nonnegative().optional(),
|
|
});
|
|
|
|
export const HandoffSchema = z.object({
|
|
id: z.string().regex(/^HOF-\d{4}$/),
|
|
fromRole: z.string().min(1),
|
|
toRole: z.string().min(1),
|
|
fromAgent: z.string().optional(),
|
|
toAgent: z.string().optional(),
|
|
taskId: z.string().optional(),
|
|
summary: z.string().min(1),
|
|
context: z.string().default(''),
|
|
decisions: z.array(z.string()).default([]),
|
|
openQuestions: z.array(z.string()).default([]),
|
|
nextSteps: z.array(z.string()).default([]),
|
|
createdAt: z.string().datetime(),
|
|
});
|
|
|
|
export const DecisionSchema = z.object({
|
|
id: z.string().regex(/^DEC-\d{4}$/),
|
|
title: z.string().min(1),
|
|
status: DecisionStatus.default('accepted'),
|
|
context: z.string().default(''),
|
|
decision: z.string().min(1),
|
|
consequences: z.array(z.string()).default([]),
|
|
alternatives: z.array(z.string()).default([]),
|
|
relatedDecisions: z.array(z.string()).default([]),
|
|
createdAt: z.string().datetime(),
|
|
updatedAt: z.string().datetime(),
|
|
});
|
|
|
|
export const MemorySchema = z.object({
|
|
id: z.string().min(1),
|
|
title: z.string().min(1),
|
|
category: MemoryCategory.default('technical'),
|
|
content: z.string().min(1),
|
|
tags: z.array(z.string()).default([]),
|
|
relatedTasks: z.array(z.string()).default([]),
|
|
relatedDecisions: z.array(z.string()).default([]),
|
|
createdAt: z.string().datetime(),
|
|
updatedAt: z.string().datetime(),
|
|
// Optional agent-supplied metadata (set via `memory add --tokens --duration --by`)
|
|
tokens: z.number().int().nonnegative().optional(),
|
|
duration: z.number().int().nonnegative().optional(),
|
|
by: z.string().optional(),
|
|
});
|
|
|
|
/**
|
|
* Direct message between agents (architect↔implementer, or agent↔agent),
|
|
* routed through the hub. Lightweight, non-task-bound — for questions,
|
|
* clarifications and pings the handoff/memory entities don't cover.
|
|
*/
|
|
export const MessageSchema = z.object({
|
|
id: z.string().min(1),
|
|
from: z.string().min(1),
|
|
to: z.string().min(1),
|
|
text: z.string().min(1),
|
|
taskId: z.string().optional(),
|
|
status: z.enum(['unread', 'read']).default('unread'),
|
|
createdAt: z.string().datetime(),
|
|
updatedAt: z.string().datetime(),
|
|
});
|
|
|
|
// Activity timeline item returned by GET /tasks/:id/activity
|
|
export const ActivityItemSchema = z.object({
|
|
at: z.string().datetime(),
|
|
kind: z.enum(['created', 'handoff', 'result', 'status']),
|
|
actor: z.string(),
|
|
summary: z.string(),
|
|
meta: z.record(z.unknown()).optional(),
|
|
});
|
|
|
|
export type ActivityItem = z.infer<typeof ActivityItemSchema>;
|
|
|
|
export const StatusSchema = z.object({
|
|
generatedAt: z.string().datetime(),
|
|
activeTasks: z.array(z.string()).default([]),
|
|
blockedTasks: z.array(z.string()).default([]),
|
|
recentDecisions: z.array(z.string()).default([]),
|
|
recentHandoffs: z.array(z.string()).default([]),
|
|
summary: z.string().default(''),
|
|
});
|
|
|
|
export const RoleConfigSchema = z.object({
|
|
preferredAgent: z.string().min(1),
|
|
description: z.string().optional(),
|
|
});
|
|
|
|
/**
|
|
* A named agent in the team roster. Lets the team view show agents (by name,
|
|
* like backyard / mo / zied) with a fixed role + the model behind them — instead
|
|
* of guessing role/identity from whatever tasks the agent happened to touch.
|
|
*/
|
|
export const AgentConfigSchema = z.object({
|
|
role: Role,
|
|
/** Display model, e.g. "Opus 4.8", "Sonnet 4.6", "Kimi K2", "GPT-5 Codex". */
|
|
model: z.string().optional(),
|
|
/** Provider/company for the logo: "anthropic" | "openai" | "moonshot" | … */
|
|
kind: z.string().optional(),
|
|
description: z.string().optional(),
|
|
/** Optional monthly cost budget in EUR — drives the budget bar on the board. */
|
|
budgetEur: z.number().nonnegative().optional(),
|
|
});
|
|
|
|
/**
|
|
* One node of the team org chart. Supports arbitrary depth via `parentId`, and
|
|
* decouples the DISPLAY (label/title/editable) from the linked roster agent, so
|
|
* org-only nodes (a human CEO, a Product Owner) can sit in the tree without a
|
|
* running agent. Live state (active/reviewing) is derived from `agent`.
|
|
*/
|
|
export const OrgNodeSchema = z.object({
|
|
id: z.string().min(1),
|
|
/** Editable display name. */
|
|
label: z.string().min(1),
|
|
/** Display role/title shown under the name (e.g. "CEO", "Product Owner").
|
|
* For linked agents it falls back to the agent's roster role. */
|
|
role: z.string().optional(),
|
|
/** One-line "what this node does" — shown behind the info button. */
|
|
title: z.string().optional(),
|
|
/** Linked roster agent name (for model/provider/logo + live task state). */
|
|
agent: z.string().optional(),
|
|
/** Parent node id; omit/undefined for the root. */
|
|
parentId: z.string().optional(),
|
|
/** Provider override for the icon when there is no linked agent. */
|
|
kind: z.string().optional(),
|
|
});
|
|
export type OrgNode = z.infer<typeof OrgNodeSchema>;
|
|
|
|
export const ConfigSchema = z.object({
|
|
version: z.literal('1'),
|
|
projectName: z.string().min(1),
|
|
delegationMode: DelegationMode.default('suggest'),
|
|
roles: z.record(z.string(), RoleConfigSchema),
|
|
/** Named team roster: agent name → role + model + provider. */
|
|
agents: z.record(z.string(), AgentConfigSchema).optional(),
|
|
/** Team org chart (arbitrary depth). When present, /team renders this tree. */
|
|
org: z.array(OrgNodeSchema).optional(),
|
|
serverUrl: z.string().url().optional(),
|
|
});
|
|
|
|
export type Task = z.infer<typeof TaskSchema>;
|
|
export type Handoff = z.infer<typeof HandoffSchema>;
|
|
export type Decision = z.infer<typeof DecisionSchema>;
|
|
export type Memory = z.infer<typeof MemorySchema>;
|
|
export type Message = z.infer<typeof MessageSchema>;
|
|
export type Status = z.infer<typeof StatusSchema>;
|
|
export type Config = z.infer<typeof ConfigSchema>;
|